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