@deepseek-ai/dsh-subagent 0.0.1-rc.1
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 +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +132 -0
- package/README.zh.md +132 -0
- package/lib/index.js +2392 -0
- package/lib/invariant.js +76 -0
- package/lib/types/activation-setup-registry.d.ts +57 -0
- package/lib/types/activation-setup-registry.js +148 -0
- package/lib/types/child-agent.d.ts +139 -0
- package/lib/types/child-agent.js +169 -0
- package/lib/types/client.d.ts +7 -0
- package/lib/types/client.js +7 -0
- package/lib/types/continuation.d.ts +375 -0
- package/lib/types/continuation.js +951 -0
- package/lib/types/depth.d.ts +31 -0
- package/lib/types/depth.js +39 -0
- package/lib/types/descriptor-seed.d.ts +21 -0
- package/lib/types/descriptor-seed.js +24 -0
- package/lib/types/descriptor.d.ts +139 -0
- package/lib/types/descriptor.js +189 -0
- package/lib/types/error.d.ts +11 -0
- package/lib/types/error.js +14 -0
- package/lib/types/index.d.ts +278 -0
- package/lib/types/index.js +338 -0
- package/lib/types/invariant.d.ts +13 -0
- package/lib/types/invariant.js +91 -0
- package/lib/types/lifecycle.d.ts +93 -0
- package/lib/types/lifecycle.js +169 -0
- package/lib/types/list-children.d.ts +112 -0
- package/lib/types/list-children.js +316 -0
- package/lib/types/out-of-process.d.ts +115 -0
- package/lib/types/out-of-process.js +181 -0
- package/lib/types/projection-types.d.ts +60 -0
- package/lib/types/projection-types.js +7 -0
- package/lib/types/projection.d.ts +48 -0
- package/lib/types/projection.js +135 -0
- package/lib/types/run-settlement.d.ts +17 -0
- package/lib/types/run-settlement.js +59 -0
- package/lib/types/types.d.ts +293 -0
- package/lib/types/types.js +19 -0
- package/package.json +106 -0
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/** Package-owned subagent registry and lifecycle invariants. @module @deepseek-ai/dsh-subagent/invariant */
|
|
3
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-subagent";
|
|
4
|
+
/** Cordis companion plugin name. */
|
|
5
|
+
const name = "subagent-invariant";
|
|
6
|
+
/** Service required before the companion can reserve package ownership. */
|
|
7
|
+
const inject = ["invariants"];
|
|
8
|
+
/** Assert that a terminal lifecycle payload matches its start identity. */
|
|
9
|
+
function validateRunEnd(start, end, fail) {
|
|
10
|
+
if (start.provider !== end.provider || start.id !== end.id || start.local !== end.local) fail(`subagent/end identity diverges from subagent/start for run ${JSON.stringify(end.runId)}`);
|
|
11
|
+
}
|
|
12
|
+
/** Install provider-registry and start/end pairing checks. */
|
|
13
|
+
const install = Object.assign((ctx, fail) => {
|
|
14
|
+
const providers = new Set(ctx.subagents.list());
|
|
15
|
+
const runs = /* @__PURE__ */ new Map();
|
|
16
|
+
const stagedProviders = /* @__PURE__ */ new WeakSet();
|
|
17
|
+
const stagedRemovals = /* @__PURE__ */ new Set();
|
|
18
|
+
const stagedStarts = /* @__PURE__ */ new WeakSet();
|
|
19
|
+
const stagedEnds = /* @__PURE__ */ new WeakSet();
|
|
20
|
+
ctx.on("internal/dispatch", (_mode, eventName, args) => {
|
|
21
|
+
if (eventName === "subagent/provider-added") {
|
|
22
|
+
const provider = args[0];
|
|
23
|
+
if (provider.name.length === 0) fail("subagent provider names must be non-empty");
|
|
24
|
+
if (providers.has(provider.name)) fail(`subagent/provider-added repeated ${JSON.stringify(provider.name)}`);
|
|
25
|
+
stagedProviders.add(provider);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (eventName === "subagent/provider-removed") {
|
|
29
|
+
const providerName = args[0];
|
|
30
|
+
if (!providers.has(providerName)) fail(`subagent/provider-removed names unknown provider ${JSON.stringify(providerName)}`);
|
|
31
|
+
stagedRemovals.add(providerName);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (eventName === "subagent/start") {
|
|
35
|
+
const info = args[0];
|
|
36
|
+
if (info.provider.length === 0 || String(info.runId).length === 0 || String(info.id).length === 0) fail("subagent/start provider, runId, and child id must be non-empty");
|
|
37
|
+
if (runs.has(info.runId)) fail(`subagent/start repeated run id ${JSON.stringify(info.runId)}`);
|
|
38
|
+
stagedStarts.add(info);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (eventName !== "subagent/end") return;
|
|
42
|
+
const info = args[0];
|
|
43
|
+
const start = runs.get(info.runId);
|
|
44
|
+
if (start === void 0) fail(`subagent/end has no matching subagent/start for run ${JSON.stringify(info.runId)}`);
|
|
45
|
+
validateRunEnd(start, info, fail);
|
|
46
|
+
stagedEnds.add(info);
|
|
47
|
+
}, { global: true });
|
|
48
|
+
ctx.on("subagent/provider-added", (provider) => {
|
|
49
|
+
/* v8 ignore next -- internal/dispatch stages the same provider object */
|
|
50
|
+
if (!stagedProviders.delete(provider)) return;
|
|
51
|
+
providers.add(provider.name);
|
|
52
|
+
}, { global: true });
|
|
53
|
+
ctx.on("subagent/provider-removed", (providerName) => {
|
|
54
|
+
/* v8 ignore next -- internal/dispatch stages the same provider name */
|
|
55
|
+
if (!stagedRemovals.delete(providerName)) return;
|
|
56
|
+
providers.delete(providerName);
|
|
57
|
+
}, { global: true });
|
|
58
|
+
ctx.on("subagent/start", (info) => {
|
|
59
|
+
/* v8 ignore next -- internal/dispatch stages the same lifecycle object */
|
|
60
|
+
if (!stagedStarts.delete(info)) return;
|
|
61
|
+
runs.set(info.runId, info);
|
|
62
|
+
}, { global: true });
|
|
63
|
+
ctx.on("subagent/end", (info) => {
|
|
64
|
+
/* v8 ignore next -- internal/dispatch stages the same lifecycle object */
|
|
65
|
+
if (!stagedEnds.delete(info)) return;
|
|
66
|
+
runs.delete(info.runId);
|
|
67
|
+
}, { global: true });
|
|
68
|
+
}, { inject: ["subagents"] });
|
|
69
|
+
/**
|
|
70
|
+
* Register the subagent invariant companion.
|
|
71
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
72
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
73
|
+
*/
|
|
74
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
75
|
+
//#endregion
|
|
76
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal registry of deployment capabilities composed into every continuable
|
|
3
|
+
* child's unpublished creation context.
|
|
4
|
+
*
|
|
5
|
+
* A contribution grants a child-scoped capability without teaching the
|
|
6
|
+
* continuation manager which capabilities exist. The manager owns residency;
|
|
7
|
+
* this registry owns the join between plugin lifetime, unpublished setup, and
|
|
8
|
+
* Activation disposal, so no installation outlives either owner and no removed
|
|
9
|
+
* contribution can be installed after revocation reports completion.
|
|
10
|
+
*
|
|
11
|
+
* @module @deepseek-ai/dsh-subagent/activation-setup-registry
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
import type { AgentSetupCommit } from '@deepseek-ai/dsh-agent';
|
|
15
|
+
/**
|
|
16
|
+
* One deployment capability installed into a continuable child's unpublished
|
|
17
|
+
* creation context. It composes synchronously before publication and returns
|
|
18
|
+
* the disposer for exactly that installation.
|
|
19
|
+
* @param childCtx - the child's unpublished scoped context.
|
|
20
|
+
* @returns the disposer revoking this installation.
|
|
21
|
+
*/
|
|
22
|
+
export type ContinuableSetupContribution = (childCtx: Context) => () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Owns continuable-child setup registrations, installations, rollback, child
|
|
25
|
+
* cleanup, and immediate live revocation.
|
|
26
|
+
*/
|
|
27
|
+
export declare class SubagentActivationSetupRegistry {
|
|
28
|
+
/** Live contributions in installation order. */
|
|
29
|
+
private readonly registrations;
|
|
30
|
+
/** Child context to its live installations. */
|
|
31
|
+
private readonly byChild;
|
|
32
|
+
/**
|
|
33
|
+
* Register one contribution.
|
|
34
|
+
* @param contribution - synchronous child-scope installer.
|
|
35
|
+
* @returns an idempotent registration undo.
|
|
36
|
+
* @throws after attempting every installation when any disposer fails.
|
|
37
|
+
*/
|
|
38
|
+
register(contribution: ContinuableSetupContribution): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Install every live contribution into one unpublished child context.
|
|
41
|
+
* @param childCtx - the child's unpublished scoped context.
|
|
42
|
+
* @returns the provisioning commit consumed at Agent publication.
|
|
43
|
+
*/
|
|
44
|
+
apply(childCtx: Context): AgentSetupCommit;
|
|
45
|
+
/** Release every remaining installation owned by one disposed child scope. */
|
|
46
|
+
private releaseChild;
|
|
47
|
+
/**
|
|
48
|
+
* Release a batch completely before reporting disposer failures.
|
|
49
|
+
* @param installations - records to release.
|
|
50
|
+
* @param during - operation name for diagnostics.
|
|
51
|
+
*/
|
|
52
|
+
private releaseAll;
|
|
53
|
+
/** Drop one installation from both indices and dispose it exactly once. */
|
|
54
|
+
private release;
|
|
55
|
+
}
|
|
56
|
+
export default SubagentActivationSetupRegistry;
|
|
57
|
+
//# sourceMappingURL=activation-setup-registry.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal registry of deployment capabilities composed into every continuable
|
|
3
|
+
* child's unpublished creation context.
|
|
4
|
+
*
|
|
5
|
+
* A contribution grants a child-scoped capability without teaching the
|
|
6
|
+
* continuation manager which capabilities exist. The manager owns residency;
|
|
7
|
+
* this registry owns the join between plugin lifetime, unpublished setup, and
|
|
8
|
+
* Activation disposal, so no installation outlives either owner and no removed
|
|
9
|
+
* contribution can be installed after revocation reports completion.
|
|
10
|
+
*
|
|
11
|
+
* @module @deepseek-ai/dsh-subagent/activation-setup-registry
|
|
12
|
+
*/
|
|
13
|
+
import { errorChain } from '@deepseek-ai/dsh-llm';
|
|
14
|
+
import { SubagentError } from "./error.js";
|
|
15
|
+
/** Re-read mutable removal state after a contribution may have revoked itself. */
|
|
16
|
+
function isRemoved(registration) {
|
|
17
|
+
return registration.removed;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Owns continuable-child setup registrations, installations, rollback, child
|
|
21
|
+
* cleanup, and immediate live revocation.
|
|
22
|
+
*/
|
|
23
|
+
export class SubagentActivationSetupRegistry {
|
|
24
|
+
/** Live contributions in installation order. */
|
|
25
|
+
registrations = new Set();
|
|
26
|
+
/** Child context to its live installations. */
|
|
27
|
+
byChild = new Map();
|
|
28
|
+
/**
|
|
29
|
+
* Register one contribution.
|
|
30
|
+
* @param contribution - synchronous child-scope installer.
|
|
31
|
+
* @returns an idempotent registration undo.
|
|
32
|
+
* @throws after attempting every installation when any disposer fails.
|
|
33
|
+
*/
|
|
34
|
+
register(contribution) {
|
|
35
|
+
const registration = { contribution, removed: false, installations: new Set() };
|
|
36
|
+
this.registrations.add(registration);
|
|
37
|
+
return () => {
|
|
38
|
+
if (registration.removed)
|
|
39
|
+
return;
|
|
40
|
+
// Close before disposal so a snapshotted apply() cannot install after
|
|
41
|
+
// revocation reports completion.
|
|
42
|
+
registration.removed = true;
|
|
43
|
+
this.registrations.delete(registration);
|
|
44
|
+
this.releaseAll([...registration.installations], 'contribution removal');
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Install every live contribution into one unpublished child context.
|
|
49
|
+
* @param childCtx - the child's unpublished scoped context.
|
|
50
|
+
* @returns the provisioning commit consumed at Agent publication.
|
|
51
|
+
*/
|
|
52
|
+
apply(childCtx) {
|
|
53
|
+
const state = { installations: [], invalidated: false };
|
|
54
|
+
try {
|
|
55
|
+
for (const registration of [...this.registrations]) {
|
|
56
|
+
/* v8 ignore next -- only a synchronous re-entrant revocation of an
|
|
57
|
+
* already-snapshotted registration reaches this guard. */
|
|
58
|
+
if (registration.removed)
|
|
59
|
+
continue;
|
|
60
|
+
const installation = {
|
|
61
|
+
registration,
|
|
62
|
+
childCtx,
|
|
63
|
+
dispose: registration.contribution(childCtx),
|
|
64
|
+
released: false,
|
|
65
|
+
transaction: state,
|
|
66
|
+
};
|
|
67
|
+
registration.installations.add(installation);
|
|
68
|
+
state.installations.push(installation);
|
|
69
|
+
let indexed = this.byChild.get(childCtx);
|
|
70
|
+
if (indexed === undefined) {
|
|
71
|
+
indexed = new Set();
|
|
72
|
+
this.byChild.set(childCtx, indexed);
|
|
73
|
+
}
|
|
74
|
+
indexed.add(installation);
|
|
75
|
+
// An installer may revoke itself before its installation record exists.
|
|
76
|
+
// Dispose that escaped record and invalidate the provisioning batch.
|
|
77
|
+
if (isRemoved(registration))
|
|
78
|
+
this.release(installation);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// Keep the installer failure authoritative, but attempt every rollback.
|
|
83
|
+
try {
|
|
84
|
+
this.releaseAll([...state.installations], 'setup rollback');
|
|
85
|
+
}
|
|
86
|
+
catch (releaseFailure) {
|
|
87
|
+
/* v8 ignore next -- requires independent installer and rollback faults. */
|
|
88
|
+
void releaseFailure;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
childCtx.effect(() => () => { this.releaseChild(childCtx); }, 'subagents.activationSetup()');
|
|
93
|
+
return {
|
|
94
|
+
commit: () => {
|
|
95
|
+
if (state.invalidated) {
|
|
96
|
+
throw new SubagentError('a continuable-subagent setup contribution was revoked while this child was being built; '
|
|
97
|
+
+ 'the child was not established', 'ACTIVATION_SETUP_REVOKED');
|
|
98
|
+
}
|
|
99
|
+
for (const installation of state.installations)
|
|
100
|
+
installation.transaction = undefined;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Release every remaining installation owned by one disposed child scope. */
|
|
105
|
+
releaseChild(childCtx) {
|
|
106
|
+
const indexed = this.byChild.get(childCtx) ?? [];
|
|
107
|
+
this.releaseAll([...indexed], 'child scope disposal');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Release a batch completely before reporting disposer failures.
|
|
111
|
+
* @param installations - records to release.
|
|
112
|
+
* @param during - operation name for diagnostics.
|
|
113
|
+
*/
|
|
114
|
+
releaseAll(installations, during) {
|
|
115
|
+
const failures = [];
|
|
116
|
+
for (const installation of installations) {
|
|
117
|
+
try {
|
|
118
|
+
this.release(installation);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
failures.push(error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (failures.length === 0)
|
|
125
|
+
return;
|
|
126
|
+
throw new SubagentError(`continuable-subagent setup ${during} failed to release ${failures.length} installation(s): `
|
|
127
|
+
+ failures.map(failure => errorChain(failure)).join('; '), 'ACTIVATION_SETUP_RELEASE_FAILED');
|
|
128
|
+
}
|
|
129
|
+
/** Drop one installation from both indices and dispose it exactly once. */
|
|
130
|
+
release(installation) {
|
|
131
|
+
if (installation.released)
|
|
132
|
+
return;
|
|
133
|
+
installation.released = true;
|
|
134
|
+
installation.registration.installations.delete(installation);
|
|
135
|
+
const indexed = this.byChild.get(installation.childCtx);
|
|
136
|
+
/* v8 ignore next 4 -- every live installation is indexed until this method removes it. */
|
|
137
|
+
if (indexed !== undefined) {
|
|
138
|
+
indexed.delete(installation);
|
|
139
|
+
if (indexed.size === 0)
|
|
140
|
+
this.byChild.delete(installation.childCtx);
|
|
141
|
+
}
|
|
142
|
+
if (installation.transaction !== undefined)
|
|
143
|
+
installation.transaction.invalidated = true;
|
|
144
|
+
installation.dispose();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export default SubagentActivationSetupRegistry;
|
|
148
|
+
//# sourceMappingURL=activation-setup-registry.js.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared in-process child composition: the delegation-depth budget, the
|
|
3
|
+
* durable session metadata, the resolved child `AgentOptions`, the delegated
|
|
4
|
+
* policy seed, and the scoped setup a child agent needs. Both the one-shot
|
|
5
|
+
* provider driver and the continuation manager compose children this way, so
|
|
6
|
+
* depth accounting, lineage stamping, and delegation policy have one home.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-subagent/child-agent
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { Agent, AgentOptions, CreateAgentOptions } from '@deepseek-ai/dsh-agent';
|
|
12
|
+
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
|
|
13
|
+
import type { Session, SessionId } from '@deepseek-ai/dsh-session';
|
|
14
|
+
import type { ToolRestriction } from '@deepseek-ai/dsh-tools';
|
|
15
|
+
/** Thrown when starting a child would exceed the requested depth cap. */
|
|
16
|
+
export declare class SubagentDepthError extends Error {
|
|
17
|
+
readonly attemptedDepth: number;
|
|
18
|
+
readonly maxDepth: number;
|
|
19
|
+
constructor(attemptedDepth: number, maxDepth: number);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the child's delegation depth from its parent and enforce an optional
|
|
23
|
+
* cap. The persisted parent header is the monotone floor, so a resumed parent
|
|
24
|
+
* cannot delegate as if it were top-level.
|
|
25
|
+
* @param parent - the delegating parent agent.
|
|
26
|
+
* @param maxDepth - optional absolute cap the resolved depth must not exceed.
|
|
27
|
+
* @returns the child's non-negative safe-integer depth.
|
|
28
|
+
* @throws {SubagentDepthError} when the resolved depth exceeds `maxDepth`.
|
|
29
|
+
* @throws {RangeError} when the resolved depth leaves the safe-integer range.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveChildDepth(parent: Agent, maxDepth: number | undefined): number;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the child's `AgentOptions`: the parent's provider/model/maxTokens
|
|
34
|
+
* route unless the request overrides it, stamped with the child's own
|
|
35
|
+
* delegation depth.
|
|
36
|
+
* @param parent - the delegating parent whose route the child inherits.
|
|
37
|
+
* @param requested - per-child overrides, if any.
|
|
38
|
+
* @param childDepth - the resolved delegation depth to stamp.
|
|
39
|
+
* @returns the resolved options for `ctx.agents.create()`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveChildAgentOptions(parent: Agent, requested: AgentOptions | undefined, childDepth: number): AgentOptions;
|
|
42
|
+
/**
|
|
43
|
+
* Build the child session's durable creation metadata: the parent's workspace,
|
|
44
|
+
* its direct lineage, coarse product origin, the recursion budget that must
|
|
45
|
+
* survive persistence, the seed boundary that separates inherited parent
|
|
46
|
+
* history from child work, and the composition the child runs under.
|
|
47
|
+
*
|
|
48
|
+
* The preset is read from the parent's LIVE scope chain rather than from its
|
|
49
|
+
* header, because a parent that switched preset while blank runs on the newer
|
|
50
|
+
* composition and its header still names the older one. Recording it is what
|
|
51
|
+
* makes a child's history reconstructable: without it a cold read of the child
|
|
52
|
+
* resolves the deployment default and rebuilds turns under a tool set the
|
|
53
|
+
* child never had.
|
|
54
|
+
* @param parent - the delegating parent agent.
|
|
55
|
+
* @param childDepth - the resolved delegation depth to persist.
|
|
56
|
+
* @param lineageSeedLength - how many leading events came from the parent's log.
|
|
57
|
+
* @returns the `meta` for `ctx.agents.create()`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function childSessionMeta(parent: Agent, childDepth: number, lineageSeedLength: number): NonNullable<CreateAgentOptions['meta']>;
|
|
60
|
+
/** The scoped composition a child agent's creation window applies. */
|
|
61
|
+
export interface ChildComposition {
|
|
62
|
+
/** Per-child persona shadowing the deployment persona. */
|
|
63
|
+
readonly persona?: string | undefined;
|
|
64
|
+
/** Per-child tool scoping. */
|
|
65
|
+
readonly toolFilter?: ToolRestriction | undefined;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Model-facing delegation-scope statement for every in-process child. A
|
|
69
|
+
* runtime-context contribution rather than a system-prompt section, so the
|
|
70
|
+
* deployment's system prompt stays uniform across parents and children.
|
|
71
|
+
*/
|
|
72
|
+
export declare const SUBAGENT_DELEGATION_CONTEXT: string;
|
|
73
|
+
/**
|
|
74
|
+
* Compose one child inside its creation window: join its parent's preset,
|
|
75
|
+
* register the fixed delegation-scope statement, then apply the child's own
|
|
76
|
+
* shadowing persona section and tool restriction, all owned by the child's
|
|
77
|
+
* scope and therefore invisible to its parent and siblings. Creation and cold
|
|
78
|
+
* resume both pass through here.
|
|
79
|
+
*
|
|
80
|
+
* The join comes first and the child's own registrations second, which is the
|
|
81
|
+
* order the layering already implies — the nearest scope wins a name, and a
|
|
82
|
+
* per-child restriction intersects with everything its chain admits — but
|
|
83
|
+
* stating it here keeps the two steps from being read as independent.
|
|
84
|
+
*
|
|
85
|
+
* The join and the per-child registrations live in ONE call because a child
|
|
86
|
+
* composed without the join is exactly the defect this function exists to
|
|
87
|
+
* prevent: with every model-facing row on the agent plane, a child that joins
|
|
88
|
+
* no preset sees an empty tool registry and none of its parent's prompt
|
|
89
|
+
* sections. Taking the parent as a parameter is what makes that omission
|
|
90
|
+
* unrepresentable at the call sites.
|
|
91
|
+
* @param childCtx - the child agent's scoped creation context.
|
|
92
|
+
* @param parent - the delegating parent whose composition the child joins.
|
|
93
|
+
* @param composition - the per-child persona and tool filter to install.
|
|
94
|
+
*/
|
|
95
|
+
export declare function applyChildComposition(childCtx: Context, parent: Agent, composition: ChildComposition): void;
|
|
96
|
+
/** Policy seeded onto a child session's log at the delegation boundary. */
|
|
97
|
+
export interface DelegatedPolicyOverrides {
|
|
98
|
+
/** The parent session's explicit sandbox-mode override, or `undefined` without one. */
|
|
99
|
+
readonly sandboxMode: SandboxMode | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* `'never'` whenever the approval capability is composed, `undefined`
|
|
102
|
+
* otherwise: a delegated child acts only within the sandbox scope fixed at
|
|
103
|
+
* delegation, so its asks are rejected deterministically.
|
|
104
|
+
*/
|
|
105
|
+
readonly approvalPolicy: 'never' | undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Capture the policy to seed into one delegation. Call synchronously before
|
|
109
|
+
* the child start's first await: a later parent switch belongs to the
|
|
110
|
+
* parent's future, not to this child. Only the parent session's explicit
|
|
111
|
+
* sandbox override is captured — never deployment defaults or one-shot
|
|
112
|
+
* grants — and the approval policy is pinned to `'never'` regardless of the
|
|
113
|
+
* parent's own policy.
|
|
114
|
+
* @param parent - the delegating parent agent.
|
|
115
|
+
* @returns the sandbox override (or `undefined` without one) and the approval pin.
|
|
116
|
+
*/
|
|
117
|
+
export declare function captureDelegatedPolicyOverrides(parent: Agent): DelegatedPolicyOverrides;
|
|
118
|
+
/**
|
|
119
|
+
* Append the captured delegation policy onto the child's own log as
|
|
120
|
+
* `source: 'delegation'` events inside the unpublished creation window, so the
|
|
121
|
+
* child's effective policy is reconstructable from its log alone. Appends land
|
|
122
|
+
* after any fork seed, so fresh policy wins stale seed state; later child
|
|
123
|
+
* switches still win over these events.
|
|
124
|
+
* @param childSession - the unpublished child's session.
|
|
125
|
+
* @param overrides - the policy captured at delegation.
|
|
126
|
+
*/
|
|
127
|
+
export declare function appendDelegatedPolicyOverrides(childSession: Session, overrides: DelegatedPolicyOverrides): void;
|
|
128
|
+
/** Identity and lineage inputs shared by every in-process child creation. */
|
|
129
|
+
export interface ChildCreateInputs {
|
|
130
|
+
/** The child's reserved session id. */
|
|
131
|
+
readonly sessionId: SessionId;
|
|
132
|
+
/** The delegating parent agent. */
|
|
133
|
+
readonly parent: Agent;
|
|
134
|
+
/** The resolved delegation depth. */
|
|
135
|
+
readonly childDepth: number;
|
|
136
|
+
/** How many leading seed events came from the parent's log. */
|
|
137
|
+
readonly lineageSeedLength: number;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=child-agent.d.ts.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared in-process child composition: the delegation-depth budget, the
|
|
3
|
+
* durable session metadata, the resolved child `AgentOptions`, the delegated
|
|
4
|
+
* policy seed, and the scoped setup a child agent needs. Both the one-shot
|
|
5
|
+
* provider driver and the continuation manager compose children this way, so
|
|
6
|
+
* depth accounting, lineage stamping, and delegation policy have one home.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-subagent/child-agent
|
|
9
|
+
*/
|
|
10
|
+
import { delegationDepthOf } from "./depth.js";
|
|
11
|
+
/** Thrown when starting a child would exceed the requested depth cap. */
|
|
12
|
+
export class SubagentDepthError extends Error {
|
|
13
|
+
attemptedDepth;
|
|
14
|
+
maxDepth;
|
|
15
|
+
constructor(attemptedDepth, maxDepth) {
|
|
16
|
+
super(`subagent depth ${attemptedDepth} exceeds maxDepth ${maxDepth}`);
|
|
17
|
+
this.attemptedDepth = attemptedDepth;
|
|
18
|
+
this.maxDepth = maxDepth;
|
|
19
|
+
this.name = 'SubagentDepthError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the child's delegation depth from its parent and enforce an optional
|
|
24
|
+
* cap. The persisted parent header is the monotone floor, so a resumed parent
|
|
25
|
+
* cannot delegate as if it were top-level.
|
|
26
|
+
* @param parent - the delegating parent agent.
|
|
27
|
+
* @param maxDepth - optional absolute cap the resolved depth must not exceed.
|
|
28
|
+
* @returns the child's non-negative safe-integer depth.
|
|
29
|
+
* @throws {SubagentDepthError} when the resolved depth exceeds `maxDepth`.
|
|
30
|
+
* @throws {RangeError} when the resolved depth leaves the safe-integer range.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveChildDepth(parent, maxDepth) {
|
|
33
|
+
const childDepth = delegationDepthOf(parent) + 1;
|
|
34
|
+
if (!Number.isSafeInteger(childDepth)) {
|
|
35
|
+
throw new RangeError('subagent child depth exceeds the safe-integer range');
|
|
36
|
+
}
|
|
37
|
+
if (maxDepth !== undefined && childDepth > maxDepth) {
|
|
38
|
+
throw new SubagentDepthError(childDepth, maxDepth);
|
|
39
|
+
}
|
|
40
|
+
return childDepth;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the child's `AgentOptions`: the parent's provider/model/maxTokens
|
|
44
|
+
* route unless the request overrides it, stamped with the child's own
|
|
45
|
+
* delegation depth.
|
|
46
|
+
* @param parent - the delegating parent whose route the child inherits.
|
|
47
|
+
* @param requested - per-child overrides, if any.
|
|
48
|
+
* @param childDepth - the resolved delegation depth to stamp.
|
|
49
|
+
* @returns the resolved options for `ctx.agents.create()`.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveChildAgentOptions(parent, requested, childDepth) {
|
|
52
|
+
const parentProvider = parent.options.provider;
|
|
53
|
+
const parentModel = parent.options.model;
|
|
54
|
+
const parentMaxTokens = parent.options.maxTokens;
|
|
55
|
+
return {
|
|
56
|
+
...parentProvider !== undefined ? { provider: parentProvider } : {},
|
|
57
|
+
...parentModel !== undefined ? { model: parentModel } : {},
|
|
58
|
+
...parentMaxTokens !== undefined ? { maxTokens: parentMaxTokens } : {},
|
|
59
|
+
...requested,
|
|
60
|
+
subagentDepth: childDepth,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build the child session's durable creation metadata: the parent's workspace,
|
|
65
|
+
* its direct lineage, coarse product origin, the recursion budget that must
|
|
66
|
+
* survive persistence, the seed boundary that separates inherited parent
|
|
67
|
+
* history from child work, and the composition the child runs under.
|
|
68
|
+
*
|
|
69
|
+
* The preset is read from the parent's LIVE scope chain rather than from its
|
|
70
|
+
* header, because a parent that switched preset while blank runs on the newer
|
|
71
|
+
* composition and its header still names the older one. Recording it is what
|
|
72
|
+
* makes a child's history reconstructable: without it a cold read of the child
|
|
73
|
+
* resolves the deployment default and rebuilds turns under a tool set the
|
|
74
|
+
* child never had.
|
|
75
|
+
* @param parent - the delegating parent agent.
|
|
76
|
+
* @param childDepth - the resolved delegation depth to persist.
|
|
77
|
+
* @param lineageSeedLength - how many leading events came from the parent's log.
|
|
78
|
+
* @returns the `meta` for `ctx.agents.create()`.
|
|
79
|
+
*/
|
|
80
|
+
export function childSessionMeta(parent, childDepth, lineageSeedLength) {
|
|
81
|
+
const parentHeader = parent.session.header;
|
|
82
|
+
const agentPreset = parent.ctx.get('agentPresets')?.composedPreset(parent.ctx);
|
|
83
|
+
return {
|
|
84
|
+
...parentHeader.cwd !== undefined ? { cwd: parentHeader.cwd } : {},
|
|
85
|
+
...agentPreset === undefined ? {} : { agentPreset },
|
|
86
|
+
parentSession: parentHeader.id,
|
|
87
|
+
// Navigation classification only; the descriptor remains the authority
|
|
88
|
+
// for mode and continuation capability.
|
|
89
|
+
origin: 'subagent',
|
|
90
|
+
// Durable: the recursion budget must survive persistence and resume.
|
|
91
|
+
delegationDepth: childDepth,
|
|
92
|
+
...lineageSeedLength > 0 ? { seedLength: lineageSeedLength } : {},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Model-facing delegation-scope statement for every in-process child. A
|
|
97
|
+
* runtime-context contribution rather than a system-prompt section, so the
|
|
98
|
+
* deployment's system prompt stays uniform across parents and children.
|
|
99
|
+
*/
|
|
100
|
+
export const SUBAGENT_DELEGATION_CONTEXT = 'You are a delegated subagent: your permission scope was fixed when you were started and cannot be '
|
|
101
|
+
+ 'widened from inside this session — operations that require approval are rejected automatically. '
|
|
102
|
+
+ 'When the task needs access beyond that scope, do not retry the denied operation; state the '
|
|
103
|
+
+ 'limitation in your reply so the delegating agent can handle it.';
|
|
104
|
+
/**
|
|
105
|
+
* Compose one child inside its creation window: join its parent's preset,
|
|
106
|
+
* register the fixed delegation-scope statement, then apply the child's own
|
|
107
|
+
* shadowing persona section and tool restriction, all owned by the child's
|
|
108
|
+
* scope and therefore invisible to its parent and siblings. Creation and cold
|
|
109
|
+
* resume both pass through here.
|
|
110
|
+
*
|
|
111
|
+
* The join comes first and the child's own registrations second, which is the
|
|
112
|
+
* order the layering already implies — the nearest scope wins a name, and a
|
|
113
|
+
* per-child restriction intersects with everything its chain admits — but
|
|
114
|
+
* stating it here keeps the two steps from being read as independent.
|
|
115
|
+
*
|
|
116
|
+
* The join and the per-child registrations live in ONE call because a child
|
|
117
|
+
* composed without the join is exactly the defect this function exists to
|
|
118
|
+
* prevent: with every model-facing row on the agent plane, a child that joins
|
|
119
|
+
* no preset sees an empty tool registry and none of its parent's prompt
|
|
120
|
+
* sections. Taking the parent as a parameter is what makes that omission
|
|
121
|
+
* unrepresentable at the call sites.
|
|
122
|
+
* @param childCtx - the child agent's scoped creation context.
|
|
123
|
+
* @param parent - the delegating parent whose composition the child joins.
|
|
124
|
+
* @param composition - the per-child persona and tool filter to install.
|
|
125
|
+
*/
|
|
126
|
+
export function applyChildComposition(childCtx, parent, composition) {
|
|
127
|
+
childCtx.get('agentPresets')?.composeFrom(childCtx, parent.ctx);
|
|
128
|
+
// Order 120: after the sandbox:policy (110) and approval:policy (115) sentences.
|
|
129
|
+
childCtx.systemPrompt.context({ name: 'subagent:delegation', order: 120, text: SUBAGENT_DELEGATION_CONTEXT });
|
|
130
|
+
if (composition.persona !== undefined) {
|
|
131
|
+
childCtx.systemPrompt.section({ name: 'deployment:persona', order: 0, text: composition.persona });
|
|
132
|
+
}
|
|
133
|
+
if (composition.toolFilter !== undefined)
|
|
134
|
+
childCtx.tools.restrict(composition.toolFilter);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Capture the policy to seed into one delegation. Call synchronously before
|
|
138
|
+
* the child start's first await: a later parent switch belongs to the
|
|
139
|
+
* parent's future, not to this child. Only the parent session's explicit
|
|
140
|
+
* sandbox override is captured — never deployment defaults or one-shot
|
|
141
|
+
* grants — and the approval policy is pinned to `'never'` regardless of the
|
|
142
|
+
* parent's own policy.
|
|
143
|
+
* @param parent - the delegating parent agent.
|
|
144
|
+
* @returns the sandbox override (or `undefined` without one) and the approval pin.
|
|
145
|
+
*/
|
|
146
|
+
export function captureDelegatedPolicyOverrides(parent) {
|
|
147
|
+
return {
|
|
148
|
+
sandboxMode: parent.ctx.get('sandboxPolicy')?.overrideOf(parent.session),
|
|
149
|
+
approvalPolicy: parent.ctx.get('approval') === undefined ? undefined : 'never',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Append the captured delegation policy onto the child's own log as
|
|
154
|
+
* `source: 'delegation'` events inside the unpublished creation window, so the
|
|
155
|
+
* child's effective policy is reconstructable from its log alone. Appends land
|
|
156
|
+
* after any fork seed, so fresh policy wins stale seed state; later child
|
|
157
|
+
* switches still win over these events.
|
|
158
|
+
* @param childSession - the unpublished child's session.
|
|
159
|
+
* @param overrides - the policy captured at delegation.
|
|
160
|
+
*/
|
|
161
|
+
export function appendDelegatedPolicyOverrides(childSession, overrides) {
|
|
162
|
+
if (overrides.sandboxMode !== undefined) {
|
|
163
|
+
childSession.append('sandbox/mode', { mode: overrides.sandboxMode, source: 'delegation' });
|
|
164
|
+
}
|
|
165
|
+
if (overrides.approvalPolicy !== undefined) {
|
|
166
|
+
childSession.append('approval/policy', { policy: overrides.approvalPolicy, source: 'delegation' });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=child-agent.js.map
|