fixed session resume launching with global extensions. resumed subagents launch with subagent-specific tools. also fixed freezing upon resume

This commit is contained in:
Amos
2026-07-06 23:47:01 +03:00
parent 20332d854c
commit a541b3d98b
5 changed files with 333 additions and 48 deletions
+64
View File
@@ -79,6 +79,70 @@ export function seedSubagentSessionFile(params: {
writeFileSync(params.childSessionFile, lines.join("\n") + "\n", "utf8");
}
/**
* A snapshot of everything needed to reconstruct a subagent's sandbox when its
* session is later resumed via `subagent_message({ sessionId })`.
*
* Written next to the session file as `<sessionFile>.loadout.json` at spawn
* time. Resume replays this exact snapshot so the reincarnated process gets the
* same `--no-extensions` + `--tools` restriction, model, identity, spawn
* whitelist, cwd, and config dir it originally ran with — instead of falling
* back to pi's default (all global extensions + full toolset). Storing the
* resolved loadout (rather than re-deriving from the agent `.md` by name) keeps
* resume faithful even if the agent definition is later edited, moved, or
* deleted.
*/
export interface SubagentLoadout {
/** Agent profile name (for PI_SUBAGENT_AGENT); null for agentless spawns. */
agent: string | null;
/** The `--tools` allowlist string, or null when the spawn was unrestricted. */
toolAllowlist: string | null;
/** Model id (without thinking suffix), or null to use the session default. */
model: string | null;
/** Thinking level appended to the model as `model:level`, or null. */
thinking: string | null;
/** How the identity text was applied: append/replace, or null. */
systemPromptMode: "append" | "replace" | null;
/** The system-prompt/identity text, only when it lived in the system prompt. */
identity: string | null;
/** Agents this subagent was allowed to spawn (for PI_SUBAGENT_ALLOWED). */
spawnable: string[] | null;
/** Whether the agent auto-exits (informational; resume forces autonomous). */
autoExit: boolean;
/** Working directory the subagent ran in, or null. */
cwd: string | null;
/** PI_CODING_AGENT_DIR the subagent resolved config/extensions from, or null. */
agentDir: string | null;
}
/** Path of the loadout sidecar written next to a subagent session file. */
export function loadoutSidecarPath(sessionFile: string): string {
return `${sessionFile}.loadout.json`;
}
/** Persist a subagent's resolved sandbox loadout beside its session file. */
export function writeSubagentLoadout(sessionFile: string, loadout: SubagentLoadout): void {
try {
writeFileSync(loadoutSidecarPath(sessionFile), JSON.stringify(loadout), "utf8");
} catch {
// Best-effort: a missing snapshot only means resume will refuse, never that
// it launches unrestricted.
}
}
/** Read a subagent's loadout snapshot, or null if absent/unparseable. */
export function readSubagentLoadout(sessionFile: string): SubagentLoadout | null {
try {
const p = loadoutSidecarPath(sessionFile);
if (!existsSync(p)) return null;
const parsed = JSON.parse(readFileSync(p, "utf8"));
if (!parsed || typeof parsed !== "object") return null;
return parsed as SubagentLoadout;
} catch {
return null;
}
}
function readEntries(sessionFile: string): SessionEntry[] {
const raw = readFileSync(sessionFile, "utf8");
return raw