@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,149 +0,0 @@
1
- /** Typed local control client; requests are never retried after a delivery error. */
2
- import { CONTROL_SCHEMA, parseControlRequest } from "../core/control-protocol.ts";
3
- import type { ContextLens, LensFocus } from "../core/control-view.ts";
4
- import type { LogEvent } from "../core/events.ts";
5
- import type { QuestionReply } from "../core/question.ts";
6
- import type { SessionList } from "../store/sessions.ts";
7
- import type { ControlNotice, ControlSnapshot } from "./session-control.ts";
8
-
9
- export interface ControlApi {
10
- "thread/read": { params: { threadId: string }; result: ControlSnapshot };
11
- "thread/list": { params: Record<string, never>; result: SessionList & { live: string[] } };
12
- "thread/new": { params: { name?: string }; result: ControlSnapshot };
13
- "thread/resume": { params: { session: string }; result: ControlSnapshot };
14
- "thread/fork": { params: { threadId: string; name?: string }; result: ControlSnapshot };
15
- "thread/close": { params: { threadId: string }; result: { closed: boolean } };
16
- "thread/mode": { params: { threadId: string; mode: "build" | "plan" }; result: ControlSnapshot };
17
- "turn/start": { params: { threadId: string; input: string }; result: { turnId: string } };
18
- "turn/steer": { params: { threadId: string; turnId: string; input: string }; result: { queued: boolean } };
19
- "turn/interrupt": { params: { threadId: string; turnId: string }; result: { interruptRequested: boolean } };
20
- "approval/respond": { params: { threadId: string; turnId: string; approvalId: string; choice: "once" | "command" | "tool" | "deny" }; result: { accepted: boolean } };
21
- "question/respond": { params: { threadId: string; turnId: string; requestId: string; reply: string }; result: { accepted: boolean } };
22
- "acceptance/accept": { params: { threadId: string; requestId: string; criterionId: string }; result: { accepted: boolean } };
23
- "context/read": { params: { threadId: string; focus: LensFocus; offset?: number; limit?: number }; result: {
24
- node: number; streamId: string; sequence: number; focus: LensFocus; total: number; entries: readonly unknown[]; nextOffset: number | null;
25
- components: ContextLens["components"]; used: ContextLens["used"]; countKind: ContextLens["countKind"] } };
26
- "item/list": { params: { threadId: string; after?: { node: number; event: string }; limit?: number }; result: {
27
- items: { node: number; id: string; type: LogEvent["t"] }[]; nextCursor: { node: number; event: string } | null } };
28
- "item/read": { params: { threadId: string; node: number; event: string }; result: { node: number; event: LogEvent } };
29
- "thread/subscribe": { params: { threadId: string; after?: { streamId: string; sequence: number } }; result: { snapshot: ControlSnapshot; resyncRequired: boolean } };
30
- }
31
- function record(value: unknown): value is Record<string, unknown> { return !!value && typeof value === "object" && !Array.isArray(value); }
32
- function identity(value: unknown): value is string { return typeof value === "string" && value.length > 0 && value.length <= 128; }
33
- function count(value: unknown): value is number { return typeof value === "number" && Number.isSafeInteger(value) && value >= 0; }
34
- function snapshot(value: unknown): boolean {
35
- return record(value) && value["version"] === CONTROL_SCHEMA.protocol && identity(value["threadId"]) && identity(value["streamId"]) && count(value["sequence"])
36
- && count(value["node"]) && (value["activeTurnId"] === null || identity(value["activeTurnId"])) && (value["goal"] === null || record(value["goal"]))
37
- && [null, "permission", "question", "auth"].includes(value["waiting"] as null) && Array.isArray(value["approvals"]) && Array.isArray(value["questions"]) && record(value["context"]);
38
- }
39
- function result(method: keyof ControlApi, value: unknown): boolean {
40
- if (["thread/read", "thread/new", "thread/resume", "thread/fork", "thread/mode"].includes(method)) return snapshot(value);
41
- if (!record(value)) return false;
42
- switch (method) {
43
- case "thread/list": return Array.isArray(value["sessions"]) && count(value["truncated"]) && Array.isArray(value["live"]) && value["live"].every(identity);
44
- case "thread/close": return typeof value["closed"] === "boolean";
45
- case "turn/start": return identity(value["turnId"]);
46
- case "turn/steer": return typeof value["queued"] === "boolean";
47
- case "turn/interrupt": return typeof value["interruptRequested"] === "boolean";
48
- case "approval/respond": case "question/respond": case "acceptance/accept": return typeof value["accepted"] === "boolean";
49
- case "thread/subscribe": return snapshot(value["snapshot"]) && typeof value["resyncRequired"] === "boolean";
50
- case "context/read": return count(value["node"]) && identity(value["streamId"]) && count(value["sequence"]) && ["messages", "omitted", "history"].includes(String(value["focus"]))
51
- && count(value["total"]) && Array.isArray(value["entries"]) && (value["nextOffset"] === null || count(value["nextOffset"])) && record(value["components"])
52
- && typeof value["used"] === "number" && typeof value["countKind"] === "string";
53
- case "item/list": return Array.isArray(value["items"]) && value["items"].every(item => record(item) && count(item["node"]) && identity(item["id"]) && typeof item["type"] === "string")
54
- && (value["nextCursor"] === null || record(value["nextCursor"]) && count(value["nextCursor"]["node"]) && identity(value["nextCursor"]["event"]));
55
- case "item/read": return count(value["node"]) && record(value["event"]) && identity(value["event"]["id"]) && typeof value["event"]["t"] === "string";
56
- default: return false;
57
- }
58
- }
59
- export class ControlClient {
60
- private readonly write: (line: string) => void;
61
- private readonly closeTransport: (() => void) | undefined;
62
- private next = 1;
63
- private buffer = "";
64
- private closed = false;
65
- private ready = false;
66
- private initializing = false;
67
- private readonly pending = new Map<number, { resolve(value: unknown): void; reject(error: Error): void; timer: ReturnType<typeof setTimeout> }>();
68
- private readonly listeners = new Set<(notice: ControlNotice) => void>();
69
- constructor(write: (line: string) => void, options: { close?: () => void } = {}) { this.write = write; this.closeTransport = options.close; }
70
- onNotice(listener: (notice: ControlNotice) => void, threadId?: string): () => void {
71
- const actual = threadId ? (notice: ControlNotice) => { if (notice.params.threadId === threadId) listener(notice); } : listener;
72
- this.listeners.add(actual); return () => { this.listeners.delete(actual); };
73
- }
74
- async initialize(name: string, version: string): Promise<{ threadId: string; streamId: string }> {
75
- if (this.ready || this.initializing) throw Error("control client already initialized or initializing");
76
- this.initializing = true;
77
- try {
78
- const value = await this.raw("initialize", { clientInfo: { name, version } });
79
- if (!record(value) || value["protocol"] !== CONTROL_SCHEMA.protocol || !identity(value["threadId"]) || !identity(value["streamId"])) {
80
- this.close("unsupported or invalid control initialize result"); throw Error("unsupported or invalid control initialize result");
81
- }
82
- try { this.write(JSON.stringify({ method: "initialized", params: {} }) + "\n"); }
83
- catch { this.close("control handshake delivery failed"); throw Error("control handshake delivery failed"); }
84
- this.ready = true; return { threadId: value["threadId"], streamId: value["streamId"] };
85
- } finally { this.initializing = false; }
86
- }
87
- request<M extends keyof ControlApi>(method: M, params: ControlApi[M]["params"]): Promise<ControlApi[M]["result"]> {
88
- if (!this.ready) return Promise.reject(Error("initialize the control client before making requests"));
89
- return this.raw(method, params).then(value => {
90
- if (!result(method, value)) { this.close("invalid typed control result"); throw Error("invalid typed control result"); }
91
- return value as ControlApi[M]["result"];
92
- });
93
- }
94
- respondQuestion(threadId: string, turnId: string, requestId: string, reply: QuestionReply): Promise<{ accepted: boolean }> {
95
- return this.request("question/respond", { threadId, turnId, requestId, reply: JSON.stringify(reply) });
96
- }
97
- raw(method: string, params: Record<string, unknown>, timeoutMs = 30_000): Promise<unknown> {
98
- if (this.closed) return Promise.reject(Error("control client is closed"));
99
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000) return Promise.reject(Error("control timeout must be 1..300000 ms"));
100
- if (this.pending.size >= CONTROL_SCHEMA.limits.queuedRequests) return Promise.reject(Error("client request limit reached"));
101
- const id = this.next++, line = JSON.stringify({ id, method, params }) + "\n";
102
- try { parseControlRequest({ id, method, params }); } catch { return Promise.reject(Error("invalid control request parameters")); }
103
- if (Buffer.byteLength(line) > CONTROL_SCHEMA.limits.frameBytes) return Promise.reject(Error("request exceeds frame limit"));
104
- return new Promise((resolve, reject) => {
105
- const timer = setTimeout(() => { this.pending.delete(id); reject(Error("request timed out; effect is unknown, inspect before retrying")); }, timeoutMs);
106
- this.pending.set(id, { resolve, reject, timer });
107
- try { this.write(line); } catch { clearTimeout(timer); this.pending.delete(id); reject(Error("control delivery failed; effects are unknown, inspect before retrying")); }
108
- });
109
- }
110
- feed(text: string): void {
111
- if (this.closed) return;
112
- this.buffer += text;
113
- for (;;) {
114
- const at = this.buffer.indexOf("\n");
115
- if (at < 0) { if (Buffer.byteLength(this.buffer) > CONTROL_SCHEMA.limits.frameBytes) this.close("oversized control frame"); return; }
116
- const line = this.buffer.slice(0, at); this.buffer = this.buffer.slice(at + 1);
117
- if (Buffer.byteLength(line) > CONTROL_SCHEMA.limits.frameBytes) { this.close("oversized control frame"); return; }
118
- if (!line.trim()) continue;
119
- let data: unknown;
120
- try { data = JSON.parse(line); } catch { this.close("invalid control JSON"); return; }
121
- if (!record(data)) { this.close("invalid control envelope"); return; }
122
- if (Object.hasOwn(data, "id")) {
123
- const id = data["id"], error = data["error"];
124
- if (!(id === null || typeof id === "number" && Number.isSafeInteger(id) && id >= 1) || Object.keys(data).some(key => !["id", "result", "error"].includes(key))
125
- || Object.hasOwn(data, "result") === Object.hasOwn(data, "error")
126
- || Object.hasOwn(data, "error") && (!record(error) || !Number.isSafeInteger(error["code"]) || typeof error["message"] !== "string" || Object.keys(error).some(key => !["code", "message"].includes(key)))) {
127
- this.close("invalid control response envelope"); return;
128
- }
129
- if (id === null) { this.close("control server rejected the connection frame"); return; }
130
- const pending = this.pending.get(id); if (!pending) continue;
131
- this.pending.delete(id); clearTimeout(pending.timer);
132
- if (record(error)) pending.reject(Error(`Control request rejected (${String(error["code"])}): ${String(error["message"]).slice(0, 512)}`)); else pending.resolve(data["result"]);
133
- } else {
134
- const params = data["params"], method = data["method"];
135
- if (Object.keys(data).some(key => !["method", "params"].includes(key)) || typeof method !== "string" || !CONTROL_SCHEMA.notifications.some(name => name === method)
136
- || !record(params) || !identity(params["threadId"]) || !identity(params["streamId"]) || !count(params["sequence"]) || params["sequence"] === 0
137
- || params["turnId"] !== undefined && !identity(params["turnId"])) { this.close("invalid control notification envelope"); return; }
138
- for (const listener of this.listeners) try { listener(structuredClone(data) as ControlNotice); } catch { /* observers do not own transport */ }
139
- }
140
- }
141
- }
142
- close(reason = "control transport closed; unresolved effects are unknown"): void {
143
- if (this.closed) return;
144
- this.closed = true; this.ready = false; this.buffer = "";
145
- for (const item of this.pending.values()) { clearTimeout(item.timer); item.reject(Error(reason)); }
146
- this.pending.clear(); this.listeners.clear();
147
- try { this.closeTransport?.(); } catch { /* transport failures cannot reopen this client or replay requests */ }
148
- }
149
- }
@@ -1,139 +0,0 @@
1
- /** Connection lifecycle is transient; durable state belongs exclusively to Session's log (Q24). */
2
- import { CONTROL_SCHEMA, parseControlRequest, ControlProtocolError, type RpcId } from "../core/control-protocol.ts";
3
- import { selectLens, type LensFocus } from "../core/control-view.ts";
4
- import { assertNever } from "../core/exhaustive.ts";
5
- import type { SessionControl } from "./session-control.ts";
6
- import type { ApprovalChoice } from "./tools.ts";
7
- import type { SessionHub } from "./session-hub.ts";
8
-
9
- export type RpcOutput = { id: RpcId | null; result?: unknown; error?: { code: number; message: string } } | { method: string; params: unknown };
10
-
11
- export class ControlConnection {
12
- private readonly control: () => SessionControl;
13
- private readonly write: (output: RpcOutput) => void;
14
- private initialized = false;
15
- private ready = false;
16
- private subscribed = false;
17
- private closed = false;
18
- private attached: SessionControl | undefined;
19
- private stop: (() => void) | undefined;
20
- private readonly denyOnClose: boolean;
21
- private readonly hub: SessionHub | undefined;
22
- private readonly subscriptions = new Map<SessionControl, () => void>();
23
- private readonly questionClients = new Map<SessionControl, () => void>();
24
-
25
- constructor(control: () => SessionControl, write: (output: RpcOutput) => void, options: { denyOnClose?: boolean; hub?: SessionHub } = {}) {
26
- this.control = control; this.write = write; this.denyOnClose = options.denyOnClose ?? false;
27
- this.hub = options.hub;
28
- }
29
- private attach(): SessionControl {
30
- const control = this.control();
31
- if (this.attached !== control) {
32
- this.stop?.(); this.attached = control;
33
- this.stop = control.subscribe(notice => { if (!this.closed && this.ready && this.subscribed) this.write(notice); });
34
- }
35
- return control;
36
- }
37
- async receive(value: unknown): Promise<void> {
38
- if (this.closed) return;
39
- let id: RpcId | null = null;
40
- try {
41
- if (typeof value === "object" && value !== null && "id" in value &&
42
- (typeof value.id === "string" && value.id.length <= 128 || typeof value.id === "number" && Number.isSafeInteger(value.id))) id = value.id;
43
- const request = parseControlRequest(value), params = request.params;
44
- let control = this.attach();
45
- if (request.method === "initialize") {
46
- if (this.initialized) throw new ControlProtocolError(-32000, "already initialized");
47
- this.initialized = true;
48
- this.write({ id, result: { protocol: CONTROL_SCHEMA.protocol, experimental: true, threadId: control.threadId, streamId: control.streamId,
49
- capabilities: { turns: true, steering: true, approvals: true, questions: true, contextLens: true, snapshotResync: true, durableEventReplay: "item/list", adaptiveExecution: false } } });
50
- return;
51
- }
52
- if (!this.initialized) throw new ControlProtocolError(-32000, "not initialized");
53
- if (request.method === "initialized") { if (this.ready) throw new ControlProtocolError(-32000, "already ready"); this.ready = true; return; }
54
- if (!this.ready) throw new ControlProtocolError(-32000, "send initialized before using the session");
55
- if (request.method === "thread/list") {
56
- if (!this.hub) throw Error("multi-session methods require app-server");
57
- this.write({ id, result: await this.hub.list() }); return;
58
- }
59
- if (request.method === "thread/new" || request.method === "thread/resume") {
60
- if (!this.hub) throw Error("multi-session methods require app-server");
61
- const opened = await this.hub.open(request.method === "thread/new" ? "new" : "resume", params);
62
- this.write({ id, result: await opened.snapshot() }); return;
63
- }
64
- if (this.hub && typeof params["threadId"] === "string") control = this.hub.get(params["threadId"]);
65
- if (params["threadId"] !== control.threadId) throw new ControlProtocolError(-32000, "stale or unknown threadId; initialize a new connection after changing sessions");
66
- let result: unknown;
67
- switch (request.method) {
68
- case "thread/fork": {
69
- if (!this.hub) throw Error("multi-session methods require app-server");
70
- result = await (await this.hub.open("fork", { ...params, session: control.session.config.logDir })).snapshot(); break;
71
- }
72
- case "thread/close": {
73
- if (!this.hub) throw Error("multi-session methods require app-server");
74
- await this.hub.close(control.threadId); this.subscriptions.get(control)?.(); this.subscriptions.delete(control);
75
- this.questionClients.get(control)?.(); this.questionClients.delete(control);
76
- result = { closed: true }; break;
77
- }
78
- case "thread/mode": {
79
- if (!control.configureWorkflow) throw Error("workflow changes are unavailable for this session");
80
- await control.configureWorkflow(params["mode"] as "build" | "plan"); result = await control.snapshot(); break;
81
- }
82
- case "thread/read": result = await control.snapshot(); break;
83
- case "thread/subscribe": {
84
- if (!this.questionClients.has(control)) this.questionClients.set(control, control.attachQuestionClient());
85
- if (this.hub) {
86
- if (!this.subscriptions.has(control)) this.subscriptions.set(control, control.subscribe(notice => { if (!this.closed) this.write(notice); }));
87
- } else this.subscribed = true;
88
- const snapshot = await control.snapshot();
89
- const after = params["after"] as { streamId: string; sequence: number } | undefined;
90
- result = { snapshot, resyncRequired: after !== undefined && (after.streamId !== snapshot.streamId || after.sequence !== snapshot.sequence) };
91
- break;
92
- }
93
- case "turn/start": result = { turnId: control.start(String(params["input"])) }; break;
94
- case "turn/steer": control.steer(String(params["turnId"]), String(params["input"])); result = { queued: true }; break;
95
- case "turn/interrupt": control.interrupt(String(params["turnId"])); result = { interruptRequested: true }; break;
96
- case "approval/respond": control.respond(String(params["turnId"]), String(params["approvalId"]), params["choice"] as ApprovalChoice); result = { accepted: true }; break;
97
- case "question/respond": control.respondQuestion(String(params["turnId"]), String(params["requestId"]), JSON.parse(String(params["reply"]))); result = { accepted: true }; break;
98
- case "acceptance/accept": await control.accept(String(params["requestId"]), String(params["criterionId"])); result = { accepted: true }; break;
99
- case "context/read": {
100
- const snapshot = await control.snapshot(false), all = selectLens(snapshot.context, params["focus"] as LensFocus);
101
- const offset = Number(params["offset"] ?? 0), limit = Number(params["limit"] ?? 50);
102
- result = { node: snapshot.node, streamId: snapshot.streamId, sequence: snapshot.sequence, focus: params["focus"], total: all.length,
103
- entries: all.slice(offset, offset + limit), nextOffset: offset + limit < all.length ? offset + limit : null,
104
- components: snapshot.context.components, used: snapshot.context.used, countKind: snapshot.context.countKind };
105
- break;
106
- }
107
- case "item/list": {
108
- const all = await control.items(), after = params["after"] as { node: number; event: string } | undefined;
109
- const index = after ? all.findIndex(item => item.node === after.node && item.event.id === after.event) : -1;
110
- if (after && index < 0) throw Error("cursor is not on the current path; refresh the snapshot");
111
- const entries = all.slice(index + 1, index + 1 + Number(params["limit"] ?? 50));
112
- const last = entries.at(-1);
113
- result = { items: entries.map(item => ({ node: item.node, id: item.event.id, type: item.event.t })),
114
- nextCursor: last && index + 1 + entries.length < all.length ? { node: last.node, event: last.event.id } : null };
115
- break;
116
- }
117
- case "item/read": {
118
- const item = (await control.items()).find(item => item.node === params["node"] && item.event.id === params["event"]);
119
- if (!item) throw Error("item is not on the current path");
120
- result = item; break;
121
- }
122
- default: assertNever(request.method, "control request");
123
- }
124
- if (!this.closed) this.write({ id, result });
125
- } catch (error) {
126
- if (!this.closed) this.write({ id, error: { code: error instanceof ControlProtocolError ? error.code : -32000,
127
- message: error instanceof Error ? error.message.slice(0, 512) : "control request failed" } });
128
- }
129
- }
130
- close(): void {
131
- this.closed = true; this.stop?.();
132
- for (const stop of this.subscriptions.values()) stop(); this.subscriptions.clear();
133
- for (const stop of this.questionClients.values()) stop(); this.questionClients.clear();
134
- if (this.denyOnClose && this.attached) {
135
- this.attached.denyApprovals();
136
- if (this.attached.activeTurnId) this.attached.interrupt(this.attached.activeTurnId);
137
- }
138
- }
139
- }
@@ -1,74 +0,0 @@
1
- /**
2
- * Counter-checks: the discriminating rung of the evidence ladder (S.1).
3
- *
4
- * A check that passed is run once more against the workspace with this
5
- * request's changes undone. If it still passes, it did not test the change --
6
- * unless it is a guard, which must still pass there (S.7). That is the harness
7
- * applying to the agent the rule verify-red applies to every card: "a test
8
- * that cannot fail proves nothing". This module only runs the checks and
9
- * reports their exit codes; what an exit code means for a criterion of a
10
- * given role is decided in core (`counterVerdict`).
11
- *
12
- * Three rules keep the verdict honest:
13
- * - Only commands that already ran and passed in this request are re-run --
14
- * checks the user accepted -- and only in a disposable clone.
15
- * - The clone must be reverted exactly: after undoing the request's changes
16
- * its source fingerprint must equal the baseline recorded when the request
17
- * began. The undo comes from a snapshot taken at the start when there is
18
- * one, else from the journal; a change neither recorded (a shell command
19
- * writing files without a checkpoint, with no snapshot) cannot be undone,
20
- * so nothing runs and the result is unavailable rather than a guess.
21
- * - The real workspace is never written.
22
- */
23
- import { join, relative } from "node:path";
24
- import { changedPaths, planFileTransition, type CheckpointEvent, type FileChanges } from "../core/checkpoints.ts";
25
- import type { LogEvent } from "../core/events.ts";
26
- import { applyFileChanges } from "../store/file-checkpoints.ts";
27
- import { observeCommand } from "../store/command.ts";
28
- import { observeWorkspace } from "../store/workspace-observation.ts";
29
- import { cloneWorkspace } from "../store/workspace-clone.ts";
30
-
31
- export type CounterCheckInput = {
32
- root: string; blobs: string; ownedPaths: readonly string[];
33
- /** The request's events, in order. */
34
- events: readonly LogEvent[];
35
- /**
36
- * The exact transition back to the request's starting state, when the
37
- * caller took a snapshot at the start (fix-counter-check-own-baseline). It
38
- * covers changes a shell command made. Absent: the request's journaled
39
- * file changes are undone instead.
40
- */
41
- revert?: FileChanges;
42
- checks: readonly { id: string; command: string; guard?: true }[];
43
- timeoutMs: number; maxOutputBytes: number; outputStore: string; signal?: AbortSignal;
44
- };
45
- export type CounterCheckResult = { id: string; command: string; guard?: true } & ({ exit: number } | { reason: string });
46
-
47
- export async function counterCheck(input: CounterCheckInput): Promise<CounterCheckResult[]> {
48
- const unavailable = (reason: string): CounterCheckResult[] => input.checks.map(check => ({ ...check, reason }));
49
- if (input.checks.length === 0) return [];
50
- const baseline = input.events.findLast(event => event.t === "workspace_observation" && event.baseline === true);
51
- if (baseline?.t !== "workspace_observation" || baseline.fingerprint === undefined) return unavailable("no workspace baseline was recorded when this request began");
52
- const applied = input.events.filter((event): event is CheckpointEvent => event.t === "checkpoint" && event.phase === "applied");
53
- // Undoing every applied change of this request and replaying none is the journal's revert.
54
- const revert = input.revert ?? planFileTransition(applied, []);
55
- if (changedPaths(revert).length === 0) return unavailable("this request recorded no file changes to undo");
56
- const clone = await cloneWorkspace(input.root, input.signal);
57
- if (!clone.ok) return unavailable(clone.reason);
58
- try {
59
- try { await applyFileChanges(clone.path, input.blobs, revert, input.signal); }
60
- catch (error) { return unavailable(`the recorded changes could not be undone: ${error instanceof Error ? error.message : String(error)}`); }
61
- const owned = input.ownedPaths.map(path => { const rel = relative(input.root, path); return rel.startsWith("..") ? path : join(clone.path, rel); });
62
- const observed = await observeWorkspace(clone.path, input.signal, owned);
63
- if (!observed.ok) return unavailable(`the reverted copy could not be observed: ${observed.reason}`);
64
- if (observed.fingerprint !== baseline.fingerprint) return unavailable(input.revert ? "the request's starting state could not be rebuilt exactly"
65
- : "some changes were made outside tracked edits, so the request's starting state could not be rebuilt");
66
- const results: CounterCheckResult[] = [];
67
- for (const check of input.checks) {
68
- const captured = await observeCommand({ command: check.command, cwd: clone.path, outputStore: input.outputStore,
69
- maxOutputBytes: input.maxOutputBytes, timeoutMs: input.timeoutMs, ...(input.signal ? { signal: input.signal } : {}) });
70
- results.push(captured.stopped ? { ...check, reason: `the check did not finish in the reverted copy (${captured.stopped})` } : { ...check, exit: captured.exit });
71
- }
72
- return results;
73
- } finally { await clone.dispose(); }
74
- }
@@ -1,60 +0,0 @@
1
- /** R6: observe through the same shell and process owner; never infer versions from the host PATH. */
2
- import { createHash } from "node:crypto";
3
- import { hostname } from "node:os";
4
- import { join, resolve } from "node:path";
5
- import { ENVIRONMENT_OUTPUT_BYTES, ENVIRONMENT_PARSE_BYTES, ENVIRONMENT_TIMEOUT_MS, ENVIRONMENT_TTL_MS,
6
- environmentCapability, parseEnvironmentFrame, type EnvironmentProbe, type EnvironmentReceipt, type EnvironmentSource } from "../core/environment.ts";
7
- import { executionOutcome } from "../core/outcome.ts";
8
- import { canonicalValue } from "../core/continuation.ts";
9
- import { failureDiagnostic } from "../core/tool-failure.ts";
10
- import { observeCommand } from "../store/command.ts";
11
- import type { ToolContext, ToolResult } from "./tools.ts";
12
-
13
- const hash = (text: string) => createHash("sha256").update(text).digest("hex");
14
- /** Hash only resolution-related inherited inputs; never expose the full environment or its credentials. */
15
- export function environmentSource(ctx: Pick<ToolContext, "root" | "hostId">, cwd: string): EnvironmentSource {
16
- const keys = ["PATH", "HOME", "SHELL", "LANG", "BASH_ENV", "PYTHONHOME", "PYTHONPATH", "PYENV_VERSION", "VIRTUAL_ENV", "CONDA_PREFIX", "NVM_BIN"];
17
- return { host: ctx.hostId ?? hostname(), workspace: resolve(ctx.root), cwd: resolve(cwd),
18
- runtime: `bun/${Bun.version};${process.platform}/${process.arch};${process.execPath}`,
19
- inherited_sha256: hash(canonicalValue(Object.fromEntries(keys.map(key => [key, process.env[key] ?? null])))) };
20
- }
21
-
22
- export async function observeEnvironment(ctx: ToolContext, cwd: string, program: string, probe: EnvironmentProbe, command: string): Promise<ToolResult> {
23
- const budget = new AbortController();
24
- const signal = ctx.signal ? AbortSignal.any([ctx.signal, budget.signal]) : budget.signal;
25
- let outputBytes = 0;
26
- const source = environmentSource(ctx, cwd);
27
- const captured = await observeCommand({ command, cwd, cleanupOnExit: true, outputLimitBytes: ENVIRONMENT_OUTPUT_BYTES,
28
- outputStore: ctx.outputStore ?? join(ctx.root, ".xeno", "observations"),
29
- maxOutputBytes: ENVIRONMENT_PARSE_BYTES, timeoutMs: Math.min(ENVIRONMENT_TIMEOUT_MS, ctx.timeoutMs, ctx.maxTimeoutMs ?? Infinity), signal,
30
- onOutput: chunk => {
31
- outputBytes += chunk.byteLength;
32
- if (outputBytes > ENVIRONMENT_OUTPUT_BYTES) budget.abort();
33
- ctx.onOutput?.(chunk);
34
- },
35
- });
36
- const outputLimited = budget.signal.aborted;
37
- const parseLimited = captured.ref.bytes > ENVIRONMENT_PARSE_BYTES;
38
- const frame = parseEnvironmentFrame(new TextDecoder().decode(captured.head), program, probe);
39
- const capability = captured.cleanup ? { state: "unknown" as const, reported_version: null, reason: captured.cleanup.message } : environmentCapability(frame, captured.exit, { complete: captured.capture === "complete",
40
- ...(captured.stopped ? { stopped: captured.stopped } : {}),
41
- ...(outputLimited ? { limited: "output" } : parseLimited ? { limited: "parse" } : {}) });
42
- const observedAt = Date.now();
43
- const receipt: EnvironmentReceipt = { kind: "environment", version: 1, query: { program, probe }, source,
44
- observed_at: observedAt, expires_at: observedAt + ENVIRONMENT_TTL_MS, ...capability,
45
- shell: frame ? { executable: frame.shell, version: frame.shell_version, cwd: frame.cwd, path_sha256: hash(frame.path) } : null,
46
- resolution: frame ? { kind: frame.kind, executable: frame.executable } : null,
47
- evidence: { output: captured.ref, exit: captured.exit, complete: captured.capture === "complete" } };
48
- const status = outputLimited ? "tool_failed" : captured.stopped ?? (capability.state === "observed" ? "tool_completed" : "tool_failed");
49
- const code = outputLimited ? "execution_failed" : captured.stopped ?? (capability.state === "missing" ? "environment_missing" : "execution_failed");
50
- const text = JSON.stringify(receipt);
51
- const bytes = new TextEncoder().encode(text);
52
- // Raw output is the primary artifact. Structured data is separately preserved by the Session broker.
53
- const content = bytes.length <= ctx.maxOutputBytes ? text :
54
- `environment ${receipt.state}; exit ${captured.exit}; full receipt in data; original output: ${captured.ref.sha256}`;
55
- return { ok: capability.state === "observed", content, content_ref: captured.ref, data: receipt,
56
- ...(captured.cleanup ? { warnings: [captured.cleanup] } : {}),
57
- observation_sha256: hash(canonicalValue({ query: receipt.query, source, ...capability, shell: receipt.shell, resolution: receipt.resolution, evidence: receipt.evidence })),
58
- effect: { kind: "command", cmd: command, exit: captured.exit }, outcome: executionOutcome(status),
59
- ...(capability.state === "observed" ? {} : { diagnostic: failureDiagnostic(code, capability.reason ?? "capability unconfirmed", { execution: "started", effects: "possible" }) }) };
60
- }
@@ -1,35 +0,0 @@
1
- /** O.6: resolve model associations against original observations in the active ancestry. */
2
- import type { EvidenceLinkEvent } from "../core/events.ts";
3
- import type { Handoff } from "../core/handoff.ts";
4
- import { historyEntries } from "../core/history-query.ts";
5
- import { observedVersion, resourceKey } from "../core/evidence-links.ts";
6
- import { scopedWorkId } from "../core/work-state.ts";
7
- import { readNodeHistory } from "../store/restore.ts";
8
- import { failureDiagnostic, NOT_STARTED, READ_ATTEMPTED } from "../core/tool-failure.ts";
9
- import type { ToolResult } from "./tools.ts";
10
-
11
- export async function resolveEvidenceNote(logDir: string, node: number, namespace: string, current: Handoff, event: EvidenceLinkEvent): Promise<{ event?: EvidenceLinkEvent; result: ToolResult }> {
12
- const work = scopedWorkId(namespace, event.work, current.state.work_items ?? []);
13
- const fail = (message: string, attempted = false) => ({ result: { ok: false, content: message, data: { recorded: false },
14
- diagnostic: failureDiagnostic(attempted ? "execution_failed" : "invalid_input", message, attempted ? READ_ATTEMPTED : NOT_STARTED) } });
15
- if (!current.state.work_items?.some(item => item.id === work)) return fail("pin requires an existing work item");
16
- const history = await readNodeHistory(logDir, node);
17
- if (!history.ok) return fail(history.error, true);
18
- const entries = historyEntries(history.nodes);
19
- const source = entries.find(entry => entry.event === event.source && entry.kind === "tool_result");
20
- if (!source) return fail("pin source must be an original tool observation in the active ancestry");
21
- const resource = event.resource === undefined ? source.resources?.[0] : resourceKey(event.resource);
22
- if (resource !== undefined && !source.resources?.includes(resource)) return fail("pin resource must match the original observation");
23
- if (event.supersedes && (event.supersedes === event.source || !current.evidence_links?.some(link =>
24
- link.work === work && link.event === event.supersedes && link.superseded_by === undefined))) return fail("supersedes must name an active pinned source for this work item");
25
- const events = history.nodes.flatMap(part => [...part.events]);
26
- const observed = events.find(item => item.id === event.source);
27
- const effect = observed?.t === "tool_result" ? observed.effect : undefined;
28
- const version = effect && "path" in effect ? effect.kind === "read" ? effect.sha256 : effect.sha256_after : undefined;
29
- const latest = resource === undefined ? undefined : observedVersion(events, resource);
30
- const resolved: EvidenceLinkEvent = { ...event, work,
31
- ...(resource !== undefined ? { resource } : {}), ...(version !== undefined ? { version } : {}),
32
- ...(latest !== undefined ? { observed_version: latest } : {}) };
33
- return { event: resolved, result: { ok: true, content: JSON.stringify({ recorded: true, work_id: work, source_event: source.event, source_node: source.node,
34
- resource: resource ?? null, instruction: "This links observed evidence; completion still requires verification." }) } };
35
- }