@visual-z/xeno 0.2.1 → 0.4.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 (282) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +126 -34
  3. package/docs/app-server.md +84 -0
  4. package/docs/install.md +51 -13
  5. package/docs/providers.md +24 -3
  6. package/package.json +7 -5
  7. package/src/apps/app-server.ts +28 -0
  8. package/src/apps/approve.ts +3 -3
  9. package/src/apps/cli.ts +25 -10
  10. package/src/apps/commands.ts +45 -8
  11. package/src/apps/completions.ts +78 -22
  12. package/src/apps/control-transport.ts +89 -0
  13. package/src/apps/image-draft.ts +46 -0
  14. package/src/apps/model-catalog.ts +2 -1
  15. package/src/apps/project-trust.ts +2 -42
  16. package/src/apps/provider-connect.ts +346 -0
  17. package/src/apps/render.ts +22 -47
  18. package/src/apps/resources.ts +5 -2
  19. package/src/apps/shell.ts +333 -1542
  20. package/src/apps/startup-input.ts +45 -9
  21. package/src/apps/startup.ts +149 -94
  22. package/src/apps/ui/app.ts +1553 -0
  23. package/src/apps/ui/control-model.ts +26 -0
  24. package/src/apps/ui/extension-host.ts +194 -0
  25. package/src/apps/ui/image-cache.ts +78 -0
  26. package/src/apps/ui/palette.ts +88 -0
  27. package/src/apps/ui/project.ts +127 -0
  28. package/src/apps/ui/standalone.ts +146 -0
  29. package/src/apps/ui/status-model.ts +68 -0
  30. package/src/apps/ui/surface.ts +86 -0
  31. package/src/apps/ui/theme.ts +86 -0
  32. package/src/core/acceptance.ts +107 -0
  33. package/src/core/action-defaults.ts +47 -295
  34. package/src/core/benchmark.ts +2 -2
  35. package/src/core/branch-receipt.ts +24 -0
  36. package/src/core/checkpoints.ts +64 -0
  37. package/src/core/churn.ts +2 -0
  38. package/src/core/command-artifacts.ts +94 -0
  39. package/src/core/command-contract.ts +35 -0
  40. package/src/core/completion-action.ts +20 -0
  41. package/src/core/completion-review.ts +37 -21
  42. package/src/core/context-detail.ts +37 -0
  43. package/src/core/context-policy.ts +74 -1
  44. package/src/core/continuation.ts +17 -2
  45. package/src/core/control-protocol.ts +62 -0
  46. package/src/core/control-view.ts +42 -0
  47. package/src/core/derive.ts +29 -3
  48. package/src/core/draft-images.ts +56 -0
  49. package/src/core/environment.ts +145 -0
  50. package/src/core/evaluation-coverage.ts +74 -0
  51. package/src/core/event-validation.ts +81 -6
  52. package/src/core/events.ts +99 -3
  53. package/src/core/evidence-links.ts +7 -0
  54. package/src/core/evidence.ts +13 -3
  55. package/src/core/execution.ts +8 -2
  56. package/src/core/experiment.ts +133 -0
  57. package/src/core/export.ts +9 -0
  58. package/src/core/failure-recovery.ts +173 -0
  59. package/src/core/file-precondition.ts +72 -0
  60. package/src/core/handoff.ts +65 -4
  61. package/src/core/harness-diagnostics.ts +206 -0
  62. package/src/core/history-query.ts +38 -2
  63. package/src/core/history.ts +38 -1
  64. package/src/core/integration-health.ts +34 -0
  65. package/src/core/keymap.ts +8 -20
  66. package/src/core/model-picker.ts +64 -0
  67. package/src/core/native-eval.ts +25 -0
  68. package/src/core/note-guidance.ts +34 -0
  69. package/src/core/oauth-fields.ts +44 -0
  70. package/src/core/observation-view.ts +49 -8
  71. package/src/core/permissions.ts +24 -2
  72. package/src/core/process-receipt.ts +105 -0
  73. package/src/core/process-state.ts +37 -0
  74. package/src/core/program-status.ts +31 -0
  75. package/src/core/project.ts +70 -19
  76. package/src/core/prompt-policy.ts +37 -0
  77. package/src/core/query-eval-contract.ts +14 -0
  78. package/src/core/read-observation.ts +82 -0
  79. package/src/core/recovery-corpus.ts +17 -0
  80. package/src/core/recovery-evaluation.ts +60 -0
  81. package/src/core/recovery-sources.ts +74 -0
  82. package/src/core/request-progress.ts +87 -0
  83. package/src/core/request-state.ts +114 -0
  84. package/src/core/runtime-target.ts +94 -0
  85. package/src/core/search-observation.ts +97 -0
  86. package/src/core/session-tree.ts +147 -0
  87. package/src/core/settings.ts +33 -10
  88. package/src/core/skills.ts +44 -0
  89. package/src/core/sql-query.ts +211 -0
  90. package/src/core/theme.ts +36 -58
  91. package/src/core/tool-contract.ts +51 -2
  92. package/src/core/tool-discovery.ts +87 -0
  93. package/src/core/tool-failure.ts +60 -0
  94. package/src/core/tool-input.ts +6 -0
  95. package/src/core/tool-mode-protocol.ts +25 -0
  96. package/src/core/transcript.ts +19 -4
  97. package/src/core/trial-lifecycle.ts +14 -0
  98. package/src/core/tui-mode.ts +29 -0
  99. package/src/core/update-check.ts +97 -0
  100. package/src/core/version.ts +55 -0
  101. package/src/core/work-state.ts +49 -3
  102. package/src/core/working-set.ts +91 -0
  103. package/src/runtime/auth.ts +11 -4
  104. package/src/runtime/branch-jobs.ts +36 -0
  105. package/src/runtime/catalog.ts +13 -3
  106. package/src/runtime/clipboard.ts +15 -4
  107. package/src/runtime/command-artifacts.ts +64 -0
  108. package/src/runtime/control-connection.ts +104 -0
  109. package/src/runtime/environment-tools.ts +60 -0
  110. package/src/runtime/evidence-notes.ts +4 -2
  111. package/src/runtime/exec-sessions.ts +206 -61
  112. package/src/runtime/execution-control.ts +1 -0
  113. package/src/runtime/extension-ui.ts +22 -30
  114. package/src/runtime/extensions.ts +95 -16
  115. package/src/runtime/harness-comparison.ts +27 -7
  116. package/src/runtime/harness-eval.ts +462 -0
  117. package/src/runtime/history-cache.ts +57 -0
  118. package/src/runtime/history-tools.ts +62 -12
  119. package/src/runtime/integrations.ts +210 -0
  120. package/src/runtime/loop.ts +1088 -282
  121. package/src/runtime/model-discovery.ts +208 -0
  122. package/src/runtime/model.ts +9 -0
  123. package/src/runtime/native-eval-fixtures.ts +33 -0
  124. package/src/runtime/native-eval.ts +146 -0
  125. package/src/runtime/oauth.ts +80 -3
  126. package/src/runtime/pi-provider.ts +169 -12
  127. package/src/runtime/program-status.ts +38 -0
  128. package/src/runtime/provider-connect.ts +109 -0
  129. package/src/runtime/provider-presets.ts +87 -0
  130. package/src/runtime/provider-request.ts +13 -2
  131. package/src/runtime/provider.ts +23 -9
  132. package/src/runtime/query-eval-verifier.ts +28 -0
  133. package/src/runtime/recovery-coverage.ts +71 -0
  134. package/src/runtime/recovery-evidence.ts +76 -0
  135. package/src/runtime/recovery-fixtures.ts +116 -0
  136. package/src/runtime/runtime-target.ts +149 -0
  137. package/src/runtime/session-control.ts +198 -0
  138. package/src/runtime/tool-broker.ts +137 -0
  139. package/src/runtime/tool-discovery.ts +57 -0
  140. package/src/runtime/tool-mode-eval.ts +146 -0
  141. package/src/runtime/tool-queue.ts +77 -0
  142. package/src/runtime/tools.ts +628 -183
  143. package/src/runtime/trial-control.ts +45 -0
  144. package/src/runtime/update-check.ts +47 -0
  145. package/src/runtime/window-trial.ts +2 -1
  146. package/src/runtime/working-notes.ts +33 -3
  147. package/src/runtime/workspace-lifecycle.ts +97 -0
  148. package/src/store/acceptance.ts +15 -0
  149. package/src/store/checkpoint-history.ts +43 -0
  150. package/src/store/command-artifacts.ts +96 -0
  151. package/src/store/command.ts +65 -37
  152. package/src/store/file-checkpoints.ts +104 -0
  153. package/src/store/file-index.ts +13 -1
  154. package/src/store/git-checkpoints.ts +67 -0
  155. package/src/store/observations.ts +61 -20
  156. package/src/store/process-receipts.ts +34 -0
  157. package/src/store/query-source.ts +13 -8
  158. package/src/store/recovery.ts +2 -3
  159. package/src/store/replay.ts +16 -4
  160. package/src/store/runtime-target.ts +64 -0
  161. package/src/store/search.ts +171 -132
  162. package/src/store/session-archive.ts +4 -0
  163. package/src/store/source-cancellation.ts +6 -0
  164. package/src/store/update-cache.ts +36 -0
  165. package/src/store/workspace-observation.ts +113 -0
  166. package/src/tui/ansi-spans.ts +126 -0
  167. package/src/tui/style.ts +19 -16
  168. package/src/tui/ui/atomic-draft.ts +121 -0
  169. package/src/tui/ui/canvas.ts +96 -0
  170. package/src/tui/ui/colors.ts +70 -0
  171. package/src/tui/ui/composer.ts +200 -0
  172. package/src/tui/ui/control-panel.ts +23 -0
  173. package/src/tui/ui/dialog.ts +379 -0
  174. package/src/tui/ui/draft.ts +23 -0
  175. package/src/tui/ui/edge.ts +186 -0
  176. package/src/tui/ui/image-size.ts +40 -0
  177. package/src/tui/ui/ink.ts +210 -0
  178. package/src/tui/ui/items.ts +40 -0
  179. package/src/tui/ui/keys.ts +40 -0
  180. package/src/tui/ui/layout.ts +74 -0
  181. package/src/tui/ui/list-model.ts +86 -0
  182. package/src/tui/ui/list-view.ts +172 -0
  183. package/src/tui/ui/mascot.ts +67 -0
  184. package/src/tui/ui/messages.ts +518 -0
  185. package/src/tui/ui/motion.ts +28 -0
  186. package/src/tui/ui/paths.ts +10 -0
  187. package/src/tui/ui/pictures.ts +78 -0
  188. package/src/tui/ui/screen.ts +421 -0
  189. package/src/tui/ui/status.ts +279 -0
  190. package/src/tui/ui/styled.ts +85 -0
  191. package/src/tui/ui/testing.ts +5 -0
  192. package/src/tui/ui/tool-card.ts +150 -0
  193. package/src/apps/extension-ui.ts +0 -330
  194. package/src/apps/resource-browser.ts +0 -75
  195. package/src/apps/session-browser.ts +0 -113
  196. package/src/apps/session-picker.ts +0 -55
  197. package/src/tui/ansi.ts +0 -67
  198. package/src/tui/autocomplete.ts +0 -29
  199. package/src/tui/chat-viewport.ts +0 -47
  200. package/src/tui/complete.ts +0 -87
  201. package/src/tui/editor.ts +0 -940
  202. package/src/tui/host.ts +0 -264
  203. package/src/tui/input.ts +0 -75
  204. package/src/tui/keys.ts +0 -378
  205. package/src/tui/markdown.ts +0 -17
  206. package/src/tui/render.ts +0 -153
  207. package/src/tui/select.ts +0 -258
  208. package/src/tui/settings.ts +0 -44
  209. package/src/tui/statusline.ts +0 -162
  210. package/src/tui/system-theme.ts +0 -700
  211. package/src/tui/terminal.ts +0 -30
  212. package/src/tui/text.ts +0 -52
  213. package/src/tui/toolview.ts +0 -386
  214. package/src/tui/transcript.ts +0 -123
  215. package/src/tui/tree.ts +0 -471
  216. package/src/vendor/pi-tui/LICENSE.upstream +0 -21
  217. package/src/vendor/pi-tui/UPSTREAM.json +0 -50
  218. package/src/vendor/pi-tui/VENDOR.md +0 -25
  219. package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
  220. package/src/vendor/pi-tui/autocomplete.ts +0 -860
  221. package/src/vendor/pi-tui/colors.ts +0 -367
  222. package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
  223. package/src/vendor/pi-tui/components/box.ts +0 -166
  224. package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
  225. package/src/vendor/pi-tui/components/editor.ts +0 -2472
  226. package/src/vendor/pi-tui/components/h-stack.ts +0 -44
  227. package/src/vendor/pi-tui/components/image.ts +0 -127
  228. package/src/vendor/pi-tui/components/input.ts +0 -494
  229. package/src/vendor/pi-tui/components/loader.ts +0 -101
  230. package/src/vendor/pi-tui/components/markdown.ts +0 -1021
  231. package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
  232. package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
  233. package/src/vendor/pi-tui/components/select-list.ts +0 -273
  234. package/src/vendor/pi-tui/components/settings-list.ts +0 -328
  235. package/src/vendor/pi-tui/components/spacer.ts +0 -28
  236. package/src/vendor/pi-tui/components/stack.ts +0 -154
  237. package/src/vendor/pi-tui/components/text.ts +0 -107
  238. package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
  239. package/src/vendor/pi-tui/components/v-stack.ts +0 -33
  240. package/src/vendor/pi-tui/editor-component.ts +0 -74
  241. package/src/vendor/pi-tui/fuzzy.ts +0 -138
  242. package/src/vendor/pi-tui/index.ts +0 -186
  243. package/src/vendor/pi-tui/keybindings.ts +0 -320
  244. package/src/vendor/pi-tui/keys.ts +0 -1401
  245. package/src/vendor/pi-tui/kill-ring.ts +0 -46
  246. package/src/vendor/pi-tui/latex.ts +0 -1506
  247. package/src/vendor/pi-tui/layout-node.ts +0 -51
  248. package/src/vendor/pi-tui/layout.ts +0 -449
  249. package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
  250. package/src/vendor/pi-tui/native/clipboard.h +0 -208
  251. package/src/vendor/pi-tui/native/darwin/README.md +0 -22
  252. package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
  253. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  254. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  255. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
  256. package/src/vendor/pi-tui/native/linux/README.md +0 -23
  257. package/src/vendor/pi-tui/native/linux/build.sh +0 -65
  258. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  259. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  260. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
  261. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
  262. package/src/vendor/pi-tui/native/napi.h +0 -91
  263. package/src/vendor/pi-tui/native/win32/README.md +0 -31
  264. package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
  265. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  266. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  267. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
  268. package/src/vendor/pi-tui/native-modifiers.ts +0 -13
  269. package/src/vendor/pi-tui/native-module-path.ts +0 -31
  270. package/src/vendor/pi-tui/native-platform.ts +0 -65
  271. package/src/vendor/pi-tui/oklab.ts +0 -233
  272. package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
  273. package/src/vendor/pi-tui/terminal-colors.ts +0 -91
  274. package/src/vendor/pi-tui/terminal-image.ts +0 -731
  275. package/src/vendor/pi-tui/terminal.ts +0 -554
  276. package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
  277. package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
  278. package/src/vendor/pi-tui/tui.ts +0 -1493
  279. package/src/vendor/pi-tui/undo-stack.ts +0 -28
  280. package/src/vendor/pi-tui/utils.ts +0 -1385
  281. package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
  282. package/src/vendor/pi-tui/word-navigation.ts +0 -117
