@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.
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,133 @@
1
+ // Host-only local-background cleanup controller. This is a private Host
2
+ // capability for conversation rewind: it cancels removed-branch local background
3
+ // commands while preserving retained rows exactly. It is not an advertised
4
+ // model tool, never falls back to an environment backend, never launches
5
+ // processes, fakes tools, mutates Native Run ledgers or marks notified. Retry
6
+ // is idempotent via the existing durable cancel file and terminal record.
7
+ import { canonical, copy, NativeError, backgroundCommandRef, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
8
+ import { ownedCommandReceipt } from './owned-command.mjs';
9
+ import { LocalCommandStore } from './local-background.mjs';
10
+ import { TERMINAL } from './local-command-state.mjs';
11
+ import { lstatSync, realpathSync } from 'node:fs';
12
+
13
+ export const CONVERSATION_COMMAND_CONTROL_CONTRACT = 'agentkit.native-conversation-command-control/v1';
14
+ const STOP_WAIT_MS = 5000;
15
+ const ACCEPTED_BACKEND = 'local_process';
16
+ // wait() already rejects 'unknown'; this set is the explicit fail-closed guard.
17
+ const KNOWN_TERMINAL = new Set([...TERMINAL].filter(status => status !== 'unknown'));
18
+
19
+ const check = (condition, code) => { if (!condition) throw new NativeError(code); };
20
+ const same = (a, b) => canonical(a) === canonical(b);
21
+
22
+ export function createNativeConversationCommandControl({ workspaceRoot, stateRoot, scope, store, sessionId, isClosed = () => false }) {
23
+ check(typeof workspaceRoot === 'string' && !!workspaceRoot, 'conversation_command_workspace_root_invalid');
24
+ check(typeof stateRoot === 'string' && !!stateRoot, 'conversation_command_state_root_invalid');
25
+ check(typeof sessionId === 'string' && !!sessionId.trim(), 'conversation_command_session_id_invalid');
26
+ check(typeof isClosed === 'function', 'conversation_command_is_closed_invalid');
27
+ check(store && typeof store.load === 'function' && typeof store.sessionHead === 'function', 'conversation_command_store_invalid');
28
+ scopeKey(scope);
29
+ const pinnedScope = copy(scope);
30
+ // The owner binding is digest([realpath(workspace), 'local_process', sessionId]);
31
+ // commandId(operationKey) is digest([owner, operationKey]). Verifying the ref's
32
+ // taskId against this binding is what ties a head row to this local store.
33
+ // Match the launch transport's physical paths (macOS /var is an alias).
34
+ // The existing state directory itself must not be a substituted symlink.
35
+ check(!lstatSync(stateRoot).isSymbolicLink(), 'background_state_symlink');
36
+ const pinnedWorkspace = realpathSync(workspaceRoot), pinnedState = realpathSync(stateRoot);
37
+ const localStore = new LocalCommandStore({ stateRoot: pinnedState, workspaceRoot: pinnedWorkspace, sessionId });
38
+
39
+ async function settle({ record, retainedRefs, requestDigest, signal, assertCurrent }) {
40
+ check(record && typeof record === 'object' && record.snapshot && typeof record.snapshot === 'object',
41
+ 'conversation_command_record_invalid');
42
+ check(signal instanceof AbortSignal, 'conversation_command_signal_required');
43
+ check(typeof assertCurrent === 'function', 'conversation_command_assert_current_required');
44
+ check(typeof requestDigest === 'string' && !!requestDigest, 'conversation_command_request_digest_invalid');
45
+ check(Array.isArray(retainedRefs), 'conversation_command_retained_refs_invalid');
46
+ signal.throwIfAborted();
47
+ const originalRecord = canonical(record), originalRefs = canonical(retainedRefs);
48
+ const pinnedRecord = copy(record);
49
+ check(same(pinnedRecord.snapshot.scope, pinnedScope), 'conversation_command_scope_mismatch');
50
+ check(pinnedRecord.snapshot.sessionId === sessionId, 'conversation_command_session_mismatch');
51
+ // assertCurrent is synchronous and must return exactly true, never a thenable.
52
+ const guard = () => {
53
+ signal.throwIfAborted();
54
+ check(!isClosed(), 'conversation_command_control_closed');
55
+ check(realpathSync(workspaceRoot) === pinnedWorkspace && realpathSync(stateRoot) === pinnedState,
56
+ 'conversation_command_workspace_changed');
57
+ check(canonical(record) === originalRecord && canonical(retainedRefs) === originalRefs,
58
+ 'conversation_command_input_changed');
59
+ const head = store.sessionHead(pinnedScope, sessionId);
60
+ check(head && head.runId === pinnedRecord.snapshot.runId, 'conversation_command_head_mismatch');
61
+ check(canonical(store.load(pinnedScope, head.runId)) === originalRecord, 'conversation_command_record_mismatch');
62
+ const result = assertCurrent();
63
+ if (result && typeof result.then === 'function') {
64
+ Promise.resolve(result).catch(() => {});
65
+ throw new NativeError('conversation_command_authority_thenable');
66
+ }
67
+ check(result === true, 'conversation_command_authority_lost');
68
+ signal.throwIfAborted();
69
+ check(canonical(record) === originalRecord && canonical(retainedRefs) === originalRefs
70
+ && store.sessionHead(pinnedScope, sessionId)?.runId === pinnedRecord.snapshot.runId
71
+ && canonical(store.load(pinnedScope, pinnedRecord.snapshot.runId)) === originalRecord,
72
+ 'conversation_command_input_changed');
73
+ };
74
+ // Validate retained refs (shape + no duplicate taskIds).
75
+ const retained = retainedRefs.map(item => backgroundCommandRef(item));
76
+ check(new Set(retained.map(ref => ref.taskId)).size === retained.length, 'conversation_command_duplicate_ref');
77
+ guard();
78
+ signal.throwIfAborted();
79
+
80
+ // Verify ALL head rows' launch provenance before any external stop. This
81
+ // covers retained rows too: a retained row with a broken receipt fails the
82
+ // whole settlement rather than leaving a partial stop behind.
83
+ const headRows = pinnedRecord.snapshot.backgroundCommands;
84
+ check(Array.isArray(headRows), 'conversation_command_record_invalid');
85
+ check(new Set(headRows.map(row => row.ref.taskId)).size === headRows.length, 'conversation_command_duplicate_ref');
86
+ const verified = [];
87
+ for (const row of headRows) {
88
+ const ref = backgroundCommandRef(row.ref);
89
+ const receipt = ownedCommandReceipt({ scope: pinnedScope, store, current: pinnedRecord, ref });
90
+ check(receipt.backend === ACCEPTED_BACKEND, 'conversation_command_unsupported_backend');
91
+ check(localStore.commandId(ref.operationKey) === ref.taskId, 'conversation_command_task_id_mismatch');
92
+ verified.push({ row, ref });
93
+ }
94
+ // Foreign retained refs: every retainedRef must match a verified head row.
95
+ for (const ref of retained) {
96
+ check(verified.some(item => same(item.ref, ref)), 'conversation_command_foreign_ref');
97
+ }
98
+ // Partition head rows in original snapshot order.
99
+ const retainedRows = [], removedRows = [];
100
+ for (const item of verified) {
101
+ if (retained.some(ref => same(ref, item.ref))) retainedRows.push(item);
102
+ else removedRows.push(item);
103
+ }
104
+ // Stop removed rows with a bounded wait; preserve retained rows (never stop).
105
+ const stopped = [];
106
+ for (const item of removedRows) {
107
+ signal.throwIfAborted();
108
+ const handle = localStore.connect(item.ref.taskId);
109
+ guard(); // before the external mutation (durable cancel file)
110
+ handle.requestCancel();
111
+ let output;
112
+ try {
113
+ output = await handle.wait({ timeoutMs: STOP_WAIT_MS, signal });
114
+ } catch (error) {
115
+ signal.throwIfAborted();
116
+ guard(); // after the await, even on the error path
117
+ throw error;
118
+ }
119
+ guard(); // after the await
120
+ check(KNOWN_TERMINAL.has(output.status), 'conversation_command_stop_status_unknown');
121
+ stopped.push({ ref: copy(item.ref), status: output.status });
122
+ }
123
+ guard();
124
+ return Object.freeze({
125
+ contract: CONVERSATION_COMMAND_CONTROL_CONTRACT,
126
+ requestDigest,
127
+ retained: retainedRows.map(item => copy(item.row)),
128
+ stopped,
129
+ });
130
+ }
131
+
132
+ return Object.freeze({ contract: CONVERSATION_COMMAND_CONTROL_CONTRACT, settle });
133
+ }
@@ -0,0 +1,39 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonical, invariant } from '@kingsoftcloud/agentkit-harness-core';
3
+ import { toolContext } from '@kingsoftcloud/agentkit-harness-core/tool-context';
4
+
5
+ const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
6
+ export const nativeDeliveryId = (scope, runId, key) => `ak-native-${hash({ identity: hash({ scope, runId }), key })}`;
7
+
8
+ /** Terminal delivery can close a call whose result never entered the transcript.
9
+ * Keep the same committed observation for delivery and later conversation cuts. */
10
+ export function nativeDeliveryClosure(record, callId) {
11
+ const intent = record.intents.find(item => item.kind === 'tool' && item.payload.calls?.some(call => call.callId === callId));
12
+ const receipt = intent && record.receipts.find(item => item.operationKey === intent.operationKey);
13
+ const index = intent?.payload.calls.findIndex(call => call.callId === callId);
14
+ const observed = receipt?.status === 'committed' && index >= 0 ? receipt.result.results?.[index] : null;
15
+ if (observed) return { context: observed.context === undefined ? { content: canonical(observed.content) } : toolContext(observed.context),
16
+ isError: observed.status !== 'ok' };
17
+ const unknown = intent?.status === 'running' && (!receipt || receipt.status === 'uncertain');
18
+ return { context: { content: canonical({ status: unknown ? 'uncertain' : 'not_dispatched',
19
+ reason: unknown ? 'effect_outcome_unknown' : record.snapshot.reason ?? 'run_ended' }) }, isError: true };
20
+ }
21
+
22
+ /** Rebuild the original immutable message positions before replaying output
23
+ * retirements. Both delivery and conversation cuts must use the same timeline. */
24
+ export function nativeDeliveryTimeline(record) {
25
+ const messages = structuredClone(record.snapshot.messages), retirements = new Map();
26
+ for (const event of record.events.filter(event => event.seq <= record.snapshot.eventSeq).toReversed()) {
27
+ if (event.kind !== 'output.restarted') continue;
28
+ const { retainedMessageCount: retained, retiredMessages: retired, notice } = event.payload;
29
+ invariant(Number.isSafeInteger(retained) && retained >= 0 && retained < messages.length && Array.isArray(retired)
30
+ && retired.every(message => message && ['user', 'assistant'].includes(message.role)
31
+ && typeof message.content === 'string' && ['continuation_fragment', 'continuation_observation'].includes(message.kind)
32
+ && Object.keys(message).every(key => ['role', 'kind', 'content'].includes(key)))
33
+ && canonical({ role: messages[retained].role, kind: messages[retained].kind ?? null, content: messages[retained].content })
34
+ === canonical(notice), 'native_delivery_output_restart_invalid');
35
+ retirements.set(event.seq, { retained, retired, notice: structuredClone(messages[retained]) });
36
+ messages.splice(retained, 1, ...structuredClone(retired));
37
+ }
38
+ return { messages, retirements };
39
+ }
@@ -0,0 +1,102 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { BACKGROUND_NOTIFICATION_ADMISSION, NATIVE_JSON_CODEC, NativeError, admitBackgroundNotification,
3
+ canonical, configure, invariant, parseNativeJson, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
4
+ import { nativeDshOwnershipHost } from './dsh-turn-ownership.mjs';
5
+
6
+ export const NATIVE_DSH_BACKGROUND_TURN_CONTRACT = 'agentkit.native-dsh-background-turn/v1';
7
+ export const NATIVE_DSH_BACKGROUND_ADMISSION = 'agentkit.native-dsh-background-admission/v1';
8
+ export const NATIVE_DSH_IDLE_ADMISSION = 'dsh.external-turn-start/v1';
9
+ const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
10
+ const text = value => typeof value === 'string' && value.trim().length > 0;
11
+ const digest = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
12
+ const fields = (value, names) => value && typeof value === 'object' && !Array.isArray(value)
13
+ && Object.keys(value).sort().join(',') === names;
14
+ const identityOf = snapshot => Object.fromEntries(['scope', 'runId', 'sessionId', 'turnId', 'buildId', 'parentRunId', 'taskChainId']
15
+ .map(key => [key, snapshot[key]]));
16
+
17
+ /** Serialize facts about an already admitted notice; no ports or new user input.
18
+ * Native notice IDs remain stable across crashes before DSH ownership is flushed.
19
+ * The numeric DSH turn is a separate delivery position, never a replacement ID.
20
+ */
21
+ export function nativeDshBackgroundOwnership({ record, turn }) {
22
+ const admission = { contract: NATIVE_DSH_BACKGROUND_ADMISSION, jsonCodec: NATIVE_JSON_CODEC,
23
+ identity: identityOf(record.snapshot), configuration: record.configuration,
24
+ configDigest: record.snapshot.configDigest, notification: record.snapshot.backgroundNotification };
25
+ const ownership = { contract: 'dsh.external-turn/v1', engine: NATIVE_DSH_BACKGROUND_TURN_CONTRACT,
26
+ turn, binding: canonical(admission) };
27
+ readNativeDshBackgroundOwnership(ownership, { sessionId: record.snapshot.sessionId, turn });
28
+ return ownership;
29
+ }
30
+
31
+ /** Parse detached recovery facts. Matching these facts never grants execution authority. */
32
+ export function readNativeDshBackgroundOwnership(ownership, { sessionId, turn }) {
33
+ invariant(fields(ownership, 'binding,contract,engine,turn') && ownership.contract === 'dsh.external-turn/v1'
34
+ && ownership.engine === NATIVE_DSH_BACKGROUND_TURN_CONTRACT && ownership.turn === turn
35
+ && Number.isSafeInteger(turn) && turn > 0 && text(ownership.binding), 'native_dsh_background_ownership_invalid');
36
+ const admission = parseNativeJson(ownership.binding);
37
+ invariant(fields(admission, 'configDigest,configuration,contract,identity,jsonCodec,notification')
38
+ && admission.contract === NATIVE_DSH_BACKGROUND_ADMISSION && admission.jsonCodec === NATIVE_JSON_CODEC
39
+ && canonical(admission) === ownership.binding, 'native_dsh_background_admission_invalid');
40
+ const { identity, configuration, notification } = admission;
41
+ invariant(fields(identity, 'buildId,parentRunId,runId,scope,sessionId,taskChainId,turnId')
42
+ && text(identity.sessionId) && identity.sessionId === sessionId && text(identity.buildId)
43
+ && text(identity.taskChainId) && text(identity.parentRunId), 'native_dsh_background_identity_invalid');
44
+ scopeKey(identity.scope);
45
+ invariant(fields(notification, 'admissionElapsedMs,contract,requestDigest,sourceConfigDigest,sourceRunId,taskId')
46
+ && notification.contract === BACKGROUND_NOTIFICATION_ADMISSION && text(notification.sourceRunId)
47
+ && notification.sourceRunId !== identity.runId && digest(notification.taskId) && digest(notification.requestDigest)
48
+ && digest(notification.sourceConfigDigest) && Number.isSafeInteger(notification.admissionElapsedMs)
49
+ && notification.admissionElapsedMs >= 0, 'native_dsh_background_origin_invalid');
50
+ invariant(identity.runId === 'wake_' + hash({ contract: BACKGROUND_NOTIFICATION_ADMISSION, scope: identity.scope,
51
+ sourceRunId: notification.sourceRunId, taskId: notification.taskId }) && identity.turnId === identity.runId,
52
+ 'native_dsh_background_identity_invalid');
53
+ invariant(hash(configuration) === admission.configDigest && canonical(configure(configuration)) === canonical(configuration)
54
+ && configuration.backgroundNotificationPolicy === 'completed-before-model/v1' && configuration.inputMessages === null,
55
+ 'native_dsh_background_configuration_invalid');
56
+ return admission;
57
+ }
58
+
59
+ function assertStoredAdmission(admission, record) {
60
+ invariant(canonical(identityOf(record.snapshot)) === canonical(admission.identity)
61
+ && record.snapshot.configDigest === admission.configDigest && hash(record.configuration) === admission.configDigest
62
+ && canonical(record.configuration) === canonical(admission.configuration)
63
+ && canonical(record.snapshot.backgroundNotification ?? null) === canonical(admission.notification),
64
+ 'native_dsh_background_stored_admission_changed');
65
+ }
66
+
67
+ /** Recheck the real ledger and original launch before trusted Host port rebinding.
68
+ * This only takes the core admission's duplicate path: a missing record cannot
69
+ * poll, recreate a notice, start a model or relaunch a command. A live notice
70
+ * must remain the session head; terminal notices can still be delivered again.
71
+ */
72
+ export async function verifyNativeDshBackgroundOwnership(ownership, { store, scope, sessionId, turn, signal }) {
73
+ invariant(signal instanceof AbortSignal, 'native_dsh_background_signal_required');
74
+ signal.throwIfAborted();
75
+ scopeKey(scope);
76
+ const admission = readNativeDshBackgroundOwnership(ownership, { sessionId, turn });
77
+ invariant(canonical(admission.identity.scope) === canonical(scope), 'native_dsh_background_scope_mismatch');
78
+ const { identity, notification } = admission;
79
+ assertStoredAdmission(admission, store.load(scope, identity.runId));
80
+ const duplicate = await admitBackgroundNotification({ store, scope, sourceRunId: notification.sourceRunId,
81
+ taskId: notification.taskId, digest: hash, signal,
82
+ observe: async () => { throw new NativeError('native_dsh_background_admission_missing'); } });
83
+ signal.throwIfAborted();
84
+ invariant(duplicate.created === false && duplicate.snapshot.runId === identity.runId,
85
+ 'native_dsh_background_admission_missing');
86
+ const record = store.load(scope, identity.runId);
87
+ assertStoredAdmission(admission, record);
88
+ const source = store.load(scope, notification.sourceRunId);
89
+ invariant(source.snapshot.sessionId === sessionId && source.snapshot.buildId === identity.buildId
90
+ && source.snapshot.taskChainId === identity.taskChainId && canonical(source.snapshot.scope) === canonical(scope),
91
+ 'native_dsh_background_source_identity_changed');
92
+ signal.throwIfAborted();
93
+ return { admission, record, source };
94
+ }
95
+
96
+ /** Require both the maintained provider version and its actual idle-start API. */
97
+ export function nativeDshIdleOwnershipHost(agent) {
98
+ const host = nativeDshOwnershipHost(agent);
99
+ invariant(host.idleAdmissionContract === NATIVE_DSH_IDLE_ADMISSION && typeof host.start === 'function',
100
+ 'native_dsh_idle_ownership_host_required');
101
+ return host;
102
+ }
@@ -0,0 +1,23 @@
1
+ import { canonical, invariant, parseNativeJson, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
2
+
3
+ export const NATIVE_DSH_MANAGED_TURN_CONTRACT = 'agentkit.native-managed-dsh-turn/v1';
4
+ const text = value => typeof value === 'string' && value.trim().length > 0;
5
+ const fields = (value, keys) => value && typeof value === 'object' && !Array.isArray(value)
6
+ && Object.keys(value).sort().join(',') === keys;
7
+
8
+ /** Persisted delivery identity, never an execution grant. The trusted Host must
9
+ * separately own the SDK turn and rebind the original Native configuration. */
10
+ export function readNativeDshManagedOwnership(owner, { sessionId, turn } = {}) {
11
+ invariant(fields(owner, 'binding,contract,engine,turn') && owner.contract === 'dsh.external-turn/v1'
12
+ && owner.engine === NATIVE_DSH_MANAGED_TURN_CONTRACT && Number.isSafeInteger(owner.turn) && owner.turn > 0
13
+ && (turn === undefined || turn === owner.turn) && text(owner.binding), 'NATIVE_MANAGED_OWNER_INVALID');
14
+ const value = parseNativeJson(owner.binding), identity = value.identity;
15
+ invariant(fields(value, 'configDigest,contract,identity') && value.contract === NATIVE_DSH_MANAGED_TURN_CONTRACT
16
+ && canonical(value) === owner.binding && /^[a-f0-9]{64}$/u.test(value.configDigest)
17
+ && fields(identity, 'buildId,parentRunId,runId,scope,sessionId,taskChainId,turnId')
18
+ && ['runId', 'sessionId', 'turnId', 'buildId', 'taskChainId'].every(key => text(identity[key]))
19
+ && (identity.parentRunId === null || text(identity.parentRunId))
20
+ && (sessionId === undefined || identity.sessionId === sessionId), 'NATIVE_MANAGED_OWNER_INVALID');
21
+ scopeKey(identity.scope);
22
+ return value;
23
+ }
@@ -0,0 +1,3 @@
1
+ /** Explicit Host/preset opt-in while full Native capability parity is pending. */
2
+ export const NATIVE_DSH_TURN_CONTRACT = 'agentkit.native-dsh-turn/v1';
3
+ export const NATIVE_DSH_CONTENT_FORMAT = 'agentkit.dsh-content/v1';
@@ -0,0 +1,54 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonical, configure, invariant, NATIVE_JSON_CODEC, parseNativeJson, scopeKey } from '@kingsoftcloud/agentkit-harness-core';
3
+ import { NATIVE_DSH_TURN_CONTRACT } from './dsh-turn-contract.mjs';
4
+
5
+ export const NATIVE_DSH_ADMISSION_CONTRACT = 'agentkit.native-dsh-admission/v1';
6
+ export const NATIVE_DSH_OWNERSHIP_HOST = 'dsh.external-turn-host/v1';
7
+ const hash = value => createHash('sha256').update(canonical(value)).digest('hex');
8
+ const text = value => typeof value === 'string' && value.trim().length > 0;
9
+ const fields = (value, names) => value && typeof value === 'object' && !Array.isArray(value)
10
+ && Object.keys(value).sort().join(',') === names;
11
+
12
+ /** Complete admission data, never live ports, credentials or execution authority. */
13
+ export function nativeDshOwnership({ identity, configuration, input, source }) {
14
+ const admission = { contract: NATIVE_DSH_ADMISSION_CONTRACT, jsonCodec: NATIVE_JSON_CODEC,
15
+ identity, configuration, configDigest: hash(configuration), messages: input.messages, source };
16
+ const ownership = { contract: 'dsh.external-turn/v1', engine: NATIVE_DSH_TURN_CONTRACT,
17
+ turn: Number(identity.turnId), binding: canonical(admission) };
18
+ readNativeDshOwnership(ownership, { sessionId: identity.sessionId, turn: ownership.turn });
19
+ return ownership;
20
+ }
21
+
22
+ /** Validate persisted facts before a trusted Host resolves the original preset. */
23
+ export function readNativeDshOwnership(ownership, { sessionId, turn }) {
24
+ invariant(fields(ownership, 'binding,contract,engine,turn') && ownership.contract === 'dsh.external-turn/v1'
25
+ && ownership.engine === NATIVE_DSH_TURN_CONTRACT && ownership.turn === turn
26
+ && Number.isSafeInteger(turn) && turn > 0 && text(ownership.binding), 'native_dsh_ownership_invalid');
27
+ const admission = parseNativeJson(ownership.binding);
28
+ invariant(fields(admission, 'configDigest,configuration,contract,identity,jsonCodec,messages,source')
29
+ && admission.contract === NATIVE_DSH_ADMISSION_CONTRACT && admission.jsonCodec === NATIVE_JSON_CODEC
30
+ && canonical(admission) === ownership.binding, 'native_dsh_admission_invalid');
31
+ const { identity, configuration, source, messages } = admission;
32
+ invariant(fields(identity, 'buildId,parentRunId,runId,scope,sessionId,taskChainId,turnId')
33
+ && text(identity.sessionId) && identity.sessionId === sessionId && identity.turnId === String(turn)
34
+ && text(identity.buildId) && text(identity.taskChainId)
35
+ && (identity.parentRunId === null || text(identity.parentRunId)), 'native_dsh_admission_identity_invalid');
36
+ scopeKey(identity.scope);
37
+ invariant(identity.runId === `native-dsh-${hash({ contract: NATIVE_DSH_TURN_CONTRACT,
38
+ scope: identity.scope, sessionId, turn })}`, 'native_dsh_admission_identity_invalid');
39
+ invariant(fields(source, 'model,provider') && text(source.model) && text(source.provider)
40
+ && Array.isArray(messages) && messages.length > 0
41
+ && messages.every(message => message?.role === 'user' && text(message.id) && Array.isArray(message.content)),
42
+ 'native_dsh_admission_input_invalid');
43
+ invariant(hash(configuration) === admission.configDigest
44
+ && canonical(configure(configuration)) === canonical(configuration), 'native_dsh_admission_configuration_invalid');
45
+ return admission;
46
+ }
47
+
48
+ /** The Host must provide the maintained lifecycle capability before any dispatch. */
49
+ export function nativeDshOwnershipHost(agent) {
50
+ const host = agent?.externalTurns;
51
+ invariant(host?.contract === NATIVE_DSH_OWNERSHIP_HOST && typeof host.claim === 'function'
52
+ && typeof host.pending === 'function' && typeof host.resume === 'function', 'native_dsh_ownership_host_required');
53
+ return host;
54
+ }