@visual-z/xeno 0.8.4 → 0.9.0

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 (350) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2071
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1818
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3108
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,150 +0,0 @@
1
- /**
2
- * D9 / research M.4: an anti-stall signal, not a judgment of task completion.
3
- * Novelty belongs to observed content and executed checks, never note wording,
4
- * event IDs, or timestamps. The full ledger is rebuilt from the active log path;
5
- * its carried handoff view may be paged like the other structural state (D20).
6
- */
7
- import type { LineRange, LogEvent } from "./events.ts";
8
- import { isProcessReceipt } from "./process-receipt.ts";
9
-
10
- export type ProgressEvidence = (
11
- | { kind: "read"; sha256: string; ranges?: readonly LineRange[]; observation_sha256?: string }
12
- | { kind: "observation"; sha256: string }
13
- | { kind: "check"; command: string; gate?: string; workspace_sha256?: string }
14
- ) & { source: { node: number; event_id: string } };
15
-
16
- /** Test coverage without expanding arbitrarily long ranges into line numbers. */
17
- function hasUnseenLines(requested: readonly LineRange[], observed: readonly LineRange[]): boolean {
18
- const sorted = [...observed].sort((a, b) => a[0] - b[0]);
19
- return requested.some(([start, end]) => {
20
- let next = start;
21
- for (const [low, high] of sorted) {
22
- if (high < next) continue;
23
- if (low > next) return true;
24
- if (high >= end) return false;
25
- next = high + 1;
26
- }
27
- return next <= end;
28
- });
29
- }
30
-
31
- /**
32
- * A file version's fingerprint in the observation set. Keyed by digest alone:
33
- * the same bytes under another path are not new work either.
34
- */
35
- const versionKey = (_path: string, sha256: string): string => `version:${sha256}`;
36
-
37
- export function deriveProgress(
38
- previous: readonly ProgressEvidence[], events: readonly LogEvent[], node: number,
39
- eligibleChecks: ReadonlySet<string>,
40
- ): { evidence: ProgressEvidence[]; progressed: boolean } {
41
- const evidence = [...previous];
42
- const reads = new Map<string, Extract<ProgressEvidence, { kind: "read" }>[]>();
43
- const observations = new Set<string>();
44
- const checks = new Set<string>();
45
- // A named gate has one status per workspace. Changing its arguments does not
46
- // make another gate, and cannot keep the loop alive by spelling a pass anew.
47
- const checkKey = (command: string, gate?: string, workspace?: string): string =>
48
- JSON.stringify([gate === undefined ? "command" : "gate", gate ?? command, workspace ?? null]);
49
- for (const item of previous) {
50
- if (item.kind === "read") {
51
- const seen = reads.get(item.sha256) ?? [];
52
- seen.push(item);
53
- reads.set(item.sha256, seen);
54
- if (item.observation_sha256 !== undefined) observations.add(item.observation_sha256);
55
- // A version read before a cut is as known as one read in this segment.
56
- observations.add(versionKey("", item.sha256));
57
- } else if (item.kind === "observation") observations.add(item.sha256);
58
- else checks.add(checkKey(item.command, item.gate, item.workspace_sha256));
59
- }
60
- let progressed = false;
61
- const calls = new Map<string, string>();
62
- for (const event of events) {
63
- if (event.t === "assistant") {
64
- for (const block of event.blocks) if (block.type === "tool_use") calls.set(block.call_id, block.name);
65
- }
66
- if (event.t !== "tool_result") continue;
67
- const name = calls.get(event.call_id);
68
- calls.delete(event.call_id);
69
- const effect = event.effect;
70
- // A failed tool that never ran observed nothing. A command that ran and
71
- // exited nonzero did observe something (S.6 item 2): its output counts
72
- // like any other, but it is never a check.
73
- if (!event.ok && effect?.kind !== "command") continue;
74
- const source = { node, event_id: event.id };
75
- const observation = event.observation_sha256 ?? event.content_ref?.sha256 ?? event.content_sha256;
76
- if (effect?.kind === "read" && effect.sha256 !== "") observations.add(versionKey(effect.path, effect.sha256));
77
- if (effect?.kind === "read" && effect.sha256 !== "") {
78
- const seen = reads.get(effect.sha256) ?? [];
79
- const novel = !seen.some((r) => r.ranges === undefined) &&
80
- (effect.ranges === undefined || hasUnseenLines(effect.ranges, seen.flatMap((r) => r.ranges ?? [])));
81
- if (novel) {
82
- const item: Extract<ProgressEvidence, { kind: "read" }> = {
83
- kind: "read", sha256: effect.sha256, source,
84
- ...(effect.ranges === undefined ? {} : { ranges: effect.ranges }),
85
- ...(observation === undefined ? {} : { observation_sha256: observation }),
86
- };
87
- evidence.push(item);
88
- reads.set(effect.sha256, [...seen, item]);
89
- // Whole-file hashes can change outside the returned range. Such a read
90
- // extends coverage for that version but unchanged bytes are not novelty.
91
- if (observation === undefined || !observations.has(observation)) progressed = true;
92
- if (observation !== undefined) observations.add(observation);
93
- }
94
- } else if (effect?.kind === "command") {
95
- // D9 revisions (2026-09-29 and S.6 item 2, user-approved): output never
96
- // seen before is a new observation, exactly as for read/grep, whatever
97
- // the exit code. A model exploring with `sed -n` or `grep` in the shell
98
- // is gathering evidence too, and a grep with no match exits 1 after the
99
- // earlier pipeline stages printed what was asked for. The digest
100
- // excludes wall time, so an unchanged `git status` or the same failure
101
- // repeats and stalls.
102
- if (observation !== undefined && !observations.has(observation)) {
103
- observations.add(observation);
104
- evidence.push({ kind: "observation", sha256: observation, source });
105
- progressed = true;
106
- }
107
- const key = checkKey(effect.cmd, effect.gate, effect.workspace_sha256);
108
- if (event.ok && effect.exit === 0 && !checks.has(key)) {
109
- checks.add(key);
110
- evidence.push({ kind: "check", command: effect.cmd, source,
111
- ...(effect.gate === undefined ? {} : { gate: effect.gate }),
112
- ...(effect.workspace_sha256 === undefined ? {} : { workspace_sha256: effect.workspace_sha256 }) });
113
- // Remember even unclaimed successes. A later model note cannot turn a
114
- // rerun of old evidence into a fresh completion by giving it a new name.
115
- if (eligibleChecks.has(event.id)) progressed = true;
116
- }
117
- } else if (effect?.kind === "edit") {
118
- // D9 revision (2026-09-29, user-approved): a file version no one has
119
- // seen before is new evidence -- it is how implementation work shows up.
120
- // Versions already seen anywhere (read, or either side of an edit) do
121
- // not count, so an A->B->A rewrite loop still stalls.
122
- // Both sides become durable evidence: the ledger is all a later
123
- // derivation sees, and a version seen only as "before" must still be
124
- // recognised when the file is edited back to it.
125
- for (const [sha, isAfter] of [[effect.sha256_before, false], [effect.sha256_after, true]] as const) {
126
- if (sha === null) continue;
127
- const key = versionKey(effect.path, sha);
128
- if (observations.has(key)) continue;
129
- observations.add(key);
130
- evidence.push({ kind: "observation", sha256: key, source });
131
- if (isAfter) progressed = true;
132
- }
133
- } else if (isProcessReceipt(event.data) && !event.data.replayed && event.data.output !== undefined && event.data.output.bytes > 0 &&
134
- !observations.has(event.data.output.sha256)) {
135
- // A managed command can produce useful evidence before it exits. Credit
136
- // raw captured bytes, never its elapsed header, changing ID or poll count.
137
- const sha256 = event.data.output.sha256;
138
- observations.add(sha256);
139
- evidence.push({ kind: "observation", sha256, source });
140
- progressed = true;
141
- } else if ((name === "grep" || name === "find" || name === "ls" || name === "query") &&
142
- observation !== undefined && (event.content_ref?.bytes ?? event.content.length) > 0 &&
143
- !observations.has(observation)) {
144
- observations.add(observation);
145
- evidence.push({ kind: "observation", sha256: observation, source });
146
- progressed = true;
147
- }
148
- }
149
- return { evidence, progressed };
150
- }
@@ -1,431 +0,0 @@
1
- /**
2
- * projectContext: log -> what the model sees.
3
- *
4
- * Pure. The whole architecture rests on this being a function rather than a
5
- * mutable array: the log is the truth, the context is one recomputable projection
6
- * of it, so a bad projection is a bug you fix and re-run rather than information
7
- * you destroyed.
8
- *
9
- * Two properties worth stating because tests pin them:
10
- *
11
- * IDENTITY. With no handoff, the projection is the identity mapping of harness
12
- * events onto messages. Segment 0 therefore behaves exactly like a harness with
13
- * no context management at all, which is what makes the design adoptable in
14
- * stages -- nothing is paid for until a boundary actually fires.
15
- *
16
- * APPEND-ONLY WITHIN A SEGMENT. Appending an event only appends to the
17
- * projection; it never rewrites an earlier message. That is what keeps the
18
- * prompt prefix byte-stable, so every turn reads the whole accumulated prefix
19
- * from cache and writes only the delta. In-place eviction would invalidate the
20
- * prefix on every request and never amortise the write cost -- the failure mode
21
- * Beyond Compaction (arXiv:2606.11213) documents for incremental eviction.
22
- *
23
- * The message shape below is structurally the Anthropic Messages shape, which is
24
- * the target wire format. It is declared locally rather than imported so the core
25
- * carries no SDK dependency; mapping to other providers belongs in an adapter,
26
- * not here.
27
- */
28
-
29
- import type { LogEvent, AssistantBlock } from "./events.ts";
30
- import { assertNever } from "./exhaustive.ts";
31
- import type { ImageContent } from "./images.ts";
32
- import { type Handoff, emitHandoff } from "./handoff.ts";
33
- import { diagnosticContent } from "./tool-contract.ts";
34
-
35
- /** O.8: persist the interpretation used to replay a frozen request. */
36
- export const PROJECTION_VERSION = 1;
37
-
38
- export type ContentBlock =
39
- | ImageContent
40
- | { type: "text"; text: string; wire?: unknown }
41
- /**
42
- * The provider's own thinking block, replayed verbatim. It is carried as
43
- * opaque rather than reconstructed because the API rejects modified blocks,
44
- * and a reconstructed one would lose the signature the next request needs.
45
- */
46
- | { type: "thinking"; raw: unknown }
47
- | { type: "tool_use"; id: string; name: string; input: unknown; wire?: unknown }
48
- | { type: "tool_result"; tool_use_id: string; content: string; is_error?: boolean };
49
-
50
- export interface Message {
51
- role: "user" | "assistant";
52
- content: ContentBlock[];
53
- }
54
-
55
- /**
56
- * System prompt and tool definitions. Must be byte-identical across every segment
57
- * (decision D6): they render at the front of the prefix, so any change there
58
- * invalidates the cache for the entire conversation. The non-obvious consequence
59
- * is that project context files must NOT be re-read into `system` at a boundary --
60
- * they belong in the handoff instead.
61
- */
62
- export interface FrozenPrefix {
63
- system: string;
64
- tools: readonly unknown[];
65
- }
66
-
67
- export interface ProjectInput {
68
- /** Diagnostic only; trace never enters provider messages. */
69
- trace?: boolean;
70
- frozen: FrozenPrefix;
71
- /** Previous segment's handoff, or null for segment 0. */
72
- handoff: Handoff | null;
73
- events: readonly LogEvent[];
74
- /** Source locations and versioned recovery guidance; workspace search does not index the log. */
75
- logHint?: { manifestPath: string; segmentPath: string };
76
- }
77
-
78
- export interface ProjectResult {
79
- provenance?: { version: number; messages: string[][]; omitted: string[] };
80
- system: string;
81
- tools: readonly unknown[];
82
- messages: Message[];
83
- /**
84
- * Pairing violations. A tool_use with no matching tool_result is a 400 from the
85
- * API, so it is reported here -- at the pure boundary, where it is debuggable --
86
- * rather than being discovered as a request failure.
87
- */
88
- violations: ProjectViolation[];
89
- }
90
-
91
- export interface ProjectViolation {
92
- code: "UNPAIRED_TOOL_USE" | "ORPHAN_TOOL_RESULT" | "EMPTY_PROJECTION" | "DUPLICATE_TOOL_USE" | "EMPTY_TOOL_USE_ID";
93
- detail: string;
94
- }
95
-
96
- /**
97
- * D5: a receipt identifies exactly one call in its assistant batch. Validate
98
- * before either the projection's Set or the runtime's scheduling Map can erase
99
- * a collision; repairing IDs would invent provider protocol history.
100
- */
101
- export function toolCallIdentityViolations(blocks: readonly AssistantBlock[]): ProjectViolation[] {
102
- const violations: ProjectViolation[] = [], seen = new Map<string, number>();
103
- for (const [index, block] of blocks.entries()) {
104
- if (block.type !== "tool_use") continue;
105
- if (block.call_id.trim().length === 0) {
106
- violations.push({ code: "EMPTY_TOOL_USE_ID", detail: `tool_use block ${index} has an empty call_id` });
107
- continue;
108
- }
109
- const previous = seen.get(block.call_id);
110
- if (previous !== undefined) {
111
- violations.push({ code: "DUPLICATE_TOOL_USE", detail: `tool_use block ${index} repeats the call_id of block ${previous}` });
112
- } else seen.set(block.call_id, index);
113
- }
114
- return violations;
115
- }
116
-
117
- /**
118
- * The only event types that replay as messages.
119
- *
120
- * Model-annotated events (claim, decision, pitfall, ...) are deliberately absent.
121
- * They are a sidecar index over tool calls the model already made, so the
122
- * tool_use/tool_result pair is what replays; including the annotation too would
123
- * show the model its own notes twice. Within a segment the model sees its raw tool
124
- * calls; across a boundary it sees them distilled into the handoff.
125
- *
126
- * `cut` is absent for a different reason: it is a boundary marker about the log,
127
- * not a thing that happened in the conversation.
128
- *
129
- * `rewind` is here conditionally, and it is the one event whose replayability
130
- * is a property of the event rather than of its type. After a "both" rewind
131
- * the context and the disk agree, so saying anything would carry the undone
132
- * work back in another form. After a partial one they disagree, and a model
133
- * planning against the wrong picture is the whole failure mode -- so it is
134
- * told, with paths.
135
- *
136
- * `interrupt` is a one-line harness note after the cancelled user message,
137
- * not a new role: the model has to tell "asked and withdrew" from "asked
138
- * two things". It is a following message rather than a mutation of the user
139
- * turn so appending the event only appends to the projection (P7).
140
- */
141
- type ReplayEvent = Extract<LogEvent, { t: "acceptance_contract" | "acceptance_decision" | "user" | "user_bash" | "guidance" | "interrupt" | "assistant" | "tool_result" | "rewind" | "extension" }>;
142
-
143
- function replayable(e: LogEvent): e is ReplayEvent {
144
- if (e.t === "tool_result" && (e.parent_call_id !== undefined || e.origin === "extension")) return false;
145
- if (e.t === "acceptance_contract" || e.t === "acceptance_decision") return true;
146
- if (e.t === "extension") return e.inContext;
147
- if (e.t === "user_bash") return !e.excludeFromContext;
148
- if (e.t === "rewind") return e.mode !== "both";
149
- return e.t === "user" || e.t === "guidance" || e.t === "interrupt" || e.t === "assistant" || e.t === "tool_result";
150
- }
151
-
152
- /**
153
- * The one thing a divergence message must produce.
154
- *
155
- * Named because it is the contract, not decoration: it is the automated form
156
- * of the remedy Claude Code leaves to the person to remember after a partial
157
- * restore, and a test asserting the message happened has to assert something
158
- * only the message can say.
159
- */
160
- export const REREAD_INSTRUCTION =
161
- "Re-read any file before editing it. Do not rely on what earlier messages say its contents are.";
162
-
163
- /**
164
- * What an interrupt has to tell the model, as one line.
165
- *
166
- * A cancelled generation was never delivered, so the projection must not
167
- * invent assistant text; the note is the whole of what the harness observed.
168
- * Same role as the user turn so we do not mint a role the wire format lacks.
169
- */
170
- export const INTERRUPT_NOTE =
171
- "The user cancelled this turn before a reply was delivered.";
172
-
173
- /**
174
- * What a partial rewind has to tell the model, as plain prose.
175
- *
176
- * Prose rather than a structured block because it is addressed to the model,
177
- * and the one thing it must produce is a re-read before an edit. That
178
- * instruction is Claude Code's documented remedy for the same desync, left
179
- * there to the person to remember; the paths make it actionable.
180
- */
181
- export function renderRewindMessage(e: Extract<LogEvent, { t: "rewind" }>): Message {
182
- const where = e.prompt ? `the point where you were asked: ${e.prompt}` : "an earlier point";
183
- let lines: string[];
184
- switch (e.mode) {
185
- case "files":
186
- lines = [
187
- `The workspace was moved back to ${where}.`,
188
- "",
189
- "Your messages above still describe edits that are no longer on disk.",
190
- "The conversation was not moved; only the files were.",
191
- ];
192
- break;
193
- case "context":
194
- lines = [
195
- `This conversation was moved back to ${where}.`,
196
- "",
197
- "The workspace was NOT moved. Files exist that nothing above accounts",
198
- "for, because the turns that made them are no longer in this context.",
199
- ];
200
- break;
201
- case "both":
202
- // Unreachable through the projection, which filters "both" out before
203
- // getting here. Spelled out rather than folded into the other branch:
204
- // falling through would have produced a message telling the model the
205
- // two had diverged when the entire point of "both" is that they did
206
- // not, and nothing would have failed.
207
- throw new Error("a \"both\" rewind has nothing to tell the model: context and disk agree");
208
- default:
209
- return assertNever(e.mode, "renderRewindMessage");
210
- }
211
-
212
- if (e.divergence.length > 0) {
213
- lines.push("", "Where they disagree:");
214
- for (const path of e.divergence) lines.push(` ${path}`);
215
- }
216
- lines.push("", REREAD_INSTRUCTION);
217
-
218
- return { role: "user", content: [{ type: "text", text: lines.join("\n") }] };
219
- }
220
-
221
- /**
222
- * Render the handoff as the opening user message of a new segment.
223
- *
224
- * It is a user message rather than part of `system` for two reasons: `system` has
225
- * to stay byte-stable (D6), and a handoff is untrusted-ish derived content, not an
226
- * operator instruction -- it does not belong in the operator channel.
227
- */
228
- export function renderHandoffMessage(
229
- handoff: Handoff,
230
- logHint?: { manifestPath: string; segmentPath: string },
231
- ): Message {
232
- const yaml = emitHandoff(handoff);
233
-
234
- const parts = [
235
- "You are continuing earlier work in a new context window.",
236
- "",
237
- "The state below was derived mechanically from the session log -- it is not a",
238
- "summary and nothing was paraphrased. Fields that are absent were absent from",
239
- "the log, not judged unimportant.",
240
- "",
241
- "```yaml",
242
- yaml.trimEnd(),
243
- "```",
244
- ];
245
-
246
- if (logHint) {
247
- parts.push(
248
- "",
249
- "The full log is on disk and nothing has been deleted from it. When you need a",
250
- "detail this state does not carry, read it rather than guessing:",
251
- ` - segment index: ${logHint.manifestPath}`,
252
- ` - previous segment: ${logHint.segmentPath}`,
253
- "Each line is one JSON event. Recover originals using the declared tools' contracts; if evidence is inaccessible, state the gap instead of guessing.",
254
- );
255
- }
256
-
257
- const unverified = handoff.state.unverified_claims.length;
258
- if (unverified > 0) {
259
- parts.push(
260
- "",
261
- `${unverified} earlier completion ${unverified === 1 ? "claim was" : "claims were"} not matched`,
262
- "to a passing command in the log. They are listed under state.unverified_claims",
263
- "and must be treated as NOT done.",
264
- );
265
- }
266
-
267
- if (handoff.file_anchors.length > 0) {
268
- parts.push(
269
- "",
270
- "file_anchors records the digest each file had when it was last touched. Re-read",
271
- "a file before relying on it; if the digest differs, the environment changed",
272
- "underneath this state.",
273
- );
274
- }
275
-
276
- return { role: "user", content: [{ type: "text", text: parts.join("\n") }] };
277
- }
278
-
279
- export function projectContext(input: ProjectInput): ProjectResult {
280
- const violations: ProjectViolation[] = [];
281
- const messages: Message[] = [];
282
- const sources: string[][] = [];
283
- const append = (message: Message, ids: string[]) => { messages.push(message); sources.push(ids); };
284
-
285
- if (input.handoff) {
286
- append(renderHandoffMessage(input.handoff, input.logHint), [`handoff:${input.handoff.predecessor?.node ?? "root"}`]);
287
- }
288
-
289
- const events = input.events.filter(replayable);
290
-
291
- // Tool results are buffered so that every result for one assistant turn lands in
292
- // a SINGLE user message. Splitting them across messages teaches the model to stop
293
- // issuing parallel calls, which is a silent, permanent capability regression.
294
- let pending: ContentBlock[] = [];
295
- let pendingSources: string[] = [];
296
- const flushPending = () => {
297
- if (pending.length > 0) {
298
- append({ role: "user", content: pending }, pendingSources);
299
- pending = [];
300
- pendingSources = [];
301
- }
302
- };
303
-
304
- /** tool_use ids emitted by the most recent assistant turn and not yet answered. */
305
- let awaiting = new Set<string>();
306
-
307
- for (const e of events) {
308
- if (e.t === "acceptance_contract" || e.t === "acceptance_decision") {
309
- flushPending();
310
- if (awaiting.size) violations.push({ code: "UNPAIRED_TOOL_USE", detail: `acceptance event ${e.id} follows unanswered tools` });
311
- append({ role: "user", content: [{ type: "text", text: e.t === "acceptance_contract"
312
- ? `Declared acceptance contract (execution success alone does not satisfy it): ${JSON.stringify(e.contract)}`
313
- : `User accepted criterion ${e.criterion} of contract ${e.contract_id} for request ${e.request}.` +
314
- (e.target_binding ? ` Bound to runtime instance ${e.target_binding}, observation ${e.target_observation}. Later runtime changes invalidate this acceptance.` : "") }] }, [e.id]);
315
- continue;
316
- }
317
- if (e.t === "extension") {
318
- flushPending();
319
- if (awaiting.size) violations.push({ code: "UNPAIRED_TOOL_USE", detail: `custom message ${e.id} follows unanswered tool_use ${[...awaiting].join(", ")}` });
320
- append({ role: "user", content: [{ type: "text", text: e.content }] }, [e.id]);
321
- continue;
322
- }
323
- if (e.t === "user_bash") {
324
- flushPending();
325
- append({ role: "user", content: [{ type: "text", text: `User ran: ${e.command}\n${e.content}` }] }, [e.id]);
326
- continue;
327
- }
328
- if (e.t === "rewind") {
329
- // Flushed first for the same reason a user turn is: it opens a user
330
- // message, and a pending tool_result batch has to close before it.
331
- flushPending();
332
- append(renderRewindMessage(e), [e.id]);
333
- continue;
334
- }
335
- if (e.t === "user" || e.t === "guidance") {
336
- flushPending();
337
- if (awaiting.size > 0) {
338
- violations.push({
339
- code: "UNPAIRED_TOOL_USE",
340
- detail: `user turn ${e.id} follows unanswered tool_use ${[...awaiting].join(", ")}`,
341
- });
342
- awaiting = new Set();
343
- }
344
- append({ role: "user", content: [...(e.text || e.t === "guidance" || !e.images?.length ? [{ type: "text" as const, text: e.text }] : []),
345
- ...(e.t === "user" ? (e.images ?? []).map(image => ({ type: "image" as const, image })) : [])] }, [e.id]);
346
- continue;
347
- }
348
- if (e.t === "interrupt") {
349
- // Own message so the preceding user turn stays byte-stable (P7). A
350
- // look-ahead mutation of that turn would rewrite it once a later
351
- // event (tool_use, another user) was already projected after it.
352
- flushPending();
353
- append({ role: "user", content: [{ type: "text", text: INTERRUPT_NOTE }] }, [e.id]);
354
- continue;
355
- }
356
-
357
- if (e.t === "assistant") {
358
- flushPending();
359
- violations.push(...toolCallIdentityViolations(e.blocks).map(violation => ({
360
- ...violation, detail: `assistant turn ${e.id}: ${violation.detail}`,
361
- })));
362
- if (awaiting.size > 0) {
363
- violations.push({
364
- code: "UNPAIRED_TOOL_USE",
365
- detail: `assistant turn ${e.id} follows unanswered tool_use ${[...awaiting].join(", ")}`,
366
- });
367
- }
368
- append({ role: "assistant", content: e.blocks.map(toContentBlock) }, [e.id]);
369
- awaiting = new Set(
370
- e.blocks.filter((b) => b.type === "tool_use").map((b) => (b as { call_id: string }).call_id),
371
- );
372
- continue;
373
- }
374
-
375
- // tool_result
376
- if (!awaiting.has(e.call_id)) {
377
- violations.push({
378
- code: "ORPHAN_TOOL_RESULT",
379
- detail: `tool_result ${e.id} answers unknown call ${e.call_id}`,
380
- });
381
- continue;
382
- }
383
- awaiting.delete(e.call_id);
384
- pendingSources.push(e.id);
385
- pending.push({
386
- type: "tool_result",
387
- tool_use_id: e.call_id,
388
- content: diagnosticContent(e.content, e.diagnostic) + (e.warnings?.length ? "\nObserver warnings (operation result retained): " + JSON.stringify(e.warnings) : "") +
389
- (e.instructions ? "\n\n" + e.instructions.text : ""),
390
- ...(e.ok ? {} : { is_error: true }),
391
- });
392
- // Images follow the paired text result, so APIs without image tool outputs receive a user image.
393
- pending.push(...(e.images ?? []).map(image => ({ type: "image" as const, image })));
394
- }
395
-
396
- flushPending();
397
-
398
- if (awaiting.size > 0) {
399
- violations.push({
400
- code: "UNPAIRED_TOOL_USE",
401
- detail: `segment ends with unanswered tool_use ${[...awaiting].join(", ")}`,
402
- });
403
- }
404
-
405
- if (messages.length === 0) {
406
- violations.push({ code: "EMPTY_PROJECTION", detail: "no replayable events" });
407
- }
408
-
409
- return {
410
- ...(input.trace ? { provenance: { version: PROJECTION_VERSION, messages: sources,
411
- omitted: input.events.filter(e => !sources.some(ids => ids.includes(e.id))).map(e => e.id) } } : {}),
412
- system: input.frozen.system,
413
- tools: input.frozen.tools,
414
- messages,
415
- violations,
416
- };
417
- }
418
-
419
- function toContentBlock(b: AssistantBlock): ContentBlock {
420
- switch (b.type) {
421
- case "tool_use":
422
- return { type: "tool_use", id: b.call_id, name: b.name, input: b.input, ...(b.wire === undefined ? {} : { wire: b.wire }) };
423
- case "thinking":
424
- // Only the raw block crosses back to the API; `text` exists for display.
425
- return { type: "thinking", raw: b.raw };
426
- case "text":
427
- return b;
428
- default:
429
- return assertNever(b, "toContentBlock");
430
- }
431
- }
@@ -1,46 +0,0 @@
1
- /** R10 / D6: stable scope and completion rules; execution details belong to tool contracts. */
2
- export function taskGuidance(): string {
3
- return [
4
- "How to work:",
5
- "- Keep going until the request is fully resolved. The user's request defines scope; a tool profile only selects capabilities. State reasonable assumptions; ask only for decisions the user must make.",
6
- "- Implementation: understand the relevant code, reproduce bugs, finish every requested change and verify the final state with the project's tests, build or type check. Finish with what changed and the command that verified it, or the specific blocker. A research answer does not complete an implementation request.",
7
- "- Research: answer the requested questions with original sources, distinguish evidence from inference, and report gaps. Do not change files merely to establish completion.",
8
- "- Data analysis: calculate over the complete declared inputs; report sources, denominators, exclusions and uncertainty. Truncated previews cannot establish totals.",
9
- "- Runtime or visual work: bind checks to the actual build and running target. Automated checks do not establish human acceptance; report any required acceptance as pending.",
10
- "- Put independent reads and searches in one response; they run in parallel. After a command fails, read the end of its output first. If the same approach fails twice, change the approach instead of retrying it.",
11
- "- Use only declared capabilities and obey admission decisions. If required evidence is inaccessible, state the specific blocker without inventing success, bypassing permission or replaying an operation with uncertain effects.",
12
- ].join("\n");
13
- }
14
-
15
- export interface ToolGuidance {
16
- /** Only builtin companions with these exact contracts can satisfy the dependency. */
17
- requires: readonly string[];
18
- text: string;
19
- }
20
-
21
- /** Capability projection is deterministic; excluding a companion cannot add advice. */
22
- export function toolDescription(description: string, guidance: readonly ToolGuidance[], tools: readonly string[]): string {
23
- return [description, ...guidance.filter(item => item.requires.every(name => tools.includes(name))).map(item => item.text)].join(" ");
24
- }
25
-
26
- /**
27
- * `usersInline`: the carried `user_turns` hold every user message verbatim.
28
- * Telling the model to page them anyway costs a guaranteed first-call read
29
- * that returns what it already has -- or an empty page after a model switch
30
- * on a fresh session (Horizyn mv3nsske-x50u, fix-recovery-inline-users).
31
- */
32
- export function recoveryInstructions(tools: readonly string[], usersInline = false, images = false): string {
33
- return (tools.includes("history")
34
- ? (usersInline ? "User messages are verbatim in user_turns. Other history(node=source.node,recovery=...) pages: failures, environment, next_actions; " : "Read original user messages before continuing: history(node=source.node,recovery=users,offset=0). Other pages: failures, environment, next_actions; ") +
35
- "event=ID reads one original. history(work_id=ID) recovers work. " +
36
- // Working set v3: the turn's image references are carried, the pixels are not.
37
- (images ? "A user turn's images are not attached here; history(event=ID) with its event reattaches them. " : "")
38
- : "Original user messages and evidence remain at the source references. The original-source recovery capability is unavailable; state any resulting evidence gap instead of guessing. ") +
39
- "Abandoned messages are cancelled; annotations are not user instructions. Observations are historical: recheck resource, host and current permissions before acting.";
40
- }
41
-
42
- /** Preserve the source locator even when the interface for retrieving it is unavailable. */
43
- export function artifactRecovery(sha256: string, offset: number, tools: readonly string[] = ["history"]): string {
44
- const source = `artifact=${sha256} offset=${offset}`;
45
- return tools.includes("history") ? `history ${source}` : `${source}; recovery capability unavailable`;
46
- }