@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,200 +0,0 @@
1
- /**
2
- * O.3: exact edits remain exact; a loose match is never silent.
3
- *
4
- * planEdit is the original single exact replacement and stays as it was.
5
- * planEdits is what the tool uses: several edits, matched on the view read
6
- * shows (see text-normalize.ts), with a loose fallback that is reported as
7
- * such in the result rather than applied quietly.
8
- */
9
- import { editDistance, fromView, normalizeLoose, similarity, toLF, toView } from "./text-normalize.ts";
10
-
11
- export function lineDiff(before: string, after: string): { diff: string; firstLine: number } {
12
- const a = before.split("\n"), b = after.split("\n");
13
- let prefix = 0, suffix = 0;
14
- while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix]) prefix++;
15
- while (suffix < a.length - prefix && suffix < b.length - prefix && a[a.length - suffix - 1] === b[b.length - suffix - 1]) suffix++;
16
- const removed = a.slice(prefix, a.length - suffix), added = b.slice(prefix, b.length - suffix);
17
- return { firstLine: prefix + 1,
18
- diff: before === after ? "(content unchanged)" : `@@ -${prefix + 1},${removed.length} +${prefix + 1},${added.length} @@\n` +
19
- [...removed.map(line => `-${line}`), ...added.map(line => `+${line}`)].join("\n") };
20
- }
21
-
22
- export function planEdit(before: string, oldText: string, newText: string, all: boolean):
23
- { ok: true; after: string; replacements: number; diff: string; firstLine: number } |
24
- { ok: false; code: "invalid_input" | "not_found" | "ambiguous_edit"; occurrences: number } {
25
- if (!oldText) return { ok: false, code: "invalid_input", occurrences: 0 };
26
- const occurrences = before.split(oldText).length - 1;
27
- if (occurrences === 0) return { ok: false, code: "not_found", occurrences };
28
- if (occurrences > 1 && !all) return { ok: false, code: "ambiguous_edit", occurrences };
29
- // split/join avoids replacement-string interpolation of literal $&, $` or $'.
30
- const at = before.indexOf(oldText);
31
- const after = all ? before.split(oldText).join(newText) : before.slice(0, at) + newText + before.slice(at + oldText.length);
32
- const a = before.split("\n"), b = after.split("\n");
33
- let prefix = 0, suffix = 0;
34
- while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix]) prefix++;
35
- while (suffix < a.length - prefix && suffix < b.length - prefix && a[a.length - suffix - 1] === b[b.length - suffix - 1]) suffix++;
36
- const removed = a.slice(prefix, a.length - suffix), added = b.slice(prefix, b.length - suffix);
37
- return { ok: true, after, replacements: all ? occurrences : 1, firstLine: prefix + 1,
38
- diff: before === after ? "(content unchanged)" : `@@ -${prefix + 1},${removed.length} +${prefix + 1},${added.length} @@\n` +
39
- [...removed.map(line => `-${line}`), ...added.map(line => `+${line}`)].join("\n") };
40
- }
41
-
42
-
43
- export interface EditSpec { old_string: string; new_string: string; replace_all?: boolean }
44
-
45
- export interface ClosestMatch {
46
- /** 1-indexed, inclusive, in the view read shows. */
47
- startLine: number;
48
- endLine: number;
49
- score: number;
50
- /** First line of old_string that differs from the file, and the file's line there. */
51
- expected: string;
52
- actual: string;
53
- }
54
-
55
- export type EditsPlan =
56
- | { ok: true; after: string; replacements: number; fuzzy: boolean; diff: string; firstLine: number }
57
- | { ok: false; code: "invalid_input" | "not_found" | "ambiguous_edit"; edit: number; occurrences: number;
58
- message: string; closest?: ClosestMatch };
59
-
60
- /** A replacement in view coordinates: [start, end) becomes text. */
61
- interface Span { start: number; end: number; text: string; edit: number }
62
-
63
- function occurrences(haystack: string, needle: string): number[] {
64
- const found: number[] = [];
65
- for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + needle.length)) found.push(at);
66
- return found;
67
- }
68
-
69
- /** Offsets of each line start in `text`, plus a sentinel one past the end. */
70
- function lineStarts(text: string): number[] {
71
- const starts = [0];
72
- for (let i = 0; i < text.length; i++) if (text[i] === "\n") starts.push(i + 1);
73
- starts.push(text.length + 1);
74
- return starts;
75
- }
76
-
77
- const lineOf = (starts: readonly number[], offset: number) => {
78
- let lo = 0, hi = starts.length - 2;
79
- while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (starts[mid]! <= offset) lo = mid; else hi = mid - 1; }
80
- return lo;
81
- };
82
-
83
- /**
84
- * Loose matches mapped back onto whole original lines.
85
- *
86
- * normalizeLoose keeps the line count, so a match starting on normalized line
87
- * i covers original lines i..j. Those lines -- and only those -- are replaced
88
- * by their normalized form with the substitution applied. Rewriting the whole
89
- * normalized file instead would quietly straighten every quote in it.
90
- */
91
- function looseSpans(text: string, spec: { old: string; next: string; all: boolean }, edit: number):
92
- { spans: Span[] } | { count: number } {
93
- const loose = normalizeLoose(text);
94
- const needle = normalizeLoose(spec.old);
95
- if (needle.trim().length === 0) return { count: 0 };
96
- const found = occurrences(loose, needle);
97
- if (found.length === 0 || (found.length > 1 && !spec.all)) return { count: found.length };
98
- const looseStarts = lineStarts(loose), starts = lineStarts(text);
99
- const spans: Span[] = [];
100
- for (const at of found) {
101
- const first = lineOf(looseStarts, at), last = lineOf(looseStarts, at + needle.length - 1);
102
- const from = looseStarts[first]!, to = looseStarts[last + 1]! - 1;
103
- const segment = loose.slice(from, to);
104
- const local = at - from;
105
- spans.push({ start: starts[first]!, end: starts[last + 1]! - 1, edit,
106
- text: segment.slice(0, local) + spec.next + segment.slice(local + needle.length) });
107
- }
108
- return { spans };
109
- }
110
-
111
- /**
112
- * Where old_string most nearly occurs, for a failure message that lets the
113
- * model fix its copy instead of rereading the file (omp reports the same:
114
- * `crates/pi-edit/src/fuzzy.rs`). Candidates are anchored on lines of
115
- * old_string that occur verbatim (trimmed); failing that, on the most similar
116
- * single line, which is only computed for files small enough to afford it.
117
- */
118
- export function closestMatch(text: string, old: string): ClosestMatch | undefined {
119
- const lines = text.split("\n");
120
- const want = old.split("\n");
121
- if (want.length > lines.length) return undefined;
122
- const clip = (line: string) => line.trim().slice(0, 200);
123
- const starts = new Set<number>();
124
- const index = new Map<string, number[]>();
125
- lines.forEach((line, i) => { const key = clip(line); if (key.length >= 4) index.set(key, [...(index.get(key) ?? []), i]); });
126
- want.forEach((line, k) => {
127
- for (const i of index.get(clip(line)) ?? []) if (i - k >= 0 && i - k + want.length <= lines.length) starts.add(i - k);
128
- });
129
- if (starts.size === 0 && lines.length <= 5_000) {
130
- const anchor = want.findIndex((line) => clip(line).length > 0);
131
- if (anchor === -1) return undefined;
132
- const target = clip(want[anchor]!).slice(0, 80);
133
- let best = -1, bestScore = 0;
134
- lines.forEach((line, i) => {
135
- const candidate = clip(line).slice(0, 80);
136
- if (Math.abs(candidate.length - target.length) > target.length / 2) return;
137
- const score = similarity(candidate, target);
138
- if (score > bestScore && i - anchor >= 0 && i - anchor + want.length <= lines.length) { best = i - anchor; bestScore = score; }
139
- });
140
- if (best >= 0) starts.add(best);
141
- }
142
- let result: ClosestMatch | undefined;
143
- const block = want.join("\n").slice(0, 2_000);
144
- for (const start of [...starts].slice(0, 20)) {
145
- const window = lines.slice(start, start + want.length);
146
- const score = 1 - editDistance(window.join("\n").slice(0, 2_000), block) / Math.max(1, Math.max(block.length, window.join("\n").slice(0, 2_000).length));
147
- if (result !== undefined && score <= result.score) continue;
148
- const differs = want.findIndex((line, k) => line !== window[k]);
149
- result = { startLine: start + 1, endLine: start + want.length, score,
150
- expected: want[Math.max(0, differs)] ?? "", actual: window[Math.max(0, differs)] ?? "" };
151
- }
152
- return result !== undefined && result.score >= 0.5 ? result : undefined;
153
- }
154
-
155
- /**
156
- * Several edits, all located in the original before any is applied, so one
157
- * edit can never match text another just wrote. All succeed or none do.
158
- */
159
- export function planEdits(raw: string, edits: readonly EditSpec[]): EditsPlan {
160
- const view = toView(raw);
161
- const text = view.text;
162
- if (edits.length === 0) return { ok: false, code: "invalid_input", edit: 0, occurrences: 0, message: "no edits given" };
163
- const spans: Span[] = [];
164
- let fuzzy = false;
165
- for (let e = 0; e < edits.length; e++) {
166
- const spec = { old: toLF(edits[e]!.old_string), next: toLF(edits[e]!.new_string), all: edits[e]!.replace_all === true };
167
- const which = edits.length > 1 ? `edit ${e + 1}: ` : "";
168
- if (spec.old.length === 0) return { ok: false, code: "invalid_input", edit: e, occurrences: 0, message: `${which}old_string must not be empty` };
169
- const exact = occurrences(text, spec.old);
170
- if (exact.length > 1 && !spec.all) {
171
- return { ok: false, code: "ambiguous_edit", edit: e, occurrences: exact.length,
172
- message: `${which}old_string appears ${exact.length} times; add surrounding lines to make it unique or pass replace_all` };
173
- }
174
- if (exact.length > 0) {
175
- for (const at of exact) spans.push({ start: at, end: at + spec.old.length, text: spec.next, edit: e });
176
- continue;
177
- }
178
- const loose = looseSpans(text, spec, e);
179
- if ("spans" in loose) { spans.push(...loose.spans); fuzzy = true; continue; }
180
- if (loose.count > 1) {
181
- return { ok: false, code: "ambiguous_edit", edit: e, occurrences: loose.count,
182
- message: `${which}old_string has no exact match and ${loose.count} matches ignoring whitespace and quote style; add surrounding lines to make it unique` };
183
- }
184
- const closest = closestMatch(text, spec.old);
185
- return { ok: false, code: "not_found", edit: e, occurrences: 0,
186
- message: `${which}old_string not found` + (closest === undefined ? "; read the current file before retrying" : ""),
187
- ...(closest === undefined ? {} : { closest }) };
188
- }
189
- spans.sort((a, b) => a.start - b.start);
190
- for (let i = 1; i < spans.length; i++) {
191
- if (spans[i]!.start < spans[i - 1]!.end) {
192
- return { ok: false, code: "invalid_input", edit: spans[i]!.edit, occurrences: 0,
193
- message: `edits ${spans[i - 1]!.edit + 1} and ${spans[i]!.edit + 1} overlap; merge them into one edit` };
194
- }
195
- }
196
- let after = text;
197
- for (let i = spans.length - 1; i >= 0; i--) after = after.slice(0, spans[i]!.start) + spans[i]!.text + after.slice(spans[i]!.end);
198
- if (after === text) return { ok: false, code: "invalid_input", edit: 0, occurrences: 0, message: "no change: new_string produces the current content" };
199
- return { ok: true, after: fromView(view, after), replacements: spans.length, fuzzy, ...lineDiff(text, after) };
200
- }
@@ -1,181 +0,0 @@
1
- import type { ArtifactRef, LogEvent } from "./events.ts";
2
- import { isObservationArtifact } from "./observation-view.ts";
3
- import { workspaceChanged } from "./checkpoints.ts";
4
-
5
- /**
6
- * Facts about the machine, fixed at session start.
7
- *
8
- * Without them the model spends its first requests discovering what one
9
- * `uname` / `pwd` / `git status` would have told it (Meta-Harness and
10
- * LangChain's LocalContext middleware inject the same snapshot for that
11
- * reason). They go into the frozen prefix, which a resumed session reads
12
- * back from its saved context rather than rebuilding, so a date that has
13
- * moved on by the next day does not change the cached prefix.
14
- */
15
- export interface SessionEnvironment {
16
- platform: string;
17
- shell: string;
18
- cwd: string;
19
- /** Local calendar date, YYYY-MM-DD. */
20
- date: string;
21
- git: boolean;
22
- /**
23
- * Whether `rg` resolves in tool shells at session start. Models reach for
24
- * it by habit; without this line a missing one costs an exit-127 request.
25
- * Optional so environments recorded before it existed still render.
26
- */
27
- ripgrep?: boolean;
28
- }
29
-
30
- export function environmentSection(env: SessionEnvironment): string {
31
- return [
32
- "Environment (recorded at session start):",
33
- `- Platform: ${env.platform}`,
34
- `- Shell: ${env.shell}`,
35
- `- Working directory: ${env.cwd}`,
36
- `- Date: ${env.date}`,
37
- `- Git repository: ${env.git ? "yes" : "no"}`,
38
- ...(env.ripgrep === undefined ? [] : [`- ripgrep (rg) in the shell: ${env.ripgrep ? "yes" : "no — use the grep and find tools"}`]),
39
- ].join("\n");
40
- }
41
-
42
- /** R6: these are operational assumptions, not measured freshness guarantees. */
43
- export const ENVIRONMENT_TTL_MS = 5 * 60_000;
44
- export const ENVIRONMENT_TIMEOUT_MS = 5_000;
45
- export const ENVIRONMENT_OUTPUT_BYTES = 256 * 1024;
46
- export const ENVIRONMENT_PARSE_BYTES = 64 * 1024;
47
- /** Version reports are metadata; verbose command output remains in the original artifact. */
48
- export const ENVIRONMENT_VERSION_BYTES = 4096;
49
- export type EnvironmentProbe = "version" | "python";
50
- export type EnvironmentSource = { host: string; workspace: string; cwd: string; runtime: string; inherited_sha256: string };
51
- export type PythonCapability = { executable: string; prefix: string; base_prefix: string; version: string;
52
- implementation: string; isolated: true; features: { tarfile_extractall_filter: boolean } };
53
- export type EnvironmentReceipt = {
54
- kind: "environment"; version: 1;
55
- query: { program: string; probe: EnvironmentProbe };
56
- source: EnvironmentSource;
57
- observed_at: number; expires_at: number;
58
- state: "observed" | "missing" | "opaque" | "failed" | "unknown";
59
- shell: { executable: string; version: string; cwd: string; path_sha256: string } | null;
60
- resolution: { kind: string; executable: string | null } | null;
61
- reported_version: string | null;
62
- python?: PythonCapability;
63
- evidence: { output: ArtifactRef; exit: number; complete: boolean };
64
- reason?: string;
65
- };
66
-
67
- const quote = (text: string) => "'" + text.replaceAll("'", "'\\''") + "'";
68
- const marker = "\0XENO_ENVIRONMENT_V1\0";
69
- const PYTHON_CAPABILITY_PROBE = [
70
- "import inspect,json,platform,sys,tarfile",
71
- "print(json.dumps({'executable':sys.executable,'prefix':sys.prefix,'base_prefix':sys.base_prefix,'version':platform.python_version(),'implementation':platform.python_implementation(),'isolated':bool(sys.flags.isolated),'features':{'tarfile_extractall_filter':'filter' in inspect.signature(tarfile.TarFile.extractall).parameters}},sort_keys=True))",
72
- ].join(";");
73
-
74
- /** Program names are data, including paths with quotes/newlines; no caller code is evaluated. */
75
- export function environmentProbeCommand(program: unknown, probe: unknown = "version"): string {
76
- if (typeof program !== "string" || !program || program.includes("\0") || new TextEncoder().encode(program).length > 4096) throw new Error("program must be a nonempty name or path up to 4096 bytes without NUL");
77
- if (probe !== "version" && probe !== "python") throw new Error("probe must be version or python");
78
- return [
79
- "builtin set +e", `xeno_probe_program=${quote(program)}`,
80
- 'xeno_probe_kind=$(builtin type -t -- "$xeno_probe_program" 2>/dev/null)',
81
- 'xeno_probe_path=""',
82
- 'if [ "$xeno_probe_kind" = file ]; then xeno_probe_path=$(builtin type -P -- "$xeno_probe_program" 2>/dev/null); fi',
83
- // A single frame precedes the program output, which remains untrusted data.
84
- `builtin printf '\\0XENO_ENVIRONMENT_V1\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0' "$xeno_probe_program" ${quote(probe)} "$xeno_probe_kind" "$xeno_probe_path" "$BASH" "$BASH_VERSION" "$PWD" "$PATH"`,
85
- 'if [ -z "$xeno_probe_kind" ]; then builtin exit 127; fi',
86
- 'if [ "$xeno_probe_kind" != file ] || [ -z "$xeno_probe_path" ]; then builtin exit 125; fi',
87
- // Replace the discovery shell: the managed group leader and exit belong
88
- // to the probed program, not an extra shell waiting for its child (R1).
89
- probe === "python" ? `builtin exec -- "$xeno_probe_path" -I -c ${quote(PYTHON_CAPABILITY_PROBE)}` : 'builtin exec -- "$xeno_probe_path" --version',
90
- ].join("\n");
91
- }
92
-
93
- export interface EnvironmentFrame { program: string; probe: EnvironmentProbe; kind: string; executable: string | null;
94
- shell: string; shell_version: string; cwd: string; path: string; output: string }
95
- export function parseEnvironmentFrame(text: string, program: string, probe: EnvironmentProbe): EnvironmentFrame | null {
96
- const start = text.indexOf(marker);
97
- if (start < 0) return null;
98
- const fields: string[] = [];
99
- let at = start + marker.length;
100
- for (let i = 0; i < 8; i++) {
101
- const end = text.indexOf("\0", at);
102
- if (end < 0) return null;
103
- fields.push(text.slice(at, end)); at = end + 1;
104
- }
105
- if (fields[0] !== program || fields[1] !== probe || !["", "file", "function", "alias", "builtin", "keyword"].includes(fields[2]!) ||
106
- !fields[4] || !fields[5] || !fields[6] || (fields[2] === "file" && !fields[3])) return null;
107
- return { program, probe, kind: fields[2]!, executable: fields[3] || null, shell: fields[4]!, shell_version: fields[5]!, cwd: fields[6]!, path: fields[7]!, output: text.slice(at) };
108
- }
109
-
110
- const object = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
111
- const digest = (value: unknown): value is string => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
112
- const strings = (value: Record<string, unknown>, keys: readonly string[]) => keys.every(key => typeof value[key] === "string" && value[key] !== "");
113
- export function isPythonCapability(value: unknown): value is PythonCapability {
114
- return object(value) && strings(value, ["executable", "prefix", "base_prefix", "version", "implementation"]) && value["isolated"] === true &&
115
- object(value["features"]) && typeof value["features"]["tarfile_extractall_filter"] === "boolean";
116
- }
117
-
118
- /** D11: neither a version-looking line nor an interrupted exit zero proves a capability. */
119
- export function environmentCapability(frame: EnvironmentFrame | null, exit: number, capture: {
120
- complete: boolean; stopped?: "cancelled" | "timeout"; limited?: "output" | "parse";
121
- }): Pick<EnvironmentReceipt, "state" | "reported_version" | "python" | "reason"> {
122
- const unknown = (reason: string) => ({ state: "unknown" as const, reported_version: null, reason });
123
- if (capture.limited) return unknown(`probe exceeded its ${capture.limited} budget; inspect the original output`);
124
- if (capture.stopped) return unknown(`probe ${capture.stopped}; inspect partial effects before continuing`);
125
- if (!capture.complete || !frame) return unknown("probe output is incomplete or its environment frame is invalid");
126
- if (frame.kind === "" && exit === 127) return { state: "missing", reported_version: null, reason: "program was not found in the command environment" };
127
- if (frame.kind !== "file") return { state: "opaque", reported_version: null, reason: "shell resolution is not an executable file; specify its exact path" };
128
- if (exit !== 0) return { state: "failed", reported_version: null, reason: "program exited unsuccessfully; a version-like line is not capability evidence" };
129
- const output = frame.output.trim();
130
- if (!output || new TextEncoder().encode(output).length > ENVIRONMENT_VERSION_BYTES) return unknown("version report is empty or exceeds the metadata budget");
131
- if (frame.probe === "version") return { state: "observed", reported_version: output };
132
- try {
133
- const value: unknown = JSON.parse(output);
134
- if (isPythonCapability(value)) return { state: "observed", reported_version: value.version, python: value };
135
- } catch { /* The complete untrusted report remains available in the artifact. */ }
136
- return unknown("Python did not return a valid isolated capability report");
137
- }
138
- export function isEnvironmentReceipt(value: unknown): value is EnvironmentReceipt {
139
- if (!object(value) || value["kind"] !== "environment" || value["version"] !== 1 || !object(value["query"]) ||
140
- !strings(value["query"], ["program"]) || !["version", "python"].includes(String(value["query"]["probe"]))) return false;
141
- const source = value["source"], evidence = value["evidence"], shell = value["shell"], resolution = value["resolution"];
142
- if (!object(source) || !strings(source, ["host", "workspace", "cwd", "runtime"]) || !digest(source["inherited_sha256"]) ||
143
- !object(evidence) || !isObservationArtifact(evidence["output"]) || !Number.isInteger(evidence["exit"]) || typeof evidence["complete"] !== "boolean") return false;
144
- const observed = value["observed_at"], expires = value["expires_at"];
145
- if (typeof observed !== "number" || !Number.isSafeInteger(observed) || observed < 0 || expires !== observed + ENVIRONMENT_TTL_MS ||
146
- !["observed", "missing", "opaque", "failed", "unknown"].includes(String(value["state"])) ||
147
- !(value["reported_version"] === null || typeof value["reported_version"] === "string") ||
148
- !(value["reason"] === undefined || typeof value["reason"] === "string")) return false;
149
- if (shell !== null && (!object(shell) || !strings(shell, ["executable", "version", "cwd"]) || !digest(shell["path_sha256"]))) return false;
150
- if (resolution !== null && (!object(resolution) || typeof resolution["kind"] !== "string" ||
151
- !(resolution["executable"] === null || typeof resolution["executable"] === "string"))) return false;
152
- if (value["python"] !== undefined && !isPythonCapability(value["python"])) return false;
153
- if (value["state"] === "observed" && (evidence["exit"] !== 0 || !evidence["complete"] || !object(shell) || !object(resolution) ||
154
- resolution["kind"] !== "file" || !resolution["executable"] || !value["reported_version"] ||
155
- value["query"]["probe"] === "python" && value["python"] === undefined)) return false;
156
- return true;
157
- }
158
-
159
- export type EnvironmentValidity = { state: "recorded" | "expired" | "scope_changed" | "invalidated" | "unconfirmed";
160
- reason: string; event?: string };
161
- /** Recomputed on consumption: recent observations are never guarantees about a future invocation. */
162
- export function environmentValidity(receipt: EnvironmentReceipt, source: EnvironmentSource, now: number, after: readonly LogEvent[]): EnvironmentValidity {
163
- for (const event of after) {
164
- if (event.t === "user_bash" || workspaceChanged(event)) {
165
- return { state: "invalidated", reason: "later command or workspace change requires environment revalidation", event: event.id };
166
- }
167
- if (event.t !== "tool_result") continue;
168
- if (isEnvironmentReceipt(event.data) && event.data.query.program === receipt.query.program && event.data.query.probe === receipt.query.probe) {
169
- return { state: "invalidated", reason: "a later probe supersedes this observation, including a failed probe", event: event.id };
170
- }
171
- if (event.effect?.kind === "edit" || event.effect?.kind === "command" || event.diagnostic?.code === "environment_missing" ||
172
- event.diagnostic?.effects === "possible" && event.diagnostic.execution !== "not_started") {
173
- return { state: "invalidated", reason: "later execution or mutation requires environment revalidation", event: event.id };
174
- }
175
- }
176
- const keys: readonly (keyof EnvironmentSource)[] = ["host", "workspace", "cwd", "runtime", "inherited_sha256"];
177
- if (keys.some(key => source[key] !== receipt.source[key])) return { state: "scope_changed", reason: "host, workspace, directory, runtime, or inherited environment changed" };
178
- if (!Number.isFinite(now) || now < receipt.observed_at || now >= receipt.expires_at) return { state: "expired", reason: "observation time is outside its validity interval" };
179
- if (receipt.state !== "observed") return { state: "unconfirmed", reason: "the probe did not establish this capability" };
180
- return { state: "recorded", reason: "recent observation only; login startup files or external changes may require another probe" };
181
- }
@@ -1,220 +0,0 @@
1
- /** D8/D11: malformed durable data is corruption, never a valid empty event. */
2
- import type { LogEvent, NodeRef } from "./events.ts";
3
- import { isExecutionOutcome } from "./outcome.ts";
4
- import { isJsonValue, isToolDiagnostic } from "./tool-contract.ts";
5
- import { validImageRef } from "./images.ts";
6
- import { parseAcceptanceContract } from "./acceptance.ts";
7
- import { validFileChanges } from "./checkpoints.ts";
8
- import { isObservationReceipt } from "./read-observation.ts";
9
- import { isSearchReceipt } from "./search-observation.ts";
10
- import { isProcessReceipt } from "./process-receipt.ts";
11
- import { isObservationArtifact } from "./observation-view.ts";
12
- import { isCommandArtifactReceipt } from "./command-artifacts.ts";
13
- import { isRuntimeTargetReceipt } from "./runtime-target.ts";
14
-
15
- export function isRecord(value: unknown): value is Record<string, unknown> {
16
- return value !== null && typeof value === "object" && !Array.isArray(value);
17
- }
18
- const strings = (value: unknown): boolean => Array.isArray(value) && value.every((v) => typeof v === "string");
19
- const optionalString = (value: unknown): boolean => value === undefined || typeof value === "string";
20
- const natural = (value: unknown): boolean => Number.isSafeInteger(value) && Number(value) >= 0;
21
- const eventRef = (value: unknown): boolean => isRecord(value) && natural(value["node"]) && typeof value["event"] === "string" && value["event"] !== "";
22
- export function validNodeRef(value: unknown): value is NodeRef {
23
- return isRecord(value) && Number.isSafeInteger(value["node"]) && Number(value["node"]) >= 0 &&
24
- typeof value["log_ref"] === "string" && typeof value["sha256"] === "string" &&
25
- (value["at_event"] === undefined || typeof value["at_event"] === "string");
26
- }
27
-
28
- /** A label is a short phrase or class name; anything longer or with control characters is not one the harness wrote. */
29
- function validFailureCause(value: unknown): boolean {
30
- return isRecord(value) && (value["source"] === "provider" || value["source"] === "harness") &&
31
- typeof value["label"] === "string" && /^[^\u0000-\u001f\u007f]{1,64}$/.test(value["label"]) &&
32
- (value["status"] === undefined || Number.isSafeInteger(value["status"])) &&
33
- (value["retries"] === undefined || natural(value["retries"]));
34
- }
35
-
36
- export function isLogEvent(value: unknown): value is LogEvent {
37
- if (!isRecord(value) || typeof value["id"] !== "string" || value["id"].length === 0 || typeof value["at"] !== "string") return false;
38
- if (value["outcome"] !== undefined && !isExecutionOutcome(value["outcome"])) return false;
39
- if (value["work_id"] !== undefined && !(typeof value["work_id"] === "string" && value["work_id"].trim().length > 0)) return false;
40
- const carried = value["carried_from"];
41
- if (carried !== undefined && !(isRecord(carried) && natural(carried["node"]) && typeof carried["event"] === "string")) return false;
42
- const has = (...keys: string[]): boolean => keys.every((key) => typeof value[key] === "string");
43
- const images = value["images"];
44
- if (images !== undefined && (!Array.isArray(images) || !images.every(validImageRef))) return false;
45
- switch (value["t"]) {
46
- case "tool_invocation": return has("call_id", "name") && value["call_id"] !== "" && value["name"] !== "" &&
47
- (value["origin"] === undefined || value["origin"] === "extension") &&
48
- isObservationArtifact(value["input_ref"]) && (value["schema_ref"] === null || isObservationArtifact(value["schema_ref"])) &&
49
- (value["input"] === undefined || isJsonValue(value["input"])) && optionalString(value["parent_call_id"]) &&
50
- (value["parent_ref"] === undefined ? value["parent_call_id"] === undefined : eventRef(value["parent_ref"]) && value["parent_call_id"] !== undefined) &&
51
- value["parent_call_id"] !== "" && value["parent_call_id"] !== value["call_id"];
52
- case "checkpoint": return has("operation") && ["edit", "git", "restore"].includes(String(value["source"])) &&
53
- ["prepared", "applied", "unavailable"].includes(String(value["phase"])) && validFileChanges(value["changes"]) &&
54
- optionalString(value["tree"]) && optionalString(value["reason"]);
55
- case "workspace_observation": return value["scope"] === "source-v1" && typeof value["changed"] === "boolean" && optionalString(value["reason"]) &&
56
- (value["baseline"] === undefined || value["baseline"] === true) &&
57
- (value["fingerprint"] === undefined || typeof value["fingerprint"] === "string" && /^[a-f0-9]{64}$/.test(value["fingerprint"]));
58
- case "process_exit": {
59
- const output = value["output"];
60
- const receipt = value["receipt"];
61
- if (receipt !== undefined && (!isProcessReceipt(receipt) || receipt.replayed || receipt.running !== false ||
62
- receipt.session_id !== value["session"] || receipt.exit !== value["exit"] || !isRecord(output) ||
63
- receipt.artifacts !== undefined && receipt.artifacts.command !== value["command"] ||
64
- receipt.output === undefined || receipt.output.sha256 !== output["sha256"] || receipt.output.bytes !== output["bytes"])) return false;
65
- return has("command") && natural(value["session"]) && Number(value["session"]) > 0 && Number.isInteger(value["exit"]) &&
66
- value["reason"] === "disposed" && isRecord(output) && typeof output["path"] === "string" &&
67
- typeof output["sha256"] === "string" && /^[a-f0-9]{64}$/.test(output["sha256"]) && natural(output["bytes"]);
68
- }
69
- case "approval": {
70
- const user = ["once", "command", "tool", "deny"].includes(String(value["decision"]));
71
- const rule = value["decision"] === "allow" || value["decision"] === "deny";
72
- return has("call_id", "tool") && value["call_id"] !== "" && value["tool"] !== "" &&
73
- optionalString(value["command"]) && optionalString(value["rule"]) &&
74
- (value["source"] === "user" ? user : value["source"] === "no_prompt" ? value["decision"] === "deny" :
75
- ["session_rule", "config", "auto"].includes(String(value["source"])) && rule);
76
- }
77
- case "skill_loaded": return has("name", "path", "sha256", "artifact") &&
78
- ["sha256", "artifact"].every(key => /^[a-f0-9]{64}$/.test(String(value[key]))) &&
79
- ["inline", "fork", "read"].includes(String(value["mode"])) && ["user", "model"].includes(String(value["origin"]));
80
- case "acceptance_contract": {
81
- try { parseAcceptanceContract(value["contract"]); return has("request"); } catch { return false; }
82
- }
83
- case "acceptance_decision": return has("request", "contract_id", "criterion") && value["actor"] === "user" &&
84
- (value["target_binding"] === undefined ? value["target_observation"] === undefined : has("target_binding", "target_observation"));
85
- case "target_observation": return isRuntimeTargetReceipt(value["observation"]);
86
- case "counter_check": return has("request", "criterion", "command") && (value["guard"] === undefined || value["guard"] === true) &&
87
- (Number.isSafeInteger(value["exit"]) ? value["reason"] === undefined : typeof value["reason"] === "string" && value["exit"] === undefined);
88
- case "blind_check": return has("request", "check") && typeof value["exposed"] === "boolean" && optionalString(value["exposure"]) &&
89
- (Number.isSafeInteger(value["exit"]) ? value["reason"] === undefined : typeof value["reason"] === "string" && value["exit"] === undefined);
90
- case "goal": {
91
- if (value["action"] === "clear") return value["reason"] === "met" || value["reason"] === "user";
92
- if (value["action"] !== "set" || !has("objective", "draft") || !strings(value["accepted"]) || !strings(value["rejected"])) return false;
93
- try {
94
- const contract = parseAcceptanceContract(value["contract"]), accepted = value["accepted"] as string[];
95
- return accepted.length > 0 && contract.criteria.map(item => item.id).join("\0") === accepted.join("\0");
96
- } catch { return false; }
97
- }
98
- case "extension": return has("customType", "content") && typeof value["display"] === "boolean" && typeof value["inContext"] === "boolean" && (value["data"] === undefined || isJsonValue(value["data"]));
99
- case "user": return has("text") && (value["request"] === undefined || typeof value["request"] === "string" && value["request"] !== "");
100
- case "outcome": return has("reason") && isExecutionOutcome(value["outcome"]) && (value["failure"] === undefined || validFailureCause(value["failure"]));
101
- case "user_bash": {
102
- const ref = value["content_ref"];
103
- const outcome = value["outcome"];
104
- if (!isExecutionOutcome(outcome) || !["tool_completed", "tool_failed", "cancelled", "timeout"].includes(outcome.reason)) return false;
105
- if (outcome.reason === "tool_completed" && value["exit"] !== 0) return false;
106
- if (outcome.reason === "tool_failed" && value["exit"] === 0) return false;
107
- return has("command", "content") && Number.isInteger(value["exit"]) && typeof value["excludeFromContext"] === "boolean" &&
108
- isRecord(ref) && typeof ref["sha256"] === "string" &&
109
- typeof ref["path"] === "string" && natural(ref["bytes"]);
110
- }
111
- case "guidance": return has("text") && ["continuation", "repetition", "budget", "boundary", "stall", "output_recovery", "completion_review", "churn", "deadline", "runtime_acceptance", "goal", "secret", "branch"].includes(String(value["reason"])) && strings(value["sources"]);
112
- case "interrupt": return has("user_id");
113
- case "assistant": return (value["stop_reason"] === null || optionalString(value["stop_reason"])) &&
114
- (value["replay_key"] === undefined || typeof value["replay_key"] === "string" && /^[a-f0-9]{64}$/.test(value["replay_key"])) &&
115
- Array.isArray(value["blocks"]) && value["blocks"].every((b: unknown) => {
116
- if (!isRecord(b)) return false;
117
- if (b["type"] === "text") return typeof b["text"] === "string";
118
- if (b["type"] === "thinking") return typeof b["text"] === "string" && "raw" in b;
119
- return b["type"] === "tool_use" && typeof b["call_id"] === "string" && typeof b["name"] === "string" && "input" in b;
120
- });
121
- case "tool_result": {
122
- if (!optionalString(value["parent_call_id"]) || value["parent_call_id"] === "" || value["parent_call_id"] === value["call_id"]) return false;
123
- if (value["origin"] !== undefined && value["origin"] !== "extension") return false;
124
- if (value["invocation_ref"] !== undefined && !eventRef(value["invocation_ref"])) return false;
125
- const observation = value["observation"];
126
- if (observation !== undefined && (!isObservationReceipt(observation) ||
127
- !isRecord(value["content_ref"]) || value["content_ref"]["sha256"] !== observation.artifact.sha256 ||
128
- value["content_ref"]["bytes"] !== observation.artifact.bytes || value["content_sha256"] !== observation.artifact.sha256)) return false;
129
- if (value["data"] !== undefined && !isJsonValue(value["data"])) return false;
130
- if (value["data_ref"] !== undefined && (!isObservationArtifact(value["data_ref"]) || value["data"] !== undefined)) return false;
131
- if (isRecord(value["data"]) && value["data"]["kind"] === "process") {
132
- const process = value["data"];
133
- if (!isProcessReceipt(process)) return false;
134
- if (process.output && (!isRecord(value["content_ref"]) || value["content_ref"]["sha256"] !== process.output.sha256 ||
135
- value["content_ref"]["bytes"] !== process.output.bytes || value["content_sha256"] !== process.output.sha256)) return false;
136
- if (process.replayed && value["effect"] !== undefined) return false;
137
- if (value["effect"] !== undefined && (!isRecord(value["effect"]) || value["effect"]["kind"] !== "command" ||
138
- process.running !== false || value["effect"]["exit"] !== process.exit ||
139
- JSON.stringify(value["effect"]["artifacts"]) !== JSON.stringify(process.artifacts))) return false;
140
- }
141
- if (isObservationReceipt(observation) && observation.descriptor !== undefined &&
142
- (!isRecord(value["data"]) || value["data"]["read_handle"] !== observation.descriptor.sha256)) return false;
143
- if (isRecord(value["data"]) && value["data"]["kind"] === "search") {
144
- const search = value["data"];
145
- if (!isSearchReceipt(search) || !isRecord(value["content_ref"]) || value["content_ref"]["sha256"] !== search.artifact.sha256 ||
146
- value["content_ref"]["bytes"] !== search.artifact.bytes || value["content_sha256"] !== search.artifact.sha256) return false;
147
- }
148
- if (value["diagnostic"] !== undefined && !isToolDiagnostic(value["diagnostic"])) return false;
149
- if (value["warnings"] !== undefined && (!Array.isArray(value["warnings"]) || !value["warnings"].every(isToolDiagnostic))) return false;
150
- const instructions = value["instructions"];
151
- if (instructions !== undefined && (!isRecord(instructions) || !Array.isArray(instructions["keys"]) || instructions["keys"].length === 0 ||
152
- !instructions["keys"].every(key => typeof key === "string" && key.length > 0) ||
153
- typeof instructions["text"] !== "string" || instructions["text"].length === 0)) return false;
154
- if (!has("call_id", "content") || typeof value["ok"] !== "boolean") return false;
155
- if (!optionalString(value["content_sha256"])) return false;
156
- if (!optionalString(value["observation_sha256"])) return false;
157
- const artifact = value["content_ref"];
158
- if (artifact !== undefined && !(isRecord(artifact) && typeof artifact["sha256"] === "string" &&
159
- typeof artifact["path"] === "string" && natural(artifact["bytes"]))) return false;
160
- const effect = value["effect"];
161
- if (effect === undefined) return true;
162
- if (!isRecord(effect)) return false;
163
- if (effect["kind"] === "command") return typeof effect["cmd"] === "string" && Number.isInteger(effect["exit"]) &&
164
- optionalString(effect["gate"]) && optionalString(effect["workspace_sha256"]) &&
165
- (effect["artifacts"] === undefined || isCommandArtifactReceipt(effect["artifacts"]) &&
166
- effect["artifacts"].exit === effect["exit"] && effect["artifacts"].command === effect["cmd"]) &&
167
- (effect["workspace_scope"] === undefined || effect["workspace_scope"] === "source-v1") &&
168
- (effect["workspace_observed"] === undefined || typeof effect["workspace_observed"] === "boolean");
169
- if (effect["kind"] === "read") return typeof effect["path"] === "string" && typeof effect["sha256"] === "string" &&
170
- (effect["ranges"] === undefined || (Array.isArray(effect["ranges"]) && effect["ranges"].every((r: unknown) =>
171
- Array.isArray(r) && r.length === 2 && r.every((n) => Number.isSafeInteger(n)) && r[0] >= 1 && r[1] >= r[0])));
172
- return effect["kind"] === "edit" && typeof effect["path"] === "string" && typeof effect["sha256_after"] === "string" &&
173
- (effect["sha256_before"] === null || typeof effect["sha256_before"] === "string");
174
- }
175
- case "cut": return has("handoff_ref") && (value["working_set_version"] === undefined || value["working_set_version"] === 2 || value["working_set_version"] === 3) && ["trigger", "hard_limit", "model_checkpoint"].includes(String(value["reason"])) &&
176
- (value["guidance_tools"] === undefined || Array.isArray(value["guidance_tools"]) && value["guidance_tools"].every(item => typeof item === "string")) &&
177
- optionalString(value["retained_from"]) && optionalString(value["next_context_ref"]) &&
178
- (value["next_node"] === undefined || natural(value["next_node"])) && optionalString(value["next_model"]) &&
179
- (value["handoff_tokens"] === undefined || (typeof value["handoff_tokens"] === "number" && Number.isFinite(value["handoff_tokens"]) && value["handoff_tokens"] > 0)) &&
180
- (value["predecessor"] === null || validNodeRef(value["predecessor"]));
181
- case "claim": {
182
- const q = value["evidence"];
183
- return has("what") && isRecord(q) && (typeof q["gate"] === "string" || typeof q["command"] === "string");
184
- }
185
- case "decision": return has("chose", "because") && (value["rejected"] === undefined || strings(value["rejected"]));
186
- case "pitfall": return has("tried", "failed_because");
187
- case "blocked": return has("what", "reason");
188
- case "question": return has("text");
189
- case "next_action": return has("what", "next_action") && (value["depends_on"] === undefined || strings(value["depends_on"]));
190
- case "work_dependencies": return has("work_id", "what") && strings(value["depends_on"]);
191
- case "work_owner": return has("work_id", "what", "owner") && ["claim", "release"].includes(String(value["operation"]));
192
- case "evidence_link": return has("work", "what", "source") && optionalString(value["resource"]) && optionalString(value["version"]) &&
193
- optionalString(value["supersedes"]) && (value["observed_version"] === null || optionalString(value["observed_version"]));
194
- case "work_status": return has("work_id", "what") && ["unblock", "reopen", "cancel"].includes(String(value["operation"])) &&
195
- optionalString(value["reason"]) && (value["operation"] === "cancel" ||
196
- (typeof value["next_action"] === "string" && value["next_action"].trim().length > 0));
197
- case "rewind": return ["both", "files", "context"].includes(String(value["mode"])) && validNodeRef(value["from"]) &&
198
- (value["files_from"] === undefined || validNodeRef(value["files_from"])) &&
199
- has("prompt") && strings(value["restored"]) && strings(value["removed"]) && strings(value["unrecoverable"]) && strings(value["divergence"]);
200
- case "spawn": {
201
- const input = value["input"];
202
- if (input !== undefined) {
203
- if (!isRecord(input) || typeof input["source_event"] !== "string" || !strings(input["tools"]) ||
204
- !Number.isSafeInteger(input["max_turns"]) || Number(input["max_turns"]) < 1 || !isRecord(input["workspace"])) return false;
205
- const workspace = input["workspace"];
206
- if (!(workspace["kind"] === "shared_readonly" || (workspace["kind"] === "git_tree" &&
207
- typeof workspace["head"] === "string" && typeof workspace["tree"] === "string"))) return false;
208
- }
209
- return (value["background"] === undefined || value["background"] === true) && validNodeRef(value["branch"]) && has("purpose") && (value["workspace"] === undefined ||
210
- (isRecord(value["workspace"]) && typeof value["workspace"]["path"] === "string" && typeof value["workspace"]["branch"] === "string"));
211
- }
212
- case "joined": return validNodeRef(value["branch"]) && has("handoff_ref", "summary") && optionalString(value["model"]) &&
213
- (value["receipt_ref"] === undefined || typeof value["receipt_ref"] === "string" && /^[a-f0-9]{64}$/.test(value["receipt_ref"])) &&
214
- (value["tip"] === undefined || validNodeRef(value["tip"]));
215
- case "adopt": return validNodeRef(value["branch"]) && (value["next_node"] === undefined || natural(value["next_node"])) &&
216
- (value["applied"] === undefined || value["applied"] === true);
217
- case "discard": return validNodeRef(value["branch"]) && has("reason");
218
- default: return false;
219
- }
220
- }