@visual-z/xeno 0.8.4 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +19 -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,168 +0,0 @@
1
- /** N.2: one ancestry-scoped reader for query, exact event, and structured pages. */
2
- import { join } from "node:path";
3
- import { deriveHandoff } from "../core/derive.ts";
4
- import type { Handoff } from "../core/handoff.ts";
5
- import { HISTORY_SECTIONS, historyPage, userTurnImages, type HistorySection } from "../core/history.ts";
6
- import { historyEntries, historyHits, historyIndex, queryHistory, type HistoryEntry } from "../core/history-query.ts";
7
- import { resourceKey } from "../core/evidence-links.ts";
8
- import { canonicalValue } from "../core/continuation.ts";
9
- import { digestNode, nodePath, readManifest } from "../store/log.ts";
10
- import { readNodeHistory } from "../store/restore.ts";
11
- import { archiveObservation, readObservation } from "../store/observations.ts";
12
- import { observationContains } from "../store/history-search.ts";
13
- import { presentObservation, type ToolContext, type ToolResult } from "./tools.ts";
14
- import { ToolFault, failureFromError, READ_ATTEMPTED } from "../core/tool-failure.ts";
15
- import { environmentValidity, isEnvironmentReceipt } from "../core/environment.ts";
16
- import { environmentSource } from "./environment-tools.ts";
17
- import { RECOVERY_SECTIONS, recoverySources, recoveryPage, type RecoverySection } from "../core/recovery-sources.ts";
18
-
19
- export async function runHistoryTool(logDir: string, currentNode: number, ctx: ToolContext, input: unknown): Promise<ToolResult> {
20
- try {
21
- if (input === null || typeof input !== "object" || Array.isArray(input)) throw new ToolFault("invalid_input", "history expects an object", READ_ATTEMPTED);
22
- const args = input as Record<string, unknown>;
23
- if (args["recovery"] !== undefined && ["artifact", "event", "events", "query", "recent", "resource", "work_id", "tool", "section"].some(key => args[key] !== undefined)) {
24
- throw new ToolFault("invalid_input", "recovery is a separate selector; use only node, recovery, offset and limit", READ_ATTEMPTED);
25
- }
26
- const archive = ctx.outputStore ?? join(logDir, "artifacts");
27
- if (typeof args["artifact"] === "string") return { ok: true, content: JSON.stringify(await readObservation(archive,
28
- args["artifact"], typeof args["offset"] === "number" ? args["offset"] : 0,
29
- Math.max(1, Math.floor(ctx.maxOutputBytes / 3)))) };
30
- const index = await readManifest(logDir);
31
- let history = await readNodeHistory(logDir, currentNode, index, "context");
32
- if (!history.ok) throw new Error(history.error);
33
- if (args["branch"] !== undefined) {
34
- if (!Number.isSafeInteger(args["branch"]) || Number(args["branch"]) < 0) throw new ToolFault("invalid_input", "history branch must be a node number", READ_ATTEMPTED);
35
- const node = args["branch"] as number;
36
- const joined = history.nodes.flatMap(part => part.events).findLast(event => event.t === "joined" && event.branch.node === node);
37
- if (joined?.t !== "joined") throw new ToolFault("permission_denied", "branch has not joined the active ancestry", READ_ATTEMPTED);
38
- history = await readNodeHistory(logDir, joined.tip?.node ?? node, index, "context");
39
- if (!history.ok) throw new Error(history.error);
40
- }
41
- // Integrity and fork cutoffs are checked above even on a cache hit. A
42
- // disposable optimization cannot turn a damaged source into valid truth.
43
- const view = ctx.historyCache?.read(logDir, history.nodes, index.nodes
44
- .filter(record => history.nodes.some(part => part.node === record.node)).map(record => ({ node: record.node, sha256: record.sha256 })));
45
- const original = view?.entries ?? historyEntries(history.nodes);
46
- // Freshness is a disposable consumption view. Never cache time-dependent
47
- // validity or overwrite the original result, including on a warm cache hit.
48
- let entries = original;
49
- const capability = (entry: HistoryEntry) => entry.tool === "environment" && isEnvironmentReceipt(entry.data) &&
50
- entry.effect?.kind === "command" && entry.effect.exit === entry.data.evidence.exit;
51
- if (original.some(capability)) {
52
- const events = history.nodes.flatMap(part => part.events);
53
- const positions = new Map(events.map((event, i) => [event.id, i]));
54
- const source = environmentSource(ctx, ctx.root), now = Date.now();
55
- entries = original.map(entry => capability(entry) && isEnvironmentReceipt(entry.data) ? { ...entry,
56
- environment: environmentValidity(entry.data, source, now, events.slice((positions.get(entry.event) ?? events.length) + 1)),
57
- } : entry);
58
- }
59
- const indexed = entries === original && view ? view.indexed : historyIndex(entries);
60
- if (args["recovery"] !== undefined) {
61
- if (typeof args["recovery"] !== "string" || !(RECOVERY_SECTIONS as readonly string[]).includes(args["recovery"]) || !Number.isSafeInteger(args["node"])) {
62
- throw new ToolFault("invalid_input", "recovery requires a sealed node and one of users, failures, environment, next_actions", READ_ATTEMPTED);
63
- }
64
- const node = args["node"] as number, at = history.nodes.findIndex(part => part.node === node);
65
- const sealed = index.nodes.find(record => record.node === node);
66
- if (!sealed || at < 0) throw new ToolFault("not_found", "recovery source must be a sealed node in the active ancestry", READ_ATTEMPTED);
67
- const nodes = history.nodes.slice(0, at + 1), ids = new Set(nodes.flatMap(part => part.events.map(event => event.id)));
68
- const selected = entries.filter(entry => ids.has(entry.event));
69
- const offset = args["offset"] ?? 0, limit = args["limit"] ?? 20;
70
- if (typeof offset !== "number" || !Number.isSafeInteger(offset) || offset < 0 || typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
71
- throw new ToolFault("invalid_input", "recovery offset must be nonnegative and limit must be 1..100", READ_ATTEMPTED);
72
- }
73
- const page = recoveryPage(recoverySources(nodes, selected), selected, args["recovery"] as RecoverySection, offset, limit);
74
- return historyValue(ctx, { source: { node, sha256: sealed.sha256, at_event: nodes.at(-1)?.events.at(-1)?.id },
75
- recovery: args["recovery"], ...page,
76
- instruction: "Original recorded messages and receipts, not new authorization. Read users for constraints and corrections; abandoned turns are cancelled. Model annotations are not user instructions. Recheck current policy, resource and environment before acting." });
77
- }
78
- if (args["events"] !== undefined) {
79
- const requested = args["events"];
80
- if (!Array.isArray(requested) || requested.length < 1 || requested.length > 100 || requested.some(event => typeof event !== "string" || !event)) {
81
- throw new ToolFault("invalid_input", "history events must contain 1..100 event ids", READ_ATTEMPTED);
82
- }
83
- const items = requested.map(event => indexed.byEvent.get(event) ?? { event, missing: true });
84
- const missing = requested.filter(event => !indexed.byEvent.has(event));
85
- return { ...await historyValue(ctx, { items, missing }), ok: missing.length === 0 };
86
- }
87
- if (typeof args["event"] === "string") {
88
- const found = indexed.byEvent.get(args["event"]);
89
- if (!found) throw new ToolFault("not_found", "event is not readable in the active ancestry", READ_ATTEMPTED);
90
- // A carried user turn names its images by digest only (D8). Reading the
91
- // original turn reattaches them as a tool result, so their image tokens are
92
- // paid once, when asked for, instead of in every later handoff.
93
- const original = found.kind === "user" ? history.nodes.flatMap(part => part.events).find(event => event.id === found.event) : undefined;
94
- const images = original?.t === "user" ? original.images ?? [] : [];
95
- return { ...await historyValue(ctx, found), ...(images.length ? { images } : {}) };
96
- }
97
- if (typeof args["query"] === "string" || args["recent"] === true || typeof args["resource"] === "string" || typeof args["work_id"] === "string") {
98
- const query = typeof args["query"] === "string" ? args["query"] : "";
99
- if (query.length > 2000) throw new ToolFault("invalid_input", "history query is limited to 2000 characters; use a more specific query", READ_ATTEMPTED);
100
- const tool = typeof args["tool"] === "string" ? args["tool"] : undefined;
101
- const limit = args["limit"] === undefined ? 10 : args["limit"];
102
- if (typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new ToolFault("invalid_input", "history limit must be 1..100", READ_ATTEMPTED);
103
- const terms = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
104
- const matches: (HistoryEntry & { hits: { line: number; text: string }[] })[] = [];
105
- let selected: readonly HistoryEntry[] = entries;
106
- if (typeof args["work_id"] === "string") selected = indexed.byWork.get(args["work_id"]) ?? [];
107
- if (typeof args["resource"] === "string") {
108
- const resources = new Set(indexed.byResource.get(resourceKey(args["resource"])) ?? []);
109
- selected = selected.filter(entry => resources.has(entry));
110
- }
111
- for (const entry of selected.toReversed()) {
112
- if (tool !== undefined && entry.tool !== tool) continue;
113
- const inline = `${entry.text}\n${canonicalValue(entry.input)}`.toLowerCase();
114
- const missing = terms.filter((term) => !inline.includes(term));
115
- if (missing.length > 0 && !(entry.artifact && await observationContains(archive, entry.artifact, missing))) continue;
116
- // This is an index excerpt, not replacement evidence. Exact event/artifact
117
- // readers retain every omitted byte, with a stable snapshot for pagination.
118
- // Hits show where the terms are, so a match deep in a long observation
119
- // does not force reading the whole original back (N.2).
120
- matches.push({ ...entry, text: entry.text.slice(0, 200), hits: historyHits(entry.text, terms) });
121
- }
122
- // Keep empty-query ordering identical to the pure reference search.
123
- if (terms.length === 0 && matches.length !== queryHistory(selected, "", tool).length) throw new Error("history index mismatch");
124
- async function* serialized(): AsyncGenerator<Uint8Array> {
125
- for (const match of matches) yield new TextEncoder().encode(JSON.stringify(match) + "\n");
126
- }
127
- const saved = await archiveObservation(archive, serialized(), ctx.maxOutputBytes);
128
- const page = matches.slice(0, limit);
129
- const render = () => JSON.stringify({ matches: matches.length, items: page,
130
- full_results: { artifact: saved.ref.sha256, format: "jsonl", offset: 0 },
131
- instruction: "Use event for original evidence; artifact and offset page this immutable search result." });
132
- let response = render();
133
- while (page.length > 0 && new TextEncoder().encode(response).length > ctx.maxOutputBytes) { page.pop(); response = render(); }
134
- if (new TextEncoder().encode(response).length > ctx.maxOutputBytes) response = `history artifact: ${saved.ref.sha256}; ${matches.length} matches; offset 0`;
135
- // The snapshot itself is a first-class observation so copy/fork preserves it.
136
- return { ok: true, content: response, content_ref: saved.ref, content_sha256: saved.ref.sha256 };
137
- }
138
- if (!Number.isSafeInteger(args["node"]) || typeof args["section"] !== "string" ||
139
- !(HISTORY_SECTIONS as readonly string[]).includes(args["section"])) throw new ToolFault("invalid_input", "history requires query, recent, event, artifact, or node and section", READ_ATTEMPTED);
140
- const node = args["node"] as number;
141
- if (!index.nodes.some((entry) => entry.node === node) || !history.nodes.some((entry) => entry.node === node)) throw new ToolFault("not_found", "history source must be a sealed node in the active ancestry", READ_ATTEMPTED);
142
- // Fold only the events contributed to this ancestry, respecting rewind points.
143
- let full: Handoff | null = null;
144
- let depth = 0;
145
- for (const part of history.nodes) {
146
- full = deriveHandoff({ prev: full, depth: ++depth, events: part.events,
147
- self: { node: part.node, log_ref: nodePath(logDir, part.node), sha256: await digestNode(logDir, part.node) },
148
- config: { maxUserTurns: Infinity, maxUserTurnBytes: Infinity } });
149
- if (part.node === node) break;
150
- }
151
- if (full === null) throw new ToolFault("not_found", "empty history", READ_ATTEMPTED);
152
- // Pages match a v3 carried suffix: user turns name their images by digest (D8).
153
- if (args["section"] === "user_turns") {
154
- const at = history.nodes.findIndex(part => part.node === node);
155
- full = { ...full, user_turns: userTurnImages(full.user_turns, history.nodes.slice(0, at + 1)) };
156
- }
157
- const page = historyPage(full, args["section"] as HistorySection,
158
- typeof args["offset"] === "number" ? args["offset"] : 0,
159
- typeof args["limit"] === "number" ? Math.min(args["limit"], 100) : 20);
160
- return { ok: true, ...await presentObservation(ctx, JSON.stringify({ source: full.predecessor, section: args["section"], ...page })) };
161
- } catch (error) { return { ok: false, content: `history: ${String(error)}`, diagnostic: failureFromError(error, READ_ATTEMPTED) }; }
162
- }
163
-
164
- async function historyValue(ctx: ToolContext, value: unknown): Promise<ToolResult> {
165
- const content = JSON.stringify(value);
166
- const observation = await presentObservation(ctx, content);
167
- return { ok: true, ...observation, data: observation.content_ref ? { artifact: observation.content_ref, format: "json" } : JSON.parse(content) };
168
- }
@@ -1,80 +0,0 @@
1
- /** Pi (MIT) processing, xeno (D8) immutable storage; decoding stays outside core and off the TUI thread. */
2
- import { readFile, stat } from "node:fs/promises";
3
- import { basename } from "node:path";
4
- import { resizeImage } from "../vendor/pi-rich/image-resize.ts";
5
- import { detectSupportedImageMimeType } from "../vendor/pi-rich/mime.ts";
6
- import { convertImageBytesToPng, convertToPng } from "../vendor/pi-rich/image-convert.ts";
7
- import { putBlob, getBlobBytes } from "../store/blobs.ts";
8
- import { imageDescription, validImageRef, type ImageRef } from "../core/images.ts";
9
- import type { ContentBlock, Message } from "../core/project.ts";
10
- import { imageDimensions, modelImageSide, MAX_IMAGE_SOURCE_BYTES, MAX_IMAGE_SOURCE_PIXELS } from "../core/image-limits.ts";
11
-
12
- // Admission limit, not a context threshold: avoid reading arbitrarily large local binaries into WASM.
13
- export async function attachImageBytes(bytes: Uint8Array, name: string, blobDir: string, autoResize = true): Promise<ImageRef> {
14
- if (bytes.byteLength > MAX_IMAGE_SOURCE_BYTES) throw new Error("Image exceeds the 64 MiB attachment limit");
15
- let mime = detectSupportedImageMimeType(bytes);
16
- if (!mime) throw new Error("Unsupported or malformed image: " + name);
17
- const dimensions = imageDimensions(bytes, mime);
18
- if (!dimensions) throw new Error("Cannot read image dimensions: " + name);
19
- if (dimensions.width * dimensions.height > MAX_IMAGE_SOURCE_PIXELS) throw new Error("Image exceeds the 25 megapixel decode limit: " + name);
20
- const maxSide = modelImageSide(dimensions);
21
- const originalBytes = bytes;
22
- if (mime === "image/bmp") {
23
- const converted = await convertImageBytesToPng(bytes);
24
- if (!converted) throw new Error("Cannot decode image: " + name);
25
- bytes = converted; mime = "image/png";
26
- }
27
- // Pi's disabled auto-resize setting preserves the input; decoding still verifies its dimensions.
28
- const result = await resizeImage(bytes, mime, autoResize ? { maxWidth: maxSide, maxHeight: maxSide } : { maxWidth: Number.MAX_SAFE_INTEGER, maxHeight: Number.MAX_SAFE_INTEGER, maxBytes: Number.MAX_SAFE_INTEGER });
29
- if (!result) throw new Error("Cannot decode or resize image: " + name);
30
- if (result.mimeType !== "image/png" && result.mimeType !== "image/jpeg" && result.mimeType !== "image/gif" && result.mimeType !== "image/webp") {
31
- throw new Error("Unsupported image encoding: " + result.mimeType);
32
- }
33
- const original = await putBlob(blobDir, originalBytes);
34
- const encoded = Buffer.from(result.data, "base64");
35
- const sha256 = await putBlob(blobDir, encoded);
36
- return { sha256, mimeType: result.mimeType, width: result.width, height: result.height, bytes: encoded.length, name,
37
- ...(sha256 !== original ? { original: { sha256: original, width: result.originalWidth, height: result.originalHeight } } : {}) };
38
- }
39
- export async function attachImageFile(path: string, blobDir: string, autoResize = true): Promise<ImageRef | null> {
40
- const size = (await stat(path)).size;
41
- if (size > MAX_IMAGE_SOURCE_BYTES) {
42
- if (/\.(png|jpe?g|gif|webp|bmp)$/i.test(path)) throw new Error("Image exceeds the 64 MiB attachment limit");
43
- return null;
44
- }
45
- const bytes = await readFile(path);
46
- if (!detectSupportedImageMimeType(bytes)) {
47
- if (/\.(png|jpe?g|gif|webp|bmp)$/i.test(path)) throw new Error("Unsupported or malformed image: " + path);
48
- return null;
49
- }
50
- return attachImageBytes(bytes, basename(path), blobDir, autoResize);
51
- }
52
- export async function loadImage(image: ImageRef, blobDir: string): Promise<string> {
53
- if (!validImageRef(image)) throw new Error("Invalid image reference");
54
- const bytes = await getBlobBytes(blobDir, image.sha256);
55
- if (!bytes) throw new Error("Image blob is missing: " + image.name);
56
- if (bytes.length !== image.bytes) throw new Error("Image blob size mismatch: " + image.name);
57
- return Buffer.from(bytes).toString("base64");
58
- }
59
- /** Kitty accepts PNG; conversion is display-only and never replaces durable provider bytes. */
60
- export async function loadDisplayImage(image: ImageRef, blobDir: string): Promise<string> {
61
- const png = await convertToPng(await loadImage(image, blobDir), image.mimeType);
62
- if (!png) throw new Error("Cannot display image: " + image.name);
63
- return png.data;
64
- }
65
- export async function hydrateImages(messages: readonly Message[], blobDir: string, acceptsImages: boolean, blocked = false): Promise<Message[]> {
66
- const loaded = new Map<string, Promise<string>>();
67
- return Promise.all(messages.map(async message => ({ ...message, content: await Promise.all(message.content.map(async (block): Promise<ContentBlock> => {
68
- if (block.type !== "image") return block;
69
- if (blocked || block.image.blocked || !acceptsImages) return { type: "text", text: imageDescription(block.image) + (blocked || block.image.blocked ? " [image transmission blocked by settings]" : " [this model does not support image input]") };
70
- let data = loaded.get(block.image.sha256);
71
- if (!data) { data = loadImage(block.image, blobDir); loaded.set(block.image.sha256, data); }
72
- return { ...block, data: await data };
73
- })) })));
74
- }
75
-
76
- /** Provider boundary fails closed if storage hydration was accidentally skipped. */
77
- export function imageData(block: Extract<ContentBlock, { type: "image" }>): string {
78
- if (!block.data) throw new Error("Image was not loaded before provider encoding: " + block.image.name);
79
- return block.data;
80
- }
@@ -1,216 +0,0 @@
1
- /** Q.13: one live owner per optional service; no automatic business-operation replay. */
2
- import { failedIntegrationHealth, initialIntegrationHealth, integrationHealthView, readyIntegrationHealth,
3
- type IntegrationFailureCode, type IntegrationTime } from "../core/integration-health.ts";
4
- import { executionOutcome } from "../core/outcome.ts";
5
- import { failureDiagnostic, NOT_STARTED } from "../core/tool-failure.ts";
6
- import type { ExtensionContext, ExtensionTool, ExtensionToolResult } from "./extensions.ts";
7
- import type { ToolResult } from "./tools.ts";
8
-
9
- /** Adapters classify transport facts explicitly; arbitrary exception messages are never logged. */
10
- export interface IntegrationFailure { kind: "integration_failure"; code: IntegrationFailureCode; retryAfterMs?: number }
11
- export interface IntegrationConnection {
12
- execute(name: string, id: string, args: Record<string, unknown>, signal: AbortSignal,
13
- onUpdate: (result: ExtensionToolResult) => void, ctx: ExtensionContext): Promise<ExtensionToolResult | IntegrationFailure>;
14
- close(): void | Promise<void>;
15
- }
16
- export interface ExtensionIntegration {
17
- name: string;
18
- tools: readonly Omit<ExtensionTool, "execute">[];
19
- /** Only handshake/connection work belongs here. Never replay a business operation in connect. */
20
- connect(signal: AbortSignal, ctx: ExtensionContext): Promise<IntegrationConnection | IntegrationFailure>;
21
- connectTimeoutMs?: number;
22
- callTimeoutMs?: number;
23
- repair?: { command: string; description: string; timeoutMs?: number;
24
- run(signal: AbortSignal, ctx: ExtensionContext): Promise<void | IntegrationFailure> };
25
- }
26
- export type IntegrationObservation = ReturnType<LazyIntegration["observe"]>;
27
- type Settled<T> = { kind: "value"; value: T } | { kind: "threw" };
28
- type Bounded<T> = Settled<T> | { kind: "timeout" | "cancelled" };
29
- type Run = { ok: true; result: ExtensionToolResult; observation: IntegrationObservation } | { ok: false; result: ToolResult };
30
- // Assumed defaults/caps bound trusted async adapters, not synchronous code or the host OS.
31
- const MAX_TIMEOUT_MS = 300_000;
32
- function timeout(value: number | undefined, fallback: number): number {
33
- if (value === undefined) return fallback;
34
- if (!Number.isSafeInteger(value) || value < 1 || value > MAX_TIMEOUT_MS) throw Error("Integration timeout must be an integer from 1 to 300000 ms");
35
- return value;
36
- }
37
- function fault(value: unknown): value is IntegrationFailure {
38
- if (!value || typeof value !== "object") return false;
39
- const v = value as Partial<IntegrationFailure>;
40
- return v.kind === "integration_failure" && ["offline", "rate_limited", "auth_required", "protocol_error", "timeout", "unknown"].includes(String(v.code));
41
- }
42
- function connection(value: unknown): value is IntegrationConnection {
43
- return !!value && typeof value === "object" && "execute" in value && typeof value.execute === "function"
44
- && "close" in value && typeof value.close === "function";
45
- }
46
- function toolResult(value: unknown): value is ExtensionToolResult {
47
- if (!value || typeof value !== "object" || !("content" in value) || !Array.isArray(value.content)) return false;
48
- return value.content.every(item => item && typeof item === "object" && (item.type === "text" && typeof item.text === "string"
49
- || item.type === "image" && typeof item.data === "string" && typeof item.mimeType === "string"));
50
- }
51
-
52
- export class LazyIntegration {
53
- private health = initialIntegrationHealth();
54
- private handle: IntegrationConnection | undefined;
55
- private busy = false;
56
- private phase: "idle" | "connect" | "call" | "repair" = "idle";
57
- private disposed = false;
58
- private lifetime = new AbortController();
59
- private readonly pending = new Set<Promise<unknown>>();
60
- private readonly closed = new WeakSet<IntegrationConnection>();
61
- private cleanup: "none" | "pending" | "complete" | "failed" = "none";
62
- private readonly connectTimeout: number;
63
- private readonly callTimeout: number;
64
- private readonly repairTimeout: number;
65
- readonly definition: ExtensionIntegration;
66
- private readonly now: () => IntegrationTime;
67
- constructor(definition: ExtensionIntegration, now: () => IntegrationTime = () => ({ at: Date.now(), tick: performance.now() })) {
68
- this.definition = definition; this.now = now;
69
- this.connectTimeout = timeout(definition.connectTimeoutMs, 10_000);
70
- this.callTimeout = timeout(definition.callTimeoutMs, 60_000);
71
- this.repairTimeout = timeout(definition.repair?.timeoutMs, 60_000);
72
- }
73
- observe() {
74
- return { service: this.definition.name, ...integrationHealthView(this.health, this.now()),
75
- phase: !this.busy && this.pending.size > 0 ? "draining" : this.phase, pending: this.pending.size > 0,
76
- connection_open: this.handle !== undefined, cached: true, disposed: this.disposed, cleanup: this.cleanup,
77
- repair_command: this.definition.repair ? `/${this.definition.repair.command}` : null };
78
- }
79
- private track<T>(promise: Promise<T>): Promise<T> {
80
- this.pending.add(promise);
81
- void promise.then(() => this.pending.delete(promise), () => this.pending.delete(promise));
82
- return promise;
83
- }
84
- private retire(handle: IntegrationConnection | undefined): Promise<void> {
85
- if (!handle || this.closed.has(handle)) return Promise.resolve();
86
- this.closed.add(handle); if (this.handle === handle) this.handle = undefined;
87
- this.cleanup = "pending";
88
- return this.track(Promise.resolve().then(() => handle.close()).then(() => { this.cleanup = "complete"; }, () => { this.cleanup = "failed"; }));
89
- }
90
- private async bounded<T>(work: (signal: AbortSignal) => Promise<T>, ms: number, signal: AbortSignal | undefined,
91
- late: (value: T | undefined) => void | Promise<void> = () => {}): Promise<Bounded<T>> {
92
- const controller = new AbortController();
93
- const combined = AbortSignal.any([controller.signal, this.lifetime.signal, ...(signal ? [signal] : [])]);
94
- if (combined.aborted) return { kind: "cancelled" };
95
- let timedOut = false;
96
- let onAbort = () => {};
97
- const stopped = new Promise<Bounded<T>>(resolve => {
98
- onAbort = () => resolve({ kind: timedOut ? "timeout" : "cancelled" });
99
- combined.addEventListener("abort", onAbort, { once: true });
100
- });
101
- const timer = setTimeout(() => { timedOut = true; controller.abort(); }, ms);
102
- const actual = this.track(Promise.resolve().then(async (): Promise<Settled<T>> => {
103
- if (combined.aborted) return { kind: "threw" as const };
104
- return { kind: "value", value: await work(combined) };
105
- }).catch(() => ({ kind: "threw" as const })));
106
- const result = await Promise.race([actual, stopped]);
107
- clearTimeout(timer); combined.removeEventListener("abort", onAbort);
108
- if (result.kind === "timeout" || result.kind === "cancelled") {
109
- // Keep ownership through late cleanup. A timeout never authorizes a replacement attempt.
110
- this.track(actual.then(value => late(value.kind === "value" ? value.value : undefined)).catch(() => { this.cleanup = "failed"; }));
111
- }
112
- return result;
113
- }
114
- private failure(code: IntegrationFailureCode | "cancelled" | "busy" | "closed", phase: "connect" | "call" | "admission" | "repair", retryAfterMs?: number, record = true): ToolResult {
115
- if (record && code !== "busy" && code !== "closed" && code !== "cancelled") this.health = failedIntegrationHealth(this.health, code, this.now(), retryAfterMs);
116
- const observation = this.observe(), started = phase === "call" || phase === "repair";
117
- const content = `Integration ${this.definition.name}: ${code} during ${phase}.`;
118
- const diagnostic = failureDiagnostic(code === "cancelled" ? "cancelled" : code === "timeout" ? "timeout" : started ? "unknown_effect"
119
- : code === "offline" || code === "rate_limited" || code === "busy" ? "transient" : "execution_failed", content,
120
- started ? { execution: "started", effects: "possible" } : NOT_STARTED);
121
- diagnostic.next = started ? "The operation may have applied. Inspect remote state before any new invocation; this harness never replays it automatically."
122
- : observation.cleanup === "failed" ? "Cleanup could not be confirmed. Ask the user to inspect the service; do not start another connection in this registry."
123
- : observation.pending ? "The previous adapter is still running or cleaning up. Wait for it to settle; do not start another connection."
124
- : code === "auth_required" || code === "protocol_error" ? observation.repair_command
125
- ? `Ask the user to run ${observation.repair_command} to repair the integration. A tool call cannot perform this user action.`
126
- : "Ask the user to repair the service configuration or credentials and reload the extension."
127
- : "Inspect the cached service observation and wait for retry_after_ms before a new explicit call. No automatic retry was started.";
128
- return { ok: false, content: content + "\nIntegration observation: " + JSON.stringify(observation), diagnostic,
129
- outcome: executionOutcome(code === "cancelled" ? "cancelled" : code === "timeout" ? "timeout" : "tool_failed"),
130
- data: { kind: "integration_observation", version: 1, phase, health: observation } };
131
- }
132
- async execute(name: string, id: string, args: Record<string, unknown>, signal: AbortSignal | undefined,
133
- update: (result: ExtensionToolResult) => void, ctx: ExtensionContext): Promise<Run> {
134
- if (this.disposed) return { ok: false, result: this.failure("closed", "admission") };
135
- if (signal?.aborted) return { ok: false, result: this.failure("cancelled", "admission") };
136
- if (this.busy || this.pending.size || this.cleanup === "failed") return { ok: false, result: this.failure("busy", "admission") };
137
- const cached = integrationHealthView(this.health, this.now());
138
- if (cached.state === "auth_required" || cached.state === "protocol_error" || (cached.retry_after_ms ?? 0) > 0) {
139
- // A cache hit is an observation, not another failed attempt or an exponential-delay extension.
140
- return { ok: false, result: this.failure(cached.error ?? "unknown", "admission", undefined, false) };
141
- }
142
- this.busy = true;
143
- try {
144
- if (!this.handle) {
145
- this.phase = "connect";
146
- const opened = await this.bounded(s => this.definition.connect(s, { ...ctx, signal: s }), this.connectTimeout, signal,
147
- value => connection(value) ? this.retire(value) : undefined);
148
- if (opened.kind !== "value") return { ok: false, result: this.failure(opened.kind === "threw" ? "unknown" : opened.kind, "connect") };
149
- if (fault(opened.value)) return { ok: false, result: this.failure(opened.value.code, "connect", opened.value.retryAfterMs) };
150
- if (!connection(opened.value)) return { ok: false, result: this.failure("protocol_error", "connect") };
151
- this.handle = opened.value;
152
- }
153
- if (signal?.aborted || this.disposed) { void this.retire(this.handle); return { ok: false, result: this.failure("cancelled", "admission") }; }
154
- this.phase = "call";
155
- const handle = this.handle;
156
- let accepting = true;
157
- const response = await this.bounded(s => handle.execute(name, id, args, s,
158
- value => { if (accepting && !s.aborted && !this.disposed) update(value); }, { ...ctx, signal: s }), this.callTimeout, signal, () => this.retire(handle));
159
- accepting = false;
160
- if (response.kind === "value" && (signal?.aborted || this.disposed)) {
161
- void this.retire(handle); return { ok: false, result: this.failure("cancelled", "call") };
162
- }
163
- if (response.kind !== "value") {
164
- if (response.kind === "threw") void this.retire(handle);
165
- else this.handle = undefined;
166
- return { ok: false, result: this.failure(response.kind === "threw" ? "unknown" : response.kind, "call") };
167
- }
168
- if (fault(response.value) || !toolResult(response.value)) {
169
- void this.retire(handle);
170
- return { ok: false, result: this.failure(fault(response.value) ? response.value.code : "protocol_error", "call",
171
- fault(response.value) ? response.value.retryAfterMs : undefined) };
172
- }
173
- this.health = readyIntegrationHealth(this.now());
174
- return { ok: true, result: response.value, observation: this.observe() };
175
- } finally { this.busy = false; this.phase = "idle"; }
176
- }
177
- async repair(ctx: ExtensionContext): Promise<ToolResult> {
178
- const repair = this.definition.repair;
179
- if (!repair || this.disposed) return this.failure("closed", "admission");
180
- if (!ctx.isIdle() || this.busy || this.pending.size) return this.failure("busy", "admission");
181
- this.busy = true; this.phase = "repair";
182
- try {
183
- const closed = await this.bounded(() => this.retire(this.handle), this.connectTimeout, ctx.signal);
184
- if (closed.kind !== "value" || this.cleanup === "failed") return this.failure(closed.kind === "cancelled" ? "cancelled" : "unknown", "admission");
185
- const result = await this.bounded(s => repair.run(s, { ...ctx, signal: s }), this.repairTimeout, ctx.signal);
186
- if (result.kind !== "value") return this.failure(result.kind === "threw" ? "unknown" : result.kind, "repair");
187
- if (fault(result.value)) return this.failure(result.value.code, "repair", result.value.retryAfterMs);
188
- this.health = initialIntegrationHealth();
189
- const health = this.observe();
190
- return { ok: true, content: `Integration ${this.definition.name}: user repair completed; the next tool call must reconnect.`,
191
- data: { kind: "integration_observation", version: 1, phase: "repair", health } };
192
- } finally { this.busy = false; this.phase = "idle"; }
193
- }
194
- async dispose(): Promise<void> {
195
- this.disposed = true; this.lifetime.abort();
196
- const handle = this.handle;
197
- if (this.pending.size) this.track(Promise.allSettled([...this.pending]).then(() => this.retire(handle)));
198
- else void this.retire(handle);
199
- // No promise can kill trusted in-process code. Pending ownership remains visible until it settles.
200
- }
201
- async releaseForReload(): Promise<boolean> {
202
- if (!this.canReleaseForReload()) return false;
203
- this.disposed = true; this.lifetime.abort();
204
- const closing = this.retire(this.handle);
205
- let timer: ReturnType<typeof setTimeout> | undefined;
206
- await Promise.race([closing, new Promise<void>(resolve => { timer = setTimeout(resolve, this.connectTimeout); })]);
207
- clearTimeout(timer);
208
- return this.pending.size === 0 && this.observe().cleanup !== "failed";
209
- }
210
- canReleaseForReload(): boolean { return !this.busy && this.pending.size === 0 && this.cleanup !== "failed"; }
211
- /** Re-enable explicit future invocations after an abandoned staged reload. No operation is replayed. */
212
- resumeAfterReload(): boolean {
213
- if (!this.canReleaseForReload()) return false;
214
- this.disposed = false; this.lifetime = new AbortController(); return true;
215
- }
216
- }