@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,62 +0,0 @@
1
- /**
2
- * The evidence ladder (research doc S.1): how far a request's evidence goes,
3
- * one rung at a time, read only from the request ledger.
4
- *
5
- * ran a required check has a fresh passing receipt
6
- * discriminating the check fails once the request's changes are undone
7
- * (a guard instead still passes there, and does not count; S.7)
8
- * independent blind checks the model never touched pass
9
- * human the user accepted what only a person can judge
10
- *
11
- * Each rung is its own fact; a higher rung does not imply the ones below it.
12
- * The summary names the highest rung reached and the first gap, so the user
13
- * sees what the evidence shows and what it does not, without a percentage.
14
- */
15
- import type { RequestState } from "./request-state.ts";
16
-
17
- export type Rung = "met" | "missing" | "failed" | "not_run" | "not_applicable";
18
- export type EvidenceLadder = { ran: Rung; discriminating: Rung; independent: Rung; human: Rung; summary: string };
19
- export const RUNGS = ["ran", "discriminating", "independent", "human"] as const;
20
-
21
- const LABEL: Record<(typeof RUNGS)[number], string> = {
22
- ran: "ran", discriminating: "tests the change", independent: "independent", human: "human accepted",
23
- };
24
-
25
- /** Null when the request had nothing to check: a conversation turn shows no ladder. */
26
- export function evidenceLadder(state: RequestState): EvidenceLadder | null {
27
- if (state.status === "not_applicable") return null;
28
- const evidence = state.evidence;
29
- const ran: Rung = evidence.ran === "pass" ? "met" : evidence.ran === "fail" ? "failed" : evidence.ran === "not_applicable" ? "not_applicable" : "missing";
30
- const discriminating: Rung = evidence.discriminating === "pass" ? "met" : evidence.discriminating === "vacuous" ? "failed"
31
- : evidence.discriminating === "missing" ? "missing" : evidence.discriminating === "not_applicable" ? "not_applicable" : "not_run";
32
- const independent: Rung = evidence.independent === "pass" ? "met" : evidence.independent === "fail" || evidence.independent === "exposed" ? "failed"
33
- : evidence.independent === "not_applicable" ? "not_applicable" : "not_run";
34
- const human: Rung = evidence.human === "accepted" ? "met" : evidence.human === "pending" ? "missing" : "not_applicable";
35
- const rungs = { ran, discriminating, independent, human };
36
- const reached = RUNGS.filter(rung => rungs[rung] === "met").at(-1);
37
- const gap = RUNGS.find(rung => rungs[rung] === "failed" || rungs[rung] === "missing");
38
- const why = !gap ? undefined
39
- : gap === "ran" ? (ran === "failed" ? "the latest check failed" : state.acceptance ? "declared checks have not passed" : "changes not checked")
40
- : gap === "discriminating" ? (discriminating === "missing" ? "no check tests the change; every check is a guard" : vacuousGap(state))
41
- : gap === "independent" ? (evidence.independent === "exposed" ? "the model touched the blind checks" : "a blind check failed")
42
- : "waiting for your /accept";
43
- const summary = [reached ? `reached: ${LABEL[reached]}` : "nothing verified yet", ...(why ? [why] : [])].join(" · ");
44
- return { ...rungs, summary };
45
- }
46
-
47
- /**
48
- * Which checks pass without the request's changes, by id. Only line mode
49
- * prints each check's counter-check; the sidebar shows just this summary, and
50
- * a guard the user asked for ("nothing else changed") reads differently from
51
- * a check that was meant to test the work (fix-ladder-names-vacuous-check).
52
- */
53
- function vacuousGap(state: RequestState): string {
54
- const ids = state.criteria.filter(item => item.counter === "vacuous").map(item => item.id);
55
- const named = ids.length <= 2 ? ids.join(", ") : `${ids.slice(0, 2).join(", ")} +${ids.length - 2}`;
56
- return `${named} ${ids.length === 1 ? "passes" : "pass"} without the changes`;
57
- }
58
-
59
- /** One glyph per rung for narrow status chrome: ✓ met, ✗ failed, ○ missing or not run, · not applicable. */
60
- export function ladderGlyph(rung: Rung): string {
61
- return rung === "met" ? "✓" : rung === "failed" ? "✗" : rung === "not_applicable" ? "·" : "○";
62
- }
@@ -1,68 +0,0 @@
1
- /** O.6: explicit evidence relationships are projections, never verification claims. */
2
- import type { LogEvent, ToolResultEvent } from "./events.ts";
3
-
4
- export type EvidenceLink = {
5
- id: string; work: string; what: string; event: string;
6
- resource?: string; version?: string;
7
- freshness: "current" | "stale" | "unknown";
8
- superseded_by?: string;
9
- };
10
-
11
- /** Normalize path spelling only; never infer basename, symlink or repository aliases. */
12
- export function resourceKey(path: string): string {
13
- const parts: string[] = [];
14
- for (const part of path.split("/")) {
15
- if (!part || part === ".") continue;
16
- if (part === ".." && parts.length > 0 && parts.at(-1) !== "..") parts.pop(); else parts.push(part);
17
- }
18
- return (path.startsWith("/") ? "/" : "") + parts.join("/");
19
- }
20
-
21
- export function observationResources(event: ToolResultEvent, input: unknown): string[] {
22
- if (event.effect && "path" in event.effect) return [resourceKey(event.effect.path)];
23
- if (!event.ok || input === null || typeof input !== "object") return [];
24
- const args = input as Record<string, unknown>;
25
- const paths = typeof args["path"] === "string" ? [args["path"]] : Array.isArray(args["paths"]) ? args["paths"] : [];
26
- return [...new Set(paths.filter((path): path is string => typeof path === "string").map(resourceKey))];
27
- }
28
-
29
- export function observedVersion(events: readonly LogEvent[], resource: string): string | null | undefined {
30
- let version: string | null | undefined;
31
- for (const event of events) {
32
- if (event.t === "tool_result" && event.effect && "path" in event.effect && resourceKey(event.effect.path) === resource) {
33
- version = event.effect.kind === "read" ? event.effect.sha256 : event.effect.sha256_after;
34
- }
35
- if (event.t === "checkpoint" && event.phase === "applied") for (const [path, change] of Object.entries(event.changes)) {
36
- if (resourceKey(path) === resource) version = change.after?.sha256 ?? null;
37
- }
38
- }
39
- return version;
40
- }
41
-
42
- export function deriveEvidenceLinks(previous: readonly EvidenceLink[], events: readonly LogEvent[]): EvidenceLink[] {
43
- const links = previous.map(link => ({ ...link }));
44
- for (const event of events) {
45
- if (event.t === "evidence_link") {
46
- if (event.supersedes) for (const link of links) {
47
- if (link.work === event.work && link.event === event.supersedes && link.superseded_by === undefined) link.superseded_by = event.id;
48
- }
49
- if (!links.some(link => link.id === event.id)) links.push({ id: event.id, work: event.work, what: event.what, event: event.source,
50
- ...(event.resource !== undefined ? { resource: resourceKey(event.resource) } : {}),
51
- ...(event.version !== undefined ? { version: event.version } : {}),
52
- freshness: event.version === undefined || event.observed_version === undefined ? "unknown"
53
- : event.version === event.observed_version ? "current" : "stale" });
54
- }
55
- for (const link of links) {
56
- if (!link.resource || !link.version) continue;
57
- if (event.t === "tool_result" && event.effect && "path" in event.effect && resourceKey(event.effect.path) === link.resource) {
58
- const version = event.effect.kind === "read" ? event.effect.sha256 : event.effect.sha256_after;
59
- link.freshness = version === link.version ? "current" : "stale";
60
- }
61
- if (event.t === "checkpoint" && event.phase === "applied") for (const [path, change] of Object.entries(event.changes)) {
62
- if (resourceKey(path) === link.resource) link.freshness = change.after?.sha256 === link.version ? "current" : "stale";
63
- }
64
- if (event.t === "workspace_observation" && event.changed || event.t === "checkpoint" && event.phase === "unavailable") link.freshness = "unknown";
65
- }
66
- }
67
- return links;
68
- }
@@ -1,240 +0,0 @@
1
- /** Shared executed-evidence rules for handoff derivation and the turn watchdog. */
2
- import type { EvidenceQuery, LogEvent, ToolResultEvent } from "./events.ts";
3
- import type { Evidence } from "./handoff.ts";
4
- import { changedPaths } from "./checkpoints.ts";
5
-
6
- type CommandEffect = Extract<NonNullable<ToolResultEvent["effect"]>, { kind: "command" }>;
7
- export type CommandResult = ToolResultEvent & { effect: CommandEffect };
8
- export function isCommandResult(event: LogEvent): event is CommandResult {
9
- return event.t === "tool_result" && event.effect?.kind === "command";
10
- }
11
- export function matchesQuery(query: EvidenceQuery, effect: CommandEffect): boolean {
12
- return "gate" in query ? effect.gate === query.gate : effect.cmd === query.command;
13
- }
14
- export function evidenceStale(events: readonly LogEvent[], evidence: Evidence, initially = false): boolean {
15
- const start = events.findIndex((event) => event.id === evidence.event_id);
16
- let stale = initially;
17
- for (const event of events.slice(start + 1)) {
18
- if (event.t === "tool_result" && event.effect?.kind === "edit") stale = true;
19
- if (event.t === "checkpoint" && (event.phase === "unavailable" || event.phase === "applied" && changedPaths(event.changes).length > 0)) stale = true;
20
- if (event.t === "workspace_observation" && event.fingerprint === undefined ||
21
- event.t === "tool_result" && event.effect?.kind === "command" && event.effect.workspace_observed === false || event.t === "user_bash") stale = true;
22
- const sha = event.t === "workspace_observation" ? event.fingerprint
23
- : event.t === "tool_result" && event.effect?.kind === "command" ? event.effect.workspace_sha256 : undefined;
24
- const scope = event.t === "workspace_observation" ? event.scope : event.t === "tool_result" && event.effect?.kind === "command" ? event.effect.workspace_scope : undefined;
25
- if (sha !== undefined && evidence.workspace_sha256 !== undefined) stale = evidence.workspace_scope === "source-v1"
26
- ? stale || scope !== evidence.workspace_scope || sha !== evidence.workspace_sha256 : scope !== undefined || sha !== evidence.workspace_sha256;
27
- }
28
- return stale;
29
- }
30
-
31
- export type EvidenceResolution = { ok: true; evidence: Evidence } | {
32
- ok: false; reason: "no_matching_event" | "nonzero_exit" | "stale_workspace"; exit?: number;
33
- };
34
-
35
- /** D11/O.2: callers supply only the current segment's prefix before the claim. */
36
- export function resolveEvidence(events: readonly LogEvent[], query: EvidenceQuery, afterEvent?: string): EvidenceResolution {
37
- const start = afterEvent === undefined ? 0 : events.findIndex(event => event.id === afterEvent) + 1;
38
- const result = events.slice(start).findLast(event => isCommandResult(event) && matchesQuery(query, event.effect));
39
- if (!result || !isCommandResult(result)) return { ok: false, reason: "no_matching_event" };
40
- if (result.effect.exit !== 0) return { ok: false, reason: "nonzero_exit", exit: result.effect.exit };
41
- if (result.effect.workspace_observed === false) return { ok: false, reason: "stale_workspace" };
42
- const evidence: Evidence = { command: result.effect.cmd, exit: result.effect.exit, at: result.at, event_id: result.id,
43
- ...(result.effect.workspace_scope ? { workspace_scope: result.effect.workspace_scope } : {}),
44
- ...(result.effect.gate !== undefined ? { gate: result.effect.gate } : {}),
45
- ...(result.effect.workspace_sha256 ? { workspace_sha256: result.effect.workspace_sha256 } : {}) };
46
- return evidenceStale(events, evidence) ? { ok: false, reason: "stale_workspace" } : { ok: true, evidence };
47
- }
48
-
49
- export function eligibleProgressChecks(events: readonly LogEvent[], claimed: ReadonlySet<string>, failingGates: { has(gate: string): boolean }): Set<string> {
50
- const eligible = new Set<string>();
51
- for (const event of events) {
52
- if (!isCommandResult(event)) continue;
53
- const gate = event.effect.gate;
54
- if (event.effect.exit !== 0 || event.effect.workspace_observed === false || (gate === undefined && !claimed.has(event.id)) || (gate !== undefined && failingGates.has(gate))) continue;
55
- if (!evidenceStale(events, { command: event.effect.cmd, exit: 0, at: event.at, event_id: event.id,
56
- ...(event.effect.workspace_scope ? { workspace_scope: event.effect.workspace_scope } : {}),
57
- ...(event.effect.workspace_sha256 === undefined ? {} : { workspace_sha256: event.effect.workspace_sha256 }) })) eligible.add(event.id);
58
- }
59
- return eligible;
60
- }
61
-
62
- /** An exchange can verify a gate or claim only an already executed command. */
63
- export function exchangeProgressChecks(events: readonly LogEvent[]): Set<string> {
64
- const commands: CommandResult[] = [], claimed = new Set<string>(), gates = new Map<string, number>();
65
- for (const event of events) {
66
- if (isCommandResult(event)) {
67
- commands.push(event);
68
- if (event.effect.gate !== undefined) gates.set(event.effect.gate, event.effect.exit);
69
- } else if (event.t === "claim") {
70
- const command = commands.findLast((result) => matchesQuery(event.evidence, result.effect));
71
- if (command?.effect.exit === 0) claimed.add(command.id);
72
- }
73
- }
74
- return eligibleProgressChecks(events, claimed, new Set([...gates].filter(([, exit]) => exit !== 0).map(([gate]) => gate)));
75
- }
76
-
77
- /**
78
- * D11: classify an actual verifier invocation, never a runner word in data.
79
- *
80
- * Only command shapes whose exit 0 proves every gate in them exited 0 are
81
- * read: simple commands joined by `&&` (the chain stops at the first failure),
82
- * each optionally ending in output redirects to fixed paths (a redirect that
83
- * cannot open fails the command, it never masks one). Anything that can turn
84
- * a failure into exit 0 or run text the log does not show -- `;`, `||`,
85
- * pipes, background jobs, subshells, expansions, input redirects -- rejects
86
- * the whole command, as before. A chain's gates join with `+` in order, so a
87
- * failed chain is recorded as that chain failing, not as one of its parts.
88
- */
89
- export function classifyGate(gates: Readonly<Record<string, string>>, command: string): string | undefined {
90
- const segments = gateSegments(command);
91
- if (!segments) return undefined;
92
- const names = [...new Set(segments.flatMap((words) => segmentGates(gates, words)))];
93
- return names.length === 0 ? undefined : names.join("+");
94
- }
95
-
96
- type ShellToken = { kind: "word"; value: string } | { kind: "and" } | { kind: "redirect"; target: boolean };
97
-
98
- /**
99
- * A deliberately small tokenizer, not a shell parser (M.2): it accepts the
100
- * subset above and returns undefined for everything else, so an unfamiliar
101
- * construct can only lose a gate, never forge one. Quotes may hold plain text
102
- * only; `$`, backticks and backslashes are rejected even inside them.
103
- */
104
- function shellTokens(command: string): ShellToken[] | undefined {
105
- const tokens: ShellToken[] = [];
106
- let word: string | undefined, i = 0;
107
- const end = () => { if (word !== undefined) tokens.push({ kind: "word", value: word }); word = undefined; };
108
- while (i < command.length) {
109
- const c = command[i]!;
110
- if (c === " " || c === "\t") { end(); i++; continue; }
111
- if (c === "#" && word === undefined) break; // a comment runs to the end of the (single) line
112
- if (c === "'" || c === '"') {
113
- const close = command.indexOf(c, i + 1);
114
- if (close < 0) return undefined;
115
- const quoted = command.slice(i + 1, close);
116
- if (/[$`\\\n\r]/.test(quoted)) return undefined;
117
- word = (word ?? "") + quoted; i = close + 1; continue;
118
- }
119
- if (c === "&" && command[i + 1] === "&") { end(); tokens.push({ kind: "and" }); i += 2; continue; }
120
- if (c === ">" || c === "&" && command[i + 1] === ">") {
121
- // An unquoted 1 or 2 directly before `>` is the descriptor, not a word.
122
- if (c === ">" && (word === "1" || word === "2") && /[12]$/.test(command.slice(0, i))) word = undefined;
123
- end();
124
- i += c === "&" ? 2 : 1;
125
- if (command[i] === ">") i++;
126
- if (c === ">" && command[i] === "&") {
127
- if (!/^[12](?![^\s&])/.test(command.slice(i + 1))) return undefined;
128
- tokens.push({ kind: "redirect", target: false }); i += 2; continue;
129
- }
130
- if (command[i] === "|") return undefined;
131
- tokens.push({ kind: "redirect", target: true }); continue;
132
- }
133
- if (/[\n\r;&|<`$(){}\\]/.test(c)) return undefined;
134
- word = (word ?? "") + c; i++;
135
- }
136
- end();
137
- return tokens;
138
- }
139
-
140
- /** The `&&`-joined segments' words with trailing output redirects removed. */
141
- function gateSegments(command: string): string[][] | undefined {
142
- const tokens = shellTokens(command);
143
- if (!tokens) return undefined;
144
- const segments: string[][] = [[]];
145
- let redirected = false, needTarget = false;
146
- for (const token of tokens) {
147
- const words = segments.at(-1)!;
148
- if (token.kind === "and") {
149
- if (needTarget || words.length === 0) return undefined;
150
- segments.push([]); redirected = false; continue;
151
- }
152
- if (token.kind === "redirect") {
153
- if (needTarget || words.length === 0) return undefined;
154
- redirected = true; needTarget = token.target; continue;
155
- }
156
- if (needTarget) { needTarget = false; continue; }
157
- // Only trailing redirects are stripped; one before an argument is rejected.
158
- if (redirected) return undefined;
159
- words.push(token.value);
160
- }
161
- return needTarget || segments.at(-1)!.length === 0 ? undefined : segments;
162
- }
163
-
164
- const INFORMATIONAL = ["--version", "-V", "--help", "-h", "--collect-only", "--listTests", "--showConfig", "--init", "--fixtures", "--markers"];
165
-
166
- /** One simple command's gates: none, the runner's gate, or a target runner's gate targets. */
167
- function segmentGates(gates: Readonly<Record<string, string>>, tokens: readonly string[]): string[] {
168
- if (tokens.some((t) => INFORMATIONAL.includes(t))) return [];
169
- const first = tokens[0] ?? "";
170
- const runner = own(TARGET_RUNNERS, first);
171
- if (runner && !Object.hasOwn(gates, first)) return targetGates(gates, runner, tokens.slice(1));
172
- const single = runnerGate(gates, tokens);
173
- return single === undefined ? [] : [single];
174
- }
175
-
176
- function runnerGate(gates: Readonly<Record<string, string>>, tokens: readonly string[]): string | undefined {
177
- let i = 0;
178
- const first = tokens[0];
179
- if (["bun", "npm", "pnpm", "yarn"].includes(first ?? "") && tokens[1] === "test") return own(gates, "test");
180
- if (first === "python" || first === "python3") {
181
- if (tokens[1] !== "-m") return undefined;
182
- i = 2;
183
- } else if (["npx", "bunx", "uvx"].includes(first ?? "")) i = 1;
184
- else if (["uv", "poetry", "pdm", "pipenv"].includes(first ?? "")) {
185
- if (tokens[1] !== "run") return undefined;
186
- i = 2;
187
- } else if (["bun", "npm", "pnpm", "yarn"].includes(first ?? "") && tokens[1] === "run") i = 2;
188
- const executable = tokens[i];
189
- if (executable === "cargo" && !["test", "check", "clippy", "build"].includes(tokens[i + 1] ?? "")) return undefined;
190
- if (executable === "vitest" && ["list", "init"].includes(tokens[i + 1] ?? "")) return undefined;
191
- return own(gates, executable ?? "");
192
- }
193
-
194
- /** A table lookup that a word like `constructor` cannot satisfy through the prototype. */
195
- function own<T>(table: Readonly<Record<string, T>>, key: string): T | undefined {
196
- return Object.hasOwn(table, key) ? table[key] : undefined;
197
- }
198
-
199
- /**
200
- * Options a target runner may carry and still prove its targets ran. An
201
- * allowlist on purpose: an unknown option might be a dry run, a touch or an
202
- * ignore-errors switch (exit 0 without the targets passing), so it makes the
203
- * segment not a gate rather than being guessed at.
204
- */
205
- type TargetRunner = { withValue: readonly string[]; flags: readonly string[]; attached: RegExp; argsAfterDashes: boolean };
206
- const TARGET_RUNNERS: Readonly<Record<string, TargetRunner>> = {
207
- make: { withValue: ["-C", "-f", "--directory", "--file", "--makefile"],
208
- flags: ["-k", "-s", "-B", "-w", "-r", "-R", "--keep-going", "--silent", "--quiet", "--always-make", "--print-directory", "--no-print-directory"],
209
- attached: /^(?:-C.+|-f.+|-j\d*|--(?:directory|file|makefile|jobs)=.+|--jobs)$/, argsAfterDashes: false },
210
- just: { withValue: ["-f", "-d", "--justfile", "--working-directory"], flags: ["-q", "-v", "--quiet", "--verbose"],
211
- attached: /^--(?:justfile|working-directory)=.+$/, argsAfterDashes: false },
212
- task: { withValue: ["-d", "-t", "--dir", "--taskfile"], flags: ["-s", "-v", "-p", "-f", "--silent", "--verbose", "--parallel", "--force"],
213
- attached: /^--(?:dir|taskfile)=.+$/, argsAfterDashes: true },
214
- };
215
-
216
- /**
217
- * `make build size test`: the runner exits 0 only when every target
218
- * succeeded, so each target named after a configured gate key (exactly, or
219
- * as `<key>-…` / `<key>:…`) is that gate; other targets do not weaken it. No
220
- * gate target, or no target at all (the default goal is unknown), is no gate.
221
- */
222
- function targetGates(gates: Readonly<Record<string, string>>, runner: TargetRunner, args: readonly string[]): string[] {
223
- const keys = Object.keys(gates).sort((a, b) => b.length - a.length);
224
- const names: string[] = [];
225
- for (let i = 0; i < args.length; i++) {
226
- const arg = args[i]!;
227
- if (arg === "--") { if (runner.argsAfterDashes) break; return []; }
228
- if (arg.startsWith("-")) {
229
- if (runner.withValue.includes(arg)) { if (++i >= args.length) return []; continue; }
230
- if (arg === "-j" && /^\d+$/.test(args[i + 1] ?? "")) { i++; continue; }
231
- if (runner.flags.includes(arg) || runner.attached.test(arg)) continue;
232
- return [];
233
- }
234
- if (arg.includes("=")) continue; // a variable override, not a target
235
- if (/[*?[\]~]/.test(arg)) return [];
236
- const key = keys.find((k) => arg === k || arg.startsWith(`${k}-`) || arg.startsWith(`${k}:`));
237
- if (key !== undefined) names.push(gates[key]!);
238
- }
239
- return names;
240
- }
@@ -1,18 +0,0 @@
1
- /** D5/D11: incomplete execution is unknown, never an instruction to run twice. */
2
- import type { LogEvent, AssistantBlock } from "./events.ts";
3
-
4
- export type PendingToolCall = Extract<AssistantBlock, { type: "tool_use" }> & { parent_call_id?: string; invocation_id?: string; origin?: "extension" };
5
- export function pendingToolCalls(events: readonly LogEvent[]): PendingToolCall[] {
6
- const pending = new Map<string, PendingToolCall>();
7
- for (const event of events) {
8
- if (event.t === "assistant") {
9
- for (const block of event.blocks) if (block.type === "tool_use") pending.set(block.call_id, block);
10
- } else if (event.t === "tool_invocation") {
11
- const existing = pending.get(event.call_id);
12
- pending.set(event.call_id, { type: "tool_use", call_id: event.call_id, name: event.name, input: event.input ?? existing?.input ?? null,
13
- invocation_id: event.id, ...(event.origin ? { origin: event.origin } : {}),
14
- ...(event.parent_call_id ? { parent_call_id: event.parent_call_id } : {}) });
15
- } else if (event.t === "tool_result") pending.delete(event.call_id);
16
- }
17
- return [...pending.values()];
18
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Exhaustiveness for discriminated unions.
3
- *
4
- * `LogEvent` has ten variants and `Effect` three; the projection's claim to be
5
- * a total function rests on every switch over them covering every case. In the
6
- * default branch of such a switch the value narrows to `never`, so passing it
7
- * here compiles only while the switch is complete -- adding a variant turns
8
- * every incomplete switch into a type error rather than a silent fallthrough.
9
- *
10
- * This is the one exhaustiveness guarantee tsc will not give on its own: a
11
- * switch that misses a case is legal TypeScript, it just falls through.
12
- */
13
- export function assertNever(value: never, context: string): never {
14
- throw new Error(
15
- `${context}: unhandled variant ${JSON.stringify(value)} -- ` +
16
- "a union gained a member without every switch over it being updated",
17
- );
18
- }
@@ -1,179 +0,0 @@
1
- /**
2
- * Transcript serialisation. Pure: no I/O, no clock.
3
- *
4
- * The HTML has to be a single file a person can open offline. A stylesheet
5
- * or font loaded from a CDN is blank in the one situation export is for, so
6
- * nothing here emits a URL. Timestamps are passed in so this module cannot
7
- * invent "now".
8
- *
9
- * User text is escaped as HTML text. A transcript that contains
10
- * `<script>alert(1)</script>` must stay text; emitting it as a tag is XSS
11
- * from the log (card: feat-session-flags-and-export).
12
- */
13
-
14
- import type { AssistantBlock, LogEvent } from "./events.ts";
15
- import { counterVerdict } from "./acceptance.ts";
16
- import { assertNever } from "./exhaustive.ts";
17
- import { imageDescription } from "./images.ts";
18
-
19
- export interface HtmlMeta {
20
- title: string;
21
- /** ISO timestamp supplied by the caller. Never read from the clock here. */
22
- exportedAt: string;
23
- sessionId?: string;
24
- }
25
-
26
- export function toJsonl(events: readonly LogEvent[]): string {
27
- if (events.length === 0) return "";
28
- return events.map((e) => JSON.stringify(e)).join("\n") + "\n";
29
- }
30
-
31
- export function toHtml(events: readonly LogEvent[], meta: HtmlMeta): string {
32
- const title = escapeHtml(meta.title);
33
- const when = escapeHtml(meta.exportedAt);
34
- const id = meta.sessionId !== undefined ? escapeHtml(meta.sessionId) : "";
35
- const body = events.map(eventHtml).join("\n");
36
- return `<!DOCTYPE html>
37
- <html lang="en">
38
- <head>
39
- <meta charset="utf-8">
40
- <meta name="viewport" content="width=device-width, initial-scale=1">
41
- <title>${title}</title>
42
- <style>
43
- body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;max-width:72rem;margin:1.5rem auto;padding:0 1rem;color:#111;background:#fff;line-height:1.45}
44
- header{border-bottom:1px solid #ddd;padding-bottom:0.75rem;margin-bottom:1rem}
45
- header p{color:#555;margin:0.25rem 0 0}
46
- .event{margin:0.85rem 0;padding:0.55rem 0.8rem;border-left:3px solid #ccc;background:#fafafa}
47
- .user{border-color:#1565c0}
48
- .assistant{border-color:#2e7d32}
49
- .tool{border-color:#ef6c00}
50
- .meta{color:#666;font-size:0.8rem;margin-bottom:0.35rem}
51
- pre{white-space:pre-wrap;word-break:break-word;margin:0;font:inherit}
52
- </style>
53
- </head>
54
- <body>
55
- <header>
56
- <h1>${title}</h1>
57
- <p>exported ${when}${id.length > 0 ? ` · ${id}` : ""}</p>
58
- </header>
59
- <main>
60
- ${body}
61
- </main>
62
- </body>
63
- </html>
64
- `;
65
- }
66
-
67
- /**
68
- * Most recent assistant text block. `/copy` is this string, not a
69
- * reconstruction of thinking or tool JSON.
70
- */
71
- export function lastAssistantText(events: readonly LogEvent[]): string | null {
72
- for (let i = events.length - 1; i >= 0; i--) {
73
- const e = events[i]!;
74
- if (e.t !== "assistant") continue;
75
- const parts: string[] = [];
76
- for (const b of e.blocks) {
77
- if (b.type === "text") parts.push(b.text);
78
- }
79
- if (parts.length > 0) return parts.join("");
80
- }
81
- return null;
82
- }
83
-
84
- function escapeHtml(s: string): string {
85
- return s
86
- .replace(/&/g, "&amp;")
87
- .replace(/</g, "&lt;")
88
- .replace(/>/g, "&gt;")
89
- .replace(/"/g, "&quot;")
90
- .replace(/'/g, "&#39;");
91
- }
92
-
93
- function eventHtml(e: LogEvent): string {
94
- switch (e.t) {
95
- case "tool_invocation": return wrap("event", "tool invocation", e.at, `${e.name} · ${e.call_id}${e.parent_call_id ? ` · parent ${e.parent_call_id}` : ""} · input ${e.input_ref.sha256} · schema ${e.schema_ref?.sha256 ?? "unavailable"}`);
96
- case "checkpoint": return wrap("event", `file checkpoint ${e.phase}`, e.at, e.reason ?? Object.keys(e.changes).join("\n"));
97
- case "workspace_observation": return wrap("event", "verification observation", e.at, e.fingerprint ?? e.reason ?? "unavailable");
98
- case "extension": return e.display ? wrap("event", e.customType, e.at, e.content || JSON.stringify(e.data)) : "";
99
- case "user":
100
- return wrap("user", "user", e.at, [e.text, ...(e.images ?? []).map(imageDescription)].join("\n"));
101
- case "user_bash": return wrap("tool", e.excludeFromContext ? "user bash (excluded)" : "user bash", e.at, `$ ${e.command}\n${e.content}`);
102
- case "outcome": return wrap("event", e.outcome.status, e.at, `${e.outcome.reason}: ${e.reason}`);
103
- case "guidance":
104
- return wrap("event", `harness ${e.reason}`, e.at, e.text);
105
- case "assistant":
106
- return wrap("assistant", "assistant", e.at, assistantText(e.blocks));
107
- case "interrupt":
108
- return wrap("event", "interrupt", e.at, `cancelled ${e.user_id}`);
109
- case "tool_result":
110
- return wrap("tool", `tool_result ${e.ok ? "ok" : "fail"}`, e.at, [e.content, ...(e.images ?? []).map(imageDescription)].join("\n"));
111
- case "cut":
112
- return wrap("event", "cut", e.at, `${e.reason} → ${e.handoff_ref}`);
113
- case "rewind":
114
- return wrap("event", `rewind ${e.mode}`, e.at, e.prompt);
115
- case "spawn":
116
- return wrap("event", "spawn", e.at, e.purpose);
117
- case "joined":
118
- return wrap("event", "joined", e.at, e.summary);
119
- case "adopt":
120
- return wrap("event", "adopt", e.at, `branch ${e.branch.node}`);
121
- case "discard":
122
- return wrap("event", "discard", e.at, e.reason);
123
- case "claim":
124
- return wrap("event", "claim", e.at, e.what);
125
- case "decision":
126
- return wrap("event", "decision", e.at, `${e.chose} — ${e.because}`);
127
- case "pitfall":
128
- return wrap("event", "pitfall", e.at, `${e.tried} — ${e.failed_because}`);
129
- case "blocked":
130
- return wrap("event", "blocked", e.at, `${e.what} — ${e.reason}`);
131
- case "question":
132
- return wrap("event", "question", e.at, e.text);
133
- case "next_action":
134
- return wrap("event", "next_action", e.at, `${e.what} → ${e.next_action}`);
135
- case "work_status":
136
- return wrap("event", `work ${e.operation}`, e.at, `${e.work_id}: ${e.what}`);
137
- case "evidence_link":
138
- return wrap("event", "evidence link", e.at, `${e.work}: ${e.what} → ${e.source}`);
139
- case "work_owner": case "work_dependencies": return wrap("event", e.t, e.at, JSON.stringify(e));
140
- case "approval": return wrap("event", "approval", e.at,
141
- [`${e.tool} · ${e.call_id} · ${e.decision} · ${e.source}`, ...(e.rule ? [`rule: ${e.rule}`] : []), ...(e.command !== undefined ? [`$ ${e.command}`] : [])].join("\n"));
142
- case "skill_loaded": return wrap("event", "skill loaded", e.at, `${e.name} · ${e.path} · ${e.sha256} · ${e.mode}`);
143
- case "process_exit": return wrap("event", "process exit", e.at, `${e.session} · exit ${e.exit} · output ${e.output.sha256}`);
144
- case "acceptance_contract": return wrap("event", "acceptance contract", e.at, JSON.stringify(e.contract));
145
- case "target_observation": return wrap("event", "runtime target", e.at, JSON.stringify(e.observation));
146
- case "acceptance_decision": return wrap("event", "human acceptance", e.at, `${e.contract_id}: ${e.criterion}`);
147
- case "counter_check": return wrap("event", "counter-check", e.at,
148
- `${e.criterion}${e.guard ? " (guard)" : ""} · ${counterVerdict(e)}${"reason" in e ? ` · ${e.reason}` : ` · exit ${e.exit}`}`);
149
- case "blind_check": return wrap("event", "blind check", e.at, `${e.check} · ${"reason" in e ? e.reason : `exit ${e.exit}`}${e.exposed ? ` · exposed: ${e.exposure ?? "yes"}` : ""}`);
150
- case "goal": return wrap("event", e.action === "set" ? "goal set" : "goal cleared", e.at,
151
- e.action === "set" ? `${e.objective}\naccepted: ${e.accepted.join(", ")}${e.rejected.length ? `\nrejected: ${e.rejected.join(", ")}` : ""}` : e.reason);
152
- default:
153
- return assertNever(e, "eventHtml");
154
- }
155
- }
156
-
157
- function assistantText(blocks: readonly AssistantBlock[]): string {
158
- const parts: string[] = [];
159
- for (const b of blocks) {
160
- switch (b.type) {
161
- case "text":
162
- parts.push(b.text);
163
- break;
164
- case "thinking":
165
- parts.push(b.text);
166
- break;
167
- case "tool_use":
168
- parts.push(`${b.name} ${JSON.stringify(b.input)}`);
169
- break;
170
- default:
171
- return assertNever(b, "assistantText");
172
- }
173
- }
174
- return parts.join("\n");
175
- }
176
-
177
- function wrap(cls: string, kind: string, at: string, text: string): string {
178
- return `<article class="event ${cls}"><div class="meta">${escapeHtml(kind)} · ${escapeHtml(at)}</div><pre>${escapeHtml(text)}</pre></article>`;
179
- }
@@ -1,14 +0,0 @@
1
- /** Typed extension flags are data; factories and the final CLI pass share their interpretation. */
2
- export interface ExtensionFlag { type: "string" | "boolean"; description?: string; default?: string | boolean }
3
- export interface FlagInput { value?: string; inline: boolean }
4
- export function extensionFlagValue(name: string, definition: ExtensionFlag, input?: FlagInput): string | boolean | undefined {
5
- if (!input) return definition.default;
6
- if (definition.type === "string") {
7
- if (input.value === undefined) throw new Error(`--${name} requires a string value`);
8
- return input.value;
9
- }
10
- if (input.value === "true") return true;
11
- if (input.value === "false") return false;
12
- if (input.inline) throw new Error(`--${name} requires true or false`);
13
- return true;
14
- }