@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.
Files changed (151) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +1 -0
  3. package/README.md +124 -2
  4. package/THIRD_PARTY_NOTICES.md +66 -0
  5. package/ZCode-LICENSE +202 -0
  6. package/dist/index.mjs +4871 -0
  7. package/dist/native-command-plan.LICENSE.txt +221 -0
  8. package/dist/native-command-plan.json +16 -0
  9. package/dist/native-command-plan.mjs +8 -0
  10. package/dist/rpc-entry.mjs +5682 -0
  11. package/package.json +169 -4
  12. package/src/native/a2a-receipt-source.mjs +19 -0
  13. package/src/native/a2a-tools.mjs +517 -0
  14. package/src/native/activity-status.mjs +30 -0
  15. package/src/native/artifact-codec.mjs +481 -0
  16. package/src/native/artifact-store.mjs +1162 -0
  17. package/src/native/assistant-stream.mjs +76 -0
  18. package/src/native/background-command-port.mjs +95 -0
  19. package/src/native/browser-action-observation.mjs +43 -0
  20. package/src/native/browser-availability.mjs +22 -0
  21. package/src/native/browser-canvas-metrics.mjs +71 -0
  22. package/src/native/browser-dom.mjs +193 -0
  23. package/src/native/browser-playwright.mjs +1249 -0
  24. package/src/native/browser-vision.mjs +369 -0
  25. package/src/native/browser-workspace-policy.mjs +22 -0
  26. package/src/native/browser-workspace-protocol.mjs +95 -0
  27. package/src/native/browser-workspace-server.mjs +164 -0
  28. package/src/native/browser-workspace.mjs +355 -0
  29. package/src/native/captured-source.mjs +3 -0
  30. package/src/native/child-budget-allocation.mjs +87 -0
  31. package/src/native/child-capabilities.mjs +35 -0
  32. package/src/native/child-command-resource.mjs +20 -0
  33. package/src/native/child-history.mjs +188 -0
  34. package/src/native/child-merge.mjs +98 -0
  35. package/src/native/child-model-surface.mjs +19 -0
  36. package/src/native/child-observation.mjs +100 -0
  37. package/src/native/child-owner.mjs +620 -0
  38. package/src/native/child-publication.mjs +76 -0
  39. package/src/native/child-readers.mjs +366 -0
  40. package/src/native/child-tools.mjs +46 -0
  41. package/src/native/child-workspace-binding.mjs +29 -0
  42. package/src/native/child-workspace-tools.mjs +1 -0
  43. package/src/native/child-workspaces.mjs +655 -0
  44. package/src/native/children.mjs +365 -0
  45. package/src/native/command-plan-worker.mjs +14 -0
  46. package/src/native/command-plan.mjs +31 -0
  47. package/src/native/command-progress.mjs +64 -0
  48. package/src/native/command-shell.mjs +9 -0
  49. package/src/native/command-workspace-observation.mjs +36 -0
  50. package/src/native/component-tools.mjs +280 -0
  51. package/src/native/context-counter.mjs +133 -0
  52. package/src/native/context-markers.mjs +51 -0
  53. package/src/native/context-profile.mjs +72 -0
  54. package/src/native/context-status.mjs +27 -0
  55. package/src/native/conversation-command-control.mjs +133 -0
  56. package/src/native/delivery-timeline.mjs +39 -0
  57. package/src/native/dsh-background-ownership.mjs +102 -0
  58. package/src/native/dsh-managed-ownership.mjs +23 -0
  59. package/src/native/dsh-turn-contract.mjs +3 -0
  60. package/src/native/dsh-turn-ownership.mjs +54 -0
  61. package/src/native/dsh-turn-runtime.mjs +498 -0
  62. package/src/native/e2b-background-supervisor.mjs +347 -0
  63. package/src/native/e2b-background.mjs +368 -0
  64. package/src/native/e2b-command-state.mjs +40 -0
  65. package/src/native/e2b-command-tools.mjs +486 -0
  66. package/src/native/e2b-sandbox-owner.mjs +227 -0
  67. package/src/native/hook-executor.mjs +249 -0
  68. package/src/native/instruction-assembly.mjs +60 -0
  69. package/src/native/instruction-context.mjs +54 -0
  70. package/src/native/local-background-supervisor.mjs +108 -0
  71. package/src/native/local-background.mjs +207 -0
  72. package/src/native/local-command-evidence.mjs +57 -0
  73. package/src/native/local-command-policy-worker.mjs +197 -0
  74. package/src/native/local-command-policy.mjs +46 -0
  75. package/src/native/local-command-state.mjs +177 -0
  76. package/src/native/local-command-tools.mjs +302 -0
  77. package/src/native/local-execution-profile.mjs +36 -0
  78. package/src/native/local-output-policy.mjs +92 -0
  79. package/src/native/local-process-bin/darwin-arm64/launcher +0 -0
  80. package/src/native/local-process-bin/darwin-x64/launcher +0 -0
  81. package/src/native/local-process-bin/linux-arm64/launcher +0 -0
  82. package/src/native/local-process-bin/linux-x64/launcher +0 -0
  83. package/src/native/local-process-launcher.c +183 -0
  84. package/src/native/local-process-licenses/GCC-COPYING.RUNTIME +72 -0
  85. package/src/native/local-process-licenses/GCC-COPYING3 +674 -0
  86. package/src/native/local-process-licenses/NOTICE +21 -0
  87. package/src/native/local-process-licenses/musl-COPYRIGHT +193 -0
  88. package/src/native/local-process-manifest.json +32 -0
  89. package/src/native/local-process.mjs +246 -0
  90. package/src/native/local-resource-profile.mjs +32 -0
  91. package/src/native/memory-candidate.mjs +121 -0
  92. package/src/native/memory-extraction-run.mjs +60 -0
  93. package/src/native/memory-extraction-tools.mjs +116 -0
  94. package/src/native/memory-recall.mjs +65 -0
  95. package/src/native/memory-store.mjs +334 -0
  96. package/src/native/memory-tools.mjs +329 -0
  97. package/src/native/model-anthropic-cache.mjs +49 -0
  98. package/src/native/model-anthropic-tool-search.mjs +85 -0
  99. package/src/native/model-anthropic.mjs +753 -0
  100. package/src/native/model-chat-usage.mjs +24 -0
  101. package/src/native/model-inline-reasoning.mjs +111 -0
  102. package/src/native/model-media.mjs +174 -0
  103. package/src/native/model-normalize.mjs +228 -0
  104. package/src/native/model-openai-chat.mjs +561 -0
  105. package/src/native/model-port.mjs +361 -0
  106. package/src/native/model-provider-compat.mjs +129 -0
  107. package/src/native/model-replay.mjs +153 -0
  108. package/src/native/model-request.mjs +15 -0
  109. package/src/native/model-responses.mjs +1205 -0
  110. package/src/native/model-sse.mjs +171 -0
  111. package/src/native/model-surface-host.mjs +27 -0
  112. package/src/native/model-tool-search-policy.mjs +16 -0
  113. package/src/native/model-transport-observation.mjs +27 -0
  114. package/src/native/owned-child.mjs +114 -0
  115. package/src/native/owned-command.mjs +106 -0
  116. package/src/native/python-syntax-worker.mjs +60 -0
  117. package/src/native/python-syntax.mjs +50 -0
  118. package/src/native/read-interruption.mjs +16 -0
  119. package/src/native/result-stream.mjs +63 -0
  120. package/src/native/ruff-ast.mjs +95 -0
  121. package/src/native/run-observation.mjs +128 -0
  122. package/src/native/session-delivery-journal.mjs +96 -0
  123. package/src/native/session-delivery.mjs +434 -0
  124. package/src/native/shell-analysis.mjs +50 -0
  125. package/src/native/skill-disclosure.mjs +404 -0
  126. package/src/native/skill-events.mjs +164 -0
  127. package/src/native/skill-tools.mjs +813 -0
  128. package/src/native/source-page.mjs +73 -0
  129. package/src/native/sqlite-session-inbox.mjs +42 -0
  130. package/src/native/sqlite-store.mjs +617 -0
  131. package/src/native/tool-authority.mjs +99 -0
  132. package/src/native/tool-guidance.mjs +12 -0
  133. package/src/native/tool-observation.mjs +75 -0
  134. package/src/native/tool-states.mjs +14 -0
  135. package/src/native/verification-authority.mjs +37 -0
  136. package/src/native/web-tools.mjs +643 -0
  137. package/src/native/workspace-checkpoints.mjs +312 -0
  138. package/src/native/workspace-context.mjs +61 -0
  139. package/src/native/workspace-display.mjs +39 -0
  140. package/src/native/workspace-edit-match.mjs +19 -0
  141. package/src/native/workspace-edit-worker.mjs +15 -0
  142. package/src/native/workspace-editing.mjs +447 -0
  143. package/src/native/workspace-image.mjs +72 -0
  144. package/src/native/workspace-instructions.mjs +107 -0
  145. package/src/native/workspace-mutation-lock.mjs +74 -0
  146. package/src/native/workspace-patch-worker.mjs +17 -0
  147. package/src/native/workspace-patch.mjs +17 -0
  148. package/src/native/workspace-paths.mjs +225 -0
  149. package/src/native/workspace-state.mjs +272 -0
  150. package/src/native/workspace-subject-observer.mjs +84 -0
  151. 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
+ }