@demicodes/agent 0.18.0 → 0.19.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.
- package/dist/client-entry.d.mts +2 -2
- package/dist/client-entry.mjs +1 -1
- package/dist/index.d.mts +97 -4
- package/dist/index.mjs +809 -11
- package/dist/stdio-transport.d.mts +1 -1
- package/dist/{transport-DtBCCS_A.d.mts → transport-BRqZoaeo.d.mts} +58 -1
- package/dist/{websocket-transport-DdfGgBjO.d.mts → websocket-transport-C6Y5CeLG.d.mts} +1 -1
- package/dist/{websocket-transport-c6P3NBuf.mjs → websocket-transport-CIOotpd-.mjs} +15 -0
- package/package.json +7 -9
package/dist/client-entry.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-
|
|
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-BRqZoaeo.mjs";
|
|
2
|
+
import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-C6Y5CeLG.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 };
|
package/dist/client-entry.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-
|
|
1
|
+
import { n as createWebSocketServerTransport, r as AgentClient, t as createWebSocketClientTransport } from "./websocket-transport-CIOotpd-.mjs";
|
|
2
2
|
export { AgentClient, createWebSocketClientTransport, createWebSocketServerTransport };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-
|
|
3
|
-
import { BashAuditEvent, BashEnvironment, BashEnvironmentOptions, CommandMetadataRecord, Host, ShellCommandStatus, ShellOutputChunk } from "@demicodes/shell";
|
|
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-BRqZoaeo.mjs";
|
|
2
|
+
import { a as AgentClient, i as AgentActionOptions, n as createWebSocketClientTransport, o as AgentClientListener, r as createWebSocketServerTransport, t as JsonWebSocket } from "./websocket-transport-C6Y5CeLG.mjs";
|
|
3
|
+
import { BashAuditEvent, BashEnvironment, BashEnvironmentOptions, Command, CommandMetadataRecord, Host, 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";
|
|
6
6
|
//#region src/recovery.d.ts
|
|
@@ -149,6 +149,13 @@ declare class AgentSession<State> {
|
|
|
149
149
|
state(): State;
|
|
150
150
|
id(): string;
|
|
151
151
|
phase(): SessionPhase;
|
|
152
|
+
/** Granular in-turn phase for observers (null when no turn is active). */
|
|
153
|
+
turnPhase(): ActiveTurnPhase | null;
|
|
154
|
+
hasPendingYields(): boolean;
|
|
155
|
+
/** Metadata of the currently executing action, if any. */
|
|
156
|
+
actionMetadata(): AgentMetadata | null;
|
|
157
|
+
/** Independent provider runtime with this session's configuration (for child sessions). */
|
|
158
|
+
cloneProviderRuntime(): AgentProvider;
|
|
152
159
|
queuedMessages(): QueuedMessage[];
|
|
153
160
|
dequeueMessage(id: string): boolean;
|
|
154
161
|
sendQueuedMessage(id: string): boolean;
|
|
@@ -321,6 +328,92 @@ declare class AgentServer {
|
|
|
321
328
|
runCommandLine(shellId: string, name: string, args: string[], opts: RunCommandLineOptions): Promise<RunCommandLineResult>;
|
|
322
329
|
}
|
|
323
330
|
//#endregion
|
|
331
|
+
//#region src/subagent.d.ts
|
|
332
|
+
declare const MAX_LIVE_SUBAGENTS = 8;
|
|
333
|
+
declare const SUBAGENT_RESULT_MAX_BYTES: number;
|
|
334
|
+
type SubagentExecution = 'idle' | 'provider_streaming' | 'tool_executing' | 'compacting' | 'finalizing' | 'pending_yield';
|
|
335
|
+
interface ChildSupervisorOptions<State> {
|
|
336
|
+
agent: AgentHarness<State>;
|
|
337
|
+
cwd: string;
|
|
338
|
+
/** Harness-configured profiles; null means the implicit `default` only. */
|
|
339
|
+
profiles: SubagentProfile<State>[] | null;
|
|
340
|
+
/** Parent registered commands (harness list, before the `demi agent` injection). */
|
|
341
|
+
parentCommands: Command[];
|
|
342
|
+
shellOptions: Omit<BashEnvironmentOptions, 'host' | 'commands'>;
|
|
343
|
+
prepareShell: PrepareShell | null;
|
|
344
|
+
sessionOptions: AgentServerSessionOptions;
|
|
345
|
+
emit(frame: ServerFrame): void;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Per-parent subagent supervisor. Owns every child `AgentSession` this parent
|
|
349
|
+
* spawns: lifecycle (spawn / steer / abort / natural end), the `demi agent`
|
|
350
|
+
* command tree, child shell environments, and the `subagent*` protocol frames
|
|
351
|
+
* on the parent connection. Children are in-memory only: no store, no
|
|
352
|
+
* checkpoint, gone with the parent.
|
|
353
|
+
*/
|
|
354
|
+
declare class ChildSupervisor<State = unknown> {
|
|
355
|
+
private readonly options;
|
|
356
|
+
private readonly jobs;
|
|
357
|
+
private parentSession;
|
|
358
|
+
private isDisposed;
|
|
359
|
+
constructor(options: ChildSupervisorOptions<State>);
|
|
360
|
+
attachParent(session: AgentSession<State>): void;
|
|
361
|
+
/** The `agent` node AgentServer grafts under the parent registry's `demi` root. */
|
|
362
|
+
rootCommandNode(): Command;
|
|
363
|
+
hasShell(shellId: string): boolean;
|
|
364
|
+
/** Resolves the child scope owning a shell, for the command bridge dispatch. */
|
|
365
|
+
environmentScopeForShell(shellId: string): {
|
|
366
|
+
environment: BashEnvironment;
|
|
367
|
+
commandNames: ReadonlySet<string>;
|
|
368
|
+
agentSessionId: string;
|
|
369
|
+
} | null;
|
|
370
|
+
/** Re-emits `subagent started` + transcript reset for every running child (transcript resync). */
|
|
371
|
+
replay(): void;
|
|
372
|
+
dispose(): Promise<void>;
|
|
373
|
+
abortSubtree(id: string): Promise<void>;
|
|
374
|
+
private spawn;
|
|
375
|
+
/** Delivers one steer to a child: mid-turn as a steer, idle as a new user turn. */
|
|
376
|
+
private steerChild;
|
|
377
|
+
private pumpStdinSteers;
|
|
378
|
+
private watchChild;
|
|
379
|
+
/** Tracks one child turn to session quiescence, then closes the job with its outcome. */
|
|
380
|
+
private watchTurn;
|
|
381
|
+
/** Resolves when the child is idle with no pending yield wakeups (or the job closed). */
|
|
382
|
+
private waitForQuiescence;
|
|
383
|
+
private closeJob;
|
|
384
|
+
/** Wakes an idle parent with a user send; a parent blocked in the spawn gets the tool result instead. */
|
|
385
|
+
private notifyIdleParent;
|
|
386
|
+
private createChildAgentNode;
|
|
387
|
+
private deliverToParent;
|
|
388
|
+
private childEnvironment;
|
|
389
|
+
private createChildEnvironment;
|
|
390
|
+
private resolveProfile;
|
|
391
|
+
private configuredProfileNames;
|
|
392
|
+
private subagentPreamble;
|
|
393
|
+
private recordTelemetry;
|
|
394
|
+
private executionOf;
|
|
395
|
+
private activityOf;
|
|
396
|
+
private executionForMs;
|
|
397
|
+
private snapshot;
|
|
398
|
+
private renderListLine;
|
|
399
|
+
private renderShow;
|
|
400
|
+
private wireJob;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Grafts the `agent` node under a `demi` root: onto an existing harness `demi`
|
|
404
|
+
* tree, or as a new `demi` root when the harness has none.
|
|
405
|
+
*/
|
|
406
|
+
declare function injectSubagentCommand(commands: Command[], agentNode: Command): Command[];
|
|
407
|
+
/**
|
|
408
|
+
* Host wrapper for read-only subagent profiles: filesystem mutation is
|
|
409
|
+
* rejected outside the shell's own command-artifacts tree, and process spawn
|
|
410
|
+
* is rejected outright (a real process cannot be write-restricted).
|
|
411
|
+
*
|
|
412
|
+
* Every facet delegates method by method: Host facets are class instances
|
|
413
|
+
* whose methods live on the prototype, so object spread would drop them.
|
|
414
|
+
*/
|
|
415
|
+
declare function createReadonlyHost(host: Host): Host;
|
|
416
|
+
//#endregion
|
|
324
417
|
//#region src/tools.d.ts
|
|
325
418
|
interface StandardAgentToolOptions<State = unknown> {
|
|
326
419
|
environment: BashEnvironment | ((ctx: AgentToolInvokeContext<State>, handle: {
|
|
@@ -387,4 +480,4 @@ interface ShellToolView {
|
|
|
387
480
|
declare function finishShellToolResult<State>(environment: BashEnvironment, result: ShellCommandStatus, ctx: AgentToolInvokeContext<State>): Promise<AgentToolInvokeResult>;
|
|
388
481
|
declare function shellCommandHandleRequired(result: ShellCommandStatus, budgetTokens: number): boolean;
|
|
389
482
|
//#endregion
|
|
390
|
-
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, ClientFrame, ClientSessionEvent, CompactionWindow, ConversationSummary, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, DrainedTranscriptPatches, ExternalMutationReservation, InProcessTransportPair, JsonWebSocket, ModelSwitchApply, PrepareShell, PrepareShellContext, ProviderStreamError, ResumePoint, RunCommandLineCommandNotRegisteredError, RunCommandLineOptions, RunCommandLineResult, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, ServerFrame, SessionEvent, SessionEventListener, ShellCommandStatusLike, ShellToolResultOptions, ShellToolView, StandardAgentToolOptions, TranscriptLog, TranscriptOptions, TranscriptPatch, TurnRetryPolicy, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
|
|
483
|
+
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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-
|
|
2
|
-
import { AbortError, abortable, asError, asRecord, asString, bytesToBase64, createId, delay, isAbortError, isRecord, noop, parseJsonOrString, safeJsonStringify, sliceHead, sliceTail, throwIfAborted, toWellFormedText, truncate } from "@demicodes/utils";
|
|
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";
|
|
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";
|
|
5
5
|
import { modelAcceptsMediaType, sniffModelMediaType } from "@demicodes/core";
|
|
6
|
+
import { z } from "zod";
|
|
6
7
|
//#region src/transcript.ts
|
|
7
8
|
const DEFAULT_MODEL_TEXT_HEAD_CHARS = 8e3;
|
|
8
9
|
const DEFAULT_MODEL_TEXT_TAIL_CHARS = 8e3;
|
|
@@ -912,7 +913,7 @@ var CompactionController = class {
|
|
|
912
913
|
text: COMPACTION_SUMMARY_INSTRUCTION
|
|
913
914
|
}]);
|
|
914
915
|
throwIfAborted(parentSignal);
|
|
915
|
-
return lastAssistantText(clone.transcript(), baseBlockCount).trim();
|
|
916
|
+
return lastAssistantText$1(clone.transcript(), baseBlockCount).trim();
|
|
916
917
|
} finally {
|
|
917
918
|
parentSignal.removeEventListener("abort", abortClone);
|
|
918
919
|
unsubscribe();
|
|
@@ -920,7 +921,7 @@ var CompactionController = class {
|
|
|
920
921
|
}
|
|
921
922
|
}
|
|
922
923
|
};
|
|
923
|
-
function lastAssistantText(transcript, startIndex) {
|
|
924
|
+
function lastAssistantText$1(transcript, startIndex) {
|
|
924
925
|
for (let index = transcript.blocks.length - 1; index >= startIndex; index -= 1) {
|
|
925
926
|
const block = transcript.blocks[index];
|
|
926
927
|
if (block?.type === "text") return block.text;
|
|
@@ -1583,6 +1584,21 @@ var AgentSession = class AgentSession {
|
|
|
1583
1584
|
phase() {
|
|
1584
1585
|
return this.currentPhase;
|
|
1585
1586
|
}
|
|
1587
|
+
/** Granular in-turn phase for observers (null when no turn is active). */
|
|
1588
|
+
turnPhase() {
|
|
1589
|
+
return this.activeTurnPhase;
|
|
1590
|
+
}
|
|
1591
|
+
hasPendingYields() {
|
|
1592
|
+
return this.yields.hasPending;
|
|
1593
|
+
}
|
|
1594
|
+
/** Metadata of the currently executing action, if any. */
|
|
1595
|
+
actionMetadata() {
|
|
1596
|
+
return this.activeMetadata;
|
|
1597
|
+
}
|
|
1598
|
+
/** Independent provider runtime with this session's configuration (for child sessions). */
|
|
1599
|
+
cloneProviderRuntime() {
|
|
1600
|
+
return this.provider.clone();
|
|
1601
|
+
}
|
|
1586
1602
|
queuedMessages() {
|
|
1587
1603
|
return this.queued.map((message) => ({
|
|
1588
1604
|
...message,
|
|
@@ -2635,6 +2651,765 @@ function shellCommandHandleRequired(result, budgetTokens) {
|
|
|
2635
2651
|
return preview.truncated || result.output.truncated || result.output.bytes > maxChars || result.stdout.truncated || result.stderr.truncated;
|
|
2636
2652
|
}
|
|
2637
2653
|
//#endregion
|
|
2654
|
+
//#region src/subagent.ts
|
|
2655
|
+
const MAX_LIVE_SUBAGENTS = 8;
|
|
2656
|
+
const SUBAGENT_RESULT_MAX_BYTES = 32768;
|
|
2657
|
+
const SHOW_RECENT_TOOLS = 8;
|
|
2658
|
+
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.";
|
|
2659
|
+
/**
|
|
2660
|
+
* Per-parent subagent supervisor. Owns every child `AgentSession` this parent
|
|
2661
|
+
* spawns: lifecycle (spawn / steer / abort / natural end), the `demi agent`
|
|
2662
|
+
* command tree, child shell environments, and the `subagent*` protocol frames
|
|
2663
|
+
* on the parent connection. Children are in-memory only: no store, no
|
|
2664
|
+
* checkpoint, gone with the parent.
|
|
2665
|
+
*/
|
|
2666
|
+
var ChildSupervisor = class {
|
|
2667
|
+
options;
|
|
2668
|
+
jobs = /* @__PURE__ */ new Map();
|
|
2669
|
+
parentSession = null;
|
|
2670
|
+
isDisposed = false;
|
|
2671
|
+
constructor(options) {
|
|
2672
|
+
this.options = options;
|
|
2673
|
+
}
|
|
2674
|
+
attachParent(session) {
|
|
2675
|
+
this.parentSession = session;
|
|
2676
|
+
}
|
|
2677
|
+
/** The `agent` node AgentServer grafts under the parent registry's `demi` root. */
|
|
2678
|
+
rootCommandNode() {
|
|
2679
|
+
const profileNames = this.configuredProfileNames();
|
|
2680
|
+
return {
|
|
2681
|
+
name: "agent",
|
|
2682
|
+
summary: "Start an isolated child agent session and wait for its result. The command stays running until the child session ends; stdout is the child's last assistant text. While it is the foreground job, shell_write steers the child and shell_abort aborts it. Run several in separate shell_exec calls with short timeoutMs to fan out.",
|
|
2683
|
+
successOutput: "first stderr line is \"subagentId: <id>\" at start; stdout is the child's last assistant text (empty is valid), written only at exit",
|
|
2684
|
+
failureOutput: "non-zero exit with the abort or failure reason on stderr",
|
|
2685
|
+
input: {
|
|
2686
|
+
prompt: z.string().optional().describe(SPAWN_PROMPT_DESCRIPTION),
|
|
2687
|
+
profile: z.string().optional().describe(`Named subagent profile configured at harness assembly. Available: ${profileNames.join(", ")}.`),
|
|
2688
|
+
description: z.string().optional().describe("Short UI title distinguishing concurrent children.")
|
|
2689
|
+
},
|
|
2690
|
+
positionals: ["prompt"],
|
|
2691
|
+
stdinField: "prompt",
|
|
2692
|
+
output: { json: z.object({
|
|
2693
|
+
subagentId: z.string(),
|
|
2694
|
+
text: z.string()
|
|
2695
|
+
}) },
|
|
2696
|
+
run: async ({ parsed, io, signal, stdinStream }) => {
|
|
2697
|
+
const prompt = String(parsed.values.prompt ?? "").trim();
|
|
2698
|
+
if (!prompt) {
|
|
2699
|
+
await io.stderr("demi agent: prompt must not be empty\n");
|
|
2700
|
+
return { exitCode: 1 };
|
|
2701
|
+
}
|
|
2702
|
+
let job;
|
|
2703
|
+
try {
|
|
2704
|
+
job = await this.spawn({
|
|
2705
|
+
prompt,
|
|
2706
|
+
profileName: parsed.values.profile === void 0 ? void 0 : String(parsed.values.profile),
|
|
2707
|
+
description: parsed.values.description === void 0 ? "" : String(parsed.values.description)
|
|
2708
|
+
});
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
await io.stderr(`demi agent: ${errorMessage(error)}\n`);
|
|
2711
|
+
return { exitCode: 1 };
|
|
2712
|
+
}
|
|
2713
|
+
await io.stderr(`subagentId: ${job.id}\n`);
|
|
2714
|
+
const onAbort = () => {
|
|
2715
|
+
this.abortSubtree(job.id).catch(noop);
|
|
2716
|
+
};
|
|
2717
|
+
if (signal.aborted) onAbort();
|
|
2718
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
2719
|
+
this.pumpStdinSteers(job.id, stdinStream);
|
|
2720
|
+
const close = await job.closed;
|
|
2721
|
+
signal.removeEventListener("abort", onAbort);
|
|
2722
|
+
if (close.phase === "completed") {
|
|
2723
|
+
const text = close.result ?? "";
|
|
2724
|
+
if (parsed.json) await io.stdout(`${JSON.stringify({
|
|
2725
|
+
subagentId: job.id,
|
|
2726
|
+
text
|
|
2727
|
+
})}\n`);
|
|
2728
|
+
else if (text.length > 0) await io.stdout(text.endsWith("\n") ? text : `${text}\n`);
|
|
2729
|
+
return { exitCode: 0 };
|
|
2730
|
+
}
|
|
2731
|
+
if (close.phase === "aborted") {
|
|
2732
|
+
await io.stderr(`demi agent: subagent ${job.id} aborted\n`);
|
|
2733
|
+
return { exitCode: 130 };
|
|
2734
|
+
}
|
|
2735
|
+
await io.stderr(`demi agent: subagent ${job.id} failed: ${close.failure ?? "unknown error"}\n`);
|
|
2736
|
+
return { exitCode: 1 };
|
|
2737
|
+
},
|
|
2738
|
+
subcommands: [
|
|
2739
|
+
{
|
|
2740
|
+
name: "steer",
|
|
2741
|
+
summary: "Send a user steer to a running child. Queues until the child can take it; does not wait.",
|
|
2742
|
+
input: {
|
|
2743
|
+
id: z.string().describe("subagentId from spawn stderr"),
|
|
2744
|
+
message: z.string().optional().describe("Message body; positional, or stdin/heredoc when omitted.")
|
|
2745
|
+
},
|
|
2746
|
+
positionals: ["id", "message"],
|
|
2747
|
+
stdinField: "message",
|
|
2748
|
+
output: { json: z.object({
|
|
2749
|
+
id: z.string(),
|
|
2750
|
+
accepted: z.boolean()
|
|
2751
|
+
}) },
|
|
2752
|
+
run: async ({ parsed, io }) => {
|
|
2753
|
+
const id = String(parsed.values.id);
|
|
2754
|
+
const message = String(parsed.values.message ?? "").trim();
|
|
2755
|
+
if (!message) {
|
|
2756
|
+
await io.stderr("demi agent steer: message must not be empty\n");
|
|
2757
|
+
return { exitCode: 1 };
|
|
2758
|
+
}
|
|
2759
|
+
const job = this.jobs.get(id);
|
|
2760
|
+
if (!job) {
|
|
2761
|
+
await io.stderr(`demi agent steer: no running subagent "${id}"\n`);
|
|
2762
|
+
return { exitCode: 1 };
|
|
2763
|
+
}
|
|
2764
|
+
await this.steerChild(job, message);
|
|
2765
|
+
await io.stdout(parsed.json ? `${JSON.stringify({
|
|
2766
|
+
id,
|
|
2767
|
+
accepted: true
|
|
2768
|
+
})}\n` : `steered ${id}\n`);
|
|
2769
|
+
return { exitCode: 0 };
|
|
2770
|
+
}
|
|
2771
|
+
},
|
|
2772
|
+
{
|
|
2773
|
+
name: "abort",
|
|
2774
|
+
summary: "Abort a running child and its subtree. Siblings are untouched.",
|
|
2775
|
+
input: { id: z.string().describe("subagentId from spawn stderr") },
|
|
2776
|
+
positionals: ["id"],
|
|
2777
|
+
output: { json: z.object({
|
|
2778
|
+
id: z.string(),
|
|
2779
|
+
aborted: z.boolean()
|
|
2780
|
+
}) },
|
|
2781
|
+
run: async ({ parsed, io }) => {
|
|
2782
|
+
const id = String(parsed.values.id);
|
|
2783
|
+
if (!this.jobs.has(id)) {
|
|
2784
|
+
await io.stderr(`demi agent abort: no running subagent "${id}"\n`);
|
|
2785
|
+
return { exitCode: 1 };
|
|
2786
|
+
}
|
|
2787
|
+
await this.abortSubtree(id);
|
|
2788
|
+
await io.stdout(parsed.json ? `${JSON.stringify({
|
|
2789
|
+
id,
|
|
2790
|
+
aborted: true
|
|
2791
|
+
})}\n` : `aborted ${id}\n`);
|
|
2792
|
+
return { exitCode: 0 };
|
|
2793
|
+
}
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
name: "list",
|
|
2797
|
+
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.",
|
|
2798
|
+
output: { json: z.object({ agents: z.array(z.unknown()) }) },
|
|
2799
|
+
run: async ({ parsed, io }) => {
|
|
2800
|
+
const jobs = [...this.jobs.values()];
|
|
2801
|
+
if (parsed.json) {
|
|
2802
|
+
await io.stdout(`${JSON.stringify({ agents: jobs.map((job) => this.snapshot(job, false)) })}\n`);
|
|
2803
|
+
return { exitCode: 0 };
|
|
2804
|
+
}
|
|
2805
|
+
if (jobs.length === 0) {
|
|
2806
|
+
await io.stdout("no running subagents\n");
|
|
2807
|
+
return { exitCode: 0 };
|
|
2808
|
+
}
|
|
2809
|
+
for (const job of jobs) await io.stdout(`${this.renderListLine(job)}\n`);
|
|
2810
|
+
return { exitCode: 0 };
|
|
2811
|
+
}
|
|
2812
|
+
},
|
|
2813
|
+
{
|
|
2814
|
+
name: "show",
|
|
2815
|
+
summary: "Bounded snapshot of one running child: execution state, recent tool titles with durations, last assistant text. Every duration is relative to now — use the ages to tell motion from stall. Omits tool outputs, file contents, and older turns. A read, not a wait — not for polling loops.",
|
|
2816
|
+
input: { id: z.string().describe("subagentId from spawn stderr") },
|
|
2817
|
+
positionals: ["id"],
|
|
2818
|
+
output: { json: z.object({ agent: z.unknown() }) },
|
|
2819
|
+
run: async ({ parsed, io }) => {
|
|
2820
|
+
const id = String(parsed.values.id);
|
|
2821
|
+
const job = this.jobs.get(id);
|
|
2822
|
+
if (!job) {
|
|
2823
|
+
await io.stderr(`demi agent show: no running subagent "${id}"\n`);
|
|
2824
|
+
return { exitCode: 1 };
|
|
2825
|
+
}
|
|
2826
|
+
if (parsed.json) await io.stdout(`${JSON.stringify({ agent: this.snapshot(job, true) })}\n`);
|
|
2827
|
+
else await io.stdout(this.renderShow(job));
|
|
2828
|
+
return { exitCode: 0 };
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
]
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
hasShell(shellId) {
|
|
2835
|
+
return this.environmentScopeForShell(shellId) !== null;
|
|
2836
|
+
}
|
|
2837
|
+
/** Resolves the child scope owning a shell, for the command bridge dispatch. */
|
|
2838
|
+
environmentScopeForShell(shellId) {
|
|
2839
|
+
for (const job of this.jobs.values()) for (const environment of job.environments.values()) if (environment.getShell(shellId)) return {
|
|
2840
|
+
environment,
|
|
2841
|
+
commandNames: new Set(job.commandNames),
|
|
2842
|
+
agentSessionId: job.id
|
|
2843
|
+
};
|
|
2844
|
+
return null;
|
|
2845
|
+
}
|
|
2846
|
+
/** Re-emits `subagent started` + transcript reset for every running child (transcript resync). */
|
|
2847
|
+
replay() {
|
|
2848
|
+
for (const job of this.jobs.values()) {
|
|
2849
|
+
this.options.emit({
|
|
2850
|
+
type: "subagent",
|
|
2851
|
+
event: "started",
|
|
2852
|
+
job: this.wireJob(job)
|
|
2853
|
+
});
|
|
2854
|
+
const transcript = job.session.transcript();
|
|
2855
|
+
this.options.emit({
|
|
2856
|
+
type: "subagent_transcript_reset",
|
|
2857
|
+
subagentId: job.id,
|
|
2858
|
+
blocks: structuredClone(transcript.blocks),
|
|
2859
|
+
revision: transcript.revision
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
async dispose() {
|
|
2864
|
+
this.isDisposed = true;
|
|
2865
|
+
for (const job of [...this.jobs.values()]) await this.closeJob(job, "aborted");
|
|
2866
|
+
}
|
|
2867
|
+
async abortSubtree(id) {
|
|
2868
|
+
const job = this.jobs.get(id);
|
|
2869
|
+
if (!job) return;
|
|
2870
|
+
await this.closeJob(job, "aborted");
|
|
2871
|
+
}
|
|
2872
|
+
async spawn(input) {
|
|
2873
|
+
const parent = this.parentSession;
|
|
2874
|
+
if (!parent) throw new Error("subagent supervisor has no parent session");
|
|
2875
|
+
if (this.isDisposed) throw new Error("parent session is closing");
|
|
2876
|
+
if (this.jobs.size >= 8) throw new Error(`at most 8 running subagents per session; abort one or wait for a result`);
|
|
2877
|
+
const profile = this.resolveProfile(input.profileName);
|
|
2878
|
+
const id = createId();
|
|
2879
|
+
const metadata = parent.actionMetadata();
|
|
2880
|
+
const agentNode = this.createChildAgentNode(id, input.description);
|
|
2881
|
+
const commands = injectSubagentCommand(profile.commands ? profile.commands([...this.options.parentCommands]) : [...this.options.parentCommands], agentNode);
|
|
2882
|
+
const commandRegistry = new CommandRegistry();
|
|
2883
|
+
for (const command of commands) commandRegistry.register(command);
|
|
2884
|
+
const commandNames = commandRegistry.list().map((command) => command.name);
|
|
2885
|
+
const commandsPrompt = commandRegistry.renderHelp();
|
|
2886
|
+
let settleClosed;
|
|
2887
|
+
const closed = new Promise((resolve) => {
|
|
2888
|
+
settleClosed = resolve;
|
|
2889
|
+
});
|
|
2890
|
+
const job = {
|
|
2891
|
+
id,
|
|
2892
|
+
description: input.description,
|
|
2893
|
+
profile,
|
|
2894
|
+
profileName: input.profileName ?? (this.options.profiles ? profile.name : null),
|
|
2895
|
+
metadata,
|
|
2896
|
+
session: null,
|
|
2897
|
+
commandRegistry,
|
|
2898
|
+
commandNames,
|
|
2899
|
+
environments: /* @__PURE__ */ new Map(),
|
|
2900
|
+
pendingEnvironments: /* @__PURE__ */ new Map(),
|
|
2901
|
+
readonlyHosts: /* @__PURE__ */ new WeakMap(),
|
|
2902
|
+
spawnedAt: Date.now(),
|
|
2903
|
+
lastEventAt: Date.now(),
|
|
2904
|
+
tools: [],
|
|
2905
|
+
lastAssistantTextAt: null,
|
|
2906
|
+
phase: "running",
|
|
2907
|
+
failure: null,
|
|
2908
|
+
unsubscribe: noop,
|
|
2909
|
+
closed,
|
|
2910
|
+
settleClosed,
|
|
2911
|
+
isClosing: false
|
|
2912
|
+
};
|
|
2913
|
+
const agent = this.options.agent;
|
|
2914
|
+
const preamble = this.subagentPreamble(id);
|
|
2915
|
+
const runtime = {
|
|
2916
|
+
harnessName: agent.name,
|
|
2917
|
+
initialState: () => agent.initialState(),
|
|
2918
|
+
systemPrompt: (ctx) => (profile.systemPrompt ?? agent.systemPrompt).call(agent, {
|
|
2919
|
+
...ctx,
|
|
2920
|
+
commandsPrompt
|
|
2921
|
+
}),
|
|
2922
|
+
preamble: async (ctx) => {
|
|
2923
|
+
const inheritedPreamble = profile.systemPrompt ? null : await agent.preamble?.(ctx) ?? null;
|
|
2924
|
+
return inheritedPreamble ? `${inheritedPreamble}\n\n${preamble}` : preamble;
|
|
2925
|
+
},
|
|
2926
|
+
tools: () => createStandardAgentTools({
|
|
2927
|
+
environment: (ctx) => this.childEnvironment(job, ctx),
|
|
2928
|
+
scheduleYield: (ctx, durationMs) => job.session.scheduleYieldWakeup(durationMs, ctx.metadata)
|
|
2929
|
+
})
|
|
2930
|
+
};
|
|
2931
|
+
job.session = new AgentSession({
|
|
2932
|
+
provider: parent.cloneProviderRuntime(),
|
|
2933
|
+
model: profile.model ?? structuredClone(parent.modelSelection),
|
|
2934
|
+
cwd: this.options.cwd,
|
|
2935
|
+
runtime,
|
|
2936
|
+
state: agent.initialState()
|
|
2937
|
+
}, {
|
|
2938
|
+
agentSessionId: id,
|
|
2939
|
+
...this.options.sessionOptions
|
|
2940
|
+
});
|
|
2941
|
+
job.unsubscribe = job.session.subscribe((event) => {
|
|
2942
|
+
if (event.type !== "transcript_changed") return;
|
|
2943
|
+
this.recordTelemetry(job, event.patches);
|
|
2944
|
+
this.options.emit({
|
|
2945
|
+
type: "subagent_transcript_patch",
|
|
2946
|
+
subagentId: id,
|
|
2947
|
+
patches: event.patches,
|
|
2948
|
+
revision: event.revision
|
|
2949
|
+
});
|
|
2950
|
+
});
|
|
2951
|
+
this.jobs.set(id, job);
|
|
2952
|
+
this.options.emit({
|
|
2953
|
+
type: "subagent",
|
|
2954
|
+
event: "started",
|
|
2955
|
+
job: this.wireJob(job)
|
|
2956
|
+
});
|
|
2957
|
+
this.options.emit({
|
|
2958
|
+
type: "subagent_transcript_reset",
|
|
2959
|
+
subagentId: id,
|
|
2960
|
+
blocks: [],
|
|
2961
|
+
revision: 0
|
|
2962
|
+
});
|
|
2963
|
+
this.watchChild(job, input.prompt);
|
|
2964
|
+
return job;
|
|
2965
|
+
}
|
|
2966
|
+
/** Delivers one steer to a child: mid-turn as a steer, idle as a new user turn. */
|
|
2967
|
+
async steerChild(job, message) {
|
|
2968
|
+
const content = [{
|
|
2969
|
+
type: "text",
|
|
2970
|
+
text: message
|
|
2971
|
+
}];
|
|
2972
|
+
if (job.session.phase() !== "idle") try {
|
|
2973
|
+
await job.session.steer(content);
|
|
2974
|
+
return;
|
|
2975
|
+
} catch {}
|
|
2976
|
+
this.watchTurn(job, job.session.send(content));
|
|
2977
|
+
}
|
|
2978
|
+
async pumpStdinSteers(id, stdinStream) {
|
|
2979
|
+
try {
|
|
2980
|
+
for await (const chunk of stdinStream) {
|
|
2981
|
+
const job = this.jobs.get(id);
|
|
2982
|
+
if (!job) return;
|
|
2983
|
+
const message = decodeUtf8(chunk).trim();
|
|
2984
|
+
if (message) await this.steerChild(job, message);
|
|
2985
|
+
}
|
|
2986
|
+
} catch {}
|
|
2987
|
+
}
|
|
2988
|
+
watchChild(job, prompt) {
|
|
2989
|
+
const opening = job.session.send([{
|
|
2990
|
+
type: "text",
|
|
2991
|
+
text: prompt
|
|
2992
|
+
}], job.metadata ? { metadata: job.metadata } : {});
|
|
2993
|
+
this.watchTurn(job, opening);
|
|
2994
|
+
}
|
|
2995
|
+
/** Tracks one child turn to session quiescence, then closes the job with its outcome. */
|
|
2996
|
+
async watchTurn(job, turn) {
|
|
2997
|
+
try {
|
|
2998
|
+
await turn;
|
|
2999
|
+
} catch (error) {
|
|
3000
|
+
job.failure = errorMessage(error);
|
|
3001
|
+
await this.closeJob(job, "error");
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
await this.waitForQuiescence(job);
|
|
3005
|
+
if (job.phase === "running" && !job.isClosing) await this.closeJob(job, "completed");
|
|
3006
|
+
}
|
|
3007
|
+
/** Resolves when the child is idle with no pending yield wakeups (or the job closed). */
|
|
3008
|
+
async waitForQuiescence(job) {
|
|
3009
|
+
while (!job.isClosing && (job.session.phase() !== "idle" || job.session.hasPendingYields())) await new Promise((resolve) => {
|
|
3010
|
+
let settled = false;
|
|
3011
|
+
const finish = () => {
|
|
3012
|
+
if (settled) return;
|
|
3013
|
+
settled = true;
|
|
3014
|
+
unsubscribe();
|
|
3015
|
+
resolve();
|
|
3016
|
+
};
|
|
3017
|
+
const unsubscribe = job.session.subscribe((event) => {
|
|
3018
|
+
if (event.type === "phase_changed") finish();
|
|
3019
|
+
});
|
|
3020
|
+
job.closed.then(finish);
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
async closeJob(job, phase) {
|
|
3024
|
+
if (job.isClosing) return;
|
|
3025
|
+
job.isClosing = true;
|
|
3026
|
+
job.phase = phase;
|
|
3027
|
+
job.unsubscribe();
|
|
3028
|
+
this.jobs.delete(job.id);
|
|
3029
|
+
const result = phase === "completed" ? boundedResultText(lastAssistantText(job.session.transcript().blocks)) : void 0;
|
|
3030
|
+
await job.session.dispose().catch(noop);
|
|
3031
|
+
const environments = /* @__PURE__ */ new Set([...job.environments.values()]);
|
|
3032
|
+
for (const pending of job.pendingEnvironments.values()) {
|
|
3033
|
+
const environment = await pending.catch(() => null);
|
|
3034
|
+
if (environment) environments.add(environment);
|
|
3035
|
+
}
|
|
3036
|
+
for (const environment of environments) await environment.disposeAllShells().catch(noop);
|
|
3037
|
+
const close = {
|
|
3038
|
+
phase,
|
|
3039
|
+
...result !== void 0 ? { result } : {},
|
|
3040
|
+
...job.failure ? { failure: job.failure } : {}
|
|
3041
|
+
};
|
|
3042
|
+
this.options.emit({
|
|
3043
|
+
type: "subagent",
|
|
3044
|
+
event: "closed",
|
|
3045
|
+
job: this.wireJob(job, result)
|
|
3046
|
+
});
|
|
3047
|
+
job.settleClosed(close);
|
|
3048
|
+
this.notifyIdleParent(job, close);
|
|
3049
|
+
}
|
|
3050
|
+
/** Wakes an idle parent with a user send; a parent blocked in the spawn gets the tool result instead. */
|
|
3051
|
+
notifyIdleParent(job, close) {
|
|
3052
|
+
if (this.isDisposed) return;
|
|
3053
|
+
const parent = this.parentSession;
|
|
3054
|
+
if (!parent || parent.phase() !== "idle") return;
|
|
3055
|
+
const label = `subagent ${job.id}${job.description ? ` — ${job.description}` : ""}`;
|
|
3056
|
+
const body = close.phase === "completed" ? `[${label}] completed.\nResult:\n${close.result || "(empty)"}` : close.phase === "aborted" ? `[${label}] aborted.` : `[${label}] failed: ${close.failure ?? "unknown error"}`;
|
|
3057
|
+
parent.send([{
|
|
3058
|
+
type: "text",
|
|
3059
|
+
text: body
|
|
3060
|
+
}]).catch(noop);
|
|
3061
|
+
}
|
|
3062
|
+
createChildAgentNode(childId, description) {
|
|
3063
|
+
return {
|
|
3064
|
+
name: "agent",
|
|
3065
|
+
summary: "Subagent bridge to the parent session.",
|
|
3066
|
+
subcommands: [{
|
|
3067
|
+
name: "send-parent",
|
|
3068
|
+
summary: "Send an interim user message to the parent session. The parent sees it only when it is not blocked waiting on this session. Your result is still the last assistant text when this session ends, not this message.",
|
|
3069
|
+
input: { message: z.string().optional().describe("Message body; positional, or stdin/heredoc when omitted.") },
|
|
3070
|
+
positionals: ["message"],
|
|
3071
|
+
stdinField: "message",
|
|
3072
|
+
output: { json: z.object({ accepted: z.boolean() }) },
|
|
3073
|
+
run: async ({ parsed, io }) => {
|
|
3074
|
+
const message = String(parsed.values.message ?? "").trim();
|
|
3075
|
+
if (!message) {
|
|
3076
|
+
await io.stderr("demi agent send-parent: message must not be empty\n");
|
|
3077
|
+
return { exitCode: 1 };
|
|
3078
|
+
}
|
|
3079
|
+
this.deliverToParent(childId, description, message);
|
|
3080
|
+
await io.stdout(parsed.json ? `${JSON.stringify({ accepted: true })}\n` : "sent\n");
|
|
3081
|
+
return { exitCode: 0 };
|
|
3082
|
+
}
|
|
3083
|
+
}]
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
deliverToParent(childId, description, message) {
|
|
3087
|
+
const parent = this.parentSession;
|
|
3088
|
+
if (!parent || this.isDisposed) return;
|
|
3089
|
+
const content = [{
|
|
3090
|
+
type: "text",
|
|
3091
|
+
text: `[subagent ${childId}${description ? ` — ${description}` : ""}] ${message}`
|
|
3092
|
+
}];
|
|
3093
|
+
if (parent.phase() !== "idle") {
|
|
3094
|
+
parent.steer(content).catch(() => {
|
|
3095
|
+
parent.send(content).catch(noop);
|
|
3096
|
+
});
|
|
3097
|
+
return;
|
|
3098
|
+
}
|
|
3099
|
+
parent.send(content).catch(noop);
|
|
3100
|
+
}
|
|
3101
|
+
async childEnvironment(job, ctx) {
|
|
3102
|
+
const resolved = await this.options.agent.host({
|
|
3103
|
+
agentSessionId: job.id,
|
|
3104
|
+
state: ctx.state,
|
|
3105
|
+
cwd: this.options.cwd,
|
|
3106
|
+
metadata: ctx.metadata
|
|
3107
|
+
});
|
|
3108
|
+
let host = resolved;
|
|
3109
|
+
if (job.profile.readonly) {
|
|
3110
|
+
const wrapped = job.readonlyHosts.get(resolved) ?? createReadonlyHost(resolved);
|
|
3111
|
+
job.readonlyHosts.set(resolved, wrapped);
|
|
3112
|
+
host = wrapped;
|
|
3113
|
+
}
|
|
3114
|
+
const existing = job.environments.get(host);
|
|
3115
|
+
if (existing) return existing;
|
|
3116
|
+
const pending = job.pendingEnvironments.get(host);
|
|
3117
|
+
if (pending) return pending;
|
|
3118
|
+
const creation = this.createChildEnvironment(job, host);
|
|
3119
|
+
job.pendingEnvironments.set(host, creation);
|
|
3120
|
+
try {
|
|
3121
|
+
const environment = await creation;
|
|
3122
|
+
job.environments.set(host, environment);
|
|
3123
|
+
return environment;
|
|
3124
|
+
} finally {
|
|
3125
|
+
job.pendingEnvironments.delete(host);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
async createChildEnvironment(job, host) {
|
|
3129
|
+
const prepared = this.options.prepareShell && !job.profile.readonly ? await this.options.prepareShell({
|
|
3130
|
+
agentSessionId: job.id,
|
|
3131
|
+
host,
|
|
3132
|
+
commandNames: job.commandNames,
|
|
3133
|
+
shell: this.options.shellOptions
|
|
3134
|
+
}) : this.options.shellOptions;
|
|
3135
|
+
return new BashEnvironment({
|
|
3136
|
+
...prepared,
|
|
3137
|
+
initialEnv: {
|
|
3138
|
+
...prepared.initialEnv,
|
|
3139
|
+
DEMI_SUBAGENT_ID: job.id,
|
|
3140
|
+
DEMI_PARENT_SESSION_ID: this.parentSession?.id() ?? "",
|
|
3141
|
+
DEMI_SUBAGENT_DEPTH: "1"
|
|
3142
|
+
},
|
|
3143
|
+
host,
|
|
3144
|
+
commands: job.commandRegistry
|
|
3145
|
+
});
|
|
3146
|
+
}
|
|
3147
|
+
resolveProfile(name) {
|
|
3148
|
+
const profiles = this.options.profiles;
|
|
3149
|
+
const implicitDefault = {
|
|
3150
|
+
name: "default",
|
|
3151
|
+
description: "Inherits the parent harness, model, Host, and commands."
|
|
3152
|
+
};
|
|
3153
|
+
if (!profiles || profiles.length === 0) {
|
|
3154
|
+
if (name !== void 0 && name !== "default") throw new Error(`unknown profile "${name}" (available: default)`);
|
|
3155
|
+
return implicitDefault;
|
|
3156
|
+
}
|
|
3157
|
+
const target = name ?? "default";
|
|
3158
|
+
const profile = profiles.find((candidate) => candidate.name === target);
|
|
3159
|
+
if (profile) return profile;
|
|
3160
|
+
if (name === void 0) return implicitDefault;
|
|
3161
|
+
throw new Error(`unknown profile "${name}" (available: ${this.configuredProfileNames().join(", ")})`);
|
|
3162
|
+
}
|
|
3163
|
+
configuredProfileNames() {
|
|
3164
|
+
const names = (this.options.profiles ?? []).map((profile) => profile.name);
|
|
3165
|
+
return names.includes("default") ? names : ["default", ...names];
|
|
3166
|
+
}
|
|
3167
|
+
subagentPreamble(childId) {
|
|
3168
|
+
return [
|
|
3169
|
+
`You are a subagent: an isolated child agent session (id ${childId}) spawned by a parent agent session. Your transcript starts empty; the task brief in the first user message is your entire context.`,
|
|
3170
|
+
"When you end your turn with no scheduled wakeups, the session ends and your last assistant text is returned to the parent as the result. Write it for the parent agent, in the shape the task brief asked for.",
|
|
3171
|
+
"`demi agent send-parent <message>` sends an interim user message to the parent; it is seen only when the parent is not blocked waiting on this session.",
|
|
3172
|
+
"You are not talking to the product user; do not address them."
|
|
3173
|
+
].join("\n");
|
|
3174
|
+
}
|
|
3175
|
+
recordTelemetry(job, patches) {
|
|
3176
|
+
const now = Date.now();
|
|
3177
|
+
for (const patch of patches) {
|
|
3178
|
+
if (patch.op === "add") {
|
|
3179
|
+
const block = patch.value;
|
|
3180
|
+
if (block.type === "tool_call") {
|
|
3181
|
+
job.tools.push({
|
|
3182
|
+
toolUseId: block.toolUseId,
|
|
3183
|
+
title: toolCallTitle(block),
|
|
3184
|
+
startedAt: now,
|
|
3185
|
+
endedAt: null,
|
|
3186
|
+
status: "executing"
|
|
3187
|
+
});
|
|
3188
|
+
trimToolRecords(job.tools);
|
|
3189
|
+
job.lastEventAt = now;
|
|
3190
|
+
} else if (block.type === "text") {
|
|
3191
|
+
job.lastAssistantTextAt = now;
|
|
3192
|
+
job.lastEventAt = now;
|
|
3193
|
+
}
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
if (patch.op === "append_text") {
|
|
3197
|
+
if (job.session.transcript().blocks[patch.path[1]]?.type === "text") {
|
|
3198
|
+
job.lastAssistantTextAt = now;
|
|
3199
|
+
job.lastEventAt = now;
|
|
3200
|
+
}
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
if (patch.op === "replace_block") {
|
|
3204
|
+
const block = patch.value;
|
|
3205
|
+
if (block.type !== "tool_call" || block.status === "executing") continue;
|
|
3206
|
+
const record = job.tools.find((tool) => tool.toolUseId === block.toolUseId && tool.endedAt === null);
|
|
3207
|
+
if (record) {
|
|
3208
|
+
record.endedAt = now;
|
|
3209
|
+
record.status = block.status === "error" ? "error" : "completed";
|
|
3210
|
+
}
|
|
3211
|
+
job.lastEventAt = now;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
executionOf(job) {
|
|
3216
|
+
const phase = job.session.phase();
|
|
3217
|
+
if (phase === "compacting") return "compacting";
|
|
3218
|
+
if (phase === "idle") return job.session.hasPendingYields() ? "pending_yield" : "idle";
|
|
3219
|
+
return job.session.turnPhase() ?? "provider_streaming";
|
|
3220
|
+
}
|
|
3221
|
+
activityOf(job, execution) {
|
|
3222
|
+
const inflight = [...job.tools].reverse().find((tool) => tool.endedAt === null);
|
|
3223
|
+
if (execution === "tool_executing" && inflight) return inflight.title;
|
|
3224
|
+
if (execution === "provider_streaming") return "streaming";
|
|
3225
|
+
return execution;
|
|
3226
|
+
}
|
|
3227
|
+
executionForMs(job, execution, now) {
|
|
3228
|
+
if (execution === "tool_executing") {
|
|
3229
|
+
const inflight = [...job.tools].reverse().find((tool) => tool.endedAt === null);
|
|
3230
|
+
if (inflight) return now - inflight.startedAt;
|
|
3231
|
+
}
|
|
3232
|
+
return now - job.lastEventAt;
|
|
3233
|
+
}
|
|
3234
|
+
snapshot(job, detailed) {
|
|
3235
|
+
const now = Date.now();
|
|
3236
|
+
const execution = this.executionOf(job);
|
|
3237
|
+
const base = {
|
|
3238
|
+
subagentId: job.id,
|
|
3239
|
+
description: job.description,
|
|
3240
|
+
profile: job.profileName,
|
|
3241
|
+
phase: job.phase,
|
|
3242
|
+
elapsedMs: now - job.spawnedAt,
|
|
3243
|
+
lastEventMs: now - job.lastEventAt,
|
|
3244
|
+
execution,
|
|
3245
|
+
activity: this.activityOf(job, execution)
|
|
3246
|
+
};
|
|
3247
|
+
if (!detailed) return base;
|
|
3248
|
+
const text = lastAssistantText(job.session.transcript().blocks);
|
|
3249
|
+
return {
|
|
3250
|
+
...base,
|
|
3251
|
+
executionForMs: this.executionForMs(job, execution, now),
|
|
3252
|
+
tools: job.tools.slice(-8).map((tool) => ({
|
|
3253
|
+
title: tool.title,
|
|
3254
|
+
status: tool.status,
|
|
3255
|
+
durationMs: (tool.endedAt ?? now) - tool.startedAt,
|
|
3256
|
+
endedAgoMs: tool.endedAt === null ? null : now - tool.endedAt
|
|
3257
|
+
})),
|
|
3258
|
+
lastAssistantText: boundedResultText(text),
|
|
3259
|
+
lastAssistantTextAgoMs: job.lastAssistantTextAt === null ? null : now - job.lastAssistantTextAt
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
renderListLine(job) {
|
|
3263
|
+
const now = Date.now();
|
|
3264
|
+
const execution = this.executionOf(job);
|
|
3265
|
+
return [
|
|
3266
|
+
job.id,
|
|
3267
|
+
job.phase,
|
|
3268
|
+
`up ${formatDuration(now - job.spawnedAt)}`,
|
|
3269
|
+
`last-event ${formatDuration(now - job.lastEventAt)} ago`,
|
|
3270
|
+
`profile=${job.profileName ?? "default"}`,
|
|
3271
|
+
job.description ? `"${job.description}"` : "(no description)",
|
|
3272
|
+
`execution=${execution}`,
|
|
3273
|
+
`activity=${this.activityOf(job, execution)}`
|
|
3274
|
+
].join(" ");
|
|
3275
|
+
}
|
|
3276
|
+
renderShow(job) {
|
|
3277
|
+
const now = Date.now();
|
|
3278
|
+
const execution = this.executionOf(job);
|
|
3279
|
+
const lines = [
|
|
3280
|
+
`id: ${job.id}`,
|
|
3281
|
+
`description: ${job.description || "(none)"}`,
|
|
3282
|
+
`profile: ${job.profileName ?? "default"}`,
|
|
3283
|
+
`phase: ${job.phase}`,
|
|
3284
|
+
`elapsed: ${formatDuration(now - job.spawnedAt)}`,
|
|
3285
|
+
`execution: ${execution} (for ${formatDuration(this.executionForMs(job, execution, now))})`,
|
|
3286
|
+
`last-event: ${formatDuration(now - job.lastEventAt)} ago`,
|
|
3287
|
+
`activity: ${this.activityOf(job, execution)}`
|
|
3288
|
+
];
|
|
3289
|
+
const recent = job.tools.slice(-8);
|
|
3290
|
+
if (recent.length > 0) {
|
|
3291
|
+
lines.push(`recent tool calls (last ${recent.length}):`);
|
|
3292
|
+
for (const tool of recent) if (tool.endedAt === null) lines.push(` [executing for ${formatDuration(now - tool.startedAt)}] ${tool.title}`);
|
|
3293
|
+
else lines.push(` [${tool.status} in ${formatDuration(tool.endedAt - tool.startedAt)}, ended ${formatDuration(now - tool.endedAt)} ago] ${tool.title}`);
|
|
3294
|
+
}
|
|
3295
|
+
const text = boundedResultText(lastAssistantText(job.session.transcript().blocks));
|
|
3296
|
+
if (text && job.lastAssistantTextAt !== null) {
|
|
3297
|
+
lines.push(`last assistant text (${formatDuration(now - job.lastAssistantTextAt)} ago):`);
|
|
3298
|
+
lines.push(text);
|
|
3299
|
+
} else lines.push("last assistant text: (none yet)");
|
|
3300
|
+
return `${lines.join("\n")}\n`;
|
|
3301
|
+
}
|
|
3302
|
+
wireJob(job, result) {
|
|
3303
|
+
return {
|
|
3304
|
+
subagentId: job.id,
|
|
3305
|
+
parentSessionId: this.parentSession?.id() ?? "",
|
|
3306
|
+
description: job.description,
|
|
3307
|
+
profile: job.profileName,
|
|
3308
|
+
phase: job.phase,
|
|
3309
|
+
...result !== void 0 ? { result } : {}
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
};
|
|
3313
|
+
/**
|
|
3314
|
+
* Grafts the `agent` node under a `demi` root: onto an existing harness `demi`
|
|
3315
|
+
* tree, or as a new `demi` root when the harness has none.
|
|
3316
|
+
*/
|
|
3317
|
+
function injectSubagentCommand(commands, agentNode) {
|
|
3318
|
+
const demiIndex = commands.findIndex((command) => command.name === "demi");
|
|
3319
|
+
if (demiIndex === -1) return [...commands, {
|
|
3320
|
+
name: "demi",
|
|
3321
|
+
summary: "Demi agent runtime commands.",
|
|
3322
|
+
subcommands: [agentNode]
|
|
3323
|
+
}];
|
|
3324
|
+
const demi = commands[demiIndex];
|
|
3325
|
+
const subcommands = [...(demi.subcommands ?? []).filter((command) => command.name !== "agent"), agentNode];
|
|
3326
|
+
const next = [...commands];
|
|
3327
|
+
next[demiIndex] = {
|
|
3328
|
+
...demi,
|
|
3329
|
+
subcommands
|
|
3330
|
+
};
|
|
3331
|
+
return next;
|
|
3332
|
+
}
|
|
3333
|
+
/**
|
|
3334
|
+
* Host wrapper for read-only subagent profiles: filesystem mutation is
|
|
3335
|
+
* rejected outside the shell's own command-artifacts tree, and process spawn
|
|
3336
|
+
* is rejected outright (a real process cannot be write-restricted).
|
|
3337
|
+
*
|
|
3338
|
+
* Every facet delegates method by method: Host facets are class instances
|
|
3339
|
+
* whose methods live on the prototype, so object spread would drop them.
|
|
3340
|
+
*/
|
|
3341
|
+
function createReadonlyHost(host) {
|
|
3342
|
+
const artifactsRoot = host.commandArtifactsDir.replace(/\/+$/, "");
|
|
3343
|
+
const isArtifactPath = (path, cwd) => {
|
|
3344
|
+
const resolved = path.startsWith("/") ? path : `${(cwd ?? host.defaultCwd).replace(/\/+$/, "")}/${path}`;
|
|
3345
|
+
return resolved === artifactsRoot || resolved.startsWith(`${artifactsRoot}/`);
|
|
3346
|
+
};
|
|
3347
|
+
const deny = (operation) => Promise.reject(/* @__PURE__ */ new Error(`read-only subagent: ${operation} is not permitted on this Host`));
|
|
3348
|
+
return {
|
|
3349
|
+
defaultCwd: host.defaultCwd,
|
|
3350
|
+
commandArtifactsDir: host.commandArtifactsDir,
|
|
3351
|
+
identity: host.identity,
|
|
3352
|
+
store: host.store,
|
|
3353
|
+
fs: {
|
|
3354
|
+
readFile: (path, options) => host.fs.readFile(path, options),
|
|
3355
|
+
exists: (path, options) => host.fs.exists(path, options),
|
|
3356
|
+
stat: (path, options) => host.fs.stat(path, options),
|
|
3357
|
+
lstat: (path, options) => host.fs.lstat(path, options),
|
|
3358
|
+
readdir: ((path, options) => host.fs.readdir(path, options)),
|
|
3359
|
+
readlink: (path, options) => host.fs.readlink(path, options),
|
|
3360
|
+
realpath: (path, options) => host.fs.realpath(path, options),
|
|
3361
|
+
writeFile: (path, data, options) => isArtifactPath(path, options?.cwd) ? host.fs.writeFile(path, data, options) : deny(`write ${path}`),
|
|
3362
|
+
appendFile: (path, data, options) => isArtifactPath(path, options?.cwd) ? host.fs.appendFile(path, data, options) : deny(`append ${path}`),
|
|
3363
|
+
mkdir: (path, options) => isArtifactPath(path, options?.cwd) ? host.fs.mkdir(path, options) : deny(`mkdir ${path}`),
|
|
3364
|
+
rm: (path) => deny(`rm ${path}`),
|
|
3365
|
+
cp: (path, destination, options) => isArtifactPath(destination, options?.cwd) ? host.fs.cp(path, destination, options) : deny(`cp to ${destination}`),
|
|
3366
|
+
mv: (path, destination) => deny(`mv ${path} ${destination}`),
|
|
3367
|
+
chmod: (path) => deny(`chmod ${path}`),
|
|
3368
|
+
symlink: (_target, path) => deny(`symlink ${path}`),
|
|
3369
|
+
link: (_existingPath, path) => deny(`link ${path}`),
|
|
3370
|
+
utimes: (path) => deny(`utimes ${path}`)
|
|
3371
|
+
},
|
|
3372
|
+
process: {
|
|
3373
|
+
openCwd: (path) => host.process.openCwd(path),
|
|
3374
|
+
spawn: () => deny("process spawn")
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
function trimToolRecords(tools) {
|
|
3379
|
+
while (tools.length > SHOW_RECENT_TOOLS) {
|
|
3380
|
+
const index = tools.findIndex((tool) => tool.endedAt !== null);
|
|
3381
|
+
if (index === -1) return;
|
|
3382
|
+
tools.splice(index, 1);
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
function toolCallTitle(block) {
|
|
3386
|
+
try {
|
|
3387
|
+
const input = JSON.parse(block.input);
|
|
3388
|
+
if (typeof input.description === "string" && input.description.trim()) return input.description.trim();
|
|
3389
|
+
} catch {}
|
|
3390
|
+
return block.toolName;
|
|
3391
|
+
}
|
|
3392
|
+
function lastAssistantText(blocks) {
|
|
3393
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
3394
|
+
const block = blocks[i];
|
|
3395
|
+
if (block.type === "text") return block.text;
|
|
3396
|
+
}
|
|
3397
|
+
return "";
|
|
3398
|
+
}
|
|
3399
|
+
function boundedResultText(text) {
|
|
3400
|
+
return utf8Slice(text, 0, SUBAGENT_RESULT_MAX_BYTES);
|
|
3401
|
+
}
|
|
3402
|
+
function formatDuration(ms) {
|
|
3403
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1e3));
|
|
3404
|
+
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
3405
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
3406
|
+
const seconds = totalSeconds % 60;
|
|
3407
|
+
if (minutes < 60) return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`;
|
|
3408
|
+
const hours = Math.floor(minutes / 60);
|
|
3409
|
+
const remMinutes = minutes % 60;
|
|
3410
|
+
return remMinutes > 0 ? `${hours}h${remMinutes}m` : `${hours}h`;
|
|
3411
|
+
}
|
|
3412
|
+
//#endregion
|
|
2638
3413
|
//#region src/server.ts
|
|
2639
3414
|
var RunCommandLineShellNotFoundError = class extends Error {
|
|
2640
3415
|
shellId;
|
|
@@ -2751,6 +3526,7 @@ var AgentTransportBindingImpl = class {
|
|
|
2751
3526
|
currentProviderId = null;
|
|
2752
3527
|
currentSessionId = null;
|
|
2753
3528
|
currentCommandNames = /* @__PURE__ */ new Set();
|
|
3529
|
+
supervisor = null;
|
|
2754
3530
|
unsubscribeSession = null;
|
|
2755
3531
|
unsubscribeTransport = null;
|
|
2756
3532
|
closed = false;
|
|
@@ -2928,6 +3704,7 @@ var AgentTransportBindingImpl = class {
|
|
|
2928
3704
|
const session = this.sessionFor("sync_transcript");
|
|
2929
3705
|
if (!session) return;
|
|
2930
3706
|
this.sendTranscriptReset(session);
|
|
3707
|
+
this.supervisor?.replay();
|
|
2931
3708
|
return;
|
|
2932
3709
|
}
|
|
2933
3710
|
case "close":
|
|
@@ -2965,7 +3742,19 @@ var AgentTransportBindingImpl = class {
|
|
|
2965
3742
|
state,
|
|
2966
3743
|
cwd: frame.cwd
|
|
2967
3744
|
};
|
|
2968
|
-
const
|
|
3745
|
+
const harnessCommands = await agent.commands?.(harnessContext) ?? [];
|
|
3746
|
+
const profiles = await agent.agents?.(harnessContext) ?? null;
|
|
3747
|
+
const supervisor = new ChildSupervisor({
|
|
3748
|
+
agent,
|
|
3749
|
+
cwd: frame.cwd,
|
|
3750
|
+
profiles,
|
|
3751
|
+
parentCommands: harnessCommands,
|
|
3752
|
+
shellOptions: this.shellOptions,
|
|
3753
|
+
prepareShell: this.prepareShell,
|
|
3754
|
+
sessionOptions: this.sessionOptions,
|
|
3755
|
+
emit: (subagentFrame) => this.send(subagentFrame)
|
|
3756
|
+
});
|
|
3757
|
+
const commands = injectSubagentCommand(harnessCommands, supervisor.rootCommandNode());
|
|
2969
3758
|
const commandRegistry = new CommandRegistry();
|
|
2970
3759
|
for (const command of commands) commandRegistry.register(command);
|
|
2971
3760
|
const commandNames = commandRegistry.list().map((command) => command.name);
|
|
@@ -3014,6 +3803,8 @@ var AgentTransportBindingImpl = class {
|
|
|
3014
3803
|
});
|
|
3015
3804
|
sessionRef = session;
|
|
3016
3805
|
this.session = session;
|
|
3806
|
+
supervisor.attachParent(session);
|
|
3807
|
+
this.supervisor = supervisor;
|
|
3017
3808
|
this.currentAgent = agent;
|
|
3018
3809
|
this.currentCommandRegistry = commandRegistry;
|
|
3019
3810
|
this.currentCwd = frame.cwd;
|
|
@@ -3107,6 +3898,7 @@ var AgentTransportBindingImpl = class {
|
|
|
3107
3898
|
const agent = this.currentAgent;
|
|
3108
3899
|
const cwd = this.currentCwd;
|
|
3109
3900
|
try {
|
|
3901
|
+
if (this.supervisor) await this.supervisor.dispose();
|
|
3110
3902
|
if (session) await session.dispose();
|
|
3111
3903
|
const pendingEnvironments = await Promise.allSettled(this.pendingEnvironmentsByHost.values());
|
|
3112
3904
|
const environments = new Set(this.environmentsByHost.values());
|
|
@@ -3122,6 +3914,7 @@ var AgentTransportBindingImpl = class {
|
|
|
3122
3914
|
this.unsubscribeSession?.();
|
|
3123
3915
|
this.unsubscribeSession = null;
|
|
3124
3916
|
this.session = null;
|
|
3917
|
+
this.supervisor = null;
|
|
3125
3918
|
this.currentAgent = null;
|
|
3126
3919
|
this.environmentsByHost.clear();
|
|
3127
3920
|
this.pendingEnvironmentsByHost.clear();
|
|
@@ -3170,10 +3963,15 @@ var AgentTransportBindingImpl = class {
|
|
|
3170
3963
|
}
|
|
3171
3964
|
/** Backs `AgentServer.runCommandLine` — see its doc comment for the contract. */
|
|
3172
3965
|
async runCommandLine(shellId, name, args, opts) {
|
|
3173
|
-
const
|
|
3174
|
-
const
|
|
3175
|
-
|
|
3176
|
-
|
|
3966
|
+
const parentEnvironment = this.environmentForShell(shellId);
|
|
3967
|
+
const scope = parentEnvironment && this.currentSessionId ? {
|
|
3968
|
+
environment: parentEnvironment,
|
|
3969
|
+
commandNames: this.currentCommandNames,
|
|
3970
|
+
agentSessionId: this.currentSessionId
|
|
3971
|
+
} : this.supervisor?.environmentScopeForShell(shellId) ?? null;
|
|
3972
|
+
if (!scope) throw new Error("runCommandLine: session has no active shell environment");
|
|
3973
|
+
const { environment, agentSessionId } = scope;
|
|
3974
|
+
if (!scope.commandNames.has(name)) throw new RunCommandLineCommandNotRegisteredError(name);
|
|
3177
3975
|
const words = [name, ...args].map(shellQuote).join(" ");
|
|
3178
3976
|
let script = words;
|
|
3179
3977
|
if (opts.stdin.length > 0) {
|
|
@@ -3213,7 +4011,7 @@ var AgentTransportBindingImpl = class {
|
|
|
3213
4011
|
}
|
|
3214
4012
|
}
|
|
3215
4013
|
hasShell(shellId) {
|
|
3216
|
-
return this.environmentForShell(shellId) !== null;
|
|
4014
|
+
return this.environmentForShell(shellId) !== null || (this.supervisor?.hasShell(shellId) ?? false);
|
|
3217
4015
|
}
|
|
3218
4016
|
async resolveEnvironment(ctx, handle) {
|
|
3219
4017
|
const agent = this.currentAgent;
|
|
@@ -3441,4 +4239,4 @@ function createProviderMap(providers) {
|
|
|
3441
4239
|
return map;
|
|
3442
4240
|
}
|
|
3443
4241
|
//#endregion
|
|
3444
|
-
export { AgentClient, AgentServer, AgentSession, DEFAULT_MAX_MEDIA_BYTES, DEFAULT_TURN_RETRY_POLICY, ProviderStreamError, RunCommandLineCommandNotRegisteredError, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError, SHELL_VIEW_MAX_CHARS, TranscriptLog, applyTranscriptPatches, cloneBlocks, createInProcessTransportPair, createStandardAgentTools, createWebSocketClientTransport, createWebSocketServerTransport, estimateTranscriptBlockTokens, findResumePoint, finishShellToolResult, isContextLengthExceeded, isRetryableCode, resolveRetryPolicy, retryDelayMs, shellCommandHandleRequired, shellPreviewBudgetTokens, toShellToolResult };
|
|
4242
|
+
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as AgentServerTransport, t as AgentClientTransport } from "./transport-
|
|
1
|
+
import { n as AgentServerTransport, t as AgentClientTransport } from "./transport-BRqZoaeo.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;
|
|
@@ -180,12 +180,33 @@ interface AgentHostContext<State> extends AgentHarnessContext<State> {
|
|
|
180
180
|
agentSessionId: string;
|
|
181
181
|
metadata: AgentMetadata | null;
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* A named subagent configuration. Every field is an override on the parent's
|
|
185
|
+
* setup: omitted fields inherit the parent harness (system prompt), the
|
|
186
|
+
* parent's registered commands, the parent Host, and the parent model.
|
|
187
|
+
*/
|
|
188
|
+
interface SubagentProfile<State = unknown> {
|
|
189
|
+
name: string;
|
|
190
|
+
/** Shown in the spawn command help so the parent model can pick a profile. */
|
|
191
|
+
description: string;
|
|
192
|
+
systemPrompt?(ctx: AgentSystemPromptContext<State>): Promise<string> | string;
|
|
193
|
+
/** Derives the child's registered commands from the parent's list. */
|
|
194
|
+
commands?(parentCommands: Command[]): Command[];
|
|
195
|
+
/** Reject filesystem writes and process spawns on the child's Host. */
|
|
196
|
+
readonly?: boolean;
|
|
197
|
+
model?: ModelSelection;
|
|
198
|
+
}
|
|
183
199
|
interface AgentHarness<State = unknown> {
|
|
184
200
|
name: string;
|
|
185
201
|
initialState(): State;
|
|
186
202
|
/** Return the same Host object for calls that target the same execution environment. */
|
|
187
203
|
host(ctx: AgentHarnessContext<State> | AgentHostContext<State>): Host | Promise<Host>;
|
|
188
204
|
commands?(ctx: AgentHarnessContext<State>): Promise<Command[]> | Command[];
|
|
205
|
+
/**
|
|
206
|
+
* Named subagent profiles for `demi agent`. Omitted: one implicit profile
|
|
207
|
+
* named `default` that fully inherits the parent's setup.
|
|
208
|
+
*/
|
|
209
|
+
agents?(ctx: AgentHarnessContext<State>): Promise<SubagentProfile<State>[]> | SubagentProfile<State>[];
|
|
189
210
|
systemPrompt(ctx: AgentSystemPromptContext<State>): Promise<string> | string;
|
|
190
211
|
preamble?(ctx: AgentPromptContext<State>): Promise<string | null> | string | null;
|
|
191
212
|
resolveReferences?(ctx: AgentReferenceResolveContext<State>, content: UserContentBlock[]): Promise<UserContentBlock[]> | UserContentBlock[];
|
|
@@ -370,6 +391,16 @@ interface ConversationSummary {
|
|
|
370
391
|
createdAt: string;
|
|
371
392
|
updatedAt: string;
|
|
372
393
|
}
|
|
394
|
+
/** One child agent session as seen on the parent connection. */
|
|
395
|
+
interface SubagentJob {
|
|
396
|
+
subagentId: string;
|
|
397
|
+
parentSessionId: string;
|
|
398
|
+
description: string;
|
|
399
|
+
profile: string | null;
|
|
400
|
+
phase: 'running' | 'completed' | 'aborted' | 'error';
|
|
401
|
+
/** Present on `closed`: the child's last assistant text, at most 32 KiB. */
|
|
402
|
+
result?: string;
|
|
403
|
+
}
|
|
373
404
|
type ClientFrame = {
|
|
374
405
|
type: 'open';
|
|
375
406
|
provider: ProviderSelection;
|
|
@@ -489,6 +520,20 @@ type ServerFrame = {
|
|
|
489
520
|
message: string;
|
|
490
521
|
code?: string;
|
|
491
522
|
diagnostics?: ProviderErrorDiagnostics;
|
|
523
|
+
} | {
|
|
524
|
+
type: 'subagent';
|
|
525
|
+
event: 'started' | 'closed';
|
|
526
|
+
job: SubagentJob;
|
|
527
|
+
} | {
|
|
528
|
+
type: 'subagent_transcript_reset';
|
|
529
|
+
subagentId: string;
|
|
530
|
+
blocks: Block[];
|
|
531
|
+
revision: number;
|
|
532
|
+
} | {
|
|
533
|
+
type: 'subagent_transcript_patch';
|
|
534
|
+
subagentId: string;
|
|
535
|
+
patches: TranscriptPatch[];
|
|
536
|
+
revision: number;
|
|
492
537
|
} | {
|
|
493
538
|
type: 'closed';
|
|
494
539
|
};
|
|
@@ -575,6 +620,18 @@ type ClientSessionEvent = {
|
|
|
575
620
|
message: string;
|
|
576
621
|
code?: string;
|
|
577
622
|
diagnostics?: ProviderErrorDiagnostics;
|
|
623
|
+
} | {
|
|
624
|
+
type: 'subagent';
|
|
625
|
+
event: 'started' | 'closed';
|
|
626
|
+
job: SubagentJob;
|
|
627
|
+
} | {
|
|
628
|
+
type: 'subagent_transcript_reset';
|
|
629
|
+
subagentId: string;
|
|
630
|
+
blocks: Block[];
|
|
631
|
+
} | {
|
|
632
|
+
type: 'subagent_transcript_patch';
|
|
633
|
+
subagentId: string;
|
|
634
|
+
patches: TranscriptPatch[];
|
|
578
635
|
} | {
|
|
579
636
|
type: 'opened';
|
|
580
637
|
} | {
|
|
@@ -595,4 +652,4 @@ interface InProcessTransportPair {
|
|
|
595
652
|
}
|
|
596
653
|
declare function createInProcessTransportPair(): InProcessTransportPair;
|
|
597
654
|
//#endregion
|
|
598
|
-
export {
|
|
655
|
+
export { AgentSystemPromptContext as A, CompactionWindow as B, AgentReferenceResolveContext as C, AgentSessionParams as D, AgentSessionOptions as E, ExternalMutationReservation as F, DEFAULT_TURN_RETRY_POLICY as G, TranscriptLog as H, ModelSwitchApply as I, resolveRetryPolicy as J, TurnRetryPolicy as K, SessionEvent as L, AgentToolContext as M, AgentToolInvokeContext as N, AgentSessionRestoreParams as O, AgentToolInvokeResult as P, SessionEventListener as R, AgentPromptContext as S, AgentSessionCloneParams as T, TranscriptOptions as U, DrainedTranscriptPatches as V, estimateTranscriptBlockTokens as W, retryDelayMs as Y, AgentHarnessContext as _, createInProcessTransportPair as a, AgentLifecycleEvent as b, ConversationSummary as c, SubagentJob as d, TranscriptPatch as f, AgentHarness as g, AgentDisposeContext as h, InProcessTransportPair as i, AgentTool as j, AgentSessionStore as k, ServerFrame as l, AbortTarget as m, AgentServerTransport as n, ClientFrame as o, AbortResult as p, isRetryableCode as q, AgentTransport as r, ClientSessionEvent as s, AgentClientTransport as t, ShellCommandStatusLike as u, AgentHarnessRuntime as v, AgentSessionCheckpoint as w, AgentMetadata as x, AgentHostContext as y, SubagentProfile as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-BRqZoaeo.mjs";
|
|
2
2
|
import { ProviderSelection } from "@demicodes/provider";
|
|
3
3
|
import { Block, UserContentBlock } from "@demicodes/core";
|
|
4
4
|
//#region src/client.d.ts
|
|
@@ -304,8 +304,23 @@ var AgentClient = class {
|
|
|
304
304
|
case "shell_write_result":
|
|
305
305
|
case "audit":
|
|
306
306
|
case "retry_scheduled":
|
|
307
|
+
case "subagent":
|
|
307
308
|
this.emit(frame);
|
|
308
309
|
return;
|
|
310
|
+
case "subagent_transcript_reset":
|
|
311
|
+
this.emit({
|
|
312
|
+
type: "subagent_transcript_reset",
|
|
313
|
+
subagentId: frame.subagentId,
|
|
314
|
+
blocks: frame.blocks
|
|
315
|
+
});
|
|
316
|
+
return;
|
|
317
|
+
case "subagent_transcript_patch":
|
|
318
|
+
this.emit({
|
|
319
|
+
type: "subagent_transcript_patch",
|
|
320
|
+
subagentId: frame.subagentId,
|
|
321
|
+
patches: frame.patches
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
309
324
|
case "conversations":
|
|
310
325
|
this.pendingConversationWaiters.shift()?.(frame.conversations);
|
|
311
326
|
return;
|
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.
|
|
4
|
+
"version": "0.19.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@demicodes/core": "^0.
|
|
23
|
-
"@demicodes/provider": "^0.
|
|
24
|
-
"@demicodes/shell": "^0.
|
|
25
|
-
"@demicodes/utils": "^0.
|
|
22
|
+
"@demicodes/core": "^0.19.0",
|
|
23
|
+
"@demicodes/provider": "^0.19.0",
|
|
24
|
+
"@demicodes/shell": "^0.19.0",
|
|
25
|
+
"@demicodes/utils": "^0.19.0",
|
|
26
|
+
"zod": "^4.0.0"
|
|
26
27
|
},
|
|
27
28
|
"license": "Apache-2.0",
|
|
28
29
|
"main": "./dist/index.mjs",
|
|
@@ -43,8 +44,5 @@
|
|
|
43
44
|
"directory": "packages/agent"
|
|
44
45
|
},
|
|
45
46
|
"homepage": "https://github.com/wspl/demi/tree/main/packages/agent#readme",
|
|
46
|
-
"bugs": "https://github.com/wspl/demi/issues"
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"zod": "^4.0.0"
|
|
49
|
-
}
|
|
47
|
+
"bugs": "https://github.com/wspl/demi/issues"
|
|
50
48
|
}
|