@morlay/dsh-subagent 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +9 -9
- package/package.json +1 -1
- package/src/continuation-messages.ts +68 -59
- package/src/index.ts +262 -189
- package/src/manager.ts +886 -716
package/src/index.ts
CHANGED
|
@@ -27,32 +27,36 @@
|
|
|
27
27
|
*
|
|
28
28
|
* @module @deepseek-ai/dsh-subagent
|
|
29
29
|
*/
|
|
30
|
-
import type { Volatile } from
|
|
30
|
+
import type { Volatile } from "@deepseek-ai/cordis";
|
|
31
31
|
|
|
32
|
-
import { Context } from
|
|
33
|
-
import z from
|
|
34
|
-
import type {} from
|
|
35
|
-
import type {} from
|
|
36
|
-
import { scopeTarget } from
|
|
37
|
-
import type {} from
|
|
32
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
33
|
+
import z from "@deepseek-ai/schemastery";
|
|
34
|
+
import type {} from "@deepseek-ai/dsh-working-directory";
|
|
35
|
+
import type {} from "@deepseek-ai/dsh-attachment";
|
|
36
|
+
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
37
|
+
import type {} from "@deepseek-ai/dsh-subagent";
|
|
38
38
|
// 服务面声明(`ctx.agentPresets`)由它给:回报指引按会话挂的 preset 选文案。
|
|
39
|
-
import type {} from
|
|
40
|
-
import { assertObjectJsonSchema } from
|
|
41
|
-
import type { ContentBlock, MessageId, MessageSource } from
|
|
42
|
-
import type { Agent } from
|
|
43
|
-
import type { SessionId } from
|
|
44
|
-
import { canonicalClientTimeZone } from
|
|
45
|
-
import { Remote, RemoteError, TypertRemoteService, principalOf, type Principal } from '@deepseek-ai/dsh-typert-protocol'
|
|
39
|
+
import type {} from "@deepseek-ai/dsh-agent-preset-registry";
|
|
40
|
+
import { assertObjectJsonSchema } from "@deepseek-ai/dsh-tools";
|
|
41
|
+
import type { ContentBlock, MessageId, MessageSource } from "@deepseek-ai/dsh-llm";
|
|
42
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
43
|
+
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
44
|
+
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
46
45
|
import {
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
Remote,
|
|
47
|
+
RemoteError,
|
|
48
|
+
TypertRemoteService,
|
|
49
|
+
principalOf,
|
|
50
|
+
type Principal,
|
|
51
|
+
} from "@deepseek-ai/dsh-typert-protocol";
|
|
52
|
+
import { rejectPrompt, validateControlRequest } from "./vendor/dsh-subagent/control.ts";
|
|
49
53
|
import type {
|
|
50
54
|
SubagentDelivery,
|
|
51
55
|
SubagentInterruptReceipt,
|
|
52
56
|
SubagentPromptReceipt,
|
|
53
57
|
SubagentPromptRequest,
|
|
54
58
|
SubagentPromptRequestId,
|
|
55
|
-
} from
|
|
59
|
+
} from "./vendor/dsh-subagent/control-types.ts";
|
|
56
60
|
import type {
|
|
57
61
|
ContinuableCreateRequest,
|
|
58
62
|
ContinuableCreateSpec,
|
|
@@ -63,25 +67,37 @@ import type {
|
|
|
63
67
|
SubagentProvider,
|
|
64
68
|
SubagentSendMessageOptions,
|
|
65
69
|
SubagentStartRequest,
|
|
66
|
-
} from
|
|
67
|
-
import { SubagentError } from
|
|
68
|
-
import { assertSubagentMaxDepth } from
|
|
69
|
-
import {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
import {
|
|
74
|
-
import
|
|
75
|
-
import {
|
|
76
|
-
import
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
} from "./vendor/dsh-subagent/types.ts";
|
|
71
|
+
import { SubagentError } from "./vendor/dsh-subagent/error.ts";
|
|
72
|
+
import { assertSubagentMaxDepth } from "./vendor/dsh-subagent/depth.ts";
|
|
73
|
+
import {
|
|
74
|
+
createActivationObserver,
|
|
75
|
+
createLifecycleEmitter,
|
|
76
|
+
} from "./vendor/dsh-subagent/lifecycle.ts";
|
|
77
|
+
import type { ActivationObserver, LifecycleEmitter } from "./vendor/dsh-subagent/lifecycle.ts";
|
|
78
|
+
import SubagentManager from "./manager.ts";
|
|
79
|
+
import { installDelegationContext } from "./delegation-context.ts";
|
|
80
|
+
import {
|
|
81
|
+
listChildren as listSubagentChildren,
|
|
82
|
+
listDescendants as listSubagentDescendants,
|
|
83
|
+
} from "./vendor/dsh-subagent/list-children.ts";
|
|
84
|
+
import type { SubagentDescendantListEntry } from "./vendor/dsh-subagent/list-children.ts";
|
|
85
|
+
import { installSubagentArchiveAdmission } from "./vendor/dsh-subagent/archive-admission.ts";
|
|
86
|
+
import type { SubagentCatalogEntry } from "./vendor/dsh-subagent/projection-types.ts";
|
|
87
|
+
import {
|
|
88
|
+
subagentIdentityProjectionDefinition,
|
|
89
|
+
subagentTimingProjectionDefinition,
|
|
90
|
+
} from "./vendor/dsh-subagent/projection.ts";
|
|
91
|
+
import { subagentCatalogProjectionDefinition } from "./vendor/dsh-subagent/catalog.ts";
|
|
92
|
+
import { deliverSubagentPrompt } from "./vendor/dsh-subagent/internal.ts";
|
|
80
93
|
|
|
81
|
-
export type {} from
|
|
82
|
-
export * from
|
|
83
|
-
export {
|
|
84
|
-
|
|
94
|
+
export type {} from "./vendor/dsh-subagent/catalog.ts";
|
|
95
|
+
export * from "./vendor/dsh-subagent/out-of-process.ts";
|
|
96
|
+
export {
|
|
97
|
+
AssistantOutputFold,
|
|
98
|
+
finalAssistantOutput,
|
|
99
|
+
} from "./vendor/dsh-subagent/assistant-output.ts";
|
|
100
|
+
export { SubagentRunId } from "./vendor/dsh-subagent/types.ts";
|
|
85
101
|
export type {
|
|
86
102
|
ContinuableCreateRequest,
|
|
87
103
|
ContinuableCreateSpec,
|
|
@@ -97,21 +113,21 @@ export type {
|
|
|
97
113
|
ResolvedSubagentStartRequest,
|
|
98
114
|
SubagentStopReason,
|
|
99
115
|
SubagentStopReasonMap,
|
|
100
|
-
} from
|
|
116
|
+
} from "./vendor/dsh-subagent/types.ts";
|
|
101
117
|
export {
|
|
102
118
|
foldSubagentDescriptor,
|
|
103
119
|
snapshotSubagentDescriptor,
|
|
104
120
|
SUBAGENT_DESCRIPTOR_VERSION,
|
|
105
|
-
} from
|
|
121
|
+
} from "./vendor/dsh-subagent/descriptor.ts";
|
|
106
122
|
export type {
|
|
107
123
|
ContinuableSubagentDescriptorData,
|
|
108
124
|
ContinuableSubagentDescriptorInput,
|
|
109
125
|
OneShotSubagentDescriptorData,
|
|
110
126
|
SubagentDescriptorData,
|
|
111
|
-
} from
|
|
112
|
-
export { STRUCTURED_OUTPUT_TOOL } from
|
|
113
|
-
export { SubagentError } from
|
|
114
|
-
export { assertSubagentMaxDepth, delegationDepthOf } from
|
|
127
|
+
} from "./vendor/dsh-subagent/descriptor.ts";
|
|
128
|
+
export { STRUCTURED_OUTPUT_TOOL } from "./vendor/dsh-subagent/structured.ts";
|
|
129
|
+
export { SubagentError } from "./vendor/dsh-subagent/error.ts";
|
|
130
|
+
export { assertSubagentMaxDepth, delegationDepthOf } from "./vendor/dsh-subagent/depth.ts";
|
|
115
131
|
export {
|
|
116
132
|
appendDelegatedPolicyOverrides,
|
|
117
133
|
applyChildComposition,
|
|
@@ -121,13 +137,20 @@ export {
|
|
|
121
137
|
resolveChildAgentOptions,
|
|
122
138
|
resolveChildDepth,
|
|
123
139
|
SubagentDepthError,
|
|
124
|
-
} from
|
|
125
|
-
export type {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
export type {
|
|
130
|
-
export type
|
|
140
|
+
} from "./vendor/dsh-subagent/child-agent.ts";
|
|
141
|
+
export type {
|
|
142
|
+
ChildComposition,
|
|
143
|
+
DelegatedPolicyOverrides,
|
|
144
|
+
} from "./vendor/dsh-subagent/child-agent.ts";
|
|
145
|
+
export type { AgentMessageSource, SubagentSettledMessageSource } from "./continuation-messages.ts";
|
|
146
|
+
export type * from "./vendor/dsh-subagent/control-types.ts";
|
|
147
|
+
export type { SubagentDescendantListEntry } from "./vendor/dsh-subagent/list-children.ts";
|
|
148
|
+
export type { SubagentRunEndInfo, SubagentRunInfo } from "./vendor/dsh-subagent/types.ts";
|
|
149
|
+
export type {
|
|
150
|
+
SubagentCatalogEntry,
|
|
151
|
+
SubagentIdentityProjection,
|
|
152
|
+
SubagentTimingProjection,
|
|
153
|
+
} from "./vendor/dsh-subagent/projection-types.ts";
|
|
131
154
|
|
|
132
155
|
/**
|
|
133
156
|
* Durable attribution of one browser-authored follow-up. The Session
|
|
@@ -137,73 +160,94 @@ export type { SubagentCatalogEntry, SubagentIdentityProjection, SubagentTimingPr
|
|
|
137
160
|
* Client reconciles its optimistic prompt against.
|
|
138
161
|
*/
|
|
139
162
|
interface BrowserPromptSource {
|
|
140
|
-
readonly kind:
|
|
141
|
-
readonly rpcId: SubagentPromptRequestId
|
|
142
|
-
readonly clientTimeZone?: string
|
|
163
|
+
readonly kind: "user";
|
|
164
|
+
readonly rpcId: SubagentPromptRequestId;
|
|
165
|
+
readonly clientTimeZone?: string;
|
|
143
166
|
/** Caller that submitted this follow-up, written only when it is not the owner; absent means the owner. */
|
|
144
|
-
readonly principal?: Principal
|
|
167
|
+
readonly principal?: Principal;
|
|
145
168
|
}
|
|
146
169
|
|
|
147
170
|
/** Host configuration for continuable subagent capacity. */
|
|
148
171
|
export interface Config {
|
|
149
172
|
/** Maximum live children sharing uninterrupted continuable parent links; defaults to 8. */
|
|
150
|
-
maxActiveSubagents: Volatile<number
|
|
173
|
+
maxActiveSubagents: Volatile<number>;
|
|
151
174
|
/** Default delegation depth for tools without an explicit limit; defaults to 1. */
|
|
152
|
-
maxDepth: Volatile<number
|
|
175
|
+
maxDepth: Volatile<number>;
|
|
153
176
|
/**
|
|
154
177
|
* Preset ids the Chinese return guidance is restricted to; empty (default) means no restriction.
|
|
155
178
|
*
|
|
156
179
|
* 不配 = 任意 preset 的会话都用本包的中文回报指引(官方四个 shipped preset 也在内,还有还没绑 preset 的会话)。
|
|
157
180
|
* 要反过来只让某几份 preset 用中文,就在这里列出来。装配面,不是用户面——所以不进设置页(`.hidden()`)。
|
|
158
181
|
*/
|
|
159
|
-
localizedReturnGuidancePresets: string[]
|
|
182
|
+
localizedReturnGuidancePresets: string[];
|
|
160
183
|
}
|
|
161
184
|
|
|
162
185
|
/** Named provider registry with managed activations, durable discovery, and local child messaging. */
|
|
163
186
|
export class SubagentRuntime extends TypertRemoteService {
|
|
164
187
|
static Config = z.object({
|
|
165
188
|
maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
|
|
166
|
-
maxActiveSubagents: z
|
|
189
|
+
maxActiveSubagents: z
|
|
190
|
+
.number()
|
|
191
|
+
.step(1)
|
|
192
|
+
.min(1)
|
|
193
|
+
.max(Number.MAX_SAFE_INTEGER)
|
|
194
|
+
.default(8)
|
|
195
|
+
.volatile(),
|
|
167
196
|
localizedReturnGuidancePresets: z.array(z.string()).default([]).hidden(),
|
|
168
|
-
})
|
|
169
|
-
static inject = [
|
|
170
|
-
private providers = new Map<string, SubagentProvider>()
|
|
171
|
-
private manager: SubagentManager | undefined
|
|
197
|
+
});
|
|
198
|
+
static inject = ["workingDirectory"];
|
|
199
|
+
private providers = new Map<string, SubagentProvider>();
|
|
200
|
+
private manager: SubagentManager | undefined;
|
|
172
201
|
/**
|
|
173
202
|
* The contained lifecycle-edge publisher. Built here because scoped dispatch
|
|
174
203
|
* keys its carrier by this exact service instance, whose own context filter
|
|
175
204
|
* composes into the carrier.
|
|
176
205
|
*/
|
|
177
|
-
private readonly emitLifecycle: LifecycleEmitter
|
|
206
|
+
private readonly emitLifecycle: LifecycleEmitter;
|
|
178
207
|
|
|
179
|
-
constructor(
|
|
180
|
-
|
|
208
|
+
constructor(
|
|
209
|
+
ctx: Context,
|
|
210
|
+
private config: Config,
|
|
211
|
+
) {
|
|
212
|
+
super(ctx, "subagents");
|
|
181
213
|
// 模型看到的委派范围说明换成中文(见 `./delegation-context.ts`)。
|
|
182
|
-
installDelegationContext(ctx)
|
|
183
|
-
this.emitLifecycle = createLifecycleEmitter(this.ctx, parent => scopeTarget(this, parent))
|
|
184
|
-
ctx.inject([
|
|
185
|
-
const manager = new SubagentManager(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
installDelegationContext(ctx);
|
|
215
|
+
this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
|
|
216
|
+
ctx.inject(["agents"], (childCtx: Context) => {
|
|
217
|
+
const manager = new SubagentManager(
|
|
218
|
+
childCtx,
|
|
219
|
+
{
|
|
220
|
+
startExternal: (name, request) => {
|
|
221
|
+
const provider = this.expectProvider(name) as SubagentProvider &
|
|
222
|
+
Required<Pick<SubagentProvider, "start">>;
|
|
223
|
+
return provider.start(request);
|
|
224
|
+
},
|
|
225
|
+
prepareContinuable: (name, request) => this.prepareContinuable(name, request),
|
|
226
|
+
observeActivation: (provider, childId, parent) =>
|
|
227
|
+
this.observeActivation(provider, childId, parent),
|
|
228
|
+
},
|
|
229
|
+
() => this.config.maxActiveSubagents.get(),
|
|
230
|
+
() => this.config.localizedReturnGuidancePresets,
|
|
231
|
+
);
|
|
232
|
+
this.manager = manager;
|
|
233
|
+
childCtx.effect(
|
|
234
|
+
() => () => {
|
|
235
|
+
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
236
|
+
if (this.manager === manager) this.manager = undefined;
|
|
189
237
|
},
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})
|
|
199
|
-
ctx.inject(['sessionProjections'], (projectionCtx) => {
|
|
200
|
-
projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition)
|
|
201
|
-
projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition)
|
|
202
|
-
projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition)
|
|
203
|
-
})
|
|
238
|
+
"subagents.managerBinding()",
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
ctx.inject(["sessionProjections"], (projectionCtx) => {
|
|
242
|
+
projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
|
|
243
|
+
projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
244
|
+
projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
245
|
+
});
|
|
204
246
|
// Archive admission: this runtime is the owner that knows which live
|
|
205
247
|
// children descend from a Session and how a parent stops them.
|
|
206
|
-
ctx.inject([
|
|
248
|
+
ctx.inject(["agents"], (agentsCtx: Context) => {
|
|
249
|
+
installSubagentArchiveAdmission(agentsCtx);
|
|
250
|
+
});
|
|
207
251
|
}
|
|
208
252
|
|
|
209
253
|
/**
|
|
@@ -211,12 +255,12 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
211
255
|
* @param configured - Explicit tool limit, or provider-managed for external delegation.
|
|
212
256
|
* @returns The numeric limit, or undefined when the provider owns depth enforcement.
|
|
213
257
|
*/
|
|
214
|
-
resolveMaxDepth(configured?: number |
|
|
215
|
-
if (configured ===
|
|
216
|
-
if (configured !== undefined) return configured
|
|
217
|
-
const depth = this.config.maxDepth.get()
|
|
218
|
-
assertSubagentMaxDepth(depth)
|
|
219
|
-
return depth
|
|
258
|
+
resolveMaxDepth(configured?: number | "provider-managed"): number | undefined {
|
|
259
|
+
if (configured === "provider-managed") return undefined;
|
|
260
|
+
if (configured !== undefined) return configured;
|
|
261
|
+
const depth = this.config.maxDepth.get();
|
|
262
|
+
assertSubagentMaxDepth(depth);
|
|
263
|
+
return depth;
|
|
220
264
|
}
|
|
221
265
|
|
|
222
266
|
/**
|
|
@@ -224,14 +268,15 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
224
268
|
* @param spec - local task, reserved child id, and result recipient.
|
|
225
269
|
* @returns activation with its accepted initial message id.
|
|
226
270
|
*/
|
|
227
|
-
startActivation(
|
|
228
|
-
|
|
271
|
+
startActivation(
|
|
272
|
+
spec: SubagentActivationSpec & { readonly childId: SessionId },
|
|
273
|
+
): Promise<SubagentActivation & { readonly messageId: MessageId }>;
|
|
229
274
|
/**
|
|
230
275
|
* Start a local or external child.
|
|
231
276
|
* @param spec - task, backend, and result recipient.
|
|
232
277
|
* @returns activation with a message id only for local children.
|
|
233
278
|
*/
|
|
234
|
-
startActivation(spec: SubagentActivationSpec): Promise<SubagentActivation
|
|
279
|
+
startActivation(spec: SubagentActivationSpec): Promise<SubagentActivation>;
|
|
235
280
|
/**
|
|
236
281
|
* Establish a managed child with backend-specific execution capabilities.
|
|
237
282
|
* @param spec - task, backend, and result recipient.
|
|
@@ -239,20 +284,26 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
239
284
|
* childId requires a local backend and guarantees an accepted messageId.
|
|
240
285
|
*/
|
|
241
286
|
async startActivation(spec: SubagentActivationSpec): Promise<SubagentActivation> {
|
|
242
|
-
const provider = this.expectProvider(spec.provider)
|
|
287
|
+
const provider = this.expectProvider(spec.provider);
|
|
243
288
|
if (spec.childId !== undefined && provider.prepareContinuable === undefined) {
|
|
244
|
-
throw new SubagentError(
|
|
289
|
+
throw new SubagentError(
|
|
290
|
+
"reserved child ids require a local backend",
|
|
291
|
+
"UNSUPPORTED_CAPABILITY",
|
|
292
|
+
);
|
|
245
293
|
}
|
|
246
|
-
assertSubagentMaxDepth(spec.request.maxDepth)
|
|
247
|
-
this.assertCapabilities(provider, { ...spec.request, signal: spec.signal })
|
|
248
|
-
const manager = this.requireManager()
|
|
249
|
-
if (this.ctx.get(
|
|
250
|
-
throw new SubagentError(
|
|
294
|
+
assertSubagentMaxDepth(spec.request.maxDepth);
|
|
295
|
+
this.assertCapabilities(provider, { ...spec.request, signal: spec.signal });
|
|
296
|
+
const manager = this.requireManager();
|
|
297
|
+
if (this.ctx.get("agents")?.get(spec.request.parent.id) !== spec.request.parent) {
|
|
298
|
+
throw new SubagentError(
|
|
299
|
+
"subagent creation requires the exact live parent agent",
|
|
300
|
+
"UNAUTHORIZED",
|
|
301
|
+
);
|
|
251
302
|
}
|
|
252
|
-
if (spec.request.outputSchema !== undefined) assertObjectJsonSchema(spec.request.outputSchema)
|
|
303
|
+
if (spec.request.outputSchema !== undefined) assertObjectJsonSchema(spec.request.outputSchema);
|
|
253
304
|
return provider.prepareContinuable === undefined
|
|
254
305
|
? manager.startExternal(spec)
|
|
255
|
-
: manager.startLocal(spec)
|
|
306
|
+
: manager.startLocal(spec);
|
|
256
307
|
}
|
|
257
308
|
|
|
258
309
|
/**
|
|
@@ -262,7 +313,7 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
262
313
|
* @returns whether work was joined; hosts recheck parent idle after true.
|
|
263
314
|
*/
|
|
264
315
|
async waitForChildren(parent: Agent): Promise<boolean> {
|
|
265
|
-
return this.manager?.waitForChildren(parent) ?? false
|
|
316
|
+
return this.manager?.waitForChildren(parent) ?? false;
|
|
266
317
|
}
|
|
267
318
|
|
|
268
319
|
/**
|
|
@@ -285,7 +336,7 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
285
336
|
content: ContentBlock[],
|
|
286
337
|
options: SubagentSendMessageOptions,
|
|
287
338
|
): Promise<MessageId> {
|
|
288
|
-
return this.requireManager().sendMessage(sender, targetId, content, options)
|
|
339
|
+
return this.requireManager().sendMessage(sender, targetId, content, options);
|
|
289
340
|
}
|
|
290
341
|
|
|
291
342
|
/**
|
|
@@ -308,9 +359,9 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
308
359
|
signal: AbortSignal,
|
|
309
360
|
delivery: SubagentDelivery,
|
|
310
361
|
): Promise<MessageId> {
|
|
311
|
-
return delivery ===
|
|
362
|
+
return delivery === "steer"
|
|
312
363
|
? this.requireManager().steerPrompt(parent, childId, content, source, signal)
|
|
313
|
-
: this.requireManager().queuePrompt(parent, childId, content, source, signal)
|
|
364
|
+
: this.requireManager().queuePrompt(parent, childId, content, source, signal);
|
|
314
365
|
}
|
|
315
366
|
|
|
316
367
|
/**
|
|
@@ -329,7 +380,7 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
329
380
|
* live target.
|
|
330
381
|
*/
|
|
331
382
|
interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void {
|
|
332
|
-
this.manager?.interrupt(targetSessionId, authority)
|
|
383
|
+
this.manager?.interrupt(targetSessionId, authority);
|
|
333
384
|
}
|
|
334
385
|
|
|
335
386
|
/**
|
|
@@ -343,10 +394,10 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
343
394
|
* @throws an aggregate error after all branches settle when any failed.
|
|
344
395
|
*/
|
|
345
396
|
async drainDescendants(parents: readonly Agent[]): Promise<void> {
|
|
346
|
-
const manager = this.manager
|
|
397
|
+
const manager = this.manager;
|
|
347
398
|
// An absent activation manager cannot own materialized children.
|
|
348
|
-
if (manager === undefined) return
|
|
349
|
-
await manager.drainDescendants(parents)
|
|
399
|
+
if (manager === undefined) return;
|
|
400
|
+
await manager.drainDescendants(parents);
|
|
350
401
|
}
|
|
351
402
|
|
|
352
403
|
/**
|
|
@@ -360,9 +411,9 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
360
411
|
* different parent or the supplied parent identity is stale.
|
|
361
412
|
*/
|
|
362
413
|
async drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise<void> {
|
|
363
|
-
const manager = this.manager
|
|
364
|
-
if (manager === undefined) return
|
|
365
|
-
await manager.drainChildren(parent, childIds)
|
|
414
|
+
const manager = this.manager;
|
|
415
|
+
if (manager === undefined) return;
|
|
416
|
+
await manager.drainChildren(parent, childIds);
|
|
366
417
|
}
|
|
367
418
|
|
|
368
419
|
/**
|
|
@@ -375,7 +426,7 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
375
426
|
* @throws SessionQueryError when the parent cannot be read or the query is cancelled.
|
|
376
427
|
*/
|
|
377
428
|
listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentCatalogEntry[]> {
|
|
378
|
-
return listSubagentChildren(this.ctx, parentSessionId, signal)
|
|
429
|
+
return listSubagentChildren(this.ctx, parentSessionId, signal);
|
|
379
430
|
}
|
|
380
431
|
|
|
381
432
|
/**
|
|
@@ -394,8 +445,11 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
394
445
|
* @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
|
|
395
446
|
* @throws SessionQueryError when the root catalog cannot be read.
|
|
396
447
|
*/
|
|
397
|
-
listDescendants(
|
|
398
|
-
|
|
448
|
+
listDescendants(
|
|
449
|
+
rootSessionId: SessionId,
|
|
450
|
+
signal?: AbortSignal,
|
|
451
|
+
): Promise<SubagentDescendantListEntry[]> {
|
|
452
|
+
return listSubagentDescendants(this.ctx, rootSessionId, signal);
|
|
399
453
|
}
|
|
400
454
|
|
|
401
455
|
/**
|
|
@@ -416,45 +470,52 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
416
470
|
* `subagent/not-resumable`, `subagent/unauthorized`,
|
|
417
471
|
* `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
|
|
418
472
|
*/
|
|
419
|
-
@Remote(
|
|
420
|
-
async prompt(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
473
|
+
@Remote("prompt")
|
|
474
|
+
async prompt(
|
|
475
|
+
request: SubagentPromptRequest,
|
|
476
|
+
signal: AbortSignal,
|
|
477
|
+
): Promise<SubagentPromptReceipt> {
|
|
478
|
+
const { parentSessionId, childSessionId, clientTimeZone, delivery } = request;
|
|
479
|
+
validateControlRequest("subagent.prompt", request);
|
|
480
|
+
const canonicalTimeZone =
|
|
481
|
+
clientTimeZone === undefined ? undefined : canonicalClientTimeZone(clientTimeZone);
|
|
426
482
|
if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
|
|
427
483
|
throw new RemoteError(
|
|
428
|
-
|
|
429
|
-
|
|
484
|
+
"subagent/invalid-time-zone",
|
|
485
|
+
"clientTimeZone must be UTC or a valid IANA Area/Location name",
|
|
430
486
|
{ value: clientTimeZone },
|
|
431
|
-
)
|
|
487
|
+
);
|
|
432
488
|
}
|
|
433
|
-
const parent = this.ctx.get(
|
|
489
|
+
const parent = this.ctx.get("agents")?.get(parentSessionId);
|
|
434
490
|
if (parent === undefined) {
|
|
435
491
|
throw new RemoteError(
|
|
436
|
-
|
|
492
|
+
"subagent/parent-unavailable",
|
|
437
493
|
`parent session "${parentSessionId}" is not live`,
|
|
438
494
|
{ parentSessionId },
|
|
439
|
-
)
|
|
495
|
+
);
|
|
440
496
|
}
|
|
441
|
-
const principal = principalOf(this.ctx.invocation)
|
|
497
|
+
const principal = principalOf(this.ctx.invocation);
|
|
442
498
|
const source: BrowserPromptSource = {
|
|
443
|
-
kind:
|
|
499
|
+
kind: "user",
|
|
444
500
|
rpcId: request.requestId,
|
|
445
501
|
...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }),
|
|
446
502
|
...(principal === undefined ? {} : { principal }),
|
|
447
|
-
}
|
|
503
|
+
};
|
|
448
504
|
try {
|
|
449
505
|
// Admission precedes delivery: image parts become durable references
|
|
450
506
|
// here, so the child inbox only ever accepts Host-persisted attachments.
|
|
451
|
-
let content: ContentBlock[]
|
|
452
|
-
if (
|
|
453
|
-
|
|
507
|
+
let content: ContentBlock[];
|
|
508
|
+
if (
|
|
509
|
+
request.content.every(
|
|
510
|
+
(part): part is { readonly type: "text"; readonly text: string } => part.type === "text",
|
|
511
|
+
)
|
|
512
|
+
) {
|
|
513
|
+
content = request.content.map((part) => ({ type: "text", text: part.text }));
|
|
454
514
|
} else {
|
|
455
|
-
const attachments = this.ctx.get(
|
|
456
|
-
if (attachments === undefined)
|
|
457
|
-
|
|
515
|
+
const attachments = this.ctx.get("attachments");
|
|
516
|
+
if (attachments === undefined)
|
|
517
|
+
throw new Error("subagent image prompt requires an attachment store");
|
|
518
|
+
content = await attachments.admitPromptContent(request.content);
|
|
458
519
|
}
|
|
459
520
|
return {
|
|
460
521
|
messageId: await this[deliverSubagentPrompt](
|
|
@@ -465,9 +526,9 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
465
526
|
signal,
|
|
466
527
|
delivery,
|
|
467
528
|
),
|
|
468
|
-
}
|
|
529
|
+
};
|
|
469
530
|
} catch (error: unknown) {
|
|
470
|
-
return rejectPrompt(error, childSessionId, signal)
|
|
531
|
+
return rejectPrompt(error, childSessionId, signal);
|
|
471
532
|
}
|
|
472
533
|
}
|
|
473
534
|
|
|
@@ -485,27 +546,27 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
485
546
|
* `subagent/unauthorized` when the address does not own the live target,
|
|
486
547
|
* otherwise `gateway/internal`.
|
|
487
548
|
*/
|
|
488
|
-
@Remote(
|
|
549
|
+
@Remote("interruptByParent")
|
|
489
550
|
interruptByParent(
|
|
490
551
|
childSessionId: SessionId,
|
|
491
552
|
parentSessionId: SessionId,
|
|
492
|
-
mode:
|
|
553
|
+
mode: "continuable",
|
|
493
554
|
): SubagentInterruptReceipt {
|
|
494
|
-
validateControlRequest(
|
|
555
|
+
validateControlRequest("subagent.interrupt", { childSessionId, parentSessionId, mode });
|
|
495
556
|
try {
|
|
496
|
-
this.interrupt(childSessionId, { kind:
|
|
557
|
+
this.interrupt(childSessionId, { kind: "user", parentSessionId });
|
|
497
558
|
} catch (error: unknown) {
|
|
498
|
-
if (error instanceof SubagentError && error.code ===
|
|
559
|
+
if (error instanceof SubagentError && error.code === "UNAUTHORIZED") {
|
|
499
560
|
throw new RemoteError(
|
|
500
|
-
|
|
501
|
-
|
|
561
|
+
"subagent/unauthorized",
|
|
562
|
+
"subagent does not belong to this parent",
|
|
502
563
|
{ childSessionId },
|
|
503
564
|
{ cause: error },
|
|
504
|
-
)
|
|
565
|
+
);
|
|
505
566
|
}
|
|
506
|
-
throw new RemoteError(
|
|
567
|
+
throw new RemoteError("gateway/internal", "subagent interrupt failed", {}, { cause: error });
|
|
507
568
|
}
|
|
508
|
-
return { accepted: true }
|
|
569
|
+
return { accepted: true };
|
|
509
570
|
}
|
|
510
571
|
|
|
511
572
|
/**
|
|
@@ -517,24 +578,33 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
517
578
|
* @returns the exact Cordis effect disposer.
|
|
518
579
|
*/
|
|
519
580
|
registerProvider(provider: SubagentProvider): () => void {
|
|
520
|
-
const name = provider.name
|
|
581
|
+
const name = provider.name;
|
|
521
582
|
if (provider.start === undefined && provider.prepareContinuable === undefined) {
|
|
522
|
-
throw new SubagentError(
|
|
583
|
+
throw new SubagentError(
|
|
584
|
+
`subagent provider "${name}" must implement start or prepareContinuable`,
|
|
585
|
+
"UNSUPPORTED_CAPABILITY",
|
|
586
|
+
);
|
|
523
587
|
}
|
|
524
588
|
// oxlint-disable-next-line typescript/no-misused-promises -- synchronous disposer
|
|
525
|
-
return this.ctx.effect(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
589
|
+
return this.ctx.effect(
|
|
590
|
+
function* (this: SubagentRuntime) {
|
|
591
|
+
if (this.providers.has(name)) {
|
|
592
|
+
throw new SubagentError(
|
|
593
|
+
`a subagent provider named "${name}" is already registered`,
|
|
594
|
+
"DUPLICATE_PROVIDER",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
this.providers.set(name, provider);
|
|
598
|
+
yield () => {
|
|
599
|
+
this.providers.delete(name);
|
|
600
|
+
this.emitLifecycle("subagent/provider-removed", name);
|
|
601
|
+
};
|
|
602
|
+
// A throwing added-listener unwinds the yielded rollback, matching the
|
|
603
|
+
// repository's fail-loud registration semantics.
|
|
604
|
+
this.ctx.emit("subagent/provider-added", provider);
|
|
605
|
+
}.bind(this),
|
|
606
|
+
"subagents.registerProvider()",
|
|
607
|
+
);
|
|
538
608
|
}
|
|
539
609
|
|
|
540
610
|
/**
|
|
@@ -543,7 +613,7 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
543
613
|
* @returns the provider, or undefined when absent.
|
|
544
614
|
*/
|
|
545
615
|
getProvider(name: string): SubagentProvider | undefined {
|
|
546
|
-
return this.providers.get(name)
|
|
616
|
+
return this.providers.get(name);
|
|
547
617
|
}
|
|
548
618
|
|
|
549
619
|
/**
|
|
@@ -551,7 +621,7 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
551
621
|
* @returns the registered names.
|
|
552
622
|
*/
|
|
553
623
|
list(): string[] {
|
|
554
|
-
return [...this.providers.keys()]
|
|
624
|
+
return [...this.providers.keys()];
|
|
555
625
|
}
|
|
556
626
|
|
|
557
627
|
/**
|
|
@@ -563,28 +633,29 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
563
633
|
name: string,
|
|
564
634
|
request: ContinuableCreateRequest,
|
|
565
635
|
): Promise<ContinuableCreateSpec> {
|
|
566
|
-
const provider = this.expectProvider(name) as SubagentProvider &
|
|
567
|
-
|
|
636
|
+
const provider = this.expectProvider(name) as SubagentProvider &
|
|
637
|
+
Required<Pick<SubagentProvider, "prepareContinuable">>;
|
|
638
|
+
return provider.prepareContinuable(request);
|
|
568
639
|
}
|
|
569
640
|
|
|
570
641
|
/** Look up a provider for dispatch or fail loud. */
|
|
571
642
|
private expectProvider(name: string): SubagentProvider {
|
|
572
|
-
const provider = this.providers.get(name)
|
|
643
|
+
const provider = this.providers.get(name);
|
|
573
644
|
if (provider === undefined) {
|
|
574
|
-
throw new SubagentError(`no subagent provider registered for "${name}"`,
|
|
645
|
+
throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
|
|
575
646
|
}
|
|
576
|
-
return provider
|
|
647
|
+
return provider;
|
|
577
648
|
}
|
|
578
649
|
|
|
579
650
|
/** Resolve the subagent manager or fail loud. */
|
|
580
651
|
private requireManager(): SubagentManager {
|
|
581
652
|
if (this.manager === undefined) {
|
|
582
653
|
throw new SubagentError(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
)
|
|
654
|
+
"continuable subagents require the agents service",
|
|
655
|
+
"CONTINUATION_UNAVAILABLE",
|
|
656
|
+
);
|
|
586
657
|
}
|
|
587
|
-
return this.manager
|
|
658
|
+
return this.manager;
|
|
588
659
|
}
|
|
589
660
|
|
|
590
661
|
/**
|
|
@@ -596,32 +667,34 @@ export class SubagentRuntime extends TypertRemoteService {
|
|
|
596
667
|
childId: SessionId,
|
|
597
668
|
parent: Agent,
|
|
598
669
|
): ActivationObserver {
|
|
599
|
-
return createActivationObserver(this.emitLifecycle, provider, childId, parent)
|
|
670
|
+
return createActivationObserver(this.emitLifecycle, provider, childId, parent);
|
|
600
671
|
}
|
|
601
672
|
|
|
602
673
|
/** Reject the first requested capability that the provider lacks. */
|
|
603
674
|
private assertCapabilities(provider: SubagentProvider, request: SubagentStartRequest): void {
|
|
604
|
-
if (
|
|
675
|
+
if (
|
|
676
|
+
request.agentPreset !== undefined &&
|
|
677
|
+
(!provider.capabilities.agentPreset || provider.inheritsParentContext)
|
|
678
|
+
) {
|
|
605
679
|
throw new SubagentError(
|
|
606
680
|
`subagent provider "${provider.name}" does not support independent agent preset selection`,
|
|
607
|
-
|
|
608
|
-
)
|
|
681
|
+
"UNSUPPORTED_CAPABILITY",
|
|
682
|
+
);
|
|
609
683
|
}
|
|
610
684
|
const needs: { when: boolean; cap: keyof SubagentCapabilities }[] = [
|
|
611
|
-
{ when: request.agentOptions !== undefined, cap:
|
|
612
|
-
{ when: request.outputSchema !== undefined, cap:
|
|
613
|
-
{ when: request.maxDepth !== undefined, cap:
|
|
614
|
-
]
|
|
685
|
+
{ when: request.agentOptions !== undefined, cap: "agentOptions" },
|
|
686
|
+
{ when: request.outputSchema !== undefined, cap: "outputSchema" },
|
|
687
|
+
{ when: request.maxDepth !== undefined, cap: "depthLimit" },
|
|
688
|
+
];
|
|
615
689
|
for (const { when, cap } of needs) {
|
|
616
690
|
if (when && !provider.capabilities[cap]) {
|
|
617
691
|
throw new SubagentError(
|
|
618
692
|
`subagent provider "${provider.name}" does not support the "${cap}" capability`,
|
|
619
|
-
|
|
620
|
-
)
|
|
693
|
+
"UNSUPPORTED_CAPABILITY",
|
|
694
|
+
);
|
|
621
695
|
}
|
|
622
696
|
}
|
|
623
697
|
}
|
|
624
|
-
|
|
625
698
|
}
|
|
626
699
|
|
|
627
|
-
export default SubagentRuntime
|
|
700
|
+
export default SubagentRuntime;
|