feat(sessions): add tracked subsessions behind a beta flag

Add spawn_subsession / list_subsessions / read_subsession tools that let an
agent start child sessions it stays attached to: the child records its parent
in the session tree, the parent is notified (as a system-authored custom
message that wakes an idle parent and queues behind in-flight work) when the
child stops working, and the parent can inspect children's status and result.

Gated behind a beta flag, off by default, mirroring spawnSessions: enable via
PI_WEB_SUBSESSIONS, the subsessions config key, or the Settings toggle. Also
requires spawnSessions.

Also fix the release skill so the version step resyncs package-lock.json
(npm install --package-lock-only) and the commit step refuses a release where
package.json and package-lock.json versions disagree.
This commit is contained in:
Federico Jaramillo Martinez
2026-06-17 12:13:23 +02:00
parent ef454f2b65
commit 355ebe8cf8
17 changed files with 658 additions and 13 deletions
+24
View File
@@ -85,6 +85,8 @@ export function effectivePiWebConfig(options: LoadOptions = {}): LoadedPiWebConf
// Always resolved (on by default) so the effective config is the single
// source of truth for the runtime state and the settings UI toggle.
spawnSessions: spawnSessionsEnabled(env, loaded.config),
// Beta capability, resolved off by default.
subsessions: subsessionsEnabled(env, loaded.config),
},
};
}
@@ -101,6 +103,7 @@ export function savePiWebConfig(config: PiWebConfig, options: LoadOptions = {}):
delete existing["plugins"];
delete existing["maxUploadBytes"];
delete existing["spawnSessions"];
delete existing["subsessions"];
const merged = { ...existing, ...piWebConfigRecord(normalized) };
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
@@ -123,6 +126,7 @@ function piWebConfigRecord(config: PiWebConfig): Record<string, unknown> {
...(config.plugins !== undefined ? { plugins: config.plugins } : {}),
...(config.maxUploadBytes !== undefined ? { maxUploadBytes: config.maxUploadBytes } : {}),
...(config.spawnSessions !== undefined ? { spawnSessions: config.spawnSessions } : {}),
...(config.subsessions !== undefined ? { subsessions: config.subsessions } : {}),
};
}
@@ -135,6 +139,7 @@ function parsePiWebConfig(value: Record<string, unknown>, path: string): PiWebCo
...(value["plugins"] !== undefined ? { plugins: parsePlugins(value["plugins"], path) } : {}),
...(value["maxUploadBytes"] !== undefined ? { maxUploadBytes: parseMaxUploadBytes(value["maxUploadBytes"], "maxUploadBytes", path) } : {}),
...(value["spawnSessions"] !== undefined ? { spawnSessions: parseSpawnSessions(value["spawnSessions"], path) } : {}),
...(value["subsessions"] !== undefined ? { subsessions: parseSubsessions(value["subsessions"], path) } : {}),
};
}
@@ -161,6 +166,25 @@ export function spawnSessionsEnabled(env: NodeJS.ProcessEnv = process.env, confi
return config.spawnSessions ?? true;
}
function parseSubsessions(value: unknown, path: string): boolean {
if (typeof value !== "boolean") throw new Error(`PI WEB config subsessions must be a boolean: ${path}`);
return value;
}
/**
* Beta: whether LLMs may start tracked child sessions via the spawn_subsession
* family of tools. Off by default while the capability stabilizes, so it can
* ship in main without affecting releases; enable with the env var
* `PI_WEB_SUBSESSIONS` or the `subsessions` config key. The env var takes
* precedence over the config file. Subsessions also require spawnSessions to be
* enabled (they share the same project-scope resolver).
*/
export function subsessionsEnabled(env: NodeJS.ProcessEnv = process.env, config: PiWebConfig = {}): boolean {
const fromEnv = env["PI_WEB_SUBSESSIONS"];
if (fromEnv !== undefined && fromEnv !== "") return fromEnv === "1" || fromEnv.toLowerCase() === "true";
return config.subsessions ?? false;
}
function parseString(value: unknown, key: string, path: string): string {
if (typeof value !== "string" || value === "") throw new Error(`PI WEB config ${key} must be a non-empty string: ${path}`);
return value;