@kingsoftcloud/agentkit-harness-node 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +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,498 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { NativeKernel, NativeError, isNativeError, NATIVE_GOAL_CONTRACT_BINDING, NATIVE_JSON_CODEC, canonical, configure, defaultPolicyBindings, invariant, terminal } from '@kingsoftcloud/agentkit-harness-core';
|
|
3
|
+
import { createNativeSessionDelivery } from './session-delivery.mjs';
|
|
4
|
+
import { reviewedBackgroundInterruption, reviewedResourceInterruption } from '@kingsoftcloud/agentkit-harness-core';
|
|
5
|
+
import { nativeLiveStream } from './assistant-stream.mjs';
|
|
6
|
+
import { NATIVE_DSH_TURN_CONTRACT } from './dsh-turn-contract.mjs';
|
|
7
|
+
import { nativeDshOwnership, nativeDshOwnershipHost, readNativeDshOwnership, NATIVE_DSH_OWNERSHIP_HOST } from './dsh-turn-ownership.mjs';
|
|
8
|
+
import { NATIVE_DSH_BACKGROUND_TURN_CONTRACT, nativeDshBackgroundOwnership, readNativeDshBackgroundOwnership,
|
|
9
|
+
verifyNativeDshBackgroundOwnership } from './dsh-background-ownership.mjs';
|
|
10
|
+
|
|
11
|
+
export { NATIVE_DSH_TURN_CONTRACT };
|
|
12
|
+
const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
|
|
13
|
+
export const nativeDshRunId = (scope, sessionId, turn) => `native-dsh-${hash({ contract: NATIVE_DSH_TURN_CONTRACT, scope, sessionId, turn })}`;
|
|
14
|
+
const failure = (code, snapshot) => Object.assign(new Error(code), { code,
|
|
15
|
+
...(snapshot ? { nativeRunId: snapshot.runId, nativePhase: snapshot.phase, nativeReason: snapshot.reason } : {}),
|
|
16
|
+
});
|
|
17
|
+
const unresolvedEffects = record => record.intents.some(intent => ['tool', 'hook', 'child', 'input'].includes(intent.kind)
|
|
18
|
+
&& (intent.status === 'running' && !record.receipts.some(receipt => receipt.operationKey === intent.operationKey)
|
|
19
|
+
|| record.receipts.some(receipt => receipt.operationKey === intent.operationKey && receipt.status === 'uncertain'
|
|
20
|
+
&& !(receipt.result.recoveryPolicy === 'safe-read/v1' && receipt.result.retryScheduled === true))));
|
|
21
|
+
|
|
22
|
+
import { canCloseNativeReadInterruption } from './read-interruption.mjs';
|
|
23
|
+
export { canCloseNativeReadInterruption };
|
|
24
|
+
|
|
25
|
+
function cancelReason(cause) {
|
|
26
|
+
return ['user', 'parent', 'disposed'].includes(cause?.kind) ? { kind: cause.kind }
|
|
27
|
+
: cause?.kind === 'hook' && typeof cause.reason === 'string' ? { kind: 'hook', reason: cause.reason }
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
function canceledEnding(signal, record) {
|
|
31
|
+
const persisted = record?.commands.find(row => row.command.commandId === `${record.snapshot.runId}:dsh-turn-cancel`)
|
|
32
|
+
?.command.payload.dshCancelReason;
|
|
33
|
+
const reason = cancelReason(signal?.aborted ? signal.reason : persisted)
|
|
34
|
+
?? { kind: signal?.aborted ? 'disposed' : 'user' };
|
|
35
|
+
return { reason: { kind: 'aborted', reason } };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Shared freezing for first admission and explicit adoption of the same Run. */
|
|
39
|
+
export function freezeNativeDshConfiguration(binding, input, contract = binding.configuration.contract, originalConfiguration) {
|
|
40
|
+
const shared = binding.configuration.backgroundNotificationPolicy === 'completed-before-model/v1'
|
|
41
|
+
&& (!originalConfiguration || Object.hasOwn(originalConfiguration, 'backgroundSessionBindingDigest'))
|
|
42
|
+
? { backgroundSessionBindingDigest: hash({ contract: 'agentkit.background-session-binding/v1', scope: binding.scope,
|
|
43
|
+
buildId: binding.buildId, declared: binding.configuration.hostBindingDigest, instructions: binding.configuration.instructions }) } : {};
|
|
44
|
+
const configuration = configure({ ...binding.configuration, ...shared, contract, hostBindingDigest: hash({
|
|
45
|
+
contract: NATIVE_DSH_TURN_CONTRACT, declared: binding.configuration.hostBindingDigest,
|
|
46
|
+
goalContractBinding: NATIVE_GOAL_CONTRACT_BINDING, jsonCodec: NATIVE_JSON_CODEC,
|
|
47
|
+
input: input.messages, source: binding.source,
|
|
48
|
+
}) });
|
|
49
|
+
if (originalConfiguration && !Object.hasOwn(originalConfiguration, 'backgroundSessionBindingDigest'))
|
|
50
|
+
delete configuration.backgroundSessionBindingDigest;
|
|
51
|
+
if (originalConfiguration && !Object.hasOwn(originalConfiguration, 'policyModules')) {
|
|
52
|
+
// Historical ownership predates explicit manifests. Only the equivalent
|
|
53
|
+
// built-in default may be projected back to absence; custom changes fail.
|
|
54
|
+
invariant(configuration.policyModules === undefined
|
|
55
|
+
|| canonical(configuration.policyModules) === canonical(defaultPolicyBindings(configuration)),
|
|
56
|
+
'native_dsh_recovery_configuration_changed');
|
|
57
|
+
delete configuration.policyModules;
|
|
58
|
+
}
|
|
59
|
+
return configuration;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Verify a trusted Host's reconstructed ports/configuration before any worker starts. */
|
|
63
|
+
export function rebindNativeDshConfiguration(record, binding, payload) {
|
|
64
|
+
invariant(hash(record.configuration) === record.snapshot.configDigest, 'stored_configuration_mismatch');
|
|
65
|
+
invariant(canonical(binding.scope) === canonical(record.snapshot.scope)
|
|
66
|
+
&& binding.buildId === record.snapshot.buildId
|
|
67
|
+
&& payload?.agent?.session?.id === record.snapshot.sessionId
|
|
68
|
+
&& Number.isSafeInteger(payload.turn) && payload.turn > 0 && String(payload.turn) === record.snapshot.turnId,
|
|
69
|
+
'native_dsh_recovery_identity_mismatch');
|
|
70
|
+
const configuration = freezeNativeDshConfiguration(binding, payload.input, record.configuration.contract, record.configuration);
|
|
71
|
+
invariant(hash(configuration) === record.snapshot.configDigest, 'native_dsh_recovery_configuration_changed');
|
|
72
|
+
return configuration;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Resolving host resources is not execution authority. A late lookup can never
|
|
76
|
+
// start a Native run after the enclosing DSH turn has been canceled.
|
|
77
|
+
async function resolveWhileLive(resolve, payload) {
|
|
78
|
+
const signal = payload.signal;
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
let onAbort;
|
|
81
|
+
const canceled = new Promise((_, reject) => {
|
|
82
|
+
onAbort = () => reject(signal.reason);
|
|
83
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
84
|
+
});
|
|
85
|
+
try {
|
|
86
|
+
const result = await Promise.race([Promise.resolve().then(() => resolve(payload)), canceled]);
|
|
87
|
+
signal.throwIfAborted();
|
|
88
|
+
return result;
|
|
89
|
+
} finally { signal.removeEventListener('abort', onAbort); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function flushOwnership(flush, payload, timeoutMs) {
|
|
93
|
+
let timer;
|
|
94
|
+
try {
|
|
95
|
+
return await resolveWhileLive(() => Promise.race([
|
|
96
|
+
Promise.resolve().then(() => flush(payload.agent.session)),
|
|
97
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(
|
|
98
|
+
new NativeError('native_dsh_ownership_flush_timeout')), timeoutMs); }),
|
|
99
|
+
]), payload);
|
|
100
|
+
} finally { clearTimeout(timer); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Optional owner consumption follows the committed terminal; it is not a new
|
|
104
|
+
// task effect or acceptance result. A lost acknowledgement must not replace
|
|
105
|
+
// the answer, and the owner must reconcile its own durable source receipts.
|
|
106
|
+
export async function consumeNativeCompletedTurn(binding, record, signal) {
|
|
107
|
+
if (!binding.consumeCompletedTurn || record.snapshot.phase !== 'completed' || signal.aborted) return;
|
|
108
|
+
const deadline = new AbortController(), scoped = AbortSignal.any([signal, deadline.signal]);
|
|
109
|
+
let listener;
|
|
110
|
+
const canceled = new Promise((_, reject) => {
|
|
111
|
+
listener = () => reject(scoped.reason);
|
|
112
|
+
scoped.addEventListener('abort', listener, { once: true });
|
|
113
|
+
if (scoped.aborted) listener();
|
|
114
|
+
});
|
|
115
|
+
const timer = setTimeout(() => deadline.abort(new NativeError('native_completed_turn_consumer_timeout')),
|
|
116
|
+
Math.max(1, Math.floor(record.configuration.limits.effectTimeoutMs / 2)));
|
|
117
|
+
try {
|
|
118
|
+
await Promise.race([Promise.resolve().then(() => {
|
|
119
|
+
scoped.throwIfAborted();
|
|
120
|
+
return binding.consumeCompletedTurn(structuredClone(record.snapshot), { signal: scoped });
|
|
121
|
+
}), canceled]);
|
|
122
|
+
} catch { /* Optional consumption failure never rewrites the committed task. */ }
|
|
123
|
+
finally { clearTimeout(timer); scoped.removeEventListener('abort', listener); deadline.abort(); }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Bind the actual Native loop to DSH's public whole-turn boundary.
|
|
128
|
+
*
|
|
129
|
+
* resolveTurn(payload) returns null to delegate an unrelated runtime, or
|
|
130
|
+
* {scope, buildId, configuration, ports, source:{provider,model}, taskChainId?, taskContractFactory?}.
|
|
131
|
+
* An optional trusted taskContractFactory receives the pinned parent facts.
|
|
132
|
+
* Its result is frozen in the new Run configuration, with session-head CAS.
|
|
133
|
+
* Optional consumeCompletedTurn(snapshot, {signal}) runs after a committed
|
|
134
|
+
* completed terminal, before releaseResources. It has half the effect timeout;
|
|
135
|
+
* failure is optional and cannot replace task completion or delivery errors.
|
|
136
|
+
* Its owner must bind policy into hostBindingDigest and use durable idempotent
|
|
137
|
+
* receipts: same-turn redelivery may call it again, never prove a prior write.
|
|
138
|
+
* The resolver must project all admitted input, including attachment bytes
|
|
139
|
+
* resolved by the host, into configuration.inputMessages. It owns that trusted
|
|
140
|
+
* projection; this adapter never reads attachment paths supplied in messages.
|
|
141
|
+
* The trusted host resolves credentials and permissions into ports; none are
|
|
142
|
+
* read from user text or copied into the Native ledger by this adapter.
|
|
143
|
+
*
|
|
144
|
+
* Same-turn replay reuses the exact Native run and committed DSH atoms. A new
|
|
145
|
+
* DSH turn cannot implicitly restart an unfinished Native run. Cross-turn
|
|
146
|
+
* adoption of interrupted work requires an explicit host recovery workflow.
|
|
147
|
+
* durableOwnership additionally requires the public DSH external-turn host:
|
|
148
|
+
* complete admission is flushed before creating the Run. resolveRecoveryTurn
|
|
149
|
+
* must independently rebind trusted ports to those original facts; persisted
|
|
150
|
+
* admission never grants execution authority. resume() uses the original turn.
|
|
151
|
+
* Optional resolveBackgroundTurn(payload, {admission,record,source}) independently
|
|
152
|
+
* binds trusted ports to an already persisted background notification and its
|
|
153
|
+
* source. It must return the exact reduced configuration, without recompiling
|
|
154
|
+
* input or resetting budgets. The source's original hostRecoveryBinding.source
|
|
155
|
+
* pins provider/model attribution; missing or changed source facts are refused.
|
|
156
|
+
* background() requires an already owned DSH turn;
|
|
157
|
+
* resumeBackground() explicitly reserves that same background engine's turn.
|
|
158
|
+
* Omitting the resolver refuses both operations and leaves normal turns intact.
|
|
159
|
+
*/
|
|
160
|
+
export function createNativeDshTurnExecutor({ store, resolveTurn, resolveRecoveryTurn, resolveBackgroundTurn, projections, flush,
|
|
161
|
+
owner = `native-dsh-${randomUUID()}`, durableOwnership = false, ownershipFlushTimeoutMs = 30_000,
|
|
162
|
+
onStream, publishStreamFrame, streamRevisions = new WeakMap(), onIdle, onCheckpoint, onActive, onDriverError } = {}) {
|
|
163
|
+
invariant((onActive === undefined || typeof onActive === 'function') && store && typeof resolveTurn === 'function' && typeof flush === 'function'
|
|
164
|
+
&& typeof projections?.stateOf === 'function' && typeof owner === 'string' && owner.trim()
|
|
165
|
+
&& (onDriverError === undefined || typeof onDriverError === 'function')
|
|
166
|
+
&& typeof durableOwnership === 'boolean' && (resolveRecoveryTurn === undefined || typeof resolveRecoveryTurn === 'function')
|
|
167
|
+
&& (resolveBackgroundTurn === undefined || typeof resolveBackgroundTurn === 'function'),
|
|
168
|
+
'native_dsh_host_binding_invalid');
|
|
169
|
+
invariant(Number.isSafeInteger(ownershipFlushTimeoutMs) && ownershipFlushTimeoutMs > 0
|
|
170
|
+
&& ownershipFlushTimeoutMs <= 2_147_483_647, 'native_dsh_ownership_flush_timeout_invalid');
|
|
171
|
+
const active = new Set(), backgroundDrivers = new Set(), revisions = streamRevisions;
|
|
172
|
+
const run = async (payload, admission, background = null) => {
|
|
173
|
+
const { agent, input, turn, signal } = payload;
|
|
174
|
+
invariant(agent?.session && typeof agent.session.id === 'string' && agent.session.id.trim()
|
|
175
|
+
&& input?.kind === 'enter' && Array.isArray(input.messages)
|
|
176
|
+
&& Number.isSafeInteger(turn) && turn > 0 && signal instanceof AbortSignal, 'native_dsh_turn_input_invalid');
|
|
177
|
+
const binding = await resolveWhileLive(background ? request => resolveBackgroundTurn(request, structuredClone(background)) : admission
|
|
178
|
+
? request => resolveRecoveryTurn(request, structuredClone(admission)) : resolveTurn, payload);
|
|
179
|
+
if (binding === null) {
|
|
180
|
+
invariant(!admission, 'native_dsh_recovery_binding_unavailable');
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
invariant(binding && binding.scope && binding.configuration
|
|
184
|
+
&& (background ? binding.configuration.inputMessages === null
|
|
185
|
+
: Array.isArray(binding.configuration.inputMessages) && binding.configuration.inputMessages.length > 0)
|
|
186
|
+
&& typeof binding.buildId === 'string' && binding.buildId.trim()
|
|
187
|
+
&& typeof binding.source?.provider === 'string' && binding.source.provider.trim()
|
|
188
|
+
&& typeof binding.source?.model === 'string' && binding.source.model.trim()
|
|
189
|
+
&& binding.ports && typeof binding.ports.model?.generate === 'function'
|
|
190
|
+
&& typeof binding.ports.tools?.execute === 'function', 'native_dsh_turn_binding_invalid');
|
|
191
|
+
const sessionId = String(agent.session.id), scope = structuredClone(binding.scope);
|
|
192
|
+
const runId = background ? admission.identity.runId
|
|
193
|
+
: nativeDshRunId(scope, sessionId, turn);
|
|
194
|
+
invariant(binding.taskContractFactory === undefined || typeof binding.taskContractFactory === 'function', 'native_dsh_contract_factory_invalid');
|
|
195
|
+
invariant(binding.releaseResources === undefined || typeof binding.releaseResources === 'function', 'native_dsh_resource_release_invalid');
|
|
196
|
+
invariant(binding.consumeCompletedTurn === undefined || typeof binding.consumeCompletedTurn === 'function', 'native_dsh_completed_turn_consumer_invalid');
|
|
197
|
+
const key = canonical({ scope, sessionId });
|
|
198
|
+
invariant(!active.has(key), 'native_dsh_session_already_driving');
|
|
199
|
+
active.add(key);
|
|
200
|
+
let live, failed = false, originalError, releaseControls, cancelListener, admittedRun = false;
|
|
201
|
+
try {
|
|
202
|
+
signal.throwIfAborted();
|
|
203
|
+
if (background) invariant(background.source.configuration.hostRecoveryBinding?.source
|
|
204
|
+
&& canonical(binding.source) === canonical(background.source.configuration.hostRecoveryBinding.source),
|
|
205
|
+
'native_dsh_background_source_changed');
|
|
206
|
+
const head = store.sessionHead(scope, sessionId);
|
|
207
|
+
if (head && head.runId !== runId) {
|
|
208
|
+
const previous = store.load(scope, head.runId);
|
|
209
|
+
// Interrupted is a terminal phase for the worker, not proof that its
|
|
210
|
+
// effects finished. Cancellation can also leave an unknown write.
|
|
211
|
+
// Requiring reconciliation prevents a new turn from racing that work.
|
|
212
|
+
const unknownEffect = unresolvedEffects(previous);
|
|
213
|
+
const unsettledChildren = (previous.snapshot.childBudgetReservations ?? []).some(item => item.settledUsageDigest === null);
|
|
214
|
+
const readInterrupted = canCloseNativeReadInterruption(previous) || reviewedBackgroundInterruption(previous)
|
|
215
|
+
|| reviewedResourceInterruption(previous);
|
|
216
|
+
if (!terminal(head.phase) || head.phase === 'interrupted' && !readInterrupted
|
|
217
|
+
|| unknownEffect && !readInterrupted || unsettledChildren) {
|
|
218
|
+
throw failure('NATIVE_RUN_RECOVERY_REQUIRED', previous.snapshot);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
let existing = null, existingConfiguration = null;
|
|
222
|
+
try { const record = store.load(scope, runId); existing = record.snapshot; existingConfiguration = record.configuration; }
|
|
223
|
+
catch (error) { if (!(isNativeError(error) && error.code === 'run_not_found')) throw error; }
|
|
224
|
+
if (admission) {
|
|
225
|
+
invariant(background ? canonical(scope) === canonical(admission.identity.scope)
|
|
226
|
+
&& sessionId === admission.identity.sessionId && binding.buildId === admission.identity.buildId
|
|
227
|
+
&& binding.taskChainId === admission.identity.taskChainId
|
|
228
|
+
: binding.taskChainId === undefined ? admission.identity.taskChainId === sessionId
|
|
229
|
+
: binding.taskChainId === admission.identity.taskChainId, 'native_dsh_recovery_identity_mismatch');
|
|
230
|
+
invariant(existing ? head?.runId === runId && existing.parentRunId === admission.identity.parentRunId
|
|
231
|
+
: (head?.runId ?? null) === admission.identity.parentRunId, 'native_dsh_recovery_superseded');
|
|
232
|
+
invariant(!existing || existing.configDigest === admission.configDigest, 'native_dsh_recovery_configuration_changed');
|
|
233
|
+
}
|
|
234
|
+
// Redelivery compiles against the original parent, even after later
|
|
235
|
+
// turns have advanced this Session. A new turn pins the current head.
|
|
236
|
+
const parentRunId = existing ? existing.parentRunId : head?.runId ?? null;
|
|
237
|
+
const parent = parentRunId ? store.load(scope, parentRunId) : null;
|
|
238
|
+
const contract = admission ? admission.configuration.contract : binding.taskContractFactory ? await resolveWhileLive(() => binding.taskContractFactory({
|
|
239
|
+
scope: structuredClone(scope), sessionId, runId, parentRunId,
|
|
240
|
+
parentGoal: parent?.snapshot.goal ?? null,
|
|
241
|
+
parentContract: structuredClone(parent?.snapshot.taskContract ?? parent?.configuration.contract ?? null),
|
|
242
|
+
goal: binding.configuration.contract.objective, signal,
|
|
243
|
+
}), payload) : binding.configuration.contract;
|
|
244
|
+
signal.throwIfAborted();
|
|
245
|
+
invariant(contract && typeof contract === 'object' && !Array.isArray(contract)
|
|
246
|
+
&& contract.objective === binding.configuration.contract.objective, 'native_dsh_task_contract_invalid');
|
|
247
|
+
// An optional locator introduced by a newer Host must not rewrite an
|
|
248
|
+
// older Run's identity during exact same-turn redelivery. Every original
|
|
249
|
+
// execution/configuration field still participates in start's digest CAS.
|
|
250
|
+
let frozenBinding = binding;
|
|
251
|
+
if (existingConfiguration && existingConfiguration.hostRecoveryBinding === undefined
|
|
252
|
+
&& binding.configuration.hostRecoveryBinding !== undefined) {
|
|
253
|
+
const legacy = { ...binding.configuration }; delete legacy.hostRecoveryBinding;
|
|
254
|
+
frozenBinding = { ...binding, configuration: legacy };
|
|
255
|
+
}
|
|
256
|
+
const configuration = background ? configure(binding.configuration) : admission ? rebindNativeDshConfiguration({ configuration: admission.configuration,
|
|
257
|
+
snapshot: { ...admission.identity, configDigest: admission.configDigest } }, binding, payload)
|
|
258
|
+
: freezeNativeDshConfiguration(frozenBinding, input, contract);
|
|
259
|
+
if (background) invariant(existing && hash(configuration) === admission.configDigest
|
|
260
|
+
&& canonical(configuration) === canonical(admission.configuration), 'native_dsh_recovery_configuration_changed');
|
|
261
|
+
if (durableOwnership && existing) invariant(hash(configuration) === existing.configDigest, 'run_creation_conflict');
|
|
262
|
+
const delivery = createNativeSessionDelivery({ store, scope, runId, session: agent.session, turn, projections, flush,
|
|
263
|
+
inputMessages: input.messages, systemText: configuration.instructions, source: binding.source,
|
|
264
|
+
...(background ? { backgroundOwnership: nativeDshBackgroundOwnership({ record: background.record, turn }) } : {}),
|
|
265
|
+
isStreamPending: key => live.pending(key), onStreamSettled: (...args) => live.settled(...args) });
|
|
266
|
+
live = nativeLiveStream({ agent, runId, turn, revisions, configuration, beginStep: delivery.beginStream,
|
|
267
|
+
publish: publishStreamFrame ? frame => publishStreamFrame(payload, frame) : undefined });
|
|
268
|
+
const kernel = new NativeKernel({ store, ports: binding.ports, digest: hash, owner,
|
|
269
|
+
onStream: (identity, event, record) => {
|
|
270
|
+
live.accept(identity, record);
|
|
271
|
+
onStream?.(payload, identity, event);
|
|
272
|
+
},
|
|
273
|
+
onCheckpoint: async snapshot => {
|
|
274
|
+
await delivery(snapshot);
|
|
275
|
+
if (onCheckpoint && !signal.aborted) await resolveWhileLive(
|
|
276
|
+
() => onCheckpoint(payload, structuredClone(snapshot)), payload);
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const identity = background ? admission.identity : { scope, runId, sessionId, turnId: String(turn), buildId: binding.buildId, parentRunId,
|
|
280
|
+
taskChainId: binding.taskChainId ?? sessionId };
|
|
281
|
+
const start = () => background ? store.load(scope, runId).snapshot : kernel.start({ ...identity, parentRunId: null }, configuration,
|
|
282
|
+
durableOwnership || binding.taskContractFactory ? { expectedParentRunId: parentRunId } : {});
|
|
283
|
+
const persistCancel = () => {
|
|
284
|
+
const commandId = `${runId}:dsh-turn-cancel`;
|
|
285
|
+
const original = store.load(scope, runId).commands.find(row => row.command.commandId === commandId)?.command;
|
|
286
|
+
return kernel.submit(scope, runId, original ?? { commandId, kind: 'cancel',
|
|
287
|
+
payload: durableOwnership ? { dshCancelReason: cancelReason(signal.reason) ?? { kind: 'disposed' } } : {} });
|
|
288
|
+
};
|
|
289
|
+
if (durableOwnership) {
|
|
290
|
+
const host = nativeDshOwnershipHost(agent);
|
|
291
|
+
host.claim(background ? nativeDshBackgroundOwnership({ record: background.record, turn })
|
|
292
|
+
: nativeDshOwnership({ identity, configuration, input, source: binding.source }));
|
|
293
|
+
try {
|
|
294
|
+
invariant(await flushOwnership(flush, payload, ownershipFlushTimeoutMs) === true, 'native_dsh_ownership_not_durable');
|
|
295
|
+
} catch (error) {
|
|
296
|
+
// Cancellation must outlive this process even if the Session barrier
|
|
297
|
+
// failed. Creating a canceled intent never dispatches an effect.
|
|
298
|
+
if (signal.aborted) { start(); persistCancel(); }
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
start();
|
|
303
|
+
admittedRun = true;
|
|
304
|
+
if (signal.aborted) persistCancel();
|
|
305
|
+
else if (durableOwnership) {
|
|
306
|
+
cancelListener = persistCancel;
|
|
307
|
+
signal.addEventListener('abort', cancelListener, { once: true });
|
|
308
|
+
}
|
|
309
|
+
let wakeIdle = () => {};
|
|
310
|
+
releaseControls = onActive?.(payload, { kernel, scope: structuredClone(scope), sessionId, runId, wake: () => wakeIdle() });
|
|
311
|
+
invariant(releaseControls === undefined || typeof releaseControls === 'function', 'native_dsh_controls_cleanup_invalid');
|
|
312
|
+
let snapshot;
|
|
313
|
+
try {
|
|
314
|
+
snapshot = await kernel.runUntilIdle(scope, runId, { signal });
|
|
315
|
+
while (!terminal(snapshot.phase)) {
|
|
316
|
+
signal.throwIfAborted();
|
|
317
|
+
if (!onIdle) throw failure('NATIVE_TURN_SUSPENDED', snapshot);
|
|
318
|
+
const idle = new AbortController(), idlePayload = { ...payload, signal: AbortSignal.any([signal, idle.signal]) };
|
|
319
|
+
let command, woken = false;
|
|
320
|
+
wakeIdle = () => { woken = true; idle.abort(new NativeError('native_idle_woken')); };
|
|
321
|
+
try {
|
|
322
|
+
if (store.load(scope, runId).commands.some(row => !row.consumed)) wakeIdle();
|
|
323
|
+
command = await resolveWhileLive(() => onIdle(idlePayload, structuredClone(snapshot), {
|
|
324
|
+
waitForToolReceipt: binding.ports.tools.waitForReceipt,
|
|
325
|
+
}), idlePayload);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (!woken || signal.aborted) throw error;
|
|
328
|
+
snapshot = await kernel.runUntilIdle(scope, runId, { signal });
|
|
329
|
+
continue;
|
|
330
|
+
} finally { wakeIdle = () => {}; idle.abort(); }
|
|
331
|
+
if (!command) throw failure('NATIVE_TURN_SUSPENDED', snapshot);
|
|
332
|
+
const revision = snapshot.revision;
|
|
333
|
+
const receipt = kernel.submit(scope, runId, command);
|
|
334
|
+
invariant(receipt.status !== 'rejected', 'native_dsh_command_rejected');
|
|
335
|
+
snapshot = await kernel.runUntilIdle(scope, runId, { signal });
|
|
336
|
+
invariant(terminal(snapshot.phase) || snapshot.revision > revision, 'native_dsh_command_made_no_progress');
|
|
337
|
+
}
|
|
338
|
+
signal.throwIfAborted();
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (signal.aborted && !terminal(store.load(scope, runId).snapshot.phase)) {
|
|
341
|
+
// A turn can be canceled while no kernel worker is active (approval
|
|
342
|
+
// or host checkpoint). Persist first, then drain without allowing a
|
|
343
|
+
// late host callback to authorize another effect. A failed delivery
|
|
344
|
+
// leaves the cancel command durable for recovery.
|
|
345
|
+
persistCancel();
|
|
346
|
+
try { await kernel.runUntilIdle(scope, runId); }
|
|
347
|
+
catch (recoveryError) { throw new AggregateError([error, recoveryError], 'Native cancellation requires recovery'); }
|
|
348
|
+
}
|
|
349
|
+
if (durableOwnership && signal.aborted) {
|
|
350
|
+
const ended = store.load(scope, runId).snapshot;
|
|
351
|
+
if (terminal(ended.phase)) {
|
|
352
|
+
await delivery(ended);
|
|
353
|
+
return canceledEnding(signal);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
if (durableOwnership && snapshot.phase === 'canceled') return canceledEnding(undefined, store.load(scope, runId));
|
|
359
|
+
if (snapshot.reason === 'host_input_rejected' && snapshot.outcome === 'blocked') return { reason: { kind: 'blocked' } };
|
|
360
|
+
if (durableOwnership && snapshot.phase === 'failed') return { reason: { kind: 'error',
|
|
361
|
+
error: { code: 'NATIVE_TURN_FAILED', message: snapshot.reason ?? 'Native run failed' } } };
|
|
362
|
+
if (durableOwnership && canCloseNativeReadInterruption(store.load(scope, runId))) return { reason: { kind: 'error',
|
|
363
|
+
error: { code: 'NATIVE_READ_INTERRUPTED', message: 'A read result was not confirmed. Obtain a fresh observation before continuing.' } } };
|
|
364
|
+
if (durableOwnership && reviewedBackgroundInterruption(store.load(scope, runId))) return { reason: { kind: 'error',
|
|
365
|
+
error: { code: 'NATIVE_BACKGROUND_RESULT_UNKNOWN', message: 'Waiting ended after operator review. The original result remains unknown.' } } };
|
|
366
|
+
if (snapshot.phase === 'canceled') { signal.throwIfAborted(); throw failure('NATIVE_TURN_CANCELED', snapshot); }
|
|
367
|
+
if (snapshot.phase !== 'completed') throw failure('NATIVE_TURN_FAILED', snapshot);
|
|
368
|
+
return { reason: { kind: snapshot.outcome === 'blocked' ? 'blocked' : 'completed' } };
|
|
369
|
+
} catch (error) {
|
|
370
|
+
failed = true; originalError = error;
|
|
371
|
+
// An unfinished journal is not an explanation for a detached worker.
|
|
372
|
+
// Report control facts without copying prompts, tool output or exception
|
|
373
|
+
// messages. Diagnostic failures cannot replace the original exception.
|
|
374
|
+
if (onDriverError) {
|
|
375
|
+
let snapshot;
|
|
376
|
+
try { snapshot = store.load(scope, runId).snapshot; } catch { /* preserve the original failure */ }
|
|
377
|
+
const code = typeof error?.code === 'string' && /^[a-z0-9_.:-]{1,128}$/i.test(error.code)
|
|
378
|
+
? error.code : 'native_driver_failed';
|
|
379
|
+
try { await onDriverError({ contract: 'agentkit.driver-diagnostic/v1', runId, sessionId,
|
|
380
|
+
phase: snapshot?.phase ?? null, revision: snapshot?.revision ?? null,
|
|
381
|
+
operationKey: snapshot?.pendingIntent ?? null, code, canceled: signal.aborted }); }
|
|
382
|
+
catch { /* diagnostics do not change execution or recovery semantics */ }
|
|
383
|
+
}
|
|
384
|
+
throw error;
|
|
385
|
+
} finally {
|
|
386
|
+
if (cancelListener) signal.removeEventListener('abort', cancelListener);
|
|
387
|
+
try {
|
|
388
|
+
try {
|
|
389
|
+
if (admittedRun && binding.consumeCompletedTurn) await consumeNativeCompletedTurn(binding, store.load(scope, runId), signal);
|
|
390
|
+
} catch { /* A failed optional owner lookup cannot suppress resource cleanup. */ }
|
|
391
|
+
const cleanup = await Promise.allSettled([
|
|
392
|
+
Promise.resolve().then(() => releaseControls?.()),
|
|
393
|
+
Promise.resolve().then(() => live?.abandon()),
|
|
394
|
+
Promise.resolve().then(() => binding.releaseResources?.({ scope: structuredClone(scope), sessionId, runId })),
|
|
395
|
+
]);
|
|
396
|
+
const errors = cleanup.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
397
|
+
if (errors.length) throw new AggregateError(failed ? [originalError, ...errors] : errors, 'Native turn resource cleanup failed');
|
|
398
|
+
} finally { active.delete(key); }
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
const execute = async payload => {
|
|
402
|
+
// A delivered-but-unclosed turn already has immutable admission. SDK
|
|
403
|
+
// redelivery must use the same trusted rebinding as explicit recovery,
|
|
404
|
+
// rather than silently recompiling newer Host defaults. Pending ownership
|
|
405
|
+
// supplies facts, never ports or permission; resolveRecoveryTurn rechecks
|
|
406
|
+
// the actual preset before the worker can run.
|
|
407
|
+
if (durableOwnership && typeof resolveRecoveryTurn === 'function') {
|
|
408
|
+
const external = payload?.agent?.externalTurns;
|
|
409
|
+
const pending = external?.contract === NATIVE_DSH_OWNERSHIP_HOST && typeof external.pending === 'function'
|
|
410
|
+
? external.pending() : null;
|
|
411
|
+
if (pending?.engine === NATIVE_DSH_TURN_CONTRACT && pending.turn === payload.turn) {
|
|
412
|
+
const admission = readNativeDshOwnership(pending, { sessionId: payload.agent.session.id, turn: payload.turn });
|
|
413
|
+
invariant(Array.isArray(payload.input?.messages) && canonical(payload.input.messages) === canonical(admission.messages),
|
|
414
|
+
'native_dsh_recovery_input_changed');
|
|
415
|
+
return run(payload, admission);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return run(payload);
|
|
419
|
+
};
|
|
420
|
+
/** Release only the SDK presentation owner after an explicit durable review.
|
|
421
|
+
* No original port is rebound, and no effect, budget or verdict is changed. */
|
|
422
|
+
const closeReviewedInterruption = (reviewed, code, message) => ({ agent, scope, runId, signal }) => {
|
|
423
|
+
invariant(durableOwnership, 'native_dsh_ownership_host_required');
|
|
424
|
+
signal?.throwIfAborted();
|
|
425
|
+
const host = nativeDshOwnershipHost(agent), owner = host.pending();
|
|
426
|
+
invariant(owner?.engine === NATIVE_DSH_TURN_CONTRACT, 'native_dsh_interruption_review_owner_mismatch');
|
|
427
|
+
return host.resume(NATIVE_DSH_TURN_CONTRACT, async resumed => {
|
|
428
|
+
signal?.throwIfAborted(); resumed.signal.throwIfAborted();
|
|
429
|
+
const admission = readNativeDshOwnership(resumed.ownership, { sessionId: agent.session.id, turn: resumed.turn });
|
|
430
|
+
const record = store.load(scope, runId);
|
|
431
|
+
invariant(admission.identity.runId === runId && canonical(admission.identity.scope) === canonical(scope)
|
|
432
|
+
&& canonical(resumed.ownership) === canonical(owner) && record.snapshot.sessionId === agent.session.id
|
|
433
|
+
&& admission.configDigest === record.snapshot.configDigest && reviewed(record),
|
|
434
|
+
'native_dsh_interruption_review_owner_mismatch');
|
|
435
|
+
const delivery = createNativeSessionDelivery({ store, scope, runId, session: agent.session, turn: resumed.turn,
|
|
436
|
+
projections, flush, inputMessages: admission.messages, systemText: record.configuration.instructions, source: admission.source });
|
|
437
|
+
await delivery(record.snapshot);
|
|
438
|
+
signal?.throwIfAborted(); resumed.signal.throwIfAborted();
|
|
439
|
+
return { reason: { kind: 'error', error: { code, message } } };
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
execute.closeReviewedBackgroundInterruption = closeReviewedInterruption(reviewedBackgroundInterruption,
|
|
443
|
+
'NATIVE_BACKGROUND_RESULT_UNKNOWN', 'Waiting ended after operator review. The original result remains unknown; do not replay it.');
|
|
444
|
+
execute.closeRetiredResources = closeReviewedInterruption(reviewedResourceInterruption,
|
|
445
|
+
'NATIVE_RESOURCE_RESULT_UNKNOWN', 'Disposable resources fenced after review. Original results remain unknown; do not replay old actions.');
|
|
446
|
+
/** Trusted Host recovery: resolve original ports, then drive the same owned turn and Run. */
|
|
447
|
+
execute.resume = ({ agent, ...request }) => {
|
|
448
|
+
invariant(durableOwnership && typeof resolveRecoveryTurn === 'function', 'native_dsh_recovery_resolver_required');
|
|
449
|
+
const host = nativeDshOwnershipHost(agent);
|
|
450
|
+
return host.resume(NATIVE_DSH_TURN_CONTRACT, resumed => {
|
|
451
|
+
const admission = readNativeDshOwnership(resumed.ownership, { sessionId: agent.session.id, turn: resumed.turn });
|
|
452
|
+
return run({ ...request, ...resumed, agent,
|
|
453
|
+
signal: request.signal ? AbortSignal.any([request.signal, resumed.signal]) : resumed.signal,
|
|
454
|
+
input: { kind: 'enter', messages: structuredClone(admission.messages) } }, admission);
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
/** Drive only the persisted notification identified by an already claimed owner. */
|
|
458
|
+
execute.background = async ({ ownership, ...payload }) => {
|
|
459
|
+
invariant(durableOwnership && typeof resolveBackgroundTurn === 'function', 'native_dsh_background_resolver_required');
|
|
460
|
+
const pending = nativeDshOwnershipHost(payload.agent).pending();
|
|
461
|
+
invariant(pending && canonical(pending) === canonical(ownership), 'native_dsh_background_owner_required');
|
|
462
|
+
const admission = readNativeDshBackgroundOwnership(ownership, { sessionId: payload.agent?.session?.id, turn: payload.turn });
|
|
463
|
+
const facts = { store, scope: admission.identity.scope, sessionId: payload.agent.session.id, turn: payload.turn };
|
|
464
|
+
const key = canonical({ scope: facts.scope, sessionId: facts.sessionId });
|
|
465
|
+
invariant(!backgroundDrivers.has(key), 'native_dsh_session_already_driving');
|
|
466
|
+
backgroundDrivers.add(key);
|
|
467
|
+
let verified;
|
|
468
|
+
try {
|
|
469
|
+
verified = await verifyNativeDshBackgroundOwnership(ownership, { ...facts, signal: payload.signal });
|
|
470
|
+
return await run({ ...payload, input: { kind: 'enter', messages: [] } }, verified.admission, verified);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (payload.signal?.aborted) {
|
|
473
|
+
try {
|
|
474
|
+
// Cancellation before port rebinding must also survive a restart.
|
|
475
|
+
// This local ledger reconciliation has no observer/model/tool port;
|
|
476
|
+
// it can only validate the existing notice and enqueue its cancel.
|
|
477
|
+
verified ??= await verifyNativeDshBackgroundOwnership(ownership, { ...facts, signal: new AbortController().signal });
|
|
478
|
+
const { identity } = verified.admission, record = store.load(identity.scope, identity.runId);
|
|
479
|
+
if (!['completed', 'failed', 'canceled'].includes(record.snapshot.phase)) {
|
|
480
|
+
const commandId = `${identity.runId}:dsh-turn-cancel`;
|
|
481
|
+
const previous = record.commands.find(row => row.command.commandId === commandId)?.command;
|
|
482
|
+
new NativeKernel({ store, digest: hash, owner, ports: {} }).submit(identity.scope, identity.runId,
|
|
483
|
+
previous ?? { commandId, kind: 'cancel', payload: { dshCancelReason: cancelReason(payload.signal.reason) ?? { kind: 'disposed' } } });
|
|
484
|
+
}
|
|
485
|
+
} catch (recoveryError) { throw new AggregateError([error, recoveryError], 'Native background cancellation requires recovery'); }
|
|
486
|
+
}
|
|
487
|
+
throw error;
|
|
488
|
+
} finally { backgroundDrivers.delete(key); }
|
|
489
|
+
};
|
|
490
|
+
/** Recover an unclosed background delivery position without admitting a new task. */
|
|
491
|
+
execute.resumeBackground = ({ agent, ...request }) => {
|
|
492
|
+
invariant(durableOwnership && typeof resolveBackgroundTurn === 'function', 'native_dsh_background_resolver_required');
|
|
493
|
+
return nativeDshOwnershipHost(agent).resume(NATIVE_DSH_BACKGROUND_TURN_CONTRACT, resumed => execute.background({
|
|
494
|
+
...request, ...resumed, agent, signal: request.signal ? AbortSignal.any([request.signal, resumed.signal]) : resumed.signal,
|
|
495
|
+
}));
|
|
496
|
+
};
|
|
497
|
+
return execute;
|
|
498
|
+
}
|