@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,312 @@
1
+ /** Native file-only rewind. A prepared
2
+ * journal is never an instruction to replay effects after process death. */
3
+ import { DatabaseSync } from 'node:sqlite';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { constants, closeSync, existsSync, fstatSync, lstatSync, openSync, readSync, readdirSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+ import { canonical } from '@kingsoftcloud/agentkit-harness-core';
8
+ import { privateDirectory, privateFile, openPrivate } from './local-command-state.mjs';
9
+ import { sessionKeyFor } from './workspace-state.mjs';
10
+ import { sha256, TWO_MB, realpathExisting } from './workspace-paths.mjs';
11
+
12
+ export const WORKSPACE_CHECKPOINT_POLICY = 'file-preimages/v1';
13
+ export const CONVERSATION_WORKSPACE_CONTROL = 'agentkit.native-conversation-workspace-control/v1';
14
+ const fail = code => { throw Object.assign(Error(code), { code }); };
15
+ const MAX_BYTES = 16 * 1024 * 1024;
16
+ const unsettled = row => ['prepared', 'compensating'].includes(row.status);
17
+ const operationIdentity = value => canonical(Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'executionFence')));
18
+ const stateIdentity = ({ kind, sha256, mode }) => ({ kind, ...(sha256 !== undefined ? { sha256 } : {}), ...(mode !== undefined ? { mode } : {}) });
19
+ const uniqueStates = states => [...new Map(states.map(state => [canonical(stateIdentity(state)), stateIdentity(state)])).values()];
20
+ const repairStatesFor = (row, path) => row.repairStates && Object.hasOwn(row.repairStates, path) ? row.repairStates[path] : [];
21
+ export function captureCheckpointFile(path) {
22
+ let stat;
23
+ try { stat = lstatSync(path); } catch (error) { if (error.code === 'ENOENT') return { kind: 'absent' }; throw error; }
24
+ if (stat.isDirectory()) {
25
+ if (readdirSync(path).length) fail('checkpoint_directory_not_empty');
26
+ return { kind: 'directory', mode: stat.mode & 0o777 };
27
+ }
28
+ if (!stat.isFile() || stat.size > TWO_MB) fail('checkpoint_file_unsupported');
29
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
30
+ let bytes;
31
+ try {
32
+ stat = fstatSync(fd); if (!stat.isFile() || stat.size > TWO_MB) fail('checkpoint_file_unsupported');
33
+ const buffer = Buffer.alloc(TWO_MB + 1); bytes = buffer.subarray(0, readSync(fd, buffer, 0, buffer.length, 0));
34
+ }
35
+ finally { closeSync(fd); }
36
+ if (bytes.length > TWO_MB) fail('checkpoint_file_unsupported');
37
+ return { kind: 'file', bytes: bytes.toString('base64'), sha256: sha256(bytes), mode: stat.mode & 0o777 };
38
+ }
39
+ export const checkpointStatesEqual = (a, b) => a.kind === b.kind && a.mode === b.mode && a.sha256 === b.sha256;
40
+
41
+ export function createWorkspaceCheckpointJournal({ stateDir, sessionId, workspaceRoot, resolvePath }) {
42
+ const filename = join(realpathExisting(stateDir), sessionKeyFor(sessionId), 'checkpoints', 'files.sqlite');
43
+ const rootDigest = sha256(workspaceRoot);
44
+ function database(write, action) {
45
+ if (!write && !existsSync(filename)) return action(null);
46
+ if (write) { privateDirectory(dirname(filename), true); const fd = openPrivate(filename, constants.O_RDWR | constants.O_CREAT); closeSync(fd); }
47
+ else { privateDirectory(dirname(filename)); privateFile(filename); }
48
+ for (const suffix of ['-journal', '-wal', '-shm']) privateFile(filename + suffix, true);
49
+ const db = new DatabaseSync(filename, { readOnly: !write });
50
+ try {
51
+ if (write) db.exec('PRAGMA synchronous=FULL; PRAGMA busy_timeout=10000; CREATE TABLE IF NOT EXISTS checkpoints (seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT UNIQUE NOT NULL, body TEXT NOT NULL, digest TEXT NOT NULL)');
52
+ return action(db);
53
+ } finally { db.close(); }
54
+ }
55
+ function decode(row) {
56
+ if (sha256(row.body) !== row.digest) fail('checkpoint_corrupt');
57
+ const value = JSON.parse(row.body);
58
+ if (value.rootDigest !== rootDigest || value.sessionId !== sessionId || value.policy !== WORKSPACE_CHECKPOINT_POLICY) fail('checkpoint_binding_changed');
59
+ return { ...value, seq: Number(row.seq) };
60
+ }
61
+ const rows = () => database(false, db => db ? db.prepare('SELECT * FROM checkpoints ORDER BY seq').all().map(decode) : []);
62
+ function save(value, undone = [], restoreUndone = false) {
63
+ const copy = { ...value }; delete copy.seq;
64
+ database(true, db => {
65
+ db.exec('BEGIN IMMEDIATE');
66
+ try {
67
+ const put = item => { const body = canonical(item); db.prepare('INSERT INTO checkpoints(id,body,digest) VALUES(?,?,?) ON CONFLICT(id) DO UPDATE SET body=excluded.body,digest=excluded.digest').run(item.id, body, sha256(body)); };
68
+ put(copy);
69
+ for (const id of undone) {
70
+ const old = decode(db.prepare('SELECT * FROM checkpoints WHERE id=?').get(id)); delete old.seq;
71
+ if (restoreUndone) {
72
+ if (old.status !== 'undone' || old.undoneBy !== value.id) fail('checkpoint_repair_chain_changed');
73
+ const previous = value.rewindStates?.find(item => item.id === id);
74
+ delete old.undoneBy;
75
+ put({ ...old, status: previous?.status ?? 'completed', ...(previous?.undoneBy ? { undoneBy: previous.undoneBy } : {}) });
76
+ } else put({ ...old, status: 'undone', undoneBy: value.id });
77
+ }
78
+ db.exec('COMMIT');
79
+ } catch (error) { db.exec('ROLLBACK'); throw error; }
80
+ });
81
+ }
82
+ function begin(request, rewindOf = []) {
83
+ if (!request.operationKey || !request.call?.callId) fail('checkpoint_execution_binding_missing');
84
+ const existing = rows();
85
+ if (existing.some(unsettled)) fail('checkpoint_outcome_unknown');
86
+ const binding = Object.fromEntries(['scope', 'sessionId', 'runId', 'turnId', 'buildId', 'operationKey', 'executionFence']
87
+ .filter(key => request[key] !== undefined).map(key => [key, structuredClone(request[key])]));
88
+ binding.callId = request.call.callId; binding.toolName = request.call.name;
89
+ // Ownership transfer changes the fence, not the identity of the effect.
90
+ // A durable completed publication must not run again just because its
91
+ // original Native receipt was lost before a new owner acquired the Run.
92
+ if (existing.some(row => row.binding && row.status !== 'failed' && operationIdentity(row.binding) === operationIdentity(binding))) fail('checkpoint_operation_already_recorded');
93
+ const record = { policy: WORKSPACE_CHECKPOINT_POLICY, rootDigest, sessionId, id: randomUUID(), createdAt: new Date().toISOString(),
94
+ status: 'prepared', binding, callDigest: sha256(canonical(request.call)), definitionDigest: sha256(canonical(request.definition)), files: [], rewindOf };
95
+ return beginRecord(record, rewindOf);
96
+ }
97
+ function beginRecord(record, rewindOf) {
98
+ const previous = new Map(rows().map(row => [row.id, row]));
99
+ record.rewindStates = rewindOf.map(id => {
100
+ const row = previous.get(id);
101
+ if (!row) fail('checkpoint_not_found');
102
+ return { id, status: row.status, ...(row.undoneBy ? { undoneBy: row.undoneBy } : {}) };
103
+ });
104
+ save(record);
105
+ return {
106
+ id: record.id,
107
+ get canCompensate() { return record.status === 'prepared'; },
108
+ stage(path, after) {
109
+ if (Buffer.byteLength(after.bytes ?? '', 'base64') > TWO_MB) fail('checkpoint_budget_exceeded');
110
+ const target = resolvePath(path), before = captureCheckpointFile(target);
111
+ if (after.kind === 'file' && after.mode === undefined) after = { ...after,
112
+ mode: before.kind === 'file' ? before.mode : 0o666 & ~process.umask() };
113
+ const entry = record.files.find(file => file.path === path);
114
+ if (entry && entry.canonicalPath !== target) fail('checkpoint_path_changed');
115
+ const intermediateStates = uniqueStates([...(entry?.intermediateStates ?? []), ...(entry ? [stateIdentity(before)] : []),
116
+ ...(after.kind === 'file' ? [{ ...stateIdentity(after), mode: before.kind === 'file' ? before.mode : 0o666 & ~process.umask() }] : [])]);
117
+ const replacement = { ...(entry ?? { path, canonicalPath: target, before }), after, intermediateStates };
118
+ const files = entry ? record.files.map(file => file === entry ? replacement : file) : [...record.files, replacement];
119
+ if (files.length > 100 || files.reduce((size, file) => size + Buffer.byteLength(file.before.bytes ?? '', 'base64')
120
+ + Buffer.byteLength(file.after.bytes ?? '', 'base64'), 0) > MAX_BYTES) fail('checkpoint_budget_exceeded');
121
+ record.files = files;
122
+ save(record);
123
+ return before;
124
+ },
125
+ finish(paths) {
126
+ record.files = record.files.filter(file => paths.has(file.path));
127
+ for (const file of record.files) {
128
+ if (resolvePath(file.path) !== file.canonicalPath) fail('checkpoint_path_changed');
129
+ const actual = captureCheckpointFile(file.canonicalPath);
130
+ if (actual.kind !== file.after.kind || actual.sha256 !== file.after.sha256
131
+ || file.after.mode !== undefined && actual.mode !== file.after.mode) fail('checkpoint_postimage_changed');
132
+ file.after = actual;
133
+ }
134
+ const completed = record.files.length > 0 || Boolean(record.controlBinding);
135
+ record.status = completed ? 'completed' : 'failed'; save(record, completed ? rewindOf : []);
136
+ },
137
+ fail() { record.status = 'failed'; save(record); },
138
+ compensating() { record.status = 'compensating'; save(record); },
139
+ compensated() {
140
+ for (const file of record.files) {
141
+ if (resolvePath(file.path) !== file.canonicalPath || !checkpointStatesEqual(captureCheckpointFile(file.canonicalPath), file.before)) fail('checkpoint_compensation_conflict');
142
+ }
143
+ record.status = 'failed'; record.compensated = true; save(record);
144
+ },
145
+ };
146
+ }
147
+ function preview(checkpointId, committed) {
148
+ const all = rows(), target = all.find(row => row.id === checkpointId);
149
+ if (!target) fail('checkpoint_not_found');
150
+ const suffix = all.filter(row => row.seq >= target.seq && row.status !== 'failed');
151
+ // An undo is itself a publication. If its inverse is in this suffix,
152
+ // include the original publication too, so earlier cuts do not resurrect
153
+ // files from an already removed branch.
154
+ const selected = suffix.filter(row => row.status !== 'undone' || suffix.some(inverse => inverse.id === row.undoneBy));
155
+ const unsafe = [], states = new Map();
156
+ if (target.status !== 'completed' || all.some(unsettled)) unsafe.push({ reason: 'checkpoint_outcome_unknown' });
157
+ for (const row of selected.toReversed()) {
158
+ if (!['completed', 'undone'].includes(row.status) || committed?.(row) !== true) { unsafe.push({ checkpoint_id: row.id, reason: 'checkpoint_receipt_uncommitted' }); continue; }
159
+ for (const file of row.files.toReversed()) {
160
+ let state;
161
+ try {
162
+ if (resolvePath(file.path) !== file.canonicalPath) fail('checkpoint_path_changed');
163
+ state = states.get(file.path);
164
+ if (!state) { const current = captureCheckpointFile(file.canonicalPath); state = { current, desired: current, path: file.path, canonicalPath: file.canonicalPath }; }
165
+ if (!checkpointStatesEqual(state.desired, file.after)) fail('checkpoint_external_modified');
166
+ state.desired = file.before; states.set(file.path, state);
167
+ } catch (error) { unsafe.push({ path: file.path, reason: error.code || 'checkpoint_read_failed' }); }
168
+ }
169
+ }
170
+ const files = [...states.values()], ids = selected.map(row => row.id);
171
+ if (files.length > 100 || files.reduce((size, file) => size + Buffer.byteLength(file.current.bytes ?? '', 'base64')
172
+ + Buffer.byteLength(file.desired.bytes ?? '', 'base64'), 0) > MAX_BYTES) unsafe.push({ reason: 'checkpoint_budget_exceeded' });
173
+ const token = sha256(canonical({ checkpointId, files, ids }));
174
+ return { files, ids, content: { ok: true, checkpoint_id: checkpointId, preview_token: token,
175
+ can_apply: files.length > 0 && unsafe.length === 0, unsafe_files: unsafe,
176
+ files: files.map(file => ({ path: file.path, action: file.desired.kind === 'absent' ? 'delete' : 'restore',
177
+ current_sha256: file.current.sha256 ?? null, restored_sha256: file.desired.sha256 ?? null })) } };
178
+ }
179
+ function originalRecord(request, original = false) {
180
+ return rows().toReversed().find(row =>
181
+ row.binding && ['scope', 'sessionId', 'runId', 'turnId', 'buildId'].every(key => canonical(row.binding[key] ?? null) === canonical(request[key] ?? null))
182
+ && row.binding.operationKey === (original ? request.originalOperationKey : request.operationKey)
183
+ && row.binding.executionFence === (original ? request.originalClaimedFence : request.executionFence)
184
+ && row.callDigest === sha256(canonical(request.call)) && row.definitionDigest === sha256(canonical(request.definition)));
185
+ }
186
+ function receiptRecord(request, original = false) {
187
+ const row = originalRecord(request, original);
188
+ return row && ['completed', 'failed'].includes(row.status) ? row : undefined;
189
+ }
190
+ function prepareRepairRow(row) {
191
+ if (!row || row.receipt || !['prepared', 'compensating', 'completed', 'failed'].includes(row.status)
192
+ || rows().some(other => other.seq > row.seq && !['failed', 'undone'].includes(other.status))) fail('checkpoint_repair_unavailable');
193
+ if (row.repairReceipt) return { result: structuredClone(row.repairReceipt) };
194
+ const files = row.files.toReversed().map(file => {
195
+ if (resolvePath(file.path) !== file.canonicalPath) fail('checkpoint_path_changed');
196
+ const current = captureCheckpointFile(file.canonicalPath);
197
+ if (![file.before, file.after, ...(file.intermediateStates ?? []), ...repairStatesFor(row, file.path)]
198
+ .some(state => checkpointStatesEqual(current, state))) fail('checkpoint_external_modified');
199
+ return { ...file, current };
200
+ });
201
+ const restore = row.status === 'completed' && row.rewindOf?.length ? row.rewindOf : [];
202
+ // Persist the chain restoration responsibility BEFORE any inverse write.
203
+ // A second crash must retain it even though status is now compensating.
204
+ const repairRewindOf = row.repairRewindOf ?? restore;
205
+ const repairStates = Object.fromEntries(files.map(file => [file.path, uniqueStates([...repairStatesFor(row, file.path),
206
+ ...(file.current.kind !== 'absent' && file.current.kind !== file.before.kind ? [{ kind: 'absent' }] : []),
207
+ ...(file.before.kind === 'file' ? [{ ...stateIdentity(file.before), mode: file.current.kind === 'file'
208
+ ? file.current.mode : 0o666 & ~process.umask() }] : []),
209
+ ...(file.before.kind === 'directory' ? [{ kind: 'directory', mode: file.before.mode & ~process.umask() }] : [])])]));
210
+ save({ ...row, status: 'compensating', repairRewindOf, repairStates });
211
+ return { files, complete() {
212
+ for (const file of files) if (resolvePath(file.path) !== file.canonicalPath
213
+ || !checkpointStatesEqual(captureCheckpointFile(file.canonicalPath), file.before)) fail('checkpoint_compensation_conflict');
214
+ save({ ...row, status: 'failed', compensated: true, repairRewindOf: [] }, repairRewindOf, true);
215
+ } };
216
+ }
217
+ function controlRecord(control) {
218
+ const matches = rows().filter(row => row.controlBinding?.commandId === control.commandId);
219
+ if (matches.length > 1) fail('checkpoint_control_duplicate');
220
+ const row = matches[0];
221
+ if (row && canonical(row.controlBinding) !== canonical(control)) fail('command_replay_conflict');
222
+ return row;
223
+ }
224
+ return { begin, preview,
225
+ controlRows: rows,
226
+ pendingControlRows: () => database(false, db => db ? db.prepare("SELECT * FROM checkpoints WHERE json_extract(body,'$.controlBinding') IS NOT NULL AND json_extract(body,'$.status') != 'failed' ORDER BY seq").all().map(decode) : []),
227
+ controlRecord,
228
+ beginControl(control, plan) {
229
+ if (control?.contract !== CONVERSATION_WORKSPACE_CONTROL || control.sessionId !== sessionId
230
+ || !control.commandId || !control.requestDigest || !control.sourceDigest || !control.scope) fail('checkpoint_control_invalid');
231
+ if (controlRecord(control) || rows().some(unsettled)) fail('checkpoint_outcome_unknown');
232
+ const record = { policy: WORKSPACE_CHECKPOINT_POLICY, rootDigest, sessionId, id: randomUUID(), createdAt: new Date().toISOString(),
233
+ status: 'prepared', controlBinding: structuredClone(control), files: [], rewindOf: [...plan.ids],
234
+ plannedPaths: plan.files.filter(file => !checkpointStatesEqual(file.current, file.desired)).map(file => file.path) };
235
+ return beginRecord(record, plan.ids);
236
+ },
237
+ completeControl(control) {
238
+ const row = controlRecord(control);
239
+ if (!row || row.status !== 'completed' || row.files.length !== row.plannedPaths.length
240
+ || row.files.some(file => !row.plannedPaths.includes(file.path))) fail('checkpoint_control_incomplete');
241
+ // finish() durably verified all publications. Observe that historical
242
+ // result on retry; later user edits must neither be overwritten nor turn
243
+ // a completed control into an unresolvable pending operation.
244
+ const receipt = { contract: CONVERSATION_WORKSPACE_CONTROL, requestDigest: control.requestDigest,
245
+ checkpointId: row.id, rewoundCheckpointIds: row.rewindOf, changedPaths: row.files.map(file => file.path),
246
+ previewToken: control.workspacePreviewToken };
247
+ if (row.receipt && canonical(row.receipt) !== canonical(receipt)) fail('checkpoint_receipt_conflict');
248
+ save({ ...row, receipt }); return structuredClone(receipt);
249
+ },
250
+ prepareControlRepair(control) { return prepareRepairRow(controlRecord(control)); },
251
+ prepareRepair(request) {
252
+ const row = originalRecord(request, true);
253
+ return prepareRepairRow(row);
254
+ },
255
+ recordRepairReceipt(request, result) {
256
+ const row = originalRecord(request, true);
257
+ if (!row || row.status !== 'failed' || row.compensated !== true) fail('checkpoint_repair_unavailable');
258
+ if (row.repairReceipt && canonical(row.repairReceipt) !== canonical(result)) fail('checkpoint_receipt_conflict');
259
+ save({ ...row, repairReceipt: structuredClone(result) });
260
+ },
261
+ recordReceipt(request, result) {
262
+ if (!['ok', 'error'].includes(result.status)) return;
263
+ const row = receiptRecord(request); if (!row) return;
264
+ // The Host calls this only after its complete permission/post-policy and
265
+ // model/display projection pipeline. A raw filesystem success is not a
266
+ // product receipt and cannot bypass that pipeline on cold recovery.
267
+ if (row.receipt && canonical(row.receipt) !== canonical(result)) fail('checkpoint_receipt_conflict');
268
+ save({ ...row, receipt: structuredClone(result) });
269
+ },
270
+ readReceipt(request) { return structuredClone(receiptRecord(request, true)?.receipt ?? null); },
271
+ list: () => rows().toReversed().slice(0, 50).map(row => ({ checkpoint_id: row.id, created_at: row.createdAt,
272
+ status: row.status, run_id: row.binding?.runId ?? row.controlBinding?.expectedHeadRunId ?? null,
273
+ tool: row.binding?.toolName ?? 'conversation_workspace_rewind', paths: row.files.map(file => file.path) })) };
274
+ }
275
+
276
+ /** Actual committed success, not a journal, UI inventory or model claim. */
277
+ export function committedWorkspaceCheckpoint(row, { store, scope, sessionId }) {
278
+ if (row.controlBinding) {
279
+ const control = row.controlBinding;
280
+ if (control.contract !== CONVERSATION_WORKSPACE_CONTROL || control.sessionId !== sessionId
281
+ || canonical(control.scope) !== canonical(scope) || row.sessionId !== sessionId
282
+ || row.receipt?.contract !== control.contract || row.receipt.requestDigest !== control.requestDigest
283
+ || row.receipt.checkpointId !== row.id || row.receipt.previewToken !== control.workspacePreviewToken
284
+ || canonical(row.receipt.rewoundCheckpointIds) !== canonical(row.rewindOf)
285
+ || canonical(row.receipt.changedPaths) !== canonical(row.files.map(file => file.path))) return false;
286
+ try { const record = store.load(scope, control.expectedHeadRunId);
287
+ return record.snapshot.sessionId === sessionId && sha256(canonical(record)) === control.sourceDigest;
288
+ } catch { return false; }
289
+ }
290
+ if (!row.binding.scope || !scope || row.sessionId !== sessionId || canonical(row.binding.scope) !== canonical(scope)) return false;
291
+ let record;
292
+ try { record = store.load(scope, row.binding.runId); } catch { return false; }
293
+ if (!record || record.snapshot.sessionId !== sessionId || record.snapshot.buildId !== row.binding.buildId) return false;
294
+ const intent = record.intents.find(item => item.operationKey === row.binding.operationKey && item.kind === 'tool');
295
+ const index = intent?.payload.calls?.findIndex(call => call.callId === row.binding.callId && call.name === row.binding.toolName) ?? -1;
296
+ if (index < 0 || intent.claimedFence !== row.binding.executionFence
297
+ || record.snapshot.turnId !== row.binding.turnId
298
+ || !record.configuration.tools.some(tool => tool.name === row.binding.toolName && tool.workspaceCheckpointPolicy === WORKSPACE_CHECKPOINT_POLICY)) return false;
299
+ const successful = result => result?.status === 'ok' && result.content?.checkpoint_id === row.id;
300
+ if (record.receipts.some(receipt => receipt.operationKey === row.binding.operationKey && receipt.status === 'committed'
301
+ && successful(receipt.result.results?.[index]))) return true;
302
+ if (index !== 0 || intent.payload.calls.length !== 1
303
+ || !record.configuration.tools.some(tool => tool.name === row.binding.toolName && tool.recoveryPolicy === 'receipt-observation/v1')) return false;
304
+ return record.receipts.some(receipt => {
305
+ const query = record.intents.find(item => item.operationKey === receipt.operationKey && item.kind === 'tool_receipt');
306
+ return receipt.status === 'committed' && query?.payload.originalOperationKey === row.binding.operationKey
307
+ && query.payload.originalClaimedFence === row.binding.executionFence && query.payload.callId === row.binding.callId
308
+ && receipt.result.contract === 'agentkit.tool-receipt-observation/v1'
309
+ && receipt.result.originalOperationKey === row.binding.operationKey && receipt.result.callId === row.binding.callId
310
+ && successful(receipt.result.result);
311
+ });
312
+ }
@@ -0,0 +1,61 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { isAbsolute } from 'node:path';
3
+ import { realpathExisting } from './workspace-paths.mjs';
4
+ import { invariant } from '@kingsoftcloud/agentkit-harness-core';
5
+
6
+ export const NATIVE_WORKSPACE_CONTEXT = 'agentkit.native-workspace-context/v1';
7
+
8
+ /** Execution workspace identity, not a filesystem permission or a Session storage address. */
9
+ export function nativeWorkspaceContextBinding(workspaceRoot, { instructionRoots } = {}) {
10
+ return validateNativeWorkspaceContext({ contract: NATIVE_WORKSPACE_CONTEXT, workspaceRoot: realpathSync(workspaceRoot),
11
+ ...(instructionRoots === undefined ? {} : { instructionSource: { contract: 'agentkit.native-instruction-files/v1',
12
+ globalRoots: [...new Set(instructionRoots.map(root => realpathExisting(root)))] } }) });
13
+ }
14
+
15
+ export function validateNativeWorkspaceContext(binding) {
16
+ invariant(binding && Object.keys(binding).sort().join(',') === ['contract', 'workspaceRoot',
17
+ ...(binding.sourceRoot === undefined ? [] : ['sourceRoot']),
18
+ ...(binding.instructionSource === undefined ? [] : ['instructionSource'])].sort().join(',')
19
+ && binding.contract === NATIVE_WORKSPACE_CONTEXT && typeof binding.workspaceRoot === 'string'
20
+ && isAbsolute(binding.workspaceRoot) && (binding.sourceRoot === undefined
21
+ || typeof binding.sourceRoot === 'string' && isAbsolute(binding.sourceRoot)), 'native_workspace_context_binding_invalid');
22
+ const source = binding.instructionSource;
23
+ invariant(source === undefined || source && Object.keys(source).sort().join(',') === 'contract,globalRoots'
24
+ && source.contract === 'agentkit.native-instruction-files/v1' && Array.isArray(source.globalRoots) && source.globalRoots.length <= 8
25
+ && new Set(source.globalRoots).size === source.globalRoots.length
26
+ && source.globalRoots.every(root => typeof root === 'string' && isAbsolute(root)), 'native_workspace_instruction_binding_invalid');
27
+ return structuredClone(binding);
28
+ }
29
+
30
+ /** Freeze where the initial Host context came from. Its validity cannot be
31
+ * recomputed from a later directory after the execution owner has aligned it. */
32
+ export function admitNativeWorkspaceContext(binding, directory) {
33
+ validateNativeWorkspaceContext(binding);
34
+ const current = directory.get();
35
+ invariant(typeof current === 'string' && isAbsolute(current), 'native_workspace_context_directory_invalid');
36
+ let sourceRoot = current;
37
+ try { sourceRoot = realpathSync(current); }
38
+ catch (error) { if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; }
39
+ return validateNativeWorkspaceContext({ ...binding, sourceRoot });
40
+ }
41
+
42
+ /** Align the context port inside the durable input effect. The execution owner
43
+ * supplies the root; the Host supplies its supported Session directory APIs.
44
+ * Historical absence stays absent. Input bytes and inbox claims belong to the
45
+ * Kernel's input port; directory alignment never rewrites admitted messages.
46
+ * No command, filesystem mutation tool or permission decision is invoked. */
47
+ export async function ensureNativeWorkspaceContext({ binding, directory, signal }) {
48
+ if (binding === undefined) return;
49
+ validateNativeWorkspaceContext(binding);
50
+ invariant(directory && ['get', 'set', 'ensure'].every(name => typeof directory[name] === 'function'), 'native_workspace_context_port_missing');
51
+ signal.throwIfAborted();
52
+ const current = directory.get();
53
+ invariant(typeof current === 'string' && isAbsolute(current), 'native_workspace_context_directory_invalid');
54
+ let same = false;
55
+ try { same = realpathSync(current) === binding.workspaceRoot; }
56
+ catch (error) { if (!['ENOENT', 'ENOTDIR'].includes(error.code)) throw error; }
57
+ if (same) return;
58
+ await directory.set(binding.workspaceRoot, signal);
59
+ signal.throwIfAborted();
60
+ invariant(realpathSync(await directory.ensure(signal)) === binding.workspaceRoot, 'native_workspace_context_directory_changed');
61
+ }
@@ -0,0 +1,39 @@
1
+ import { structuredPatch } from 'diff';
2
+
3
+ export const WORKSPACE_DISPLAY_CONTRACT = 'agentkit.workspace-display/v1';
4
+ const MAX_SOURCE_BYTES = 128 * 1024;
5
+ const MAX_PATCH_CHARS = 24000;
6
+
7
+ function text(state) {
8
+ if (state?.kind === 'absent') return '';
9
+ if (state?.kind !== 'file' || typeof state.bytes !== 'string') return null;
10
+ const bytes = Buffer.from(state.bytes, 'base64');
11
+ if (bytes.length > MAX_SOURCE_BYTES || bytes.includes(0)) return null;
12
+ try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { return null; }
13
+ }
14
+
15
+ /** Project already captured execution preimages; never open a path for the UI. */
16
+ export function workspaceChangeDisplay(changes) {
17
+ const files = [];
18
+ let remaining = MAX_PATCH_CHARS, omittedFiles = 0;
19
+ for (const [path, { before, after }] of changes) {
20
+ if (files.length >= 16) { omittedFiles++; continue; }
21
+ const oldText = text(before), newText = text(after);
22
+ if (oldText === null || newText === null || !remaining) { omittedFiles++; continue; }
23
+ let patch;
24
+ try { patch = structuredPatch(path, path, oldText, newText, undefined, undefined, { context: 3, timeout: 50, maxEditLength: 10000 }); }
25
+ catch { /* Presentation cannot alter an already-known execution outcome. */ }
26
+ if (!patch) { omittedFiles++; continue; }
27
+ let added = 0, removed = 0, rendered = '', truncated = false;
28
+ for (const hunk of patch.hunks) {
29
+ for (const line of hunk.lines) { if (line[0] === '+') added++; else if (line[0] === '-') removed++; }
30
+ const block = `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@\n${hunk.lines.join('\n')}\n`;
31
+ if (block.length > remaining) { truncated = true; continue; }
32
+ rendered += block; remaining -= block.length;
33
+ }
34
+ files.push({ path, patch: rendered, changeStat: { added, removed }, truncated,
35
+ operationKind: before.kind === 'absent' ? 'create' : after.kind === 'absent' ? 'delete' : 'edit' });
36
+ }
37
+ return files.length || omittedFiles ? { contract: WORKSPACE_DISPLAY_CONTRACT,
38
+ display: { kind: 'file_edit', files, omittedFiles } } : null;
39
+ }
@@ -0,0 +1,19 @@
1
+ import { Worker } from 'node:worker_threads';
2
+
3
+ /** The worker sees captured strings only; it cannot publish workspace changes. */
4
+ export async function prepareWorkspaceEdits(input, { signal, timeoutMs = 2000 } = {}) {
5
+ signal?.throwIfAborted();
6
+ const worker = new Worker(new URL('./workspace-edit-worker.mjs', import.meta.url), { workerData: input, execArgv: [],
7
+ resourceLimits: { maxOldGenerationSizeMb: 64, maxYoungGenerationSizeMb: 16, stackSizeMb: 4 } });
8
+ return new Promise(resolve => {
9
+ let output, settled = false;
10
+ const finish = value => { if (settled) return; settled = true; clearTimeout(timer); signal?.removeEventListener('abort', stop); resolve(value); };
11
+ const stop = () => { worker.unref(); void worker.terminate().catch(() => {}); finish(null); };
12
+ const timer = setTimeout(stop, timeoutMs);
13
+ worker.once('message', value => { output = value; });
14
+ worker.once('error', stop);
15
+ worker.once('exit', code => finish(!signal?.aborted && code === 0 && output?.contract === 'zcode-match/v1' ? output.results : null));
16
+ signal?.addEventListener('abort', stop, { once: true });
17
+ if (signal?.aborted) stop();
18
+ });
19
+ }
@@ -0,0 +1,15 @@
1
+ import { parentPort, workerData } from 'node:worker_threads';
2
+ import { applyZcodeEdit } from './workspace-editing.mjs';
3
+
4
+ let text = workerData.text;
5
+ const results = [];
6
+ for (const edit of workerData.edits) {
7
+ const result = applyZcodeEdit(text, edit.old_text, edit.new_text, {
8
+ relative: workerData.relative, replaceAll: Boolean(edit.replace_all ?? workerData.replaceAll),
9
+ expectedReplacements: edit.expected_replacements ?? 1,
10
+ });
11
+ results.push(result);
12
+ if (!result.ok) break;
13
+ text = result.updated;
14
+ }
15
+ parentPort.postMessage({ contract: 'zcode-match/v1', results });