@kingsoftcloud/agentkit-harness-node 0.0.0-stage → 0.0.2
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 +201 -0
- package/NOTICE +1 -0
- package/README.md +124 -2
- package/THIRD_PARTY_NOTICES.md +66 -0
- package/ZCode-LICENSE +202 -0
- package/dist/index.mjs +4871 -0
- package/dist/native-command-plan.LICENSE.txt +221 -0
- package/dist/native-command-plan.json +16 -0
- package/dist/native-command-plan.mjs +8 -0
- package/dist/rpc-entry.mjs +5682 -0
- package/package.json +169 -4
- package/src/native/a2a-receipt-source.mjs +19 -0
- package/src/native/a2a-tools.mjs +517 -0
- package/src/native/activity-status.mjs +30 -0
- package/src/native/artifact-codec.mjs +481 -0
- package/src/native/artifact-store.mjs +1162 -0
- package/src/native/assistant-stream.mjs +76 -0
- package/src/native/background-command-port.mjs +95 -0
- package/src/native/browser-action-observation.mjs +43 -0
- package/src/native/browser-availability.mjs +22 -0
- package/src/native/browser-canvas-metrics.mjs +71 -0
- package/src/native/browser-dom.mjs +193 -0
- package/src/native/browser-playwright.mjs +1249 -0
- package/src/native/browser-vision.mjs +369 -0
- package/src/native/browser-workspace-policy.mjs +22 -0
- package/src/native/browser-workspace-protocol.mjs +95 -0
- package/src/native/browser-workspace-server.mjs +164 -0
- package/src/native/browser-workspace.mjs +355 -0
- package/src/native/captured-source.mjs +3 -0
- package/src/native/child-budget-allocation.mjs +87 -0
- package/src/native/child-capabilities.mjs +35 -0
- package/src/native/child-command-resource.mjs +20 -0
- package/src/native/child-history.mjs +188 -0
- package/src/native/child-merge.mjs +98 -0
- package/src/native/child-model-surface.mjs +19 -0
- package/src/native/child-observation.mjs +100 -0
- package/src/native/child-owner.mjs +620 -0
- package/src/native/child-publication.mjs +76 -0
- package/src/native/child-readers.mjs +366 -0
- package/src/native/child-tools.mjs +46 -0
- package/src/native/child-workspace-binding.mjs +29 -0
- package/src/native/child-workspace-tools.mjs +1 -0
- package/src/native/child-workspaces.mjs +655 -0
- package/src/native/children.mjs +365 -0
- package/src/native/command-plan-worker.mjs +14 -0
- package/src/native/command-plan.mjs +31 -0
- package/src/native/command-progress.mjs +64 -0
- package/src/native/command-shell.mjs +9 -0
- package/src/native/command-workspace-observation.mjs +36 -0
- package/src/native/component-tools.mjs +280 -0
- package/src/native/context-counter.mjs +133 -0
- package/src/native/context-markers.mjs +51 -0
- package/src/native/context-profile.mjs +72 -0
- package/src/native/context-status.mjs +27 -0
- package/src/native/conversation-command-control.mjs +133 -0
- package/src/native/delivery-timeline.mjs +39 -0
- package/src/native/dsh-background-ownership.mjs +102 -0
- package/src/native/dsh-managed-ownership.mjs +23 -0
- package/src/native/dsh-turn-contract.mjs +3 -0
- package/src/native/dsh-turn-ownership.mjs +54 -0
- package/src/native/dsh-turn-runtime.mjs +498 -0
- package/src/native/e2b-background-supervisor.mjs +347 -0
- package/src/native/e2b-background.mjs +368 -0
- package/src/native/e2b-command-state.mjs +40 -0
- package/src/native/e2b-command-tools.mjs +486 -0
- package/src/native/e2b-sandbox-owner.mjs +227 -0
- package/src/native/hook-executor.mjs +249 -0
- package/src/native/instruction-assembly.mjs +60 -0
- package/src/native/instruction-context.mjs +54 -0
- package/src/native/local-background-supervisor.mjs +108 -0
- package/src/native/local-background.mjs +207 -0
- package/src/native/local-command-evidence.mjs +57 -0
- package/src/native/local-command-policy-worker.mjs +197 -0
- package/src/native/local-command-policy.mjs +46 -0
- package/src/native/local-command-state.mjs +177 -0
- package/src/native/local-command-tools.mjs +302 -0
- package/src/native/local-execution-profile.mjs +36 -0
- package/src/native/local-output-policy.mjs +92 -0
- package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
- package/src/native/local-process-bin/darwin-x64/launcher +0 -0
- package/src/native/local-process-bin/linux-arm64/launcher +0 -0
- package/src/native/local-process-bin/linux-x64/launcher +0 -0
- package/src/native/local-process-launcher.c +183 -0
- package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
- package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
- package/src/native/local-process-licenses/NOTICE +21 -0
- package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
- package/src/native/local-process-manifest.json +32 -0
- package/src/native/local-process.mjs +246 -0
- package/src/native/local-resource-profile.mjs +32 -0
- package/src/native/memory-candidate.mjs +121 -0
- package/src/native/memory-extraction-run.mjs +60 -0
- package/src/native/memory-extraction-tools.mjs +116 -0
- package/src/native/memory-recall.mjs +65 -0
- package/src/native/memory-store.mjs +334 -0
- package/src/native/memory-tools.mjs +329 -0
- package/src/native/model-anthropic-cache.mjs +49 -0
- package/src/native/model-anthropic-tool-search.mjs +85 -0
- package/src/native/model-anthropic.mjs +753 -0
- package/src/native/model-chat-usage.mjs +24 -0
- package/src/native/model-inline-reasoning.mjs +111 -0
- package/src/native/model-media.mjs +174 -0
- package/src/native/model-normalize.mjs +228 -0
- package/src/native/model-openai-chat.mjs +561 -0
- package/src/native/model-port.mjs +361 -0
- package/src/native/model-provider-compat.mjs +129 -0
- package/src/native/model-replay.mjs +153 -0
- package/src/native/model-request.mjs +15 -0
- package/src/native/model-responses.mjs +1205 -0
- package/src/native/model-sse.mjs +171 -0
- package/src/native/model-surface-host.mjs +27 -0
- package/src/native/model-tool-search-policy.mjs +16 -0
- package/src/native/model-transport-observation.mjs +27 -0
- package/src/native/owned-child.mjs +114 -0
- package/src/native/owned-command.mjs +106 -0
- package/src/native/python-syntax-worker.mjs +60 -0
- package/src/native/python-syntax.mjs +50 -0
- package/src/native/read-interruption.mjs +16 -0
- package/src/native/result-stream.mjs +63 -0
- package/src/native/ruff-ast.mjs +95 -0
- package/src/native/run-observation.mjs +128 -0
- package/src/native/session-delivery-journal.mjs +96 -0
- package/src/native/session-delivery.mjs +434 -0
- package/src/native/shell-analysis.mjs +50 -0
- package/src/native/skill-disclosure.mjs +404 -0
- package/src/native/skill-events.mjs +164 -0
- package/src/native/skill-tools.mjs +813 -0
- package/src/native/source-page.mjs +73 -0
- package/src/native/sqlite-session-inbox.mjs +42 -0
- package/src/native/sqlite-store.mjs +617 -0
- package/src/native/tool-authority.mjs +99 -0
- package/src/native/tool-guidance.mjs +12 -0
- package/src/native/tool-observation.mjs +75 -0
- package/src/native/tool-states.mjs +14 -0
- package/src/native/verification-authority.mjs +37 -0
- package/src/native/web-tools.mjs +643 -0
- package/src/native/workspace-checkpoints.mjs +312 -0
- package/src/native/workspace-context.mjs +61 -0
- package/src/native/workspace-display.mjs +39 -0
- package/src/native/workspace-edit-match.mjs +19 -0
- package/src/native/workspace-edit-worker.mjs +15 -0
- package/src/native/workspace-editing.mjs +447 -0
- package/src/native/workspace-image.mjs +72 -0
- package/src/native/workspace-instructions.mjs +107 -0
- package/src/native/workspace-mutation-lock.mjs +74 -0
- package/src/native/workspace-patch-worker.mjs +17 -0
- package/src/native/workspace-patch.mjs +17 -0
- package/src/native/workspace-paths.mjs +225 -0
- package/src/native/workspace-state.mjs +272 -0
- package/src/native/workspace-subject-observer.mjs +84 -0
- package/src/native/workspace-tools.mjs +1740 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { NativeKernel, NativeError, isNativeError, CHILD_OWNER_CONTRACT, canonical, copy, configure,
|
|
3
|
+
addUsage, admittedToolBudget, childLifecycleRequest, childReadyProbe, scopeKey, terminal,
|
|
4
|
+
CHILD_TASK_POLICY, COORDINATOR_RESPONSE_POLICY, activePlanMode, isPlanChildCall, planModeWriteDenial,
|
|
5
|
+
CHILD_PLAN_POLICY, CHILD_PLAN_MODE_POLICY, defaultPolicyBindings } from '@kingsoftcloud/agentkit-harness-core';
|
|
6
|
+
import { loadSessionChild, accountedChildDelivery, hasUnsafeChildReceipt, childNeedsApproval } from './owned-child.mjs';
|
|
7
|
+
import { CHILD_ALLOCATION_POLICY, CHILD_ALLOCATION_DEFAULTS, LEGACY_CALL_DEFAULTS,
|
|
8
|
+
roleAllocationDefaults, resolveChildCallLimits } from './child-budget-allocation.mjs';
|
|
9
|
+
|
|
10
|
+
export { CHILD_ALLOCATION_POLICY, CHILD_ALLOCATION_DEFAULTS, LEGACY_CALL_DEFAULTS,
|
|
11
|
+
roleAllocationDefaults, resolveChildCallLimits };
|
|
12
|
+
|
|
13
|
+
const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
|
|
14
|
+
const check = (value, code) => { if (!value) throw new NativeError(code); };
|
|
15
|
+
const same = (left, right) => canonical(left) === canonical(right);
|
|
16
|
+
const identity = value => Object.fromEntries(['scope', 'runId', 'sessionId', 'turnId', 'buildId', 'parentRunId', 'taskChainId'].map(key => [key, copy(value[key])]));
|
|
17
|
+
const locator = request => `native-child-${hash({ scope: request.scope, parentRunId: request.runId, operationKey: request.operationKey })}`;
|
|
18
|
+
|
|
19
|
+
/** Detach only this observer. Execution is owned by the child Kernel and ledger. */
|
|
20
|
+
async function observeWhileLive(operation, signal) {
|
|
21
|
+
signal.throwIfAborted();
|
|
22
|
+
let onAbort;
|
|
23
|
+
const canceled = new Promise((_, reject) => {
|
|
24
|
+
onAbort = () => reject(isNativeError(signal.reason) ? signal.reason : new NativeError('child_observation_paused'));
|
|
25
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
26
|
+
});
|
|
27
|
+
try { const result = await Promise.race([operation, canceled]); signal.throwIfAborted(); return result; }
|
|
28
|
+
finally { signal.removeEventListener('abort', onAbort); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Durable owner for background children. resolveChild/restoreChild are trusted
|
|
33
|
+
* Host bindings; they provide already admitted models/tools/workspaces, never
|
|
34
|
+
* an environment/profile fallback. This service does not advertise model tools.
|
|
35
|
+
*/
|
|
36
|
+
export function createNativeChildOwner({ scope, childScopes = [scope], store, resolveChild, restoreChild, waitForIdle, canWaitForReceipt, onStream, now = Date.now,
|
|
37
|
+
childAllocationPolicy, admitRequirements, owner = `native-children-${randomUUID()}` }) {
|
|
38
|
+
const parentScope = copy(scope), entries = new Map();
|
|
39
|
+
scopeKey(parentScope);
|
|
40
|
+
check(childAllocationPolicy === undefined || childAllocationPolicy === CHILD_ALLOCATION_POLICY, 'invalid_child_owner_allocation_policy');
|
|
41
|
+
const allocationPolicy = childAllocationPolicy;
|
|
42
|
+
const scopes = copy(childScopes);
|
|
43
|
+
check(Array.isArray(scopes) && scopes.length > 0 && scopes.every(item => {
|
|
44
|
+
scopeKey(item); return ['tenantId', 'workspaceId', 'userId'].every(key => item[key] === parentScope[key]);
|
|
45
|
+
}), 'invalid_child_owner_scopes');
|
|
46
|
+
check(store && typeof resolveChild === 'function' && typeof owner === 'string' && owner.trim(), 'invalid_child_owner');
|
|
47
|
+
check(waitForIdle === undefined && canWaitForReceipt === undefined
|
|
48
|
+
|| typeof waitForIdle === 'function' && typeof canWaitForReceipt === 'function', 'invalid_child_idle_owner');
|
|
49
|
+
const effective = record => record.snapshot.taskContract ? { ...record.configuration, contract: record.snapshot.taskContract } : record.configuration;
|
|
50
|
+
const parentRecord = request => {
|
|
51
|
+
check(same(request.scope, parentScope), 'child_parent_scope_mismatch');
|
|
52
|
+
const record = store.load(parentScope, request.runId);
|
|
53
|
+
check(same(identity(request), identity(record.snapshot)), 'child_parent_identity_mismatch');
|
|
54
|
+
return record;
|
|
55
|
+
};
|
|
56
|
+
const toolInvocation = request => {
|
|
57
|
+
const record = parentRecord(request), intent = record.intents.find(item => item.operationKey === request.operationKey);
|
|
58
|
+
const calls = intent?.payload.calls, config = effective(record);
|
|
59
|
+
check(intent?.kind === 'tool' && intent.status === 'running' && record.snapshot.pendingIntent === request.operationKey
|
|
60
|
+
&& intent.claimedFence === request.executionFence && record.lease?.fence === request.executionFence && record.lease.expiresAt > now()
|
|
61
|
+
&& Array.isArray(calls) && calls.length === 1 && same(calls[0], request.call)
|
|
62
|
+
&& same(config.tools.find(tool => tool.name === request.call.name), request.definition)
|
|
63
|
+
&& same(intent.payload.interaction, request.interaction)
|
|
64
|
+
&& same(admittedToolBudget(record.snapshot, config, 1), request.budget), 'child_start_not_owned');
|
|
65
|
+
return record;
|
|
66
|
+
};
|
|
67
|
+
const startupReceipt = (reservation, role) => ({ status: 'ok', content: { status: 'started', child_run_id: reservation.childRunId, role,
|
|
68
|
+
instruction: 'The task is owned and its budget is reserved. Findings are not available in this startup receipt.' },
|
|
69
|
+
childBudgetReservation: copy(reservation) });
|
|
70
|
+
const makeReservation = (record, childScope) => {
|
|
71
|
+
const binding = record.configuration.childOwnerBinding;
|
|
72
|
+
return { childRunId: record.snapshot.runId, childScope: copy(childScope), parentOperationKey: binding.parentOperationKey,
|
|
73
|
+
parentToolCallId: binding.parentToolCallId, childRequestDigest: binding.childRequestDigest,
|
|
74
|
+
configurationDigest: record.snapshot.configDigest, maxModelCalls: binding.maxModelCalls,
|
|
75
|
+
maxToolCalls: binding.maxToolCalls, settledUsageDigest: null,
|
|
76
|
+
...(binding.deliveryMode ? { deliveryMode: binding.deliveryMode } : {}) };
|
|
77
|
+
};
|
|
78
|
+
const attachKernel = (record, ports) => {
|
|
79
|
+
check(ports && typeof ports.model?.generate === 'function' && typeof ports.tools?.execute === 'function', 'child_ports_unavailable');
|
|
80
|
+
const binding = record.configuration.childOwnerBinding;
|
|
81
|
+
const admitted = () => {
|
|
82
|
+
const parent = store.load(binding.parent.scope, binding.parent.runId);
|
|
83
|
+
if (terminal(parent.snapshot.phase) || parent.snapshot.phase === 'finalizing' || parent.snapshot.childTerminalPhase
|
|
84
|
+
|| parent.commands.some(row => !row.consumed && ['cancel', 'policy_revoked'].includes(row.command.kind))) return false;
|
|
85
|
+
const reservation = (parent.snapshot.childBudgetReservations ?? []).find(item => item.childRunId === record.snapshot.runId);
|
|
86
|
+
if (reservation) return reservation.configurationDigest === record.snapshot.configDigest
|
|
87
|
+
&& reservation.childRequestDigest === binding.childRequestDigest && reservation.settledUsageDigest === null;
|
|
88
|
+
const start = parent.intents.find(item => item.operationKey === binding.parentOperationKey);
|
|
89
|
+
return start?.status === 'running' && parent.snapshot.pendingIntent === binding.parentOperationKey
|
|
90
|
+
&& start.claimedFence === binding.parentExecutionFence && parent.lease?.fence === binding.parentExecutionFence && parent.lease.expiresAt > now();
|
|
91
|
+
};
|
|
92
|
+
const guarded = { ...ports, model: { ...ports.model, generate: (request, emit) => admitted()
|
|
93
|
+
? ports.model.generate(request, emit) : Promise.resolve({ kind: 'policy_abort', text: '', toolCalls: [],
|
|
94
|
+
usage: { inputTokens: 0, outputTokens: 0, modelCalls: 0, toolCalls: 0, cost: 0 }, usageReported: true }) },
|
|
95
|
+
tools: { ...ports.tools, execute: (request, lifecycle) => admitted() ? ports.tools.execute(request, lifecycle)
|
|
96
|
+
: Promise.resolve({ status: 'error', content: { code: 'child_parent_authority_lost', executed: false } }) } };
|
|
97
|
+
for (const [port, method] of [['hooks', 'invoke'], ['verification', 'verify'], ['artifacts', 'write'], ['input', 'prepare'], ['context', 'readSource']]) {
|
|
98
|
+
if (typeof ports[port]?.[method] !== 'function') continue;
|
|
99
|
+
guarded[port] = { ...ports[port], [method]: (...args) => {
|
|
100
|
+
check(admitted(), 'child_parent_authority_lost'); return ports[port][method](...args);
|
|
101
|
+
} };
|
|
102
|
+
if (port === 'artifacts') for (const reader of ['read', 'search']) {
|
|
103
|
+
if (typeof ports.artifacts[reader] === 'function') guarded.artifacts[reader] = (...args) => ports.artifacts[reader](...args);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const entry = { scope: copy(record.snapshot.scope), runId: record.snapshot.runId, configuration: copy(record.configuration),
|
|
107
|
+
kernel: new NativeKernel({ store, ports: guarded, owner: `${owner}:${record.snapshot.runId}`, digest: hash, onStream }),
|
|
108
|
+
ports, admitted, idle: null, promise: null, error: null };
|
|
109
|
+
entries.set(record.snapshot.runId, entry); return entry;
|
|
110
|
+
};
|
|
111
|
+
const submit = (entry, command) => {
|
|
112
|
+
const receipt = entry.kernel.submit(entry.scope, entry.runId, command);
|
|
113
|
+
// A queued child control interrupts only this local observer. The Kernel
|
|
114
|
+
// consumes the durable command, including cancellation, on the next pass.
|
|
115
|
+
entry.idle?.abort(new NativeError('child_idle_woken'));
|
|
116
|
+
return receipt;
|
|
117
|
+
};
|
|
118
|
+
const drive = entry => {
|
|
119
|
+
if (entry.promise) return entry.promise;
|
|
120
|
+
entry.error = null;
|
|
121
|
+
entry.promise = (async () => {
|
|
122
|
+
for (;;) {
|
|
123
|
+
const snapshot = await entry.kernel.runUntilIdle(entry.scope, entry.runId);
|
|
124
|
+
if (terminal(snapshot.phase)) { entry.ports.tools.settleQuestions?.(snapshot); return; }
|
|
125
|
+
const record = store.load(entry.scope, entry.runId);
|
|
126
|
+
if (!canWaitForReceipt?.(record) || !entry.admitted()) return;
|
|
127
|
+
const idle = new AbortController(); entry.idle = idle;
|
|
128
|
+
try {
|
|
129
|
+
const command = await observeWhileLive(Promise.resolve().then(() => waitForIdle({
|
|
130
|
+
snapshot: copy(snapshot), tools: entry.ports.tools, signal: idle.signal,
|
|
131
|
+
})), idle.signal);
|
|
132
|
+
idle.signal.throwIfAborted();
|
|
133
|
+
check(entry.admitted() && same(store.load(entry.scope, entry.runId).snapshot, snapshot), 'child_idle_authority_lost');
|
|
134
|
+
if (!command) return;
|
|
135
|
+
check(submit(entry, command).status !== 'rejected', 'child_idle_command_rejected');
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (!idle.signal.aborted || idle.signal.reason?.code !== 'child_idle_woken') throw error;
|
|
138
|
+
} finally { if (entry.idle === idle) entry.idle = null; idle.abort(); }
|
|
139
|
+
}
|
|
140
|
+
})().catch(error => { entry.error = error; })
|
|
141
|
+
.finally(() => { entry.promise = null; });
|
|
142
|
+
return entry.promise;
|
|
143
|
+
};
|
|
144
|
+
const ownedChild = (request, reservation) => {
|
|
145
|
+
check(scopes.some(item => same(item, reservation.childScope)), 'child_scope_not_admitted');
|
|
146
|
+
const record = store.load(reservation.childScope, reservation.childRunId), binding = record.configuration.childOwnerBinding;
|
|
147
|
+
check(binding?.contract === CHILD_OWNER_CONTRACT && same(binding.parent, identity(request))
|
|
148
|
+
&& (binding.childAllocationPolicy ?? null) === (allocationPolicy ?? null)
|
|
149
|
+
&& binding.parentOperationKey === reservation.parentOperationKey && binding.parentToolCallId === reservation.parentToolCallId
|
|
150
|
+
&& binding.childRequestDigest === reservation.childRequestDigest && binding.maxModelCalls === reservation.maxModelCalls
|
|
151
|
+
&& binding.deliveryMode === reservation.deliveryMode
|
|
152
|
+
&& binding.maxToolCalls === reservation.maxToolCalls && record.snapshot.configDigest === reservation.configurationDigest
|
|
153
|
+
&& hash(record.configuration) === reservation.configurationDigest, 'child_owner_binding_mismatch');
|
|
154
|
+
return record;
|
|
155
|
+
};
|
|
156
|
+
const liveEntry = async (request, reservation, record) => {
|
|
157
|
+
let entry = entries.get(reservation.childRunId);
|
|
158
|
+
if (!entry && !terminal(record.snapshot.phase)) {
|
|
159
|
+
check(typeof restoreChild === 'function', 'child_owner_unavailable');
|
|
160
|
+
request.signal.throwIfAborted();
|
|
161
|
+
const ports = await observeWhileLive(Promise.resolve().then(() => restoreChild({ identity: identity(record.snapshot),
|
|
162
|
+
configuration: copy(record.configuration), binding: copy(record.configuration.childOwnerBinding) })), request.signal);
|
|
163
|
+
request.signal.throwIfAborted();
|
|
164
|
+
entry = attachKernel(record, ports);
|
|
165
|
+
}
|
|
166
|
+
return entry;
|
|
167
|
+
};
|
|
168
|
+
const unsafeReceipt = hasUnsafeChildReceipt;
|
|
169
|
+
const continuationSource = request => {
|
|
170
|
+
const args = request.call.arguments;
|
|
171
|
+
if (args.continuation_of === undefined) return null;
|
|
172
|
+
const resolved = loadSessionChild({ store, childScopes: scopes, parent: toolInvocation(request).snapshot,
|
|
173
|
+
childRunId: args.continuation_of });
|
|
174
|
+
check(resolved, 'child_followup_not_available');
|
|
175
|
+
check(resolved.child.configuration.childOwnerBinding.role === args.role, 'child_followup_role_mismatch');
|
|
176
|
+
check(accountedChildDelivery(resolved.parent, resolved.child), 'child_followup_requires_result_collection');
|
|
177
|
+
return resolved;
|
|
178
|
+
};
|
|
179
|
+
const responses = (record, cursors) => record.configuration.coordinatorResponsePolicy === COORDINATOR_RESPONSE_POLICY
|
|
180
|
+
? record.events.filter(event => event.kind === 'coordinator.response_queued'
|
|
181
|
+
&& event.seq > (cursors?.[record.snapshot.runId] ?? 0)).map(event => ({ childRunId: record.snapshot.runId,
|
|
182
|
+
sequence: event.seq, ...copy(event.payload) })) : [];
|
|
183
|
+
const terminalReceipt = (record, reservation, includeResponses = true) => {
|
|
184
|
+
check(terminal(record.snapshot.phase) && record.snapshot.phase !== 'interrupted' && !unsafeReceipt(record), 'child_receipt_unavailable');
|
|
185
|
+
const parent = store.load(record.configuration.childOwnerBinding.parent.scope, record.configuration.childOwnerBinding.parent.runId);
|
|
186
|
+
return { status: record.snapshot.phase === 'completed' ? 'ok' : 'error', content: {
|
|
187
|
+
child_run_id: record.snapshot.runId, status: record.snapshot.phase, outcome: record.snapshot.outcome,
|
|
188
|
+
reason: record.snapshot.reason, result: record.snapshot.finalText, parent_acceptance: 'not_evaluated' },
|
|
189
|
+
resourceUsage: addUsage(record.snapshot.usage, record.snapshot.descendantUsage),
|
|
190
|
+
...(includeResponses && parent.configuration.childTaskPolicy === CHILD_TASK_POLICY
|
|
191
|
+
? { coordinatorResponses: responses(record, parent.snapshot.childResponseCursors) } : {}),
|
|
192
|
+
childRunIds: [record.snapshot.runId, ...(record.snapshot.childRunIds ?? [])],
|
|
193
|
+
childBudgetSettlement: { childRunId: record.snapshot.runId, parentOperationKey: reservation.parentOperationKey,
|
|
194
|
+
childRequestDigest: reservation.childRequestDigest, terminalSnapshotDigest: hash(record.snapshot) },
|
|
195
|
+
metadata: { usageReported: record.snapshot.diagnostics.providerUsageIncomplete !== true
|
|
196
|
+
&& record.snapshot.diagnostics.descendantUsageIncomplete !== true
|
|
197
|
+
&& !record.receipts.some(receipt => receipt.status === 'uncertain') } };
|
|
198
|
+
};
|
|
199
|
+
const childApproval = (record, reservation) => {
|
|
200
|
+
const pending = record.snapshot.pendingInteraction;
|
|
201
|
+
const call = record.snapshot.pendingTools.find(item => item.callId === pending?.callId);
|
|
202
|
+
const definition = record.configuration.tools.find(item => item.name === call?.name);
|
|
203
|
+
check(pending && call && definition, 'child_approval_unavailable');
|
|
204
|
+
return { childRunId: record.snapshot.runId, childScope: copy(record.snapshot.scope), parentOperationKey: reservation.parentOperationKey,
|
|
205
|
+
parentToolCallId: reservation.parentToolCallId, interactionId: pending.interactionId, hostInteractionId: pending.hostInteractionId,
|
|
206
|
+
toolCall: copy(call), definitionDigest: hash(definition) };
|
|
207
|
+
};
|
|
208
|
+
const foregroundResult = async (request, reservation) => {
|
|
209
|
+
let record = ownedChild(request, reservation);
|
|
210
|
+
const entry = await liveEntry(request, reservation, record);
|
|
211
|
+
const startup = () => startupReceipt(reservation, record.configuration.childOwnerBinding.role);
|
|
212
|
+
const cancel = () => {
|
|
213
|
+
if (entry && !terminal(store.load(entry.scope, entry.runId).snapshot.phase)) {
|
|
214
|
+
submit(entry, { commandId: `foreground-cancel-${hash({ parent: request.runId,
|
|
215
|
+
operationKey: request.operationKey })}`, kind: 'cancel', payload: {} });
|
|
216
|
+
drive(entry);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
request.signal.addEventListener('abort', cancel, { once: true });
|
|
220
|
+
try {
|
|
221
|
+
if (request.signal.aborted) cancel();
|
|
222
|
+
request.signal.throwIfAborted();
|
|
223
|
+
toolInvocation(request);
|
|
224
|
+
if (entry && !terminal(record.snapshot.phase)) drive(entry);
|
|
225
|
+
while (!terminal(record.snapshot.phase) && !childNeedsApproval(record)) {
|
|
226
|
+
request.signal.throwIfAborted(); const parent = toolInvocation(request);
|
|
227
|
+
if (parent.commands.some(row => !row.consumed && ['pause', 'promote_child', 'message_child', 'cancel_child'].includes(row.command.kind))
|
|
228
|
+
|| entry?.error && entry.error.code !== 'lease_busy') return startup();
|
|
229
|
+
await observeWhileLive(new Promise(resolve => setTimeout(resolve, 25)), request.signal);
|
|
230
|
+
record = ownedChild(request, reservation);
|
|
231
|
+
if (entry?.error?.code === 'lease_busy') drive(entry);
|
|
232
|
+
}
|
|
233
|
+
request.signal.throwIfAborted(); toolInvocation(request);
|
|
234
|
+
if (childNeedsApproval(record) || record.snapshot.phase === 'interrupted' || unsafeReceipt(record)) return startup();
|
|
235
|
+
return { ...terminalReceipt(record, reservation), childBudgetReservation: copy(reservation) };
|
|
236
|
+
} finally { request.signal.removeEventListener('abort', cancel); }
|
|
237
|
+
};
|
|
238
|
+
const projection = (record, reservation) => ({ runId: record.snapshot.runId, sessionId: record.snapshot.sessionId,
|
|
239
|
+
operationKey: reservation.parentOperationKey, callId: reservation.parentToolCallId,
|
|
240
|
+
state: unsafeReceipt(record) && !canWaitForReceipt?.(record) ? 'interrupted' : record.snapshot.phase === 'created' ? 'prepared' : childNeedsApproval(record) ? 'awaiting_interaction'
|
|
241
|
+
: terminal(record.snapshot.phase) ? record.snapshot.phase : 'running',
|
|
242
|
+
reservedModelCalls: reservation.maxModelCalls, reservedToolCalls: reservation.maxToolCalls,
|
|
243
|
+
usage: addUsage(record.snapshot.usage, record.snapshot.descendantUsage), notified: false, metadata: {} });
|
|
244
|
+
|
|
245
|
+
return Object.freeze({
|
|
246
|
+
contract: CHILD_OWNER_CONTRACT,
|
|
247
|
+
async admitPlan(request) {
|
|
248
|
+
try {
|
|
249
|
+
request.signal.throwIfAborted();
|
|
250
|
+
const parent = toolInvocation(request);
|
|
251
|
+
if (!activePlanMode(parent.snapshot, effective(parent)) || !isPlanChildCall(effective(parent), request.call, request.definition)) return false;
|
|
252
|
+
if (request.definition.childBudgetPolicy === 'settle/v1') {
|
|
253
|
+
const reservation = parent.snapshot.childBudgetReservations.find(item => item.childRunId === request.call.arguments.child_run_id);
|
|
254
|
+
if (!reservation) return false;
|
|
255
|
+
ownedChild(request, reservation);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
} catch { return false; }
|
|
259
|
+
},
|
|
260
|
+
tasks(request) {
|
|
261
|
+
const parent = parentRecord(request), snapshot = parent.snapshot;
|
|
262
|
+
const rows = [];
|
|
263
|
+
for (const childScope of scopes) for (const child of store.list(childScope)) {
|
|
264
|
+
const record = store.load(childScope, child.runId), binding = record.configuration.childOwnerBinding;
|
|
265
|
+
if (!binding || !same(binding.parent, identity(request))) continue;
|
|
266
|
+
let reservation = snapshot.childBudgetReservations.find(item => item.childRunId === child.runId);
|
|
267
|
+
if (!reservation) {
|
|
268
|
+
const intent = parent.intents.find(item => item.operationKey === binding.parentOperationKey);
|
|
269
|
+
const call = intent?.payload.calls?.[0];
|
|
270
|
+
if (intent?.kind !== 'tool' || intent.status !== 'running' || intent.payload.calls.length !== 1
|
|
271
|
+
|| snapshot.pendingIntent !== intent.operationKey || intent.claimedFence !== binding.parentExecutionFence
|
|
272
|
+
|| parent.lease?.fence !== binding.parentExecutionFence || parent.lease.expiresAt <= now()
|
|
273
|
+
|| call.callId !== binding.parentToolCallId
|
|
274
|
+
|| !['start/v1', 'delegate/v1'].includes(effective(parent).tools.find(tool => tool.name === call.name)?.childBudgetPolicy)
|
|
275
|
+
|| hash({ parent: identity(snapshot), operationKey: intent.operationKey, call, budget: admittedToolBudget(snapshot, effective(parent), 1) }) !== binding.childRequestDigest) continue;
|
|
276
|
+
reservation = makeReservation(record, childScope);
|
|
277
|
+
}
|
|
278
|
+
ownedChild(request, reservation);
|
|
279
|
+
const delivery = reservation.deliveryMode === 'foreground' && !(snapshot.promotedChildRunIds ?? []).includes(child.runId)
|
|
280
|
+
? 'foreground' : 'background';
|
|
281
|
+
const live = !terminal(snapshot.phase) && snapshot.phase !== 'finalizing' && !snapshot.childTerminalPhase
|
|
282
|
+
&& !parent.commands.some(row => !row.consumed && ['cancel', 'policy_revoked'].includes(row.command.kind))
|
|
283
|
+
&& !terminal(child.phase) && (!unsafeReceipt(record) || canWaitForReceipt?.(record)) && reservation.settledUsageDigest === null;
|
|
284
|
+
rows.push({ childRunId: child.runId, sessionId: child.sessionId, role: binding.role, phase: child.phase, goalRevision: child.goalRevision, delivery,
|
|
285
|
+
canPromote: live && delivery === 'foreground' && effective(parent).childPromotionPolicy === 'owned-child-promotion/v1',
|
|
286
|
+
canCancel: live, canSendInput: live });
|
|
287
|
+
}
|
|
288
|
+
return rows;
|
|
289
|
+
},
|
|
290
|
+
controlTarget(request, childRunId) {
|
|
291
|
+
const row = this.tasks(request).find(task => task.childRunId === childRunId);
|
|
292
|
+
if (!row) return null;
|
|
293
|
+
for (const childScope of scopes) {
|
|
294
|
+
let record;
|
|
295
|
+
try { record = store.load(childScope, childRunId); }
|
|
296
|
+
catch (error) { if (error.code === 'run_not_found') continue; throw error; }
|
|
297
|
+
if (!same(record.configuration.childOwnerBinding?.parent, identity(request))) continue;
|
|
298
|
+
const reservation = makeReservation(record, childScope);
|
|
299
|
+
ownedChild(request, reservation);
|
|
300
|
+
return { scope: copy(childScope), runId: childRunId, configurationDigest: record.snapshot.configDigest,
|
|
301
|
+
parentOperationKey: reservation.parentOperationKey, parentToolCallId: reservation.parentToolCallId };
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
},
|
|
305
|
+
async wakeControl(request, delivery) {
|
|
306
|
+
const parent = parentRecord(request), target = delivery.target;
|
|
307
|
+
const reservation = parent.snapshot.childBudgetReservations.find(item => item.childRunId === target.runId);
|
|
308
|
+
check(reservation && reservation.settledUsageDigest === null, 'child_control_not_owned');
|
|
309
|
+
const record = ownedChild(request, reservation);
|
|
310
|
+
check(same(record.snapshot.scope, target.scope) && record.snapshot.configDigest === target.configurationDigest
|
|
311
|
+
&& record.commands.some(row => same(row.command, delivery.command)), 'child_control_delivery_missing');
|
|
312
|
+
if (terminal(record.snapshot.phase)) return;
|
|
313
|
+
const entry = await liveEntry({ ...request, signal: new AbortController().signal }, reservation, record);
|
|
314
|
+
check(entry, 'child_owner_unavailable');
|
|
315
|
+
// Only an exact duplicate of the transactionally admitted command may
|
|
316
|
+
// notify an active Kernel. Cancellation interrupts its existing effect.
|
|
317
|
+
check(submit(entry, delivery.command).status === 'duplicate', 'child_control_delivery_missing');
|
|
318
|
+
if (!childNeedsApproval(record) || delivery.command.kind === 'cancel') drive(entry);
|
|
319
|
+
},
|
|
320
|
+
async start(request) {
|
|
321
|
+
try {
|
|
322
|
+
request.signal.throwIfAborted();
|
|
323
|
+
const parent = toolInvocation(request), inheritedPlan = effective(parent).childPlanPolicy === CHILD_PLAN_POLICY;
|
|
324
|
+
if (activePlanMode(parent.snapshot, effective(parent)) && !isPlanChildCall(effective(parent), request.call, request.definition)) return planModeWriteDenial();
|
|
325
|
+
check(['start/v1', 'delegate/v1'].includes(request.definition.childBudgetPolicy), 'child_start_not_admitted');
|
|
326
|
+
const args = request.call.arguments;
|
|
327
|
+
if (effective(parent).childCapabilityPolicy === 'required-tools/v1') {
|
|
328
|
+
check(typeof admitRequirements === 'function', 'child_capability_owner_unavailable');
|
|
329
|
+
const refusal = admitRequirements(args);
|
|
330
|
+
if (refusal) return { status: 'error', content: refusal };
|
|
331
|
+
}
|
|
332
|
+
check(typeof args.role === 'string' && args.role.trim() && typeof args.task === 'string' && args.task.trim()
|
|
333
|
+
&& (args.background === undefined || typeof args.background === 'boolean')
|
|
334
|
+
&& (args.continuation_of === undefined || typeof args.continuation_of === 'string'
|
|
335
|
+
&& args.continuation_of.trim() && args.continuation_of.length <= 256), 'child_background_request_invalid');
|
|
336
|
+
const foreground = args.background === false || request.call.name === 'native_delegate' && args.background !== true;
|
|
337
|
+
check(request.budget.depth < request.budget.maxDepth, 'child_depth_exhausted');
|
|
338
|
+
// Omitted call limits defer to the role cap + remaining parent budget under the
|
|
339
|
+
// allocation policy (no stacked 4/8 default); legacy keeps the historical 4/8.
|
|
340
|
+
// availableTools stays signed so an exhausted parent (maxToolCallsRemaining 0
|
|
341
|
+
// for start/v1) is refused here, before any child starts, rather than having core
|
|
342
|
+
// reject its receipt after execution.
|
|
343
|
+
const { maxModelCalls: requestedModelCalls, maxToolCalls: requestedToolCalls } = resolveChildCallLimits(
|
|
344
|
+
{ policy: allocationPolicy, modelCallLimit: args.model_call_limit, toolCallLimit: args.tool_call_limit,
|
|
345
|
+
budget: request.budget, childBudgetPolicy: request.definition.childBudgetPolicy });
|
|
346
|
+
check(Number.isSafeInteger(requestedModelCalls) && requestedModelCalls > 0
|
|
347
|
+
&& Number.isSafeInteger(requestedToolCalls) && requestedToolCalls >= 0, 'child_budget_exhausted');
|
|
348
|
+
let maxModelCalls = requestedModelCalls, maxToolCalls = requestedToolCalls;
|
|
349
|
+
const requestDigest = hash({ parent: identity(request), operationKey: request.operationKey, call: request.call, budget: request.budget });
|
|
350
|
+
const binding = { contract: CHILD_OWNER_CONTRACT, parent: identity(request), parentOperationKey: request.operationKey,
|
|
351
|
+
parentToolCallId: request.call.callId, parentExecutionFence: request.executionFence, childRequestDigest: requestDigest,
|
|
352
|
+
...(inheritedPlan ? { planMode: activePlanMode(parent.snapshot, effective(parent)) } : {}),
|
|
353
|
+
...(allocationPolicy ? { childAllocationPolicy } : {}),
|
|
354
|
+
role: args.role, maxModelCalls, maxToolCalls, ...(foreground ? { deliveryMode: 'foreground' } : {}) };
|
|
355
|
+
const runId = locator(request);
|
|
356
|
+
const existing = scopes.flatMap(childScope => store.list(childScope)).find(snapshot => snapshot.runId === runId);
|
|
357
|
+
if (existing) {
|
|
358
|
+
const record = store.load(existing.scope, runId), saved = record.configuration.childOwnerBinding;
|
|
359
|
+
// A stored child is never re-admitted under a foreign allocation policy: a
|
|
360
|
+
// legacy child must not silently gain the larger allowance, and vice versa.
|
|
361
|
+
check(saved?.childRequestDigest === requestDigest && saved.parentToolCallId === request.call.callId
|
|
362
|
+
&& (saved.childAllocationPolicy ?? null) === (allocationPolicy ?? null)
|
|
363
|
+
&& saved.maxModelCalls <= maxModelCalls && saved.maxToolCalls <= maxToolCalls, 'child_start_identity_conflict');
|
|
364
|
+
const reservation = makeReservation(record, existing.scope);
|
|
365
|
+
ownedChild(request, reservation);
|
|
366
|
+
return foreground ? foregroundResult(request, reservation) : startupReceipt(reservation, saved.role);
|
|
367
|
+
}
|
|
368
|
+
const source = continuationSource(request);
|
|
369
|
+
const childSessionId = source?.child.snapshot.sessionId ?? runId;
|
|
370
|
+
if (source) {
|
|
371
|
+
binding.continuationOf = source.child.snapshot.runId;
|
|
372
|
+
binding.continuationParentRunId = source.parent.snapshot.runId;
|
|
373
|
+
}
|
|
374
|
+
const bound = await observeWhileLive(Promise.resolve().then(() => resolveChild({ parent: identity(request), call: copy(request.call), childRunId: runId,
|
|
375
|
+
childSessionId,
|
|
376
|
+
role: args.role, task: args.task, parentContract: copy(effective(toolInvocation(request)).contract),
|
|
377
|
+
maxModelCalls, maxToolCalls, depth: request.budget.depth + 1, signal: request.signal })), request.signal);
|
|
378
|
+
request.signal.throwIfAborted(); toolInvocation(request);
|
|
379
|
+
check(bound?.scope && scopes.some(item => same(item, bound.scope))
|
|
380
|
+
&& bound.configuration?.contract.objective === args.task, 'child_admission_binding_invalid');
|
|
381
|
+
if (source) {
|
|
382
|
+
const current = continuationSource(request);
|
|
383
|
+
check(same(bound.scope, source.child.snapshot.scope) && same(identity(current.child.snapshot), identity(source.child.snapshot))
|
|
384
|
+
&& current.parent.snapshot.runId === source.parent.snapshot.runId, 'child_followup_not_available');
|
|
385
|
+
}
|
|
386
|
+
binding.maxModelCalls = Math.min(maxModelCalls, bound.configuration.limits.maxModelCalls ?? maxModelCalls);
|
|
387
|
+
binding.maxToolCalls = Math.min(maxToolCalls, bound.configuration.limits.maxToolCalls ?? maxToolCalls);
|
|
388
|
+
check(binding.maxModelCalls >= 1 && binding.maxToolCalls >= 0, 'child_budget_exhausted');
|
|
389
|
+
const childConfiguration = { ...copy(bound.configuration), delegationDepth: request.budget.depth + 1,
|
|
390
|
+
...(inheritedPlan ? { planModePolicy: CHILD_PLAN_MODE_POLICY, planModeInitialActive: binding.planMode } : {}),
|
|
391
|
+
limits: { ...bound.configuration.limits, maxModelCalls: binding.maxModelCalls, maxToolCalls: binding.maxToolCalls }, childOwnerBinding: binding };
|
|
392
|
+
// This is a newly admitted child, not an upgrade of a stored run.
|
|
393
|
+
// Inherited Plan mode adds a required strategy to its own frozen set.
|
|
394
|
+
const modules = childConfiguration.policyModules ?? defaultPolicyBindings(childConfiguration);
|
|
395
|
+
childConfiguration.policyModules = [...modules, ...defaultPolicyBindings(childConfiguration)
|
|
396
|
+
.filter(required => !modules.some(module => module.id === required.id))];
|
|
397
|
+
const configuration = configure(childConfiguration);
|
|
398
|
+
const childIdentity = { scope: copy(bound.scope), runId, sessionId: childSessionId,
|
|
399
|
+
turnId: '1', buildId: request.buildId, parentRunId: null, taskChainId: source ? runId : request.taskChainId };
|
|
400
|
+
const kernel = new NativeKernel({ store, ports: bound.ports, owner: `${owner}:${runId}`, digest: hash, onStream });
|
|
401
|
+
try { kernel.start(childIdentity, configuration, { expectedParentRunId: source?.child.snapshot.runId ?? null }); }
|
|
402
|
+
catch (error) {
|
|
403
|
+
if (source && ['session_head_conflict', 'session_already_active'].includes(error.code))
|
|
404
|
+
throw new NativeError('child_followup_source_not_current');
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
const record = store.load(childIdentity.scope, runId), reservation = makeReservation(record, childIdentity.scope);
|
|
408
|
+
const entry = attachKernel(record, bound.ports);
|
|
409
|
+
drive(entry);
|
|
410
|
+
return foreground ? foregroundResult(request, reservation) : startupReceipt(reservation, binding.role);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
// Admission refusals happen before a child is created. They are known
|
|
413
|
+
// tool outcomes, not unknown external effects requiring reconciliation.
|
|
414
|
+
if (['child_followup_not_available', 'child_followup_role_mismatch', 'child_followup_requires_result_collection',
|
|
415
|
+
'child_followup_source_not_current', 'child_followup_model_image_unavailable'].includes(error.code))
|
|
416
|
+
return { status: 'error', content: { error_code: error.code, executed: false } };
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
async control(request) {
|
|
421
|
+
request.signal.throwIfAborted();
|
|
422
|
+
let parent = toolInvocation(request);
|
|
423
|
+
if (activePlanMode(parent.snapshot, effective(parent)) && !isPlanChildCall(effective(parent), request.call, request.definition)) return planModeWriteDenial();
|
|
424
|
+
check(request.definition.childBudgetPolicy === 'settle/v1', 'child_control_not_admitted');
|
|
425
|
+
const args = request.call.arguments, waitSeconds = args.wait_seconds ?? 15;
|
|
426
|
+
check(typeof args.child_run_id === 'string' && ['wait', 'send', 'stop'].includes(args.action)
|
|
427
|
+
&& typeof waitSeconds === 'number' && Number.isFinite(waitSeconds) && waitSeconds >= 0 && waitSeconds <= 30
|
|
428
|
+
&& (args.action === 'send' ? typeof args.message === 'string' && args.message.trim() && args.message.length <= 32000
|
|
429
|
+
: args.message === undefined), 'child_control_arguments_invalid');
|
|
430
|
+
const reservation = parent.snapshot.childBudgetReservations.find(item => item.childRunId === args.child_run_id);
|
|
431
|
+
check(reservation && parent.snapshot.childRunIds.includes(args.child_run_id), 'child_control_not_owned');
|
|
432
|
+
let record = ownedChild(request, reservation);
|
|
433
|
+
if (terminal(record.snapshot.phase)) return terminalReceipt(record, reservation);
|
|
434
|
+
check(reservation.settledUsageDigest === null, 'child_control_terminal_conflict');
|
|
435
|
+
const entry = await liveEntry(request, reservation, record);
|
|
436
|
+
parent = toolInvocation(request); request.signal.throwIfAborted();
|
|
437
|
+
check(entry, 'child_owner_unavailable');
|
|
438
|
+
if (args.action !== 'wait') {
|
|
439
|
+
submit(entry, { commandId: `parent-control-${hash({ parent: request.runId,
|
|
440
|
+
operationKey: request.operationKey, callId: request.call.callId })}`, kind: args.action === 'send' ? 'guide' : 'cancel',
|
|
441
|
+
payload: args.action === 'send' ? { text: args.message } : {} });
|
|
442
|
+
}
|
|
443
|
+
if (!childNeedsApproval(record) || args.action === 'stop') drive(entry);
|
|
444
|
+
const deadline = performance.now() + waitSeconds * 1000;
|
|
445
|
+
while (waitSeconds > 0 && args.action !== 'send') {
|
|
446
|
+
record = ownedChild(request, reservation);
|
|
447
|
+
if (terminal(record.snapshot.phase) || childNeedsApproval(record) && args.action !== 'stop' || performance.now() >= deadline) break;
|
|
448
|
+
await observeWhileLive(new Promise(resolve => setTimeout(resolve, 25)), request.signal);
|
|
449
|
+
if (entry.error?.code === 'lease_busy' || !entry.promise && record.commands.some(row => !row.consumed)) drive(entry);
|
|
450
|
+
}
|
|
451
|
+
request.signal.throwIfAborted(); toolInvocation(request);
|
|
452
|
+
record = ownedChild(request, reservation);
|
|
453
|
+
if (terminal(record.snapshot.phase)) return terminalReceipt(record, reservation);
|
|
454
|
+
return { status: 'ok', content: { child_run_id: record.snapshot.runId, status: record.snapshot.phase,
|
|
455
|
+
action: args.action, parent_acceptance: 'not_evaluated', result_available: false } };
|
|
456
|
+
},
|
|
457
|
+
async collect(request) {
|
|
458
|
+
request.signal.throwIfAborted();
|
|
459
|
+
const parent = parentRecord(request), intent = parent.intents.find(item => item.operationKey === request.operationKey);
|
|
460
|
+
check(intent?.status === 'running', 'child_collection_not_owned');
|
|
461
|
+
check(intent.claimedFence === request.executionFence && parent.lease?.fence === request.executionFence
|
|
462
|
+
&& parent.lease.expiresAt > now(), 'child_collection_fence_lost');
|
|
463
|
+
const { signal, ...plain } = childLifecycleRequest(parent.snapshot, intent, request.signal);
|
|
464
|
+
const { signal: ignored, ...supplied } = request;
|
|
465
|
+
check(same(plain, supplied), 'child_collection_not_owned');
|
|
466
|
+
const records = request.reservations.map(reservation => ownedChild(request, reservation));
|
|
467
|
+
const awaited = [];
|
|
468
|
+
for (let index = 0; index < records.length; index++) {
|
|
469
|
+
const reservation = request.reservations[index], record = records[index];
|
|
470
|
+
const entry = await liveEntry(request, reservation, record);
|
|
471
|
+
request.signal.throwIfAborted();
|
|
472
|
+
if (!entry || terminal(record.snapshot.phase)) continue;
|
|
473
|
+
if (request.action === 'cancel') submit(entry, {
|
|
474
|
+
commandId: `parent-cancel-${hash({ parent: request.runId, operationKey: request.operationKey, child: entry.runId })}`, kind: 'cancel', payload: {} });
|
|
475
|
+
else if (request.childApproval?.childRunId === entry.runId && request.interaction) {
|
|
476
|
+
const expected = request.childApproval;
|
|
477
|
+
check(request.interaction.interactionId === `native-child-${hash(expected)}`
|
|
478
|
+
&& request.interaction.callId === expected.toolCall.callId && ['allow', 'deny'].includes(request.interaction.decision), 'child_approval_identity_conflict');
|
|
479
|
+
const commandId = `parent-approval-${hash({ parent: identity(request), approval: expected, decision: request.interaction.decision })}`;
|
|
480
|
+
// A promotion may detach this observer after the decision was
|
|
481
|
+
// durably delivered. Accept only that same saved decision; never
|
|
482
|
+
// infer approval from a child's phase or ask for it a second time.
|
|
483
|
+
const previousIds = parent.intents.filter(item => item.kind === 'child' && same(item.payload.childApproval, expected)
|
|
484
|
+
&& same(item.payload.interaction, request.interaction)).map(item =>
|
|
485
|
+
`parent-approval-${hash({ parent: item.operationKey, approval: expected, decision: request.interaction.decision })}`);
|
|
486
|
+
const delivered = record.commands.some(row => row.command.kind === 'submit_interaction'
|
|
487
|
+
&& [commandId, ...previousIds].includes(row.command.commandId)
|
|
488
|
+
&& same(row.command.payload, { interactionId: expected.interactionId, decision: request.interaction.decision }));
|
|
489
|
+
if (!delivered) {
|
|
490
|
+
check(same(childApproval(record, reservation), expected), 'child_approval_identity_conflict');
|
|
491
|
+
submit(entry, { commandId, kind: 'submit_interaction',
|
|
492
|
+
payload: { interactionId: expected.interactionId, decision: request.interaction.decision } });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (!childNeedsApproval(record) || request.action === 'cancel' || request.childApproval?.childRunId === entry.runId) {
|
|
496
|
+
const promise = drive(entry);
|
|
497
|
+
if (!request.waitForChildRunIds || request.waitForChildRunIds.includes(entry.runId)) awaited.push(promise);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (request.action !== 'poll') {
|
|
501
|
+
await observeWhileLive(Promise.all(awaited), request.signal);
|
|
502
|
+
while (request.reservations.some(reservation => {
|
|
503
|
+
const record = ownedChild(request, reservation);
|
|
504
|
+
return (!request.waitForChildRunIds || request.waitForChildRunIds.includes(reservation.childRunId))
|
|
505
|
+
&& !terminal(record.snapshot.phase) && record.commands.some(row => !row.consumed);
|
|
506
|
+
})) {
|
|
507
|
+
const continuations = request.reservations.flatMap(reservation => {
|
|
508
|
+
if (request.waitForChildRunIds && !request.waitForChildRunIds.includes(reservation.childRunId)) return [];
|
|
509
|
+
const record = ownedChild(request, reservation), entry = entries.get(record.snapshot.runId);
|
|
510
|
+
return entry && !terminal(record.snapshot.phase) && record.commands.some(row => !row.consumed) ? [drive(entry)] : [];
|
|
511
|
+
});
|
|
512
|
+
if (!continuations.length) break;
|
|
513
|
+
await observeWhileLive(Promise.all(continuations), request.signal);
|
|
514
|
+
}
|
|
515
|
+
// Another live owner may still hold the child's lease. Observe it;
|
|
516
|
+
// after expiry the same Kernel classifies its existing claims.
|
|
517
|
+
while (request.reservations.some(reservation => {
|
|
518
|
+
if (request.waitForChildRunIds && !request.waitForChildRunIds.includes(reservation.childRunId)) return false;
|
|
519
|
+
const record = ownedChild(request, reservation);
|
|
520
|
+
return !terminal(record.snapshot.phase) && !childNeedsApproval(record)
|
|
521
|
+
&& entries.get(record.snapshot.runId)?.error?.code === 'lease_busy';
|
|
522
|
+
})) {
|
|
523
|
+
await observeWhileLive(new Promise(resolve => setTimeout(resolve, 25)), request.signal);
|
|
524
|
+
const retries = request.reservations.flatMap(reservation => {
|
|
525
|
+
if (request.waitForChildRunIds && !request.waitForChildRunIds.includes(reservation.childRunId)) return [];
|
|
526
|
+
const record = ownedChild(request, reservation), entry = entries.get(record.snapshot.runId);
|
|
527
|
+
return entry && !terminal(record.snapshot.phase) && entry.error?.code === 'lease_busy' ? [drive(entry)] : [];
|
|
528
|
+
});
|
|
529
|
+
await observeWhileLive(Promise.all(retries), request.signal);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
request.signal.throwIfAborted();
|
|
533
|
+
const result = { operationKey: request.operationKey, results: [], pendingChildRunIds: [], approvals: [], errorCode: null, children: [] };
|
|
534
|
+
if (request.responseCursors !== undefined) result.responses = [];
|
|
535
|
+
for (const reservation of request.reservations) {
|
|
536
|
+
const record = ownedChild(request, reservation);
|
|
537
|
+
if (result.responses) result.responses.push(...responses(record, request.responseCursors));
|
|
538
|
+
result.children.push(projection(record, reservation));
|
|
539
|
+
if (terminal(record.snapshot.phase) && record.snapshot.phase !== 'interrupted' && !unsafeReceipt(record)) result.results.push(terminalReceipt(record, reservation, false));
|
|
540
|
+
else {
|
|
541
|
+
result.pendingChildRunIds.push(record.snapshot.runId);
|
|
542
|
+
if (childNeedsApproval(record) && request.action !== 'cancel') result.approvals.push(childApproval(record, reservation));
|
|
543
|
+
if (record.snapshot.phase === 'interrupted' || unsafeReceipt(record) && !canWaitForReceipt?.(record)
|
|
544
|
+
|| entries.get(record.snapshot.runId)?.error && entries.get(record.snapshot.runId).error.code !== 'lease_busy') result.errorCode = 'child_receipt_unavailable';
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return result;
|
|
548
|
+
},
|
|
549
|
+
async readyChildren(request) {
|
|
550
|
+
request.signal.throwIfAborted();
|
|
551
|
+
const parent = parentRecord(request), intent = parent.intents.find(item => item.operationKey === parent.snapshot.pendingIntent) ?? null;
|
|
552
|
+
const { signal, ...plain } = request;
|
|
553
|
+
check(same(childReadyProbe(parent.snapshot, effective(parent), intent), plain), 'child_readiness_not_owned');
|
|
554
|
+
const ids = [];
|
|
555
|
+
for (const reservation of request.reservations) {
|
|
556
|
+
const record = ownedChild(request, reservation);
|
|
557
|
+
if (terminal(record.snapshot.phase) || request.includeApprovals && childNeedsApproval(record)
|
|
558
|
+
|| request.responseCursors !== undefined && responses(record, request.responseCursors).length) ids.push(record.snapshot.runId);
|
|
559
|
+
}
|
|
560
|
+
return { childRunIds: ids };
|
|
561
|
+
},
|
|
562
|
+
async reconcile(request) {
|
|
563
|
+
request.signal.throwIfAborted();
|
|
564
|
+
const parent = parentRecord(request), intent = parent.intents.find(item => item.operationKey === request.operationKey);
|
|
565
|
+
check(intent?.status === 'running', 'child_recovery_not_owned');
|
|
566
|
+
const { signal, ...expected } = childLifecycleRequest(parent.snapshot, intent, request.signal);
|
|
567
|
+
const { signal: ignored, ...plain } = request;
|
|
568
|
+
check(same(expected, plain), 'child_recovery_not_owned');
|
|
569
|
+
const records = request.reservations.map(reservation => ownedChild(request, reservation));
|
|
570
|
+
// Reconcile observes ledgers only. A proven original receipt wait may
|
|
571
|
+
// remain pending after the parent collector died; it is not a failed
|
|
572
|
+
// collection and grants no authority to query/send/drive that child.
|
|
573
|
+
// Rebinding and execution happen later in a fresh fenced collect.
|
|
574
|
+
if (records.some(record => terminal(record.snapshot.phase)
|
|
575
|
+
? record.snapshot.phase === 'interrupted' || unsafeReceipt(record)
|
|
576
|
+
: request.action === 'cancel' || !canWaitForReceipt?.(record))) return null;
|
|
577
|
+
return { operationKey: request.operationKey,
|
|
578
|
+
results: records.flatMap((record, index) => terminal(record.snapshot.phase) ? [terminalReceipt(record, request.reservations[index], false)] : []),
|
|
579
|
+
...(request.responseCursors === undefined ? {} : { responses: records.flatMap(record => responses(record, request.responseCursors)) }),
|
|
580
|
+
pendingChildRunIds: records.filter(record => !terminal(record.snapshot.phase)).map(record => record.snapshot.runId),
|
|
581
|
+
approvals: [], errorCode: null, children: records.map((record, index) => projection(record, request.reservations[index])) };
|
|
582
|
+
},
|
|
583
|
+
async interruptedReceipts(request) {
|
|
584
|
+
check(same(request.scope, parentScope), 'child_parent_scope_mismatch');
|
|
585
|
+
const parent = store.load(parentScope, request.runId), intent = parent.intents.find(item => item.operationKey === request.parentOperationKey);
|
|
586
|
+
check(intent?.kind === 'tool' && intent.status === 'running' && parent.snapshot.pendingIntent === intent.operationKey, 'child_recovery_not_owned');
|
|
587
|
+
const calls = intent.payload.calls;
|
|
588
|
+
check(Array.isArray(calls) && calls.length === 1 && ['start/v1', 'delegate/v1'].includes(effective(parent).tools.find(tool => tool.name === calls[0].name)?.childBudgetPolicy), 'child_recovery_not_owned');
|
|
589
|
+
const childId = locator({ scope: parentScope, runId: request.runId, operationKey: intent.operationKey });
|
|
590
|
+
const records = scopes.flatMap(childScope => store.list(childScope)).filter(snapshot => snapshot.runId === childId);
|
|
591
|
+
const entry = entries.get(childId);
|
|
592
|
+
if (!records.length && !entry) return [];
|
|
593
|
+
const childScope = entry?.scope ?? records[0].scope, record = store.load(childScope, childId);
|
|
594
|
+
const reservation = makeReservation(record, childScope);
|
|
595
|
+
ownedChild(identity(parent.snapshot), reservation);
|
|
596
|
+
check(record.configuration.childOwnerBinding.parentToolCallId === calls[0].callId
|
|
597
|
+
&& record.configuration.childOwnerBinding.childRequestDigest === hash({ parent: identity(parent.snapshot),
|
|
598
|
+
operationKey: intent.operationKey, call: calls[0], budget: admittedToolBudget(parent.snapshot, effective(parent), 1) }), 'child_recovery_identity_conflict');
|
|
599
|
+
return [{ parentOperationKey: intent.operationKey, parentToolCallId: calls[0].callId,
|
|
600
|
+
result: reservation.deliveryMode === 'foreground' && terminal(record.snapshot.phase)
|
|
601
|
+
&& record.snapshot.phase !== 'interrupted' && !unsafeReceipt(record)
|
|
602
|
+
? { ...terminalReceipt(record, reservation), childBudgetReservation: copy(reservation) }
|
|
603
|
+
: startupReceipt(reservation, record.configuration.childOwnerBinding.role) }];
|
|
604
|
+
},
|
|
605
|
+
async releaseRun(request) {
|
|
606
|
+
check(same(request.scope, parentScope), 'child_parent_scope_mismatch');
|
|
607
|
+
const jobs = [];
|
|
608
|
+
for (const entry of entries.values()) {
|
|
609
|
+
if (entry.configuration.childOwnerBinding.parent.runId !== request.runId) continue;
|
|
610
|
+
const record = store.load(entry.scope, entry.runId);
|
|
611
|
+
if (!terminal(record.snapshot.phase)) {
|
|
612
|
+
submit(entry, { commandId: `owner-release-${hash({ owner, runId: request.runId })}`, kind: 'cancel', payload: {} });
|
|
613
|
+
jobs.push(drive(entry));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
await Promise.all(jobs);
|
|
617
|
+
for (const [id, entry] of entries) if (entry.configuration.childOwnerBinding.parent.runId === request.runId) entries.delete(id);
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
}
|