@demicodes/agent 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { f as TranscriptPatch, l as ServerFrame, n as AgentServerTransport, o as ClientFrame, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandStatusLike, x as AgentMetadata } from "./transport-CHrPzcrU.mjs";
2
- import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-NZ5Btmb4.mjs";
1
+ import { f as TranscriptPatch, l as ServerFrame, n as AgentServerTransport, o as ClientFrame, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandStatusLike, x as AgentMetadata } from "./transport-_eV2zg9M.mjs";
2
+ import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-BC7OstFf.mjs";
3
3
  import { ProviderSelection } from "@demicodes/provider";
4
4
  export { type AgentActionOptions, AgentClient, type AgentClientListener, type AgentClientTransport, type AgentMetadata, type AgentServerTransport, type AgentTransport, type ClientFrame, type ClientSessionEvent, type JsonWebSocket, type ProviderSelection, type ServerFrame, type ShellCommandStatusLike, type TranscriptPatch, createWebSocketClientTransport, createWebSocketServerTransport };
@@ -1,2 +1,2 @@
1
- import { n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-CIOotpd-.mjs";
1
+ import { n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-BsKvbXIp.mjs";
2
2
  export { AgentClient, createWebSocketClientTransport, createWebSocketServerTransport };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AgentSystemPromptContext, B as CompactionWindow, C as AgentReferenceResolveContext, D as AgentSessionParams, E as AgentSessionOptions, F as ExternalMutationReservation, G as DEFAULT_TURN_RETRY_POLICY, H as TranscriptLog, I as ModelSwitchApply, J as resolveRetryPolicy, K as TurnRetryPolicy, L as SessionEvent, M as AgentToolContext, N as AgentToolInvokeContext, O as AgentSessionRestoreParams, P as AgentToolInvokeResult, R as SessionEventListener, S as AgentPromptContext, T as AgentSessionCloneParams, U as TranscriptOptions, V as DrainedTranscriptPatches, W as estimateTranscriptBlockTokens, Y as retryDelayMs, _ as AgentHarnessContext, a as createInProcessTransportPair, b as AgentLifecycleEvent, c as ConversationSummary, d as SubagentJob, f as TranscriptPatch, g as AgentHarness, h as AgentDisposeContext, i as InProcessTransportPair, j as AgentTool, k as AgentSessionStore, l as ServerFrame, m as AbortTarget, n as AgentServerTransport, o as ClientFrame, p as AbortResult, q as isRetryableCode, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandStatusLike, v as AgentHarnessRuntime, w as AgentSessionCheckpoint, x as AgentMetadata, y as AgentHostContext, z as SubagentProfile } from "./transport-CHrPzcrU.mjs";
2
- import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-NZ5Btmb4.mjs";
1
+ import { A as AgentSystemPromptContext, B as CompactionWindow, C as AgentReferenceResolveContext, D as AgentSessionParams, E as AgentSessionOptions, F as ExternalMutationReservation, G as DEFAULT_TURN_RETRY_POLICY, H as TranscriptLog, I as ModelSwitchApply, J as resolveRetryPolicy, K as TurnRetryPolicy, L as SessionEvent, M as AgentToolContext, N as AgentToolInvokeContext, O as AgentSessionRestoreParams, P as AgentToolInvokeResult, R as SessionEventListener, S as AgentPromptContext, T as AgentSessionCloneParams, U as TranscriptOptions, V as DrainedTranscriptPatches, W as estimateTranscriptBlockTokens, Y as retryDelayMs, _ as AgentHarnessContext, a as createInProcessTransportPair, b as AgentLifecycleEvent, c as ConversationSummary, d as SubagentJob, f as TranscriptPatch, g as AgentHarness, h as AgentDisposeContext, i as InProcessTransportPair, j as AgentTool, k as AgentSessionStore, l as ServerFrame, m as AbortTarget, n as AgentServerTransport, o as ClientFrame, p as AbortResult, q as isRetryableCode, r as AgentTransport, s as ClientSessionEvent, t as AgentClientTransport, u as ShellCommandStatusLike, v as AgentHarnessRuntime, w as AgentSessionCheckpoint, x as AgentMetadata, y as AgentHostContext, z as SubagentProfile } from "./transport-_eV2zg9M.mjs";
2
+ import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-BC7OstFf.mjs";
3
3
  import { BashAuditEvent, BashEnvironment, BashEnvironmentOptions, Command, CommandMetadataRecord, Host, HostStore, ShellCommandStatus, ShellOutputChunk } from "@demicodes/shell";
4
4
  import { AgentProvider, Provider } from "@demicodes/provider";
5
5
  import { Block, Model, ModelMediaKind, ModelSelection, ProviderErrorDiagnostics, QueuedMessage, SessionPhase, UserContentBlock } from "@demicodes/core";
@@ -339,6 +339,7 @@ declare class AgentServer {
339
339
  //#endregion
340
340
  //#region src/subagent.d.ts
341
341
  declare const MAX_LIVE_SUBAGENTS = 8;
342
+ declare const MAX_ARCHIVED_SUBAGENTS = 16;
342
343
  declare const SUBAGENT_RESULT_MAX_BYTES: number;
343
344
  type SubagentExecution = 'idle' | 'provider_streaming' | 'tool_executing' | 'compacting' | 'finalizing' | 'pending_yield';
344
345
  interface ChildSupervisorOptions<State> {
@@ -364,7 +365,8 @@ interface ChildSupervisorOptions<State> {
364
365
  * on the parent connection. Children persist exactly like the parent: each
365
366
  * live child keeps a checkpoint and job record under the parent's session
366
367
  * directory, and reopening the parent restores and resumes them (`restore`).
367
- * A closed child leaves nothing behind.
368
+ * A closed child moves to the archive: its transcript checkpoint stays on
369
+ * store and `demi agent resume` revives it with a new user message.
368
370
  */
369
371
  declare class ChildSupervisor<State = unknown> {
370
372
  private readonly options;
@@ -399,7 +401,21 @@ declare class ChildSupervisor<State = unknown> {
399
401
  */
400
402
  restore(): Promise<void>;
401
403
  private restoreJob;
404
+ /** Shared by restore and resume: rebuild a persisted child's job and session from its checkpoint. */
405
+ private reassembleJob;
406
+ /**
407
+ * Revives an archived child: its job record turns live again (this round's
408
+ * metadata, a fresh spawnedAt), the session rebuilds from the preserved
409
+ * checkpoint, and the message opens its next turn on top of the old
410
+ * transcript.
411
+ */
412
+ private resumeArchived;
413
+ /** Every archived (finished, revivable) child of this parent, newest first. */
414
+ private listArchivedJobs;
415
+ private pruneArchive;
402
416
  abortSubtree(id: string): Promise<void>;
417
+ /** Aborts every live child; the archive is untouched. */
418
+ abortAll(): Promise<void>;
403
419
  private spawn;
404
420
  /** Everything spawn and restore share: the command tree, the harness runtime, and the job record (session attached separately). */
405
421
  private assembleJob;
@@ -407,6 +423,8 @@ declare class ChildSupervisor<State = unknown> {
407
423
  private childStoreKey;
408
424
  private childSessionStore;
409
425
  private deletePersistedJob;
426
+ /** Shared spawn/resume foreground behaviour: announce the id, wire abort and stdin steers, wait for close, report the outcome. */
427
+ private attendChild;
410
428
  /** Delivers one steer to a child: mid-turn as a steer, idle as a new user turn. */
411
429
  private steerChild;
412
430
  private pumpStdinSteers;
@@ -516,4 +534,4 @@ interface ShellToolView {
516
534
  declare function finishShellToolResult<State>(environment: BashEnvironment, result: ShellCommandStatus, ctx: AgentToolInvokeContext<State>): Promise<AgentToolInvokeResult>;
517
535
  declare function shellCommandHandleRequired(result: ShellCommandStatus, budgetTokens: number): boolean;
518
536
  //#endregion
519
- export { AbortResult, AbortTarget, ActiveTurnPhase, AgentActionOptions, AgentClient, AgentClientListener, AgentClientTransport, AgentDisposeContext, AgentHarness, AgentHarnessContext, AgentHarnessRuntime, AgentHostContext, AgentLifecycleEvent, AgentMetadata, AgentPromptContext, AgentReferenceResolveContext, AgentServer, AgentServerOptions, AgentServerSessionOptions, AgentServerTransport, AgentSession, AgentSessionCheckpoint, AgentSessionCloneParams, AgentSessionOptions, AgentSessionParams, AgentSessionRestoreParams, AgentSessionStore, AgentSystemPromptContext, AgentTool, AgentToolContext, AgentToolInvokeContext, AgentToolInvokeResult, AgentTransport, AgentTransportBinding, ChildSupervisor, ChildSupervisorOptions, ClientFrame, ClientSessionEvent, CompactionWindow, ConversationSummary, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, DrainedTranscriptPatches, ExternalMutationReservation, InProcessTransportPair, JsonWebSocket, MAX_LIVE_SUBAGENTS, ModelSwitchApply, PrepareShell, PrepareShellContext, ProviderStreamError, ResumePoint, RunCommandLineCommandNotRegisteredError, RunCommandLineOptions, RunCommandLineResult, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, SUBAGENT_RESULT_MAX_BYTES, ServerFrame, SessionEvent, SessionEventListener, ShellCommandStatusLike, ShellToolResultOptions, ShellToolView, StandardAgentToolOptions, SubagentExecution, SubagentJob, SubagentProfile, TranscriptLog, TranscriptOptions, TranscriptPatch, TurnRetryPolicy, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createReadonlyHost, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, injectSubagentCommand, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
537
+ export { AbortResult, AbortTarget, ActiveTurnPhase, AgentActionOptions, AgentClient, AgentClientListener, AgentClientTransport, AgentDisposeContext, AgentHarness, AgentHarnessContext, AgentHarnessRuntime, AgentHostContext, AgentLifecycleEvent, AgentMetadata, AgentPromptContext, AgentReferenceResolveContext, AgentServer, AgentServerOptions, AgentServerSessionOptions, AgentServerTransport, AgentSession, AgentSessionCheckpoint, AgentSessionCloneParams, AgentSessionOptions, AgentSessionParams, AgentSessionRestoreParams, AgentSessionStore, AgentSystemPromptContext, AgentTool, AgentToolContext, AgentToolInvokeContext, AgentToolInvokeResult, AgentTransport, AgentTransportBinding, ChildSupervisor, ChildSupervisorOptions, ClientFrame, ClientSessionEvent, CompactionWindow, ConversationSummary, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, DrainedTranscriptPatches, ExternalMutationReservation, InProcessTransportPair, JsonWebSocket, MAX_ARCHIVED_SUBAGENTS, MAX_LIVE_SUBAGENTS, ModelSwitchApply, PrepareShell, PrepareShellContext, ProviderStreamError, ResumePoint, RunCommandLineCommandNotRegisteredError, RunCommandLineOptions, RunCommandLineResult, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, SUBAGENT_RESULT_MAX_BYTES, ServerFrame, SessionEvent, SessionEventListener, ShellCommandStatusLike, ShellToolResultOptions, ShellToolView, StandardAgentToolOptions, SubagentExecution, SubagentJob, SubagentProfile, TranscriptLog, TranscriptOptions, TranscriptPatch, TurnRetryPolicy, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createReadonlyHost, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, injectSubagentCommand, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport, o as ProviderStreamError, r as AgentClient, s as isContextLengthExceeded, t as createWebSocketClientTransport } from "./websocket-transport-CIOotpd-.mjs";
1
+ import { a as cloneBlocks, i as applyTranscriptPatches, n as createWebSocketServerTransport, o as ProviderStreamError, r as AgentClient, s as isContextLengthExceeded, t as createWebSocketClientTransport } from "./websocket-transport-BsKvbXIp.mjs";
2
2
  import { AbortError, abortable, asError, asRecord, asString, bytesToBase64, createId, decodeUtf8, delay, errorMessage, isAbortError, isRecord, noop, parseJsonOrString, safeJsonStringify, sliceHead, sliceTail, throwIfAborted, toWellFormedText, truncate, utf8Slice } from "@demicodes/utils";
3
3
  import { BashEnvironment, CommandRegistry, MAX_TIMEOUT_MS, heredocDelimiter, shellQuote } from "@demicodes/shell";
4
4
  import { providerRuntime } from "@demicodes/provider";
@@ -2653,6 +2653,7 @@ function shellCommandHandleRequired(result, budgetTokens) {
2653
2653
  //#endregion
2654
2654
  //#region src/subagent.ts
2655
2655
  const MAX_LIVE_SUBAGENTS = 8;
2656
+ const MAX_ARCHIVED_SUBAGENTS = 16;
2656
2657
  const SUBAGENT_RESULT_MAX_BYTES = 32768;
2657
2658
  const SHOW_RECENT_TOOLS = 8;
2658
2659
  const SPAWN_PROMPT_DESCRIPTION = "The child's first user message and only task brief. The child starts with an empty transcript and cannot see this conversation: do not refer to prior turns, and do not paste this conversation or the product user's message unchanged. Include the goal for this child, applicable decisions and constraints, whether to edit or only report, how to verify, and every concrete identifier it needs (paths, ids, error text, commands already tried and their key results). State the exact shape of the last assistant text it should return.";
@@ -2663,7 +2664,8 @@ const SPAWN_PROMPT_DESCRIPTION = "The child's first user message and only task b
2663
2664
  * on the parent connection. Children persist exactly like the parent: each
2664
2665
  * live child keeps a checkpoint and job record under the parent's session
2665
2666
  * directory, and reopening the parent restores and resumes them (`restore`).
2666
- * A closed child leaves nothing behind.
2667
+ * A closed child moves to the archive: its transcript checkpoint stays on
2668
+ * store and `demi agent resume` revives it with a new user message.
2667
2669
  */
2668
2670
  var ChildSupervisor = class {
2669
2671
  options;
@@ -2712,30 +2714,12 @@ var ChildSupervisor = class {
2712
2714
  await io.stderr(`demi agent: ${errorMessage(error)}\n`);
2713
2715
  return { exitCode: 1 };
2714
2716
  }
2715
- await io.stderr(`subagentId: ${job.id}\n`);
2716
- const onAbort = () => {
2717
- this.abortSubtree(job.id).catch(noop);
2718
- };
2719
- if (signal.aborted) onAbort();
2720
- else signal.addEventListener("abort", onAbort, { once: true });
2721
- this.pumpStdinSteers(job.id, stdinStream);
2722
- const close = await job.closed;
2723
- signal.removeEventListener("abort", onAbort);
2724
- if (close.phase === "completed") {
2725
- const text = close.result ?? "";
2726
- if (parsed.json) await io.stdout(`${JSON.stringify({
2727
- subagentId: job.id,
2728
- text
2729
- })}\n`);
2730
- else if (text.length > 0) await io.stdout(text.endsWith("\n") ? text : `${text}\n`);
2731
- return { exitCode: 0 };
2732
- }
2733
- if (close.phase === "aborted") {
2734
- await io.stderr(`demi agent: subagent ${job.id} aborted\n`);
2735
- return { exitCode: 130 };
2736
- }
2737
- await io.stderr(`demi agent: subagent ${job.id} failed: ${close.failure ?? "unknown error"}\n`);
2738
- return { exitCode: 1 };
2717
+ return this.attendChild(job, {
2718
+ io,
2719
+ isJson: parsed.json === true,
2720
+ signal,
2721
+ stdinStream
2722
+ });
2739
2723
  },
2740
2724
  subcommands: [
2741
2725
  {
@@ -2794,21 +2778,73 @@ var ChildSupervisor = class {
2794
2778
  return { exitCode: 0 };
2795
2779
  }
2796
2780
  },
2781
+ {
2782
+ name: "resume",
2783
+ summary: "Revive an archived (finished) child with a new user message on top of its preserved transcript. Behaves like the spawn command afterwards: stays running until the child ends again, stdout is its new last assistant text, shell_write steers, shell_abort aborts. Archived ids are in `demi agent list`.",
2784
+ input: {
2785
+ id: z.string().describe("subagentId of an archived child"),
2786
+ message: z.string().optional().describe("The reviving user message; positional, or stdin/heredoc when omitted.")
2787
+ },
2788
+ positionals: ["id", "message"],
2789
+ stdinField: "message",
2790
+ output: { json: z.object({
2791
+ subagentId: z.string(),
2792
+ text: z.string()
2793
+ }) },
2794
+ run: async ({ parsed, io, signal, stdinStream }) => {
2795
+ const id = String(parsed.values.id);
2796
+ const message = String(parsed.values.message ?? "").trim();
2797
+ if (!message) {
2798
+ await io.stderr("demi agent resume: message must not be empty\n");
2799
+ return { exitCode: 1 };
2800
+ }
2801
+ let job;
2802
+ try {
2803
+ job = await this.resumeArchived(id, message);
2804
+ } catch (error) {
2805
+ await io.stderr(`demi agent resume: ${errorMessage(error)}\n`);
2806
+ return { exitCode: 1 };
2807
+ }
2808
+ return this.attendChild(job, {
2809
+ io,
2810
+ isJson: parsed.json === true,
2811
+ signal,
2812
+ stdinStream
2813
+ });
2814
+ }
2815
+ },
2797
2816
  {
2798
2817
  name: "list",
2799
- summary: "Snapshot roster of this session's running children (finished children are absent). One line per child with ages relative to now. A read, not a wait — not for polling loops.",
2800
- output: { json: z.object({ agents: z.array(z.unknown()) }) },
2818
+ summary: "Snapshot roster of this session's running children, then its archived (finished, revivable via resume) children. One line per child with ages relative to now. A read, not a wait — not for polling loops.",
2819
+ output: { json: z.object({
2820
+ agents: z.array(z.unknown()),
2821
+ archived: z.array(z.unknown())
2822
+ }) },
2801
2823
  run: async ({ parsed, io }) => {
2802
2824
  const jobs = [...this.jobs.values()];
2825
+ const archived = await this.listArchivedJobs();
2803
2826
  if (parsed.json) {
2804
- await io.stdout(`${JSON.stringify({ agents: jobs.map((job) => this.snapshot(job, false)) })}\n`);
2805
- return { exitCode: 0 };
2806
- }
2807
- if (jobs.length === 0) {
2808
- await io.stdout("no running subagents\n");
2827
+ await io.stdout(`${JSON.stringify({
2828
+ agents: jobs.map((job) => this.snapshot(job, false)),
2829
+ archived: archived.map(({ id, meta }) => ({
2830
+ subagentId: id,
2831
+ description: meta.description,
2832
+ profile: meta.profileName,
2833
+ phase: meta.closedPhase,
2834
+ closedAgoMs: meta.closedAt === void 0 ? null : Date.now() - meta.closedAt
2835
+ }))
2836
+ })}\n`);
2809
2837
  return { exitCode: 0 };
2810
2838
  }
2839
+ if (jobs.length === 0) await io.stdout("no running subagents\n");
2811
2840
  for (const job of jobs) await io.stdout(`${this.renderListLine(job)}\n`);
2841
+ if (archived.length > 0) {
2842
+ await io.stdout("archived (revivable with `demi agent resume <id>`):\n");
2843
+ for (const { id, meta } of archived) {
2844
+ const closedAgo = meta.closedAt === void 0 ? "" : ` closed ${formatDuration(Date.now() - meta.closedAt)} ago`;
2845
+ await io.stdout(` ${id} ${meta.closedPhase}${closedAgo} ${meta.description ? `"${meta.description}"` : "(no description)"}\n`);
2846
+ }
2847
+ }
2812
2848
  return { exitCode: 0 };
2813
2849
  }
2814
2850
  },
@@ -2901,11 +2937,18 @@ var ChildSupervisor = class {
2901
2937
  }
2902
2938
  }
2903
2939
  async restoreJob(id) {
2904
- const parent = this.parentSession;
2905
- if (!parent) return;
2940
+ if (!this.parentSession) return;
2906
2941
  const meta = await this.options.store.readJson(this.childStoreKey(id, "job.json"));
2942
+ if (meta?.closedPhase) return;
2907
2943
  const checkpoint = await this.childSessionStore(id).loadCheckpoint();
2908
2944
  if (!meta || !checkpoint) throw new Error("incomplete persisted subagent");
2945
+ const job = this.reassembleJob(id, meta, checkpoint);
2946
+ this.watchTurn(job, job.session.resume(meta.metadata ? { metadata: meta.metadata } : {}));
2947
+ }
2948
+ /** Shared by restore and resume: rebuild a persisted child's job and session from its checkpoint. */
2949
+ reassembleJob(id, meta, checkpoint) {
2950
+ const parent = this.parentSession;
2951
+ if (!parent) throw new Error("subagent supervisor has no parent session");
2909
2952
  const profile = this.resolveProfile(meta.profileName ?? void 0);
2910
2953
  const { job, runtime } = this.assembleJob({
2911
2954
  id,
@@ -2925,13 +2968,69 @@ var ChildSupervisor = class {
2925
2968
  ...this.options.sessionOptions
2926
2969
  });
2927
2970
  this.attachSession(job, session);
2928
- this.watchTurn(job, session.resume(meta.metadata ? { metadata: meta.metadata } : {}));
2971
+ return job;
2972
+ }
2973
+ /**
2974
+ * Revives an archived child: its job record turns live again (this round's
2975
+ * metadata, a fresh spawnedAt), the session rebuilds from the preserved
2976
+ * checkpoint, and the message opens its next turn on top of the old
2977
+ * transcript.
2978
+ */
2979
+ async resumeArchived(id, message) {
2980
+ const parent = this.parentSession;
2981
+ if (!parent) throw new Error("subagent supervisor has no parent session");
2982
+ if (this.isDisposed) throw new Error("parent session is closing");
2983
+ if (this.jobs.has(id)) throw new Error(`subagent "${id}" is still running; steer it instead`);
2984
+ if (this.jobs.size >= 8) throw new Error(`at most 8 running subagents per session; abort one or wait for a result`);
2985
+ const meta = await this.options.store.readJson(this.childStoreKey(id, "job.json"));
2986
+ if (!meta?.closedPhase) throw new Error(`no archived subagent "${id}" (see \`demi agent list\`)`);
2987
+ const checkpoint = await this.childSessionStore(id).loadCheckpoint();
2988
+ if (!checkpoint) throw new Error(`archived subagent "${id}" has no checkpoint left`);
2989
+ const liveMeta = {
2990
+ description: meta.description,
2991
+ profileName: meta.profileName,
2992
+ metadata: parent.actionMetadata(),
2993
+ spawnedAt: Date.now()
2994
+ };
2995
+ await this.options.store.writeJson(this.childStoreKey(id, "job.json"), liveMeta);
2996
+ const job = this.reassembleJob(id, liveMeta, checkpoint);
2997
+ this.watchTurn(job, job.session.send([{
2998
+ type: "text",
2999
+ text: message
3000
+ }], liveMeta.metadata ? { metadata: liveMeta.metadata } : {}));
3001
+ return job;
3002
+ }
3003
+ /** Every archived (finished, revivable) child of this parent, newest first. */
3004
+ async listArchivedJobs() {
3005
+ const parent = this.parentSession;
3006
+ if (!parent) return [];
3007
+ const prefix = `agent-sessions/${parent.id()}/subagents/`;
3008
+ const keys = await this.options.store.list(prefix).catch(() => []);
3009
+ const ids = [...new Set(keys.map((key) => key.slice(prefix.length).split("/")[0] ?? "").filter(Boolean))];
3010
+ const archived = [];
3011
+ for (const id of ids) {
3012
+ if (this.jobs.has(id)) continue;
3013
+ const meta = await this.options.store.readJson(this.childStoreKey(id, "job.json"));
3014
+ if (meta?.closedPhase) archived.push({
3015
+ id,
3016
+ meta
3017
+ });
3018
+ }
3019
+ return archived.sort((a, b) => (b.meta.closedAt ?? 0) - (a.meta.closedAt ?? 0));
3020
+ }
3021
+ async pruneArchive() {
3022
+ const archived = await this.listArchivedJobs();
3023
+ for (const stale of archived.slice(16)) await this.deletePersistedJob(stale.id);
2929
3024
  }
2930
3025
  async abortSubtree(id) {
2931
3026
  const job = this.jobs.get(id);
2932
3027
  if (!job) return;
2933
3028
  await this.closeJob(job, "aborted");
2934
3029
  }
3030
+ /** Aborts every live child; the archive is untouched. */
3031
+ async abortAll() {
3032
+ for (const id of [...this.jobs.keys()]) await this.abortSubtree(id);
3033
+ }
2935
3034
  async spawn(input) {
2936
3035
  const parent = this.parentSession;
2937
3036
  if (!parent) throw new Error("subagent supervisor has no parent session");
@@ -3068,6 +3167,34 @@ var ChildSupervisor = class {
3068
3167
  await this.options.store.delete(this.childStoreKey(id, "checkpoint.json")).catch(noop);
3069
3168
  await this.options.store.delete(this.childStoreKey(id, "job.json")).catch(noop);
3070
3169
  }
3170
+ /** Shared spawn/resume foreground behaviour: announce the id, wire abort and stdin steers, wait for close, report the outcome. */
3171
+ async attendChild(job, ctx) {
3172
+ const { io, isJson, signal, stdinStream } = ctx;
3173
+ await io.stderr(`subagentId: ${job.id}\n`);
3174
+ const onAbort = () => {
3175
+ this.abortSubtree(job.id).catch(noop);
3176
+ };
3177
+ if (signal.aborted) onAbort();
3178
+ else signal.addEventListener("abort", onAbort, { once: true });
3179
+ this.pumpStdinSteers(job.id, stdinStream);
3180
+ const close = await job.closed;
3181
+ signal.removeEventListener("abort", onAbort);
3182
+ if (close.phase === "completed") {
3183
+ const text = close.result ?? "";
3184
+ if (isJson) await io.stdout(`${JSON.stringify({
3185
+ subagentId: job.id,
3186
+ text
3187
+ })}\n`);
3188
+ else if (text.length > 0) await io.stdout(text.endsWith("\n") ? text : `${text}\n`);
3189
+ return { exitCode: 0 };
3190
+ }
3191
+ if (close.phase === "aborted") {
3192
+ await io.stderr(`demi agent: subagent ${job.id} aborted\n`);
3193
+ return { exitCode: 130 };
3194
+ }
3195
+ await io.stderr(`demi agent: subagent ${job.id} failed: ${close.failure ?? "unknown error"}\n`);
3196
+ return { exitCode: 1 };
3197
+ }
3071
3198
  /** Delivers one steer to a child: mid-turn as a steer, idle as a new user turn. */
3072
3199
  async steerChild(job, message) {
3073
3200
  const content = [{
@@ -3134,7 +3261,15 @@ var ChildSupervisor = class {
3134
3261
  const result = phase === "completed" ? boundedResultText(lastAssistantText(job.session.transcript().blocks)) : void 0;
3135
3262
  await job.session.dispose().catch(noop);
3136
3263
  await this.disposeJobShells(job);
3137
- await this.deletePersistedJob(job.id);
3264
+ await this.options.store.writeJson(this.childStoreKey(job.id, "job.json"), {
3265
+ description: job.description,
3266
+ profileName: job.profileName,
3267
+ metadata: job.metadata,
3268
+ spawnedAt: job.spawnedAt,
3269
+ closedPhase: phase,
3270
+ closedAt: Date.now()
3271
+ }).catch(noop);
3272
+ await this.pruneArchive().catch(noop);
3138
3273
  const close = {
3139
3274
  phase,
3140
3275
  ...result !== void 0 ? { result } : {},
@@ -3811,6 +3946,9 @@ var AgentTransportBindingImpl = class {
3811
3946
  });
3812
3947
  return;
3813
3948
  }
3949
+ case "abort_subagents":
3950
+ await this.supervisor?.abortAll();
3951
+ return;
3814
3952
  case "shell_write":
3815
3953
  await this.handleShellWrite(frame);
3816
3954
  return;
@@ -4360,4 +4498,4 @@ function createProviderMap(providers) {
4360
4498
  return map;
4361
4499
  }
4362
4500
  //#endregion
4363
- export { AgentClient, AgentServer, AgentSession, ChildSupervisor, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, MAX_LIVE_SUBAGENTS, ProviderStreamError, RunCommandLineCommandNotRegisteredError, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, SUBAGENT_RESULT_MAX_BYTES, TranscriptLog, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createReadonlyHost, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, injectSubagentCommand, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
4501
+ export { AgentClient, AgentServer, AgentSession, ChildSupervisor, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, MAX_ARCHIVED_SUBAGENTS, MAX_LIVE_SUBAGENTS, ProviderStreamError, RunCommandLineCommandNotRegisteredError, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, SUBAGENT_RESULT_MAX_BYTES, TranscriptLog, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createReadonlyHost, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, injectSubagentCommand, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
@@ -1,4 +1,4 @@
1
- import { n as AgentServerTransport, t as AgentClientTransport } from "./transport-CHrPzcrU.mjs";
1
+ import { n as AgentServerTransport, t as AgentClientTransport } from "./transport-_eV2zg9M.mjs";
2
2
  import { Readable, Writable } from "node:stream";
3
3
  //#region src/stdio-transport.d.ts
4
4
  declare function createStdioClientTransport(readable: Readable, writable: Writable): AgentClientTransport;
@@ -438,6 +438,8 @@ type ClientFrame = {
438
438
  apply?: ModelSwitchApply;
439
439
  } | {
440
440
  type: 'abort';
441
+ } | {
442
+ type: 'abort_subagents';
441
443
  } | {
442
444
  type: 'retry';
443
445
  metadata?: AgentMetadata;
@@ -1,4 +1,4 @@
1
- import { I as ModelSwitchApply, c as ConversationSummary, n as AgentServerTransport, p as AbortResult, s as ClientSessionEvent, t as AgentClientTransport, x as AgentMetadata } from "./transport-CHrPzcrU.mjs";
1
+ import { I as ModelSwitchApply, c as ConversationSummary, n as AgentServerTransport, p as AbortResult, s as ClientSessionEvent, t as AgentClientTransport, x as AgentMetadata } from "./transport-_eV2zg9M.mjs";
2
2
  import { ProviderSelection } from "@demicodes/provider";
3
3
  import { Block, UserContentBlock } from "@demicodes/core";
4
4
  //#region src/client.d.ts
@@ -59,6 +59,12 @@ declare class AgentClient {
59
59
  resume(options?: AgentActionOptions): Promise<void>;
60
60
  compact(options?: AgentActionOptions): Promise<void>;
61
61
  abort(): Promise<AbortResult>;
62
+ /**
63
+ * Aborts every live child agent of this session (the archive is untouched).
64
+ * Fire-and-forget: each child settles through its own `subagent closed`
65
+ * frame with phase 'aborted'.
66
+ */
67
+ abortSubagents(): void;
62
68
  shellWrite(commandId: string, stdin: string, options?: AgentActionOptions): Promise<void>;
63
69
  close(): Promise<void>;
64
70
  subscribe(listener: AgentClientListener): () => void;
@@ -209,6 +209,14 @@ var AgentClient = class {
209
209
  this.sendFrame({ type: "abort" });
210
210
  return wait;
211
211
  }
212
+ /**
213
+ * Aborts every live child agent of this session (the archive is untouched).
214
+ * Fire-and-forget: each child settles through its own `subagent closed`
215
+ * frame with phase 'aborted'.
216
+ */
217
+ abortSubagents() {
218
+ this.sendFrame({ type: "abort_subagents" });
219
+ }
212
220
  shellWrite(commandId, stdin, options = {}) {
213
221
  const wait = this.waitForShellWrite(commandId);
214
222
  this.sendFrame({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@demicodes/agent",
3
3
  "description": "Session runtime and transport-neutral client and server protocol for Demi.",
4
- "version": "0.19.2",
4
+ "version": "0.20.0",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -19,10 +19,10 @@
19
19
  }
20
20
  },
21
21
  "dependencies": {
22
- "@demicodes/core": "^0.19.2",
23
- "@demicodes/provider": "^0.19.2",
24
- "@demicodes/shell": "^0.19.2",
25
- "@demicodes/utils": "^0.19.2",
22
+ "@demicodes/core": "^0.20.0",
23
+ "@demicodes/provider": "^0.20.0",
24
+ "@demicodes/shell": "^0.20.0",
25
+ "@demicodes/utils": "^0.20.0",
26
26
  "zod": "^4.0.0"
27
27
  },
28
28
  "license": "Apache-2.0",