/** * subagent-mode.ts — subagent sessions are read-only for memory and skill * writes. All automatic write-related work (background review, correction * detection, session flushes, session indexing) is skipped as well. * * A child session is identified by the `` identity * tag that pi-subagents stamps into every child prompt * (permission-system-bridge.ts): the tag arrives via * `systemPromptOptions.customPrompt`. The tag is only trusted on that field — * never in the rendered system prompt, which a main session can legitimately * contain through project docs. * * Membership is tracked per session by its sessionManager identity: the same * process hosts the parent and its children concurrently, so a global boolean * would let a child's turn flip the parent's session into read-only mid-turn. */ import type { BeforeAgentStartEvent, ExtensionContext } from "@earendil-works/pi-coding-agent"; /** pi-subagents `` identity tag contract. */ const ACTIVE_AGENT_TAG = /]*>/i; const LEADING_ACTIVE_AGENT_TAG = /^\s*]*>/i; export const SUBAGENT_WRITE_DISABLED_MESSAGE = "Memory and skill writes are disabled in subagent sessions; include this in your final report so the delegating agent can decide."; const subagentSessionManagers = new WeakSet(); /** Same regex as the pi-subagents `` contract, applied only to customPrompt. */ export function isSubagentCustomPrompt(customPrompt: string | undefined | null): boolean { return typeof customPrompt === "string" && ACTIVE_AGENT_TAG.test(customPrompt); } /** * before_agent_start hook: authoritative detection via the event's * systemPromptOptions.customPrompt. */ export function markSubagentSession(event: BeforeAgentStartEvent, ctx: ExtensionContext): void { if (isSubagentCustomPrompt(event.systemPromptOptions?.customPrompt)) { subagentSessionManagers.add(ctx.sessionManager); } } /** * session_start runs before before_agent_start; detect tag-leading prompts * early so session_start writes (skill migration, root setup) can be skipped. * A main prompt cannot lead with the tag — without customPrompt the rendered * prompt always opens with the base preamble. */ export function markSubagentSessionFromPrompt(systemPrompt: string, ctx: ExtensionContext): void { if (LEADING_ACTIVE_AGENT_TAG.test(systemPrompt)) { subagentSessionManagers.add(ctx.sessionManager); } } export function isSubagentSession(ctx: unknown): boolean { const sm = (ctx as { sessionManager?: unknown } | undefined | null)?.sessionManager; return typeof sm === "object" && sm !== null && subagentSessionManagers.has(sm); }