package/src/tui/tree.ts DELETED
@@ -1,471 +0,0 @@
1
- /**
2
- * Session-tree layout and selection. Pure: renderTree(nodes, selection,
3
- * width, style) → string[]. Collapse/filter/cursor never reach the log —
4
- * the thing that happened is rewind, and that already has an event.
5
- *
6
- * Connectors (`├─` / `└─` / `│`) are kept before prompt text when the
7
- * terminal is too narrow. A line that wraps would make the line-diff
8
- * renderer count one row where the screen used two (PLAYBOOK width rule;
9
- * same trap as toolview's `" → "` prefix).
10
- */
11
-
12
- import { assertNever } from "./exhaustive.ts";
13
- import type { Key } from "./keys.ts";
14
- import type { Component } from "./text.ts";
15
- import { displayWidth } from "./width.ts";
16
- import { PLAIN_ATTRS, paint, type Style } from "./style.ts";
17
- import { Input } from "../vendor/pi-tui/components/input.ts";
18
- import { encodeKey } from "./input.ts";
19
- import { truncateToWidth } from "../vendor/pi-tui/utils.ts";
20
- import { getKeybindings } from "../vendor/pi-tui/keybindings.ts";
21
-
22
- /** Same mark /back uses for a branch the session rewound away from. */
23
- export const LEFT_BRANCH_MARK = "↩";
24
-
25
- /**
26
- * Why the live region is not a blank box. Named in the card: an empty
27
- * frame looks like a render bug, and there is a real reason (no completed
28
- * exchange to jump to).
29
- */
30
- export const EMPTY_TREE_MESSAGE = "nothing to go back to -- no exchange has completed yet";
31
-
32
- /** Filter toggle lives here so the key is not a hidden chord. */
33
- export const TREE_HINT = "tab user/all · enter jump · esc cancel";
34
-
35
- const BRANCH_MID = "├─ ";
36
- const BRANCH_END = "└─ ";
37
- const PIPE = "│ ";
38
- const BLANK = " ";
39
- const SELECTED = "→ ";
40
- const UNSELECTED = " ";
41
- const CURRENT_MARK = "● ";
42
- const ON_PATH_MARK = " ";
43
- const LEFT_MARK = `${LEFT_BRANCH_MARK} `;
44
-
45
- export type TreeKind = "user" | "other";
46
- export type TreeFilter = "user" | "default" | "no-tools" | "user-only" | "labeled-only" | "all";
47
-
48
- export interface TreeNode {
49
- readonly id: string;
50
- readonly prompt: string;
51
- readonly onPath: boolean;
52
- readonly current: boolean;
53
- readonly kind: TreeKind;
54
- readonly children: readonly TreeNode[];
55
- readonly category?: "tool" | "message" | "metadata";
56
- readonly label?: string;
57
- readonly labelAt?: string;
58
- readonly at?: string;
59
- readonly content?: string;
60
- /** Exact event position; user messages navigate before themselves. */
61
- readonly target?: string;
62
- }
63
-
64
- export interface TreeSelection {
65
- readonly index: number;
66
- readonly filter: TreeFilter;
67
- }
68
-
69
- export interface TreeState {
70
- readonly nodes: readonly TreeNode[];
71
- readonly selected: number;
72
- readonly filter: TreeFilter;
73
- readonly confirmed: TreeNode | null;
74
- readonly cancelled: boolean;
75
- }
76
-
77
- export interface CreateTreeOptions {
78
- readonly filter?: TreeFilter;
79
- readonly style?: Style;
80
- readonly bindings?: Readonly<Record<string, readonly string[]>>;
81
- }
82
-
83
- /**
84
- * A restore point as the log already knows it. parent pointers are a
85
- * separate map so this file never imports store.
86
- */
87
- export interface TreePoint {
88
- readonly node: number;
89
- readonly atEvent: string;
90
- readonly prompt: string;
91
- readonly onPath: boolean;
92
- readonly kind?: TreeKind;
93
- readonly category?: "tool" | "message" | "metadata";
94
- readonly label?: string;
95
- readonly labelAt?: string;
96
- readonly at?: string;
97
- readonly content?: string;
98
- readonly target?: string;
99
- }
100
-
101
- export type NodeParent = { readonly node: number; readonly at_event?: string } | null;
102
-
103
- export function createTree(
104
- nodes: readonly TreeNode[],
105
- opts: CreateTreeOptions = {},
106
- ): TreeState {
107
- return {
108
- nodes,
109
- selected: 0,
110
- filter: opts.filter ?? "all",
111
- confirmed: null,
112
- cancelled: false,
113
- };
114
- }
115
-
116
- export function applyTreeKey(state: TreeState, key: Key): TreeState {
117
- switch (key.type) {
118
- case "up":
119
- return move(state, -1);
120
- case "down":
121
- return move(state, 1);
122
- case "enter":
123
- return confirm(state);
124
- case "escape":
125
- return { ...state, cancelled: true, confirmed: null };
126
- case "tab":
127
- return toggleFilter(state);
128
- case "char":
129
- case "backspace":
130
- case "delete":
131
- case "left":
132
- case "right":
133
- case "home":
134
- case "end":
135
- case "pageup":
136
- case "pagedown":
137
- case "ctrl":
138
- case "alt":
139
- return state;
140
- default:
141
- return assertNever(key, "applyTreeKey");
142
- }
143
- }
144
-
145
- function visibleNodes(nodes: readonly TreeNode[], filter: TreeFilter): TreeNode[] {
146
- if (filter === "all") return [...nodes];
147
- const out: TreeNode[] = [];
148
- const stack = [...nodes].reverse().map(node => ({ node, into: out }));
149
- while (stack.length) {
150
- const { node: n, into } = stack.pop()!;
151
- const include = filter === "user" || filter === "user-only" ? n.kind === "user" :
152
- filter === "labeled-only" ? Boolean(n.label) : filter === "no-tools" ? n.category !== "tool" && n.category !== "metadata" : n.category !== "metadata";
153
- const children: TreeNode[] = [];
154
- if (include) into.push({ ...n, children });
155
- for (let i = n.children.length - 1; i >= 0; i--) stack.push({ node: n.children[i]!, into: include ? children : into });
156
- }
157
- return out;
158
- }
159
-
160
- function flatten(nodes: readonly TreeNode[]): TreeNode[] {
161
- const out: TreeNode[] = [];
162
- const stack = [...nodes].reverse();
163
- while (stack.length) {
164
- const n = stack.pop()!;
165
- out.push(n);
166
- for (let i = n.children.length - 1; i >= 0; i--) stack.push(n.children[i]!);
167
- }
168
- return out;
169
- }
170
-
171
- function move(state: TreeState, dir: -1 | 1): TreeState {
172
- const n = flatten(visibleNodes(state.nodes, state.filter)).length;
173
- if (n === 0) return state;
174
- const selected = (state.selected + dir + n) % n;
175
- return { ...state, selected, confirmed: null, cancelled: false };
176
- }
177
-
178
- function confirm(state: TreeState): TreeState {
179
- const item = flatten(visibleNodes(state.nodes, state.filter))[state.selected];
180
- if (item === undefined) return state;
181
- return { ...state, confirmed: item, cancelled: false };
182
- }
183
-
184
- function toggleFilter(state: TreeState): TreeState {
185
- const filter: TreeFilter = state.filter === "all" || state.filter === "default" ? "user" : "all";
186
- const n = flatten(visibleNodes(state.nodes, filter)).length;
187
- const selected = n === 0 ? 0 : Math.min(state.selected, n - 1);
188
- return { ...state, filter, selected, confirmed: null, cancelled: false };
189
- }
190
-
191
- /**
192
- * Truncate on display columns. Slicing with s.length would cut a CJK
193
- * prompt in half and the next cell would paint garbage.
194
- */
195
- function truncate(s: string, width: number): string {
196
- if (width <= 0) return "";
197
- if (displayWidth(s) <= width) return s;
198
- let out = "";
199
- let w = 0;
200
- for (const ch of s) {
201
- const cw = displayWidth(ch);
202
- if (w + cw > width) break;
203
- out += ch;
204
- w += cw;
205
- }
206
- return out;
207
- }
208
-
209
- export function renderTree(
210
- nodes: readonly TreeNode[],
211
- selection: TreeSelection,
212
- width: number,
213
- style?: Style,
214
- ): string[] {
215
- const w = Math.max(0, Math.floor(width));
216
- const filtered = visibleNodes(nodes, selection.filter);
217
- if (filtered.length === 0) {
218
- return [truncate(EMPTY_TREE_MESSAGE, w)];
219
- }
220
-
221
- const flat = flatten(filtered);
222
- const selected = flat[selection.index] ?? flat[0];
223
- const selectedId = selected?.id;
224
- const lines: string[] = [];
225
- walk(filtered, "", true, selectedId, w, style, lines);
226
- const hint = truncate(TREE_HINT, w);
227
- if (hint.length > 0) {
228
- lines.push(style !== undefined ? paint(hint, { ...PLAIN_ATTRS, dim: true }, style) : hint);
229
- }
230
- return lines;
231
- }
232
-
233
- function walk(
234
- nodes: readonly TreeNode[],
235
- prefix: string,
236
- isRoot: boolean,
237
- selectedId: string | undefined,
238
- width: number,
239
- style: Style | undefined,
240
- lines: string[],
241
- ): void {
242
- const stack = nodes.map((node, i) => ({ node, prefix, root: isRoot, last: i === nodes.length - 1 })).reverse();
243
- while (stack.length) {
244
- const { node, prefix: indent, root, last } = stack.pop()!;
245
- const branch = root ? "" : last ? BRANCH_END : BRANCH_MID;
246
- const sel = node.id === selectedId ? SELECTED : UNSELECTED;
247
- const mark = node.current ? CURRENT_MARK : node.onPath ? ON_PATH_MARK : LEFT_MARK;
248
- // Left-to-right: connectors stay, prompt is what width drops first.
249
- const raw = sel + indent + branch + mark + (node.label ? `[${node.label}] ` : "") + node.prompt;
250
- const clipped = truncate(raw, width);
251
- const chosen = node.id === selectedId;
252
- lines.push(
253
- chosen && style !== undefined
254
- ? paint(clipped, { ...PLAIN_ATTRS, selected: true }, style)
255
- : clipped,
256
- );
257
- const nextPrefix = root ? "" : indent + (last ? BLANK : PIPE);
258
- // Bound indentation to the display, independently of history depth.
259
- const budget = Math.max(3, Math.min(30, width - 20));
260
- const childPrefix = nextPrefix.length > budget ? "… " + nextPrefix.slice(-(budget - 2)) : nextPrefix;
261
- for (let i = node.children.length - 1; i >= 0; i--) stack.push({ node: node.children[i]!, prefix: childPrefix, root: false, last: i === node.children.length - 1 });
262
- }
263
- }
264
-
265
- /**
266
- * Restore points plus parent pointers → the tree the view paints.
267
- *
268
- * Same-node exchanges stay a chain. A child node whose parent.at_event
269
- * matches a point hangs off that point as a sibling of the continuation,
270
- * which is how a rewind becomes a visible fork instead of a missing row.
271
- */
272
- export function treeFromPoints(
273
- points: readonly TreePoint[],
274
- parents: ReadonlyMap<number, NodeParent>,
275
- currentNode: number,
276
- ): TreeNode[] {
277
- const byNode = new Map<number, TreePoint[]>();
278
- for (const p of points) {
279
- const list = byNode.get(p.node) ?? [];
280
- list.push(p);
281
- byNode.set(p.node, list);
282
- }
283
-
284
- const lastHere = [...points].reverse().find((p) => p.onPath && p.node === currentNode);
285
- const lastOnPath = lastHere ?? [...points].reverse().find((p) => p.onPath);
286
-
287
- function toNode(p: TreePoint, children: readonly TreeNode[]): TreeNode {
288
- return {
289
- ...p,
290
- id: `${p.node}:${p.atEvent}`,
291
- prompt: p.prompt,
292
- onPath: p.onPath,
293
- current: lastOnPath !== undefined && lastOnPath.node === p.node && lastOnPath.atEvent === p.atEvent,
294
- kind: p.kind ?? "user",
295
- children,
296
- };
297
- }
298
-
299
- // Wire event chains and branch edges separately. Recursing once per event
300
- // overflows the JS stack on otherwise valid long sessions.
301
- type MutableNode = TreeNode & { children: TreeNode[] };
302
- const chains = new Map<number, MutableNode[]>();
303
- const events = new Map<string, MutableNode>();
304
- for (const [id, pts] of byNode) {
305
- const nodes = pts.map(p => toNode(p, []) as MutableNode);
306
- for (let i = 0; i < nodes.length; i++) {
307
- events.set(nodes[i]!.id, nodes[i]!);
308
- if (nodes[i + 1]) nodes[i]!.children.push(nodes[i + 1]!);
309
- }
310
- chains.set(id, nodes);
311
- }
312
- const forest: TreeNode[] = [];
313
- for (const [id, nodes] of [...chains].sort(([a], [b]) => a - b)) {
314
- const first = nodes[0];
315
- if (!first) continue;
316
- let parent = parents.get(id);
317
- const visited = new Set<number>([id]);
318
- while (parent && !chains.get(parent.node)?.length && !visited.has(parent.node)) {
319
- visited.add(parent.node); parent = parents.get(parent.node);
320
- }
321
- const anchor = parent && !visited.has(parent.node) ? events.get(`${parent.node}:${parent.at_event}`) ?? chains.get(parent.node)?.at(-1) : undefined;
322
- if (anchor) anchor.children.push(first); else forest.push(first);
323
- }
324
- return forest;
325
- }
326
-
327
- export class TreeList implements Component {
328
- private current: TreeState;
329
- private style: Style | undefined;
330
- private readonly bindings: Readonly<Record<string, readonly string[]>> | undefined;
331
- private readonly search = new Input({ prompt: "Search tree: " });
332
- private readonly folded = new Set<string>();
333
- private labelEditor: { node: TreeNode; input: Input } | undefined;
334
- labelChange: { target: string; text: string } | undefined;
335
- copyText: string | undefined;
336
- showTimestamps = false;
337
- onInput: ((data: string) => void) | undefined;
338
-
339
- constructor(nodes: readonly TreeNode[], opts: CreateTreeOptions = {}) {
340
- this.current = createTree(nodes, opts);
341
- this.style = opts.style;
342
- this.bindings = opts.bindings;
343
- }
344
-
345
- get state(): TreeState {
346
- return { ...this.current, nodes: this.filtered() };
347
- }
348
- get selectedNode(): TreeNode | undefined { return flatten(this.filtered())[this.current.selected]; }
349
- setNodes(nodes: readonly TreeNode[]): void {
350
- const id = this.selectedNode?.id;
351
- this.current = { ...this.current, nodes, selected: 0 };
352
- this.current = { ...this.current, selected: Math.max(0, flatten(this.filtered()).findIndex(node => node.id === id)) };
353
- }
354
-
355
- applyKey(key: Key): TreeState {
356
- return this.acceptInput(encodeKey(key));
357
- }
358
-
359
- get focused(): boolean { return (this.labelEditor?.input ?? this.search).focused; }
360
- set focused(value: boolean) { (this.labelEditor?.input ?? this.search).focused = value; }
361
- setStyle(style: Style): void { this.style = style; }
362
- invalidate(): void { this.search.invalidate(); }
363
- handleInput(data: string): void { if (this.onInput) this.onInput(data); else this.acceptInput(data); }
364
- private filtered(): readonly TreeNode[] {
365
- const tokens = this.search.getValue().toLowerCase().trim().split(/\s+/).filter(Boolean);
366
- const nodes = visibleNodes(this.current.nodes, this.current.filter);
367
- if (tokens.length) return flatten(nodes).filter(node => tokens.every(token => `${node.content ?? node.prompt} ${node.label ?? ""}`.toLowerCase().includes(token))).map(node => ({ ...node, children: [] }));
368
- const out: TreeNode[] = [];
369
- const stack = [...nodes].reverse().map(node => ({ node, into: out }));
370
- while (stack.length) {
371
- const { node, into } = stack.pop()!;
372
- const children: TreeNode[] = [];
373
- into.push({ ...node, prompt: `${this.showTimestamps && node.labelAt ? `${node.labelAt} ` : ""}${this.folded.has(node.id) ? "▸ " : ""}${node.prompt}`, children });
374
- if (!this.folded.has(node.id)) for (let i = node.children.length - 1; i >= 0; i--) stack.push({ node: node.children[i]!, into: children });
375
- }
376
- return out;
377
- }
378
- acceptInput(data: string, action?: string): TreeState {
379
- this.current = { ...this.current, confirmed: null, cancelled: false };
380
- this.labelChange = undefined; this.copyText = undefined;
381
- const kb = getKeybindings();
382
- if (this.labelEditor) {
383
- if (kb.matches(data, "tui.select.cancel")) this.labelEditor = undefined;
384
- else if (kb.matches(data, "tui.select.confirm")) {
385
- this.labelChange = { target: this.labelEditor.node.target ?? this.labelEditor.node.id, text: this.labelEditor.input.getValue() };
386
- this.labelEditor = undefined;
387
- } else this.labelEditor.input.handleInput(data);
388
- return this.state;
389
- }
390
- const filters: TreeFilter[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
391
- const filterByAction: Record<string, TreeFilter> = { "app.tree.filter.default": "default", "app.tree.filter.noTools": "no-tools",
392
- "app.tree.filter.userOnly": "user-only", "app.tree.filter.labeledOnly": "labeled-only", "app.tree.filter.all": "all" };
393
- const filter = action ? filterByAction[action] : undefined;
394
- if (filter) { this.folded.clear(); this.current = { ...this.current, filter: this.current.filter === filter ? "default" : filter, selected: 0 }; return this.state; }
395
- if (action === "app.tree.filter.cycleForward" || action === "app.tree.filter.cycleBackward") {
396
- const index = Math.max(0, filters.indexOf(this.current.filter));
397
- this.folded.clear();
398
- this.current = { ...this.current, filter: filters[(index + (action.endsWith("Forward") ? 1 : 4)) % 5]!, selected: 0 }; return this.state;
399
- }
400
- if (action === "app.tree.toggleLabelTimestamp") { this.showTimestamps = !this.showTimestamps; return this.state; }
401
- const selected = this.selectedNode;
402
- if (action === "app.tree.editLabel") {
403
- if (selected) { const input = new Input({ prompt: "Label: " }); input.setValue(selected.label ?? ""); input.focused = this.focused; this.labelEditor = { node: selected, input }; }
404
- return this.state;
405
- }
406
- if (action === "app.message.copy") { this.copyText = selected?.content ?? selected?.prompt; return this.state; }
407
- if (selected && (action === "app.tree.foldOrUp" || action === "app.tree.unfoldOrDown")) {
408
- const flat = flatten(this.filtered());
409
- const parentOf = new Map<string, TreeNode>();
410
- for (const node of flat) for (const child of node.children) parentOf.set(child.id, node);
411
- const parent = parentOf.get(selected.id);
412
- if (action === "app.tree.foldOrUp") {
413
- if (selected.children.length && (!parent || parent.children.length > 1) && !this.folded.has(selected.id)) this.folded.add(selected.id);
414
- else {
415
- let target = selected;
416
- while (parentOf.has(target.id)) {
417
- const ancestor = parentOf.get(target.id)!;
418
- if (ancestor.children.length > 1 && target.id !== selected.id) break;
419
- target = ancestor;
420
- }
421
- this.current = { ...this.current, selected: flat.findIndex(node => node.id === target.id) };
422
- }
423
- } else if (this.folded.delete(selected.id)) { /* Expand in place before moving to a child. */ }
424
- else {
425
- let target = selected;
426
- while (target.children.length) { const branch = target.children.length > 1; target = target.children[0]!; if (branch) break; }
427
- this.current = { ...this.current, selected: flat.findIndex(node => node.id === target.id) };
428
- }
429
- return this.state;
430
- }
431
- if (kb.matches(data, "tui.select.cancel")) {
432
- if (this.search.getValue()) { this.search.setValue(""); this.folded.clear(); this.current = { ...this.current, selected: 0 }; }
433
- else this.current = { ...this.current, cancelled: true };
434
- }
435
- else if (kb.matches(data, "tui.select.confirm")) this.current = { ...this.current, confirmed: selected ?? null };
436
- else if (kb.matches(data, "tui.select.up") || kb.matches(data, "tui.select.down")) {
437
- const size = flatten(this.filtered()).length;
438
- this.current = { ...this.current, selected: size ? (this.current.selected + (kb.matches(data, "tui.select.up") ? -1 : 1) + size) % size : 0 };
439
- } else if (kb.matches(data, "tui.input.tab")) this.current = toggleFilter(this.current);
440
- else if (kb.matches(data, "tui.select.pageUp") || kb.matches(data, "tui.select.pageDown") || kb.matches(data, "tui.editor.cursorLeft") || kb.matches(data, "tui.editor.cursorRight")) {
441
- this.current = { ...this.current, selected: Math.max(0, Math.min(flatten(this.filtered()).length - 1,
442
- this.current.selected + (kb.matches(data, "tui.select.pageUp") || kb.matches(data, "tui.editor.cursorLeft") ? -10 : 10))) };
443
- } else {
444
- const before = this.search.getValue(); this.search.handleInput(data);
445
- if (before !== this.search.getValue()) this.folded.clear();
446
- this.current = { ...this.current, selected: 0 };
447
- }
448
- return this.state;
449
- }
450
-
451
- render(width: number): string[] {
452
- if (width <= 0) return [""];
453
- if (this.labelEditor) return ["Edit label · empty removes it", ...this.labelEditor.input.render(width),
454
- `${getKeybindings().getKeys("tui.select.confirm").join("/")} save · ${getKeybindings().getKeys("tui.select.cancel").join("/")} cancel`].map(line => truncateToWidth(line, width, ""));
455
- const body = renderTree(
456
- this.filtered(),
457
- { index: this.current.selected, filter: this.current.filter },
458
- width,
459
- this.style,
460
- );
461
- // This is a viewport, not a total result cap. Arrows/search can reach every row.
462
- const start = Math.max(0, this.current.selected - 5);
463
- const rows = body.slice(start, start + 10);
464
- const hints = this.bindings ? [["app.tree.filter.cycleForward", "filters"], ["app.tree.foldOrUp", "branch"], ["app.tree.editLabel", "label"],
465
- ["app.tree.toggleLabelTimestamp", "label time"], ["app.message.copy", "copy"], ["tui.select.cancel", "cancel"]]
466
- .flatMap(([action, label]) => this.bindings![action!]?.length ? [`${this.bindings![action!]!.join("/")} ${label}`] : []).join(" · ") : TREE_HINT;
467
- return ["Session tree · Select an event to navigate", ...this.search.render(width),
468
- ...rows, `${this.current.filter} · ${hints}`]
469
- .map((line) => truncateToWidth(line, width, ""));
470
- }
471
- }
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Mario Zechner
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,50 +0,0 @@
1
- {
2
- "commit": "02eed88fd8912e54a804ddebd409e2e4c08ac5ef",
3
- "files": {
4
- "alt-screen-search.ts": "2ed3821323723515523075ac143ce5e25fdcd7c7",
5
- "autocomplete.ts": "3ff45bf17a5def8d0bdf81e9eb1af264f1c5f768",
6
- "colors.ts": "070a261344e307988f45e7c86d1728db5a00b8f3",
7
- "components/alt-screen-flash.ts": "e4b823cd2bdd54696895cac93807cc6fcddaddd4",
8
- "components/box.ts": "7c880d9145eff2c26db7969937223bb3776acced",
9
- "components/cancellable-loader.ts": "7822cb04219440703a9100e02679725b3326fa1c",
10
- "components/editor.ts": "c7fda44eb8288a10f518791f931cff4fc36c62b5",
11
- "components/h-stack.ts": "07f8f8ffd055789d69266152f62ee97390bacf01",
12
- "components/image.ts": "91d68016aae5d292e22a3e892631b7ed53b13e64",
13
- "components/input.ts": "1bc26453098639991c22515cc94fee89b53f289d",
14
- "components/loader.ts": "0c59e73a83bb4bd9978818b16bf556da53f48123",
15
- "components/markdown.ts": "30c40c1715dfc374687b538d447988a81feb237f",
16
- "components/mouse-region.ts": "a77ded0c1e59167fe2f0643e87531b95ed9e544d",
17
- "components/scroll-view.ts": "b8f8a51ff0f8f93aabce0df072ee29c5a47c82b3",
18
- "components/select-list.ts": "f4f4354dd850939e2e84ea8ce8a755206d3e8e43",
19
- "components/settings-list.ts": "b1b8366bf349b30a1d93073a4cc1d0e2bfa9dc8a",
20
- "components/spacer.ts": "7abe1551ca79a17bbb03d6b62f3ec18c50274a75",
21
- "components/stack.ts": "68bde0368455646144a1dd3942b56a48e9d4f45a",
22
- "components/text.ts": "7a50e2721028d544f641ab5f66ef1b752ff6d1f7",
23
- "components/truncated-text.ts": "c26b8892967a8462a8828cfed4ea572a73af1602",
24
- "components/v-stack.ts": "ce7b9292d6dfd0981650a1cf927f223b926f9714",
25
- "editor-component.ts": "595645033a074d17b8190b73709c2623d3e479b5",
26
- "fuzzy.ts": "1b5352c1c592956ea614a2ec3630cb6f606ccd83",
27
- "index.ts": "93fb8b72777a3a4f8423cf4e7d5f74b8dbd982f0",
28
- "keybindings.ts": "d6afb5ec39642c098b7d95e10ab6b57568e514b4",
29
- "keys.ts": "8b6f8a0a708cc9a1c9b795ff1ec9766c4209114b",
30
- "kill-ring.ts": "2292f91aa36baa1bf762df2890bdbed8ca17eb19",
31
- "latex.ts": "060fd052d820b42903896e53053614e1c578a995",
32
- "layout-node.ts": "a3c06b24564efaa35321f190641a80e95ce7c7ab",
33
- "layout.ts": "897c51de2ffbc4c543e87399eaa4e8b6421449ef",
34
- "native-modifiers.ts": "8cdc60d484717431bd5f1577b36f189275aa06d3",
35
- "native-module-path.ts": "b75117feb7384da268cfd7d3a8c8fc6af0aeb10e",
36
- "native-platform.ts": "3a6580a445d8a816225ed824d30e48a93268a761",
37
- "oklab.ts": "83ddda42fca5bcd485464c74029c2f3db7a5cc6d",
38
- "stdin-buffer.ts": "cc10c61a55196eb90d9da3e1858e48cab27e925f",
39
- "terminal-colors.ts": "6a972a95bb5c3c0de65657f4861cdb3ad3579c41",
40
- "terminal-image.ts": "a84d22c575599c55e5ea96c528e641e54df8511c",
41
- "terminal.ts": "cce2dd116ca55b2c9f3add30b080a395e8f1f4bb",
42
- "tui-alt-screen.ts": "bf8affb13ba1d3625711268c8b6f0956c9e5d90e",
43
- "tui-main-screen.ts": "c079dd776984b23f5e0b4a833ac5b67fb31ff337",
44
- "tui.ts": "7968a9e792375b9e8701ca4dbe89d18735aa387b",
45
- "undo-stack.ts": "5b9a7e9cee03a08b5d7ee637b92834a3a28d7fd2",
46
- "utils.ts": "f537ad7e37dcf35db1259aa05d43f1b45bdef7c7",
47
- "wheel-scroll.ts": "a6c0018bd4caa9b5bc197d05d548bfdca4036172",
48
- "word-navigation.ts": "7c7eced2b6ac3ff5b49418824c3ac00a1f0204a3"
49
- }
50
- }
@@ -1,25 +0,0 @@
1
- # Vendored: pi TUI
2
-
3
- Source: https://github.com/earendil-works/pi — `packages/tui/src`
4
- License: MIT (see `LICENSE.upstream`), Copyright (c) 2025 Mario Zechner
5
- Copied: 2026-09-30
6
- Commit: `02eed88fd8912e54a804ddebd409e2e4c08ac5ef`
7
- All 45 source files verified against upstream Git blob hashes in `UPSTREAM.json`.
8
-
9
- The application also follows coding-agent's CustomEditor, searchable model
10
- selector and interactive-mode composition. Those product behaviors are adapted
11
- in `src/tui/` and `src/apps/`; Pi's AgentSession is not imported. Its dark/light
12
- palette is mapped into `src/core/theme.ts` with attribution there.
13
-
14
- This directory is an **unmodified copy**. Do not patch it in place: upgrade
15
- by recopying from upstream. Behaviour changes belong in `src/tui/`, which
16
- adapts this to xeno and is the only thing allowed to import from here.
17
-
18
- `AGENTS.md` exempts `src/vendor/` from the import-shape and dependency
19
- checks, because those exist to keep *our* code's layering decidable and
20
- upstream organises its code its own way.
21
-
22
- Native sources and all six upstream prebuilt `.node` binaries are included
23
- under `native/`, verified by `native/UPSTREAM.json` at the same commit.
24
- The upstream resolver loads them directly; unavailable platform capabilities
25
- retain its optional fallback. No install script or startup download is required.