@toddzheng024/dscode-bundle 0.3.0 → 0.5.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/THIRD_PARTY_NOTICES.md +15 -0
- package/cordis.patch.yml +5 -3
- package/package.json +14 -1
- package/plugins/clipboard-image/clipboard-image.swift +56 -0
- package/plugins/clipboard-image/index.mjs +70 -0
- package/plugins/code-review/git.mjs +95 -0
- package/plugins/code-review/index.mjs +88 -0
- package/plugins/dscode/index.mjs +17 -3
- package/plugins/email/cli.mjs +32 -0
- package/plugins/email/contacts.mjs +36 -0
- package/plugins/email/gmail-oauth.mjs +69 -0
- package/plugins/email/gmail-store.mjs +2 -0
- package/plugins/email/gmail.mjs +194 -0
- package/plugins/email/imap.mjs +136 -0
- package/plugins/email/inbox.d.mts +25 -0
- package/plugins/email/inbox.mjs +76 -0
- package/plugins/email/smtp.mjs +69 -0
- package/plugins/email/store.mjs +32 -0
- package/plugins/email-tools/index.mjs +44 -0
- package/plugins/session-metrics/index.mjs +6 -2
- package/plugins/session-metrics/rate.mjs +62 -0
- package/plugins/session-metrics/view.mjs +15 -9
- package/plugins/tui-tools/doctor-cli.mjs +22 -0
- package/plugins/tui-tools/doctor.mjs +141 -0
- package/plugins/tui-tools/index.mjs +14 -6
- package/plugins/ultra/policy.mjs +12 -1
- package/plugins/worktree-subagent/worktree.mjs +50 -0
- package/presets/dscode/agent.cordis.yml +7 -2
- package/vendor/deepseek/index.js +12 -2
- package/vendor/subagent/index.js +29 -9
- package/vendor/subagent-core/LICENSE +21 -0
- package/vendor/subagent-core/index.js +3232 -0
- package/vendor/subagent-core/invariant.js +76 -0
- package/vendor/subagent-core/typert.host.d.ts +3 -0
- package/vendor/subagent-core/typert.host.js +964 -0
- package/vendor/subagent-core/typert.remote-client.d.ts +27 -0
- package/vendor/subagent-core/typert.remote-client.js +160 -0
- package/vendor/subagent-core/types/assistant-output.d.ts +48 -0
- package/vendor/subagent-core/types/assistant-output.js +73 -0
- package/vendor/subagent-core/types/catalog.d.ts +77 -0
- package/vendor/subagent-core/types/catalog.js +103 -0
- package/vendor/subagent-core/types/child-agent.d.ts +150 -0
- package/vendor/subagent-core/types/child-agent.js +207 -0
- package/vendor/subagent-core/types/client.d.ts +8 -0
- package/vendor/subagent-core/types/client.js +7 -0
- package/vendor/subagent-core/types/continuation-activation.d.ts +251 -0
- package/vendor/subagent-core/types/continuation-activation.js +663 -0
- package/vendor/subagent-core/types/continuation-messages.d.ts +62 -0
- package/vendor/subagent-core/types/continuation-messages.js +102 -0
- package/vendor/subagent-core/types/continuation.d.ts +124 -0
- package/vendor/subagent-core/types/continuation.js +455 -0
- package/vendor/subagent-core/types/control-types.d.ts +146 -0
- package/vendor/subagent-core/types/control-types.js +9 -0
- package/vendor/subagent-core/types/control.d.ts +71 -0
- package/vendor/subagent-core/types/control.js +116 -0
- package/vendor/subagent-core/types/depth.d.ts +31 -0
- package/vendor/subagent-core/types/depth.js +39 -0
- package/vendor/subagent-core/types/descriptor.d.ts +144 -0
- package/vendor/subagent-core/types/descriptor.js +193 -0
- package/vendor/subagent-core/types/error.d.ts +11 -0
- package/vendor/subagent-core/types/error.js +14 -0
- package/vendor/subagent-core/types/inbox.d.ts +43 -0
- package/vendor/subagent-core/types/inbox.js +61 -0
- package/vendor/subagent-core/types/index.d.ts +316 -0
- package/vendor/subagent-core/types/index.js +511 -0
- package/vendor/subagent-core/types/internal.d.ts +58 -0
- package/vendor/subagent-core/types/internal.js +58 -0
- package/vendor/subagent-core/types/invariant.d.ts +13 -0
- package/vendor/subagent-core/types/invariant.js +91 -0
- package/vendor/subagent-core/types/lifecycle.d.ts +113 -0
- package/vendor/subagent-core/types/lifecycle.js +178 -0
- package/vendor/subagent-core/types/list-children.d.ts +65 -0
- package/vendor/subagent-core/types/list-children.js +381 -0
- package/vendor/subagent-core/types/out-of-process.d.ts +118 -0
- package/vendor/subagent-core/types/out-of-process.js +219 -0
- package/vendor/subagent-core/types/projection-types.d.ts +74 -0
- package/vendor/subagent-core/types/projection-types.js +7 -0
- package/vendor/subagent-core/types/projection.d.ts +95 -0
- package/vendor/subagent-core/types/projection.js +151 -0
- package/vendor/subagent-core/types/run-settlement.d.ts +17 -0
- package/vendor/subagent-core/types/run-settlement.js +70 -0
- package/vendor/subagent-core/types/types.d.ts +377 -0
- package/vendor/subagent-core/types/types.js +19 -0
- package/vendor/subagent-driver/LICENSE +21 -0
- package/vendor/subagent-driver/index.js +255 -0
- package/vendor/subagent-driver/types/index.d.ts +33 -0
- package/vendor/subagent-driver/types/structured.d.ts +42 -0
- package/vendor/subagent-fork/LICENSE +21 -0
- package/vendor/subagent-fork/index.js +61 -0
- package/vendor/subagent-fork/types/index.d.ts +20 -0
- package/vendor/subagent-spawn/LICENSE +21 -0
- package/vendor/subagent-spawn/index.js +45 -0
- package/vendor/subagent-spawn/types/index.d.ts +19 -0
- package/vendor/terminal/LICENSE +21 -0
- package/vendor/terminal/index.js +1013 -0
- package/vendor/terminal/types/config.d.ts +71 -0
- package/vendor/terminal/types/index.d.ts +36 -0
- package/vendor/terminal/types/sanitize.d.ts +47 -0
- package/vendor/terminal/types/session.d.ts +87 -0
- package/vendor/tui/dscode-clipboard-image/clipboard-image.swift +56 -0
- package/vendor/tui/dscode-clipboard-image/index.mjs +70 -0
- package/vendor/tui/dscode-email/cli.mjs +32 -0
- package/vendor/tui/dscode-email/contacts.mjs +36 -0
- package/vendor/tui/dscode-email/gmail-oauth.mjs +69 -0
- package/vendor/tui/dscode-email/gmail-store.mjs +2 -0
- package/vendor/tui/dscode-email/gmail.mjs +194 -0
- package/vendor/tui/dscode-email/imap.mjs +136 -0
- package/vendor/tui/dscode-email/inbox.d.mts +25 -0
- package/vendor/tui/dscode-email/inbox.mjs +76 -0
- package/vendor/tui/dscode-email/smtp.mjs +69 -0
- package/vendor/tui/dscode-email/store.mjs +32 -0
- package/vendor/tui/dscode-email.mjs +69 -0
- package/vendor/tui/index.mjs +473 -82
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-local Activation ownership for continuable subagents: admission,
|
|
3
|
+
* parent-child residency, serialized delivery, settlement, and disposal.
|
|
4
|
+
*
|
|
5
|
+
* The continuation manager owns durable request orchestration and delegates
|
|
6
|
+
* every mutable residency decision to this registry, so delivery and teardown
|
|
7
|
+
* share one child lock and one Activation map.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-subagent/continuation-activation
|
|
10
|
+
*/
|
|
11
|
+
import { errorChain } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import { appendDelegatedPolicyOverrides, applyChildComposition, } from "./child-agent.js";
|
|
13
|
+
import { createSettlementMessage } from "./continuation-messages.js";
|
|
14
|
+
import { SubagentError } from "./error.js";
|
|
15
|
+
import { SubagentInbox } from "./inbox.js";
|
|
16
|
+
/** Serialize each durable child's delivery, release, and disposal. */
|
|
17
|
+
export class ChildLock {
|
|
18
|
+
tails = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Run `operation` after every previously queued operation for `childId`.
|
|
21
|
+
* @param childId - the durable child whose operations are linearized.
|
|
22
|
+
* @param operation - the critical section to run in order.
|
|
23
|
+
* @returns the operation's own settlement.
|
|
24
|
+
*/
|
|
25
|
+
run(childId, operation) {
|
|
26
|
+
const previous = this.tails.get(childId) ?? Promise.resolve();
|
|
27
|
+
const result = previous.then(operation, operation);
|
|
28
|
+
// Absorb rejections in the chaining tail so one failed critical section
|
|
29
|
+
// cannot reject an unrelated later caller.
|
|
30
|
+
const tail = result.then(() => undefined, () => undefined);
|
|
31
|
+
this.tails.set(childId, tail);
|
|
32
|
+
void tail.then(() => {
|
|
33
|
+
if (this.tails.get(childId) === tail)
|
|
34
|
+
this.tails.delete(childId);
|
|
35
|
+
});
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Own the complete process-local lifetime of continuable child Activations. */
|
|
40
|
+
export class ContinuableActivationRegistry {
|
|
41
|
+
ctx;
|
|
42
|
+
observeActivation;
|
|
43
|
+
/** Child session id → its live Activation. Process-local, never durable. */
|
|
44
|
+
resident = new Map();
|
|
45
|
+
/** Materializations admitted before drain, tracked through publication or rollback. */
|
|
46
|
+
materializations = new Set();
|
|
47
|
+
/** Per-child serializer shared by delivery, release, and disposal. */
|
|
48
|
+
locks = new ChildLock();
|
|
49
|
+
/** Structural Cordis owner of every Activation handle. */
|
|
50
|
+
ownerCtx;
|
|
51
|
+
/**
|
|
52
|
+
* Exact roots whose host teardown has begun, with the live lineage members
|
|
53
|
+
* observed under each root. Entries remain until that exact root leaves the
|
|
54
|
+
* Agent registry, closing admission throughout its host's teardown without
|
|
55
|
+
* poisoning a later same-id replacement.
|
|
56
|
+
*/
|
|
57
|
+
closingScopes = new Map();
|
|
58
|
+
draining = false;
|
|
59
|
+
/**
|
|
60
|
+
* Build one registry inside the service's Agent-injected context.
|
|
61
|
+
* @param ctx - context providing Agents, Sessions, and teardown ownership.
|
|
62
|
+
* @param observeActivation - build the lifecycle observer for one residency epoch.
|
|
63
|
+
*/
|
|
64
|
+
constructor(ctx, observeActivation) {
|
|
65
|
+
this.ctx = ctx;
|
|
66
|
+
this.observeActivation = observeActivation;
|
|
67
|
+
// Ordinary Cordis owner effects unwind in reverse registration order, which
|
|
68
|
+
// cannot express the dynamic child graph. Register the private scope's
|
|
69
|
+
// structural disposer FIRST and the drain SECOND, so reverse unwind invokes
|
|
70
|
+
// the drain before releasing the scope; a cleanup effect on the same scope
|
|
71
|
+
// as the Agent handles would let structural handle disposal bypass
|
|
72
|
+
// child-first ordering.
|
|
73
|
+
const scope = ctx.plugin(function activationOwner() { });
|
|
74
|
+
this.ownerCtx = scope.ctx;
|
|
75
|
+
ctx.on('agent/disposed', ({ agent }) => {
|
|
76
|
+
this.closingScopes.delete(agent);
|
|
77
|
+
});
|
|
78
|
+
ctx.effect(function* () {
|
|
79
|
+
yield scope.dispose;
|
|
80
|
+
yield () => this.drain();
|
|
81
|
+
}.bind(this), 'subagents.continuations()');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Return the live Activation for a durable child id, if resident.
|
|
85
|
+
* @param childId - durable child session id to look up.
|
|
86
|
+
* @returns the process-local Activation, or `undefined` when it is not resident.
|
|
87
|
+
*/
|
|
88
|
+
get(childId) {
|
|
89
|
+
return this.resident.get(childId);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Reject one child identity already owned by a live Agent or Session.
|
|
93
|
+
* @param childId - proposed durable child session id.
|
|
94
|
+
*/
|
|
95
|
+
assertChildIdAvailable(childId) {
|
|
96
|
+
if (this.ctx.agents.get(childId) !== undefined || this.ctx.get('sessions')?.get(childId) !== undefined) {
|
|
97
|
+
throw new SubagentError(`subagent "${childId}" already exists`, 'DUPLICATE_CHILD');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
102
|
+
* parent cannot settle while a caller is still establishing or resuming that
|
|
103
|
+
* child. Returns a releaser for the failure path; it removes only a hold
|
|
104
|
+
* this call added, and leaves ownership in place once a live Activation for
|
|
105
|
+
* the child exists.
|
|
106
|
+
* @param parent - the live direct parent the operation is admitted under.
|
|
107
|
+
* @param childId - the durable child the operation addresses.
|
|
108
|
+
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
109
|
+
*/
|
|
110
|
+
holdOwnership(parent, childId) {
|
|
111
|
+
const parentActivation = this.resident.get(parent.id);
|
|
112
|
+
if (parentActivation === undefined || parentActivation.handle.agent !== parent)
|
|
113
|
+
return () => { };
|
|
114
|
+
if (parentActivation.inbox.closing !== undefined) {
|
|
115
|
+
throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, 'ACTIVATION_CLOSING');
|
|
116
|
+
}
|
|
117
|
+
if (parentActivation.ownedChildren.has(childId))
|
|
118
|
+
return () => { };
|
|
119
|
+
parentActivation.ownedChildren.add(childId);
|
|
120
|
+
return () => {
|
|
121
|
+
const live = this.resident.get(childId);
|
|
122
|
+
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
123
|
+
* between this operation's failure and its releaser running, which no test can schedule
|
|
124
|
+
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
125
|
+
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
126
|
+
if (live !== undefined && live.inbox.closing === undefined)
|
|
127
|
+
return;
|
|
128
|
+
if (parentActivation.ownedChildren.delete(childId))
|
|
129
|
+
this.wake(parentActivation);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
134
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
135
|
+
* @param authority - the human parent address or exact live ancestor Agent.
|
|
136
|
+
*/
|
|
137
|
+
interrupt(targetSessionId, authority) {
|
|
138
|
+
if (authority.kind === 'ancestor') {
|
|
139
|
+
const caller = authority.agent;
|
|
140
|
+
if (this.ctx.agents.get(caller.id) !== caller) {
|
|
141
|
+
throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, 'UNAUTHORIZED');
|
|
142
|
+
}
|
|
143
|
+
if (caller.id === targetSessionId) {
|
|
144
|
+
throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, 'UNAUTHORIZED');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const activation = this.resident.get(targetSessionId);
|
|
148
|
+
if (activation === undefined)
|
|
149
|
+
return;
|
|
150
|
+
if (authority.kind === 'user') {
|
|
151
|
+
if (activation.handle.agent.session.header.parentSession !== authority.parentSessionId) {
|
|
152
|
+
throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, 'UNAUTHORIZED');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (!activation.ancestry.has(authority.agent)) {
|
|
156
|
+
throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, 'UNAUTHORIZED');
|
|
157
|
+
}
|
|
158
|
+
// Disposal already stopped the target with a whole-Activation teardown;
|
|
159
|
+
// a second cancel would be a redundant signal on a closing handle.
|
|
160
|
+
if (activation.inbox.closing !== undefined)
|
|
161
|
+
return;
|
|
162
|
+
activation.handle.agent.cancel(authority.kind === 'user' ? { kind: 'user' } : { kind: 'parent' }, { keepInbox: true });
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Send through a receiving parent's Activation inbox when it has one.
|
|
166
|
+
* @param parent - exact live Agent receiving the message.
|
|
167
|
+
* @param message - durable user message to deliver.
|
|
168
|
+
* @param delivery - receiving inbox destination.
|
|
169
|
+
*/
|
|
170
|
+
sendWaking(parent, message, delivery) {
|
|
171
|
+
const parentActivation = this.resident.get(parent.id);
|
|
172
|
+
if (parentActivation !== undefined && parentActivation.handle.agent === parent) {
|
|
173
|
+
try {
|
|
174
|
+
parentActivation.inbox.deliver(message, delivery);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
this.wake(parentActivation);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (delivery === 'steer')
|
|
182
|
+
parent.steer(message);
|
|
183
|
+
else
|
|
184
|
+
parent.followup(message);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Close admission, await every already-admitted materialization through
|
|
188
|
+
* publication or rollback, then dispose the stable live Activation graph
|
|
189
|
+
* child-first.
|
|
190
|
+
*/
|
|
191
|
+
async drain() {
|
|
192
|
+
this.draining = true;
|
|
193
|
+
await Promise.all([...this.materializations].map(materialization => materialization.settled));
|
|
194
|
+
const owned = new Set();
|
|
195
|
+
for (const activation of this.resident.values()) {
|
|
196
|
+
for (const child of activation.ownedChildren)
|
|
197
|
+
owned.add(child);
|
|
198
|
+
}
|
|
199
|
+
const roots = [...this.resident.values()].filter(activation => !owned.has(activation.childId));
|
|
200
|
+
await this.disposeRoots(roots, 'activation(s)');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Stop only the continuable descendants of exact live host-owned parents.
|
|
204
|
+
* @param parents - exact live roots whose continuable descendants must stop.
|
|
205
|
+
*/
|
|
206
|
+
async drainDescendants(parents) {
|
|
207
|
+
const roots = new Set(parents.filter(parent => this.ctx.agents.get(parent.id) === parent));
|
|
208
|
+
if (roots.size === 0)
|
|
209
|
+
return;
|
|
210
|
+
for (const root of roots) {
|
|
211
|
+
this.closingMembers(root).add(root);
|
|
212
|
+
}
|
|
213
|
+
const targets = [];
|
|
214
|
+
for (const activation of this.resident.values()) {
|
|
215
|
+
const lineage = this.liveLineage(activation.handle.agent);
|
|
216
|
+
const owners = [...roots].filter(root => activation.handle.agent !== root
|
|
217
|
+
&& activation.ancestry.has(root));
|
|
218
|
+
if (owners.length === 0)
|
|
219
|
+
continue;
|
|
220
|
+
targets.push(activation);
|
|
221
|
+
for (const owner of owners) {
|
|
222
|
+
const members = this.closingMembers(owner);
|
|
223
|
+
members.add(activation.handle.agent);
|
|
224
|
+
for (const agent of lineage)
|
|
225
|
+
members.add(agent);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const materializations = [...this.materializations].filter((materialization) => {
|
|
229
|
+
const owners = [...roots].filter(root => materialization.lineage.includes(root));
|
|
230
|
+
for (const owner of owners) {
|
|
231
|
+
const members = this.closingMembers(owner);
|
|
232
|
+
for (const agent of materialization.lineage)
|
|
233
|
+
members.add(agent);
|
|
234
|
+
}
|
|
235
|
+
return owners.length > 0;
|
|
236
|
+
});
|
|
237
|
+
const ownedTargets = new Set();
|
|
238
|
+
for (const activation of targets) {
|
|
239
|
+
for (const child of activation.ownedChildren)
|
|
240
|
+
ownedTargets.add(child);
|
|
241
|
+
}
|
|
242
|
+
const targetRoots = targets.filter(activation => !ownedTargets.has(activation.childId));
|
|
243
|
+
for (const activation of targets) {
|
|
244
|
+
const disposal = this.dispose(activation);
|
|
245
|
+
void disposal.catch(() => undefined);
|
|
246
|
+
}
|
|
247
|
+
await Promise.all(materializations.map(materialization => materialization.settled));
|
|
248
|
+
await this.disposeRoots(targetRoots, 'scoped activation(s)');
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Release selected resident direct children of one exact live parent.
|
|
252
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
253
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
254
|
+
*/
|
|
255
|
+
async drainChildren(parent, childIds) {
|
|
256
|
+
if (this.ctx.agents.get(parent.id) !== parent) {
|
|
257
|
+
throw new SubagentError('selected child teardown requires the exact live parent agent', 'UNAUTHORIZED');
|
|
258
|
+
}
|
|
259
|
+
const targets = [];
|
|
260
|
+
for (const childId of new Set(childIds)) {
|
|
261
|
+
const activation = this.resident.get(childId);
|
|
262
|
+
if (activation === undefined)
|
|
263
|
+
continue;
|
|
264
|
+
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) {
|
|
265
|
+
throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, 'UNAUTHORIZED');
|
|
266
|
+
}
|
|
267
|
+
targets.push(activation);
|
|
268
|
+
}
|
|
269
|
+
for (const activation of targets) {
|
|
270
|
+
const disposal = this.dispose(activation);
|
|
271
|
+
void disposal.catch(() => undefined);
|
|
272
|
+
}
|
|
273
|
+
await this.disposeRoots(targets, 'selected activation(s)');
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Reject new admission once the registry or this exact parent tree began draining.
|
|
277
|
+
* @param agent - exact live Agent whose lineage determines admission.
|
|
278
|
+
*/
|
|
279
|
+
assertAdmitting(agent) {
|
|
280
|
+
const closing = this.closingTeardownFor(agent);
|
|
281
|
+
if (closing === undefined)
|
|
282
|
+
return;
|
|
283
|
+
throw new SubagentError(closing === 'manager'
|
|
284
|
+
? 'continuable subagents are draining; the operation was not admitted'
|
|
285
|
+
: `continuable subagents below parent "${closing.id}" are draining; the operation was not admitted`, 'DRAINING');
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Authorize one operation against the durable direct-parent lineage.
|
|
289
|
+
* @param parent - exact live Agent claiming direct-parent authority.
|
|
290
|
+
* @param childId - durable child session id addressed by the operation.
|
|
291
|
+
* @param parentSession - durable direct-parent id recorded by the child.
|
|
292
|
+
*/
|
|
293
|
+
authorizeLineage(parent, childId, parentSession) {
|
|
294
|
+
if (this.ctx.agents.get(parent.id) !== parent) {
|
|
295
|
+
throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, 'UNAUTHORIZED');
|
|
296
|
+
}
|
|
297
|
+
if (parentSession !== parent.id) {
|
|
298
|
+
throw new SubagentError(`subagent "${childId}" belongs to another parent session`, 'UNAUTHORIZED');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Create or resume one child Agent and publish its Activation.
|
|
303
|
+
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
304
|
+
* @returns the published process-local Activation.
|
|
305
|
+
*/
|
|
306
|
+
materialize(inputs) {
|
|
307
|
+
this.assertAdmitting(inputs.parent);
|
|
308
|
+
const settled = Promise.withResolvers();
|
|
309
|
+
const lineage = this.liveLineage(inputs.parent);
|
|
310
|
+
const materialization = {
|
|
311
|
+
lineage,
|
|
312
|
+
settled: settled.promise,
|
|
313
|
+
};
|
|
314
|
+
this.materializations.add(materialization);
|
|
315
|
+
return this.materializeTracked(inputs, lineage).finally(() => {
|
|
316
|
+
this.materializations.delete(materialization);
|
|
317
|
+
settled.resolve();
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Cross the final admission cutoff and submit without yielding.
|
|
322
|
+
* @param activation - the exact resident child receiving the message.
|
|
323
|
+
* @param message - the already-built durable user message.
|
|
324
|
+
* @param delivery - the Agent inbox destination.
|
|
325
|
+
* @param parent - exact live direct parent authorizing admission.
|
|
326
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
327
|
+
* @returns the accepted durable message id.
|
|
328
|
+
*/
|
|
329
|
+
submitAdmitted(activation, message, delivery, parent, signal) {
|
|
330
|
+
signal.throwIfAborted();
|
|
331
|
+
this.assertAdmitting(parent);
|
|
332
|
+
this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
|
|
333
|
+
this.acquireOwnership(parent, activation.childId);
|
|
334
|
+
try {
|
|
335
|
+
activation.inbox.deliver(message, delivery);
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
this.wake(activation);
|
|
339
|
+
}
|
|
340
|
+
return message.id;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Stop and release one Activation through its memoized close transaction.
|
|
344
|
+
* @param activation - exact residency epoch to close.
|
|
345
|
+
* @param finalStateFlushed - whether natural settlement already flushed final state.
|
|
346
|
+
* @returns the shared close transaction.
|
|
347
|
+
*/
|
|
348
|
+
dispose(activation, finalStateFlushed = false) {
|
|
349
|
+
return activation.inbox.close(() => this.finishDisposal(activation, finalStateFlushed));
|
|
350
|
+
}
|
|
351
|
+
/** Dispose independent roots and report every branch failure after all settle. */
|
|
352
|
+
async disposeRoots(roots, failureSubject) {
|
|
353
|
+
const failures = await Promise.all(roots.map(async (activation) => {
|
|
354
|
+
try {
|
|
355
|
+
await this.dispose(activation);
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
return error;
|
|
360
|
+
}
|
|
361
|
+
}));
|
|
362
|
+
const reasons = failures.filter(failure => failure !== undefined);
|
|
363
|
+
if (reasons.length > 0) {
|
|
364
|
+
throw new SubagentError(`continuable subagent teardown failed for ${reasons.length} ${failureSubject}: `
|
|
365
|
+
+ reasons.map(reason => errorChain(reason)).join('; '), 'ACTIVATION_TEARDOWN_FAILED');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Return the retained member set for one exact scoped-teardown root. */
|
|
369
|
+
closingMembers(root) {
|
|
370
|
+
const existing = this.closingScopes.get(root);
|
|
371
|
+
if (existing !== undefined)
|
|
372
|
+
return existing;
|
|
373
|
+
const members = new Set();
|
|
374
|
+
this.closingScopes.set(root, members);
|
|
375
|
+
return members;
|
|
376
|
+
}
|
|
377
|
+
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
378
|
+
liveLineage(agent) {
|
|
379
|
+
const lineage = [agent];
|
|
380
|
+
const seen = new Set([agent.id]);
|
|
381
|
+
let parentSession = agent.session.header.parentSession;
|
|
382
|
+
while (parentSession !== undefined) {
|
|
383
|
+
const parent = this.ctx.agents.get(parentSession);
|
|
384
|
+
if (parent === undefined || seen.has(parent.id))
|
|
385
|
+
break;
|
|
386
|
+
lineage.push(parent);
|
|
387
|
+
seen.add(parent.id);
|
|
388
|
+
parentSession = parent.session.header.parentSession;
|
|
389
|
+
}
|
|
390
|
+
return lineage;
|
|
391
|
+
}
|
|
392
|
+
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
393
|
+
closingTeardownFor(agent) {
|
|
394
|
+
if (this.draining)
|
|
395
|
+
return 'manager';
|
|
396
|
+
const lineage = this.liveLineage(agent);
|
|
397
|
+
for (const [root, members] of this.closingScopes) {
|
|
398
|
+
if (members.has(agent) || lineage.includes(root))
|
|
399
|
+
return root;
|
|
400
|
+
}
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
/** Perform one tracked materialization through publication or rollback. */
|
|
404
|
+
async materializeTracked(inputs, parentLineage) {
|
|
405
|
+
const { childId, provider, parent, create } = inputs;
|
|
406
|
+
inputs.signal.throwIfAborted();
|
|
407
|
+
const setup = (childCtx, child) => {
|
|
408
|
+
// Only fresh creation appends the descriptor and delegated policy after
|
|
409
|
+
// the inherited marker; a cold resume replays those persisted events.
|
|
410
|
+
if (create !== undefined) {
|
|
411
|
+
child.session.append('subagent/descriptor', create.descriptor);
|
|
412
|
+
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
413
|
+
}
|
|
414
|
+
applyChildComposition(childCtx, parent, inputs.composition);
|
|
415
|
+
};
|
|
416
|
+
const observer = this.observeActivation(provider, childId, parent);
|
|
417
|
+
const handle = create === undefined
|
|
418
|
+
? await this.ownerCtx.agents.resume({
|
|
419
|
+
resumeSessionId: childId,
|
|
420
|
+
parentAgent: parent,
|
|
421
|
+
agentOptions: inputs.agentOptions,
|
|
422
|
+
signal: inputs.signal,
|
|
423
|
+
setup,
|
|
424
|
+
})
|
|
425
|
+
: await this.ownerCtx.agents.create({
|
|
426
|
+
sessionId: childId,
|
|
427
|
+
parentAgent: parent,
|
|
428
|
+
meta: create.meta,
|
|
429
|
+
...(create.seed === undefined ? {} : { seed: create.seed }),
|
|
430
|
+
inheritedEventCount: create.inheritedEventCount,
|
|
431
|
+
agentOptions: inputs.agentOptions,
|
|
432
|
+
signal: inputs.signal,
|
|
433
|
+
setup,
|
|
434
|
+
});
|
|
435
|
+
const activation = {
|
|
436
|
+
childId,
|
|
437
|
+
parentSession: parent.id,
|
|
438
|
+
provider,
|
|
439
|
+
handle,
|
|
440
|
+
inbox: new SubagentInbox(handle.agent),
|
|
441
|
+
ancestry: new WeakSet([handle.agent, ...parentLineage]),
|
|
442
|
+
ownedChildren: new Set(),
|
|
443
|
+
observer,
|
|
444
|
+
announced: false,
|
|
445
|
+
poke: Promise.withResolvers(),
|
|
446
|
+
};
|
|
447
|
+
this.resident.set(childId, activation);
|
|
448
|
+
try {
|
|
449
|
+
inputs.signal.throwIfAborted();
|
|
450
|
+
this.assertAdmitting(parent);
|
|
451
|
+
this.acquireOwnership(parent, childId);
|
|
452
|
+
const wakeOnInboxRemoval = () => { this.wake(activation); };
|
|
453
|
+
handle.agent.ctx.on('agent/inbox/claimed', wakeOnInboxRemoval);
|
|
454
|
+
handle.agent.ctx.on('agent/inbox/discarded', wakeOnInboxRemoval);
|
|
455
|
+
observer.start(handle.agent);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
459
|
+
* that prevented this operation from returning an accepted message id. */
|
|
460
|
+
await this.rollbackUnpublished(activation).catch(() => undefined);
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
this.watchSettlement(activation);
|
|
464
|
+
return activation;
|
|
465
|
+
}
|
|
466
|
+
/** Release an Activation whose start edge was not published. */
|
|
467
|
+
rollbackUnpublished(activation) {
|
|
468
|
+
return activation.inbox.close(async () => {
|
|
469
|
+
try {
|
|
470
|
+
await activation.handle.dispose();
|
|
471
|
+
}
|
|
472
|
+
finally {
|
|
473
|
+
this.resident.delete(activation.childId);
|
|
474
|
+
this.releaseOwnership(activation.childId);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/** Register the child in a continuation-managed parent's owned set. */
|
|
479
|
+
acquireOwnership(parent, childId) {
|
|
480
|
+
const parentActivation = this.resident.get(parent.id);
|
|
481
|
+
if (parentActivation === undefined)
|
|
482
|
+
return;
|
|
483
|
+
if (parentActivation.inbox.closing !== undefined) {
|
|
484
|
+
throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, 'ACTIVATION_CLOSING');
|
|
485
|
+
}
|
|
486
|
+
parentActivation.ownedChildren.add(childId);
|
|
487
|
+
}
|
|
488
|
+
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
489
|
+
releaseOwnership(childId) {
|
|
490
|
+
for (const candidate of this.resident.values()) {
|
|
491
|
+
if (candidate.ownedChildren.delete(childId))
|
|
492
|
+
this.wake(candidate);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
496
|
+
wake(activation) {
|
|
497
|
+
activation.poke.resolve();
|
|
498
|
+
activation.poke = Promise.withResolvers();
|
|
499
|
+
}
|
|
500
|
+
/** Follow one Activation to natural settlement. */
|
|
501
|
+
watchSettlement(activation) {
|
|
502
|
+
void (async () => {
|
|
503
|
+
while (true) {
|
|
504
|
+
const idleObservation = activation.poke;
|
|
505
|
+
await activation.handle.agent.whenIdle();
|
|
506
|
+
if (activation.inbox.closing !== undefined)
|
|
507
|
+
return;
|
|
508
|
+
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
509
|
+
if (readiness === 'closed')
|
|
510
|
+
return;
|
|
511
|
+
if (readiness === 'retry')
|
|
512
|
+
continue;
|
|
513
|
+
if (readiness === 'wait') {
|
|
514
|
+
await idleObservation.promise;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const finalSeq = activation.handle.agent.session.seq;
|
|
518
|
+
await this.flushFinalState(activation);
|
|
519
|
+
const attempt = await this.locks.run(activation.childId, () => {
|
|
520
|
+
const state = this.settlementState(activation, idleObservation);
|
|
521
|
+
if (state !== 'ready')
|
|
522
|
+
return Promise.resolve(state);
|
|
523
|
+
if (activation.handle.agent.session.seq !== finalSeq) {
|
|
524
|
+
return Promise.resolve('retry');
|
|
525
|
+
}
|
|
526
|
+
// The task starts synchronously, so idle ownership and Inbox closure share one turn.
|
|
527
|
+
let done;
|
|
528
|
+
try {
|
|
529
|
+
void activation.handle.agent.runMaintenance(() => {
|
|
530
|
+
done = this.dispose(activation, true);
|
|
531
|
+
return Promise.resolve();
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
// Another activity won the idle phase after the preceding observation.
|
|
536
|
+
return Promise.resolve('retry');
|
|
537
|
+
}
|
|
538
|
+
return Promise.resolve({ done });
|
|
539
|
+
});
|
|
540
|
+
if (attempt === 'closed')
|
|
541
|
+
return;
|
|
542
|
+
if (attempt === 'retry')
|
|
543
|
+
continue;
|
|
544
|
+
if (attempt === 'wait') {
|
|
545
|
+
await idleObservation.promise;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
await attempt.done;
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${errorChain(error)}`);
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
})();
|
|
557
|
+
}
|
|
558
|
+
/** Classify one Inbox and owned-child observation without reading Agent execution state. */
|
|
559
|
+
settlementState(activation, observation) {
|
|
560
|
+
if (activation.inbox.closing !== undefined)
|
|
561
|
+
return 'closed';
|
|
562
|
+
if (activation.poke !== observation)
|
|
563
|
+
return 'retry';
|
|
564
|
+
if (activation.inbox.hasPending || activation.ownedChildren.size > 0)
|
|
565
|
+
return 'wait';
|
|
566
|
+
return 'ready';
|
|
567
|
+
}
|
|
568
|
+
/** Propagate stop synchronously, then finish the child-first release. */
|
|
569
|
+
async finishDisposal(activation, finalStateFlushed) {
|
|
570
|
+
this.wake(activation);
|
|
571
|
+
const { childId } = activation;
|
|
572
|
+
const failures = [];
|
|
573
|
+
if (finalStateFlushed) {
|
|
574
|
+
try {
|
|
575
|
+
activation.observer.capture(activation.handle.agent);
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, 'ACTIVATION_TEARDOWN_FAILED', { cause: error }));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
activation.handle.agent.cancel({ kind: 'parent' });
|
|
583
|
+
const idle = activation.handle.agent.whenIdle();
|
|
584
|
+
const children = [...activation.ownedChildren]
|
|
585
|
+
.map(child => this.resident.get(child))
|
|
586
|
+
.filter((child) => child !== undefined);
|
|
587
|
+
const childDisposals = children.map(child => this.dispose(child));
|
|
588
|
+
try {
|
|
589
|
+
const childFailures = await Promise.all(childDisposals.map(async (disposal) => {
|
|
590
|
+
try {
|
|
591
|
+
await disposal;
|
|
592
|
+
return undefined;
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
return error;
|
|
596
|
+
}
|
|
597
|
+
}));
|
|
598
|
+
const reasons = childFailures.filter(reason => reason !== undefined);
|
|
599
|
+
if (reasons.length > 0) {
|
|
600
|
+
failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map(reason => errorChain(reason)).join('; ')}`, 'ACTIVATION_TEARDOWN_FAILED'));
|
|
601
|
+
}
|
|
602
|
+
await idle;
|
|
603
|
+
await this.flushFinalState(activation);
|
|
604
|
+
activation.observer.capture(activation.handle.agent);
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, 'ACTIVATION_TEARDOWN_FAILED', { cause: error }));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
await activation.handle.dispose();
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${errorChain(error)}`, 'ACTIVATION_TEARDOWN_FAILED', { cause: error }));
|
|
615
|
+
}
|
|
616
|
+
let failure;
|
|
617
|
+
if (failures.length === 1) {
|
|
618
|
+
failure = failures[0];
|
|
619
|
+
}
|
|
620
|
+
else if (failures.length > 1) {
|
|
621
|
+
failure = new SubagentError(`subagent "${childId}" activation teardown failed at ${failures.length} boundaries: `
|
|
622
|
+
+ failures.map(item => errorChain(item)).join('; '), 'ACTIVATION_TEARDOWN_FAILED', { cause: new AggregateError(failures) });
|
|
623
|
+
}
|
|
624
|
+
this.resident.delete(childId);
|
|
625
|
+
this.notifySettlement(activation, activation.observer.terminal(failure));
|
|
626
|
+
this.releaseOwnership(childId);
|
|
627
|
+
activation.observer.settle(failure);
|
|
628
|
+
if (failure !== undefined)
|
|
629
|
+
throw failure;
|
|
630
|
+
}
|
|
631
|
+
/** Tell the durable direct parent how this Activation ended. */
|
|
632
|
+
notifySettlement(activation, terminal) {
|
|
633
|
+
if (!activation.announced)
|
|
634
|
+
return;
|
|
635
|
+
try {
|
|
636
|
+
const parent = this.ctx.agents.get(activation.parentSession);
|
|
637
|
+
if (parent === undefined)
|
|
638
|
+
return;
|
|
639
|
+
const message = createSettlementMessage(activation.childId, terminal);
|
|
640
|
+
if (this.closingTeardownFor(parent) !== undefined) {
|
|
641
|
+
parent.inject(message);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
this.sendWaking(parent, message, parent.status === 'idle' ? 'queue' : 'steer');
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: `
|
|
648
|
+
+ errorChain(error));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
652
|
+
async flushFinalState(activation) {
|
|
653
|
+
const child = activation.handle.agent;
|
|
654
|
+
try {
|
|
655
|
+
await child.ctx.sessions.flush(child.session);
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" best-effort final session flush failed; `
|
|
659
|
+
+ `the persisted state may be unavailable or stale on resume: ${errorChain(error)}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
//# sourceMappingURL=continuation-activation.js.map
|