import type { ExtensionAPI, ExtensionContext } from "@selesai/code"; import { StringEnum } from "@earendil-works/pi-ai"; import { randomUUID } from "crypto"; import { Type } from "typebox"; import { Text } from "@earendil-works/pi-tui"; import { IntercomClient } from "./broker/client.ts"; import { spawnBrokerIfNeeded } from "./broker/spawn.ts"; import { SessionListOverlay } from "./ui/session-list.ts"; import { ComposeOverlay, type ComposeResult } from "./ui/compose.ts"; import { InlineMessageComponent } from "./ui/inline-message.ts"; import { getAskTimeoutMs, loadConfig, type IntercomConfig } from "./config.ts"; import { EXTENSION_BUS_FEATURE } from "./types.ts"; import type { Attachment, BrokerMessage, Message, MessageControl, MessageReceiptStatus, SessionInfo, SessionRegistration } from "./types.ts"; import { INTERCOM_EXTENSION_REGISTER_EVENT, INTERCOM_EXTENSION_REGISTRY_READY_EVENT, type IntercomExtensionChannel, type IntercomExtensionEvent, type IntercomExtensionOwner, type IntercomExtensionRegistration, type IntercomExtensionState, } from "./extension-api.ts"; import { ReplyTracker } from "./reply-tracker.ts"; import { resolve as resolvePath } from "node:path"; import { sameCwd } from "./cwd.ts"; import { formatContextUsage } from "./format-context.ts"; const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom"; const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom"; const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery"; const INBOUND_FLUSH_DELAY_MS = 200; const INBOUND_IDLE_RETRY_MS = 500; const INBOUND_MESSAGE_DEDUPE_MAX = 1000; const INBOUND_MESSAGE_DEDUPE_RETENTION_MS = 60 * 60 * 1000; const DEFAULT_UNNAMED_SESSION_ALIAS_PREFIX = "subagent-chat"; const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "SELESAI_SUBAGENT_ORCHESTRATOR_TARGET"; const SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV = "SELESAI_SUBAGENT_ORCHESTRATOR_SESSION_ID"; const INTERCOM_SESSION_ID_ENV = "PI_INTERCOM_SESSION_ID"; const STABLE_INTERCOM_SESSION_ID_ENV = "PI_INTERCOM_STABLE_ID"; const NAME_POLL_MS_ENV = "PI_INTERCOM_NAME_POLL_MS"; const SUBAGENT_RUN_ID_ENV = "SELESAI_SUBAGENT_RUN_ID"; const SUBAGENT_CHILD_AGENT_ENV = "SELESAI_SUBAGENT_CHILD_AGENT"; const SUBAGENT_CHILD_INDEX_ENV = "SELESAI_SUBAGENT_CHILD_INDEX"; const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "SELESAI_SUBAGENT_INTERCOM_SESSION_NAME"; const SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV = "SELESAI_SUBAGENT_SUPERVISOR_CHANNEL_DIR"; interface ChildOrchestratorMetadata { orchestratorTarget: string; orchestratorSessionId?: string; runId: string; agent: string; index: string; sessionName?: string; } interface InboundMessageEntry { from: SessionInfo; message: Message; replyCommand?: string; bodyText: string; } type ContactSupervisorReason = "need_decision" | "progress_update" | "interview_request"; interface SupervisorInterviewQuestion extends Record { id: string; type: "single" | "multi" | "text" | "image" | "info"; question: string; options?: unknown[]; } interface SupervisorInterviewRequest extends Record { title?: string; description?: string; questions: SupervisorInterviewQuestion[]; } interface SupervisorInterviewReply { responses: Array<{ id: string; value: unknown }>; } function getErrorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } function toError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)); } function formatAttachments(attachments: Attachment[]): string { let text = ""; for (const att of attachments) { if (att.language) { text += `\n\n---\n๐Ÿ“Ž ${att.name}\n~~~${att.language}\n${att.content}\n~~~`; } else { text += `\n\n---\n๐Ÿ“Ž ${att.name}\n${att.content}`; } } return text; } function readChildOrchestratorMetadata(): ChildOrchestratorMetadata | null { const orchestratorTarget = process.env[SUBAGENT_ORCHESTRATOR_TARGET_ENV]?.trim(); const orchestratorSessionId = process.env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV]?.trim() || process.env[INTERCOM_SESSION_ID_ENV]?.trim(); const runId = process.env[SUBAGENT_RUN_ID_ENV]?.trim(); const agent = process.env[SUBAGENT_CHILD_AGENT_ENV]?.trim(); const index = process.env[SUBAGENT_CHILD_INDEX_ENV]?.trim(); if (!orchestratorTarget || !runId || !agent || !index) { return null; } const sessionName = process.env[SUBAGENT_INTERCOM_SESSION_NAME_ENV]?.trim(); return { orchestratorTarget, ...(orchestratorSessionId ? { orchestratorSessionId } : {}), runId, agent, index, ...(sessionName ? { sessionName } : {}), }; } function formatChildOrchestratorMessage(kind: "ask" | "update" | "interview", metadata: ChildOrchestratorMetadata, message: string): string { const heading = kind === "ask" ? "Subagent needs a supervisor decision." : kind === "interview" ? "Subagent requests a structured supervisor interview." : "Subagent progress update."; return [ heading, `Run: ${metadata.runId}`, `Agent: ${metadata.agent}`, `Child index: ${metadata.index}`, metadata.sessionName ? `Child intercom target: ${metadata.sessionName}` : undefined, "", message, ].filter((line): line is string => line !== undefined).join("\n"); } function validateSupervisorInterviewRequest(input: unknown): { ok: true; interview: SupervisorInterviewRequest } | { ok: false; error: string } { if (!input || typeof input !== "object" || Array.isArray(input)) { return { ok: false, error: "interview must be an object with a questions array" }; } const raw = input as Record; if (raw.title !== undefined && typeof raw.title !== "string") { return { ok: false, error: "interview.title must be a string when provided" }; } if (raw.description !== undefined && typeof raw.description !== "string") { return { ok: false, error: "interview.description must be a string when provided" }; } if (!Array.isArray(raw.questions) || raw.questions.length === 0) { return { ok: false, error: "interview.questions must be a non-empty array" }; } const validTypes = new Set(["single", "multi", "text", "image", "info"]); const ids = new Set(); const questions: SupervisorInterviewQuestion[] = []; for (let index = 0; index < raw.questions.length; index++) { const questionInput = raw.questions[index]; if (!questionInput || typeof questionInput !== "object" || Array.isArray(questionInput)) { return { ok: false, error: `interview.questions[${index}] must be an object` }; } const question = questionInput as Record; if (typeof question.id !== "string" || question.id.trim() === "") { return { ok: false, error: `interview.questions[${index}].id must be a non-empty string` }; } const id = question.id.trim(); if (ids.has(id)) { return { ok: false, error: `interview question id must be unique: ${id}` }; } ids.add(id); if (typeof question.type !== "string" || !validTypes.has(question.type)) { return { ok: false, error: `interview.questions[${index}].type must be one of: single, multi, text, image, info` }; } if (typeof question.question !== "string" || question.question.trim() === "") { return { ok: false, error: `interview.questions[${index}].question must be a non-empty string` }; } if (question.context !== undefined && typeof question.context !== "string") { return { ok: false, error: `interview.questions[${index}].context must be a string when provided` }; } let options: unknown[] | undefined; if (question.options !== undefined) { if (!Array.isArray(question.options)) { return { ok: false, error: `interview.questions[${index}].options must be an array when provided` }; } options = []; for (let optionIndex = 0; optionIndex < question.options.length; optionIndex++) { const option = question.options[optionIndex]; if (typeof option === "string") { const label = option.trim(); if (!label) { return { ok: false, error: `interview.questions[${index}].options[${optionIndex}] must not be empty` }; } options.push(label); } else if (!option || typeof option !== "object" || Array.isArray(option) || typeof (option as { label?: unknown }).label !== "string" || (option as { label: string }).label.trim() === "") { return { ok: false, error: `interview.questions[${index}].options[${optionIndex}] must be a non-empty string or an object with a non-empty label` }; } else { options.push({ ...option, label: (option as { label: string }).label.trim() }); } } } if ((question.type === "single" || question.type === "multi") && (!options || options.length === 0)) { return { ok: false, error: `interview.questions[${index}].options must be a non-empty array for ${question.type} questions` }; } if (question.type !== "single" && question.type !== "multi" && options) { return { ok: false, error: `interview.questions[${index}].options is only valid for single and multi questions` }; } questions.push({ ...question, id, type: question.type as SupervisorInterviewQuestion["type"], question: question.question.trim(), ...(options ? { options } : {}), }); } return { ok: true, interview: { ...raw, ...(typeof raw.title === "string" ? { title: raw.title.trim() } : {}), ...(typeof raw.description === "string" ? { description: raw.description.trim() } : {}), questions, }, }; } function interviewOptionLabel(option: unknown): string { return typeof option === "string" ? option : (option as { label: string }).label; } function interviewExampleValue(question: SupervisorInterviewQuestion): unknown { if (question.type === "multi") { return question.options?.slice(0, 2).map(interviewOptionLabel) ?? []; } if (question.type === "single") { return question.options?.[0] !== undefined ? interviewOptionLabel(question.options[0]) : "option label"; } if (question.type === "image") { return "image/file reference or description"; } return "answer text"; } function formatSupervisorInterviewRequest(interview: SupervisorInterviewRequest, message?: string): string { const lines: string[] = []; const title = interview.title?.trim(); if (title) lines.push(`Interview: ${title}`); const description = interview.description?.trim(); if (description) lines.push(description); const note = message?.trim(); if (note) lines.push(`Child note: ${note}`); if (lines.length > 0) lines.push(""); lines.push("Questions:"); interview.questions.forEach((question, index) => { lines.push(`${index + 1}. [${question.id}] (${question.type}) ${question.question}`); if (typeof question.context === "string" && question.context.trim()) { lines.push(` Context: ${question.context.trim()}`); } if (question.options?.length) { lines.push(" Options:"); for (const option of question.options) { lines.push(` - ${interviewOptionLabel(option)}`); } } }); const responseExample = { responses: interview.questions .filter((question) => question.type !== "info") .map((question) => ({ id: question.id, value: interviewExampleValue(question), })), }; lines.push( "", "Supervisor reply instructions:", "Reply with plain JSON or a fenced ```json block using this stable shape. Use the question ids exactly. Info questions are context-only and do not need responses. For single questions, value is one option label. For multi questions, value is an array of option labels. For text/image questions, value is a string unless the question asks otherwise.", "", "```json", JSON.stringify(responseExample, null, 2), "```", ); return lines.join("\n"); } function validateSupervisorInterviewReply(value: unknown, interview: SupervisorInterviewRequest): SupervisorInterviewReply { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new Error("reply JSON must be an object with a responses array"); } const responsesInput = (value as Record).responses; if (!Array.isArray(responsesInput)) { throw new Error("reply JSON must include a responses array"); } const questionById = new Map(interview.questions .filter((question) => question.type !== "info") .map((question) => [question.id, question])); const seenIds = new Set(); const responses: SupervisorInterviewReply["responses"] = []; for (let index = 0; index < responsesInput.length; index++) { const response = responsesInput[index]; if (!response || typeof response !== "object" || Array.isArray(response)) { throw new Error(`responses[${index}] must be an object`); } const raw = response as Record; if (typeof raw.id !== "string" || raw.id.trim() === "") { throw new Error(`responses[${index}].id must be a non-empty string`); } const id = raw.id.trim(); const question = questionById.get(id); if (!question) { throw new Error(`responses[${index}].id must match a non-info interview question id`); } if (seenIds.has(id)) { throw new Error(`responses[${index}].id is duplicated: ${id}`); } seenIds.add(id); if (!Object.hasOwn(raw, "value")) { throw new Error(`responses[${index}].value is required`); } const value = raw.value; if (question.type === "single") { if (typeof value !== "string") throw new Error(`responses[${index}].value must be a string for single questions`); const optionLabels = new Set(question.options?.map(interviewOptionLabel)); if (!optionLabels.has(value.trim())) throw new Error(`responses[${index}].value must match one of the question options`); responses.push({ id, value: value.trim() }); continue; } if (question.type === "multi") { if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) { throw new Error(`responses[${index}].value must be an array of strings for multi questions`); } const optionLabels = new Set(question.options?.map(interviewOptionLabel)); const selected = value.map((item) => item.trim()); const invalid = selected.find((item) => !optionLabels.has(item)); if (invalid) throw new Error(`responses[${index}].value contains an option that is not in the question options: ${invalid}`); responses.push({ id, value: selected }); continue; } if (typeof value !== "string") { throw new Error(`responses[${index}].value must be a string for ${question.type} questions`); } responses.push({ id, value }); } return { responses }; } function parseStructuredSupervisorReply(text: string, interview: SupervisorInterviewRequest): { value?: SupervisorInterviewReply; error?: string } | undefined { const fencedMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/i); const candidate = (fencedMatch?.[1] ?? text).trim(); if (!candidate.startsWith("{") && !candidate.startsWith("[")) { return undefined; } try { return { value: validateSupervisorInterviewReply(JSON.parse(candidate), interview) }; } catch (error) { return { error: getErrorMessage(error) }; } } function duplicateSessionNames(sessions: SessionInfo[]): Set { return new Set( sessions .map(s => s.name?.toLowerCase()) .filter((name): name is string => Boolean(name)) .filter((name, index, names) => names.indexOf(name) !== index) ); } function shortSessionId(sessionId: string): string { return sessionId.slice(0, 8); } function parseSubagentIntercomPayload(payload: unknown): { to: string; message: string; requestId?: string } | null { if (typeof payload !== "object" || payload === null) { return null; } const record = payload as Record; if (typeof record.to !== "string" || typeof record.message !== "string") { return null; } const requestId = typeof record.requestId === "string" ? record.requestId : undefined; return { to: record.to, message: record.message, ...(requestId ? { requestId } : {}) }; } function resolveIntercomPresenceName(sessionName: string | undefined, sessionId: string): string { const trimmedName = sessionName?.trim(); if (trimmedName) { return trimmedName; } const normalizedSessionId = sessionId.startsWith("session-") ? sessionId.slice("session-".length) : sessionId; return `${DEFAULT_UNNAMED_SESSION_ALIAS_PREFIX}-${normalizedSessionId.slice(0, 8)}`; } function buildPresenceIdentity(pi: ExtensionAPI, sessionId: string): { name: string } { return { name: resolveIntercomPresenceName(pi.getSessionName(), sessionId), }; } function resolveConfiguredIntercomSessionId(piSessionId: string, config: IntercomConfig): string { return process.env[STABLE_INTERCOM_SESSION_ID_ENV]?.trim() || config.stableId || piSessionId; } function formatIntercomContactSnippet(sessionId: string): string { return `Use pi-intercom: intercom({ action: "send", to: "${sessionId}", message: "..." })`; } function formatSessionLabel(session: SessionInfo, duplicates: Set): string { if (!session.name) { return session.id; } return duplicates.has(session.name.toLowerCase()) ? `${session.name} (${shortSessionId(session.id)})` : session.name; } function formatSessionListRow(session: SessionInfo, currentCwd: string, isSelf: boolean): string { const name = session.name || "Unnamed session"; const tags = [isSelf ? "self" : session.cwd === currentCwd ? "same cwd" : undefined, session.status] .filter((tag): tag is string => Boolean(tag)); const suffix = tags.length ? ` [${tags.join(", ")}]` : ""; return `โ€ข ${name} (${shortSessionId(session.id)}) โ€” ${session.cwd} (${session.model}${formatContextUsage(session)})${suffix}`; } function previewText(value: unknown, maxLength = 72): string | undefined { if (typeof value !== "string") { return undefined; } const normalized = value.replace(/\s+/g, " ").trim(); if (!normalized) { return undefined; } return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}โ€ฆ` : normalized; } function firstTextContent(result: { content?: Array<{ type: string; text?: string }> }): string { return result.content?.find((item) => item.type === "text" && typeof item.text === "string")?.text?.replace(/\*\*/g, "") ?? ""; } function formatMessageTimestamp(timestamp: number | undefined): string | undefined { return typeof timestamp === "number" && Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : undefined; } function formatInboundDeliveryMetadata(message: Message): string { const parts = [`id ${message.id}`]; if (typeof message.senderSequence === "number") parts.push(`seq ${message.senderSequence}`); if (message.supersedes) parts.push(`supersedes ${message.supersedes}`); if (message.retryOf) parts.push(`retry of ${message.retryOf}`); const sentAt = formatMessageTimestamp(message.timestamp); if (sentAt) parts.push(`sent ${sentAt}`); const brokerDeliveredAt = formatMessageTimestamp(message.brokerDeliveredAt); if (brokerDeliveredAt) parts.push(`broker delivered ${brokerDeliveredAt}`); const receiverReceivedAt = formatMessageTimestamp(message.receiverReceivedAt); if (receiverReceivedAt) parts.push(`receiver received ${receiverReceivedAt}`); const injectedAt = formatMessageTimestamp(message.injectedAt); if (injectedAt) parts.push(`injected ${injectedAt}`); return parts.join(" ยท "); } function getNamePollMs(): number { const configured = process.env[NAME_POLL_MS_ENV]; if (configured !== undefined) { const value = Number(configured); if (Number.isFinite(value) && value > 0) { return value; } } return 1000; } export default function piIntercomExtension(pi: ExtensionAPI) { let client: IntercomClient | null = null; const config: IntercomConfig = loadConfig(); const askTimeoutMs = getAskTimeoutMs(); const localExtensions = new Map(); let runtimeContext: ExtensionContext | null = null; let currentSessionId: string | null = null; let currentIntercomSessionId: string | null = null; let currentModel = "unknown"; let sessionStartedAt: number | null = null; let reconnectTimer: NodeJS.Timeout | null = null; let namePollTimer: NodeJS.Timeout | null = null; let lastPresenceName: string | null = null; const previousIntercomSessionId = process.env[INTERCOM_SESSION_ID_ENV]; let reconnectPromise: Promise | null = null; let reconnectPromiseGeneration: number | null = null; let startupConnectTimer: NodeJS.Timeout | null = null; let reconnectAttempt = 0; let shuttingDown = false; let disposed = true; let runtimeStarted = false; let runtimeGeneration = 0; let agentRunning = false; const activeTools = new Map(); const replyTracker = new ReplyTracker(); const pendingIdleMessages: InboundMessageEntry[] = []; const seenInboundMessages = new Map(); const latestOutboundReceipts = new Map(); function dismissIncomingAsk(messageId: string): void { replyTracker.dismissPendingAsk(messageId); const queuedIndex = pendingIdleMessages.findIndex((entry) => entry.message.id === messageId); if (queuedIndex >= 0) pendingIdleMessages.splice(queuedIndex, 1); } function expirePendingIdleMessages(detail: string): void { for (const entry of pendingIdleMessages) { emitMessageReceipt(entry.message.id, "expired", detail); } pendingIdleMessages.length = 0; } function hasSeenInboundMessage(from: SessionInfo, message: Message, now = Date.now()): boolean { for (const [key, seenAt] of seenInboundMessages) { if (now - seenAt > INBOUND_MESSAGE_DEDUPE_RETENTION_MS) { seenInboundMessages.delete(key); } } const key = `${from.id}\0${message.id}`; if (seenInboundMessages.has(key)) { return true; } seenInboundMessages.set(key, now); while (seenInboundMessages.size > INBOUND_MESSAGE_DEDUPE_MAX) { const oldestKey = seenInboundMessages.keys().next().value; if (typeof oldestKey !== "string") break; seenInboundMessages.delete(oldestKey); } return false; } function emitMessageReceipt(messageId: string, status: MessageReceiptStatus, detail?: string): void { try { client?.sendMessageReceipt({ messageId, status, timestamp: Date.now(), ...(detail ? { detail } : {}), }); } catch { // Receipts are diagnostics; message handling should not fail when the sender disconnects. } } function handleMessageControl(control: MessageControl): void { const queuedIndex = pendingIdleMessages.findIndex((entry) => entry.message.id === control.messageId); if (control.action === "cancel") { if (queuedIndex >= 0) { pendingIdleMessages.splice(queuedIndex, 1); replyTracker.dismissPendingAsk(control.messageId); emitMessageReceipt(control.messageId, "cancelled", "cancelled before injection"); } else { emitMessageReceipt(control.messageId, "cancellation_requested", "message was not queued; it may already be injected or processed"); } return; } if (queuedIndex >= 0) { pendingIdleMessages.splice(queuedIndex, 1); } replyTracker.dismissPendingAsk(control.messageId); emitMessageReceipt(control.messageId, "superseded", control.supersededBy ? `superseded by ${control.supersededBy}` : undefined); } function latestDeliveryState(messageId: string | null, fallback: string): string { if (!messageId) { return fallback; } const receipt = latestOutboundReceipts.get(messageId); return receipt ? receipt.status : fallback; } let inboundFlushTimer: NodeJS.Timeout | null = null; let replyWaiter: { from: string; replyTo: string; resolve: (message: Message) => void; reject: (error: Error) => void; } | null = null; function waitForReply(from: string, replyTo: string, signal?: AbortSignal, cancelOnAbort?: () => void, getDeliveryState: () => string = () => "unknown"): Promise { if (replyWaiter) { return Promise.reject(new Error("Already waiting for a reply")); } if (signal?.aborted) { return Promise.reject(new Error("Cancelled")); } return new Promise((resolve, reject) => { const timeout = setTimeout(() => { const timeoutDescription = askTimeoutMs % 60000 === 0 ? `${askTimeoutMs / 60000} minutes` : `${askTimeoutMs}ms`; rejectReplyWaiter(new Error(`No reply from "${from}" for message ${replyTo} within ${timeoutDescription}. Last known delivery state: ${getDeliveryState()}. This waiter timeout is not cancellation; the delivered message may still be queued or actionable in the recipient session.`)); }, askTimeoutMs); const cleanup = () => { clearTimeout(timeout); signal?.removeEventListener("abort", onAbort); if (replyWaiter?.replyTo === replyTo) { replyWaiter = null; } }; const onAbort = () => { cancelOnAbort?.(); cleanup(); reject(new Error("Cancelled")); }; signal?.addEventListener("abort", onAbort, { once: true }); replyWaiter = { from, replyTo, resolve: (message) => { cleanup(); resolve(message); }, reject: (error) => { cleanup(); reject(error); }, }; }); } function rejectReplyWaiter(error: Error): void { replyWaiter?.reject(error); } function clearReconnectTimer(): void { if (!reconnectTimer) { return; } clearTimeout(reconnectTimer); reconnectTimer = null; } function clearStartupConnectTimer(): void { if (!startupConnectTimer) { return; } clearTimeout(startupConnectTimer); startupConnectTimer = null; } function clearNamePollTimer(): void { if (!namePollTimer) { return; } clearInterval(namePollTimer); namePollTimer = null; } function clearInboundFlushTimer(): void { if (!inboundFlushTimer) { return; } clearTimeout(inboundFlushTimer); inboundFlushTimer = null; } function getLiveContext(ctx: ExtensionContext | null = runtimeContext, generation = runtimeGeneration): ExtensionContext | null { if (disposed || shuttingDown || generation !== runtimeGeneration || !ctx) { return null; } try { if (currentSessionId && ctx.sessionManager.getSessionId() !== currentSessionId) { return null; } void ctx.hasUI; return ctx; } catch { // A context that throws while reading session/UI state is no longer usable. return null; } } function notifyIfLive(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error", generation = runtimeGeneration): void { const liveContext = getLiveContext(ctx, generation); if (!liveContext?.hasUI) { return; } try { liveContext.ui.notify(message, level); } catch { // The UI can disappear during session shutdown/reload while async overlay work is settling. } } function getReconnectDelayMs(): number { const backoffMs = [1000, 2000, 5000, 10000, 30000]; return backoffMs[Math.min(reconnectAttempt, backoffMs.length - 1)]!; } function currentStatus(): string { const activeToolName = activeTools.values().next().value; const lifecycleStatus = activeToolName ? `tool:${activeToolName}` : agentRunning ? "thinking" : "idle"; return config.status ? `${lifecycleStatus} ยท ${config.status}` : lifecycleStatus; } function emitLocalExtensionEvent(namespace: string, event: IntercomExtensionEvent): void { try { localExtensions.get(namespace)?.registration.onEvent(event); } catch { // One local extension must not break intercom or other extension channels. } } function createExtensionChannel(namespace: string): IntercomExtensionChannel { return { namespace, snapshot() { const extension = localExtensions.get(namespace); return { connected: Boolean(client?.isConnected()), supported: Boolean(client?.supportsFeature(EXTENSION_BUS_FEATURE)), ...(extension?.owner ? { owner: extension.owner } : {}), ...(extension?.state ? { state: extension.state } : {}), }; }, publish(payload, options = {}) { const activeClient = client; if (!activeClient?.isConnected()) throw new Error("Intercom is not connected"); const extension = localExtensions.get(namespace); const ownerOnly = options.ownerOnly ?? false; const ownerEpoch = ownerOnly ? extension?.owner?.epoch : undefined; if (ownerOnly && !ownerEpoch) throw new Error(`No owner is available for ${namespace}`); activeClient.sendExtensionMessage({ type: "extension_publish", namespace, audience: options.audience ?? "owner", ...(ownerOnly ? { ownerOnly: true, ownerEpoch } : {}), payload, }); }, commitState(payload, expectedRevision) { const activeClient = client; if (!activeClient?.isConnected()) throw new Error("Intercom is not connected"); const extension = localExtensions.get(namespace); const ownerEpoch = extension?.owner?.epoch; if (!ownerEpoch || extension.owner?.sessionId !== activeClient.sessionId) { throw new Error(`Current session is not the owner of ${namespace}`); } activeClient.sendExtensionMessage({ type: "extension_state_commit", namespace, ownerEpoch, expectedRevision: expectedRevision ?? extension.state?.revision ?? 0, payload, }); }, async listSessions() { const activeClient = client; if (!activeClient?.isConnected()) throw new Error("Intercom is not connected"); return activeClient.listSessions(); }, }; } function currentExtensionCapabilities() { return [...localExtensions.values()].map(({ registration }) => ({ namespace: registration.namespace, ownerEligible: registration.ownerEligible, })); } function registerLocalExtension(registration: IntercomExtensionRegistration): void { if (!/^[a-z0-9][a-z0-9._/-]{0,63}$/.test(registration.namespace)) { throw new Error(`Invalid intercom extension namespace: ${registration.namespace}`); } if (localExtensions.has(registration.namespace)) { throw new Error(`Intercom extension namespace already registered: ${registration.namespace}`); } const channel = createExtensionChannel(registration.namespace); localExtensions.set(registration.namespace, { registration, channel }); const activeClient = client; const connected = Boolean(activeClient?.isConnected()); const supported = Boolean(activeClient?.supportsFeature(EXTENSION_BUS_FEATURE)); // Write the capability update before exposing a connected channel. Socket // framing preserves this order if onReady publishes synchronously. if (activeClient && connected && supported) { activeClient.updateExtensionCapabilities(currentExtensionCapabilities()); } registration.onReady(channel); if (connected) { emitLocalExtensionEvent(registration.namespace, { type: "connection", connected: true, supported }); } } function buildRegistration(): SessionRegistration { const liveContext = getLiveContext(); if (!liveContext || !currentSessionId || sessionStartedAt === null) { throw new Error("Intercom runtime not initialized"); } const identity = buildPresenceIdentity(pi, currentIntercomSessionId ?? currentSessionId); return { name: identity.name, cwd: liveContext.cwd, model: currentModel, pid: process.pid, startedAt: sessionStartedAt, lastActivity: Date.now(), status: currentStatus(), ...(localExtensions.size > 0 ? { extensions: currentExtensionCapabilities(), } : {}), }; } // Snapshot the live session's context-window usage for presence. getContextUsage() // (stock SDK) reports { tokens, contextWindow, percent }, with tokens/percent null // right after a compaction (before the next assistant response). We emit null in // that case to CLEAR a peer's stale value rather than freeze the old percentage. // Feature-detected so an older runtime without getContextUsage() just omits it. function currentContextUsage(): { contextPct?: number | null; contextTokens?: number | null; contextWindow?: number } { const usage = getLiveContext()?.getContextUsage?.(); if (!usage) { return {}; } const result: { contextPct?: number | null; contextTokens?: number | null; contextWindow?: number } = { contextPct: typeof usage.percent === "number" && Number.isFinite(usage.percent) ? Math.round(usage.percent) : null, contextTokens: typeof usage.tokens === "number" && Number.isFinite(usage.tokens) ? usage.tokens : null, }; if (typeof usage.contextWindow === "number" && usage.contextWindow > 0) { result.contextWindow = usage.contextWindow; } return result; } function syncPresenceIdentity(sessionId: string): void { if (!client || !getLiveContext()) { return; } const identity = buildPresenceIdentity(pi, currentIntercomSessionId ?? sessionId); lastPresenceName = identity.name; client.updatePresence({ ...identity, status: currentStatus(), ...currentContextUsage() }); } function startNamePoll(): void { clearNamePollTimer(); lastPresenceName = currentSessionId ? buildPresenceIdentity(pi, currentIntercomSessionId ?? currentSessionId).name : null; namePollTimer = setInterval(() => { if (!currentSessionId || !getLiveContext()) { return; } const identity = buildPresenceIdentity(pi, currentIntercomSessionId ?? currentSessionId); if (identity.name !== lastPresenceName) { syncPresenceIdentity(currentSessionId); } }, getNamePollMs()); namePollTimer.unref?.(); } function publishIntercomSessionId(sessionId: string): void { process.env[INTERCOM_SESSION_ID_ENV] = sessionId; } function restoreIntercomSessionId(): void { if (previousIntercomSessionId === undefined) { delete process.env[INTERCOM_SESSION_ID_ENV]; return; } process.env[INTERCOM_SESSION_ID_ENV] = previousIntercomSessionId; } function syncPresenceStatus(): void { if (!client || !currentSessionId || !getLiveContext()) { return; } // context% rides the status heartbeat so peers see live usage at turn boundaries. client.updatePresence({ status: currentStatus(), ...currentContextUsage() }); } function currentSessionTargetMatches(to: string, resolvedTo?: string | null, activeClient?: IntercomClient): boolean { const targets = new Set(); const addTarget = (target: string | undefined | null) => { const trimmed = target?.trim(); if (trimmed) targets.add(trimmed.toLowerCase()); }; addTarget(currentSessionId); addTarget(currentIntercomSessionId); addTarget(activeClient?.sessionId); addTarget(pi.getSessionName()); if (currentSessionId) addTarget(buildPresenceIdentity(pi, currentIntercomSessionId ?? currentSessionId).name); return Boolean(resolvedTo && activeClient?.sessionId && resolvedTo === activeClient.sessionId) || targets.has(to.trim().toLowerCase()); } function shouldTriggerInboundMessage(entry: InboundMessageEntry, forceTrigger = false): boolean { if (forceTrigger) { return true; } if (config.inboundTrigger === "always") { return true; } if (config.inboundTrigger === "replies") { return Boolean(entry.message.replyTo); } return false; } function sendIncomingMessage(entry: InboundMessageEntry, delivery: "trigger" | "followUp", generation = runtimeGeneration, forceTrigger = false): void { if (runtimeStarted && !getLiveContext(runtimeContext, generation)) { return; } const injectedMessage = { ...entry.message, injectedAt: Date.now() }; emitMessageReceipt(injectedMessage.id, "injected"); const deliveredEntry = { ...entry, message: injectedMessage }; if (delivery !== "followUp") { replyTracker.queueTurnContext({ from: entry.from, message: injectedMessage, receivedAt: Date.now() }); } const senderDisplay = entry.from.name || entry.from.id.slice(0, 8); const replyInstruction = entry.replyCommand ? `\n\nTo reply, use the intercom tool: ${entry.replyCommand}` : ""; const deliveryMetadata = formatInboundDeliveryMetadata(injectedMessage); pi.sendMessage( { customType: "intercom_message", content: `**๐Ÿ“จ From ${senderDisplay}** (${entry.from.cwd})${replyInstruction}\n\n_${deliveryMetadata}_\n\n${entry.bodyText}`, display: true, details: deliveredEntry, }, delivery === "trigger" && shouldTriggerInboundMessage(entry, forceTrigger) ? { triggerTurn: true } : { deliverAs: "followUp" } ); } function scheduleInboundFlush(delayMs = INBOUND_FLUSH_DELAY_MS): void { if (!getLiveContext()) { return; } const scheduledGeneration = runtimeGeneration; clearInboundFlushTimer(); inboundFlushTimer = setTimeout(() => { inboundFlushTimer = null; flushIdleMessages(scheduledGeneration); }, delayMs); } function flushIdleMessages(generation = runtimeGeneration): void { if (pendingIdleMessages.length === 0) { return; } const ctx = getLiveContext(runtimeContext, generation); if (!ctx) { return; } let isIdle: boolean; try { isIdle = ctx.isIdle(); } catch { // Stale contexts are cleaned up by shutdown/reload; do not deliver queued messages through them. return; } if (!isIdle) { scheduleInboundFlush(INBOUND_IDLE_RETRY_MS); return; } const entries = pendingIdleMessages.splice(0, pendingIdleMessages.length); entries.forEach((entry, index) => { sendIncomingMessage(entry, index === 0 ? "trigger" : "followUp"); }); } function queueIdleMessage(entry: InboundMessageEntry): void { pendingIdleMessages.push(entry); emitMessageReceipt(entry.message.id, "queued"); scheduleInboundFlush(); } function handleIncomingMessage(ctx: ExtensionContext, from: SessionInfo, message: Message): void { const messageGeneration = runtimeGeneration; const liveContext = getLiveContext(ctx, messageGeneration); if (!liveContext) { return; } const receiverReceivedAt = Date.now(); if (hasSeenInboundMessage(from, message, receiverReceivedAt)) { emitMessageReceipt(message.id, "acknowledged", "duplicate message id suppressed"); return; } const receivedMessage = { ...message, receiverReceivedAt }; emitMessageReceipt(receivedMessage.id, "receiver_received"); if (replyWaiter) { const senderTarget = from.name || from.id; const fromMatches = senderTarget.toLowerCase() === replyWaiter.from.toLowerCase() || from.id === replyWaiter.from; const replyMatches = receivedMessage.replyTo === replyWaiter.replyTo; if (fromMatches && replyMatches) { emitMessageReceipt(receivedMessage.id, "acknowledged", "matched reply waiter"); replyWaiter.resolve(receivedMessage); return; } } const attachmentText = receivedMessage.content.attachments?.length ? formatAttachments(receivedMessage.content.attachments) : ""; const bodyText = `${receivedMessage.content.text}${attachmentText}`; const replyCommand = config.replyHint && receivedMessage.expectsReply ? `intercom({ action: "reply", message: "..." })` : undefined; replyTracker.recordIncomingMessage(from, receivedMessage, receiverReceivedAt); emitMessageReceipt(receivedMessage.id, "acknowledged", "accepted by receiver"); const entry = { from, message: receivedMessage, replyCommand, bodyText }; void (async () => { const activeContext = getLiveContext(liveContext, messageGeneration); if (!activeContext) { return; } if (!activeContext.isIdle()) { if (!activeContext.hasUI) { const activeClient = client; if (!message.replyTo && activeClient?.isConnected()) { try { const result = await activeClient.send(from.id, { text: "This agent is running in non-interactive mode and cannot respond to intercom messages while it is working. It will continue its current task and exit when done.", replyTo: message.id, }); if (result.delivered && getLiveContext(liveContext, messageGeneration)) { dismissIncomingAsk(message.id); } } catch { // Best-effort reply; keep the busy non-interactive session running either way. } } return; } queueIdleMessage(entry); return; } if (getLiveContext(liveContext, messageGeneration)) { sendIncomingMessage(entry, "trigger", messageGeneration); } })(); } function attachClientHandlers(nextClient: IntercomClient): void { nextClient.onBrokerMessage((message: BrokerMessage) => { if (client !== nextClient) return; switch (message.type) { case "registered": { const supported = message.features?.includes(EXTENSION_BUS_FEATURE) ?? false; if (supported && localExtensions.size > 0) { nextClient.updateExtensionCapabilities(currentExtensionCapabilities()); } for (const namespace of localExtensions.keys()) { emitLocalExtensionEvent(namespace, { type: "connection", connected: true, supported }); } break; } case "extension_owner": { const extension = localExtensions.get(message.namespace); if (!extension) break; extension.owner = message.ownerId && message.ownerEpoch ? { sessionId: message.ownerId, epoch: message.ownerEpoch } : undefined; emitLocalExtensionEvent(message.namespace, { type: "owner", ...(extension.owner ? { owner: extension.owner } : {}) }); break; } case "extension_message": { const extension = localExtensions.get(message.namespace); if (!extension) break; emitLocalExtensionEvent(message.namespace, { type: "message", fromSessionId: message.fromSessionId, ...(message.ownerId && message.ownerEpoch ? { owner: { sessionId: message.ownerId, epoch: message.ownerEpoch } } : {}), payload: message.payload, }); break; } case "extension_state": { const extension = localExtensions.get(message.namespace); if (!extension) break; extension.state = { revision: message.revision, payload: message.payload }; emitLocalExtensionEvent(message.namespace, { type: "state", state: extension.state }); break; } case "extension_state_result": emitLocalExtensionEvent(message.namespace, { type: "state_result", committed: message.committed, revision: message.revision, ...(message.reason ? { reason: message.reason } : {}), }); break; case "message_receipt": latestOutboundReceipts.set(message.receipt.messageId, { status: message.receipt.status, timestamp: message.receipt.timestamp, ...(message.receipt.detail ? { detail: message.receipt.detail } : {}), }); break; case "message_control": handleMessageControl(message.control); break; case "session_joined": for (const namespace of localExtensions.keys()) { emitLocalExtensionEvent(namespace, { type: "session_joined", session: message.session }); } break; case "session_left": for (const namespace of localExtensions.keys()) { emitLocalExtensionEvent(namespace, { type: "session_left", sessionId: message.sessionId }); } break; case "presence_update": for (const namespace of localExtensions.keys()) { emitLocalExtensionEvent(namespace, { type: "presence_update", session: message.session }); } break; } }); nextClient.on("message", (from, message) => { const liveContext = getLiveContext(); if (client !== nextClient || !liveContext) { return; } handleIncomingMessage(liveContext, from, message); }); nextClient.on("disconnected", (error: Error) => { if (client !== nextClient) { return; } rejectReplyWaiter(new Error(`Disconnected while waiting for reply: ${error.message}`, { cause: error })); for (const [namespace, extension] of localExtensions) { extension.owner = undefined; emitLocalExtensionEvent(namespace, { type: "connection", connected: false, supported: false }); emitLocalExtensionEvent(namespace, { type: "owner" }); } client = null; if (!shuttingDown && !disposed) { clearReconnectTimer(); scheduleReconnect(); } }); nextClient.on("error", () => { // Keep broker/socket noise out of the TUI. Reconnect logic runs from the disconnect path. }); } function scheduleReconnect(): void { if (disposed || shuttingDown || reconnectTimer || reconnectPromise || !getLiveContext()) { return; } const scheduledGeneration = runtimeGeneration; reconnectTimer = setTimeout(() => { reconnectTimer = null; if (scheduledGeneration !== runtimeGeneration || !getLiveContext()) { return; } reconnectAttempt += 1; void ensureConnected("background").catch(() => { // ensureConnected("background") already queued the next retry. }); }, getReconnectDelayMs()); } async function ensureConnected(reason: "startup" | "background" | "tool" | "overlay"): Promise { if (!config.enabled) { throw new Error("Intercom disabled"); } if (disposed || shuttingDown) { throw new Error("Intercom shutting down"); } if (client && client.isConnected()) { return client; } const contextAtStart = getLiveContext(); const generationAtStart = runtimeGeneration; if (!contextAtStart || !currentSessionId || sessionStartedAt === null) { throw new Error("Intercom runtime not initialized"); } clearReconnectTimer(); if (reconnectPromise && reconnectPromiseGeneration === generationAtStart) { return reconnectPromise; } const nextReconnectPromise = (async () => { const nextClient = new IntercomClient(); client = nextClient; attachClientHandlers(nextClient); try { await spawnBrokerIfNeeded(config.brokerCommand, config.brokerArgs); await nextClient.connect(buildRegistration(), currentIntercomSessionId ?? currentSessionId); if (!getLiveContext(contextAtStart, generationAtStart)) { await nextClient.disconnect(); throw new Error("Intercom runtime no longer active"); } client = nextClient; reconnectAttempt = 0; return nextClient; } catch (error) { if (client === nextClient) { client = null; } if (reason === "background" && getLiveContext(contextAtStart, generationAtStart)) { scheduleReconnect(); } throw toError(error); } finally { if (reconnectPromise === nextReconnectPromise) { reconnectPromise = null; reconnectPromiseGeneration = null; } } })(); reconnectPromise = nextReconnectPromise; reconnectPromiseGeneration = generationAtStart; return nextReconnectPromise; } async function resolveSessionTarget(activeClient: IntercomClient, nameOrId: string): Promise { const sessions = await activeClient.listSessions(); const byId = sessions.find(s => s.id === nameOrId); if (byId) { return byId.id; } const lowerName = nameOrId.toLowerCase(); const byName = sessions.filter(s => s.name?.toLowerCase() === lowerName); if (byName.length > 1) { const ids = byName.map(s => shortSessionId(s.id)).join(", "); throw new Error(`Multiple sessions named "${nameOrId}" are connected. Address one by the id shown in parentheses by "list" (${ids}).`); } if (byName.length === 1) { return byName[0]!.id; } const byIdPrefix = sessions.filter(s => s.id.startsWith(nameOrId)); if (byIdPrefix.length === 1) { return byIdPrefix[0]!.id; } if (byIdPrefix.length > 1) { throw new Error(`Multiple sessions match ID prefix "${nameOrId}". Use a longer session ID prefix.`); } return null; } async function resolveSupervisorTarget(activeClient: IntercomClient, metadata: ChildOrchestratorMetadata): Promise { if (metadata.orchestratorSessionId) { const bySessionId = await resolveSessionTarget(activeClient, metadata.orchestratorSessionId); if (bySessionId) { return bySessionId; } } return await resolveSessionTarget(activeClient, metadata.orchestratorTarget) ?? metadata.orchestratorTarget; } function deliverLocalSubagentRelayMessage(sender: "subagent-control" | "subagent-result", status: string, messageText: string): void { const liveContext = getLiveContext(); const now = Date.now(); sendIncomingMessage({ from: { id: sender, name: sender, cwd: liveContext?.cwd ?? "", model: sender, pid: process.pid, startedAt: now, lastActivity: now, status, }, message: { id: randomUUID(), timestamp: now, content: { text: messageText }, }, bodyText: messageText, }, "trigger", runtimeGeneration, true); } function recordSubagentDeliveryError(entryType: string, to: string, message: string, error: unknown): void { pi.appendEntry(entryType, { to, message, error: getErrorMessage(error), timestamp: Date.now(), }); } function startSessionRuntime(ctx: ExtensionContext): void { const previousClient = client; shuttingDown = false; disposed = false; runtimeStarted = true; runtimeGeneration += 1; reconnectAttempt = 0; clearReconnectTimer(); clearStartupConnectTimer(); clearNamePollTimer(); clearInboundFlushTimer(); rejectReplyWaiter(new Error("Session replaced")); replyTracker.reset(); expirePendingIdleMessages("receiver session replaced before injection"); if (previousClient) { client = null; void previousClient.disconnect().catch(() => undefined); } runtimeContext = ctx; currentSessionId = ctx.sessionManager.getSessionId(); currentIntercomSessionId = resolveConfiguredIntercomSessionId(currentSessionId, config); publishIntercomSessionId(currentIntercomSessionId); currentModel = ctx.model?.id ?? "unknown"; sessionStartedAt = Date.now(); lastPresenceName = buildPresenceIdentity(pi, currentIntercomSessionId).name; agentRunning = false; activeTools.clear(); startNamePoll(); const startupGeneration = runtimeGeneration; startupConnectTimer = setTimeout(() => { startupConnectTimer = null; if (!getLiveContext(ctx, startupGeneration)) { return; } void ensureConnected("startup").catch(() => { if (!getLiveContext(ctx, startupGeneration)) { return; } client = null; scheduleReconnect(); }); }, 0); } function emitResultDelivery(requestId: string | undefined, delivered: boolean, error?: unknown): void { if (!requestId) return; pi.events.emit(SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT, { requestId, delivered, ...(error ? { error: getErrorMessage(error) } : {}), }); } function relaySubagentIntercomPayload(payload: unknown, options: { sender: "subagent-control" | "subagent-result"; status: string; errorEntryType: string; acknowledge?: boolean; }): void { const parsed = parseSubagentIntercomPayload(payload); if (!parsed) return; const relayGeneration = runtimeGeneration; void (async () => { const relayStillLive = () => !runtimeStarted || Boolean(getLiveContext(runtimeContext, relayGeneration)); if (!relayStillLive()) { return; } if (currentSessionTargetMatches(parsed.to)) { deliverLocalSubagentRelayMessage(options.sender, options.status, parsed.message); if (options.acknowledge) emitResultDelivery(parsed.requestId, true); return; } let activeClient: IntercomClient; let target: string; try { activeClient = await ensureConnected("background"); target = await resolveSessionTarget(activeClient, parsed.to) ?? parsed.to; } catch (error) { if (!relayStillLive()) return; recordSubagentDeliveryError(options.errorEntryType, parsed.to, parsed.message, error); if (options.acknowledge) emitResultDelivery(parsed.requestId, false, error); return; } if (!relayStillLive()) { return; } if (currentSessionTargetMatches(parsed.to, target, activeClient)) { deliverLocalSubagentRelayMessage(options.sender, options.status, parsed.message); if (options.acknowledge) emitResultDelivery(parsed.requestId, true); return; } try { const result = await activeClient.send(target, { text: parsed.message }); if (!relayStillLive()) return; if (!result.delivered) { const error = new Error(result.reason ?? "Session may not exist or has disconnected."); recordSubagentDeliveryError(options.errorEntryType, parsed.to, parsed.message, error); if (options.acknowledge) emitResultDelivery(parsed.requestId, false, error); return; } if (options.acknowledge) emitResultDelivery(parsed.requestId, true); } catch (error) { if (!relayStillLive()) return; recordSubagentDeliveryError(options.errorEntryType, parsed.to, parsed.message, error); if (options.acknowledge) emitResultDelivery(parsed.requestId, false, error); } })(); } const unsubscribeExtensionRegister = pi.events.on(INTERCOM_EXTENSION_REGISTER_EVENT, (payload) => { if (!payload || typeof payload !== "object") return; const registration = payload as Partial; if ( typeof registration.namespace !== "string" || typeof registration.ownerEligible !== "boolean" || typeof registration.onEvent !== "function" || typeof registration.onReady !== "function" ) { return; } registerLocalExtension(registration as IntercomExtensionRegistration); }); pi.events.emit(INTERCOM_EXTENSION_REGISTRY_READY_EVENT, { version: 1 }); const unsubscribeSubagentControlIntercom = pi.events.on(SUBAGENT_CONTROL_INTERCOM_EVENT, (payload) => { relaySubagentIntercomPayload(payload, { sender: "subagent-control", status: "needs_attention", errorEntryType: "intercom_control_error", }); }); const unsubscribeSubagentResultIntercom = pi.events.on(SUBAGENT_RESULT_INTERCOM_EVENT, (payload) => { relaySubagentIntercomPayload(payload, { sender: "subagent-result", status: "result", errorEntryType: "intercom_result_error", acknowledge: true, }); }); pi.on("session_start", (_event, ctx) => { if (!config.enabled) { return; } startSessionRuntime(ctx); }); pi.on("session_shutdown", async () => { unsubscribeExtensionRegister(); unsubscribeSubagentControlIntercom(); unsubscribeSubagentResultIntercom(); shuttingDown = true; disposed = true; runtimeGeneration += 1; clearStartupConnectTimer(); clearReconnectTimer(); clearNamePollTimer(); restoreIntercomSessionId(); rejectReplyWaiter(new Error("Session shutting down")); replyTracker.reset(); expirePendingIdleMessages("receiver session shut down before injection"); clearInboundFlushTimer(); agentRunning = false; activeTools.clear(); if (client) { await client.disconnect(); client = null; } runtimeContext = null; currentSessionId = null; currentIntercomSessionId = null; sessionStartedAt = null; }); pi.on("turn_end", () => { if (!getLiveContext()) { return; } replyTracker.endTurn(); scheduleInboundFlush(0); }); pi.on("agent_start", () => { if (!getLiveContext()) { return; } agentRunning = true; activeTools.clear(); syncPresenceStatus(); }); pi.on("tool_execution_start", (event) => { if (!getLiveContext()) { return; } activeTools.set(event.toolCallId, event.toolName); syncPresenceStatus(); }); pi.on("tool_execution_end", (event) => { if (!getLiveContext()) { return; } activeTools.delete(event.toolCallId); syncPresenceStatus(); }); pi.on("agent_end", () => { if (!getLiveContext()) { return; } agentRunning = false; activeTools.clear(); syncPresenceStatus(); scheduleInboundFlush(0); }); pi.on("turn_start", (_event, ctx) => { const sessionId = ctx.sessionManager.getSessionId(); if (!currentSessionId || sessionId !== currentSessionId) { if (!config.enabled) { return; } startSessionRuntime(ctx); replyTracker.beginTurn(); return; } if (!getLiveContext(ctx)) { return; } syncPresenceIdentity(sessionId); replyTracker.beginTurn(); }); pi.on("session_info_changed", (_event, ctx) => { if (!getLiveContext(ctx)) { return; } const sessionId = ctx.sessionManager.getSessionId(); currentSessionId = sessionId; syncPresenceIdentity(sessionId); }); pi.on("model_select", (event, ctx) => { if (!getLiveContext(ctx)) { return; } currentModel = event.model.id; if (client) { client.updatePresence({ ...buildPresenceIdentity(pi, currentIntercomSessionId ?? ctx.sessionManager.getSessionId()), model: event.model.id, status: currentStatus(), }); } }); pi.registerMessageRenderer("intercom_message", (message, options, theme) => { const details = message.details as { from: SessionInfo; message: Message; replyCommand?: string; bodyText?: string } | undefined; if (!details) return undefined; return new InlineMessageComponent(details.from, details.message, theme, details.replyCommand, details.bodyText, !options.expanded); }); pi.on("tool_result", (event) => { if (event.toolName !== "intercom" && event.toolName !== "contact_supervisor") { return; } if (!event.details || typeof event.details !== "object") { return; } const details = event.details as { error?: unknown; delivered?: unknown }; if (details.error === true || details.delivered === false) { return { isError: true }; } }); const childOrchestratorMetadata = readChildOrchestratorMetadata(); const nativeSupervisorChannelAvailable = Boolean(process.env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV]?.trim()); if (childOrchestratorMetadata && !nativeSupervisorChannelAvailable) { pi.registerTool({ name: "contact_supervisor", label: "Contact Supervisor", description: "Subagent-only tool for contacting the supervisor agent that delegated this task. Use need_decision when blocked, uncertain, needing approval, or facing a product/API/scope decision before continuing; this waits for the supervisor's reply. Use interview_request when multiple structured questions need supervisor answers; this also waits for a reply. Use progress_update only for meaningful progress or unexpected discoveries that change the plan; this does not wait for a reply. Do not use for routine completion handoffs.", promptSnippet: "Subagent-only: contact the supervisor for decisions, structured interviews, or meaningful plan-changing updates. Do not use for routine completion handoffs.", promptGuidelines: [ "Use contact_supervisor with reason='need_decision' when a subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision before continuing.", "Use contact_supervisor with reason='interview_request' when the child needs multiple structured answers from the supervisor in one blocking exchange.", "Use contact_supervisor with reason='progress_update' only for meaningful progress or unexpected discoveries that change the plan.", "Do not use contact_supervisor for routine completion handoffs; return the final subagent result normally.", ], parameters: Type.Object({ reason: StringEnum(["need_decision", "progress_update", "interview_request"] as const, { description: "Contact reason: 'need_decision' waits for a reply; 'interview_request' sends structured questions and waits for a reply; 'progress_update' sends a non-blocking update", }), message: Type.Optional(Type.String({ description: "Decision request, optional interview note, or meaningful progress update for the supervisor", })), interview: Type.Optional(Type.Object({ title: Type.Optional(Type.String()), description: Type.Optional(Type.String()), questions: Type.Array(Type.Object({ id: Type.String(), type: StringEnum(["single", "multi", "text", "image", "info"] as const, { description: "Question type: single, multi, text, image, or info", }), question: Type.String(), options: Type.Optional(Type.Array(Type.Any())), context: Type.Optional(Type.String()), })), }, { description: "Structured interview request for reason='interview_request'" })), }), async execute(_toolCallId, params, signal, _onUpdate, ctx) { const reason = params.reason as ContactSupervisorReason; if (reason !== "need_decision" && reason !== "progress_update" && reason !== "interview_request") { return { content: [{ type: "text", text: "Invalid reason. Use 'need_decision', 'interview_request', or 'progress_update'." }], details: { error: true }, }; } if ((reason === "need_decision" || reason === "progress_update") && typeof params.message !== "string") { return { content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }], details: { error: true }, }; } const interviewValidation = reason === "interview_request" ? validateSupervisorInterviewRequest(params.interview) : undefined; if (interviewValidation?.ok === false) { return { content: [{ type: "text", text: `Invalid interview request: ${interviewValidation.error}` }], details: { error: true }, }; } const supervisorInterview = interviewValidation?.ok === true ? interviewValidation.interview : undefined; let connectedClient: IntercomClient; try { connectedClient = await ensureConnected("tool"); } catch (error) { return { content: [{ type: "text", text: `Intercom not connected: ${getErrorMessage(error)}` }], details: { error: true }, }; } syncPresenceIdentity(ctx.sessionManager.getSessionId()); if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }], details: { error: true }, }; } const metadata = childOrchestratorMetadata; let sendTo: string; try { sendTo = await resolveSupervisorTarget(connectedClient, metadata); } catch (error) { return { content: [{ type: "text", text: `Failed to resolve supervisor target: ${getErrorMessage(error)}` }], details: { error: true }, }; } if (signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }], details: { error: true }, }; } if (sendTo === connectedClient.sessionId) { return { content: [{ type: "text", text: "Cannot message the current session" }], details: { error: true }, }; } if (reason === "progress_update") { const message = params.message as string; try { const result = await connectedClient.send(sendTo, { text: formatChildOrchestratorMessage("update", metadata, message), }); if (!result.delivered) { const errorText = result.reason ?? "Session may not exist or has disconnected."; return { content: [{ type: "text", text: `Message to "${metadata.orchestratorTarget}" was not delivered: ${errorText}` }], details: { messageId: result.id, delivered: false, reason: result.reason }, }; } pi.appendEntry("intercom_sent", { to: metadata.orchestratorTarget, message: { text: message, reason }, messageId: result.id, timestamp: Date.now(), subagent: { runId: metadata.runId, agent: metadata.agent, index: metadata.index }, }); return { content: [{ type: "text", text: `Progress update sent to supervisor ${metadata.orchestratorTarget}` }], details: { messageId: result.id, delivered: true }, }; } catch (error) { return { content: [{ type: "text", text: `Failed to send progress update: ${getErrorMessage(error)}` }], details: { error: true }, }; } } if (replyWaiter) { return { content: [{ type: "text", text: "Already waiting for a reply" }], details: { error: true }, }; } let replyPromise: Promise | null = null; let deliveryState = "created"; let questionId: string | null = null; try { questionId = randomUUID(); replyPromise = waitForReply(sendTo, questionId, signal, () => connectedClient.cancelAsk(questionId!), () => latestDeliveryState(questionId, deliveryState)); replyPromise.catch(() => undefined); if (signal?.aborted) { rejectReplyWaiter(new Error("Cancelled")); try { await replyPromise; } catch { // The waiter was intentionally rejected above; the tool result reports cancellation. } return { content: [{ type: "text", text: "Cancelled" }], details: { error: true }, }; } const requestText = reason === "interview_request" ? formatChildOrchestratorMessage("interview", metadata, formatSupervisorInterviewRequest(supervisorInterview!, typeof params.message === "string" ? params.message : undefined)) : formatChildOrchestratorMessage("ask", metadata, params.message as string); const sendResult = await connectedClient.send(sendTo, { messageId: questionId, text: requestText, expectsReply: true, }); deliveryState = sendResult.delivered ? "socket_delivered" : "delivery_failed"; if (!sendResult.delivered) { const errorText = sendResult.reason ?? "Session may not exist or has disconnected."; rejectReplyWaiter(new Error(`Message to "${metadata.orchestratorTarget}" was not delivered: ${errorText}`)); if (replyPromise) { try { await replyPromise; } catch { // The waiter was already rejected above. Keep the delivery failure as the only error here. } } return { content: [{ type: "text", text: `Message to "${metadata.orchestratorTarget}" was not delivered: ${errorText}` }], details: { error: true }, }; } pi.appendEntry("intercom_sent", { to: metadata.orchestratorTarget, message: { text: reason === "interview_request" ? requestText : params.message, reason, ...(reason === "interview_request" ? { interview: supervisorInterview } : {}), }, messageId: sendResult.id, timestamp: Date.now(), subagent: { runId: metadata.runId, agent: metadata.agent, index: metadata.index }, }); const replyMessage = await replyPromise; const replyText = replyMessage.content.text; const replyAttachments = replyMessage.content.attachments?.length ? formatAttachments(replyMessage.content.attachments) : ""; const structuredReply = reason === "interview_request" ? parseStructuredSupervisorReply(replyText, supervisorInterview!) : undefined; pi.appendEntry("intercom_received", { from: metadata.orchestratorTarget, message: { text: replyText, attachments: replyMessage.content.attachments }, messageId: replyMessage.id, timestamp: replyMessage.timestamp, subagent: { runId: metadata.runId, agent: metadata.agent, index: metadata.index }, }); return { content: [{ type: "text", text: `**Reply from supervisor:**\n${replyText}${replyAttachments}` }], details: structuredReply ? structuredReply.value !== undefined ? { structuredReply: structuredReply.value } : { structuredReplyParseError: structuredReply.error } : {}, }; } catch (error) { rejectReplyWaiter(toError(error)); if (replyPromise) { try { await replyPromise; } catch { // The waiter is cleanup-only on this path. The real failure is the one from the outer catch. } } return { content: [{ type: "text", text: `Failed: ${getErrorMessage(error)}` }], details: { error: true, ...(questionId ? { messageId: questionId, deliveryState: latestDeliveryState(questionId, deliveryState) } : {}) }, }; } }, renderCall(args, theme) { const reason = typeof args.reason === "string" ? args.reason : "contact"; const messagePreview = previewText(args.message, 96); const interview = args.interview && typeof args.interview === "object" ? args.interview as { title?: unknown } : undefined; let text = theme.fg("toolTitle", theme.bold("contact_supervisor ")); text += theme.fg(reason === "need_decision" ? "warning" : reason === "progress_update" ? "muted" : "accent", reason); if (typeof interview?.title === "string" && interview.title.trim()) { text += " " + theme.fg("accent", interview.title.trim()); } if (messagePreview) { text += "\n " + theme.fg("dim", messagePreview); } return new Text(text, 0, 0); }, renderResult(result, { isPartial }, theme, context) { if (isPartial) { return new Text(theme.fg("warning", "Waiting for supervisor..."), 0, 0); } const details = result.details as { delivered?: boolean; error?: boolean; messageId?: string; reason?: string; structuredReplyParseError?: string } | undefined; const textContent = firstTextContent(result); const failed = Boolean(context.isError || details?.error === true || details?.delivered === false); const parseWarning = typeof details?.structuredReplyParseError === "string"; let text = failed ? theme.fg("error", "โœ— ") : parseWarning ? theme.fg("warning", "โš  ") : theme.fg("success", "โœ“ "); text += theme.fg(failed ? "error" : "text", textContent); if (parseWarning) { text += "\n" + theme.fg("warning", `Structured reply parse issue: ${details.structuredReplyParseError}`); } return new Text(text, 0, 0); }, } as any); } pi.registerTool({ name: "intercom", label: "Intercom", description: `Send a message to another Selesai session running on this machine. Use this to communicate findings, request help, or coordinate work with other sessions. Target a session by name, full session ID, or the short id shown in parentheses by "list" (a leading prefix of the ID is enough). Prefer the short id when two sessions share a name. Usage: intercom({ action: "list" }) โ†’ List active sessions intercom({ action: "list-cwd" }) โ†’ List sessions in the current working directory intercom({ action: "list-cwd", cwd: "/path" }) โ†’ List sessions in a specific directory intercom({ action: "send", to: "name-or-id", message: "..." }) โ†’ Send message intercom({ action: "ask", to: "name-or-id", message: "..." }) โ†’ Ask and wait for reply intercom({ action: "cancel", messageId: "..." }) โ†’ Request cancellation of a sent message intercom({ action: "reply", message: "..." }) โ†’ Reply to the active/single pending ask intercom({ action: "pending" }) โ†’ List unresolved inbound asks intercom({ action: "status" }) โ†’ Show connection status`, promptSnippet: "Use to coordinate with other local Selesai sessions: list peers, send updates, ask for help, or check intercom connectivity.", parameters: Type.Object({ action: StringEnum(["list", "list-cwd", "send", "ask", "reply", "pending", "status", "cancel"] as const, { description: "Action: 'list', 'list-cwd', 'send', 'ask', 'reply', 'pending', 'status', or 'cancel'", }), to: Type.Optional(Type.String({ description: "Target session: name, full session ID, or the short id shown in parentheses by 'list' (a leading ID prefix resolves). For 'send', 'ask', or disambiguating 'reply'.", })), message: Type.Optional(Type.String({ description: "Message to send (for 'send', 'ask', or 'reply' action)", })), attachments: Type.Optional(Type.Array(Type.Object({ type: StringEnum(["file", "snippet", "context"] as const), name: Type.String(), content: Type.String(), language: Type.Optional(Type.String()), }))), replyTo: Type.Optional(Type.String({ description: "Message ID to reply to (for threading or responding to an 'ask')", })), messageId: Type.Optional(Type.String({ description: "Message ID for actions that operate on an existing message, such as 'cancel'.", })), supersedes: Type.Optional(Type.String({ description: "Previous message ID this send/ask explicitly supersedes. Only works for the same sender and receiver.", })), retryOf: Type.Optional(Type.String({ description: "Previous message ID this send/ask is a user-authored retry of. Retries always send a new message ID.", })), cwd: Type.Optional(Type.String({ description: "Working directory filter for 'list-cwd' (absolute, or relative to the current session's cwd; '.' means the current cwd). Defaults to the current session's cwd.", })), }), async execute(_toolCallId, params, _signal, _onUpdate, ctx) { let connectedClient: IntercomClient; try { connectedClient = await ensureConnected("tool"); } catch (error) { return { content: [{ type: "text", text: `Intercom not connected: ${getErrorMessage(error)}` }], details: { error: true }, }; } syncPresenceIdentity(ctx.sessionManager.getSessionId()); const { action, to, message, attachments, replyTo, messageId, supersedes, retryOf, cwd } = params; switch (action) { case "list": { try { const mySessionId = connectedClient.sessionId; const sessions = await connectedClient.listSessions(); const currentSession = sessions.find(s => s.id === mySessionId); const otherSessions = sessions.filter(s => s.id !== mySessionId); if (!currentSession) { return { content: [{ type: "text", text: "Current session is missing from intercom session list." }], details: { error: true }, }; } const currentSection = `**Current session:**\n${formatSessionListRow(currentSession, currentSession.cwd, true)}`; const otherSection = otherSessions.length === 0 ? "**Other sessions:**\nNo other sessions connected." : `**Other sessions:**\n${otherSessions.map(s => formatSessionListRow(s, currentSession.cwd, false)).join("\n")}`; return { content: [{ type: "text", text: `${currentSection}\n\n${otherSection}` }], details: {}, }; } catch (error) { return { content: [{ type: "text", text: `Failed to list sessions: ${getErrorMessage(error)}` }], details: { error: true }, }; } } case "list-cwd": { try { const mySessionId = connectedClient.sessionId; const sessions = await connectedClient.listSessions(); const currentSession = sessions.find(s => s.id === mySessionId); if (!currentSession) { return { content: [{ type: "text", text: "Current session is missing from intercom session list." }], details: { error: true }, }; } // Default to the current session's cwd; an explicit `cwd` overrides // (relative paths resolved against it, "." meaning the current cwd). const filterCwd = cwd && cwd !== "." ? resolvePath(currentSession.cwd, cwd) : currentSession.cwd; const otherSessions = sessions.filter( s => s.id !== mySessionId && sameCwd(s.cwd, filterCwd), ); // Fail loud: filtering by a directory with no peers while the // session's OWN cwd has some otherwise reads as a misleading empty // result (common when a caller passes a guessed parent cwd). let emptyNote = "No other sessions in this directory."; if (otherSessions.length === 0 && !sameCwd(filterCwd, currentSession.cwd)) { const here = sessions.filter( s => s.id !== mySessionId && sameCwd(s.cwd, currentSession.cwd), ).length; if (here > 0) { emptyNote += ` Your session's cwd is ${currentSession.cwd} (${here} peer${here === 1 ? "" : "s"} there) โ€” call list-cwd without a cwd argument to list them.`; } } const currentSection = `**Current session:**\n${formatSessionListRow(currentSession, currentSession.cwd, true)}`; const otherSection = otherSessions.length === 0 ? `**Other sessions (cwd: ${filterCwd}):**\n${emptyNote}` : `**Other sessions (cwd: ${filterCwd}):**\n${otherSessions.map(s => formatSessionListRow(s, currentSession.cwd, false)).join("\n")}`; return { content: [{ type: "text", text: `${currentSection}\n\n${otherSection}` }], details: {}, }; } catch (error) { return { content: [{ type: "text", text: `Failed to list sessions: ${getErrorMessage(error)}` }], details: { error: true }, }; } } case "cancel": { if (!messageId) { return { content: [{ type: "text", text: "Missing 'messageId' parameter" }], details: { error: true }, }; } try { const result = await connectedClient.cancelMessage(messageId); if (!result.delivered) { const errorText = result.reason ?? "Message may not exist or may belong to another sender."; return { content: [{ type: "text", text: `Cancellation for ${messageId} was not delivered: ${errorText}` }], details: { messageId, delivered: false, reason: result.reason }, }; } return { content: [{ type: "text", text: `Cancellation requested for ${messageId}` }], details: { messageId, delivered: true }, }; } catch (error) { return { content: [{ type: "text", text: `Failed to cancel message: ${getErrorMessage(error)}` }], details: { error: true, messageId }, }; } } case "send": { if (!to || !message) { return { content: [{ type: "text", text: "Missing 'to' or 'message' parameter" }], details: { error: true }, }; } try { const sendTo = await resolveSessionTarget(connectedClient, to) ?? to; if (sendTo === connectedClient.sessionId) { return { content: [{ type: "text", text: "Cannot message the current session" }], details: { error: true }, }; } if (!replyTo && config.confirmSend && ctx.hasUI) { const attachmentText = attachments?.length ? formatAttachments(attachments) : ""; const confirmed = await ctx.ui.confirm( "Send Message", `Send to "${to}":\n\n${message}${attachmentText}`, ); if (!confirmed) { return { content: [{ type: "text", text: "Message cancelled by user" }], details: {}, }; } } const result = await connectedClient.send(sendTo, { text: message, attachments, replyTo, supersedes, retryOf, }); if (!result.delivered) { const errorText = result.reason ?? "Session may not exist or has disconnected."; return { content: [{ type: "text", text: `Message to "${to}" was not delivered: ${errorText}` }], details: { messageId: result.id, delivered: false, reason: result.reason }, }; } pi.appendEntry("intercom_sent", { to, message: { text: message, attachments, replyTo, supersedes, retryOf }, messageId: result.id, timestamp: Date.now(), }); if (replyTo) { dismissIncomingAsk(replyTo); } return { content: [{ type: "text", text: `Message sent to ${to}` }], details: { messageId: result.id, delivered: true }, }; } catch (error) { return { content: [{ type: "text", text: `Failed to send: ${getErrorMessage(error)}` }], details: { error: true }, }; } } case "ask": { if (!to || !message) { return { content: [{ type: "text", text: "Missing 'to' or 'message' parameter" }], details: { error: true }, }; } if (replyWaiter) { return { content: [{ type: "text", text: "Already waiting for a reply" }], details: { error: true }, }; } if (_signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }], details: { error: true }, }; } let replyPromise: Promise | null = null; let deliveryState = "created"; let questionId: string | null = null; try { const sendTo = await resolveSessionTarget(connectedClient, to) ?? to; if (_signal?.aborted) { return { content: [{ type: "text", text: "Cancelled" }], details: { error: true }, }; } if (sendTo === connectedClient.sessionId) { return { content: [{ type: "text", text: "Cannot message the current session" }], details: { error: true }, }; } if (replyWaiter) { return { content: [{ type: "text", text: "Already waiting for a reply" }], details: { error: true }, }; } questionId = randomUUID(); replyPromise = waitForReply(sendTo, questionId, _signal, () => connectedClient.cancelAsk(questionId!), () => latestDeliveryState(questionId, deliveryState)); replyPromise.catch(() => undefined); const sendResult = await connectedClient.send(sendTo, { messageId: questionId, text: message, attachments, replyTo, expectsReply: true, supersedes, retryOf, }); deliveryState = sendResult.delivered ? "socket_delivered" : "delivery_failed"; if (!sendResult.delivered) { const errorText = sendResult.reason ?? "Session may not exist or has disconnected."; rejectReplyWaiter(new Error(`Message to "${to}" was not delivered: ${errorText}`)); if (replyPromise) { try { await replyPromise; } catch { // The waiter was already rejected above. Keep the delivery failure as the only error here. } } return { content: [{ type: "text", text: `Message to "${to}" was not delivered: ${errorText}` }], details: { error: true }, }; } pi.appendEntry("intercom_sent", { to, message: { text: message, attachments, replyTo, supersedes, retryOf }, messageId: sendResult.id, timestamp: Date.now(), }); const replyMessage = await replyPromise; const replyText = replyMessage.content.text; const replyAttachments = replyMessage.content.attachments?.length ? formatAttachments(replyMessage.content.attachments) : ""; pi.appendEntry("intercom_received", { from: to, message: { text: replyText, attachments: replyMessage.content.attachments }, messageId: replyMessage.id, timestamp: replyMessage.timestamp, }); return { content: [{ type: "text", text: `**Reply from ${to}:**\n${replyText}${replyAttachments}` }], details: {}, }; } catch (error) { rejectReplyWaiter(toError(error)); if (replyPromise) { try { await replyPromise; } catch { // The waiter is cleanup-only on this path. The real failure is the one from the outer catch. } } return { content: [{ type: "text", text: `Failed: ${getErrorMessage(error)}` }], details: { error: true, ...(questionId ? { messageId: questionId, deliveryState: latestDeliveryState(questionId, deliveryState) } : {}) }, }; } } case "reply": { if (!message) { return { content: [{ type: "text", text: "Missing 'message' parameter" }], details: { error: true }, }; } try { const target = replyTracker.resolveReplyTarget({ to, replyTo }); if (target.from.id === connectedClient.sessionId) { return { content: [{ type: "text", text: "Cannot message the current session" }], details: { error: true }, }; } const result = await connectedClient.send(target.from.id, { text: message, replyTo: target.message.id, }); if (!result.delivered) { const errorText = result.reason ?? "Session may not exist or has disconnected."; if (result.reason === "Session not found") { dismissIncomingAsk(target.message.id); } return { content: [{ type: "text", text: `Reply to "${target.from.name || target.from.id}" was not delivered: ${errorText}` }], details: { messageId: result.id, delivered: false, reason: result.reason }, }; } dismissIncomingAsk(target.message.id); pi.appendEntry("intercom_sent", { to: target.from.name || target.from.id, message: { text: message, replyTo: target.message.id }, messageId: result.id, timestamp: Date.now(), }); return { content: [{ type: "text", text: `Reply sent to ${target.from.name || target.from.id}` }], details: { messageId: result.id, delivered: true, replyTo: target.message.id }, }; } catch (error) { return { content: [{ type: "text", text: `Failed to reply: ${getErrorMessage(error)}` }], details: { error: true }, }; } } case "pending": { const pendingAsks = replyTracker.listPending(); if (pendingAsks.length === 0) { return { content: [{ type: "text", text: "No unresolved inbound asks." }], details: {}, }; } const now = Date.now(); const lines = pendingAsks.map(({ from, message, receivedAt }) => { const preview = message.content.text.replace(/\s+/g, " ").slice(0, 80); const elapsedSeconds = Math.max(0, Math.floor((now - receivedAt) / 1000)); return `- ${from.name || from.id} ยท ${message.id} ยท ${elapsedSeconds}s ago ยท ${preview}`; }); return { content: [{ type: "text", text: `**Pending asks:**\n${lines.join("\n")}` }], details: {}, }; } case "status": { try { const mySessionId = connectedClient.sessionId; const sessions = await connectedClient.listSessions(); return { content: [{ type: "text", text: `**Intercom Status:**\nConnected: Yes\nSession ID: ${mySessionId}\nActive sessions: ${sessions.length}`, }], details: {}, }; } catch (error) { return { content: [{ type: "text", text: `Failed to get status: ${getErrorMessage(error)}` }], details: { error: true }, }; } } default: return { content: [{ type: "text", text: `Unknown action: ${action}` }], details: { error: true }, }; } }, renderCall(args, theme) { const action = typeof args.action === "string" ? args.action : "intercom"; const target = typeof args.to === "string" && args.to.trim() ? args.to.trim() : undefined; const messagePreview = previewText(args.message, 96); const attachmentCount = Array.isArray(args.attachments) ? args.attachments.length : 0; let text = theme.fg("toolTitle", theme.bold("intercom ")); text += theme.fg(action === "ask" ? "warning" : action === "reply" ? "success" : "accent", action); if (target) { text += " " + theme.fg("muted", "โ†’") + " " + theme.fg("accent", target); } if (attachmentCount > 0) { text += " " + theme.fg("dim", `(${attachmentCount} attachment${attachmentCount === 1 ? "" : "s"})`); } if (messagePreview) { text += "\n " + theme.fg("dim", messagePreview); } return new Text(text, 0, 0); }, renderResult(result, { isPartial }, theme, context) { if (isPartial) { return new Text(theme.fg("warning", "Intercom working..."), 0, 0); } const details = result.details as { delivered?: boolean; error?: boolean; messageId?: string; reason?: string } | undefined; const failed = Boolean(context.isError || details?.error === true || details?.delivered === false); let text = failed ? theme.fg("error", "โœ— ") : theme.fg("success", "โœ“ "); text += theme.fg(failed ? "error" : "text", firstTextContent(result)); if (details?.messageId && !context.expanded) { text += theme.fg("dim", ` (${details.messageId.slice(0, 8)})`); } if (details?.reason && context.expanded) { text += "\n" + theme.fg("dim", `Reason: ${details.reason}`); } return new Text(text, 0, 0); }, } as any); function insertIntoEditor(ctx: ExtensionContext, text: string): boolean { if (!ctx.hasUI) return false; const ui = ctx.ui as { getEditorText?: () => string; setEditorText?: (text: string) => void }; if (typeof ui.setEditorText !== "function") return false; const existing = typeof ui.getEditorText === "function" ? ui.getEditorText() : ""; ui.setEditorText(existing.trim() ? `${existing.trimEnd()}\n\n${text}` : text); return true; } async function insertIntercomId(ctx: ExtensionContext): Promise { const commandGeneration = runtimeGeneration; const liveContext = getLiveContext(ctx, commandGeneration); if (!liveContext) return; let contactClient: IntercomClient; try { contactClient = await ensureConnected("tool"); } catch (error) { notifyIfLive(ctx, `Intercom unavailable: ${getErrorMessage(error)}`, "error", commandGeneration); return; } const sessionId = contactClient.sessionId; if (!sessionId || !getLiveContext(liveContext, commandGeneration)) return; const snippet = formatIntercomContactSnippet(sessionId); if (insertIntoEditor(liveContext, snippet)) { notifyIfLive(liveContext, `Inserted intercom contact target: ${sessionId}`, "info", commandGeneration); return; } notifyIfLive(liveContext, `Intercom contact target: ${sessionId}`, "info", commandGeneration); } async function openIntercomOverlay(ctx: ExtensionContext): Promise { const overlayGeneration = runtimeGeneration; const liveContext = getLiveContext(ctx, overlayGeneration); if (!liveContext?.hasUI || (liveContext as ExtensionContext & { mode?: string }).mode !== "tui") return; let overlayClient: IntercomClient; try { overlayClient = await ensureConnected("overlay"); } catch (error) { notifyIfLive(ctx, `Intercom unavailable: ${getErrorMessage(error)}`, "error", overlayGeneration); return; } if (!getLiveContext(ctx, overlayGeneration)) return; syncPresenceIdentity(ctx.sessionManager.getSessionId()); let currentSession: SessionInfo; let sessions: SessionInfo[]; let duplicates: Set; try { const mySessionId = overlayClient.sessionId; const allSessions = await overlayClient.listSessions(); if (!getLiveContext(ctx, overlayGeneration)) return; const foundCurrentSession = allSessions.find(s => s.id === mySessionId); if (!foundCurrentSession) { notifyIfLive(ctx, "Current session is missing from intercom session list", "error", overlayGeneration); return; } currentSession = foundCurrentSession; duplicates = duplicateSessionNames(allSessions); sessions = allSessions.filter(s => s.id !== mySessionId); } catch (error) { notifyIfLive(ctx, `Failed to list sessions: ${getErrorMessage(error)}`, "error", overlayGeneration); return; } const selectedSession = await ctx.ui.custom( (_tui, theme, keybindings, done) => new SessionListOverlay(theme, keybindings, currentSession, sessions, done), { overlay: true, overlayOptions: { width: 88 } } ).catch(() => undefined); if (!selectedSession || !getLiveContext(ctx, overlayGeneration)) return; try { overlayClient = await ensureConnected("overlay"); } catch (error) { notifyIfLive(ctx, `Intercom unavailable: ${getErrorMessage(error)}`, "error", overlayGeneration); return; } if (!getLiveContext(ctx, overlayGeneration)) return; const targetLabel = formatSessionLabel(selectedSession, duplicates); const result = await ctx.ui.custom( (tui, theme, keybindings, done) => new ComposeOverlay(tui, theme, keybindings, selectedSession, targetLabel, overlayClient, done), { overlay: true, overlayOptions: { width: 72 } } ).catch(() => undefined); if (result?.sent && result.messageId && result.text && getLiveContext(ctx, overlayGeneration)) { pi.appendEntry("intercom_sent", { to: selectedSession.name || selectedSession.id, message: { text: result.text }, messageId: result.messageId, timestamp: Date.now(), }); notifyIfLive(ctx, `Message sent to ${targetLabel}`, "info", overlayGeneration); } } pi.registerCommand("intercom", { description: "Open session intercom overlay", handler: async (_args, ctx) => openIntercomOverlay(ctx), }); pi.registerCommand("intercom-id", { description: "Insert a stable pi-intercom handoff snippet for this session into the editor", handler: async (_args, ctx) => insertIntercomId(ctx), }); pi.registerShortcut("alt+m", { description: "Open session intercom", handler: async (ctx) => openIntercomOverlay(ctx), }); }