@fishwithcat/dsh-subagent 0.0.0-stage → 0.2.2-alpha.3
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 +121 -0
- package/README.md +258 -2
- package/README.zh.md +259 -0
- package/lib/index.js +3065 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1086 -0
- package/lib/typert.remote-client.d.ts +29 -0
- package/lib/typert.remote-client.js +122 -0
- package/lib/types/activation.d.ts +110 -0
- package/lib/types/activation.js +112 -0
- package/lib/types/archive-admission.d.ts +23 -0
- package/lib/types/archive-admission.js +75 -0
- package/lib/types/assistant-output.d.ts +48 -0
- package/lib/types/assistant-output.js +73 -0
- package/lib/types/catalog.d.ts +87 -0
- package/lib/types/catalog.js +113 -0
- package/lib/types/child-agent.d.ts +145 -0
- package/lib/types/child-agent.js +229 -0
- package/lib/types/client.d.ts +8 -0
- package/lib/types/client.js +7 -0
- package/lib/types/continuation-messages.d.ts +66 -0
- package/lib/types/continuation-messages.js +116 -0
- package/lib/types/control-types.d.ts +121 -0
- package/lib/types/control-types.js +8 -0
- package/lib/types/control.d.ts +41 -0
- package/lib/types/control.js +75 -0
- package/lib/types/depth.d.ts +31 -0
- package/lib/types/depth.js +39 -0
- package/lib/types/descriptor.d.ts +105 -0
- package/lib/types/descriptor.js +192 -0
- package/lib/types/error.d.ts +11 -0
- package/lib/types/error.js +14 -0
- package/lib/types/index.d.ts +282 -0
- package/lib/types/index.js +398 -0
- package/lib/types/lifecycle.d.ts +54 -0
- package/lib/types/lifecycle.js +89 -0
- package/lib/types/list-children.d.ts +36 -0
- package/lib/types/list-children.js +166 -0
- package/lib/types/manager.d.ts +215 -0
- package/lib/types/manager.js +1171 -0
- package/lib/types/out-of-process.d.ts +92 -0
- package/lib/types/out-of-process.js +177 -0
- package/lib/types/projection-types.d.ts +93 -0
- package/lib/types/projection-types.js +7 -0
- package/lib/types/projection.d.ts +94 -0
- package/lib/types/projection.js +157 -0
- package/lib/types/structured.d.ts +49 -0
- package/lib/types/structured.js +130 -0
- package/lib/types/types.d.ts +331 -0
- package/lib/types/types.js +17 -0
- package/locale/de.json +6 -0
- package/locale/en.json +5 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/ja.json +6 -0
- package/locale/pt.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +131 -4
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
36
|
+
import z from '@deepseek-ai/schemastery';
|
|
37
|
+
import { scopeTarget } from '@deepseek-ai/dsh-scope';
|
|
38
|
+
import { assertObjectJsonSchema } from '@deepseek-ai/dsh-tools';
|
|
39
|
+
import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time';
|
|
40
|
+
import { Remote, RemoteError, TypertRemoteService, principalOf } from '@deepseek-ai/dsh-typert-protocol';
|
|
41
|
+
import { rejectPrompt, validateControlRequest, } from "./control.js";
|
|
42
|
+
import { SubagentError } from "./error.js";
|
|
43
|
+
import { assertSubagentMaxDepth } from "./depth.js";
|
|
44
|
+
import { createLifecycleEmitter } from "./lifecycle.js";
|
|
45
|
+
import SubagentManager from "./manager.js";
|
|
46
|
+
import { listDescendants as listSubagentDescendants } from "./list-children.js";
|
|
47
|
+
import { subagentIdentityProjectionDefinition, subagentTimingProjectionDefinition } from "./projection.js";
|
|
48
|
+
import { subagentCatalogProjectionDefinition } from "./catalog.js";
|
|
49
|
+
export * from "./out-of-process.js";
|
|
50
|
+
export { AssistantOutputFold, finalAssistantOutput } from "./assistant-output.js";
|
|
51
|
+
export { SubagentRunId } from "./types.js";
|
|
52
|
+
export { foldSubagentDescriptor, snapshotSubagentDescriptor, SUBAGENT_DESCRIPTOR_VERSION, } from "./descriptor.js";
|
|
53
|
+
export { STRUCTURED_OUTPUT_TOOL } from "./structured.js";
|
|
54
|
+
export { SubagentError } from "./error.js";
|
|
55
|
+
export { assertSubagentMaxDepth, delegationDepthOf } from "./depth.js";
|
|
56
|
+
export { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides, childSessionMeta, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildDepth, SubagentDepthError, } from "./child-agent.js";
|
|
57
|
+
/** Named provider registry with managed activations, durable discovery, and local child messaging. */
|
|
58
|
+
let SubagentRuntime = (() => {
|
|
59
|
+
let _classSuper = TypertRemoteService;
|
|
60
|
+
let _instanceExtraInitializers = [];
|
|
61
|
+
let _prompt_decorators;
|
|
62
|
+
let _interruptByUser_decorators;
|
|
63
|
+
return class SubagentRuntime extends _classSuper {
|
|
64
|
+
static {
|
|
65
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
66
|
+
_prompt_decorators = [Remote({ name: 'prompt', resources: { session: request => request.childSessionId } })];
|
|
67
|
+
_interruptByUser_decorators = [Remote({ name: 'interruptByUser', resources: { session: childSessionId => childSessionId } })];
|
|
68
|
+
__esDecorate(this, null, _prompt_decorators, { kind: "method", name: "prompt", static: false, private: false, access: { has: obj => "prompt" in obj, get: obj => obj.prompt }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
69
|
+
__esDecorate(this, null, _interruptByUser_decorators, { kind: "method", name: "interruptByUser", static: false, private: false, access: { has: obj => "interruptByUser" in obj, get: obj => obj.interruptByUser }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
70
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
71
|
+
}
|
|
72
|
+
config = __runInitializers(this, _instanceExtraInitializers);
|
|
73
|
+
static Config = z.object({
|
|
74
|
+
delegatedApproval: z.union(['never', 'inherit']).default('never').volatile(),
|
|
75
|
+
maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
|
|
76
|
+
maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile(),
|
|
77
|
+
});
|
|
78
|
+
static inject = ['workingDirectory', 'sessionProjections'];
|
|
79
|
+
providers = new Map();
|
|
80
|
+
manager;
|
|
81
|
+
/**
|
|
82
|
+
* The contained lifecycle-edge publisher. Built here because scoped dispatch
|
|
83
|
+
* keys its carrier by this exact service instance, whose own context filter
|
|
84
|
+
* composes into the carrier.
|
|
85
|
+
*/
|
|
86
|
+
emitLifecycle;
|
|
87
|
+
constructor(ctx, config) {
|
|
88
|
+
super(ctx, 'subagents');
|
|
89
|
+
this.config = config;
|
|
90
|
+
this.emitLifecycle = createLifecycleEmitter(this.ctx, parent => scopeTarget(this, parent));
|
|
91
|
+
}
|
|
92
|
+
async [Service.init]() {
|
|
93
|
+
const ctx = this.ctx;
|
|
94
|
+
await ctx.sessionProjections.register(subagentCatalogProjectionDefinition);
|
|
95
|
+
await ctx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
96
|
+
const identityProjection = await ctx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
97
|
+
if (ctx.fiber.uid === null)
|
|
98
|
+
return;
|
|
99
|
+
ctx.inject(['agents'], (childCtx) => {
|
|
100
|
+
const manager = new SubagentManager(childCtx, {
|
|
101
|
+
identityOf: session => identityProjection.stateOf(session).identity,
|
|
102
|
+
emitLifecycle: this.emitLifecycle,
|
|
103
|
+
}, () => this.config.maxActiveSubagents.get(), () => this.config.delegatedApproval.get());
|
|
104
|
+
this.manager = manager;
|
|
105
|
+
childCtx.effect(() => () => {
|
|
106
|
+
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
107
|
+
if (this.manager === manager)
|
|
108
|
+
this.manager = undefined;
|
|
109
|
+
}, 'subagents.managerBinding()');
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a delegation tool's depth policy against the current user setting.
|
|
114
|
+
* @param configured - Explicit tool limit, or provider-managed for external delegation.
|
|
115
|
+
* @returns The numeric limit, or undefined when the provider owns depth enforcement.
|
|
116
|
+
*/
|
|
117
|
+
resolveMaxDepth(configured) {
|
|
118
|
+
if (configured === 'provider-managed')
|
|
119
|
+
return undefined;
|
|
120
|
+
if (configured !== undefined)
|
|
121
|
+
return configured;
|
|
122
|
+
const depth = this.config.maxDepth.get();
|
|
123
|
+
assertSubagentMaxDepth(depth);
|
|
124
|
+
return depth;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Establish a managed child with backend-specific execution capabilities.
|
|
128
|
+
* @param spec - task, backend, and result recipient.
|
|
129
|
+
* @returns identities, execution result, and exact-activation disposal. A reserved
|
|
130
|
+
* childId requires a local backend and guarantees an accepted messageId.
|
|
131
|
+
* @throws PROVIDER_CHANGED if the selected provider is removed or replaced before dispatch.
|
|
132
|
+
*/
|
|
133
|
+
async startActivation(spec) {
|
|
134
|
+
const provider = this.expectProvider(spec.provider);
|
|
135
|
+
if (spec.childId !== undefined && provider.prepareContinuable === undefined) {
|
|
136
|
+
throw new SubagentError('reserved child ids require a local backend', 'UNSUPPORTED_CAPABILITY');
|
|
137
|
+
}
|
|
138
|
+
assertSubagentMaxDepth(spec.maxDepth);
|
|
139
|
+
this.assertCapabilities(provider, spec);
|
|
140
|
+
const manager = this.requireManager();
|
|
141
|
+
if (this.ctx.get('agents')?.get(spec.parent.id) !== spec.parent) {
|
|
142
|
+
throw new SubagentError('subagent creation requires the exact live parent agent', 'UNAUTHORIZED');
|
|
143
|
+
}
|
|
144
|
+
if (spec.outputSchema !== undefined)
|
|
145
|
+
assertObjectJsonSchema(spec.outputSchema);
|
|
146
|
+
const prepare = provider.prepareContinuable?.bind(provider);
|
|
147
|
+
if (prepare !== undefined) {
|
|
148
|
+
return manager.startLocal(spec, (request) => {
|
|
149
|
+
this.assertProviderCurrent(provider);
|
|
150
|
+
return prepare(request);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// oxlint-disable-next-line typescript/no-non-null-assertion -- registerProvider requires start when prepareContinuable is absent.
|
|
154
|
+
const start = provider.start.bind(provider);
|
|
155
|
+
return manager.startExternal(spec, (request) => {
|
|
156
|
+
this.assertProviderCurrent(provider);
|
|
157
|
+
return start(request);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the root shared by an Agent and its other agents in its subagent tree.
|
|
162
|
+
* @param caller - exact live Agent whose subagent ancestry selects the tree.
|
|
163
|
+
* @returns the live root; ordinary Session forks lead independent trees.
|
|
164
|
+
* @throws when the caller or its runtime owner is unavailable.
|
|
165
|
+
*/
|
|
166
|
+
rootOf(caller) {
|
|
167
|
+
return this.requireManager().rootOf(caller);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Join progressing subagent tree work for its root. Idle members with parked input
|
|
171
|
+
* stay resident without delaying host completion. Ordinary members return false.
|
|
172
|
+
* @param root - exact live subagent tree root whose member work is awaited.
|
|
173
|
+
* @returns whether work was joined; hosts recheck root idle after true.
|
|
174
|
+
*/
|
|
175
|
+
async waitForTreeWork(root) {
|
|
176
|
+
return this.manager?.waitForTreeWork(root) ?? false;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Steer one model-authored message to another member of the same subagent tree.
|
|
180
|
+
* A running target receives it at its next step; an idle target starts a turn.
|
|
181
|
+
* A cold target resumes under its runtime subagent tree root without restoring
|
|
182
|
+
* its creation ancestors. Sender identity is rechecked before inbox acceptance; cancellation
|
|
183
|
+
* stops only pre-acceptance work and never retracts an accepted message.
|
|
184
|
+
* @param sender - exact live root or resident continuable Agent originating the message.
|
|
185
|
+
* @param targetId - another root or continuable member's durable Session id.
|
|
186
|
+
* @param content - model-authored content to deliver.
|
|
187
|
+
* @param options - caller cancellation before inbox acceptance.
|
|
188
|
+
* @returns the accepted message's inbox id.
|
|
189
|
+
* @throws when the target is outside the tree, cannot resume, or rejects input.
|
|
190
|
+
*/
|
|
191
|
+
async sendMessage(sender, targetId, content, options) {
|
|
192
|
+
return this.requireManager().sendMessage(sender, targetId, content, options);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Interrupt one live child's current execution under a trusted user
|
|
196
|
+
* operation or an exact live ancestor Agent. Fire-and-return: the cancel
|
|
197
|
+
* signal is issued before this returns, but the target may keep running
|
|
198
|
+
* until it observes the signal. Unclaimed pending inbox work, the Activation,
|
|
199
|
+
* and published descendants are preserved; claimed work is not requeued.
|
|
200
|
+
* Once the interrupted Agent is idle, a waking send resumes the parked FIFO
|
|
201
|
+
* queue. External backends stop their single execution. An absent target
|
|
202
|
+
* is an accepted no-op, as is a manager-less composition, which cannot own a
|
|
203
|
+
* live Activation.
|
|
204
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
205
|
+
* @param authority - a trusted user operation or exact live ancestor Agent.
|
|
206
|
+
* @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
|
|
207
|
+
* live target.
|
|
208
|
+
*/
|
|
209
|
+
interrupt(targetSessionId, authority) {
|
|
210
|
+
this.manager?.interrupt(targetSessionId, authority);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Close admission and stop all instances in the selected subagent trees, then await
|
|
214
|
+
* pending startup and cleanup. Ordinary members are ignored. The cutoff lasts
|
|
215
|
+
* until each exact root leaves the registry; unrelated subagent trees remain live.
|
|
216
|
+
* @param parents - exact live subagent tree roots entering teardown.
|
|
217
|
+
* @returns once all admitted startup and member cleanup have settled.
|
|
218
|
+
* @throws an aggregate error after all branches settle when any failed.
|
|
219
|
+
*/
|
|
220
|
+
async drainDescendants(parents) {
|
|
221
|
+
const manager = this.manager;
|
|
222
|
+
// An absent activation manager cannot own materialized children.
|
|
223
|
+
if (manager === undefined)
|
|
224
|
+
return;
|
|
225
|
+
await manager.drainDescendants(parents);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Recursively list reachable parent catalogs in stable pre-order, preserving
|
|
229
|
+
* each catalog's event order. Each row carries its catalog parent and depth;
|
|
230
|
+
* external children are leaves; one-shot and unknown-mode children remain
|
|
231
|
+
* traversal nodes. Unknown modes
|
|
232
|
+
* produce unsupported diagnostics. Unreadable child catalogs produce corrupt
|
|
233
|
+
* or unavailable diagnostics and stop only that branch. Root read failures,
|
|
234
|
+
* missing services or projections, and cancellation reject the whole listing.
|
|
235
|
+
* Each catalog is observed once and released before the next read. No Agent
|
|
236
|
+
* is loaded or resumed; Sessions absent from reachable catalogs are omitted.
|
|
237
|
+
* @param rootSessionId - session whose catalog starts descendant discovery.
|
|
238
|
+
* @param signal - cancellation forwarded to and checked around each catalog read.
|
|
239
|
+
* @returns children and branch diagnostics in parent-catalog pre-order.
|
|
240
|
+
* @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
|
|
241
|
+
* @throws SessionQueryError when the root catalog cannot be read.
|
|
242
|
+
*/
|
|
243
|
+
listDescendants(rootSessionId, signal) {
|
|
244
|
+
return listSubagentDescendants(this.ctx, rootSessionId, signal);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Deliver one browser-authored message to a continuable child through its
|
|
248
|
+
* live subagent tree root, retaining the caller-minted request identity and
|
|
249
|
+
* validated browser zone on the accepted message. Success identifies the
|
|
250
|
+
* message the child's inbox accepted; later execution is independent of this
|
|
251
|
+
* call. Queue delivery targets a later turn; steer delivery targets the
|
|
252
|
+
* nearest step and retains the Agent loop's best-effort fallback semantics.
|
|
253
|
+
* Image parts are admitted and persisted through the attachment store
|
|
254
|
+
* before delivery, and the child's model must accept image input.
|
|
255
|
+
* Cold resume at capacity rejects with `subagent/delivery-unavailable`.
|
|
256
|
+
* Access is checked on the target Session before attachment admission or restoration.
|
|
257
|
+
* @param request - target id, delivery, minted identity, content, and optional browser zone.
|
|
258
|
+
* @param signal - carrier cancellation, owning the call until inbox acceptance.
|
|
259
|
+
* @returns the accepted message's inbox identity.
|
|
260
|
+
* @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
|
|
261
|
+
* `subagent/invalid-time-zone`,
|
|
262
|
+
* `subagent/not-resumable`, `subagent/unauthorized`,
|
|
263
|
+
* `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
|
|
264
|
+
*/
|
|
265
|
+
async prompt(request, signal) {
|
|
266
|
+
const { childSessionId, clientTimeZone, delivery } = request;
|
|
267
|
+
validateControlRequest('subagent.prompt', request);
|
|
268
|
+
const canonicalTimeZone = clientTimeZone === undefined
|
|
269
|
+
? undefined
|
|
270
|
+
: canonicalClientTimeZone(clientTimeZone);
|
|
271
|
+
if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
|
|
272
|
+
throw new RemoteError('subagent/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: clientTimeZone });
|
|
273
|
+
}
|
|
274
|
+
const principal = principalOf(this.ctx.invocation);
|
|
275
|
+
const source = {
|
|
276
|
+
kind: 'user',
|
|
277
|
+
rpcId: request.requestId,
|
|
278
|
+
...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }),
|
|
279
|
+
...(principal === undefined ? {} : { principal }),
|
|
280
|
+
};
|
|
281
|
+
try {
|
|
282
|
+
// Admission precedes delivery: image parts become durable references
|
|
283
|
+
// here, so the child inbox only ever accepts Host-persisted attachments.
|
|
284
|
+
let content;
|
|
285
|
+
if (request.content.every((part) => part.type === 'text')) {
|
|
286
|
+
content = request.content.map(part => ({ type: 'text', text: part.text }));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const attachments = this.ctx.get('attachments');
|
|
290
|
+
if (attachments === undefined)
|
|
291
|
+
throw new Error('subagent image prompt requires an attachment store');
|
|
292
|
+
content = await attachments.admitPromptContent(request.content);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
messageId: await this.requireManager().deliverPrompt(childSessionId, content, { source, signal, delivery }),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
return rejectPrompt(error, childSessionId, signal);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Remote face of {@link interrupt}, authorized on the target Session by the Gateway.
|
|
304
|
+
* No catalog, history, persistence, or parent Agent lookup runs.
|
|
305
|
+
* Absent, idle, and already-completed targets are accepted no-ops there.
|
|
306
|
+
* @param childSessionId - durable child session id to interrupt.
|
|
307
|
+
* @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
|
|
308
|
+
* @throws {RemoteError} `gateway/bad-request` for an empty id,
|
|
309
|
+
* otherwise `gateway/internal`.
|
|
310
|
+
*/
|
|
311
|
+
interruptByUser(childSessionId) {
|
|
312
|
+
validateControlRequest('subagent.interrupt', { childSessionId });
|
|
313
|
+
try {
|
|
314
|
+
this.interrupt(childSessionId, { kind: 'user' });
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
throw new RemoteError('gateway/internal', 'subagent interrupt failed', {}, { cause: error });
|
|
318
|
+
}
|
|
319
|
+
return { accepted: true };
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Register a provider under its name. Registration is effect-scoped and HMR
|
|
323
|
+
* safe; removing a provider blocks new starts but does not revoke runs that
|
|
324
|
+
* were already returned to their holders. Providers without either execution
|
|
325
|
+
* method are rejected with UNSUPPORTED_CAPABILITY before registration.
|
|
326
|
+
* @param provider - the trusted provider implementation.
|
|
327
|
+
* @returns the exact Cordis effect disposer.
|
|
328
|
+
*/
|
|
329
|
+
registerProvider(provider) {
|
|
330
|
+
const name = provider.name;
|
|
331
|
+
if (provider.start === undefined && provider.prepareContinuable === undefined) {
|
|
332
|
+
throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, 'UNSUPPORTED_CAPABILITY');
|
|
333
|
+
}
|
|
334
|
+
// oxlint-disable-next-line typescript/no-misused-promises -- synchronous disposer
|
|
335
|
+
return this.ctx.effect(function* () {
|
|
336
|
+
if (this.providers.has(name)) {
|
|
337
|
+
throw new SubagentError(`a subagent provider named "${name}" is already registered`, 'DUPLICATE_PROVIDER');
|
|
338
|
+
}
|
|
339
|
+
this.providers.set(name, provider);
|
|
340
|
+
yield () => {
|
|
341
|
+
this.providers.delete(name);
|
|
342
|
+
this.emitLifecycle('subagent/provider-removed', name);
|
|
343
|
+
};
|
|
344
|
+
// A throwing added-listener unwinds the yielded rollback, matching the
|
|
345
|
+
// repository's fail-loud registration semantics.
|
|
346
|
+
this.ctx.emit('subagent/provider-added', provider);
|
|
347
|
+
}.bind(this), 'subagents.registerProvider()');
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Look up a provider by name.
|
|
351
|
+
* @param name - the provider name.
|
|
352
|
+
* @returns the provider, or undefined when absent.
|
|
353
|
+
*/
|
|
354
|
+
getProvider(name) {
|
|
355
|
+
return this.providers.get(name);
|
|
356
|
+
}
|
|
357
|
+
/** A pending start must use the exact provider whose capabilities were checked. */
|
|
358
|
+
assertProviderCurrent(provider) {
|
|
359
|
+
if (this.providers.get(provider.name) !== provider) {
|
|
360
|
+
throw new SubagentError(`subagent provider "${provider.name}" changed during startup; retry the delegation`, 'PROVIDER_CHANGED');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Look up a provider for dispatch or fail loud. */
|
|
364
|
+
expectProvider(name) {
|
|
365
|
+
const provider = this.providers.get(name);
|
|
366
|
+
if (provider === undefined) {
|
|
367
|
+
throw new SubagentError(`no subagent provider registered for "${name}"`, 'NO_PROVIDER');
|
|
368
|
+
}
|
|
369
|
+
return provider;
|
|
370
|
+
}
|
|
371
|
+
/** Resolve the subagent manager or fail loud. */
|
|
372
|
+
requireManager() {
|
|
373
|
+
if (this.manager === undefined) {
|
|
374
|
+
throw new SubagentError('continuable subagents require the agents service', 'CONTINUATION_UNAVAILABLE');
|
|
375
|
+
}
|
|
376
|
+
return this.manager;
|
|
377
|
+
}
|
|
378
|
+
/** Reject the first requested capability that the provider lacks. */
|
|
379
|
+
assertCapabilities(provider, request) {
|
|
380
|
+
if (request.agentPreset !== undefined && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) {
|
|
381
|
+
throw new SubagentError(`subagent provider "${provider.name}" does not support independent agent preset selection`, 'UNSUPPORTED_CAPABILITY');
|
|
382
|
+
}
|
|
383
|
+
const needs = [
|
|
384
|
+
{ when: request.agentOptions !== undefined, cap: 'agentOptions' },
|
|
385
|
+
{ when: request.outputSchema !== undefined, cap: 'outputSchema' },
|
|
386
|
+
{ when: request.maxDepth !== undefined, cap: 'depthLimit' },
|
|
387
|
+
];
|
|
388
|
+
for (const { when, cap } of needs) {
|
|
389
|
+
if (when && !provider.capabilities[cap]) {
|
|
390
|
+
throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, 'UNSUPPORTED_CAPABILITY');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
})();
|
|
396
|
+
export { SubagentRuntime };
|
|
397
|
+
export default SubagentRuntime;
|
|
398
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contained lifecycle notifications for subagents.
|
|
3
|
+
*
|
|
4
|
+
* The service owns scoped dispatch and listener containment; the manager
|
|
5
|
+
* supplies event payloads from each activation.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-subagent/lifecycle
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
11
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
12
|
+
import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from './types.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Publish one notification with per-listener exception containment. Run edges
|
|
15
|
+
* carry the subagent tree root that keys scoped dispatch. Provider removal
|
|
16
|
+
* reaches all scopes.
|
|
17
|
+
*
|
|
18
|
+
* The service owns this closure because scoped dispatch keys its carrier by the
|
|
19
|
+
* exact service instance, whose own context filter composes into the carrier;
|
|
20
|
+
* a narrowed stand-in would silently change scope filtering.
|
|
21
|
+
*/
|
|
22
|
+
export type LifecycleEmitter = {
|
|
23
|
+
(name: 'subagent/start', info: SubagentRunInfo, parent: Agent): void;
|
|
24
|
+
(name: 'subagent/end', info: SubagentRunEndInfo, parent: Agent): void;
|
|
25
|
+
(name: 'subagent/provider-removed', info: string): void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Build the contained lifecycle emitter this seam publishes every edge through.
|
|
29
|
+
* Every listener is independently contained: a synchronous throw or a rejected
|
|
30
|
+
* returned promise is logged without starving peer listeners, changing the run,
|
|
31
|
+
* or — for provider removal, which fires from a disposer — breaking teardown.
|
|
32
|
+
* @param ctx - the service's own context, owning dispatch and the logger.
|
|
33
|
+
* @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
|
|
34
|
+
* @returns the emitter the manager and provider registry publish through.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createLifecycleEmitter(ctx: Context, carrier: (parent: Agent) => object): LifecycleEmitter;
|
|
37
|
+
/**
|
|
38
|
+
* Why this child's epoch ended, for the terminal lifecycle edge and the
|
|
39
|
+
* manager's own parent delivery. The child's own log is authoritative:
|
|
40
|
+
* teardown succeeding says nothing about whether the model errored, hit its
|
|
41
|
+
* token ceiling, or was cancelled, so deriving the reason from disposal would
|
|
42
|
+
* report failed work as completed.
|
|
43
|
+
*
|
|
44
|
+
* {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
|
|
45
|
+
* which turn accounts for the work this epoch consumed, and whether accepted
|
|
46
|
+
* work was cancelled after it without any turn opening over it. A recorded
|
|
47
|
+
* failure still wins over a cancellation — stopping a child that had already
|
|
48
|
+
* failed does not turn its failure into a cancellation.
|
|
49
|
+
* @param events - this epoch's own event suffix.
|
|
50
|
+
* @returns its terminal stop reason; `completed` only for an epoch that both
|
|
51
|
+
* closed cleanly and had nothing left to run.
|
|
52
|
+
*/
|
|
53
|
+
export declare function epochStopReason(events: readonly SessionEvent[]): SubagentResult['stopReason'];
|
|
54
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contained lifecycle notifications for subagents.
|
|
3
|
+
*
|
|
4
|
+
* The service owns scoped dispatch and listener containment; the manager
|
|
5
|
+
* supplies event payloads from each activation.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-subagent/lifecycle
|
|
8
|
+
*/
|
|
9
|
+
import { foldConsumedWork } from '@deepseek-ai/dsh-agent';
|
|
10
|
+
/**
|
|
11
|
+
* Build the contained lifecycle emitter this seam publishes every edge through.
|
|
12
|
+
* Every listener is independently contained: a synchronous throw or a rejected
|
|
13
|
+
* returned promise is logged without starving peer listeners, changing the run,
|
|
14
|
+
* or — for provider removal, which fires from a disposer — breaking teardown.
|
|
15
|
+
* @param ctx - the service's own context, owning dispatch and the logger.
|
|
16
|
+
* @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
|
|
17
|
+
* @returns the emitter the manager and provider registry publish through.
|
|
18
|
+
*/
|
|
19
|
+
export function createLifecycleEmitter(ctx, carrier) {
|
|
20
|
+
return (name, info, parent) => {
|
|
21
|
+
const dispatchArgs = parent === undefined
|
|
22
|
+
? [name, info]
|
|
23
|
+
: [carrier(parent), name, info];
|
|
24
|
+
for (const callback of ctx.events.dispatch('emit', dispatchArgs)) {
|
|
25
|
+
try {
|
|
26
|
+
const returned = callback(info);
|
|
27
|
+
void Promise.resolve(returned).catch((error) => {
|
|
28
|
+
ctx.logger.warn(`subagent: ${name} listener rejected: ${renderThrown(error)}`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
ctx.logger.warn(`subagent: ${name} listener threw: ${renderThrown(error)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Why this child's epoch ended, for the terminal lifecycle edge and the
|
|
39
|
+
* manager's own parent delivery. The child's own log is authoritative:
|
|
40
|
+
* teardown succeeding says nothing about whether the model errored, hit its
|
|
41
|
+
* token ceiling, or was cancelled, so deriving the reason from disposal would
|
|
42
|
+
* report failed work as completed.
|
|
43
|
+
*
|
|
44
|
+
* {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
|
|
45
|
+
* which turn accounts for the work this epoch consumed, and whether accepted
|
|
46
|
+
* work was cancelled after it without any turn opening over it. A recorded
|
|
47
|
+
* failure still wins over a cancellation — stopping a child that had already
|
|
48
|
+
* failed does not turn its failure into a cancellation.
|
|
49
|
+
* @param events - this epoch's own event suffix.
|
|
50
|
+
* @returns its terminal stop reason; `completed` only for an epoch that both
|
|
51
|
+
* closed cleanly and had nothing left to run.
|
|
52
|
+
*/
|
|
53
|
+
export function epochStopReason(events) {
|
|
54
|
+
const { end, droppedUnrun } = foldConsumedWork(events);
|
|
55
|
+
switch (end?.data.reason.kind) {
|
|
56
|
+
case 'max-tokens':
|
|
57
|
+
return 'max-tokens';
|
|
58
|
+
case 'aborted':
|
|
59
|
+
case 'interrupted':
|
|
60
|
+
return 'aborted';
|
|
61
|
+
case 'error':
|
|
62
|
+
return 'error';
|
|
63
|
+
// A pre-step rejection — a hook deny, a policy plugin — discarded input
|
|
64
|
+
// this epoch had claimed: the work was declined, not done.
|
|
65
|
+
case 'blocked':
|
|
66
|
+
return 'refusal';
|
|
67
|
+
// A clean ending and no accounting turn at all share one rule: the epoch
|
|
68
|
+
// finished what it was given unless a cancelled queue says otherwise.
|
|
69
|
+
case undefined:
|
|
70
|
+
case 'completed':
|
|
71
|
+
return droppedUnrun ? 'aborted' : 'completed';
|
|
72
|
+
/* v8 ignore next 4 -- `forked` appears only in constructor seed history, while
|
|
73
|
+
* this function reads an epoch-owned suffix. `TurnEndReason` is merge-extensible,
|
|
74
|
+
* so a backend-added variant cannot be listed; treating an unnameable reason as
|
|
75
|
+
* success would report failed work as completed. */
|
|
76
|
+
default:
|
|
77
|
+
return 'error';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Render any listener-thrown value without letting coercion escape containment. */
|
|
81
|
+
function renderThrown(value) {
|
|
82
|
+
try {
|
|
83
|
+
return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return '<unrenderable thrown value>';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct-child and recursive descendant discovery from parent-owned catalogs.
|
|
3
|
+
* Each catalog read releases its Session observation before the next branch.
|
|
4
|
+
* @module @deepseek-ai/dsh-subagent
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
8
|
+
import type { SubagentListEntry } from './control-types.ts';
|
|
9
|
+
import type { SubagentCatalogEntry } from './projection-types.ts';
|
|
10
|
+
export type { SubagentListEntry } from './control-types.ts';
|
|
11
|
+
/** One catalog descendant with its direct parent and edge distance from the requested root. */
|
|
12
|
+
export type SubagentDescendantListEntry = SubagentListEntry & {
|
|
13
|
+
/** Parent whose catalog contains this child. */
|
|
14
|
+
readonly parentId: SessionId;
|
|
15
|
+
/** Edge distance from the requested root; direct children are `1`. */
|
|
16
|
+
readonly depth: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Read one parent's durable catalog through a live-preferred Session observation.
|
|
20
|
+
* @param ctx - context carrying the Session query service.
|
|
21
|
+
* @param parentSessionId - parent whose direct children are requested.
|
|
22
|
+
* @param signal - cancellation forwarded to the Session observation.
|
|
23
|
+
* @returns direct-child rows in parent catalog event order.
|
|
24
|
+
* @throws {@link SubagentError} when query or catalog projection is unavailable.
|
|
25
|
+
*/
|
|
26
|
+
export declare function listChildren(ctx: Context, parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentCatalogEntry[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Walk reachable parent catalogs in stable pre-order without loading Agents.
|
|
29
|
+
* @see SubagentRuntime.listDescendants for failure and cancellation semantics.
|
|
30
|
+
* @param ctx - context carrying the Session store and query service.
|
|
31
|
+
* @param rootSessionId - parent whose catalog starts the traversal.
|
|
32
|
+
* @param signal - cancellation checked around each catalog read.
|
|
33
|
+
* @returns children and branch diagnostics with catalog parent and depth.
|
|
34
|
+
*/
|
|
35
|
+
export declare function listDescendants(ctx: Context, rootSessionId: SessionId, signal?: AbortSignal): Promise<SubagentDescendantListEntry[]>;
|
|
36
|
+
//# sourceMappingURL=list-children.d.ts.map
|