@visual-z/xeno 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +24 -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 -2066
  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 -1713
  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 -3001
  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/canvas.ts +0 -96
  312. package/src/tui/ui/colors.ts +0 -70
  313. package/src/tui/ui/composer.ts +0 -200
  314. package/src/tui/ui/control-panel.ts +0 -25
  315. package/src/tui/ui/control-sidebar.ts +0 -49
  316. package/src/tui/ui/dialog.ts +0 -398
  317. package/src/tui/ui/draft.ts +0 -23
  318. package/src/tui/ui/duration.ts +0 -16
  319. package/src/tui/ui/edge.ts +0 -186
  320. package/src/tui/ui/image-size.ts +0 -44
  321. package/src/tui/ui/ink.ts +0 -210
  322. package/src/tui/ui/items.ts +0 -42
  323. package/src/tui/ui/keys.ts +0 -40
  324. package/src/tui/ui/layout.ts +0 -74
  325. package/src/tui/ui/list-model.ts +0 -86
  326. package/src/tui/ui/list-view.ts +0 -172
  327. package/src/tui/ui/mascot.ts +0 -67
  328. package/src/tui/ui/messages.ts +0 -518
  329. package/src/tui/ui/motion.ts +0 -28
  330. package/src/tui/ui/paths.ts +0 -10
  331. package/src/tui/ui/pictures.ts +0 -108
  332. package/src/tui/ui/screen.ts +0 -548
  333. package/src/tui/ui/status.ts +0 -403
  334. package/src/tui/ui/styled.ts +0 -85
  335. package/src/tui/ui/tool-card.ts +0 -150
  336. package/src/tui/width.ts +0 -567
  337. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  338. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  339. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  340. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  341. package/src/vendor/pi-rich/html.ts +0 -51
  342. package/src/vendor/pi-rich/image-convert.ts +0 -49
  343. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  344. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  345. package/src/vendor/pi-rich/image-resize.ts +0 -123
  346. package/src/vendor/pi-rich/mime.ts +0 -116
  347. package/src/vendor/pi-rich/photon.ts +0 -139
  348. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,148 +0,0 @@
