@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,85 +0,0 @@
1
- /** A hub owns live sessions, not transcript mirrors. Durable listing stays a store projection. */
2
- import type { SessionControl } from "./session-control.ts";
3
- import { basename } from "node:path";
4
-
5
- // Assumed live-owner bound includes admitted opens, so concurrent requests cannot exceed it.
6
- export const MAX_LIVE_SESSIONS = 32;
7
-
8
- export interface SessionHubHost {
9
- list(): Promise<unknown>;
10
- open(kind: "new" | "resume" | "fork", params: Record<string, unknown>): Promise<SessionControl>;
11
- dispose(control: SessionControl): Promise<void>;
12
- }
13
- export class SessionHub {
14
- private readonly controls = new Map<string, SessionControl>();
15
- private readonly opening = new Set<Promise<SessionControl>>();
16
- private readonly resuming = new Map<string, Promise<SessionControl>>();
17
- private readonly closing = new Map<string, Promise<void>>();
18
- private reserved = 0;
19
- private closed = false;
20
- private disposal: Promise<void> | undefined;
21
- private readonly host: SessionHubHost;
22
- readonly initial: SessionControl;
23
- constructor(initial: SessionControl, host: SessionHubHost) {
24
- this.initial = initial; this.host = host; this.controls.set(initial.threadId, initial);
25
- }
26
- get(threadId: string): SessionControl {
27
- if (this.closed || this.closing.has(threadId)) throw Error("session hub or thread is closing");
28
- const control = this.controls.get(threadId);
29
- if (!control) throw Error("unknown or closed threadId; resume the session first");
30
- return control;
31
- }
32
- async list(): Promise<unknown> { if (this.closed) throw Error("session hub is closing"); return this.host.list(); }
33
- open(kind: "new" | "resume" | "fork", params: Record<string, unknown>): Promise<SessionControl> {
34
- if (this.closed) return Promise.reject(Error("session hub is closing; no new session was admitted"));
35
- const captured = structuredClone(params);
36
- // Coalesce resume only. Two explicit new/fork requests create two distinct sessions.
37
- const key = kind === "resume" ? JSON.stringify(captured) : "";
38
- const pending = key ? this.resuming.get(key) : undefined;
39
- if (pending) return pending;
40
- const requested = captured["session"];
41
- const known = kind === "resume" && [...this.controls.values()].some(control => !this.closing.has(control.threadId)
42
- && [control.threadId, control.session.config.logDir, basename(control.session.config.logDir)].includes(String(requested)));
43
- if (!known && this.controls.size + this.reserved >= MAX_LIVE_SESSIONS) return Promise.reject(Error("live session limit reached; close a session first"));
44
- if (!known) this.reserved++;
45
- const operation = (async () => {
46
- try {
47
- const control = await this.host.open(kind, captured), existing = this.controls.get(control.threadId);
48
- if (this.closing.has(control.threadId) || !existing && this.controls.size >= MAX_LIVE_SESSIONS) {
49
- if (existing !== control) await this.host.dispose(control);
50
- throw Error("thread is closing or the live session limit was reached");
51
- }
52
- if (existing && existing !== control) { await this.host.dispose(control); if (this.closed) throw Error("session hub closed during opening"); return existing; }
53
- this.controls.set(control.threadId, control);
54
- if (this.closed) throw Error("session hub closed during opening; the admitted owner will be drained");
55
- return control;
56
- } finally { if (!known) this.reserved--; }
57
- })();
58
- this.opening.add(operation); if (key) this.resuming.set(key, operation);
59
- // Observe rejected opens without changing the caller's promise or leaking unhandled cleanup rejections.
60
- void operation.finally(() => { this.opening.delete(operation); if (key) this.resuming.delete(key); }).catch(() => {});
61
- return operation;
62
- }
63
- close(threadId: string): Promise<void> {
64
- const pending = this.closing.get(threadId); if (pending) return pending;
65
- const control = this.controls.get(threadId); if (!control) return Promise.reject(Error("unknown or closed threadId"));
66
- const operation = (async () => {
67
- if (control.activeTurnId) control.interrupt(control.activeTurnId);
68
- await control.whenIdle().catch(() => {});
69
- await this.host.dispose(control); this.controls.delete(threadId);
70
- })();
71
- this.closing.set(threadId, operation);
72
- void operation.then(() => { if (this.closing.get(threadId) === operation) this.closing.delete(threadId); }, () => {});
73
- return operation;
74
- }
75
- dispose(): Promise<void> {
76
- if (this.disposal) return this.disposal;
77
- this.closed = true;
78
- return this.disposal = (async () => {
79
- // Every kind is tracked; late publication belongs to this same draining generation.
80
- await Promise.allSettled([...this.opening]);
81
- const results = await Promise.allSettled([...this.controls.keys()].map(id => this.close(id)));
82
- if (results.some(result => result.status === "rejected")) throw Error("session cleanup could not be confirmed");
83
- })();
84
- }
85
- }
@@ -1,80 +0,0 @@
1
- /**
2
- * Abandons one attempt whose stream has gone silent.
3
- *
4
- * Without this a stream the network silently dropped waits until the user
5
- * presses Escape (Horizyn 2026-10-12: a request sat 5m39s before the socket
6
- * error surfaced). OpenAI's Codex CLI (`stream_idle_timeout_ms`, default
7
- * 300000) and Claude Code (300s) both cut a stream that delivers nothing for
8
- * five minutes against the same backends, so a live stream -- extended
9
- * thinking included -- is not that quiet. The limit is theirs, not measured
10
- * here (feat-stream-idle-watchdog).
11
- *
12
- * The clock starts when the request is sent, so it also bounds the wait for
13
- * the first byte, and restarts on anything received: body bytes where the
14
- * adapter owns fetch, stream events (pings included) where an SDK does.
15
- */
16
-
17
- export const STREAM_IDLE_MS = 300_000;
18
-
19
- /** retry.ts recognises this by name, so it need not import an adapter. */
20
- export class StreamIdleError extends Error {
21
- constructor(ms: number) {
22
- super(`no data from the provider for ${Math.round(ms / 1000)}s; the stream was abandoned`);
23
- this.name = "StreamIdleError";
24
- }
25
- }
26
-
27
- export class IdleWatch {
28
- /** Pass this, not the caller's, to the request: it also aborts on silence. */
29
- readonly signal: AbortSignal;
30
- private readonly controller = new AbortController();
31
- private timer: ReturnType<typeof setTimeout> | undefined;
32
- private expired = false;
33
- private readonly outer: AbortSignal | undefined;
34
- private readonly ms: number;
35
-
36
- constructor(outer: AbortSignal | undefined, ms: number = STREAM_IDLE_MS) {
37
- this.outer = outer; this.ms = ms;
38
- this.signal = outer === undefined ? this.controller.signal : AbortSignal.any([outer, this.controller.signal]);
39
- this.touch();
40
- }
41
-
42
- touch(): void {
43
- if (this.expired) return;
44
- clearTimeout(this.timer);
45
- this.timer = setTimeout(() => {
46
- this.expired = true;
47
- this.controller.abort(new StreamIdleError(this.ms));
48
- }, this.ms);
49
- }
50
-
51
- /** A body that restarts the clock on every chunk. */
52
- body(stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
53
- return stream.pipeThrough(new TransformStream<Uint8Array, Uint8Array>({
54
- transform: (chunk, sink) => { this.touch(); sink.enqueue(chunk); },
55
- }));
56
- }
57
-
58
- /** An event stream that restarts the clock on every event. */
59
- async *events<T>(source: AsyncIterable<T>): AsyncGenerator<T> {
60
- for await (const event of source) { this.touch(); yield event; }
61
- }
62
-
63
- /**
64
- * Whatever the abort surfaced as -- AbortError, a closed body, the SDK's
65
- * own wrapper -- silence becomes StreamIdleError. The caller's own abort
66
- * stays an abort: Escape must not start another attempt.
67
- */
68
- rethrow(error: unknown): never {
69
- if (this.expired && this.outer?.aborted !== true) throw new StreamIdleError(this.ms);
70
- throw error;
71
- }
72
-
73
- dispose(): void { clearTimeout(this.timer); }
74
- }
75
-
76
- /** Runs one attempt under its own idle watch, cleared however it ends. */
77
- export async function watchIdle<T>(outer: AbortSignal | undefined, ms: number | undefined, run: (idle: IdleWatch) => Promise<T>): Promise<T> {
78
- const idle = new IdleWatch(outer, ms);
79
- try { return await run(idle); } catch (error) { return idle.rethrow(error); } finally { idle.dispose(); }
80
- }
@@ -1,137 +0,0 @@
1
- /** Pi A / D8 / D11: direct and nested execution share one admitted, durable call contract. */
2
- import type { AssistantBlock, ToolInvocationEvent } from "../core/events.ts";
3
- import { failureDiagnostic, NOT_STARTED, UNKNOWN_EFFECT, ToolFault } from "../core/tool-failure.ts";
4
- import { executionOutcome } from "../core/outcome.ts";
5
- import { finishToolResult, nativeCommandContext, NATIVE_COMMAND_NAME, normalizeToolInput, type ToolContext, type ToolResult, type NestedInvocationOptions } from "./tools.ts";
6
- import { ToolQueue } from "./tool-queue.ts";
7
-
8
- export type BrokerCall = Extract<AssistantBlock, { type: "tool_use" }>;
9
- type Mode = "parallel" | "exclusive";
10
- /** Inherits the existing extension limit, now shared by the entire invocation tree. */
11
- export const MAX_NESTED_CALLS = 32;
12
- export const MAX_NESTED_DEPTH = 32;
13
- /** Assumed per-call composition budget; direct large edits retain their existing interface. */
14
- export const MAX_NESTED_ARGUMENT_BYTES = 64 * 1024;
15
- /** Assumed log metadata bound; complete structured results are independent artifacts. */
16
- export const MAX_INLINE_RESULT_DATA_BYTES = 64 * 1024;
17
-
18
- export interface BrokerHost<Result extends ToolResult> {
19
- parallelLimit: number;
20
- callId(): string;
21
- mode(call: BrokerCall): Mode;
22
- invocation(call: BrokerCall, ctx: ToolContext, parent: ToolInvocationEvent | undefined): Promise<ToolInvocationEvent>;
23
- /** Receives normalized input and must still apply admission, including Session-owned tools. */
24
- prepare(call: BrokerCall, ctx: ToolContext): Promise<Result | (() => Promise<Result>)>;
25
- /** Session annotations and branch receipts are accepted before their result becomes evidence. */
26
- settle(result: Result): Promise<void>;
27
- result(invocation: ToolInvocationEvent, result: Result): Promise<void>;
28
- }
29
- export interface BrokerReceipt<Result extends ToolResult> {
30
- value: Result;
31
- invocation: ToolInvocationEvent;
32
- settle(): Promise<void>;
33
- commit(): Promise<void>;
34
- }
35
- export type BrokerExecution<Result extends ToolResult> = (() => Promise<BrokerReceipt<Result>>) & { close(): Promise<void> };
36
- type Budget = { calls: number };
37
- type Parent = { invocation: ToolInvocationEvent; budget: Budget; depth: number };
38
-
39
- /** Scheduling belongs to a live scope; immutable requests/results belong to the host log. */
40
- export class ToolBroker<Result extends ToolResult = ToolResult> {
41
- private readonly host: BrokerHost<Result>;
42
- constructor(host: BrokerHost<Result>) { this.host = host; }
43
-
44
- async prepare(call: BrokerCall, context: ToolContext, parent?: Parent): Promise<BrokerExecution<Result>> {
45
- let actual = call;
46
- let invalid: ToolResult | undefined;
47
- try {
48
- // Extension argument preparation cannot rewrite the already-recorded model
49
- // request. Hooks receive the exact immutable value archived for admission.
50
- actual = { ...call, input: freezeInput(structuredClone(normalizeToolInput(context, call.name, structuredClone(call.input)))) };
51
- }
52
- catch (error) { invalid = error instanceof ToolFault ? finishToolResult({ ok: false, content: error.message, diagnostic: error.diagnostic }) : refused(`Invalid ${call.name} arguments: ${String(error)}`); }
53
- const invocation = await this.host.invocation(actual, context, parent?.invocation);
54
- if (parent && invocation.input === undefined) invalid = refused("nested arguments exceed the inline composition budget or are not JSON; pass a bounded request or use the direct tool");
55
- const budget = parent?.budget ?? { calls: 0 }, depth = (parent?.depth ?? 0) + 1;
56
- const children = new ToolQueue(this.host.parallelLimit);
57
- const pending = new Set<Promise<ToolResult>>();
58
- const failures: string[] = [];
59
- let incomplete = false;
60
- const mode = this.host.mode(actual);
61
- let open = true;
62
- const invoke = (name: string, input: unknown, options?: NestedInvocationOptions, native = false): Promise<ToolResult> => {
63
- if (!open) return Promise.resolve(refused("tool scope is closed; no invocation started"));
64
- if (mode !== "exclusive") return Promise.resolve(refused("only an exclusive composing tool may request nested execution"));
65
- if (depth >= MAX_NESTED_DEPTH || budget.calls >= MAX_NESTED_CALLS) return Promise.resolve(refused("nested tool budget exhausted; no invocation started"));
66
- // Capture at submission, before an asynchronous queue can observe a
67
- // caller's later mutation of the same object.
68
- try { input = structuredClone(input); }
69
- catch { return Promise.resolve(refused("nested arguments must be serializable; no invocation started")); }
70
- budget.calls++;
71
- const nested: BrokerCall = { type: "tool_use", call_id: this.host.callId(), name, input };
72
- const signals = [ctx.signal, options?.signal].filter((signal): signal is AbortSignal => signal !== undefined);
73
- const nestedContext: ToolContext = native ? nativeCommandContext(ctx, options?.signal) : { ...ctx,
74
- ...(signals.length ? { signal: AbortSignal.any(signals) } : {}) };
75
- const result = children.submit(this.host.mode(nested), async () => {
76
- const execute = await this.prepare(nested, nestedContext, { invocation, budget, depth });
77
- return async () => {
78
- const receipt = await execute();
79
- await receipt.settle(); await receipt.commit();
80
- if (!receipt.value.ok) failures.push(`nested call ${receipt.invocation.call_id} failed`);
81
- return { ...receipt.value, invocation: { call_id: receipt.invocation.call_id, event: receipt.invocation.id } };
82
- };
83
- });
84
- pending.add(result);
85
- // The broker owns the lifetime even when extension code drops the promise.
86
- void result.then(() => pending.delete(result), error => { incomplete = true; failures.push(`nested execution or persistence failed: ${String(error)}`); pending.delete(result); });
87
- return result;
88
- };
89
- const ctx: ToolContext = { ...context, callId: call.call_id,
90
- ...(parent ? { parentCallId: parent.invocation.call_id } : {}),
91
- invoke: (name, input, options) => invoke(name, input, options),
92
- runOwnedCommand: execute => children.submit("exclusive", async () => execute),
93
- drainNested: () => children.whenIdle(),
94
- invokeHost: (input, signal) => invoke(NATIVE_COMMAND_NAME, input, signal ? { signal } : {}, true) };
95
- let closing: Promise<void> | undefined;
96
- const close = () => {
97
- open = false;
98
- return closing ??= (async () => { await children.close(); await Promise.allSettled(pending); })();
99
- };
100
- let prepared: ToolResult | (() => Promise<Result>);
101
- try { prepared = invalid ?? await this.host.prepare(actual, ctx); }
102
- catch (error) { prepared = finishToolResult({ ok: false, content: String(error), diagnostic: failureDiagnostic("unknown_effect", String(error), UNKNOWN_EFFECT) }); }
103
- return Object.assign(async () => {
104
- let value: Result;
105
- try { value = (typeof prepared === "function" ? await prepared() : prepared) as Result; }
106
- catch (error) {
107
- value = finishToolResult({ ok: false, content: `tool ${call.name} failed: ${String(error)}`,
108
- diagnostic: failureDiagnostic("unknown_effect", String(error), UNKNOWN_EFFECT) }) as Result;
109
- } finally {
110
- await close();
111
- }
112
- if (failures.length) value = { ...value, warnings: [...(value.warnings ?? []),
113
- failureDiagnostic("unknown_effect", `${failures.length} nested failures; inspect the original invocation receipts. ${failures[0]}`, UNKNOWN_EFFECT)] };
114
- if (incomplete) value = { ...value, ok: false, outcome: executionOutcome("unknown"),
115
- diagnostic: failureDiagnostic("unknown_effect", "nested execution or persistence did not produce a complete receipt", UNKNOWN_EFFECT) };
116
- let settled: Promise<void> | undefined, committed: Promise<void> | undefined;
117
- const settle = () => settled ??= this.host.settle(value);
118
- return { value, invocation, settle,
119
- commit: () => committed ??= settle().then(() => this.host.result(invocation, value)) };
120
- }, { close });
121
- }
122
- }
123
-
124
- function refused(message: string): ToolResult {
125
- return finishToolResult({ ok: false, content: message, diagnostic: failureDiagnostic("invalid_input", message, NOT_STARTED) });
126
- }
127
-
128
- function freezeInput(value: unknown, ancestors = new Set<unknown>()): unknown {
129
- if (value !== null && typeof value === "object") {
130
- if (ancestors.has(value)) throw new Error("cyclic tool arguments are not JSON");
131
- ancestors.add(value);
132
- for (const child of Object.values(value)) freezeInput(child, ancestors);
133
- ancestors.delete(value);
134
- Object.freeze(value);
135
- }
136
- return value;
137
- }
@@ -1,58 +0,0 @@
1
- /** R11: a harness-owned discovery entry shares normal Session admission and result persistence. */
2
- import { createHash } from "node:crypto";
3
- import { discoverTools, MAX_DISCOVERY_QUERY, MAX_DISCOVERY_RESULTS, type DeclarationRequest } from "../core/tool-discovery.ts";
4
- import { decideWithSessionRules } from "../core/permissions.ts";
5
- import { failureDiagnostic, NOT_STARTED } from "../core/tool-failure.ts";
6
- import type { ToolSchema } from "./provider.ts";
7
- import type { ToolContext, ToolDefinition, ToolResult } from "./tools.ts";
8
-
9
- export const DISCOVERY_TOOL_NAME = "tool_search";
10
- export const DISCOVERY_TOOL: ToolDefinition = {
11
- name: DISCOVERY_TOOL_NAME,
12
- description: "Find optional registered tools by name or purpose. Reads the local catalog without connecting to services or executing tools. Returns purpose, important input types and declaration status. Matching permitted tools become declared only after the complete tool batch, for the next model request; then call them with their actual schema. Discovery does not grant execution permission or prove service health. Example: {\"query\":\"issue tracker\",\"limit\":3}.",
13
- input_schema: { type: "object", properties: {
14
- query: { type: "string", minLength: 1, maxLength: MAX_DISCOVERY_QUERY },
15
- limit: { type: "integer", minimum: 1, maximum: MAX_DISCOVERY_RESULTS, description: "Maximum matches to request; defaults to 5." },
16
- }, required: ["query"] },
17
- access: "read", execution: "exclusive", branch: "none",
18
- run: async () => ({ ok: false, content: "Tool discovery requires a live Session checkpoint." }),
19
- };
20
-
21
- /** Bind a request to the observed contract; an edited/replaced registration needs another lookup. */
22
- export function discoveryFingerprint(tool: ToolSchema): string {
23
- return createHash("sha256").update(JSON.stringify({ name: tool.name, description: tool.description, input_schema: tool.input_schema })).digest("hex");
24
- }
25
- export function discoveryPermitted(ctx: ToolContext, name: string): boolean {
26
- if (ctx.workflowMode === "plan" && ctx.extensions?.get(name)?.access !== "read") return false;
27
- return decideWithSessionRules(ctx.rules ?? [], ctx.sessionRules ?? [], { tool: name }) !== "deny";
28
- }
29
- /** A directory entry without its matching live implementation is unavailable, regardless of history. */
30
- export function currentDeferredTools(ctx: ToolContext): readonly ToolSchema[] {
31
- return (ctx.deferredTools ?? []).filter(tool => {
32
- const definition = ctx.extensions?.get(tool.name);
33
- return definition !== undefined && discoveryFingerprint(definition) === discoveryFingerprint(tool);
34
- });
35
- }
36
- export function searchDeferredTools(ctx: ToolContext, input: Record<string, unknown>, declared: readonly string[]): ToolResult {
37
- const catalog = currentDeferredTools(ctx);
38
- let matches;
39
- try { matches = discoverTools(catalog, input["query"] as string, input["limit"] as number | undefined); }
40
- catch {
41
- const content = "Discovery requires a nonblank query of at most 256 characters and an integer limit from 1 to 8.";
42
- return { ok: false, content, diagnostic: failureDiagnostic("invalid_input", content, NOT_STARTED) };
43
- }
44
- const requested: DeclarationRequest[] = [];
45
- const result = matches.map(match => {
46
- const tool = catalog.find(item => item.name === match.name)!;
47
- const permitted = discoveryPermitted(ctx, match.name);
48
- const status = !permitted ? "permission_denied" : declared.includes(match.name) ? "declared" : "requested_for_next_request";
49
- if (status === "requested_for_next_request") requested.push({ name: tool.name, fingerprint: discoveryFingerprint(tool) });
50
- const health = ctx.extensions?.get(match.name)?.integrationHealth?.();
51
- return { ...match, declaration: status, service_health: health?.state ?? "not_checked", ...(health ? { service_observation: health } : {}) };
52
- });
53
- return { ok: true, content: JSON.stringify({ matches: result, note: result.length
54
- ? "Only declared tools can run. Requested schemas become available after the current batch at a checkpoint; execution permissions still apply."
55
- : "No matching tools in the currently permitted discovery catalog. No service was contacted." }),
56
- // Keep only small, original requests inline so cold recovery never depends on a generated summary.
57
- data: { kind: "tool_discovery", version: 1, requested: requested.map(item => ({ ...item })) } };
58
- }
@@ -1,77 +0,0 @@
1
- /** Pi A / O.5: ordered admission, bounded readers and an exclusive write barrier. */
2
- import { schedulingWindow } from "../core/scheduling.ts";
3
-
4
- type Mode = "parallel" | "exclusive";
5
- type Job = { mode: Mode; prepare(): Promise<() => Promise<unknown>>; resolve(value: unknown): void; reject(error: unknown): void };
6
-
7
- /**
8
- * A composing call owns an exclusive slot in its parent queue and gives its
9
- * children a fresh queue. Re-entering the held parent slot would deadlock.
10
- * Only exclusive tools may compose; concurrent readers therefore cannot each
11
- * multiply the session's parallel limit with another queue.
12
- */
13
- export class ToolQueue {
14
- private readonly limit: number;
15
- private readonly pending: Job[] = [];
16
- private active = 0;
17
- private exclusive = false;
18
- private pumping = false;
19
- private readonly idle = new Set<() => void>();
20
- private closed = false;
21
-
22
- constructor(limit: number) {
23
- schedulingWindow([], 0, limit);
24
- this.limit = limit;
25
- }
26
-
27
- submit<T>(mode: Mode, prepare: () => Promise<() => Promise<T>>): Promise<T> {
28
- if (this.closed) return Promise.reject(new Error("tool scope is closed; no invocation started"));
29
- const result = new Promise<T>((resolve, reject) => {
30
- this.pending.push({ mode, prepare, resolve: value => resolve(value as T), reject });
31
- });
32
- this.kick();
33
- return result;
34
- }
35
-
36
- private kick(): void { if (!this.pumping) { this.pumping = true; void this.pump(); } }
37
-
38
- private async pump(): Promise<void> {
39
- try {
40
- while (this.pending.length > 0) {
41
- const next = this.pending[0]!;
42
- const occupied: Mode[] = Array.from({ length: this.active }, () => this.exclusive ? "exclusive" : "parallel");
43
- if (schedulingWindow([...occupied, next.mode], 0, this.limit) <= this.active) break;
44
- const job = this.pending.shift()!;
45
- this.active++; this.exclusive = job.mode === "exclusive";
46
- const finished = () => { this.active--; if (job.mode === "exclusive") this.exclusive = false; this.kick(); };
47
- try {
48
- const execute = await job.prepare();
49
- void Promise.resolve().then(execute).then(job.resolve, job.reject).finally(finished);
50
- } catch (error) { job.reject(error); this.active--; this.exclusive = false; }
51
- }
52
- } finally {
53
- this.pumping = false;
54
- if (this.active === 0 && this.pending.length === 0) { for (const resolve of this.idle) resolve(); this.idle.clear(); }
55
- }
56
- }
57
-
58
- /** A generator can retain its slot across admission/display yields without executing early. */
59
- acquire(mode: Mode): Promise<() => void> {
60
- return new Promise((resolve, reject) => {
61
- let release!: () => void;
62
- const done = new Promise<void>(finish => { release = finish; });
63
- void this.submit(mode, async () => async () => { resolve(release); await done; }).catch(reject);
64
- });
65
- }
66
-
67
- whenIdle(): Promise<void> {
68
- if (!this.pumping && this.active === 0 && this.pending.length === 0) return Promise.resolve();
69
- return new Promise(resolve => this.idle.add(resolve));
70
- }
71
-
72
- /** Accepted jobs drain even when their caller forgot to await their promises. */
73
- async close(): Promise<void> {
74
- this.closed = true;
75
- await this.whenIdle();
76
- }
77
- }