@deepseek-ai/dsh-subagent 0.1.2-alpha.5 → 0.1.3-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +11 -7
- package/README.zh.md +11 -7
- package/lib/index.js +958 -914
- package/lib/typert.host.js +44 -11
- package/lib/typert.remote-client.js +3 -2
- package/lib/types/assistant-output.d.ts +3 -3
- package/lib/types/assistant-output.js +5 -4
- package/lib/types/child-agent.js +2 -2
- package/lib/types/continuation-activation.d.ts +251 -0
- package/lib/types/continuation-activation.js +663 -0
- package/lib/types/continuation-messages.d.ts +62 -0
- package/lib/types/continuation-messages.js +102 -0
- package/lib/types/continuation.d.ts +44 -358
- package/lib/types/continuation.js +135 -987
- package/lib/types/control-types.d.ts +2 -0
- package/lib/types/control.d.ts +4 -0
- package/lib/types/control.js +1 -0
- package/lib/types/inbox.d.ts +43 -0
- package/lib/types/inbox.js +61 -0
- package/lib/types/index.d.ts +11 -11
- package/lib/types/index.js +14 -12
- package/lib/types/internal.d.ts +17 -5
- package/lib/types/internal.js +16 -3
- package/lib/types/out-of-process.d.ts +1 -1
- package/lib/types/out-of-process.js +1 -1
- package/lib/types/types.d.ts +46 -2
- package/package.json +44 -44
- package/lib/types/descriptor-seed.d.ts +0 -21
- package/lib/types/descriptor-seed.js +0 -24
package/lib/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { AttachmentError, admitPromptContent } from "@deepseek-ai/dsh-attachment";
|
|
2
1
|
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
3
2
|
import { assertObjectJsonSchema } from "@deepseek-ai/dsh-tools";
|
|
4
3
|
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
5
4
|
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
5
|
+
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain } from "@deepseek-ai/dsh-llm";
|
|
7
|
+
import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, joinAssistantStreamText } from "@deepseek-ai/dsh-llm";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
|
|
10
|
-
import {
|
|
10
|
+
import { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
11
11
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
12
12
|
import { snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
|
|
13
13
|
import { accessSync, constants, statSync } from "node:fs";
|
|
@@ -40,7 +40,8 @@ const CONTROL_ID_SCHEMAS = {
|
|
|
40
40
|
"subagent.prompt": z.object({
|
|
41
41
|
parentSessionId: SESSION_ID_SCHEMA,
|
|
42
42
|
childSessionId: SESSION_ID_SCHEMA,
|
|
43
|
-
mode: z.literal("continuable")
|
|
43
|
+
mode: z.literal("continuable"),
|
|
44
|
+
delivery: z.enum(["queue", "steer"])
|
|
44
45
|
}),
|
|
45
46
|
"subagent.interrupt": z.object({
|
|
46
47
|
parentSessionId: SESSION_ID_SCHEMA,
|
|
@@ -175,15 +176,16 @@ var AssistantOutputFold = class {
|
|
|
175
176
|
partial = [];
|
|
176
177
|
/**
|
|
177
178
|
* Fold one session event: a non-empty assistant message becomes the
|
|
178
|
-
* candidate final answer,
|
|
179
|
-
* fallback; every other event contributes nothing.
|
|
179
|
+
* candidate final answer, while its embedded stream and any log-only attempt
|
|
180
|
+
* extend the streamed fallback; every other event contributes nothing.
|
|
180
181
|
* @param event - the next observed session event.
|
|
181
182
|
*/
|
|
182
183
|
push(event) {
|
|
183
184
|
if (event.type === "assistant/message") {
|
|
184
185
|
const content = event.data.message.content;
|
|
185
186
|
if (content.length > 0) this.message = content;
|
|
186
|
-
}
|
|
187
|
+
}
|
|
188
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
|
|
187
189
|
}
|
|
188
190
|
/**
|
|
189
191
|
* Extend the streamed fallback with text observed outside session events.
|
|
@@ -395,166 +397,6 @@ function renderThrown(value) {
|
|
|
395
397
|
}
|
|
396
398
|
}
|
|
397
399
|
//#endregion
|
|
398
|
-
//#region lib/types/descriptor.js
|
|
399
|
-
/**
|
|
400
|
-
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
401
|
-
* `subagent/descriptor` session event that identifies every session-backed
|
|
402
|
-
* subagent and records whether it is one-shot or continuable. Continuable
|
|
403
|
-
* descriptors additionally preserve the declared composition required for
|
|
404
|
-
* cold resume. Providers append it turn-enclosed in the child's initial turn.
|
|
405
|
-
*
|
|
406
|
-
* The descriptor deliberately snapshots explicit fields rather than the
|
|
407
|
-
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
408
|
-
* make continuation fail merely because it is not JSON, and later composition
|
|
409
|
-
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
410
|
-
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
411
|
-
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
412
|
-
* to one activation's result contract rather than durable child composition.
|
|
413
|
-
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
414
|
-
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
415
|
-
* live parent for authorization but reconstructs child options only from the
|
|
416
|
-
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
417
|
-
* the parent's current one; the resumed route's defaults apply instead.
|
|
418
|
-
*
|
|
419
|
-
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
420
|
-
*/
|
|
421
|
-
/**
|
|
422
|
-
* The current descriptor format version, stamped into every appended
|
|
423
|
-
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
424
|
-
* Supporting another composition input is a deliberate version change, never
|
|
425
|
-
* an implicit extra field.
|
|
426
|
-
*/
|
|
427
|
-
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
428
|
-
const DESCRIPTOR_BASE_KEYS = [
|
|
429
|
-
"version",
|
|
430
|
-
"mode",
|
|
431
|
-
"provider",
|
|
432
|
-
"label"
|
|
433
|
-
];
|
|
434
|
-
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
435
|
-
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
436
|
-
...DESCRIPTOR_BASE_KEYS,
|
|
437
|
-
"agentProvider",
|
|
438
|
-
"agentModel",
|
|
439
|
-
"agentReasoningEffort",
|
|
440
|
-
"persona",
|
|
441
|
-
"toolFilter"
|
|
442
|
-
]);
|
|
443
|
-
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
444
|
-
/** Whether a persisted JSON value is an object record. */
|
|
445
|
-
function isRecord(value) {
|
|
446
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
447
|
-
}
|
|
448
|
-
/** Reject fields outside one versioned record's declared schema. */
|
|
449
|
-
function assertKnownKeys(value, keys, path) {
|
|
450
|
-
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
451
|
-
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
452
|
-
}
|
|
453
|
-
/** Read one optional string field from a persisted descriptor record. */
|
|
454
|
-
function optionalString(value, key) {
|
|
455
|
-
if (!Object.hasOwn(value, key)) return void 0;
|
|
456
|
-
const field = value[key];
|
|
457
|
-
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
458
|
-
return field;
|
|
459
|
-
}
|
|
460
|
-
/** Read one optional string-array field from a persisted tool restriction. */
|
|
461
|
-
function optionalStringArray(value, key) {
|
|
462
|
-
if (!Object.hasOwn(value, key)) return void 0;
|
|
463
|
-
const field = value[key];
|
|
464
|
-
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
465
|
-
const items = field;
|
|
466
|
-
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
467
|
-
return items;
|
|
468
|
-
}
|
|
469
|
-
/** Validate and reconstruct a persisted tool restriction. */
|
|
470
|
-
function parseToolFilter(value) {
|
|
471
|
-
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
472
|
-
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
473
|
-
const allow = optionalStringArray(value, "allow");
|
|
474
|
-
const deny = optionalStringArray(value, "deny");
|
|
475
|
-
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
476
|
-
return {
|
|
477
|
-
...allow !== void 0 ? { allow } : {},
|
|
478
|
-
...deny !== void 0 ? { deny } : {}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
/** Validate one persisted descriptor payload for the current runtime. */
|
|
482
|
-
function parseSubagentDescriptor(value) {
|
|
483
|
-
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
484
|
-
const version = value["version"];
|
|
485
|
-
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
486
|
-
if (version !== 3) return void 0;
|
|
487
|
-
const mode = value["mode"];
|
|
488
|
-
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
489
|
-
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
490
|
-
const provider = value["provider"];
|
|
491
|
-
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
492
|
-
if (mode === "one-shot") {
|
|
493
|
-
const label = optionalString(value, "label");
|
|
494
|
-
return {
|
|
495
|
-
version: 3,
|
|
496
|
-
mode,
|
|
497
|
-
provider,
|
|
498
|
-
...label !== void 0 ? { label } : {}
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
const label = value["label"];
|
|
502
|
-
if (typeof label !== "string") throw new Error("persisted subagent descriptor label must be a string");
|
|
503
|
-
const agentProvider = optionalString(value, "agentProvider");
|
|
504
|
-
const agentModel = optionalString(value, "agentModel");
|
|
505
|
-
const agentReasoningEffort = optionalString(value, "agentReasoningEffort");
|
|
506
|
-
const persona = optionalString(value, "persona");
|
|
507
|
-
const toolFilter = Object.hasOwn(value, "toolFilter") ? parseToolFilter(value["toolFilter"]) : void 0;
|
|
508
|
-
return {
|
|
509
|
-
version: 3,
|
|
510
|
-
mode,
|
|
511
|
-
provider,
|
|
512
|
-
label,
|
|
513
|
-
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
514
|
-
...agentModel !== void 0 ? { agentModel } : {},
|
|
515
|
-
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
516
|
-
...persona !== void 0 ? { persona } : {},
|
|
517
|
-
...toolFilter !== void 0 ? { toolFilter } : {}
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
function snapshotSubagentDescriptor(input) {
|
|
521
|
-
const snapshot = snapshotJsonValue(input.mode === "one-shot" ? {
|
|
522
|
-
version: 3,
|
|
523
|
-
mode: input.mode,
|
|
524
|
-
provider: input.provider,
|
|
525
|
-
...input.label !== void 0 ? { label: input.label } : {}
|
|
526
|
-
} : {
|
|
527
|
-
version: 3,
|
|
528
|
-
mode: input.mode,
|
|
529
|
-
provider: input.provider,
|
|
530
|
-
label: input.label,
|
|
531
|
-
...input.agentProvider !== void 0 ? { agentProvider: input.agentProvider } : {},
|
|
532
|
-
...input.agentModel !== void 0 ? { agentModel: input.agentModel } : {},
|
|
533
|
-
...input.agentReasoningEffort !== void 0 ? { agentReasoningEffort: input.agentReasoningEffort } : {},
|
|
534
|
-
...input.persona !== void 0 ? { persona: input.persona } : {},
|
|
535
|
-
...input.toolFilter !== void 0 ? { toolFilter: input.toolFilter } : {}
|
|
536
|
-
});
|
|
537
|
-
if (snapshot === void 0) throw new Error("subagent descriptor is not losslessly JSON-serializable");
|
|
538
|
-
return snapshot;
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Fold a persisted child log to its supported descriptor. The first
|
|
542
|
-
* `subagent/descriptor` event is authoritative — the establishing provider
|
|
543
|
-
* appends exactly one, so a later same-type event cannot rewrite the declared
|
|
544
|
-
* composition.
|
|
545
|
-
* @param events - the loaded child session events.
|
|
546
|
-
* @returns the descriptor, or `undefined` when the log has none or its
|
|
547
|
-
* version is not {@link SUBAGENT_DESCRIPTOR_VERSION} (the child cannot be
|
|
548
|
-
* classified by this runtime).
|
|
549
|
-
* @throws when a current-version persisted payload does not match its complete
|
|
550
|
-
* declared schema.
|
|
551
|
-
*/
|
|
552
|
-
function foldSubagentDescriptor(events) {
|
|
553
|
-
const event = events.find((candidate) => candidate.type === "subagent/descriptor");
|
|
554
|
-
if (event === void 0) return void 0;
|
|
555
|
-
return parseSubagentDescriptor(event.data);
|
|
556
|
-
}
|
|
557
|
-
//#endregion
|
|
558
400
|
//#region lib/types/child-agent.js
|
|
559
401
|
/**
|
|
560
402
|
* Shared in-process child composition: the delegation-depth budget, the
|
|
@@ -704,8 +546,8 @@ function applyChildComposition(childCtx, parent, composition) {
|
|
|
704
546
|
text: SUBAGENT_DELEGATION_CONTEXT
|
|
705
547
|
});
|
|
706
548
|
if (composition.persona !== void 0) childCtx.systemPrompt.section({
|
|
707
|
-
name: "deployment:persona",
|
|
708
|
-
order: childCtx.systemPrompt.getSectionOrder("
|
|
549
|
+
name: "deployment:persona-prefix",
|
|
550
|
+
order: childCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_PREFIX"),
|
|
709
551
|
text: composition.persona
|
|
710
552
|
});
|
|
711
553
|
if (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);
|
|
@@ -746,144 +588,12 @@ function appendDelegatedPolicyOverrides(childSession, overrides) {
|
|
|
746
588
|
});
|
|
747
589
|
}
|
|
748
590
|
//#endregion
|
|
749
|
-
//#region lib/types/
|
|
750
|
-
/**
|
|
751
|
-
* Seeding of a continuable child's durable descriptor event: the model-hidden
|
|
752
|
-
* record of the child's declared composition before its first request, so a
|
|
753
|
-
* later cold resume can reconstruct it from its own log.
|
|
754
|
-
*
|
|
755
|
-
* @module @deepseek-ai/dsh-subagent/descriptor-seed
|
|
756
|
-
*/
|
|
757
|
-
/**
|
|
758
|
-
* Build the child's creation seed: any inherited parent-history prefix followed
|
|
759
|
-
* by one model-hidden, between-turn `descriptor` event. Staging through a
|
|
760
|
-
* `Session` assigns the sequence number and enforces the same lossless-JSON
|
|
761
|
-
* rules the durable log does.
|
|
762
|
-
* @param childId - the reserved child session id the staged log belongs to.
|
|
763
|
-
* @param seed - the inherited completed-turn prefix, or `undefined` for a fresh child.
|
|
764
|
-
* @param descriptor - the snapshotted composition record to persist.
|
|
765
|
-
* @returns the complete seed events, contiguous from sequence zero.
|
|
766
|
-
*/
|
|
767
|
-
function seedDescriptorTurn(childId, seed, descriptor) {
|
|
768
|
-
const staged = Session.create(childId, seed);
|
|
769
|
-
staged.append("subagent/descriptor", descriptor);
|
|
770
|
-
return staged.snapshotEvents();
|
|
771
|
-
}
|
|
772
|
-
//#endregion
|
|
773
|
-
//#region lib/types/internal.js
|
|
774
|
-
/**
|
|
775
|
-
* Continuation integration markers and host adapters outside the public
|
|
776
|
-
* Service Definition and model-facing Agent messaging contract.
|
|
777
|
-
* @module @deepseek-ai/dsh-subagent/internal
|
|
778
|
-
*/
|
|
779
|
-
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
780
|
-
const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
|
|
781
|
-
/**
|
|
782
|
-
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
783
|
-
* @param definition - the scope-resolved `send_message` candidate.
|
|
784
|
-
* @returns whether the definition carries the internal standard-tool identity.
|
|
785
|
-
*/
|
|
786
|
-
function isAdjacentAgentSendMessageTool(definition) {
|
|
787
|
-
return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Process-stable symbol-keyed Queue delivery shared by the bundled runtime
|
|
791
|
-
* entry and this unbundled internal subpath.
|
|
792
|
-
* @internal
|
|
793
|
-
*/
|
|
794
|
-
const queueSubagentPrompt = Symbol.for("dsh.subagent.queuePrompt");
|
|
795
|
-
//#endregion
|
|
796
|
-
//#region lib/types/continuation.js
|
|
591
|
+
//#region lib/types/continuation-messages.js
|
|
797
592
|
/**
|
|
798
|
-
*
|
|
799
|
-
* persistence, activation admission, the live ownership graph, cold resume,
|
|
800
|
-
* child-first disposal, and settlement delivery to the parent, behind
|
|
801
|
-
* `ctx.subagents`.
|
|
593
|
+
* Model-visible messages owned by continuable-subagent orchestration.
|
|
802
594
|
*
|
|
803
|
-
*
|
|
804
|
-
* {@link Activation} — one residency epoch for a reconstructed child Agent. An
|
|
805
|
-
* Activation is not a request, result, cancellation, or Task boundary: it may
|
|
806
|
-
* execute many FIFO turns and stays resident while descendants it created are
|
|
807
|
-
* still running. The Agent inbox is the only turn queue, so this manager owns
|
|
808
|
-
* residency while the Agent loop owns all turn ordering and execution. No
|
|
809
|
-
* continuable path creates a Task or an intermediate result-bearing wrapper.
|
|
810
|
-
*
|
|
811
|
-
* Because residency is this manager's alone to end, telling the parent that a
|
|
812
|
-
* child settled is its job too. An external `subagent/end` listener cannot do
|
|
813
|
-
* it correctly: that payload names no parent, the child handle is already
|
|
814
|
-
* disposed by then, and the release that wakes the parent's own settlement
|
|
815
|
-
* watcher has already run. See {@link SubagentContinuationManager.notifySettlement}.
|
|
816
|
-
*
|
|
817
|
-
* @module @deepseek-ai/dsh-subagent
|
|
595
|
+
* @module @deepseek-ai/dsh-subagent/continuation-messages
|
|
818
596
|
*/
|
|
819
|
-
var __addDisposableResource$1 = function(env, value, async) {
|
|
820
|
-
if (value !== null && value !== void 0) {
|
|
821
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
822
|
-
var dispose, inner;
|
|
823
|
-
if (async) {
|
|
824
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
825
|
-
dispose = value[Symbol.asyncDispose];
|
|
826
|
-
}
|
|
827
|
-
if (dispose === void 0) {
|
|
828
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
829
|
-
dispose = value[Symbol.dispose];
|
|
830
|
-
if (async) inner = dispose;
|
|
831
|
-
}
|
|
832
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
833
|
-
if (inner) dispose = function() {
|
|
834
|
-
try {
|
|
835
|
-
inner.call(this);
|
|
836
|
-
} catch (e) {
|
|
837
|
-
return Promise.reject(e);
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
|
-
env.stack.push({
|
|
841
|
-
value,
|
|
842
|
-
dispose,
|
|
843
|
-
async
|
|
844
|
-
});
|
|
845
|
-
} else if (async) env.stack.push({ async: true });
|
|
846
|
-
return value;
|
|
847
|
-
};
|
|
848
|
-
var __disposeResources$1 = (function(SuppressedError) {
|
|
849
|
-
return function(env) {
|
|
850
|
-
function fail(e) {
|
|
851
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
852
|
-
env.hasError = true;
|
|
853
|
-
}
|
|
854
|
-
var r, s = 0;
|
|
855
|
-
function next() {
|
|
856
|
-
while (r = env.stack.pop()) try {
|
|
857
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
858
|
-
if (r.dispose) {
|
|
859
|
-
var result = r.dispose.call(r.value);
|
|
860
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
861
|
-
fail(e);
|
|
862
|
-
return next();
|
|
863
|
-
});
|
|
864
|
-
} else s |= 1;
|
|
865
|
-
} catch (e) {
|
|
866
|
-
fail(e);
|
|
867
|
-
}
|
|
868
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
869
|
-
if (env.hasError) throw env.error;
|
|
870
|
-
}
|
|
871
|
-
return next();
|
|
872
|
-
};
|
|
873
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
874
|
-
var e = new Error(message);
|
|
875
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
876
|
-
});
|
|
877
|
-
/**
|
|
878
|
-
* Read one Activation's current disposal transaction. This indirection exists
|
|
879
|
-
* because TypeScript would otherwise narrow repeated reads of the mutable field
|
|
880
|
-
* inside a long-lived closure to constants instead of re-reading runtime state.
|
|
881
|
-
* @param activation - the Activation to inspect.
|
|
882
|
-
* @returns the in-flight or settled disposal, or `undefined` while resident.
|
|
883
|
-
*/
|
|
884
|
-
function disposalOf(activation) {
|
|
885
|
-
return activation.disposal;
|
|
886
|
-
}
|
|
887
597
|
/** Build durable attribution for one adjacent-Agent message. */
|
|
888
598
|
function agentMessageSource(sender) {
|
|
889
599
|
return {
|
|
@@ -892,19 +602,29 @@ function agentMessageSource(sender) {
|
|
|
892
602
|
senderSessionId: sender.id
|
|
893
603
|
};
|
|
894
604
|
}
|
|
895
|
-
/**
|
|
896
|
-
|
|
605
|
+
/**
|
|
606
|
+
* Build the model-visible and durable representation of one adjacent-Agent message.
|
|
607
|
+
* @param sender - exact live Agent that authored the message.
|
|
608
|
+
* @param content - model-visible message blocks supplied by the sender.
|
|
609
|
+
* @returns the durable user-message representation delivered to the recipient.
|
|
610
|
+
*/
|
|
611
|
+
function createAgentMessage(sender, content) {
|
|
897
612
|
return createUserMessage({
|
|
898
613
|
content: [{
|
|
899
614
|
type: "text",
|
|
900
|
-
text: `Agent ${sender.id} sent a message
|
|
615
|
+
text: `Agent ${sender.id} sent a message: `
|
|
901
616
|
}, ...content],
|
|
902
617
|
source: agentMessageSource(sender)
|
|
903
618
|
});
|
|
904
619
|
}
|
|
905
|
-
/**
|
|
906
|
-
|
|
907
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Append adjacent-Agent return guidance to a continuable child's initial task.
|
|
622
|
+
* @param parentId - durable parent session id named in the guidance.
|
|
623
|
+
* @param prompt - initial model-visible task blocks.
|
|
624
|
+
* @returns task blocks followed by the continuable return guidance.
|
|
625
|
+
*/
|
|
626
|
+
function withContinuableReturnGuidance(parentId, prompt) {
|
|
627
|
+
const encodedParentId = JSON.stringify(parentId);
|
|
908
628
|
return [...prompt, {
|
|
909
629
|
type: "text",
|
|
910
630
|
text: `Your parent agent id is ${encodedParentId}. Before you finish, send your result to that agent with send_message({ agent_id: ${encodedParentId}, message: "<self-contained result>" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn.`
|
|
@@ -931,6 +651,101 @@ function settlementSummary(childId, stopReason) {
|
|
|
931
651
|
default: return `${subject} ended abnormally (${String(stopReason)}) before it finished.`;
|
|
932
652
|
}
|
|
933
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* Build the runtime-owned settlement notice delivered to a child's parent.
|
|
656
|
+
* @param childId - durable child session id named in the notice.
|
|
657
|
+
* @param terminal - recorded terminal state for the settled Activation.
|
|
658
|
+
* @returns the durable user-message representation delivered to the parent.
|
|
659
|
+
*/
|
|
660
|
+
function createSettlementMessage(childId, terminal) {
|
|
661
|
+
const summary = settlementSummary(childId, terminal.stopReason);
|
|
662
|
+
return createUserMessage({
|
|
663
|
+
content: [{
|
|
664
|
+
type: "text",
|
|
665
|
+
text: summary
|
|
666
|
+
}, ...terminal.output === void 0 ? [{
|
|
667
|
+
type: "text",
|
|
668
|
+
text: "It left no closing message."
|
|
669
|
+
}] : [{
|
|
670
|
+
type: "text",
|
|
671
|
+
text: "Its closing message:"
|
|
672
|
+
}, ...terminal.output]],
|
|
673
|
+
source: {
|
|
674
|
+
kind: "subagent-settled",
|
|
675
|
+
form: "notice",
|
|
676
|
+
summary: boundContextSummary(summary),
|
|
677
|
+
senderSessionId: childId
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region lib/types/inbox.js
|
|
683
|
+
/**
|
|
684
|
+
* Activation-local admission around one continuable subagent's Agent inbox.
|
|
685
|
+
*
|
|
686
|
+
* @module @deepseek-ai/dsh-subagent/inbox
|
|
687
|
+
*/
|
|
688
|
+
/** Delegate Queue and Steer to one live Agent until its Activation starts closing. */
|
|
689
|
+
var SubagentInbox = class {
|
|
690
|
+
agent;
|
|
691
|
+
closingPromise;
|
|
692
|
+
/**
|
|
693
|
+
* Wrap one live continuable Agent.
|
|
694
|
+
* @param agent - the Agent whose inbox receives accepted deliveries.
|
|
695
|
+
*/
|
|
696
|
+
constructor(agent) {
|
|
697
|
+
this.agent = agent;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Read the Activation's close transaction.
|
|
701
|
+
* @returns the memoized transaction, or `undefined` while delivery remains open.
|
|
702
|
+
*/
|
|
703
|
+
get closing() {
|
|
704
|
+
return this.closingPromise;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Read whether the underlying Agent still has accepted work to claim.
|
|
708
|
+
* @returns whether either Agent inbox destination is non-empty.
|
|
709
|
+
*/
|
|
710
|
+
get hasPending() {
|
|
711
|
+
return this.agent.inbox.hasPending;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Submit through the Agent only while its Activation remains resident.
|
|
715
|
+
* @param message - the accepted input to submit.
|
|
716
|
+
* @param delivery - whether to queue a distinct turn or steer the nearest step.
|
|
717
|
+
*/
|
|
718
|
+
deliver(message, delivery) {
|
|
719
|
+
if (this.closingPromise !== void 0) throw new SubagentError(`subagent "${this.agent.id}" activation is being disposed; the message was not accepted`, "ACTIVATION_CLOSING");
|
|
720
|
+
if (delivery === "steer") this.agent.steer(message);
|
|
721
|
+
else this.agent.followup(message);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Close delivery synchronously and share one asynchronous release.
|
|
725
|
+
* @param release - the one release operation to start after closing admission.
|
|
726
|
+
* @returns the memoized release transaction.
|
|
727
|
+
*/
|
|
728
|
+
close(release) {
|
|
729
|
+
const existing = this.closingPromise;
|
|
730
|
+
if (existing !== void 0) return existing;
|
|
731
|
+
const completion = Promise.withResolvers();
|
|
732
|
+
this.closingPromise = completion.promise;
|
|
733
|
+
release().then(completion.resolve, completion.reject);
|
|
734
|
+
return completion.promise;
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region lib/types/continuation-activation.js
|
|
739
|
+
/**
|
|
740
|
+
* Process-local Activation ownership for continuable subagents: admission,
|
|
741
|
+
* parent-child residency, serialized delivery, settlement, and disposal.
|
|
742
|
+
*
|
|
743
|
+
* The continuation manager owns durable request orchestration and delegates
|
|
744
|
+
* every mutable residency decision to this registry, so delivery and teardown
|
|
745
|
+
* share one child lock and one Activation map.
|
|
746
|
+
*
|
|
747
|
+
* @module @deepseek-ai/dsh-subagent/continuation-activation
|
|
748
|
+
*/
|
|
934
749
|
/** Serialize each durable child's delivery, release, and disposal. */
|
|
935
750
|
var ChildLock = class {
|
|
936
751
|
tails = /* @__PURE__ */ new Map();
|
|
@@ -950,19 +765,15 @@ var ChildLock = class {
|
|
|
950
765
|
return result;
|
|
951
766
|
}
|
|
952
767
|
};
|
|
953
|
-
/**
|
|
954
|
-
|
|
955
|
-
* schema and host adapters are consumers of this one contract; foreground
|
|
956
|
-
* one-shot delegation keeps calling `ctx.subagents.start()` and never enters
|
|
957
|
-
* this lifecycle.
|
|
958
|
-
*/
|
|
959
|
-
var SubagentContinuationManager = class {
|
|
768
|
+
/** Own the complete process-local lifetime of continuable child Activations. */
|
|
769
|
+
var ContinuableActivationRegistry = class {
|
|
960
770
|
ctx;
|
|
961
|
-
|
|
771
|
+
observeActivation;
|
|
962
772
|
/** Child session id → its live Activation. Process-local, never durable. */
|
|
963
|
-
|
|
773
|
+
resident = /* @__PURE__ */ new Map();
|
|
964
774
|
/** Materializations admitted before drain, tracked through publication or rollback. */
|
|
965
775
|
materializations = /* @__PURE__ */ new Set();
|
|
776
|
+
/** Per-child serializer shared by delivery, release, and disposal. */
|
|
966
777
|
locks = new ChildLock();
|
|
967
778
|
/** Structural Cordis owner of every Activation handle. */
|
|
968
779
|
ownerCtx;
|
|
@@ -974,9 +785,14 @@ var SubagentContinuationManager = class {
|
|
|
974
785
|
*/
|
|
975
786
|
closingScopes = /* @__PURE__ */ new Map();
|
|
976
787
|
draining = false;
|
|
977
|
-
|
|
788
|
+
/**
|
|
789
|
+
* Build one registry inside the service's Agent-injected context.
|
|
790
|
+
* @param ctx - context providing Agents, Sessions, and teardown ownership.
|
|
791
|
+
* @param observeActivation - build the lifecycle observer for one residency epoch.
|
|
792
|
+
*/
|
|
793
|
+
constructor(ctx, observeActivation) {
|
|
978
794
|
this.ctx = ctx;
|
|
979
|
-
this.
|
|
795
|
+
this.observeActivation = observeActivation;
|
|
980
796
|
const scope = ctx.plugin(function activationOwner() {});
|
|
981
797
|
this.ownerCtx = scope.ctx;
|
|
982
798
|
ctx.on("agent/disposed", ({ agent }) => {
|
|
@@ -988,187 +804,50 @@ var SubagentContinuationManager = class {
|
|
|
988
804
|
}.bind(this), "subagents.continuations()");
|
|
989
805
|
}
|
|
990
806
|
/**
|
|
991
|
-
*
|
|
992
|
-
*
|
|
993
|
-
*
|
|
994
|
-
* ownership, and submit the initial prompt. Resolves when inbox acceptance
|
|
995
|
-
* yields the message id — without waiting for the turn to start or for the
|
|
996
|
-
* message to reach the Session log.
|
|
997
|
-
*
|
|
998
|
-
* Every failure before that acceptance rejects without either id, disposing
|
|
999
|
-
* any created handle and rolling back the Activation and parent ownership.
|
|
1000
|
-
* The caller signal owns lookup, materialization, and admission only until
|
|
1001
|
-
* acceptance; afterwards the manager owns the Activation independently.
|
|
1002
|
-
* @param spec - provider, delegation request, and caller cancellation.
|
|
1003
|
-
* @returns the durable child id and the accepted initial prompt's message id.
|
|
807
|
+
* Return the live Activation for a durable child id, if resident.
|
|
808
|
+
* @param childId - durable child session id to look up.
|
|
809
|
+
* @returns the process-local Activation, or `undefined` when it is not resident.
|
|
1004
810
|
*/
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
const parent = request.parent;
|
|
1008
|
-
this.assertAdmitting(parent);
|
|
1009
|
-
const persistence = this.requirePersistence();
|
|
1010
|
-
assertSubagentMaxDepth(request.maxDepth);
|
|
1011
|
-
const childId = spec.childId ?? brandString(randomUUID());
|
|
1012
|
-
this.assertChildIdAvailable(childId);
|
|
1013
|
-
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
1014
|
-
const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
|
|
1015
|
-
const agentProvider = agentOptions.provider;
|
|
1016
|
-
const agentModel = agentOptions.model;
|
|
1017
|
-
const agentReasoningEffort = agentOptions.reasoningEffort;
|
|
1018
|
-
const descriptor = snapshotSubagentDescriptor({
|
|
1019
|
-
mode: "continuable",
|
|
1020
|
-
provider: spec.provider,
|
|
1021
|
-
label: spec.label,
|
|
1022
|
-
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
1023
|
-
...agentModel !== void 0 ? { agentModel } : {},
|
|
1024
|
-
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
1025
|
-
...request.persona !== void 0 ? { persona: request.persona } : {},
|
|
1026
|
-
...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
|
|
1027
|
-
});
|
|
1028
|
-
const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
|
|
1029
|
-
const prepared = await this.host.prepareContinuable(spec.provider, {
|
|
1030
|
-
sessionId: childId,
|
|
1031
|
-
parent,
|
|
1032
|
-
signal: spec.signal
|
|
1033
|
-
});
|
|
1034
|
-
spec.signal.throwIfAborted();
|
|
1035
|
-
this.assertAdmitting(parent);
|
|
1036
|
-
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
1037
|
-
const seed = seedDescriptorTurn(childId, prepared.seed, descriptor);
|
|
1038
|
-
return {
|
|
1039
|
-
childId,
|
|
1040
|
-
messageId: await this.locks.run(childId, async () => {
|
|
1041
|
-
spec.signal.throwIfAborted();
|
|
1042
|
-
this.assertAdmitting(parent);
|
|
1043
|
-
this.assertChildIdAvailable(childId);
|
|
1044
|
-
if (spec.childId !== void 0) {
|
|
1045
|
-
const persisted = await persistence.listSnapshots(spec.signal);
|
|
1046
|
-
spec.signal.throwIfAborted();
|
|
1047
|
-
this.assertAdmitting(parent);
|
|
1048
|
-
this.assertChildIdAvailable(childId);
|
|
1049
|
-
if (persisted.some((snapshot) => snapshot.header.id === childId)) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1050
|
-
}
|
|
1051
|
-
const activation = await this.materialize({
|
|
1052
|
-
childId,
|
|
1053
|
-
provider: spec.provider,
|
|
1054
|
-
parent,
|
|
1055
|
-
create: {
|
|
1056
|
-
seed,
|
|
1057
|
-
meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
|
|
1058
|
-
inheritedEventCount,
|
|
1059
|
-
delegatedPolicies
|
|
1060
|
-
},
|
|
1061
|
-
agentOptions,
|
|
1062
|
-
composition: {
|
|
1063
|
-
persona: request.persona,
|
|
1064
|
-
toolFilter: request.toolFilter
|
|
1065
|
-
},
|
|
1066
|
-
signal: spec.signal
|
|
1067
|
-
});
|
|
1068
|
-
return this.submitMaterialized(activation, isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", activation.handle.agent)) ? continuableInitialPrompt(parent.id, request.prompt) : request.prompt, {
|
|
1069
|
-
source: { kind: "user" },
|
|
1070
|
-
signal: spec.signal,
|
|
1071
|
-
delivery: "queue"
|
|
1072
|
-
}, parent);
|
|
1073
|
-
})
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
/** Reject one child identity already owned by a live Agent or Session. */
|
|
1077
|
-
assertChildIdAvailable(childId) {
|
|
1078
|
-
if (this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
811
|
+
get(childId) {
|
|
812
|
+
return this.resident.get(childId);
|
|
1079
813
|
}
|
|
1080
814
|
/**
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
* the message at its nearest step boundary, while an idle target starts a
|
|
1084
|
-
* turn. A missing direct child cold-resumes through the ordinary continuation
|
|
1085
|
-
* lifecycle. The caller signal owns the operation only until inbox acceptance.
|
|
1086
|
-
* @param sender - exact live Agent authorizing and originating the message.
|
|
1087
|
-
* @param targetId - durable direct-parent or direct-child session id.
|
|
1088
|
-
* @param content - model-authored content to deliver.
|
|
1089
|
-
* @param options - caller cancellation before acceptance.
|
|
1090
|
-
* @returns the accepted message's inbox id.
|
|
1091
|
-
* @throws when adjacency, availability, or admission rejects delivery.
|
|
815
|
+
* Reject one child identity already owned by a live Agent or Session.
|
|
816
|
+
* @param childId - proposed durable child session id.
|
|
1092
817
|
*/
|
|
1093
|
-
|
|
1094
|
-
if (this.ctx.agents.get(
|
|
1095
|
-
this.assertAdmitting(sender);
|
|
1096
|
-
const senderActivation = this.activations.get(sender.id);
|
|
1097
|
-
if (senderActivation !== void 0 && senderActivation.handle.agent === sender && senderActivation.parentSession === targetId) {
|
|
1098
|
-
options.signal.throwIfAborted();
|
|
1099
|
-
return this.sendToParent(senderActivation, sender, content);
|
|
1100
|
-
}
|
|
1101
|
-
if (sender.session.header.parentSession === targetId) throw new SubagentError(`agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`, "UNAUTHORIZED");
|
|
1102
|
-
return this.deliverToChild(sender, targetId, content, {
|
|
1103
|
-
signal: options.signal,
|
|
1104
|
-
delivery: "steer"
|
|
1105
|
-
});
|
|
818
|
+
assertChildIdAvailable(childId) {
|
|
819
|
+
if (this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1106
820
|
}
|
|
1107
821
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
1113
|
-
* @param
|
|
1114
|
-
* @
|
|
822
|
+
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
823
|
+
* parent cannot settle while a caller is still establishing or resuming that
|
|
824
|
+
* child. Returns a releaser for the failure path; it removes only a hold
|
|
825
|
+
* this call added, and leaves ownership in place once a live Activation for
|
|
826
|
+
* the child exists.
|
|
827
|
+
* @param parent - the live direct parent the operation is admitted under.
|
|
828
|
+
* @param childId - the durable child the operation addresses.
|
|
829
|
+
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
1115
830
|
*/
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
/* v8 ignore next 3 -- the send-versus-dispose cutoff: reaching this arm needs a
|
|
1132
|
-
* delivery to observe the transaction inside the same critical section that opened it,
|
|
1133
|
-
* which no test can schedule deterministically. The behavior is covered end-to-end by
|
|
1134
|
-
* "cold-resumes a delivery that lost the race with final disposal". */
|
|
1135
|
-
if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
|
|
1136
|
-
if (contentHasImage(content)) {
|
|
1137
|
-
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1138
|
-
if (activation.disposal !== void 0) {
|
|
1139
|
-
await Promise.allSettled([activation.disposal]);
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
return this.submitAdmitted(activation, content, options, parent);
|
|
1144
|
-
});
|
|
1145
|
-
/* v8 ignore start -- only the lost-cutoff arm above returns undefined, so only that
|
|
1146
|
-
* race reaches the retry below, which then cold-resumes a new Activation. */
|
|
1147
|
-
if (live !== void 0) return live;
|
|
1148
|
-
this.assertAdmitting(parent);
|
|
1149
|
-
options.signal.throwIfAborted();
|
|
1150
|
-
}
|
|
831
|
+
holdOwnership(parent, childId) {
|
|
832
|
+
const parentActivation = this.resident.get(parent.id);
|
|
833
|
+
if (parentActivation === void 0 || parentActivation.handle.agent !== parent) return () => {};
|
|
834
|
+
if (parentActivation.inbox.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
835
|
+
if (parentActivation.ownedChildren.has(childId)) return () => {};
|
|
836
|
+
parentActivation.ownedChildren.add(childId);
|
|
837
|
+
return () => {
|
|
838
|
+
const live = this.resident.get(childId);
|
|
839
|
+
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
840
|
+
* between this operation's failure and its releaser running, which no test can schedule
|
|
841
|
+
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
842
|
+
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
843
|
+
if (live !== void 0 && live.inbox.closing === void 0) return;
|
|
844
|
+
if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
|
|
845
|
+
};
|
|
1151
846
|
}
|
|
1152
847
|
/**
|
|
1153
|
-
* Interrupt one live continuable child's current turn
|
|
1154
|
-
* synchronous and the effect is asynchronous: this authorizes the caller,
|
|
1155
|
-
* requests `Agent.cancel(cause, { keepInbox: true })` on the target, and
|
|
1156
|
-
* returns without waiting for the target to observe the signal or reach
|
|
1157
|
-
* quiescence. The Activation, its handle, accepted unclaimed inbox work, and
|
|
1158
|
-
* already-published descendants are untouched; work already claimed into the
|
|
1159
|
-
* interrupted turn is not requeued. Once the interrupted driver is idle, a
|
|
1160
|
-
* waking send resumes the parked queue.
|
|
1161
|
-
*
|
|
1162
|
-
* An absent target is an accepted no-op, which uniformly covers natural
|
|
1163
|
-
* completion races, repeated requests, one-shot ids, and unknown ids without
|
|
1164
|
-
* consulting the durable catalog. A target whose disposal transaction is
|
|
1165
|
-
* already open is likewise an accepted no-op after authorization.
|
|
848
|
+
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
1166
849
|
* @param targetSessionId - the durable child session id to interrupt.
|
|
1167
850
|
* @param authority - the human parent address or exact live ancestor Agent.
|
|
1168
|
-
* @throws {SubagentError} `UNAUTHORIZED` when the presented authority does
|
|
1169
|
-
* not own the live target: a stale or self-targeting ancestor caller, a
|
|
1170
|
-
* parent address that is not the live target's durable direct parent, or
|
|
1171
|
-
* an ancestor outside the target's recorded live lineage.
|
|
1172
851
|
*/
|
|
1173
852
|
interrupt(targetSessionId, authority) {
|
|
1174
853
|
if (authority.kind === "ancestor") {
|
|
@@ -1176,81 +855,56 @@ var SubagentContinuationManager = class {
|
|
|
1176
855
|
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
|
|
1177
856
|
if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
|
|
1178
857
|
}
|
|
1179
|
-
const activation = this.
|
|
858
|
+
const activation = this.resident.get(targetSessionId);
|
|
1180
859
|
if (activation === void 0) return;
|
|
1181
860
|
if (authority.kind === "user") {
|
|
1182
861
|
if (activation.handle.agent.session.header.parentSession !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
1183
862
|
} else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
|
|
1184
|
-
if (activation.
|
|
863
|
+
if (activation.inbox.closing !== void 0) return;
|
|
1185
864
|
activation.handle.agent.cancel(authority.kind === "user" ? { kind: "user" } : { kind: "parent" }, { keepInbox: true });
|
|
1186
865
|
}
|
|
1187
|
-
/** Deliver one resident continuable child's message to its live direct parent. */
|
|
1188
|
-
sendToParent(activation, sender, content) {
|
|
1189
|
-
/* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
1190
|
-
* transaction between exact-agent authorization and this no-await span. */
|
|
1191
|
-
if (activation.disposal !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
|
|
1192
|
-
const parent = this.ctx.agents.get(activation.parentSession);
|
|
1193
|
-
if (parent === void 0) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
|
|
1194
|
-
const message = agentMessage(sender, content);
|
|
1195
|
-
this.sendWaking(parent, message, () => {
|
|
1196
|
-
this.sendAgentMessage(parent, message);
|
|
1197
|
-
});
|
|
1198
|
-
return message.id;
|
|
1199
|
-
}
|
|
1200
866
|
/**
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
*
|
|
1205
|
-
* @param parent - the exact live parent receiving the waking message.
|
|
1206
|
-
* @param message - the message whose id is accounted.
|
|
1207
|
-
* @param send - the synchronous waking send to perform.
|
|
867
|
+
* Send through a receiving parent's Activation inbox when it has one.
|
|
868
|
+
* @param parent - exact live Agent receiving the message.
|
|
869
|
+
* @param message - durable user message to deliver.
|
|
870
|
+
* @param delivery - receiving inbox destination.
|
|
1208
871
|
*/
|
|
1209
|
-
sendWaking(parent, message,
|
|
1210
|
-
const parentActivation = this.
|
|
1211
|
-
if (parentActivation !== void 0 && parentActivation.handle.agent === parent)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
} catch (error) {
|
|
1219
|
-
throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
|
|
872
|
+
sendWaking(parent, message, delivery) {
|
|
873
|
+
const parentActivation = this.resident.get(parent.id);
|
|
874
|
+
if (parentActivation !== void 0 && parentActivation.handle.agent === parent) {
|
|
875
|
+
try {
|
|
876
|
+
parentActivation.inbox.deliver(message, delivery);
|
|
877
|
+
} finally {
|
|
878
|
+
this.wake(parentActivation);
|
|
879
|
+
}
|
|
880
|
+
return;
|
|
1220
881
|
}
|
|
882
|
+
if (delivery === "steer") parent.steer(message);
|
|
883
|
+
else parent.followup(message);
|
|
1221
884
|
}
|
|
1222
885
|
/**
|
|
1223
886
|
* Close admission, await every already-admitted materialization through
|
|
1224
|
-
* publication or rollback, then dispose the stable live Activation
|
|
1225
|
-
* child-first.
|
|
1226
|
-
* but never prevents the remaining handles from being attempted, and the
|
|
1227
|
-
* aggregate rejects only after every branch settles.
|
|
1228
|
-
* @returns once materialization is quiescent and every live Activation released its handle.
|
|
1229
|
-
* @throws an aggregate error when any branch failed to release.
|
|
887
|
+
* publication or rollback, then dispose the stable live Activation graph
|
|
888
|
+
* child-first.
|
|
1230
889
|
*/
|
|
1231
890
|
async drain() {
|
|
1232
891
|
this.draining = true;
|
|
1233
892
|
await Promise.all([...this.materializations].map((materialization) => materialization.settled));
|
|
1234
893
|
const owned = /* @__PURE__ */ new Set();
|
|
1235
|
-
for (const activation of this.
|
|
1236
|
-
const roots = [...this.
|
|
894
|
+
for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
|
|
895
|
+
const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
|
|
1237
896
|
await this.disposeRoots(roots, "activation(s)");
|
|
1238
897
|
}
|
|
1239
898
|
/**
|
|
1240
899
|
* Stop only the continuable descendants of exact live host-owned parents.
|
|
1241
|
-
* Admission stays closed for those parent trees until each exact parent
|
|
1242
|
-
* leaves the Agent registry; unrelated trees and manager-wide admission stay
|
|
1243
|
-
* live.
|
|
1244
900
|
* @param parents - exact live roots whose continuable descendants must stop.
|
|
1245
|
-
* @returns once every retained descendant Activation released its handle.
|
|
1246
|
-
* @throws an aggregate error after all scoped branches settle when any failed.
|
|
1247
901
|
*/
|
|
1248
902
|
async drainDescendants(parents) {
|
|
1249
903
|
const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
|
|
1250
904
|
if (roots.size === 0) return;
|
|
1251
905
|
for (const root of roots) this.closingMembers(root).add(root);
|
|
1252
906
|
const targets = [];
|
|
1253
|
-
for (const activation of this.
|
|
907
|
+
for (const activation of this.resident.values()) {
|
|
1254
908
|
const lineage = this.liveLineage(activation.handle.agent);
|
|
1255
909
|
const owners = [...roots].filter((root) => activation.handle.agent !== root && activation.ancestry.has(root));
|
|
1256
910
|
if (owners.length === 0) continue;
|
|
@@ -1277,20 +931,15 @@ var SubagentContinuationManager = class {
|
|
|
1277
931
|
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
1278
932
|
}
|
|
1279
933
|
/**
|
|
1280
|
-
* Release selected resident direct children of one exact live parent
|
|
1281
|
-
* closing admission for the parent's other continuable children. Owned
|
|
1282
|
-
* descendants are released recursively through the same lifecycle.
|
|
934
|
+
* Release selected resident direct children of one exact live parent.
|
|
1283
935
|
* @param parent - exact live direct parent authorizing the selected release.
|
|
1284
936
|
* @param childIds - durable direct-child ids to release when resident.
|
|
1285
|
-
* @returns once every selected Activation released its handle.
|
|
1286
|
-
* @throws {SubagentError} `UNAUTHORIZED` when a resident target is not the
|
|
1287
|
-
* parent's direct continuable child or the parent identity is stale.
|
|
1288
937
|
*/
|
|
1289
938
|
async drainChildren(parent, childIds) {
|
|
1290
939
|
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
1291
940
|
const targets = [];
|
|
1292
941
|
for (const childId of new Set(childIds)) {
|
|
1293
|
-
const activation = this.
|
|
942
|
+
const activation = this.resident.get(childId);
|
|
1294
943
|
if (activation === void 0) continue;
|
|
1295
944
|
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
1296
945
|
targets.push(activation);
|
|
@@ -1298,6 +947,75 @@ var SubagentContinuationManager = class {
|
|
|
1298
947
|
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1299
948
|
await this.disposeRoots(targets, "selected activation(s)");
|
|
1300
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Reject new admission once the registry or this exact parent tree began draining.
|
|
952
|
+
* @param agent - exact live Agent whose lineage determines admission.
|
|
953
|
+
*/
|
|
954
|
+
assertAdmitting(agent) {
|
|
955
|
+
const closing = this.closingTeardownFor(agent);
|
|
956
|
+
if (closing === void 0) return;
|
|
957
|
+
throw new SubagentError(closing === "manager" ? "continuable subagents are draining; the operation was not admitted" : `continuable subagents below parent "${closing.id}" are draining; the operation was not admitted`, "DRAINING");
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Authorize one operation against the durable direct-parent lineage.
|
|
961
|
+
* @param parent - exact live Agent claiming direct-parent authority.
|
|
962
|
+
* @param childId - durable child session id addressed by the operation.
|
|
963
|
+
* @param parentSession - durable direct-parent id recorded by the child.
|
|
964
|
+
*/
|
|
965
|
+
authorizeLineage(parent, childId, parentSession) {
|
|
966
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
|
|
967
|
+
if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Create or resume one child Agent and publish its Activation.
|
|
971
|
+
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
972
|
+
* @returns the published process-local Activation.
|
|
973
|
+
*/
|
|
974
|
+
materialize(inputs) {
|
|
975
|
+
this.assertAdmitting(inputs.parent);
|
|
976
|
+
const settled = Promise.withResolvers();
|
|
977
|
+
const lineage = this.liveLineage(inputs.parent);
|
|
978
|
+
const materialization = {
|
|
979
|
+
lineage,
|
|
980
|
+
settled: settled.promise
|
|
981
|
+
};
|
|
982
|
+
this.materializations.add(materialization);
|
|
983
|
+
return this.materializeTracked(inputs, lineage).finally(() => {
|
|
984
|
+
this.materializations.delete(materialization);
|
|
985
|
+
settled.resolve();
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Cross the final admission cutoff and submit without yielding.
|
|
990
|
+
* @param activation - the exact resident child receiving the message.
|
|
991
|
+
* @param message - the already-built durable user message.
|
|
992
|
+
* @param delivery - the Agent inbox destination.
|
|
993
|
+
* @param parent - exact live direct parent authorizing admission.
|
|
994
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
995
|
+
* @returns the accepted durable message id.
|
|
996
|
+
*/
|
|
997
|
+
submitAdmitted(activation, message, delivery, parent, signal) {
|
|
998
|
+
signal.throwIfAborted();
|
|
999
|
+
this.assertAdmitting(parent);
|
|
1000
|
+
this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
|
|
1001
|
+
this.acquireOwnership(parent, activation.childId);
|
|
1002
|
+
try {
|
|
1003
|
+
activation.inbox.deliver(message, delivery);
|
|
1004
|
+
} finally {
|
|
1005
|
+
this.wake(activation);
|
|
1006
|
+
}
|
|
1007
|
+
activation.announced = true;
|
|
1008
|
+
return message.id;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Stop and release one Activation through its memoized close transaction.
|
|
1012
|
+
* @param activation - exact residency epoch to close.
|
|
1013
|
+
* @param finalStateFlushed - whether natural settlement already flushed final state.
|
|
1014
|
+
* @returns the shared close transaction.
|
|
1015
|
+
*/
|
|
1016
|
+
dispose(activation, finalStateFlushed = false) {
|
|
1017
|
+
return activation.inbox.close(() => this.finishDisposal(activation, finalStateFlushed));
|
|
1018
|
+
}
|
|
1301
1019
|
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1302
1020
|
async disposeRoots(roots, failureSubject) {
|
|
1303
1021
|
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
@@ -1318,11 +1036,7 @@ var SubagentContinuationManager = class {
|
|
|
1318
1036
|
this.closingScopes.set(root, members);
|
|
1319
1037
|
return members;
|
|
1320
1038
|
}
|
|
1321
|
-
/**
|
|
1322
|
-
* Return the exact currently resolvable ancestry from `agent` upward. The
|
|
1323
|
-
* first element is always the supplied identity, even when it is already
|
|
1324
|
-
* stale; each ancestor after it must be the registry's current exact entry.
|
|
1325
|
-
*/
|
|
1039
|
+
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
1326
1040
|
liveLineage(agent) {
|
|
1327
1041
|
const lineage = [agent];
|
|
1328
1042
|
const seen = new Set([agent.id]);
|
|
@@ -1336,172 +1050,25 @@ var SubagentContinuationManager = class {
|
|
|
1336
1050
|
}
|
|
1337
1051
|
return lineage;
|
|
1338
1052
|
}
|
|
1339
|
-
/**
|
|
1340
|
-
* The teardown that closed continuable admission for this agent's lineage.
|
|
1341
|
-
* `'manager'` is the whole manager draining; an Agent is the exact scoped root
|
|
1342
|
-
* whose forest is closing.
|
|
1343
|
-
* @param agent - the agent whose lineage is tested.
|
|
1344
|
-
* @returns the closing teardown, or `undefined` while admission is open.
|
|
1345
|
-
*/
|
|
1053
|
+
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
1346
1054
|
closingTeardownFor(agent) {
|
|
1347
1055
|
if (this.draining) return "manager";
|
|
1348
1056
|
const lineage = this.liveLineage(agent);
|
|
1349
1057
|
for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
|
|
1350
1058
|
}
|
|
1351
|
-
/**
|
|
1352
|
-
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
*
|
|
1361
|
-
* `Agent.status` alone is insufficient: it stays `idle` between an accepted
|
|
1362
|
-
* waking send and the microtask that admits it, so a synchronous inbox
|
|
1363
|
-
* observer would see `settled` while a turn is already queued. `accepted`
|
|
1364
|
-
* holds the ids this manager admitted but has not yet seen drained.
|
|
1365
|
-
*/
|
|
1366
|
-
stateOf(activation) {
|
|
1367
|
-
if (activation.handle.agent.status === "running" || activation.accepted.size > 0) return "running";
|
|
1368
|
-
if (activation.ownedChildren.size > 0) return "waiting";
|
|
1369
|
-
return "settled";
|
|
1370
|
-
}
|
|
1371
|
-
/**
|
|
1372
|
-
* Cold-resume a persisted child: retain and authorize its prepared Session, fold the
|
|
1373
|
-
* generic descriptor, create the Activation through `ctx.agents.resume()`,
|
|
1374
|
-
* and submit the waiting turn. This never dispatches through a subagent
|
|
1375
|
-
* provider — the persisted Session already holds the initial prefix and the
|
|
1376
|
-
* descriptor is the whole reconstruction input.
|
|
1377
|
-
*/
|
|
1378
|
-
async coldResume(parent, childId, content, options) {
|
|
1379
|
-
const env_1 = {
|
|
1380
|
-
stack: [],
|
|
1381
|
-
error: void 0,
|
|
1382
|
-
hasError: false
|
|
1383
|
-
};
|
|
1384
|
-
try {
|
|
1385
|
-
const query = this.requireSessionQuery();
|
|
1386
|
-
let observation;
|
|
1387
|
-
try {
|
|
1388
|
-
observation = await query.observeSession(childId, { signal: options.signal });
|
|
1389
|
-
} catch (error) {
|
|
1390
|
-
options.signal.throwIfAborted();
|
|
1391
|
-
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1059
|
+
/** Perform one tracked materialization through publication or rollback. */
|
|
1060
|
+
async materializeTracked(inputs, parentLineage) {
|
|
1061
|
+
const { childId, provider, parent, create } = inputs;
|
|
1062
|
+
inputs.signal.throwIfAborted();
|
|
1063
|
+
const setup = (childCtx) => {
|
|
1064
|
+
const child = childCtx.agent;
|
|
1065
|
+
if (create !== void 0) {
|
|
1066
|
+
child.session.append("subagent/descriptor", create.descriptor);
|
|
1067
|
+
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1392
1068
|
}
|
|
1393
|
-
const source = __addDisposableResource$1(env_1, observation, false);
|
|
1394
|
-
this.assertAdmitting(parent);
|
|
1395
|
-
this.authorizeLineage(parent, childId, source.header.parentSession);
|
|
1396
|
-
const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
|
|
1397
|
-
if (descriptor === void 0 || descriptor.mode !== "continuable") throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, "NOT_RESUMABLE");
|
|
1398
|
-
let activation;
|
|
1399
|
-
try {
|
|
1400
|
-
activation = await this.materialize({
|
|
1401
|
-
childId,
|
|
1402
|
-
provider: descriptor.provider,
|
|
1403
|
-
parent,
|
|
1404
|
-
agentOptions: {
|
|
1405
|
-
...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
|
|
1406
|
-
...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
|
|
1407
|
-
...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
|
|
1408
|
-
},
|
|
1409
|
-
composition: {
|
|
1410
|
-
persona: descriptor.persona,
|
|
1411
|
-
toolFilter: descriptor.toolFilter
|
|
1412
|
-
},
|
|
1413
|
-
signal: options.signal
|
|
1414
|
-
});
|
|
1415
|
-
} catch (error) {
|
|
1416
|
-
options.signal.throwIfAborted();
|
|
1417
|
-
if (error instanceof SubagentError) throw error;
|
|
1418
|
-
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1419
|
-
}
|
|
1420
|
-
return await this.submitMaterialized(activation, content, options, parent);
|
|
1421
|
-
} catch (e_1) {
|
|
1422
|
-
env_1.error = e_1;
|
|
1423
|
-
env_1.hasError = true;
|
|
1424
|
-
} finally {
|
|
1425
|
-
__disposeResources$1(env_1);
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
/**
|
|
1429
|
-
* Submit to a freshly materialized Activation or roll it back completely.
|
|
1430
|
-
* @param activation - the just-published Activation to admit or release.
|
|
1431
|
-
* @param content - the initial or resumed message content.
|
|
1432
|
-
* @param options - durable source, scheduling, and pre-acceptance cancellation.
|
|
1433
|
-
* @param parent - the live direct parent authorizing admission.
|
|
1434
|
-
* @returns the accepted inbox message id.
|
|
1435
|
-
*/
|
|
1436
|
-
async submitMaterialized(activation, content, options, parent) {
|
|
1437
|
-
try {
|
|
1438
|
-
if (contentHasImage(content)) {
|
|
1439
|
-
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1440
|
-
if (activation.disposal !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
|
|
1441
|
-
}
|
|
1442
|
-
return this.submitAdmitted(activation, content, options, parent);
|
|
1443
|
-
} catch (error) {
|
|
1444
|
-
/* v8 ignore next -- rollback disposal failures must not mask the
|
|
1445
|
-
* pre-acceptance signal, drain, or lifecycle failure. */
|
|
1446
|
-
await this.dispose(activation).catch(() => void 0);
|
|
1447
|
-
throw error;
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Refuse image content addressed to a child whose model accepts text only.
|
|
1452
|
-
* Callers guard with `contentHasImage`, so text-only delivery never awaits.
|
|
1453
|
-
* The check runs inside the per-child delivery lock, before the message
|
|
1454
|
-
* exists, so a rejection leaves no partial user message. When the child's
|
|
1455
|
-
* route is not fixed by its options (a request-waterfall listener owns it)
|
|
1456
|
-
* or no LLM registry is composed, delivery proceeds and the LLM layer's
|
|
1457
|
-
* text-only projection replaces each image with its stable placeholder.
|
|
1458
|
-
* @param agent - the live or freshly materialized child agent.
|
|
1459
|
-
* @param signal - caller cancellation bounding the model-info read.
|
|
1460
|
-
* @throws {SubagentError} `MODEL_DOES_NOT_SUPPORT_IMAGES` when the child's resolved model declines image input.
|
|
1461
|
-
*/
|
|
1462
|
-
async assertImageCapable(agent, signal) {
|
|
1463
|
-
const { provider, model } = agent.options;
|
|
1464
|
-
if (provider === void 0 || model === void 0) return;
|
|
1465
|
-
const llm = this.ctx.get("llm");
|
|
1466
|
-
/* v8 ignore next -- a deployment without the LLM registry serves no model
|
|
1467
|
-
* to refuse against; delivery then defers to the text-only projection. */
|
|
1468
|
-
if (llm === void 0) return;
|
|
1469
|
-
const info = await llm.resolveModelInfo(provider, model, signal);
|
|
1470
|
-
if (info.inputModalities !== void 0 && !info.inputModalities.includes("image")) throw new SubagentError(`Model "${model}" does not support image input.`, "MODEL_DOES_NOT_SUPPORT_IMAGES");
|
|
1471
|
-
}
|
|
1472
|
-
/**
|
|
1473
|
-
* Create or resume the child Agent through the private activation-owner
|
|
1474
|
-
* scope, install the handle in a fresh Activation, and register ownership on
|
|
1475
|
-
* a continuation-managed parent. Rejection leaves no Activation, no handle,
|
|
1476
|
-
* and no ownership membership.
|
|
1477
|
-
*/
|
|
1478
|
-
materialize(inputs) {
|
|
1479
|
-
this.assertAdmitting(inputs.parent);
|
|
1480
|
-
const settled = Promise.withResolvers();
|
|
1481
|
-
const lineage = this.liveLineage(inputs.parent);
|
|
1482
|
-
const materialization = {
|
|
1483
|
-
lineage,
|
|
1484
|
-
settled: settled.promise
|
|
1485
|
-
};
|
|
1486
|
-
this.materializations.add(materialization);
|
|
1487
|
-
return this.materializeTracked(inputs, lineage).finally(() => {
|
|
1488
|
-
this.materializations.delete(materialization);
|
|
1489
|
-
settled.resolve();
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
/**
|
|
1493
|
-
* Perform one tracked materialization. The caller keeps the drain barrier
|
|
1494
|
-
* registered until this either returns a resident Activation or finishes
|
|
1495
|
-
* rollback.
|
|
1496
|
-
*/
|
|
1497
|
-
async materializeTracked(inputs, parentLineage) {
|
|
1498
|
-
const { childId, provider, parent, create } = inputs;
|
|
1499
|
-
inputs.signal.throwIfAborted();
|
|
1500
|
-
const setup = (childCtx) => {
|
|
1501
|
-
if (create !== void 0) appendDelegatedPolicyOverrides(childCtx.agent.session, create.delegatedPolicies);
|
|
1502
1069
|
applyChildComposition(childCtx, parent, inputs.composition);
|
|
1503
1070
|
};
|
|
1504
|
-
const observer = this.
|
|
1071
|
+
const observer = this.observeActivation(provider, childId, parent);
|
|
1505
1072
|
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
1506
1073
|
resumeSessionId: childId,
|
|
1507
1074
|
agentOptions: inputs.agentOptions,
|
|
@@ -1510,7 +1077,7 @@ var SubagentContinuationManager = class {
|
|
|
1510
1077
|
}) : await this.ownerCtx.agents.create({
|
|
1511
1078
|
sessionId: childId,
|
|
1512
1079
|
meta: create.meta,
|
|
1513
|
-
seed: create.seed,
|
|
1080
|
+
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1514
1081
|
inheritedEventCount: create.inheritedEventCount,
|
|
1515
1082
|
agentOptions: inputs.agentOptions,
|
|
1516
1083
|
signal: inputs.signal,
|
|
@@ -1521,27 +1088,23 @@ var SubagentContinuationManager = class {
|
|
|
1521
1088
|
parentSession: parent.id,
|
|
1522
1089
|
provider,
|
|
1523
1090
|
handle,
|
|
1091
|
+
inbox: new SubagentInbox(handle.agent),
|
|
1524
1092
|
ancestry: new WeakSet([handle.agent, ...parentLineage]),
|
|
1525
1093
|
ownedChildren: /* @__PURE__ */ new Set(),
|
|
1526
1094
|
observer,
|
|
1527
|
-
disposal: void 0,
|
|
1528
|
-
accepted: /* @__PURE__ */ new Set(),
|
|
1529
1095
|
announced: false,
|
|
1530
1096
|
poke: Promise.withResolvers()
|
|
1531
1097
|
};
|
|
1532
|
-
this.
|
|
1098
|
+
this.resident.set(childId, activation);
|
|
1533
1099
|
try {
|
|
1534
1100
|
inputs.signal.throwIfAborted();
|
|
1535
1101
|
this.assertAdmitting(parent);
|
|
1536
1102
|
this.acquireOwnership(parent, childId);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
handle.agent.ctx.on("agent/inbox/discarded", ({ message }) => {
|
|
1543
|
-
if (activation.accepted.delete(message.id)) this.wake(activation);
|
|
1544
|
-
});
|
|
1103
|
+
const wakeOnInboxRemoval = () => {
|
|
1104
|
+
this.wake(activation);
|
|
1105
|
+
};
|
|
1106
|
+
handle.agent.ctx.on("agent/inbox/claimed", wakeOnInboxRemoval);
|
|
1107
|
+
handle.agent.ctx.on("agent/inbox/discarded", wakeOnInboxRemoval);
|
|
1545
1108
|
observer.start(handle.agent);
|
|
1546
1109
|
} catch (error) {
|
|
1547
1110
|
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
@@ -1552,126 +1115,72 @@ var SubagentContinuationManager = class {
|
|
|
1552
1115
|
this.watchSettlement(activation);
|
|
1553
1116
|
return activation;
|
|
1554
1117
|
}
|
|
1555
|
-
/**
|
|
1556
|
-
* Release an Activation whose start edge was not published. The memoized
|
|
1557
|
-
* transaction remains in the live map until handle disposal settles, so a
|
|
1558
|
-
* concurrent drain or delivery observes the same closing boundary.
|
|
1559
|
-
*/
|
|
1118
|
+
/** Release an Activation whose start edge was not published. */
|
|
1560
1119
|
rollbackUnpublished(activation) {
|
|
1561
|
-
return activation.
|
|
1120
|
+
return activation.inbox.close(async () => {
|
|
1562
1121
|
try {
|
|
1563
1122
|
await activation.handle.dispose();
|
|
1564
1123
|
} finally {
|
|
1565
|
-
this.
|
|
1124
|
+
this.resident.delete(activation.childId);
|
|
1566
1125
|
this.releaseOwnership(activation.childId);
|
|
1567
1126
|
}
|
|
1568
|
-
})
|
|
1127
|
+
});
|
|
1569
1128
|
}
|
|
1570
|
-
/**
|
|
1571
|
-
* Register the child in a continuation-managed parent's owned set before the
|
|
1572
|
-
* child can run, so that parent cannot settle while the child is live. A
|
|
1573
|
-
* top-level or other non-continuation Agent has no Activation and stays
|
|
1574
|
-
* outside the waiting graph.
|
|
1575
|
-
*/
|
|
1129
|
+
/** Register the child in a continuation-managed parent's owned set. */
|
|
1576
1130
|
acquireOwnership(parent, childId) {
|
|
1577
|
-
const parentActivation = this.
|
|
1131
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1578
1132
|
if (parentActivation === void 0) return;
|
|
1579
|
-
if (parentActivation.
|
|
1133
|
+
if (parentActivation.inbox.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1580
1134
|
parentActivation.ownedChildren.add(childId);
|
|
1581
1135
|
}
|
|
1582
1136
|
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
1583
1137
|
releaseOwnership(childId) {
|
|
1584
|
-
for (const candidate of this.
|
|
1138
|
+
for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
|
|
1585
1139
|
}
|
|
1586
|
-
/** Let a settlement watcher re-
|
|
1140
|
+
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
1587
1141
|
wake(activation) {
|
|
1588
1142
|
activation.poke.resolve();
|
|
1589
1143
|
activation.poke = Promise.withResolvers();
|
|
1590
1144
|
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Submit one message as the child's next FIFO turn and return its accepted
|
|
1593
|
-
* inbox id. Acceptance is the operation's success boundary; the manager owns
|
|
1594
|
-
* the Activation independently afterwards.
|
|
1595
|
-
*/
|
|
1596
|
-
submit(activation, content, options, parent) {
|
|
1597
|
-
this.acquireOwnership(parent, activation.childId);
|
|
1598
|
-
const message = options.delivery === "steer" ? agentMessage(parent, content) : createUserMessage({
|
|
1599
|
-
content,
|
|
1600
|
-
source: options.source
|
|
1601
|
-
});
|
|
1602
|
-
const accepted = this.admitWaking(activation, message.id, () => {
|
|
1603
|
-
if (options.delivery === "steer") activation.handle.agent.steer(message);
|
|
1604
|
-
else activation.handle.agent.followup(message);
|
|
1605
|
-
});
|
|
1606
|
-
activation.announced = true;
|
|
1607
|
-
return accepted;
|
|
1608
|
-
}
|
|
1609
|
-
/**
|
|
1610
|
-
* Account one waking send across a resident Activation's settlement window.
|
|
1611
|
-
* @param activation - Activation receiving waking inbox work.
|
|
1612
|
-
* @param messageId - stable identity of the message about to be sent.
|
|
1613
|
-
* @param send - synchronous send that publishes one enqueue occurrence.
|
|
1614
|
-
* @returns the accepted message id.
|
|
1615
|
-
*/
|
|
1616
|
-
admitWaking(activation, messageId, send) {
|
|
1617
|
-
activation.accepted.add(messageId);
|
|
1618
|
-
try {
|
|
1619
|
-
send();
|
|
1620
|
-
} catch (error) {
|
|
1621
|
-
activation.accepted.delete(messageId);
|
|
1622
|
-
throw error;
|
|
1623
|
-
}
|
|
1624
|
-
this.wake(activation);
|
|
1625
|
-
return messageId;
|
|
1626
|
-
}
|
|
1627
|
-
/**
|
|
1628
|
-
* Cross the final admission cutoff and submit without yielding. Signal abort,
|
|
1629
|
-
* manager drain, or Activation disposal that wins before this synchronous
|
|
1630
|
-
* span rejects without inbox acceptance.
|
|
1631
|
-
*/
|
|
1632
|
-
submitAdmitted(activation, content, options, parent) {
|
|
1633
|
-
options.signal.throwIfAborted();
|
|
1634
|
-
this.assertAdmitting(parent);
|
|
1635
|
-
/* v8 ignore next 6 -- only a synchronous re-entrant disposer can change
|
|
1636
|
-
* this field between the caller's live check and this no-await boundary. */
|
|
1637
|
-
if (disposalOf(activation) !== void 0) throw new SubagentError(`subagent "${activation.childId}" activation is being disposed; the message was not accepted`, "ACTIVATION_CLOSING");
|
|
1638
|
-
this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
|
|
1639
|
-
return this.submit(activation, content, options, parent);
|
|
1640
|
-
}
|
|
1641
|
-
/**
|
|
1642
|
-
* Authorize one operation against the durable direct-parent lineage. Other
|
|
1643
|
-
* agents, ancestors, teams, workflows, and hosts remain rejected until an
|
|
1644
|
-
* explicit authority protocol has a production consumer.
|
|
1645
|
-
*/
|
|
1646
|
-
authorizeLineage(parent, childId, parentSession) {
|
|
1647
|
-
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
|
|
1648
|
-
if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Follow one Activation to settlement: wait for Agent quiescence, then for
|
|
1652
|
-
* every owned child to complete disposal, and dispose the handle once both
|
|
1653
|
-
* hold. A `next-turn` delivered while `waiting` wakes the same Agent and
|
|
1654
|
-
* returns it to `running`, so this re-observes rather than settling early.
|
|
1655
|
-
*/
|
|
1145
|
+
/** Follow one Activation to natural settlement. */
|
|
1656
1146
|
watchSettlement(activation) {
|
|
1657
1147
|
(async () => {
|
|
1658
|
-
while (
|
|
1659
|
-
const
|
|
1660
|
-
await
|
|
1661
|
-
if (
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1148
|
+
while (true) {
|
|
1149
|
+
const idleObservation = activation.poke;
|
|
1150
|
+
await activation.handle.agent.whenIdle();
|
|
1151
|
+
if (activation.inbox.closing !== void 0) return;
|
|
1152
|
+
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
1153
|
+
if (readiness === "closed") return;
|
|
1154
|
+
if (readiness === "retry") continue;
|
|
1155
|
+
if (readiness === "wait") {
|
|
1156
|
+
await idleObservation.promise;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
const finalSeq = activation.handle.agent.session.seq;
|
|
1160
|
+
await this.flushFinalState(activation);
|
|
1161
|
+
const attempt = await this.locks.run(activation.childId, () => {
|
|
1162
|
+
const state = this.settlementState(activation, idleObservation);
|
|
1163
|
+
if (state !== "ready") return Promise.resolve(state);
|
|
1164
|
+
if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
|
|
1165
|
+
let done;
|
|
1166
|
+
try {
|
|
1167
|
+
activation.handle.agent.runMaintenance(() => {
|
|
1168
|
+
done = this.dispose(activation, true);
|
|
1169
|
+
return Promise.resolve();
|
|
1170
|
+
});
|
|
1171
|
+
} catch {
|
|
1172
|
+
return Promise.resolve("retry");
|
|
1173
|
+
}
|
|
1174
|
+
return Promise.resolve({ done });
|
|
1668
1175
|
});
|
|
1669
|
-
if (
|
|
1670
|
-
|
|
1176
|
+
if (attempt === "closed") return;
|
|
1177
|
+
if (attempt === "retry") continue;
|
|
1178
|
+
if (attempt === "wait") {
|
|
1179
|
+
await idleObservation.promise;
|
|
1671
1180
|
continue;
|
|
1672
1181
|
}
|
|
1673
1182
|
try {
|
|
1674
|
-
await
|
|
1183
|
+
await attempt.done;
|
|
1675
1184
|
} catch (error) {
|
|
1676
1185
|
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${errorChain(error)}`);
|
|
1677
1186
|
}
|
|
@@ -1679,53 +1188,44 @@ var SubagentContinuationManager = class {
|
|
|
1679
1188
|
}
|
|
1680
1189
|
})();
|
|
1681
1190
|
}
|
|
1682
|
-
/**
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
* or ownership release, because retaining a child would permanently pin its
|
|
1689
|
-
* ancestors in `waiting`.
|
|
1690
|
-
* @param activation - the residency epoch to stop and release.
|
|
1691
|
-
* @returns the one disposal transaction owned by this Activation.
|
|
1692
|
-
*/
|
|
1693
|
-
dispose(activation) {
|
|
1694
|
-
const existing = activation.disposal;
|
|
1695
|
-
if (existing !== void 0) return existing;
|
|
1696
|
-
const completion = Promise.withResolvers();
|
|
1697
|
-
activation.disposal = completion.promise;
|
|
1698
|
-
this.finishDisposal(activation).then(completion.resolve, completion.reject);
|
|
1699
|
-
return completion.promise;
|
|
1191
|
+
/** Classify one Inbox and owned-child observation without reading Agent execution state. */
|
|
1192
|
+
settlementState(activation, observation) {
|
|
1193
|
+
if (activation.inbox.closing !== void 0) return "closed";
|
|
1194
|
+
if (activation.poke !== observation) return "retry";
|
|
1195
|
+
if (activation.inbox.hasPending || activation.ownedChildren.size > 0) return "wait";
|
|
1196
|
+
return "ready";
|
|
1700
1197
|
}
|
|
1701
|
-
/**
|
|
1702
|
-
|
|
1703
|
-
* @param activation - the Activation whose disposal transaction is installed.
|
|
1704
|
-
* @returns once the handle and ownership edge are released.
|
|
1705
|
-
*/
|
|
1706
|
-
async finishDisposal(activation) {
|
|
1198
|
+
/** Propagate stop synchronously, then finish the child-first release. */
|
|
1199
|
+
async finishDisposal(activation, finalStateFlushed) {
|
|
1707
1200
|
this.wake(activation);
|
|
1708
1201
|
const { childId } = activation;
|
|
1709
|
-
activation.handle.agent.cancel({ kind: "parent" });
|
|
1710
|
-
const idle = activation.handle.agent.whenIdle();
|
|
1711
|
-
const childDisposals = [...activation.ownedChildren].map((child) => this.activations.get(child)).filter((child) => child !== void 0).map((child) => this.dispose(child));
|
|
1712
1202
|
const failures = [];
|
|
1713
|
-
try {
|
|
1714
|
-
const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
|
|
1715
|
-
try {
|
|
1716
|
-
await disposal;
|
|
1717
|
-
return;
|
|
1718
|
-
} catch (error) {
|
|
1719
|
-
return error;
|
|
1720
|
-
}
|
|
1721
|
-
}))).filter((reason) => reason !== void 0);
|
|
1722
|
-
if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => errorChain(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
|
|
1723
|
-
await idle;
|
|
1724
|
-
await this.flushFinalState(activation);
|
|
1203
|
+
if (finalStateFlushed) try {
|
|
1725
1204
|
activation.observer.capture(activation.handle.agent);
|
|
1726
1205
|
} catch (error) {
|
|
1727
1206
|
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1728
1207
|
}
|
|
1208
|
+
else {
|
|
1209
|
+
activation.handle.agent.cancel({ kind: "parent" });
|
|
1210
|
+
const idle = activation.handle.agent.whenIdle();
|
|
1211
|
+
const childDisposals = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0).map((child) => this.dispose(child));
|
|
1212
|
+
try {
|
|
1213
|
+
const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
|
|
1214
|
+
try {
|
|
1215
|
+
await disposal;
|
|
1216
|
+
return;
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
return error;
|
|
1219
|
+
}
|
|
1220
|
+
}))).filter((reason) => reason !== void 0);
|
|
1221
|
+
if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => errorChain(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
|
|
1222
|
+
await idle;
|
|
1223
|
+
await this.flushFinalState(activation);
|
|
1224
|
+
activation.observer.capture(activation.handle.agent);
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1729
1229
|
try {
|
|
1730
1230
|
await activation.handle.dispose();
|
|
1731
1231
|
} catch (error) {
|
|
@@ -1734,72 +1234,29 @@ var SubagentContinuationManager = class {
|
|
|
1734
1234
|
let failure;
|
|
1735
1235
|
if (failures.length === 1) failure = failures[0];
|
|
1736
1236
|
else if (failures.length > 1) failure = new SubagentError(`subagent "${childId}" activation teardown failed at ${failures.length} boundaries: ` + failures.map((item) => errorChain(item)).join("; "), "ACTIVATION_TEARDOWN_FAILED", { cause: new AggregateError(failures) });
|
|
1737
|
-
this.
|
|
1237
|
+
this.resident.delete(childId);
|
|
1738
1238
|
this.notifySettlement(activation, activation.observer.terminal(failure));
|
|
1739
1239
|
this.releaseOwnership(childId);
|
|
1740
1240
|
activation.observer.settle(failure);
|
|
1741
1241
|
if (failure !== void 0) throw failure;
|
|
1742
1242
|
}
|
|
1743
|
-
/**
|
|
1744
|
-
* Tell the durable direct parent that this child produced everything it is
|
|
1745
|
-
* going to. Unconditional for every child the caller received an id for: it
|
|
1746
|
-
* does not consider whether the child reported, because the cases that most
|
|
1747
|
-
* need it — a token ceiling, a model failure, cancellation, teardown — are
|
|
1748
|
-
* exactly the ones where the child never got to choose. A materialization
|
|
1749
|
-
* rolled back before its first acceptance stays silent, since the caller was
|
|
1750
|
-
* told that child was not established. A parent that is no longer live is not
|
|
1751
|
-
* an error; the child's own Session remains the durable record either way.
|
|
1752
|
-
* A parent whose own lineage is already closing receives the notice without a
|
|
1753
|
-
* wake, because teardown is not a reason to start a turn.
|
|
1754
|
-
*
|
|
1755
|
-
* Never blocks disposal. A delivery failure is logged and dropped, because
|
|
1756
|
-
* retaining a child to retry a notice would pin its whole ancestry in
|
|
1757
|
-
* `waiting` forever.
|
|
1758
|
-
* @param activation - the settling Activation, still owned by its parent.
|
|
1759
|
-
* @param terminal - how this epoch ended, as the terminal edge will report it.
|
|
1760
|
-
*/
|
|
1243
|
+
/** Tell the durable direct parent how this Activation ended. */
|
|
1761
1244
|
notifySettlement(activation, terminal) {
|
|
1762
1245
|
if (!activation.announced) return;
|
|
1763
1246
|
try {
|
|
1764
1247
|
const parent = this.ctx.agents.get(activation.parentSession);
|
|
1765
1248
|
if (parent === void 0) return;
|
|
1766
|
-
const
|
|
1767
|
-
const message = createUserMessage({
|
|
1768
|
-
content: [{
|
|
1769
|
-
type: "text",
|
|
1770
|
-
text: summary
|
|
1771
|
-
}, ...terminal.output === void 0 ? [{
|
|
1772
|
-
type: "text",
|
|
1773
|
-
text: "It left no closing message."
|
|
1774
|
-
}] : [{
|
|
1775
|
-
type: "text",
|
|
1776
|
-
text: "Its closing message:"
|
|
1777
|
-
}, ...terminal.output]],
|
|
1778
|
-
source: {
|
|
1779
|
-
kind: "subagent-settled",
|
|
1780
|
-
form: "notice",
|
|
1781
|
-
summary: boundContextSummary(summary),
|
|
1782
|
-
senderSessionId: activation.childId
|
|
1783
|
-
}
|
|
1784
|
-
});
|
|
1249
|
+
const message = createSettlementMessage(activation.childId, terminal);
|
|
1785
1250
|
if (this.closingTeardownFor(parent) !== void 0) {
|
|
1786
1251
|
parent.inject(message);
|
|
1787
1252
|
return;
|
|
1788
1253
|
}
|
|
1789
|
-
this.sendWaking(parent, message,
|
|
1790
|
-
if (parent.status === "idle") parent.followup(message);
|
|
1791
|
-
else parent.steer(message);
|
|
1792
|
-
});
|
|
1254
|
+
this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
|
|
1793
1255
|
} catch (error) {
|
|
1794
1256
|
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
|
|
1795
1257
|
}
|
|
1796
1258
|
}
|
|
1797
|
-
/**
|
|
1798
|
-
* Request a best-effort final session flush after the child is quiescent.
|
|
1799
|
-
* Listener failure is logged because flush participation cannot identify a
|
|
1800
|
-
* particular persistence backend, and teardown must still release ownership.
|
|
1801
|
-
* @param activation - the Activation whose final events should be flushed.
|
|
1802
|
-
*/
|
|
1259
|
+
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
1803
1260
|
async flushFinalState(activation) {
|
|
1804
1261
|
const child = activation.handle.agent;
|
|
1805
1262
|
try {
|
|
@@ -1808,6 +1265,591 @@ var SubagentContinuationManager = class {
|
|
|
1808
1265
|
this.ctx.logger.warn(`subagent "${activation.childId}" best-effort final session flush failed; the persisted state may be unavailable or stale on resume: ${errorChain(error)}`);
|
|
1809
1266
|
}
|
|
1810
1267
|
}
|
|
1268
|
+
};
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region lib/types/descriptor.js
|
|
1271
|
+
/**
|
|
1272
|
+
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
1273
|
+
* `subagent/descriptor` session event that identifies every session-backed
|
|
1274
|
+
* subagent and records whether it is one-shot or continuable. Continuable
|
|
1275
|
+
* descriptors additionally preserve the declared composition required for
|
|
1276
|
+
* cold resume. Providers append it turn-enclosed in the child's initial turn.
|
|
1277
|
+
*
|
|
1278
|
+
* The descriptor deliberately snapshots explicit fields rather than the
|
|
1279
|
+
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
1280
|
+
* make continuation fail merely because it is not JSON, and later composition
|
|
1281
|
+
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
1282
|
+
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
1283
|
+
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
1284
|
+
* to one activation's result contract rather than durable child composition.
|
|
1285
|
+
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
1286
|
+
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
1287
|
+
* live parent for authorization but reconstructs child options only from the
|
|
1288
|
+
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
1289
|
+
* the parent's current one; the resumed route's defaults apply instead.
|
|
1290
|
+
*
|
|
1291
|
+
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
1292
|
+
*/
|
|
1293
|
+
/**
|
|
1294
|
+
* The current descriptor format version, stamped into every appended
|
|
1295
|
+
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
1296
|
+
* Supporting another composition input is a deliberate version change, never
|
|
1297
|
+
* an implicit extra field.
|
|
1298
|
+
*/
|
|
1299
|
+
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
1300
|
+
const DESCRIPTOR_BASE_KEYS = [
|
|
1301
|
+
"version",
|
|
1302
|
+
"mode",
|
|
1303
|
+
"provider",
|
|
1304
|
+
"label"
|
|
1305
|
+
];
|
|
1306
|
+
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
1307
|
+
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
1308
|
+
...DESCRIPTOR_BASE_KEYS,
|
|
1309
|
+
"agentProvider",
|
|
1310
|
+
"agentModel",
|
|
1311
|
+
"agentReasoningEffort",
|
|
1312
|
+
"persona",
|
|
1313
|
+
"toolFilter"
|
|
1314
|
+
]);
|
|
1315
|
+
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
1316
|
+
/** Whether a persisted JSON value is an object record. */
|
|
1317
|
+
function isRecord(value) {
|
|
1318
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1319
|
+
}
|
|
1320
|
+
/** Reject fields outside one versioned record's declared schema. */
|
|
1321
|
+
function assertKnownKeys(value, keys, path) {
|
|
1322
|
+
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
1323
|
+
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
1324
|
+
}
|
|
1325
|
+
/** Read one optional string field from a persisted descriptor record. */
|
|
1326
|
+
function optionalString(value, key) {
|
|
1327
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
1328
|
+
const field = value[key];
|
|
1329
|
+
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
1330
|
+
return field;
|
|
1331
|
+
}
|
|
1332
|
+
/** Read one optional string-array field from a persisted tool restriction. */
|
|
1333
|
+
function optionalStringArray(value, key) {
|
|
1334
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
1335
|
+
const field = value[key];
|
|
1336
|
+
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
1337
|
+
const items = field;
|
|
1338
|
+
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
1339
|
+
return items;
|
|
1340
|
+
}
|
|
1341
|
+
/** Validate and reconstruct a persisted tool restriction. */
|
|
1342
|
+
function parseToolFilter(value) {
|
|
1343
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
1344
|
+
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
1345
|
+
const allow = optionalStringArray(value, "allow");
|
|
1346
|
+
const deny = optionalStringArray(value, "deny");
|
|
1347
|
+
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
1348
|
+
return {
|
|
1349
|
+
...allow !== void 0 ? { allow } : {},
|
|
1350
|
+
...deny !== void 0 ? { deny } : {}
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
/** Validate one persisted descriptor payload for the current runtime. */
|
|
1354
|
+
function parseSubagentDescriptor(value) {
|
|
1355
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
1356
|
+
const version = value["version"];
|
|
1357
|
+
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
1358
|
+
if (version !== 3) return void 0;
|
|
1359
|
+
const mode = value["mode"];
|
|
1360
|
+
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
1361
|
+
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
1362
|
+
const provider = value["provider"];
|
|
1363
|
+
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
1364
|
+
if (mode === "one-shot") {
|
|
1365
|
+
const label = optionalString(value, "label");
|
|
1366
|
+
return {
|
|
1367
|
+
version: 3,
|
|
1368
|
+
mode,
|
|
1369
|
+
provider,
|
|
1370
|
+
...label !== void 0 ? { label } : {}
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
const label = value["label"];
|
|
1374
|
+
if (typeof label !== "string") throw new Error("persisted subagent descriptor label must be a string");
|
|
1375
|
+
const agentProvider = optionalString(value, "agentProvider");
|
|
1376
|
+
const agentModel = optionalString(value, "agentModel");
|
|
1377
|
+
const agentReasoningEffort = optionalString(value, "agentReasoningEffort");
|
|
1378
|
+
const persona = optionalString(value, "persona");
|
|
1379
|
+
const toolFilter = Object.hasOwn(value, "toolFilter") ? parseToolFilter(value["toolFilter"]) : void 0;
|
|
1380
|
+
return {
|
|
1381
|
+
version: 3,
|
|
1382
|
+
mode,
|
|
1383
|
+
provider,
|
|
1384
|
+
label,
|
|
1385
|
+
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
1386
|
+
...agentModel !== void 0 ? { agentModel } : {},
|
|
1387
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
1388
|
+
...persona !== void 0 ? { persona } : {},
|
|
1389
|
+
...toolFilter !== void 0 ? { toolFilter } : {}
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
function snapshotSubagentDescriptor(input) {
|
|
1393
|
+
const snapshot = snapshotJsonValue(input.mode === "one-shot" ? {
|
|
1394
|
+
version: 3,
|
|
1395
|
+
mode: input.mode,
|
|
1396
|
+
provider: input.provider,
|
|
1397
|
+
...input.label !== void 0 ? { label: input.label } : {}
|
|
1398
|
+
} : {
|
|
1399
|
+
version: 3,
|
|
1400
|
+
mode: input.mode,
|
|
1401
|
+
provider: input.provider,
|
|
1402
|
+
label: input.label,
|
|
1403
|
+
...input.agentProvider !== void 0 ? { agentProvider: input.agentProvider } : {},
|
|
1404
|
+
...input.agentModel !== void 0 ? { agentModel: input.agentModel } : {},
|
|
1405
|
+
...input.agentReasoningEffort !== void 0 ? { agentReasoningEffort: input.agentReasoningEffort } : {},
|
|
1406
|
+
...input.persona !== void 0 ? { persona: input.persona } : {},
|
|
1407
|
+
...input.toolFilter !== void 0 ? { toolFilter: input.toolFilter } : {}
|
|
1408
|
+
});
|
|
1409
|
+
if (snapshot === void 0) throw new Error("subagent descriptor is not losslessly JSON-serializable");
|
|
1410
|
+
return snapshot;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Fold a persisted child log to its supported descriptor. The first
|
|
1414
|
+
* `subagent/descriptor` event is authoritative — the establishing provider
|
|
1415
|
+
* appends exactly one, so a later same-type event cannot rewrite the declared
|
|
1416
|
+
* composition.
|
|
1417
|
+
* @param events - the loaded child session events.
|
|
1418
|
+
* @returns the descriptor, or `undefined` when the log has none or its
|
|
1419
|
+
* version is not {@link SUBAGENT_DESCRIPTOR_VERSION} (the child cannot be
|
|
1420
|
+
* classified by this runtime).
|
|
1421
|
+
* @throws when a current-version persisted payload does not match its complete
|
|
1422
|
+
* declared schema.
|
|
1423
|
+
*/
|
|
1424
|
+
function foldSubagentDescriptor(events) {
|
|
1425
|
+
const event = events.find((candidate) => candidate.type === "subagent/descriptor");
|
|
1426
|
+
if (event === void 0) return void 0;
|
|
1427
|
+
return parseSubagentDescriptor(event.data);
|
|
1428
|
+
}
|
|
1429
|
+
//#endregion
|
|
1430
|
+
//#region lib/types/internal.js
|
|
1431
|
+
/**
|
|
1432
|
+
* Continuation integration markers and host adapters outside the public
|
|
1433
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
1434
|
+
* @module @deepseek-ai/dsh-subagent/internal
|
|
1435
|
+
*/
|
|
1436
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
1437
|
+
const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
|
|
1438
|
+
/**
|
|
1439
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
1440
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
1441
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
1442
|
+
*/
|
|
1443
|
+
function isAdjacentAgentSendMessageTool(definition) {
|
|
1444
|
+
return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
1448
|
+
* entry and this unbundled internal subpath.
|
|
1449
|
+
* @internal
|
|
1450
|
+
*/
|
|
1451
|
+
const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
|
|
1452
|
+
//#endregion
|
|
1453
|
+
//#region lib/types/continuation.js
|
|
1454
|
+
/**
|
|
1455
|
+
* Continuable-subagent orchestration behind `ctx.subagents`: stable child ids,
|
|
1456
|
+
* descriptor persistence, provider preparation, cold resume, authorization,
|
|
1457
|
+
* and message routing. {@link ContinuableActivationRegistry} owns the mutable
|
|
1458
|
+
* process-local Activation graph and its settlement and disposal lifecycle.
|
|
1459
|
+
*
|
|
1460
|
+
* A continuable child has one durable Session and at most one process-local
|
|
1461
|
+
* Activation. The Agent inbox is the only turn queue, so this manager owns
|
|
1462
|
+
* durable orchestration while the Agent loop owns all turn ordering and
|
|
1463
|
+
* execution. No continuable path creates a Task or an intermediate
|
|
1464
|
+
* result-bearing wrapper.
|
|
1465
|
+
*
|
|
1466
|
+
* @module @deepseek-ai/dsh-subagent
|
|
1467
|
+
*/
|
|
1468
|
+
var __addDisposableResource$1 = function(env, value, async) {
|
|
1469
|
+
if (value !== null && value !== void 0) {
|
|
1470
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1471
|
+
var dispose, inner;
|
|
1472
|
+
if (async) {
|
|
1473
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
1474
|
+
dispose = value[Symbol.asyncDispose];
|
|
1475
|
+
}
|
|
1476
|
+
if (dispose === void 0) {
|
|
1477
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
1478
|
+
dispose = value[Symbol.dispose];
|
|
1479
|
+
if (async) inner = dispose;
|
|
1480
|
+
}
|
|
1481
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
1482
|
+
if (inner) dispose = function() {
|
|
1483
|
+
try {
|
|
1484
|
+
inner.call(this);
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
return Promise.reject(e);
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
env.stack.push({
|
|
1490
|
+
value,
|
|
1491
|
+
dispose,
|
|
1492
|
+
async
|
|
1493
|
+
});
|
|
1494
|
+
} else if (async) env.stack.push({ async: true });
|
|
1495
|
+
return value;
|
|
1496
|
+
};
|
|
1497
|
+
var __disposeResources$1 = (function(SuppressedError) {
|
|
1498
|
+
return function(env) {
|
|
1499
|
+
function fail(e) {
|
|
1500
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
1501
|
+
env.hasError = true;
|
|
1502
|
+
}
|
|
1503
|
+
var r, s = 0;
|
|
1504
|
+
function next() {
|
|
1505
|
+
while (r = env.stack.pop()) try {
|
|
1506
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
1507
|
+
if (r.dispose) {
|
|
1508
|
+
var result = r.dispose.call(r.value);
|
|
1509
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
1510
|
+
fail(e);
|
|
1511
|
+
return next();
|
|
1512
|
+
});
|
|
1513
|
+
} else s |= 1;
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
fail(e);
|
|
1516
|
+
}
|
|
1517
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
1518
|
+
if (env.hasError) throw env.error;
|
|
1519
|
+
}
|
|
1520
|
+
return next();
|
|
1521
|
+
};
|
|
1522
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
1523
|
+
var e = new Error(message);
|
|
1524
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1525
|
+
});
|
|
1526
|
+
/**
|
|
1527
|
+
* The continuable-subagent orchestration service behind `ctx.subagents`. Tool
|
|
1528
|
+
* schema and host adapters are consumers of this one contract; foreground
|
|
1529
|
+
* one-shot delegation keeps calling `ctx.subagents.start()` and never enters
|
|
1530
|
+
* this lifecycle.
|
|
1531
|
+
*/
|
|
1532
|
+
var SubagentContinuationManager = class {
|
|
1533
|
+
ctx;
|
|
1534
|
+
host;
|
|
1535
|
+
activations;
|
|
1536
|
+
constructor(ctx, host) {
|
|
1537
|
+
this.ctx = ctx;
|
|
1538
|
+
this.host = host;
|
|
1539
|
+
this.activations = new ContinuableActivationRegistry(ctx, (provider, childId, parent) => host.observeActivation(provider, childId, parent));
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Start one continuable background child and resolve at initial inbox acceptance.
|
|
1543
|
+
* Every earlier failure disposes any created handle and rolls back Activation
|
|
1544
|
+
* and parent ownership without returning either id.
|
|
1545
|
+
* @param spec - provider, delegation request, and caller cancellation.
|
|
1546
|
+
* @returns the durable child id and accepted initial prompt message id.
|
|
1547
|
+
*/
|
|
1548
|
+
async startContinuable(spec) {
|
|
1549
|
+
const request = spec.request;
|
|
1550
|
+
const parent = request.parent;
|
|
1551
|
+
this.activations.assertAdmitting(parent);
|
|
1552
|
+
const persistence = this.requirePersistence();
|
|
1553
|
+
assertSubagentMaxDepth(request.maxDepth);
|
|
1554
|
+
const childId = spec.childId ?? brandString(randomUUID());
|
|
1555
|
+
this.activations.assertChildIdAvailable(childId);
|
|
1556
|
+
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
1557
|
+
const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
|
|
1558
|
+
const agentProvider = agentOptions.provider;
|
|
1559
|
+
const agentModel = agentOptions.model;
|
|
1560
|
+
const agentReasoningEffort = agentOptions.reasoningEffort;
|
|
1561
|
+
const descriptor = snapshotSubagentDescriptor({
|
|
1562
|
+
mode: "continuable",
|
|
1563
|
+
provider: spec.provider,
|
|
1564
|
+
label: spec.label,
|
|
1565
|
+
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
1566
|
+
...agentModel !== void 0 ? { agentModel } : {},
|
|
1567
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
1568
|
+
...request.persona !== void 0 ? { persona: request.persona } : {},
|
|
1569
|
+
...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
|
|
1570
|
+
});
|
|
1571
|
+
const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
|
|
1572
|
+
const releaseHold = this.activations.holdOwnership(parent, childId);
|
|
1573
|
+
try {
|
|
1574
|
+
const prepared = await this.host.prepareContinuable(spec.provider, {
|
|
1575
|
+
sessionId: childId,
|
|
1576
|
+
parent,
|
|
1577
|
+
signal: spec.signal
|
|
1578
|
+
});
|
|
1579
|
+
spec.signal.throwIfAborted();
|
|
1580
|
+
this.activations.assertAdmitting(parent);
|
|
1581
|
+
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
1582
|
+
const seed = prepared.seed;
|
|
1583
|
+
return {
|
|
1584
|
+
childId,
|
|
1585
|
+
messageId: await this.activations.locks.run(childId, async () => {
|
|
1586
|
+
spec.signal.throwIfAborted();
|
|
1587
|
+
this.activations.assertAdmitting(parent);
|
|
1588
|
+
this.activations.assertChildIdAvailable(childId);
|
|
1589
|
+
if (spec.childId !== void 0) {
|
|
1590
|
+
const persisted = await persistence.stat(childId, { signal: spec.signal });
|
|
1591
|
+
spec.signal.throwIfAborted();
|
|
1592
|
+
this.activations.assertAdmitting(parent);
|
|
1593
|
+
this.activations.assertChildIdAvailable(childId);
|
|
1594
|
+
if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1595
|
+
}
|
|
1596
|
+
const activation = await this.activations.materialize({
|
|
1597
|
+
childId,
|
|
1598
|
+
provider: spec.provider,
|
|
1599
|
+
parent,
|
|
1600
|
+
create: {
|
|
1601
|
+
seed,
|
|
1602
|
+
meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
|
|
1603
|
+
inheritedEventCount,
|
|
1604
|
+
delegatedPolicies,
|
|
1605
|
+
descriptor
|
|
1606
|
+
},
|
|
1607
|
+
agentOptions,
|
|
1608
|
+
composition: {
|
|
1609
|
+
persona: request.persona,
|
|
1610
|
+
toolFilter: request.toolFilter
|
|
1611
|
+
},
|
|
1612
|
+
signal: spec.signal
|
|
1613
|
+
});
|
|
1614
|
+
return this.submitMaterialized(activation, isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", activation.handle.agent)) ? withContinuableReturnGuidance(parent.id, request.prompt) : request.prompt, {
|
|
1615
|
+
source: { kind: "user" },
|
|
1616
|
+
signal: spec.signal,
|
|
1617
|
+
delivery: "queue"
|
|
1618
|
+
}, parent);
|
|
1619
|
+
})
|
|
1620
|
+
};
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
releaseHold();
|
|
1623
|
+
throw error;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Deliver one model-authored message to a direct continuable child or to the
|
|
1628
|
+
* sender's direct parent. A missing direct child cold-resumes through the
|
|
1629
|
+
* ordinary continuation lifecycle.
|
|
1630
|
+
* @param sender - exact live Agent authorizing and originating the message.
|
|
1631
|
+
* @param targetId - durable direct-parent or direct-child session id.
|
|
1632
|
+
* @param content - model-authored content to deliver.
|
|
1633
|
+
* @param options - caller cancellation before acceptance.
|
|
1634
|
+
* @returns the accepted message's inbox id.
|
|
1635
|
+
*/
|
|
1636
|
+
async sendMessage(sender, targetId, content, options) {
|
|
1637
|
+
if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
|
|
1638
|
+
this.activations.assertAdmitting(sender);
|
|
1639
|
+
const senderActivation = this.activations.get(sender.id);
|
|
1640
|
+
if (senderActivation !== void 0 && senderActivation.handle.agent === sender && senderActivation.parentSession === targetId) {
|
|
1641
|
+
options.signal.throwIfAborted();
|
|
1642
|
+
return this.sendToParent(senderActivation, sender, content);
|
|
1643
|
+
}
|
|
1644
|
+
if (sender.session.header.parentSession === targetId) throw new SubagentError(`agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`, "UNAUTHORIZED");
|
|
1645
|
+
return this.deliverToChild(sender, targetId, content, {
|
|
1646
|
+
signal: options.signal,
|
|
1647
|
+
delivery: "steer"
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Queue one human-authored prompt as a distinct direct-child turn.
|
|
1652
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1653
|
+
* @param childId - durable direct-child session id.
|
|
1654
|
+
* @param content - model-visible prompt blocks.
|
|
1655
|
+
* @param source - durable attribution for the human prompt.
|
|
1656
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1657
|
+
* @returns the accepted durable message id.
|
|
1658
|
+
*/
|
|
1659
|
+
async queuePrompt(parent, childId, content, source, signal) {
|
|
1660
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1661
|
+
source,
|
|
1662
|
+
signal,
|
|
1663
|
+
delivery: "queue"
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Steer one host-authored prompt to a direct continuable child.
|
|
1668
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1669
|
+
* @param childId - durable direct-child session id.
|
|
1670
|
+
* @param content - model-visible prompt blocks.
|
|
1671
|
+
* @param source - durable attribution for the host prompt.
|
|
1672
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1673
|
+
* @returns the accepted durable message id.
|
|
1674
|
+
*/
|
|
1675
|
+
async steerPrompt(parent, childId, content, source, signal) {
|
|
1676
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1677
|
+
source,
|
|
1678
|
+
signal,
|
|
1679
|
+
delivery: "steer"
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1682
|
+
/** Route one parent-originated delivery through residency and cold resume. */
|
|
1683
|
+
async deliverToChild(parent, childId, content, options) {
|
|
1684
|
+
this.activations.assertAdmitting(parent);
|
|
1685
|
+
const releaseHold = this.activations.holdOwnership(parent, childId);
|
|
1686
|
+
try {
|
|
1687
|
+
return await this.deliverFollowup(parent, childId, content, options);
|
|
1688
|
+
} catch (error) {
|
|
1689
|
+
releaseHold();
|
|
1690
|
+
throw error;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
/** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
|
|
1694
|
+
async deliverFollowup(parent, childId, content, options) {
|
|
1695
|
+
while (true) {
|
|
1696
|
+
const live = await this.activations.locks.run(childId, async () => {
|
|
1697
|
+
const activation = this.activations.get(childId);
|
|
1698
|
+
if (activation === void 0) return this.coldResume(parent, childId, content, options);
|
|
1699
|
+
const disposal = activation.inbox.closing;
|
|
1700
|
+
/* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
|
|
1701
|
+
* observe the transaction inside the same critical section that opened it. */
|
|
1702
|
+
if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
|
|
1703
|
+
if (contentHasImage(content)) {
|
|
1704
|
+
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1705
|
+
if (activation.inbox.closing !== void 0) {
|
|
1706
|
+
await Promise.allSettled([activation.inbox.closing]);
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
return this.submitAdmitted(activation, content, options, parent);
|
|
1711
|
+
});
|
|
1712
|
+
/* v8 ignore start -- only a delivery that lost the disposal cutoff retries. */
|
|
1713
|
+
if (live !== void 0) return live;
|
|
1714
|
+
this.activations.assertAdmitting(parent);
|
|
1715
|
+
options.signal.throwIfAborted();
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Interrupt one live continuable child's current turn. Admission is
|
|
1720
|
+
* synchronous and the cancellation effect is asynchronous. An absent or
|
|
1721
|
+
* already-closing target is an accepted no-op after authority checks.
|
|
1722
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
1723
|
+
* @param authority - the human parent address or exact live ancestor Agent.
|
|
1724
|
+
*/
|
|
1725
|
+
interrupt(targetSessionId, authority) {
|
|
1726
|
+
this.activations.interrupt(targetSessionId, authority);
|
|
1727
|
+
}
|
|
1728
|
+
/** Deliver one resident continuable child's message to its live direct parent. */
|
|
1729
|
+
sendToParent(activation, sender, content) {
|
|
1730
|
+
/* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
1731
|
+
* transaction between exact-agent authorization and this no-await span. */
|
|
1732
|
+
if (activation.inbox.closing !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
|
|
1733
|
+
const parent = this.ctx.agents.get(activation.parentSession);
|
|
1734
|
+
if (parent === void 0) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
|
|
1735
|
+
const message = createAgentMessage(sender, content);
|
|
1736
|
+
this.sendAgentMessage(parent, message);
|
|
1737
|
+
return message.id;
|
|
1738
|
+
}
|
|
1739
|
+
/** Send one Agent message while translating only the target's own rejection. */
|
|
1740
|
+
sendAgentMessage(parent, message) {
|
|
1741
|
+
try {
|
|
1742
|
+
this.activations.sendWaking(parent, message, "steer");
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
/** Close manager-wide admission and release every live Activation. */
|
|
1748
|
+
async drain() {
|
|
1749
|
+
await this.activations.drain();
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Stop only the continuable descendants of exact live host-owned parents.
|
|
1753
|
+
* @param parents - exact live roots whose continuable descendants must stop.
|
|
1754
|
+
*/
|
|
1755
|
+
async drainDescendants(parents) {
|
|
1756
|
+
await this.activations.drainDescendants(parents);
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Release selected resident direct children of one exact live parent.
|
|
1760
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
1761
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
1762
|
+
*/
|
|
1763
|
+
async drainChildren(parent, childIds) {
|
|
1764
|
+
await this.activations.drainChildren(parent, childIds);
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Cold-resume a persisted child and submit the waiting turn. The descriptor
|
|
1768
|
+
* supplies every reconstruction input; no subagent provider is dispatched.
|
|
1769
|
+
*/
|
|
1770
|
+
async coldResume(parent, childId, content, options) {
|
|
1771
|
+
const env_1 = {
|
|
1772
|
+
stack: [],
|
|
1773
|
+
error: void 0,
|
|
1774
|
+
hasError: false
|
|
1775
|
+
};
|
|
1776
|
+
try {
|
|
1777
|
+
const query = this.requireSessionQuery();
|
|
1778
|
+
let observation;
|
|
1779
|
+
try {
|
|
1780
|
+
observation = await query.observeSession(childId, { signal: options.signal });
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
options.signal.throwIfAborted();
|
|
1783
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1784
|
+
}
|
|
1785
|
+
const source = __addDisposableResource$1(env_1, observation, false);
|
|
1786
|
+
this.activations.assertAdmitting(parent);
|
|
1787
|
+
this.activations.authorizeLineage(parent, childId, source.header.parentSession);
|
|
1788
|
+
const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
|
|
1789
|
+
if (descriptor === void 0 || descriptor.mode !== "continuable") throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, "NOT_RESUMABLE");
|
|
1790
|
+
let activation;
|
|
1791
|
+
try {
|
|
1792
|
+
activation = await this.activations.materialize({
|
|
1793
|
+
childId,
|
|
1794
|
+
provider: descriptor.provider,
|
|
1795
|
+
parent,
|
|
1796
|
+
agentOptions: {
|
|
1797
|
+
...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
|
|
1798
|
+
...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
|
|
1799
|
+
...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
|
|
1800
|
+
},
|
|
1801
|
+
composition: {
|
|
1802
|
+
persona: descriptor.persona,
|
|
1803
|
+
toolFilter: descriptor.toolFilter
|
|
1804
|
+
},
|
|
1805
|
+
signal: options.signal
|
|
1806
|
+
});
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
options.signal.throwIfAborted();
|
|
1809
|
+
if (error instanceof SubagentError) throw error;
|
|
1810
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1811
|
+
}
|
|
1812
|
+
return await this.submitMaterialized(activation, content, options, parent);
|
|
1813
|
+
} catch (e_1) {
|
|
1814
|
+
env_1.error = e_1;
|
|
1815
|
+
env_1.hasError = true;
|
|
1816
|
+
} finally {
|
|
1817
|
+
__disposeResources$1(env_1);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
/** Submit to a freshly materialized Activation or roll it back completely. */
|
|
1821
|
+
async submitMaterialized(activation, content, options, parent) {
|
|
1822
|
+
try {
|
|
1823
|
+
if (contentHasImage(content)) {
|
|
1824
|
+
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1825
|
+
if (activation.inbox.closing !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
|
|
1826
|
+
}
|
|
1827
|
+
return this.submitAdmitted(activation, content, options, parent);
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
/* v8 ignore next -- rollback disposal failures must not mask the
|
|
1830
|
+
* pre-acceptance signal, drain, or lifecycle failure. */
|
|
1831
|
+
await this.activations.dispose(activation).catch(() => void 0);
|
|
1832
|
+
throw error;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
/** Build and submit one message across the final synchronous admission cutoff. */
|
|
1836
|
+
submitAdmitted(activation, content, options, parent) {
|
|
1837
|
+
const message = options.source === void 0 ? createAgentMessage(parent, content) : createUserMessage({
|
|
1838
|
+
content,
|
|
1839
|
+
source: options.source
|
|
1840
|
+
});
|
|
1841
|
+
return this.activations.submitAdmitted(activation, message, options.delivery, parent, options.signal);
|
|
1842
|
+
}
|
|
1843
|
+
/** Refuse image content for a child whose fixed model accepts text only. */
|
|
1844
|
+
async assertImageCapable(agent, signal) {
|
|
1845
|
+
const { provider, model } = agent.options;
|
|
1846
|
+
if (provider === void 0 || model === void 0) return;
|
|
1847
|
+
const llm = this.ctx.get("llm");
|
|
1848
|
+
/* v8 ignore next -- without an LLM registry, delivery defers to projection. */
|
|
1849
|
+
if (llm === void 0) return;
|
|
1850
|
+
const info = await llm.resolveModelInfo(provider, model, signal);
|
|
1851
|
+
if (info.inputModalities !== void 0 && !info.inputModalities.includes("image")) throw new SubagentError(`Model "${model}" does not support image input.`, "MODEL_DOES_NOT_SUPPORT_IMAGES");
|
|
1852
|
+
}
|
|
1811
1853
|
/** Resolve the persistence service continuable children require, or fail loud. */
|
|
1812
1854
|
requirePersistence() {
|
|
1813
1855
|
const persistence = this.ctx.get("sessionPersistence");
|
|
@@ -2319,7 +2361,7 @@ const subagentIdentityProjectionDefinition = {
|
|
|
2319
2361
|
* working-directory resolution (config override, else the delegating parent
|
|
2320
2362
|
* session's workspace), the never-reject result settlement, and the standard
|
|
2321
2363
|
* run-handle publication. Backends compose these with their own wire drivers;
|
|
2322
|
-
* the process machinery itself (spawn, env scrub,
|
|
2364
|
+
* the process machinery itself (spawn, env scrub, managed-range teardown)
|
|
2323
2365
|
* belongs to the `dsh-subprocess` seam.
|
|
2324
2366
|
*
|
|
2325
2367
|
* @module @deepseek-ai/dsh-subagent/out-of-process
|
|
@@ -2751,7 +2793,7 @@ let SubagentRuntime = (() => {
|
|
|
2751
2793
|
return this.requireContinuations().sendMessage(sender, targetId, content, options);
|
|
2752
2794
|
}
|
|
2753
2795
|
/**
|
|
2754
|
-
*
|
|
2796
|
+
* Deliver one host-protocol message to a direct continuable child.
|
|
2755
2797
|
* Symbol-keyed so host adapters can preserve their own provenance without
|
|
2756
2798
|
* widening the public Service Definition or impersonating an Agent sender.
|
|
2757
2799
|
* @param parent - exact live direct parent authorizing delivery.
|
|
@@ -2759,10 +2801,11 @@ let SubagentRuntime = (() => {
|
|
|
2759
2801
|
* @param content - host-authored content to deliver.
|
|
2760
2802
|
* @param source - durable host-protocol provenance.
|
|
2761
2803
|
* @param signal - caller cancellation before inbox acceptance.
|
|
2804
|
+
* @param delivery - Queue as a distinct turn or Steer at the nearest step.
|
|
2762
2805
|
* @returns the accepted message's inbox id.
|
|
2763
2806
|
*/
|
|
2764
|
-
[
|
|
2765
|
-
return this.requireContinuations().queuePrompt(parent, childId, content, source, signal);
|
|
2807
|
+
[deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
|
|
2808
|
+
return delivery === "steer" ? this.requireContinuations().steerPrompt(parent, childId, content, source, signal) : this.requireContinuations().queuePrompt(parent, childId, content, source, signal);
|
|
2766
2809
|
}
|
|
2767
2810
|
/**
|
|
2768
2811
|
* Interrupt one live continuable child's current turn under a human parent
|
|
@@ -2875,11 +2918,12 @@ let SubagentRuntime = (() => {
|
|
|
2875
2918
|
* Deliver one browser-authored message to a continuable child through the
|
|
2876
2919
|
* exact live direct parent, retaining the caller-minted request identity and
|
|
2877
2920
|
* validated browser zone on the accepted message. Success identifies the
|
|
2878
|
-
* message the child's
|
|
2879
|
-
*
|
|
2921
|
+
* message the child's inbox accepted; later execution is independent of this
|
|
2922
|
+
* call. Queue delivery targets a later turn; steer delivery targets the
|
|
2923
|
+
* nearest step and retains the Agent loop's best-effort fallback semantics.
|
|
2880
2924
|
* Image parts are admitted and persisted through the attachment store
|
|
2881
2925
|
* before delivery, and the child's model must accept image input.
|
|
2882
|
-
* @param request - durable address, minted identity, content, and optional browser zone.
|
|
2926
|
+
* @param request - durable address, delivery, minted identity, content, and optional browser zone.
|
|
2883
2927
|
* @param signal - carrier cancellation, owning the call until inbox acceptance.
|
|
2884
2928
|
* @returns the accepted message's inbox identity.
|
|
2885
2929
|
* @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
|
|
@@ -2888,7 +2932,7 @@ let SubagentRuntime = (() => {
|
|
|
2888
2932
|
* `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
|
|
2889
2933
|
*/
|
|
2890
2934
|
async prompt(request, signal) {
|
|
2891
|
-
const { parentSessionId, childSessionId, clientTimeZone } = request;
|
|
2935
|
+
const { parentSessionId, childSessionId, clientTimeZone, delivery } = request;
|
|
2892
2936
|
validateControlRequest("subagent.prompt", request);
|
|
2893
2937
|
const canonicalTimeZone = clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(clientTimeZone);
|
|
2894
2938
|
if (clientTimeZone !== void 0 && canonicalTimeZone === void 0) throw new RemoteError("subagent/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: clientTimeZone });
|
|
@@ -2908,9 +2952,9 @@ let SubagentRuntime = (() => {
|
|
|
2908
2952
|
else {
|
|
2909
2953
|
const attachments = this.ctx.get("attachments");
|
|
2910
2954
|
if (attachments === void 0) throw new Error("subagent image prompt requires an attachment store");
|
|
2911
|
-
content = await admitPromptContent(
|
|
2955
|
+
content = await attachments.admitPromptContent(request.content);
|
|
2912
2956
|
}
|
|
2913
|
-
return { messageId: await this[
|
|
2957
|
+
return { messageId: await this[deliverSubagentPrompt](parent, childSessionId, content, source, signal, delivery) };
|
|
2914
2958
|
} catch (error) {
|
|
2915
2959
|
return rejectPrompt(error, childSessionId, signal);
|
|
2916
2960
|
}
|
|
@@ -3063,4 +3107,4 @@ let SubagentRuntime = (() => {
|
|
|
3063
3107
|
};
|
|
3064
3108
|
})();
|
|
3065
3109
|
//#endregion
|
|
3066
|
-
export { AssistantOutputFold, NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildCwd, resolveChildDepth,
|
|
3110
|
+
export { AssistantOutputFold, NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildCwd, resolveChildDepth, settleRun, settleRunResult, snapshotSubagentDescriptor, subprocessRunHandle, validateConfiguredCwd };
|