1
- /**
2
- * Pickers that run before a session exists (startup session choice, project
3
- * trust, `xeno config`). Each borrows the terminal for one dialog on its own
4
- * renderer and gives it back; none of them creates a durable session.
5
- * Same dialog component as the session UI, so keys and look match.
6
- */
7
- import { BoxRenderable, createCliRenderer, type CliRenderer } from "@opentui/core";
8
- import { defaultKeymap, type Keymap } from "../../core/keymap.ts";
9
- import { nextResourceOverride } from "../../core/resource-selection.ts";
10
- import { selectSessions } from "../../core/session-browser.ts";
11
- import { builtinTheme, type Theme } from "../../core/theme.ts";
12
- import { projectTrust } from "../../store/trust.ts";
13
- import { discoverResources, setResourceOverride, type ResourceOptions } from "../../store/resource-index.ts";
14
- import { loadUiResources } from "../../store/resources.ts";
15
- import { registerSessionRoot, scanSessionRoots, sessionRoots } from "../../store/session-browser.ts";
16
- import { sessionsDir, type ListedSession } from "../../store/sessions.ts";
17
- import { fuzzyMatch } from "../../tui/fuzzy.ts";
18
- import { DialogHost, type DialogSpec } from "../../tui/ui/dialog.ts";
19
- import { keyPress } from "../../tui/ui/keys.ts";
20
- import type { ListItem } from "../../tui/ui/list-model.ts";
21
- import { Canvas } from "../../tui/ui/canvas.ts";
22
- import { edgeLine } from "../../tui/ui/edge.ts";
23
- import { applyTrustChoice, type TrustChoice } from "../project-trust.ts";
24
- import { uiColors } from "./theme.ts";
25
- import { trustItems } from "./app.ts";
26
-
27
- interface Standalone { renderer: CliRenderer; dialogs: DialogHost; close(): void }
28
-
29
- /**
30
- * A one-dialog screen: a heading row on top and the same input box the
31
- * session uses, pinned to the bottom, with the dialog's title and keys on its
32
- * top edge (refactor-tui-dock-layout). Nothing is painted behind it.
33
- */
34
- async function open(theme: Theme, keymap: Keymap, heading: string, handoff?: () => void, renderer?: CliRenderer): Promise<Standalone> {
35
- handoff?.();
36
- const r = renderer ?? await createCliRenderer({ screenMode: "alternate-screen", exitOnCtrlC: false, exitSignals: [], consoleMode: "disabled", openConsoleOnError: false,
37
- enableMouseMovement: false, gatherStats: false, memorySnapshotInterval: 0, maxFps: 60 });
38
- // The terminal's palette is not known yet on this short-lived renderer; the
39
- // theme's hint picks safe slots. The background is still never painted.
40
- const colors = uiColors(theme);
41
- r.setBackgroundColor(colors.background);
42
- const root = new BoxRenderable(r, { id: "standalone", flexDirection: "column", width: "100%", height: "100%" });
43
- const top = new Canvas(r, { id: "standalone-heading", height: 1, flexShrink: 0, marginLeft: 1, marginRight: 1, colors });
44
- top.setRows([[{ text: "xeno", role: "text", bold: true }, { text: ` ${heading}`, role: "muted" }]]);
45
- const spacer = new BoxRenderable(r, { id: "standalone-space", flexGrow: 1 });
46
- const box = new BoxRenderable(r, { id: "standalone-box", flexDirection: "column", flexShrink: 0, marginLeft: 1, marginRight: 1 });
47
- const topEdge = new Canvas(r, { id: "standalone-top", height: 1, flexShrink: 0, colors });
48
- const body = new BoxRenderable(r, { id: "standalone-body", flexDirection: "column", flexShrink: 0, border: ["left", "right"], borderStyle: "rounded", borderColor: colors.edge, paddingLeft: 1, paddingRight: 1 });
49
- const slot = new BoxRenderable(r, { id: "dialog", flexDirection: "column", flexShrink: 0, visible: false });
50
- const bottomEdge = new Canvas(r, { id: "standalone-bottom", height: 1, flexShrink: 0, colors });
51
- body.add(slot); box.add(topEdge); box.add(body); box.add(bottomEdge);
52
- root.add(top); root.add(spacer); root.add(box);
53
- r.root.add(root);
54
- const dialogs = new DialogHost(r, colors, () => keymap.byAction, slot, () => Math.max(1, r.height - 4));
55
- const paint = () => {
56
- const width = Math.max(2, r.width - 2);
57
- topEdge.setRows([edgeLine({ width, corners: ["╭", "╮"], left: dialogs.title ? [{ segs: [{ text: dialogs.title, role: "text", bold: true }], priority: 0, min: 8 }] : [],
58
- right: dialogs.edgeHint ? [{ segs: [{ text: dialogs.edgeHint, role: "muted" }], priority: 3, min: 10 }] : [] })]);
59
- bottomEdge.setRows([edgeLine({ width, corners: ["╰", "╯"] })]);
60
- };
61
- dialogs.onChrome = paint;
62
- r.on("resize", () => { dialogs.layout(); paint(); });
63
- paint();
64
- r.keyInput.on("keypress", event => { if (dialogs.key(keyPress(event))) event.preventDefault(); });
65
- r.keyInput.on("paste", event => { if (dialogs.paste(new TextDecoder().decode(event.bytes)) === false) event.preventDefault(); });
66
- return { renderer: r, dialogs, close: () => { dialogs.dispose(); if (!renderer) r.destroy(); } };
67
- }
68
-
69
- /** Show one dialog; resolves with the picked value, or null when it is closed without a choice. */
70
- function ask(host: Standalone, spec: (resolve: (value: string | null) => void) => DialogSpec): Promise<string | null> {
71
- return new Promise(resolve => {
72
- let settled = false;
73
- const done = (value: string | null) => { if (!settled) { settled = true; resolve(value); } };
74
- // A pick empties the stack before its callback runs; let the callback answer first.
75
- host.dialogs.onClosed = () => queueMicrotask(() => done(null));
76
- host.dialogs.push(spec(done));
77
- });
78
- }
79
-
80
- export async function pickProjectTrust(workspace: string, handoff?: () => void, renderer?: CliRenderer): Promise<boolean> {
81
- const decision = await projectTrust(workspace);
82
- const ui = await loadUiResources({ workspace, trusted: false });
83
- const host = await open(ui.theme, ui.keymap, "project trust", handoff, renderer);
84
- try {
85
- const choice = await ask(host, resolve => ({ kind: "list", title: "Trust this project?", search: false, items: trustItems(workspace),
86
- intro: [workspace, "This project has its own .xeno configuration (settings, skills, extensions). Tool permissions are separate."],
87
- ...(decision.saved ? { selected: decision.saved.trusted ? decision.saved.path === workspace ? "here" : "parent" : "never" } : {}),
88
- onPick: item => resolve(item.value) }));
89
- return choice === null ? false : applyTrustChoice(workspace, choice as TrustChoice);
90
- } finally { host.close(); }
91
- }
92
-
93
- type Session = Extract<ListedSession, { ok: true }>;
94
-
95
- export async function pickStartupSession(opts: { workspace: string; sessionDir?: string; say: (line: string) => void; keymap?: Keymap; theme?: Theme },
96
- handoff?: () => void, renderer?: CliRenderer): Promise<string | null> {
97
- const root = { path: sessionsDir(opts.workspace, opts.sessionDir), workspace: opts.workspace };
98
- await registerSessionRoot(root).catch(error => opts.say(String(error)));
99
- const host = await open(opts.theme ?? builtinTheme("dark"), opts.keymap ?? defaultKeymap(), "resume a session", handoff, renderer);
100
- const scan = new AbortController();
101
- let rows: Session[] = [];
102
- let scope: "current" | "all" = "current";
103
- const items = (query = ""): ListItem[] => selectSessions(rows, query, "threaded", false, (q, text) => { const score = fuzzyMatch(text, q); return { matches: score !== null, score: score ?? Infinity }; })
104
- .map(row => ({ value: row.path, label: " ".repeat(Math.min(8, row.depth)) + (row.depth ? "↳ " : "") + (row.name?.trim() || row.text?.split("\n").find(Boolean)?.slice(0, 90) || row.id),
105
- description: [row.updated_at?.slice(0, 16).replace("T", " ") ?? "", row.forked_at ? `forked ${row.forked_at.slice(0, 16).replace("T", " ")}` : "",
106
- row.model ?? "", scope === "all" ? row.workspace ?? "" : ""].filter(Boolean).join(" · ") }));
107
- try {
108
- let spec: DialogSpec & { kind: "list" } | undefined;
109
- const load = async () => {
110
- rows = [];
111
- for await (const batch of scanSessionRoots(scope === "current" ? [root] : await sessionRoots(root), scan.signal)) {
112
- // A session left without a word is not something to resume (fix-skip-empty-sessions).
113
- rows.push(...batch.sessions.filter((row): row is Session => row.ok && row.conversation !== false));
114
- if (spec && host.dialogs.top === spec) host.dialogs.list?.setItems(items(host.dialogs.list.query));
115
- }
116
- };
117
- const picked = ask(host, resolve => (spec = { kind: "list", title: "Resume · this folder", items: [], rows: 16, empty: "Loading sessions…", placeholder: "search sessions",
118
- actions: [{ key: "tab", label: "all workspaces", run: async () => { scope = scope === "current" ? "all" : "current"; spec!.title = `Resume · ${scope === "current" ? "this folder" : "all workspaces"}`; await load(); } }],
119
- onPick: item => resolve(item.value) }));
120
- await load();
121
- if (!rows.length && scope === "current" && (await sessionRoots(root)).length === 1) { opts.say("No sessions to resume"); host.dialogs.clear(); return null; }
122
- return await picked;
123
- } finally { scan.abort(); host.close(); }
124
- }
125
-
126
- export async function runResourceConfig(options: ResourceOptions, handoff?: () => void, renderer?: CliRenderer): Promise<void> {
127
- let index = await discoverResources(options);
128
- const ui = await loadUiResources(options, index);
129
- const host = await open(ui.theme, ui.keymap, "resources", handoff, renderer);
130
- let scope: "user" | "project" = options.local ? "project" : "user";
131
- const items = (): ListItem[] => index.entries.map(entry => ({ value: `${entry.kind}:${entry.path}`, label: `${entry.enabled ? "◉" : "○"} ${entry.name}`, section: entry.kind,
132
- description: `${entry.scope}${entry.trusted ? "" : " · untrusted"}${scope === "project" ? ` · ${entry.override}` : ""}`, keywords: entry.path }));
133
- try {
134
- let spec: DialogSpec & { kind: "list" } | undefined;
135
- await ask(host, () => (spec = { kind: "list", title: `Resources · ${scope === "user" ? "global" : "project"}`, items: items(), rows: 16, empty: "No resources found",
136
- hint: "space toggle · tab scope · esc close",
137
- actions: [
138
- { key: "tab", label: "scope", run: () => { scope = scope === "user" ? "project" : "user"; spec!.title = `Resources · ${scope === "user" ? "global" : "project"}`; host.dialogs.list?.setItems(items()); } },
139
- { key: "space", label: "toggle", run: async item => {
140
- const entry = index.entries.find(row => `${row.kind}:${row.path}` === item?.value);
141
- if (!entry) return;
142
- await setResourceOverride(index, entry, scope, scope === "project" ? nextResourceOverride(entry.override) : entry.inherited ? "skip" : "load");
143
- index = await discoverResources(options);
144
- host.dialogs.list?.setItems(items(), item?.value);
145
- } },
146
- ] }));
147
- } finally { host.close(); }
148
- }
@@ -1,68 +0,0 @@
1
- /**
2
- * Log → status facts for the sidebar and top bar (docs/tasks/refactor-tui-dock-layout.md). Pure.
3
- *
4
- * Everything here is a projection of what the log already records: file
5
- * counts come from the logged input of edit calls that succeeded, handoff
6
- * entries from the derived preview (D8), background work from the processes
7
- * and branches the log observed. Nothing reads model narration (D11).
8
- */
9
- import type { Handoff } from "../../core/handoff.ts";
10
- import type { TranscriptItem } from "../../core/transcript.ts";
11
- import { diffCounts } from "../../tui/ui/tool-card.ts";
12
- import type { FileStatus, StatusModel } from "../../tui/ui/status.ts";
13
-
14
- /** Files the session touched, most recent first. Counts only for files changed solely by edits (a write's prior content is unknown). */
15
- export function fileChanges(items: readonly TranscriptItem[]): FileStatus[] {
16
- const files = new Map<string, { edited: boolean; added: number; removed: number; rewritten: boolean; at: number }>();
17
- items.forEach((item, at) => {
18
- if (item.kind !== "tool" || !item.result?.ok) return;
19
- const input = item.input !== null && typeof item.input === "object" ? item.input as Record<string, unknown> : {};
20
- const path = typeof input["path"] === "string" ? input["path"] : undefined;
21
- if (!path || !["read", "edit", "write"].includes(item.name)) return;
22
- const entry = files.get(path) ?? { edited: false, added: 0, removed: 0, rewritten: false, at };
23
- entry.at = at;
24
- if (item.name === "edit") { const counts = diffCounts(item.input); entry.edited = true; entry.added += counts.added; entry.removed += counts.removed; }
25
- if (item.name === "write") { entry.edited = true; entry.rewritten = true; }
26
- files.set(path, entry);
27
- });
28
- return [...files.entries()].sort((a, b) => b[1].at - a[1].at)
29
- .map(([path, entry]) => ({ path, edited: entry.edited, added: entry.rewritten ? 0 : entry.added, removed: entry.rewritten ? 0 : entry.removed }));
30
- }
31
-
32
- export function firstPrompt(items: readonly TranscriptItem[]): string | null {
33
- const user = items.find(item => item.kind === "user" && item.text.trim());
34
- return user?.kind === "user" ? user.text.trim() : null;
35
- }
36
-
37
- type Derived = Pick<StatusModel, "handoff" | "tasks" | "background">;
38
-
39
- /** The parts of a handoff preview the status surfaces show. Each list keeps its most recent entries last. */
40
- export function handoffStatus(handoff: Handoff): Derived {
41
- const background = [
42
- ...handoff.branches.filter(branch => branch.background && branch.resolution === null && !branch.handoff_ref).map(branch => `branch ${branch.node} · ${branch.purpose}`),
43
- ...(handoff.processes ?? []).filter(process => process.observed === "running").map(process => process.command ?? `process ${process.session}`),
44
- ];
45
- const tasks = (handoff.state.work_items ?? []).filter(item => item.status === "active" || item.status === "blocked")
46
- .map(item => ({ what: item.what, status: item.status === "blocked" ? "blocked" as const : "active" as const }));
47
- return {
48
- handoff: {
49
- verified: handoff.state.completed.map(item => item.what),
50
- inFlight: handoff.state.in_flight.map(item => item.what),
51
- unverified: handoff.state.unverified_claims.map(item => item.what),
52
- },
53
- tasks, background,
54
- };
55
- }
56
-
57
- const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
58
-
59
- /** "20:13" today, "Oct 5 20:13" on another day, in the given local offset. */
60
- export function sessionClock(iso: string, now: Date, offsetMinutes = -now.getTimezoneOffset()): string | undefined {
61
- const at = Date.parse(iso);
62
- if (!Number.isFinite(at)) return undefined;
63
- const local = (ms: number) => new Date(ms + offsetMinutes * 60_000);
64
- const t = local(at), n = local(now.getTime());
65
- const hhmm = `${String(t.getUTCHours()).padStart(2, "0")}:${String(t.getUTCMinutes()).padStart(2, "0")}`;
66
- const sameDay = t.getUTCFullYear() === n.getUTCFullYear() && t.getUTCMonth() === n.getUTCMonth() && t.getUTCDate() === n.getUTCDate();
67
- return sameDay ? hhmm : `${MONTHS[t.getUTCMonth()]} ${t.getUTCDate()} ${hhmm}`;
68
- }
@@ -1,91 +0,0 @@
1
- /**
2
- * What command and turn code may ask of the interactive UI. shell.ts
3
- * depends on this interface only; apps/ui/app.ts implements it. A null Ui
4
- * means print/json/line mode, where every branch has a text fallback.
5
- */
6
- import type { ImageRef, UserInput } from "../../core/images.ts";
7
- import type { Phase, QueueKind } from "../../core/queue.ts";
8
- import type { PermissionRequest } from "../../core/permissions.ts";
9
- import type { ApprovalChoice } from "../../runtime/tools.ts";
10
- import type { LoopEvent } from "../../runtime/loop.ts";
11
- import type { ExtensionUI } from "../../runtime/extension-ui.ts";
12
- import type { ClipboardContent } from "../../runtime/clipboard.ts";
13
- import type { CallbackServer, OAuthCredential, OAuthInteraction } from "../../runtime/oauth.ts";
14
-
15
- export interface TurnSurface {
16
- pushDelta(kind: "text" | "thinking", text: string): void;
17
- discardStream(): void;
18
- commitStream(): void;
19
- syncTranscript(): Promise<void>;
20
- pushProgress?(progress: NonNullable<LoopEvent["progress"]>): void;
21
- pullQueued?(phase: Phase): Promise<UserInput | null>;
22
- observeEvent?(event: LoopEvent): void;
23
- /** A plain-text notice in the message stream (the interactive app); line mode prints instead. */
24
- notice?(text: string, tone?: "info" | "success" | "warning" | "error"): void;
25
- }
26
-
27
- export interface Ui extends TurnSurface {
28
- readonly extensionUi: ExtensionUI;
29
- readonly signal: AbortSignal | undefined;
30
- readonly queueLength: number;
31
- sessionName: string | null;
32
- oauthEffects: OAuthEffects;
33
- clipboardWriter: (text: string) => boolean | Promise<boolean>;
34
- clipboardReader: () => Promise<ClipboardContent>;
35
-
36
- /** Notices and documents in the message stream. */
37
- notice(text: string, tone?: "info" | "success" | "warning" | "error"): void;
38
- showDocument(title: string, text: string): void;
39
- checkForUpdate(): void;
40
- toast(text: string, tone?: "info" | "success" | "warning" | "error"): void;
41
- redraw(): void;
42
-
43
- /** A masked single-line form; resolves undefined when cancelled. The value never reaches a renderable. */
44
- askSecret(title: string, label: string, hint: string): Promise<string | undefined>;
45
- confirm(title: string, message: string): Promise<boolean>;
46
- /** A cancellable "waiting" dialog (OAuth browser / device code). Returns a closer. */
47
- waiting(title: string, lines: readonly string[], onCancel: () => void, pasteLabel?: string, onPaste?: (value: string) => void): () => void;
48
-
49
- openLogin(method?: "oauth" | "api_key"): void;
50
- openLogout(): void;
51
- openModel(): void;
52
- openScopedModels(): void;
53
- openThinking(): void;
54
- openSettings(field?: string): void;
55
- openResume(): Promise<void>;
56
- openFork(): Promise<void>;
57
- openTree(): Promise<void>;
58
- openResources(): Promise<void>;
59
- openControl(tab?: "goal" | "progress" | "context"): Promise<void>;
60
- /** S.5: draft criteria for an objective, ask the user which to accept, then work toward them. */
61
- startGoal(objective: string): void;
62
- openTrust(): Promise<void>;
63
- openAttachments(): void;
64
-
65
- restoreDraft(text: string, images?: readonly ImageRef[]): void;
66
- refreshResources(): Promise<void>;
67
- applyConfiguration(): void;
68
- followTheme(): void;
69
- beginBash(command: string): void;
70
- pushBash(chunk: Uint8Array): void;
71
- ask(request: PermissionRequest): Promise<ApprovalChoice>;
72
-
73
- runExtensionAction(action: () => void | Promise<void>): void;
74
- runExtensionCommand(name: string, args: string): void;
75
- runSessionTransition(action: () => Promise<void>): void;
76
- deliverExtension(text: string, kind: QueueKind): void;
77
- bindExtensionRegistry(): void;
78
- requestShutdown(): void;
79
- /** Abort the running turn (Esc semantics) without quitting. */
80
- interrupt(): void;
81
- /** Plain-text snapshot of the current frame (/debug). */
82
- snapshot(): { width: number; height: number; lines: string[] };
83
- }
84
-
85
- /** Injectable OAuth side effects (browser, callback server, device flow, fetch). */
86
- export interface OAuthEffects {
87
- fetch: typeof globalThis.fetch;
88
- openBrowser(url: string): void;
89
- callback(options: { expectedState: string; host: string; port: number; path: string }): Promise<CallbackServer>;
90
- device(interaction: OAuthInteraction): Promise<OAuthCredential>;
91
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * core Theme + the terminal's reported palette → the view's colours.
3
- *
4
- * Built-in appearances (dark, light, system/auto) take every colour from the
5
- * terminal (tui/ui/ink.ts); their name only says which way to guess when the
6
- * terminal does not answer the palette query. A custom theme file is an
7
- * explicit choice and is drawn with its own colours; it paints a background
8
- * only when it sets one (refactor-tui-terminal-colors).
9
- */
10
- import type { TerminalColors } from "@opentui/core";
11
- import { isBuiltinTheme, type Theme } from "../../core/theme.ts";
12
- import { colorsFromInks, rgba, type UiColors } from "../../tui/ui/colors.ts";
13
- import { isHex, terminalInks, type InkSet, type InkSpec, type TerminalPalette } from "../../tui/ui/ink.ts";
14
-
15
- /** OpenTUI's detection result → the 16-slot palette ink.ts expects, or null when incomplete. */
16
- export function terminalPalette(colors: TerminalColors | null | undefined): TerminalPalette | null {
17
- if (!colors) return null;
18
- const slots = colors.palette.slice(0, 16).map(hex => normalizeHex(hex));
19
- const foreground = normalizeHex(colors.defaultForeground), background = normalizeHex(colors.defaultBackground);
20
- if (!foreground || !background || slots.length < 16 || slots.some(slot => !slot)) return null;
21
- const hb = normalizeHex(colors.highlightBackground), hf = normalizeHex(colors.highlightForeground);
22
- return { foreground, background, slots: slots as string[], ...(hb && hf ? { highlight: { background: hb, foreground: hf } } : {}) };
23
- }
24
-
25
- function normalizeHex(value: string | null | undefined): string | undefined {
26
- if (!value) return undefined;
27
- const hex = value.startsWith("#") ? value : `#${value}`;
28
- return isHex(hex) ? hex.toLowerCase() : undefined;
29
- }
30
-
31
- function hint(theme: Theme): "dark" | "light" { return theme.name === "light" ? "light" : "dark"; }
32
-
33
- /** "38;2;r;g;b" / "48;2;r;g;b" → "#rrggbb"; anything else is undefined. */
34
- export function sgrHex(value: string | undefined): string | undefined {
35
- const match = value ? /(?:^|;)[34]8;2;(\d+);(\d+);(\d+)/.exec(value) : null;
36
- if (!match) return undefined;
37
- return "#" + [match[1], match[2], match[3]].map(n => Math.max(0, Math.min(255, Number(n))).toString(16).padStart(2, "0")).join("");
38
- }
39
-
40
- export function uiColors(theme: Theme, palette: TerminalPalette | null = null): UiColors {
41
- const terminal = colorsFromInks(terminalInks(palette, hint(theme)));
42
- if (isBuiltinTheme(theme)) return terminal;
43
- // A theme file's colours are the user's explicit choice; tokens it leaves
44
- // out (or sets to the terminal default) still come from the terminal.
45
- const pick = (...tokens: string[]) => { const hex = tokens.map(token => sgrHex(theme.colors[token])).find(Boolean); return hex ? rgba(hex) : undefined; };
46
- return {
47
- background: pick("background") ?? terminal.background,
48
- text: pick("text") ?? terminal.text,
49
- muted: pick("muted", "toolOutput") ?? terminal.muted,
50
- edge: pick("border", "borderMuted") ?? terminal.edge,
51
- line: pick("borderMuted", "border") ?? terminal.line,
52
- accent: pick("accent", "mdLink") ?? terminal.accent,
53
- success: pick("success", "toolDiffAdded") ?? terminal.success,
54
- error: pick("error", "toolDiffRemoved") ?? terminal.error,
55
- warning: pick("warning") ?? terminal.warning,
56
- reasoning: pick("thinkingText") ?? terminal.reasoning,
57
- code: pick("mdCode") ?? terminal.code,
58
- selectionBg: pick("selectedBg") ?? terminal.selectionBg,
59
- selectionFg: terminal.selectionFg,
60
- };
61
- }
62
-
63
- /**
64
- * SGR tokens for the ANSI renderers (tool details, diffs, code highlighting)
65
- * from the same terminal-derived inks, so text drawn through ANSI strings
66
- * matches the view. Custom themes keep their own tokens.
67
- */
68
- export function styleTokens(theme: Theme, palette: TerminalPalette | null): Readonly<Record<string, string>> {
69
- if (!isBuiltinTheme(theme)) return theme.colors;
70
- const inks: InkSet = terminalInks(palette, hint(theme));
71
- const sgr = (spec: InkSpec): string => spec.kind === "default" ? "" : spec.kind === "index"
72
- ? String(spec.index < 8 ? 30 + spec.index : 90 + spec.index - 8)
73
- : `38;2;${[1, 3, 5].map(i => Number.parseInt(spec.hex.slice(i, i + 2), 16)).join(";")}`;
74
- const muted = sgr(inks.muted);
75
- return {
76
- ...theme.colors,
77
- text: "", prompt: "", toolTitle: "",
78
- muted, dim: muted, status: muted, toolOutput: muted, thinkingOff: muted, syntaxComment: muted,
79
- error: sgr(inks.error), toolDiffRemoved: sgr(inks.error), toolDiffAdded: sgr(inks.success), success: sgr(inks.success),
80
- warning: sgr(inks.warning), syntaxNumber: sgr(inks.warning),
81
- accent: sgr(inks.accent), mdHeading: sgr(inks.accent), mdLink: sgr(inks.accent), syntaxFunction: sgr(inks.accent),
82
- mdCode: sgr(inks.code), syntaxString: sgr(inks.success),
83
- thinkingText: sgr(inks.reasoning), syntaxKeyword: sgr(inks.reasoning),
84
- border: sgr(inks.line), borderMuted: sgr(inks.line),
85
- };
86
- }
@@ -1,112 +0,0 @@
1
- /** Shared lifecycle for automatic hints and explicit checks; policy remains in core (D16). */
2
- import { dirname } from "node:path";
3
- import { installArgv, installKind, installResult, manualUpdateResult, updateDisabled, updatePlan, type UpdateInput } from "../core/update-check.ts";
4
- import { packageLocation, readUpdateCache, writeUpdateCache } from "../store/update-cache.ts";
5
- import { fetchLatestVersion, UPDATE_TIMEOUT_MS } from "../runtime/update-check.ts";
6
- import { INSTALL_TIMEOUT_MS, runInstall } from "../runtime/self-update.ts";
7
- import { XENO_VERSION } from "../runtime/version.ts";
8
-
9
- export interface UpdateOptions {
10
- readonly offline?: boolean;
11
- readonly enabled?: boolean | undefined;
12
- /** Install a newer release after the check: `/update` does, `xeno update --check` never does. */
13
- readonly install?: boolean;
14
- }
15
-
16
- export type ManualUpdateActivity =
17
- | { readonly state: "checking"; readonly current: string; readonly timeoutMs: number }
18
- | { readonly state: "installing"; readonly version: string; readonly timeoutMs: number; readonly startedAt: number; readonly detail?: string }
19
- | { readonly state: "idle" };
20
-
21
- type ManualUpdateReporter = (text: string, tone?: "info" | "error") => void;
22
-
23
- export class UpdateChecks {
24
- private readonly controller = new AbortController();
25
- private pending: Promise<string | null> | undefined;
26
- private manualTask: Promise<number> | undefined;
27
- /** The release this process already installed; the running code stays old until a restart. */
28
- private installed: string | undefined;
29
-
30
- async prepare(options: UpdateOptions, manual = false) {
31
- const location = packageLocation();
32
- const input: UpdateInput = { current: XENO_VERSION, now: Date.now(), cache: await readUpdateCache(), env: process.env,
33
- offline: options.offline === true, enabled: options.enabled, install: installKind(location.root, location.git), manual };
34
- return { input, plan: updatePlan(input), disabled: updateDisabled(input) };
35
- }
36
-
37
- /** One request and one cache write even when a manual check joins startup's request. */
38
- refresh(input: UpdateInput): Promise<string | null> {
39
- if (this.pending) return this.pending;
40
- const pending = (async () => {
41
- const latest = await fetchLatestVersion(input.env["npm_config_registry"], { signal: this.controller.signal });
42
- if (!this.controller.signal.aborted) {
43
- const known = latest ?? input.cache?.latest;
44
- await writeUpdateCache({ checkedAt: Date.now(), ...(known ? { latest: known } : {}) });
45
- }
46
- return latest;
47
- })().finally(() => { if (this.pending === pending) this.pending = undefined; });
48
- this.pending = pending;
49
- return pending;
50
- }
51
-
52
- manual(options: UpdateOptions, say: ManualUpdateReporter, onActivity?: (activity: ManualUpdateActivity) => void): Promise<number> {
53
- if (this.manualTask) return this.manualTask;
54
- const task = this.runManual(options, say, onActivity).finally(() => { if (this.manualTask === task) this.manualTask = undefined; });
55
- this.manualTask = task;
56
- return task;
57
- }
58
-
59
- private async runManual(options: UpdateOptions, say: ManualUpdateReporter, onActivity?: (activity: ManualUpdateActivity) => void): Promise<number> {
60
- if (options.install && this.installed) {
61
- say(`xeno ${this.installed} is already installed. Restart xeno to use it.`);
62
- return 0;
63
- }
64
- const { input, plan, disabled } = await this.prepare(options, true);
65
- if (this.controller.signal.aborted) return 130;
66
- if (!plan.check) {
67
- say(input.install === "checkout" ? `Source checkout: xeno ${input.current}. Use git pull to update this checkout.`
68
- : `Update check skipped: ${disabled ?? "check unavailable"} (xeno ${input.current}).`);
69
- return input.install === "checkout" ? 0 : 1;
70
- }
71
- if (onActivity) onActivity({ state: "checking", current: input.current, timeoutMs: UPDATE_TIMEOUT_MS });
72
- else say(`Checking for xeno updates (current ${input.current})...`);
73
- try {
74
- const latest = await this.refresh(input);
75
- if (this.controller.signal.aborted) return 130;
76
- const result = manualUpdateResult(latest, input.current, input.install);
77
- const argv = options.install && result.status === "available" && latest ? installArgv(input.install, latest) : undefined;
78
- if (!argv || !latest) {
79
- say(result.text, result.status === "failed" ? "error" : "info");
80
- return result.status === "failed" ? 1 : 0;
81
- }
82
- return await this.install(argv, latest, input.current, say, onActivity);
83
- } finally {
84
- if (onActivity) onActivity({ state: "idle" });
85
- }
86
- }
87
-
88
- private async install(argv: readonly string[], latest: string, current: string, say: ManualUpdateReporter, onActivity?: (activity: ManualUpdateActivity) => void): Promise<number> {
89
- const startedAt = Date.now();
90
- if (onActivity) onActivity({ state: "installing", version: latest, timeoutMs: INSTALL_TIMEOUT_MS, startedAt });
91
- else say(`Installing xeno ${latest} with ${argv.join(" ")}...`);
92
- const outcome = await runInstall(argv, {
93
- signal: this.controller.signal, timeoutMs: INSTALL_TIMEOUT_MS, writableDir: dirname(packageLocation().root),
94
- ...(onActivity ? { onLine: (detail: string) => onActivity({ state: "installing", version: latest, timeoutMs: INSTALL_TIMEOUT_MS, startedAt, detail }) } : {}),
95
- });
96
- if (outcome.kind === "cancelled") return 130;
97
- const result = installResult(outcome, argv, current, latest);
98
- if (result.ok) this.installed = latest;
99
- say(result.text, result.ok ? "info" : "error");
100
- return result.ok ? 0 : 1;
101
- }
102
-
103
- cancel(): void { this.controller.abort(); }
104
- }
105
-
106
- /** A standalone command has no UI lifecycle to own cancellation. */
107
- export async function runManualUpdate(options: UpdateOptions, say: (text: string) => void): Promise<number> {
108
- const checks = new UpdateChecks(), cancel = () => checks.cancel();
109
- process.once("SIGINT", cancel); process.once("SIGTERM", cancel);
110
- try { return await checks.manual(options, say); }
111
- finally { checks.cancel(); process.off("SIGINT", cancel); process.off("SIGTERM", cancel); }
112
- }
@@ -1,58 +0,0 @@
1
- /** D5/D6: the app changes capabilities only through the live session's checkpoint owner. */
2
- import { isWorkflowMode, workflowInstructions, type WorkflowMode } from "../core/workflow-mode.ts";
3
- import { sessionControl } from "../runtime/session-control.ts";
4
- import { sessionToolCatalog, type App } from "./startup.ts";
5
-
6
- export type WorkflowApp = Pick<App, "args" | "session" | "extensions" | "system">;
7
-
8
- export async function setAppWorkflow(app: WorkflowApp, mode: WorkflowMode): Promise<boolean> {
9
- if (!isWorkflowMode(mode)) throw Error("workflow mode must be build or plan");
10
- const session = app.session, previousMode = session.config.tools.workflowMode;
11
- const current = previousMode ?? app.args.workflow ?? "build";
12
- return sessionControl(session).withIdleChange(async () => {
13
- const previousProfile = session.config.agentProfile, name = previousProfile?.name ?? app.args.agent;
14
- const nextProfile = name ? { name, workflowExplicit: true } : undefined;
15
- const args = structuredClone(app.args), previousScope = session.config.workflowToolProfile;
16
- const promote = mode === "plan" && args.tools === undefined && !args.noTools && args.toolProfile === undefined;
17
- if (promote) args.toolProfile = "foundation";
18
- const nextScope = args.toolProfile === "foundation" ? "foundation" as const : undefined;
19
- const metadataChanged = JSON.stringify(previousProfile) !== JSON.stringify(nextProfile) || previousScope !== nextScope;
20
- if (mode === current && !metadataChanged && !promote) {
21
- app.args.workflow = mode; app.args.cliFlags = [...new Set([...app.args.cliFlags ?? [], "--workflow"])];
22
- return false;
23
- }
24
- const oldInstruction = workflowInstructions(current), nextInstruction = workflowInstructions(mode);
25
- // Replace only a complete harness paragraph: user context and all other frozen bytes survive.
26
- const paragraphs = session.config.frozen.system.split("\n\n");
27
- const index = paragraphs.indexOf(oldInstruction);
28
- if (index >= 0) paragraphs[index] = nextInstruction; else paragraphs.push(nextInstruction);
29
- const system = mode === current ? session.config.frozen.system : paragraphs.join("\n\n");
30
- const catalog = sessionToolCatalog({ ...args, workflow: mode }, app.extensions, session.config.frozen.tools);
31
- // The persisted checkpoint owns the admission mode as well as declarations, including cold restore.
32
- session.config.tools.workflowMode = mode;
33
- if (nextProfile) session.config.agentProfile = nextProfile;
34
- if (nextScope) session.config.workflowToolProfile = nextScope; else delete session.config.workflowToolProfile;
35
- try {
36
- const changed = await session.reloadResources(mode === current && !promote ? session.config.frozen : { ...session.config.frozen, system, tools: catalog.schemas }, undefined, undefined, metadataChanged);
37
- if (mode !== current || promote) session.config.tools.deferredTools = catalog.deferred;
38
- args.workflow = mode; args.cliFlags = [...new Set([...args.cliFlags ?? [], "--workflow"])]; app.args = args; app.system = system;
39
- return changed;
40
- } catch (error) {
41
- if (previousMode === undefined) delete session.config.tools.workflowMode;
42
- else session.config.tools.workflowMode = previousMode;
43
- if (previousProfile === undefined) delete session.config.agentProfile;
44
- else session.config.agentProfile = previousProfile;
45
- if (previousScope === undefined) delete session.config.workflowToolProfile;
46
- else session.config.workflowToolProfile = previousScope;
47
- throw error;
48
- }
49
- });
50
- }
51
-
52
- export function bindAppWorkflow(app: WorkflowApp): void {
53
- const session = app.session;
54
- sessionControl(session).configureWorkflow = async mode => {
55
- if (app.session !== session) throw Error("this session is no longer attached to the app");
56
- await setAppWorkflow(app, mode);
57
- };
58
- }