@qlzt/dsh-subagent 0.0.0-stage → 0.2.1-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/LICENSE +21 -0
- package/README.i18n.yaml +94 -0
- package/README.md +215 -2
- package/README.zh.md +216 -0
- package/lib/index.js +3333 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +991 -0
- package/lib/typert.remote-client.d.ts +25 -0
- package/lib/typert.remote-client.js +114 -0
- package/lib/types/activation.d.ts +114 -0
- package/lib/types/activation.js +112 -0
- package/lib/types/archive-admission.d.ts +16 -0
- package/lib/types/archive-admission.js +151 -0
- package/lib/types/assistant-output.d.ts +48 -0
- package/lib/types/assistant-output.js +73 -0
- package/lib/types/catalog.d.ts +92 -0
- package/lib/types/catalog.js +113 -0
- package/lib/types/child-agent.d.ts +154 -0
- package/lib/types/child-agent.js +211 -0
- package/lib/types/client.d.ts +8 -0
- package/lib/types/client.js +7 -0
- package/lib/types/continuation-messages.d.ts +63 -0
- package/lib/types/continuation-messages.js +114 -0
- package/lib/types/control-types.d.ts +142 -0
- package/lib/types/control-types.js +8 -0
- package/lib/types/control.d.ts +45 -0
- package/lib/types/control.js +79 -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 +319 -0
- package/lib/types/index.js +455 -0
- package/lib/types/internal.d.ts +58 -0
- package/lib/types/internal.js +58 -0
- package/lib/types/lifecycle.d.ts +83 -0
- package/lib/types/lifecycle.js +115 -0
- package/lib/types/list-children.d.ts +36 -0
- package/lib/types/list-children.js +166 -0
- package/lib/types/manager.d.ts +247 -0
- package/lib/types/manager.js +1294 -0
- package/lib/types/out-of-process.d.ts +92 -0
- package/lib/types/out-of-process.js +178 -0
- package/lib/types/projection-types.d.ts +85 -0
- package/lib/types/projection-types.js +7 -0
- package/lib/types/projection.d.ts +100 -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 +353 -0
- package/lib/types/types.js +19 -0
- package/package.json +136 -4
|
@@ -0,0 +1,455 @@
|
|
|
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 z from '@deepseek-ai/schemastery';
|
|
36
|
+
import { scopeTarget } from '@qlzt/dsh-scope';
|
|
37
|
+
import { assertObjectJsonSchema } from '@qlzt/dsh-tools';
|
|
38
|
+
import { canonicalClientTimeZone } from '@qlzt/dsh-util-time';
|
|
39
|
+
import { Remote, RemoteError, TypertRemoteService } from '@qlzt/dsh-typert-protocol';
|
|
40
|
+
import { rejectPrompt, validateControlRequest, } from "./control.js";
|
|
41
|
+
import { SubagentError } from "./error.js";
|
|
42
|
+
import { assertSubagentMaxDepth } from "./depth.js";
|
|
43
|
+
import { createActivationObserver, createLifecycleEmitter } from "./lifecycle.js";
|
|
44
|
+
import SubagentManager from "./manager.js";
|
|
45
|
+
import { listChildren as listSubagentChildren, listDescendants as listSubagentDescendants } from "./list-children.js";
|
|
46
|
+
import { installSubagentArchiveAdmission } from "./archive-admission.js";
|
|
47
|
+
import { subagentIdentityProjectionDefinition, subagentTimingProjectionDefinition } from "./projection.js";
|
|
48
|
+
import { subagentCatalogProjectionDefinition } from "./catalog.js";
|
|
49
|
+
import { deliverSubagentPrompt } from "./internal.js";
|
|
50
|
+
export * from "./out-of-process.js";
|
|
51
|
+
export { AssistantOutputFold, finalAssistantOutput } from "./assistant-output.js";
|
|
52
|
+
export { SubagentRunId } from "./types.js";
|
|
53
|
+
export { foldSubagentDescriptor, snapshotSubagentDescriptor, SUBAGENT_DESCRIPTOR_VERSION, } from "./descriptor.js";
|
|
54
|
+
export { STRUCTURED_OUTPUT_TOOL } from "./structured.js";
|
|
55
|
+
export { SubagentError } from "./error.js";
|
|
56
|
+
export { assertSubagentMaxDepth, delegationDepthOf } from "./depth.js";
|
|
57
|
+
export { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides, childSessionMeta, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildDepth, SubagentDepthError, } from "./child-agent.js";
|
|
58
|
+
/** Named provider registry with managed activations, durable discovery, and local child messaging. */
|
|
59
|
+
let SubagentRuntime = (() => {
|
|
60
|
+
let _classSuper = TypertRemoteService;
|
|
61
|
+
let _instanceExtraInitializers = [];
|
|
62
|
+
let _prompt_decorators;
|
|
63
|
+
let _interruptByParent_decorators;
|
|
64
|
+
return class SubagentRuntime extends _classSuper {
|
|
65
|
+
static {
|
|
66
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
67
|
+
_prompt_decorators = [Remote('prompt')];
|
|
68
|
+
_interruptByParent_decorators = [Remote('interruptByParent')];
|
|
69
|
+
__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);
|
|
70
|
+
__esDecorate(this, null, _interruptByParent_decorators, { kind: "method", name: "interruptByParent", static: false, private: false, access: { has: obj => "interruptByParent" in obj, get: obj => obj.interruptByParent }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
71
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
72
|
+
}
|
|
73
|
+
config = __runInitializers(this, _instanceExtraInitializers);
|
|
74
|
+
static Config = z.object({
|
|
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'];
|
|
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
|
+
ctx.inject(['agents'], (childCtx) => {
|
|
92
|
+
const manager = new SubagentManager(childCtx, {
|
|
93
|
+
startExternal: (name, request) => {
|
|
94
|
+
const provider = this.expectProvider(name);
|
|
95
|
+
return provider.start(request);
|
|
96
|
+
},
|
|
97
|
+
prepareContinuable: (name, request) => this.prepareContinuable(name, request),
|
|
98
|
+
observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent),
|
|
99
|
+
}, () => this.config.maxActiveSubagents.get());
|
|
100
|
+
this.manager = manager;
|
|
101
|
+
childCtx.effect(() => () => {
|
|
102
|
+
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
103
|
+
if (this.manager === manager)
|
|
104
|
+
this.manager = undefined;
|
|
105
|
+
}, 'subagents.managerBinding()');
|
|
106
|
+
});
|
|
107
|
+
ctx.inject(['sessionProjections'], (projectionCtx) => {
|
|
108
|
+
projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
|
|
109
|
+
projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
110
|
+
projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
111
|
+
});
|
|
112
|
+
// Archive admission: this runtime is the owner that knows which live
|
|
113
|
+
// children descend from a Session and how a parent stops them.
|
|
114
|
+
ctx.inject(['agents'], (agentsCtx) => { installSubagentArchiveAdmission(agentsCtx); });
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a delegation tool's depth policy against the current user setting.
|
|
118
|
+
* @param configured - Explicit tool limit, or provider-managed for external delegation.
|
|
119
|
+
* @returns The numeric limit, or undefined when the provider owns depth enforcement.
|
|
120
|
+
*/
|
|
121
|
+
resolveMaxDepth(configured) {
|
|
122
|
+
if (configured === 'provider-managed')
|
|
123
|
+
return undefined;
|
|
124
|
+
if (configured !== undefined)
|
|
125
|
+
return configured;
|
|
126
|
+
const depth = this.config.maxDepth.get();
|
|
127
|
+
assertSubagentMaxDepth(depth);
|
|
128
|
+
return depth;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Establish a managed child with backend-specific execution capabilities.
|
|
132
|
+
* @param spec - task, backend, and result recipient.
|
|
133
|
+
* @returns identities, execution result, and exact-activation disposal. A reserved
|
|
134
|
+
* childId requires a local backend and guarantees an accepted messageId.
|
|
135
|
+
*/
|
|
136
|
+
async startActivation(spec) {
|
|
137
|
+
const provider = this.expectProvider(spec.provider);
|
|
138
|
+
if (spec.childId !== undefined && provider.prepareContinuable === undefined) {
|
|
139
|
+
throw new SubagentError('reserved child ids require a local backend', 'UNSUPPORTED_CAPABILITY');
|
|
140
|
+
}
|
|
141
|
+
assertSubagentMaxDepth(spec.request.maxDepth);
|
|
142
|
+
this.assertCapabilities(provider, { ...spec.request, signal: spec.signal });
|
|
143
|
+
const manager = this.requireManager();
|
|
144
|
+
if (this.ctx.get('agents')?.get(spec.request.parent.id) !== spec.request.parent) {
|
|
145
|
+
throw new SubagentError('subagent creation requires the exact live parent agent', 'UNAUTHORIZED');
|
|
146
|
+
}
|
|
147
|
+
if (spec.request.outputSchema !== undefined)
|
|
148
|
+
assertObjectJsonSchema(spec.request.outputSchema);
|
|
149
|
+
return provider.prepareContinuable === undefined
|
|
150
|
+
? manager.startExternal(spec)
|
|
151
|
+
: manager.startLocal(spec);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Join progressing descendants without cancelling them. Idle descendants whose
|
|
155
|
+
* inboxes require a later wake stay resident and do not delay host completion.
|
|
156
|
+
* @param parent - the exact parent whose descendant work is observed.
|
|
157
|
+
* @returns whether work was joined; hosts recheck parent idle after true.
|
|
158
|
+
*/
|
|
159
|
+
async waitForChildren(parent) {
|
|
160
|
+
return this.manager?.waitForChildren(parent) ?? false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Steer one model-authored message to the sender's direct parent or direct
|
|
164
|
+
* continuable child. A running target admits it at the nearest step boundary;
|
|
165
|
+
* an idle target starts a turn, and an absent direct child cold-resumes from
|
|
166
|
+
* persistence. The service derives durable sender attribution from the exact
|
|
167
|
+
* live sender. Caller cancellation stops only pre-acceptance work.
|
|
168
|
+
* @param sender - exact live Agent authorizing and originating the message.
|
|
169
|
+
* @param targetId - durable direct-parent or direct-child session id.
|
|
170
|
+
* @param content - model-authored content to deliver.
|
|
171
|
+
* @param options - caller cancellation before inbox acceptance.
|
|
172
|
+
* @returns the accepted message's inbox id.
|
|
173
|
+
* @throws when continuation services are unavailable, adjacency is rejected,
|
|
174
|
+
* or the message was not admitted.
|
|
175
|
+
*/
|
|
176
|
+
async sendMessage(sender, targetId, content, options) {
|
|
177
|
+
return this.requireManager().sendMessage(sender, targetId, content, options);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Deliver one host-protocol message to a direct continuable child.
|
|
181
|
+
* Symbol-keyed so host adapters can preserve their own source descriptors without
|
|
182
|
+
* widening the public Service Definition or impersonating an Agent sender.
|
|
183
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
184
|
+
* @param childId - durable direct-child session id.
|
|
185
|
+
* @param content - host-authored content to deliver.
|
|
186
|
+
* @param source - durable host-protocol source descriptor.
|
|
187
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
188
|
+
* @param delivery - Queue as a distinct turn or Steer at the nearest step.
|
|
189
|
+
* @returns the accepted message's inbox id.
|
|
190
|
+
*/
|
|
191
|
+
[deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
|
|
192
|
+
return delivery === 'steer'
|
|
193
|
+
? this.requireManager().steerPrompt(parent, childId, content, source, signal)
|
|
194
|
+
: this.requireManager().queuePrompt(parent, childId, content, source, signal);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Interrupt one live child's current execution under a human parent
|
|
198
|
+
* address or an exact live ancestor Agent. Fire-and-return: the cancel
|
|
199
|
+
* signal is issued before this returns, but the target may keep running
|
|
200
|
+
* until it observes the signal. Unclaimed pending inbox work, the Activation,
|
|
201
|
+
* and published descendants are preserved; claimed work is not requeued.
|
|
202
|
+
* Once the interrupted Agent is idle, a waking send resumes the parked FIFO
|
|
203
|
+
* queue. External backends stop their single execution. An absent target
|
|
204
|
+
* is an accepted no-op, as is a manager-less composition, which cannot own a
|
|
205
|
+
* live Activation.
|
|
206
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
207
|
+
* @param authority - the human parent address or exact live ancestor Agent.
|
|
208
|
+
* @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
|
|
209
|
+
* live target.
|
|
210
|
+
*/
|
|
211
|
+
interrupt(targetSessionId, authority) {
|
|
212
|
+
this.manager?.interrupt(targetSessionId, authority);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Close subagent admission below exact live parent Agents, stop only their
|
|
216
|
+
* visible descendant Activations synchronously, then await admitted scoped
|
|
217
|
+
* materializations and release those forests child-first. The scoped cutoff
|
|
218
|
+
* lasts until each exact parent leaves the registry; unrelated parent trees
|
|
219
|
+
* remain live.
|
|
220
|
+
* @param parents - exact host-owned parent Agents entering teardown.
|
|
221
|
+
* @returns once every retained descendant activation released its execution handle.
|
|
222
|
+
* @throws an aggregate error after all branches settle when any failed.
|
|
223
|
+
*/
|
|
224
|
+
async drainDescendants(parents) {
|
|
225
|
+
const manager = this.manager;
|
|
226
|
+
// An absent activation manager cannot own materialized children.
|
|
227
|
+
if (manager === undefined)
|
|
228
|
+
return;
|
|
229
|
+
await manager.drainDescendants(parents);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Release selected resident direct children of one exact live
|
|
233
|
+
* parent. Other children of the same parent remain admitted and resident.
|
|
234
|
+
* Absent targets and a manager-less composition are accepted no-ops.
|
|
235
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
236
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
237
|
+
* @returns once every selected activation released its execution handle.
|
|
238
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
239
|
+
* different parent or the supplied parent identity is stale.
|
|
240
|
+
*/
|
|
241
|
+
async drainChildren(parent, childIds) {
|
|
242
|
+
const manager = this.manager;
|
|
243
|
+
if (manager === undefined)
|
|
244
|
+
return;
|
|
245
|
+
await manager.drainChildren(parent, childIds);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Read the parent's durable direct-child catalog without loading or resuming an Agent.
|
|
249
|
+
* The service owns and releases the live-preferred Session observation.
|
|
250
|
+
* @param parentSessionId - parent whose direct children are requested.
|
|
251
|
+
* @param signal - cancellation forwarded to the Session query.
|
|
252
|
+
* @returns catalog children in parent event order.
|
|
253
|
+
* @throws {@link SubagentError} when query or catalog projection is unavailable.
|
|
254
|
+
* @throws SessionQueryError when the parent cannot be read or the query is cancelled.
|
|
255
|
+
*/
|
|
256
|
+
listChildren(parentSessionId, signal) {
|
|
257
|
+
return listSubagentChildren(this.ctx, parentSessionId, signal);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Recursively list reachable parent catalogs in stable pre-order, preserving
|
|
261
|
+
* each catalog's event order. Each row carries its catalog parent and depth;
|
|
262
|
+
* external children are leaves; one-shot and unknown-mode children remain
|
|
263
|
+
* traversal nodes. Unknown modes
|
|
264
|
+
* produce unsupported diagnostics. Unreadable child catalogs produce corrupt
|
|
265
|
+
* or unavailable diagnostics and stop only that branch. Root read failures,
|
|
266
|
+
* missing services or projections, and cancellation reject the whole listing.
|
|
267
|
+
* Each catalog is observed once and released before the next read. No Agent
|
|
268
|
+
* is loaded or resumed; Sessions absent from reachable catalogs are omitted.
|
|
269
|
+
* @param rootSessionId - session whose catalog starts descendant discovery.
|
|
270
|
+
* @param signal - cancellation forwarded to and checked around each catalog read.
|
|
271
|
+
* @returns children and branch diagnostics in parent-catalog pre-order.
|
|
272
|
+
* @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
|
|
273
|
+
* @throws SessionQueryError when the root catalog cannot be read.
|
|
274
|
+
*/
|
|
275
|
+
listDescendants(rootSessionId, signal) {
|
|
276
|
+
return listSubagentDescendants(this.ctx, rootSessionId, signal);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Deliver one browser-authored message to a continuable child through the
|
|
280
|
+
* exact live direct parent, retaining the caller-minted request identity and
|
|
281
|
+
* validated browser zone on the accepted message. Success identifies the
|
|
282
|
+
* message the child's inbox accepted; later execution is independent of this
|
|
283
|
+
* call. Queue delivery targets a later turn; steer delivery targets the
|
|
284
|
+
* nearest step and retains the Agent loop's best-effort fallback semantics.
|
|
285
|
+
* Image parts are admitted and persisted through the attachment store
|
|
286
|
+
* before delivery, and the child's model must accept image input.
|
|
287
|
+
* Cold resume at capacity rejects with `subagent/delivery-unavailable`.
|
|
288
|
+
* @param request - durable address, delivery, minted identity, content, and optional browser zone.
|
|
289
|
+
* @param signal - carrier cancellation, owning the call until inbox acceptance.
|
|
290
|
+
* @returns the accepted message's inbox identity.
|
|
291
|
+
* @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
|
|
292
|
+
* `subagent/invalid-time-zone`, `subagent/parent-unavailable`,
|
|
293
|
+
* `subagent/not-resumable`, `subagent/unauthorized`,
|
|
294
|
+
* `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
|
|
295
|
+
*/
|
|
296
|
+
async prompt(request, signal) {
|
|
297
|
+
const { parentSessionId, childSessionId, clientTimeZone, delivery } = request;
|
|
298
|
+
validateControlRequest('subagent.prompt', request);
|
|
299
|
+
const canonicalTimeZone = clientTimeZone === undefined
|
|
300
|
+
? undefined
|
|
301
|
+
: canonicalClientTimeZone(clientTimeZone);
|
|
302
|
+
if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
|
|
303
|
+
throw new RemoteError('subagent/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: clientTimeZone });
|
|
304
|
+
}
|
|
305
|
+
const parent = this.ctx.get('agents')?.get(parentSessionId);
|
|
306
|
+
if (parent === undefined) {
|
|
307
|
+
throw new RemoteError('subagent/parent-unavailable', `parent session "${parentSessionId}" is not live`, { parentSessionId });
|
|
308
|
+
}
|
|
309
|
+
const source = {
|
|
310
|
+
kind: 'user',
|
|
311
|
+
rpcId: request.requestId,
|
|
312
|
+
...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }),
|
|
313
|
+
};
|
|
314
|
+
try {
|
|
315
|
+
// Admission precedes delivery: image parts become durable references
|
|
316
|
+
// here, so the child inbox only ever accepts Host-persisted attachments.
|
|
317
|
+
let content;
|
|
318
|
+
if (request.content.every((part) => part.type === 'text')) {
|
|
319
|
+
content = request.content.map(part => ({ type: 'text', text: part.text }));
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const attachments = this.ctx.get('attachments');
|
|
323
|
+
if (attachments === undefined)
|
|
324
|
+
throw new Error('subagent image prompt requires an attachment store');
|
|
325
|
+
content = await attachments.admitPromptContent(request.content);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
messageId: await this[deliverSubagentPrompt](parent, childSessionId, content, source, signal, delivery),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
return rejectPrompt(error, childSessionId, signal);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Remote face of {@link interrupt} under one durable parent address. No
|
|
337
|
+
* catalog, history, persistence, or parent Agent lookup runs: the core
|
|
338
|
+
* primitive alone authorizes the address against the live Activation, which
|
|
339
|
+
* is what keeps a live child interruptible while its parent Agent is offline.
|
|
340
|
+
* Absent, idle, and already-completed targets are accepted no-ops there.
|
|
341
|
+
* @param childSessionId - durable child session id to interrupt.
|
|
342
|
+
* @param parentSessionId - durable direct parent whose authority is claimed.
|
|
343
|
+
* @param mode - required continuable-address discriminator.
|
|
344
|
+
* @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
|
|
345
|
+
* @throws {RemoteError} `gateway/bad-request` for an empty id,
|
|
346
|
+
* `subagent/unauthorized` when the address does not own the live target,
|
|
347
|
+
* otherwise `gateway/internal`.
|
|
348
|
+
*/
|
|
349
|
+
interruptByParent(childSessionId, parentSessionId, mode) {
|
|
350
|
+
validateControlRequest('subagent.interrupt', { childSessionId, parentSessionId, mode });
|
|
351
|
+
try {
|
|
352
|
+
this.interrupt(childSessionId, { kind: 'user', parentSessionId });
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
if (error instanceof SubagentError && error.code === 'UNAUTHORIZED') {
|
|
356
|
+
throw new RemoteError('subagent/unauthorized', 'subagent does not belong to this parent', { childSessionId }, { cause: error });
|
|
357
|
+
}
|
|
358
|
+
throw new RemoteError('gateway/internal', 'subagent interrupt failed', {}, { cause: error });
|
|
359
|
+
}
|
|
360
|
+
return { accepted: true };
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Register a provider under its name. Registration is effect-scoped and HMR
|
|
364
|
+
* safe; removing a provider blocks new starts but does not revoke runs that
|
|
365
|
+
* were already returned to their holders. Providers without either execution
|
|
366
|
+
* method are rejected with UNSUPPORTED_CAPABILITY before registration.
|
|
367
|
+
* @param provider - the trusted provider implementation.
|
|
368
|
+
* @returns the exact Cordis effect disposer.
|
|
369
|
+
*/
|
|
370
|
+
registerProvider(provider) {
|
|
371
|
+
const name = provider.name;
|
|
372
|
+
if (provider.start === undefined && provider.prepareContinuable === undefined) {
|
|
373
|
+
throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, 'UNSUPPORTED_CAPABILITY');
|
|
374
|
+
}
|
|
375
|
+
// oxlint-disable-next-line typescript/no-misused-promises -- synchronous disposer
|
|
376
|
+
return this.ctx.effect(function* () {
|
|
377
|
+
if (this.providers.has(name)) {
|
|
378
|
+
throw new SubagentError(`a subagent provider named "${name}" is already registered`, 'DUPLICATE_PROVIDER');
|
|
379
|
+
}
|
|
380
|
+
this.providers.set(name, provider);
|
|
381
|
+
yield () => {
|
|
382
|
+
this.providers.delete(name);
|
|
383
|
+
this.emitLifecycle('subagent/provider-removed', name);
|
|
384
|
+
};
|
|
385
|
+
// A throwing added-listener unwinds the yielded rollback, matching the
|
|
386
|
+
// repository's fail-loud registration semantics.
|
|
387
|
+
this.ctx.emit('subagent/provider-added', provider);
|
|
388
|
+
}.bind(this), 'subagents.registerProvider()');
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Look up a provider by name.
|
|
392
|
+
* @param name - the provider name.
|
|
393
|
+
* @returns the provider, or undefined when absent.
|
|
394
|
+
*/
|
|
395
|
+
getProvider(name) {
|
|
396
|
+
return this.providers.get(name);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* List registered provider names in insertion order.
|
|
400
|
+
* @returns the registered names.
|
|
401
|
+
*/
|
|
402
|
+
list() {
|
|
403
|
+
return [...this.providers.keys()];
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Resolve one provider's detached continuable-creation contribution. Method
|
|
407
|
+
* presence on the provider IS the capability, so a provider without it is
|
|
408
|
+
* rejected before the manager reserves any child resources.
|
|
409
|
+
*/
|
|
410
|
+
async prepareContinuable(name, request) {
|
|
411
|
+
const provider = this.expectProvider(name);
|
|
412
|
+
return provider.prepareContinuable(request);
|
|
413
|
+
}
|
|
414
|
+
/** Look up a provider for dispatch or fail loud. */
|
|
415
|
+
expectProvider(name) {
|
|
416
|
+
const provider = this.providers.get(name);
|
|
417
|
+
if (provider === undefined) {
|
|
418
|
+
throw new SubagentError(`no subagent provider registered for "${name}"`, 'NO_PROVIDER');
|
|
419
|
+
}
|
|
420
|
+
return provider;
|
|
421
|
+
}
|
|
422
|
+
/** Resolve the subagent manager or fail loud. */
|
|
423
|
+
requireManager() {
|
|
424
|
+
if (this.manager === undefined) {
|
|
425
|
+
throw new SubagentError('continuable subagents require the agents service', 'CONTINUATION_UNAVAILABLE');
|
|
426
|
+
}
|
|
427
|
+
return this.manager;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Build the lifecycle observer for one continuable Activation's residency
|
|
431
|
+
* epoch, so the manager publishes its edges without owning event dispatch.
|
|
432
|
+
*/
|
|
433
|
+
observeActivation(provider, childId, parent) {
|
|
434
|
+
return createActivationObserver(this.emitLifecycle, provider, childId, parent);
|
|
435
|
+
}
|
|
436
|
+
/** Reject the first requested capability that the provider lacks. */
|
|
437
|
+
assertCapabilities(provider, request) {
|
|
438
|
+
const needs = [
|
|
439
|
+
{ when: request.agentOptions !== undefined, cap: 'agentOptions' },
|
|
440
|
+
{ when: request.outputSchema !== undefined, cap: 'outputSchema' },
|
|
441
|
+
{ when: request.maxDepth !== undefined, cap: 'depthLimit' },
|
|
442
|
+
{ when: request.toolFilter !== undefined, cap: 'toolFilter' },
|
|
443
|
+
{ when: request.persona !== undefined, cap: 'persona' },
|
|
444
|
+
];
|
|
445
|
+
for (const { when, cap } of needs) {
|
|
446
|
+
if (when && !provider.capabilities[cap]) {
|
|
447
|
+
throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, 'UNSUPPORTED_CAPABILITY');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
})();
|
|
453
|
+
export { SubagentRuntime };
|
|
454
|
+
export default SubagentRuntime;
|
|
455
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation integration markers and host adapters outside the public
|
|
3
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
4
|
+
* @module @qlzt/dsh-subagent/internal
|
|
5
|
+
*/
|
|
6
|
+
import type { Agent } from '@qlzt/dsh-agent';
|
|
7
|
+
import type { ContentBlock, MessageId, MessageSource } from '@qlzt/dsh-llm';
|
|
8
|
+
import type { SessionId } from '@qlzt/dsh-session';
|
|
9
|
+
import type { ToolDefinition } from '@qlzt/dsh-tools';
|
|
10
|
+
import type SubagentRuntime from './index.ts';
|
|
11
|
+
import type { SubagentDelivery } from './control-types.ts';
|
|
12
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
13
|
+
export declare const adjacentAgentSendMessageTool: unique symbol;
|
|
14
|
+
/**
|
|
15
|
+
* Mark the standard adjacent-Agent messaging tool without changing its model-visible schema.
|
|
16
|
+
* @param definition - the standard `send_message` definition.
|
|
17
|
+
* @returns the same definition with its internal identity installed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function markAdjacentAgentSendMessageTool(definition: ToolDefinition): ToolDefinition;
|
|
20
|
+
/**
|
|
21
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
22
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
23
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isAdjacentAgentSendMessageTool(definition: ToolDefinition | undefined): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
28
|
+
* entry and this unbundled internal subpath.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare const deliverSubagentPrompt: unique symbol;
|
|
32
|
+
/** Runtime face required by the host-only prompt adapters. */
|
|
33
|
+
export interface HostPromptDeliverer {
|
|
34
|
+
[deliverSubagentPrompt](parent: Agent, childId: SessionId, content: ContentBlock[], source: MessageSource, signal: AbortSignal, delivery: SubagentDelivery): Promise<MessageId>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Queue one host-protocol message without exposing another Service operation.
|
|
38
|
+
* @param runtime - subagent runtime owning continuation residency.
|
|
39
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
40
|
+
* @param childId - durable direct-child session id.
|
|
41
|
+
* @param content - host-authored content to deliver.
|
|
42
|
+
* @param source - durable host-protocol source descriptor.
|
|
43
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
44
|
+
* @returns the accepted message's inbox id.
|
|
45
|
+
*/
|
|
46
|
+
export declare function queueHostSubagentPrompt(runtime: SubagentRuntime, parent: Agent, childId: SessionId, content: ContentBlock[], source: MessageSource, signal: AbortSignal): Promise<MessageId>;
|
|
47
|
+
/**
|
|
48
|
+
* Steer one host-protocol message without exposing another Service operation.
|
|
49
|
+
* @param runtime - subagent runtime owning continuation residency.
|
|
50
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
51
|
+
* @param childId - durable direct-child session id.
|
|
52
|
+
* @param content - host-authored content to deliver.
|
|
53
|
+
* @param source - durable host-protocol source descriptor.
|
|
54
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
55
|
+
* @returns the accepted message's inbox id.
|
|
56
|
+
*/
|
|
57
|
+
export declare function steerHostSubagentPrompt(runtime: SubagentRuntime, parent: Agent, childId: SessionId, content: ContentBlock[], source: MessageSource, signal: AbortSignal): Promise<MessageId>;
|
|
58
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation integration markers and host adapters outside the public
|
|
3
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
4
|
+
* @module @qlzt/dsh-subagent/internal
|
|
5
|
+
*/
|
|
6
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
7
|
+
export const adjacentAgentSendMessageTool = Symbol.for('dsh.subagent.adjacentAgentSendMessageTool');
|
|
8
|
+
/**
|
|
9
|
+
* Mark the standard adjacent-Agent messaging tool without changing its model-visible schema.
|
|
10
|
+
* @param definition - the standard `send_message` definition.
|
|
11
|
+
* @returns the same definition with its internal identity installed.
|
|
12
|
+
*/
|
|
13
|
+
export function markAdjacentAgentSendMessageTool(definition) {
|
|
14
|
+
Object.defineProperty(definition, adjacentAgentSendMessageTool, { value: true });
|
|
15
|
+
return definition;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
19
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
20
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
21
|
+
*/
|
|
22
|
+
export function isAdjacentAgentSendMessageTool(definition) {
|
|
23
|
+
return definition !== undefined
|
|
24
|
+
&& definition[adjacentAgentSendMessageTool] === true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
28
|
+
* entry and this unbundled internal subpath.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export const deliverSubagentPrompt = Symbol.for('dsh.subagent.deliverPrompt');
|
|
32
|
+
/**
|
|
33
|
+
* Queue one host-protocol message without exposing another Service operation.
|
|
34
|
+
* @param runtime - subagent runtime owning continuation residency.
|
|
35
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
36
|
+
* @param childId - durable direct-child session id.
|
|
37
|
+
* @param content - host-authored content to deliver.
|
|
38
|
+
* @param source - durable host-protocol source descriptor.
|
|
39
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
40
|
+
* @returns the accepted message's inbox id.
|
|
41
|
+
*/
|
|
42
|
+
export function queueHostSubagentPrompt(runtime, parent, childId, content, source, signal) {
|
|
43
|
+
return runtime[deliverSubagentPrompt](parent, childId, content, source, signal, 'queue');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Steer one host-protocol message without exposing another Service operation.
|
|
47
|
+
* @param runtime - subagent runtime owning continuation residency.
|
|
48
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
49
|
+
* @param childId - durable direct-child session id.
|
|
50
|
+
* @param content - host-authored content to deliver.
|
|
51
|
+
* @param source - durable host-protocol source descriptor.
|
|
52
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
53
|
+
* @returns the accepted message's inbox id.
|
|
54
|
+
*/
|
|
55
|
+
export function steerHostSubagentPrompt(runtime, parent, childId, content, source, signal) {
|
|
56
|
+
return runtime[deliverSubagentPrompt](parent, childId, content, source, signal, 'steer');
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle-edge publication for managed subagent activations.
|
|
3
|
+
*
|
|
4
|
+
* Public event payloads live in `./types.ts`; the package-private observer
|
|
5
|
+
* retains only the identity shared by each start/end pair.
|
|
6
|
+
*
|
|
7
|
+
* @module @qlzt/dsh-subagent/lifecycle
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { Agent } from '@qlzt/dsh-agent';
|
|
11
|
+
import type { SessionEvent, SessionId } from '@qlzt/dsh-session';
|
|
12
|
+
import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from './types.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Lifecycle observer for one activation's residency epoch. Package-private: the
|
|
15
|
+
* continuation manager is the only consumer, and its call ordering is an
|
|
16
|
+
* in-package contract rather than a published extension point.
|
|
17
|
+
*/
|
|
18
|
+
export interface ActivationObserver {
|
|
19
|
+
/**
|
|
20
|
+
* Publish the start edge once the epoch is resident.
|
|
21
|
+
* @param child - the resident local Agent, or undefined for an external execution.
|
|
22
|
+
*/
|
|
23
|
+
start(child?: Agent): void;
|
|
24
|
+
/**
|
|
25
|
+
* Publish the terminal edge exactly once, pairing this epoch's {@link start},
|
|
26
|
+
* after the disposal outcome is known. Called only for a resident epoch: a
|
|
27
|
+
* failure before residency publishes no edge, because inventing one would
|
|
28
|
+
* report a lifecycle the child never had.
|
|
29
|
+
* @param result - the captured result, with teardown failures applied.
|
|
30
|
+
*/
|
|
31
|
+
settle(result: SubagentResult): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Publish one lifecycle edge with per-listener exception containment. Run edges
|
|
35
|
+
* carry the delegating parent that keys scoped dispatch; provider removal has no
|
|
36
|
+
* parent carrier and reaches listeners unscoped.
|
|
37
|
+
*
|
|
38
|
+
* The service owns this closure because scoped dispatch keys its carrier by the
|
|
39
|
+
* exact service instance, whose own context filter composes into the carrier;
|
|
40
|
+
* a narrowed stand-in would silently change scope filtering.
|
|
41
|
+
*/
|
|
42
|
+
export type LifecycleEmitter = {
|
|
43
|
+
(name: 'subagent/start', info: SubagentRunInfo, parent: Agent): void;
|
|
44
|
+
(name: 'subagent/end', info: SubagentRunEndInfo, parent: Agent): void;
|
|
45
|
+
(name: 'subagent/provider-removed', info: string): void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Build the contained lifecycle emitter this seam publishes every edge through.
|
|
49
|
+
* Every listener is independently contained: a synchronous throw or a rejected
|
|
50
|
+
* returned promise is logged without starving peer listeners, changing the run,
|
|
51
|
+
* or — for provider removal, which fires from a disposer — breaking teardown.
|
|
52
|
+
* @param ctx - the service's own context, owning dispatch and the logger.
|
|
53
|
+
* @param carrier - resolve the scoped dispatch carrier for one delegating parent.
|
|
54
|
+
* @returns the emitter both observers and the provider registry publish through.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createLifecycleEmitter(ctx: Context, carrier: (parent: Agent) => object): LifecycleEmitter;
|
|
57
|
+
/**
|
|
58
|
+
* Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
|
|
59
|
+
* @param emit - the contained lifecycle emitter.
|
|
60
|
+
* @param provider - the provider name recorded in the durable descriptor.
|
|
61
|
+
* @param childId - the durable child session id.
|
|
62
|
+
* @param parent - the exact live direct parent keying scoped dispatch.
|
|
63
|
+
* @returns the observer whose edges this epoch publishes.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createActivationObserver(emit: LifecycleEmitter, provider: string, childId: SessionId, parent: Agent): ActivationObserver;
|
|
66
|
+
/**
|
|
67
|
+
* Why this child's epoch ended, for the terminal lifecycle edge and the
|
|
68
|
+
* manager's own parent delivery. The child's own log is authoritative:
|
|
69
|
+
* teardown succeeding says nothing about whether the model errored, hit its
|
|
70
|
+
* token ceiling, or was cancelled, so deriving the reason from disposal would
|
|
71
|
+
* report failed work as completed.
|
|
72
|
+
*
|
|
73
|
+
* {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
|
|
74
|
+
* which turn accounts for the work this epoch consumed, and whether accepted
|
|
75
|
+
* work was cancelled after it without any turn opening over it. A recorded
|
|
76
|
+
* failure still wins over a cancellation — stopping a child that had already
|
|
77
|
+
* failed does not turn its failure into a cancellation.
|
|
78
|
+
* @param events - this epoch's own event suffix.
|
|
79
|
+
* @returns its terminal stop reason; `completed` only for an epoch that both
|
|
80
|
+
* closed cleanly and had nothing left to run.
|
|
81
|
+
*/
|
|
82
|
+
export declare function epochStopReason(events: readonly SessionEvent[]): SubagentResult['stopReason'];
|
|
83
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|