@visual-z/xeno 0.2.1 → 0.3.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.
- package/CHANGELOG.md +9 -0
- package/README.md +106 -26
- package/docs/install.md +51 -13
- package/docs/providers.md +11 -0
- package/package.json +5 -4
- package/src/apps/approve.ts +3 -3
- package/src/apps/commands.ts +38 -5
- package/src/apps/completions.ts +73 -19
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/project-trust.ts +2 -42
- package/src/apps/provider-connect.ts +346 -0
- package/src/apps/render.ts +22 -47
- package/src/apps/resources.ts +5 -2
- package/src/apps/shell.ts +298 -1526
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +128 -86
- package/src/apps/ui/app.ts +1383 -0
- package/src/apps/ui/extension-host.ts +194 -0
- package/src/apps/ui/image-cache.ts +78 -0
- package/src/apps/ui/palette.ts +88 -0
- package/src/apps/ui/project.ts +127 -0
- package/src/apps/ui/standalone.ts +146 -0
- package/src/apps/ui/status-model.ts +68 -0
- package/src/apps/ui/surface.ts +84 -0
- package/src/apps/ui/theme.ts +86 -0
- package/src/core/acceptance.ts +107 -0
- package/src/core/action-defaults.ts +47 -295
- package/src/core/benchmark.ts +2 -2
- package/src/core/branch-receipt.ts +24 -0
- package/src/core/checkpoints.ts +64 -0
- package/src/core/churn.ts +2 -0
- package/src/core/command-artifacts.ts +94 -0
- package/src/core/command-contract.ts +35 -0
- package/src/core/completion-review.ts +21 -5
- package/src/core/context-detail.ts +37 -0
- package/src/core/context-policy.ts +74 -1
- package/src/core/continuation.ts +17 -2
- package/src/core/derive.ts +29 -3
- package/src/core/draft-images.ts +56 -0
- package/src/core/environment.ts +145 -0
- package/src/core/evaluation-coverage.ts +74 -0
- package/src/core/event-validation.ts +80 -5
- package/src/core/events.ts +97 -3
- package/src/core/evidence-links.ts +7 -0
- package/src/core/evidence.ts +13 -3
- package/src/core/execution.ts +8 -2
- package/src/core/experiment.ts +133 -0
- package/src/core/export.ts +9 -0
- package/src/core/failure-recovery.ts +173 -0
- package/src/core/file-precondition.ts +72 -0
- package/src/core/handoff.ts +65 -4
- package/src/core/history-query.ts +38 -2
- package/src/core/history.ts +38 -1
- package/src/core/integration-health.ts +34 -0
- package/src/core/keymap.ts +8 -20
- package/src/core/model-picker.ts +64 -0
- package/src/core/native-eval.ts +25 -0
- package/src/core/note-guidance.ts +34 -0
- package/src/core/observation-view.ts +49 -8
- package/src/core/permissions.ts +24 -2
- package/src/core/process-receipt.ts +105 -0
- package/src/core/process-state.ts +37 -0
- package/src/core/project.ts +45 -18
- package/src/core/prompt-policy.ts +37 -0
- package/src/core/query-eval-contract.ts +14 -0
- package/src/core/read-observation.ts +82 -0
- package/src/core/recovery-corpus.ts +17 -0
- package/src/core/recovery-evaluation.ts +60 -0
- package/src/core/recovery-sources.ts +74 -0
- package/src/core/runtime-target.ts +94 -0
- package/src/core/search-observation.ts +97 -0
- package/src/core/session-tree.ts +147 -0
- package/src/core/settings.ts +26 -10
- package/src/core/skills.ts +44 -0
- package/src/core/sql-query.ts +211 -0
- package/src/core/theme.ts +36 -58
- package/src/core/tool-contract.ts +51 -2
- package/src/core/tool-discovery.ts +87 -0
- package/src/core/tool-failure.ts +60 -0
- package/src/core/tool-mode-protocol.ts +25 -0
- package/src/core/transcript.ts +19 -4
- package/src/core/trial-lifecycle.ts +14 -0
- package/src/core/tui-mode.ts +29 -0
- package/src/core/update-check.ts +97 -0
- package/src/core/version.ts +55 -0
- package/src/core/work-state.ts +49 -3
- package/src/core/working-set.ts +91 -0
- package/src/runtime/auth.ts +2 -2
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/environment-tools.ts +60 -0
- package/src/runtime/evidence-notes.ts +4 -2
- package/src/runtime/exec-sessions.ts +206 -61
- package/src/runtime/execution-control.ts +1 -0
- package/src/runtime/extension-ui.ts +22 -30
- package/src/runtime/extensions.ts +95 -16
- package/src/runtime/harness-comparison.ts +27 -7
- package/src/runtime/harness-eval.ts +460 -0
- package/src/runtime/history-cache.ts +57 -0
- package/src/runtime/history-tools.ts +62 -12
- package/src/runtime/integrations.ts +210 -0
- package/src/runtime/loop.ts +1021 -268
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +143 -0
- package/src/runtime/oauth.ts +1 -1
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/query-eval-verifier.ts +28 -0
- package/src/runtime/recovery-coverage.ts +71 -0
- package/src/runtime/recovery-evidence.ts +76 -0
- package/src/runtime/recovery-fixtures.ts +116 -0
- package/src/runtime/runtime-target.ts +149 -0
- package/src/runtime/tool-broker.ts +137 -0
- package/src/runtime/tool-discovery.ts +57 -0
- package/src/runtime/tool-mode-eval.ts +146 -0
- package/src/runtime/tool-queue.ts +77 -0
- package/src/runtime/tools.ts +628 -183
- package/src/runtime/trial-control.ts +45 -0
- package/src/runtime/update-check.ts +47 -0
- package/src/runtime/window-trial.ts +2 -1
- package/src/runtime/working-notes.ts +33 -3
- package/src/runtime/workspace-lifecycle.ts +97 -0
- package/src/store/acceptance.ts +15 -0
- package/src/store/checkpoint-history.ts +43 -0
- package/src/store/command-artifacts.ts +96 -0
- package/src/store/command.ts +65 -37
- package/src/store/file-checkpoints.ts +104 -0
- package/src/store/file-index.ts +13 -1
- package/src/store/git-checkpoints.ts +67 -0
- package/src/store/observations.ts +61 -20
- package/src/store/process-receipts.ts +34 -0
- package/src/store/query-source.ts +13 -8
- package/src/store/recovery.ts +2 -3
- package/src/store/replay.ts +16 -4
- package/src/store/runtime-target.ts +64 -0
- package/src/store/search.ts +171 -132
- package/src/store/session-archive.ts +4 -0
- package/src/store/source-cancellation.ts +6 -0
- package/src/store/update-cache.ts +36 -0
- package/src/store/workspace-observation.ts +113 -0
- package/src/tui/ansi-spans.ts +126 -0
- package/src/tui/style.ts +19 -16
- package/src/tui/ui/atomic-draft.ts +121 -0
- package/src/tui/ui/canvas.ts +96 -0
- package/src/tui/ui/colors.ts +70 -0
- package/src/tui/ui/composer.ts +199 -0
- package/src/tui/ui/dialog.ts +379 -0
- package/src/tui/ui/draft.ts +23 -0
- package/src/tui/ui/edge.ts +186 -0
- package/src/tui/ui/image-size.ts +40 -0
- package/src/tui/ui/ink.ts +210 -0
- package/src/tui/ui/items.ts +40 -0
- package/src/tui/ui/keys.ts +40 -0
- package/src/tui/ui/layout.ts +74 -0
- package/src/tui/ui/list-model.ts +86 -0
- package/src/tui/ui/list-view.ts +172 -0
- package/src/tui/ui/mascot.ts +67 -0
- package/src/tui/ui/messages.ts +518 -0
- package/src/tui/ui/motion.ts +28 -0
- package/src/tui/ui/paths.ts +10 -0
- package/src/tui/ui/pictures.ts +78 -0
- package/src/tui/ui/screen.ts +421 -0
- package/src/tui/ui/status.ts +271 -0
- package/src/tui/ui/styled.ts +85 -0
- package/src/tui/ui/testing.ts +5 -0
- package/src/tui/ui/tool-card.ts +150 -0
- package/src/apps/extension-ui.ts +0 -330
- package/src/apps/resource-browser.ts +0 -75
- package/src/apps/session-browser.ts +0 -113
- package/src/apps/session-picker.ts +0 -55
- package/src/tui/ansi.ts +0 -67
- package/src/tui/autocomplete.ts +0 -29
- package/src/tui/chat-viewport.ts +0 -47
- package/src/tui/complete.ts +0 -87
- package/src/tui/editor.ts +0 -940
- package/src/tui/host.ts +0 -264
- package/src/tui/input.ts +0 -75
- package/src/tui/keys.ts +0 -378
- package/src/tui/markdown.ts +0 -17
- package/src/tui/render.ts +0 -153
- package/src/tui/select.ts +0 -258
- package/src/tui/settings.ts +0 -44
- package/src/tui/statusline.ts +0 -162
- package/src/tui/system-theme.ts +0 -700
- package/src/tui/terminal.ts +0 -30
- package/src/tui/text.ts +0 -52
- package/src/tui/toolview.ts +0 -386
- package/src/tui/transcript.ts +0 -123
- package/src/tui/tree.ts +0 -471
- package/src/vendor/pi-tui/LICENSE.upstream +0 -21
- package/src/vendor/pi-tui/UPSTREAM.json +0 -50
- package/src/vendor/pi-tui/VENDOR.md +0 -25
- package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
- package/src/vendor/pi-tui/autocomplete.ts +0 -860
- package/src/vendor/pi-tui/colors.ts +0 -367
- package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
- package/src/vendor/pi-tui/components/box.ts +0 -166
- package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
- package/src/vendor/pi-tui/components/editor.ts +0 -2472
- package/src/vendor/pi-tui/components/h-stack.ts +0 -44
- package/src/vendor/pi-tui/components/image.ts +0 -127
- package/src/vendor/pi-tui/components/input.ts +0 -494
- package/src/vendor/pi-tui/components/loader.ts +0 -101
- package/src/vendor/pi-tui/components/markdown.ts +0 -1021
- package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
- package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
- package/src/vendor/pi-tui/components/select-list.ts +0 -273
- package/src/vendor/pi-tui/components/settings-list.ts +0 -328
- package/src/vendor/pi-tui/components/spacer.ts +0 -28
- package/src/vendor/pi-tui/components/stack.ts +0 -154
- package/src/vendor/pi-tui/components/text.ts +0 -107
- package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
- package/src/vendor/pi-tui/components/v-stack.ts +0 -33
- package/src/vendor/pi-tui/editor-component.ts +0 -74
- package/src/vendor/pi-tui/fuzzy.ts +0 -138
- package/src/vendor/pi-tui/index.ts +0 -186
- package/src/vendor/pi-tui/keybindings.ts +0 -320
- package/src/vendor/pi-tui/keys.ts +0 -1401
- package/src/vendor/pi-tui/kill-ring.ts +0 -46
- package/src/vendor/pi-tui/latex.ts +0 -1506
- package/src/vendor/pi-tui/layout-node.ts +0 -51
- package/src/vendor/pi-tui/layout.ts +0 -449
- package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
- package/src/vendor/pi-tui/native/clipboard.h +0 -208
- package/src/vendor/pi-tui/native/darwin/README.md +0 -22
- package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
- package/src/vendor/pi-tui/native/linux/README.md +0 -23
- package/src/vendor/pi-tui/native/linux/build.sh +0 -65
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
- package/src/vendor/pi-tui/native/napi.h +0 -91
- package/src/vendor/pi-tui/native/win32/README.md +0 -31
- package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
- package/src/vendor/pi-tui/native-modifiers.ts +0 -13
- package/src/vendor/pi-tui/native-module-path.ts +0 -31
- package/src/vendor/pi-tui/native-platform.ts +0 -65
- package/src/vendor/pi-tui/oklab.ts +0 -233
- package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
- package/src/vendor/pi-tui/terminal-colors.ts +0 -91
- package/src/vendor/pi-tui/terminal-image.ts +0 -731
- package/src/vendor/pi-tui/terminal.ts +0 -554
- package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
- package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
- package/src/vendor/pi-tui/tui.ts +0 -1493
- package/src/vendor/pi-tui/undo-stack.ts +0 -28
- package/src/vendor/pi-tui/utils.ts +0 -1385
- package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
- package/src/vendor/pi-tui/word-navigation.ts +0 -117
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# xeno changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
- Replace the vendored Pi terminal UI with OpenTUI: a growing composer, command palette, model and login pickers, responsive status surfaces, terminal colours and bounded message rendering.
|
|
6
|
+
- Keep inline image tags atomic through cursor movement, deletion, undo/redo, history and queued drafts; send only retained attachments and preview the image beside the cursor. Share bounded image decoding across drafts, messages and tool results.
|
|
7
|
+
- Add plain-text mouse selection copying and optional daily version notices, with offline and non-interactive opt-outs.
|
|
8
|
+
- Preserve tool admission and nested-call receipts, cancellable process ownership, deferred discovery, recoverable output and integration health handling. Code Mode is removed.
|
|
9
|
+
- Strengthen file edit preconditions, workspace lifecycle and completion evidence; opening the editor no longer requires an eager workspace snapshot.
|
|
10
|
+
- Require Bun 1.3.0 or newer for the native renderer. Existing session logs remain append-only.
|
|
11
|
+
|
|
3
12
|
## 0.2.1 — 2026-09-30
|
|
4
13
|
|
|
5
14
|
- Handle Ctrl+C and termination signals while startup awaits resources or workspace snapshots, restoring the terminal before exit.
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ is ever summarized by a model, and nothing is ever rewritten in place.
|
|
|
17
17
|
messages[] frozen system prefix + handoff + this node's events
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Version **0.
|
|
20
|
+
Version **0.3.0**. The on-disk session format will still change; `0.x` is that
|
|
21
21
|
statement. Published as `@visual-z/xeno`; the license is `UNLICENSED` (readable and installable, no reuse granted).
|
|
22
22
|
|
|
23
23
|
## Install
|
|
@@ -52,6 +52,7 @@ Validation commands and current evidence are recorded in
|
|
|
52
52
|
- Writing branches start from the parent's working copy, including uncommitted files, in an isolated Git worktree. Adopt continues that checkout.
|
|
53
53
|
- Independent readers and read-only branches run in a bounded window; writes and state changes are barriers. Results remain in request order.
|
|
54
54
|
- `note(kind="pin", work_id, event, what)` links original observations; `supersedes` replaces an earlier reference. `history(work_id=...)`, `history(resource=...)`, and `history(events=[...])` locate or batch-read the originals.
|
|
55
|
+
- Verified history projections use a bounded per-session cache. Original ancestry and sealed digests are checked before every hit; a lost cache is rebuilt from the log.
|
|
55
56
|
|
|
56
57
|
Use `/new` or `--fresh` to start with the current tool protocol. Existing sessions
|
|
57
58
|
keep their saved frozen schemas and concurrency policy. New sessions default to
|
|
@@ -78,6 +79,17 @@ authorized `--budget unlimited` on both `plan` and `run` records costs without a
|
|
|
78
79
|
dollar ceiling; upstream errors stop the trial, with HTTP 402 marked as awaiting top-up.
|
|
79
80
|
This checks a controlled configuration, not stock Pi or an official leaderboard.
|
|
80
81
|
|
|
82
|
+
The new single-factor protocol separately compares tool profiles, the carried
|
|
83
|
+
working set, and strict verification. It runs four isolated scenarios through
|
|
84
|
+
real sessions and grades files, fresh command evidence, and pending human review.
|
|
85
|
+
Use `bun scripts/eval-harness.ts smoke --output /absolute/new/eval-directory`,
|
|
86
|
+
then `bun scripts/eval-harness.ts audit --output /absolute/new/eval-directory`.
|
|
87
|
+
Smoke has no paid requests and reports synthetic usage. `plan` and `run` require
|
|
88
|
+
the same explicit numeric `--budget`; inputs, source, schemas, model settings,
|
|
89
|
+
and paired order are frozen before spending. Started directories cannot be
|
|
90
|
+
replayed automatically. Failures and missing trials remain in the denominator,
|
|
91
|
+
and unknown charges retain their reservation. Details are in `docs/benchmarks.md`.
|
|
92
|
+
|
|
81
93
|
## Usage
|
|
82
94
|
|
|
83
95
|
```
|
|
@@ -92,6 +104,8 @@ xeno [-m <model>] [--provider <name>] [--api-key <key>]
|
|
|
92
104
|
[--system-prompt <text-or-file>] [--append-system-prompt <text>]
|
|
93
105
|
[--no-context-files|-nc]
|
|
94
106
|
[--tools names] [--exclude-tools names] [--no-tools]
|
|
107
|
+
[--tool-profile coding|readonly|research|data|full]
|
|
108
|
+
[--acceptance-file <contract.json>]
|
|
95
109
|
[--allow-tools names] [--approve|--no-approve]
|
|
96
110
|
[--theme <name|path>] [--use-theme <name>] [--no-themes]
|
|
97
111
|
[--tui-mode regular|fullscreen]
|
|
@@ -102,6 +116,28 @@ xeno [-m <model>] [--provider <name>] [--api-key <key>]
|
|
|
102
116
|
```
|
|
103
117
|
|
|
104
118
|
- No prompt, a tty: interactive session.
|
|
119
|
+
- `/acceptance` shows declared command checks and human criteria. `/accept ID`
|
|
120
|
+
records your acceptance of a human criterion; subsequent edits invalidate it.
|
|
121
|
+
Use `--acceptance-file` or a trusted project's `.xeno/acceptance.json` with
|
|
122
|
+
`{ "version": 1, "id": "task", "criteria": [{ "id": "tests", "kind": "command", "description": "tests pass", "command": "bun test" }] }`.
|
|
123
|
+
The declaration is frozen in the session. A normal execution exit does not
|
|
124
|
+
imply that human acceptance has been granted.
|
|
125
|
+
A contract may additionally declare `targets`, each with `id`, `build`
|
|
126
|
+
(a command criterion ID), `artifacts: { paths, output_roots }`, and `entry`
|
|
127
|
+
(one of those artifact paths). Optional `interpreter` is an absolute executable
|
|
128
|
+
path; `args` are literal strings, `workdir` stays inside the workspace, and
|
|
129
|
+
`port` declares an IPv4 loopback listener. A human criterion binds to it with
|
|
130
|
+
`target: "ID"`. Enable the `target` tool explicitly with `--tools` or the
|
|
131
|
+
`full` profile; the default coding profile stays compact. Run the declared
|
|
132
|
+
build with its exact artifact plan, then `target({action:"start",target:"ID"})`.
|
|
133
|
+
`inspect` rechecks the owned instance, build inputs, artifact/executable identity
|
|
134
|
+
and port ownership. `/acceptance` shows the entry, build, PID and session;
|
|
135
|
+
`/accept ID` binds your decision to that observed instance. Rebuilds, changed
|
|
136
|
+
files, process exits or lost ownership require fresh acceptance. The first
|
|
137
|
+
implementation observes macOS executable identity with the system `lsof` and
|
|
138
|
+
Linux identity with `/proc`; port checks require system `lsof`. Missing OS
|
|
139
|
+
evidence remains unknown. It never adopts an existing server or proves visual
|
|
140
|
+
correctness automatically, and a resumed session cannot reattach an old PID.
|
|
105
141
|
- A prompt, or `-p` / `--print`: one shot, then exit. `-p` puts only the
|
|
106
142
|
assistant's final text on stdout; the banner, usage, thinking, and tool
|
|
107
143
|
lines go to stderr. Piped stdin is appended after the command-line prompt.
|
|
@@ -231,19 +267,43 @@ Project trust (`/trust`, `--approve`) only decides whether to load
|
|
|
231
267
|
It does not change what tools can do. `AGENTS.md` is loaded whether the
|
|
232
268
|
project is trusted or not.
|
|
233
269
|
|
|
270
|
+
New sessions default to the nine-tool `coding` profile: read, grep, find,
|
|
271
|
+
write, edit, exec_command, write_stdin, history and note. `readonly`,
|
|
272
|
+
`research` (with branch/resolve), `data` (with query) and `full` choose other
|
|
273
|
+
builtin defaults. Extensions remain available; explicit `--tools` overrides
|
|
274
|
+
the profile and exclusions apply last. Resumed sessions keep their frozen tools.
|
|
275
|
+
|
|
234
276
|
`--tools` / `--exclude-tools` / `--no-tools` remove tools from the schema
|
|
235
|
-
the model sees. `--allow-tools
|
|
277
|
+
the model sees. `--allow-tools exec_command` lets commands run without asking, which
|
|
236
278
|
is what a CI job that has no tty has to pass explicitly. Without a tty,
|
|
237
279
|
an `ask` is a deny that names that flag.
|
|
238
280
|
|
|
239
281
|
## Workspace data
|
|
240
282
|
|
|
283
|
+
Opening a session does not snapshot the working directory. File tools save the
|
|
284
|
+
original bytes before editing and record the observed result. `/back` restores
|
|
285
|
+
only recorded paths, preserves unrelated files, and refuses conflicting external
|
|
286
|
+
edits. Conversation-only navigation leaves files unchanged.
|
|
287
|
+
|
|
288
|
+
Shell effects are not automatically reversible. Opt in for a new session with
|
|
289
|
+
`--checkpoint-shell` to capture eligible Git files around commands. This uses a
|
|
290
|
+
private index and object store without changing your staging area or HEAD. Git
|
|
291
|
+
ignored paths, non-regular files, untracked files over 2 MiB, and effects outside
|
|
292
|
+
the workspace are excluded. Capture is best effort; unavailable recovery does
|
|
293
|
+
not prevent a conversation or change the command's exit result. A running process
|
|
294
|
+
has a pending checkpoint until its exit is observed; overlapping writes make that
|
|
295
|
+
checkpoint unavailable. Initial capture can take time.
|
|
296
|
+
|
|
297
|
+
Verification observes a bounded source scope separately from file recovery.
|
|
298
|
+
Unknown observations cannot certify a completion claim. Old snapshot logs remain
|
|
299
|
+
readable; new file records and scope markers are appended without rewriting them.
|
|
300
|
+
|
|
241
301
|
A session writes into the workspace:
|
|
242
302
|
|
|
243
303
|
| Path | |
|
|
244
304
|
|---|---|
|
|
245
305
|
| `.xeno/sessions/<id>/` | node logs, handoffs, metadata journal, derived manifest, and observation artifacts |
|
|
246
|
-
| `.xeno/blobs/` | content-addressed file
|
|
306
|
+
| `.xeno/blobs/` | content-addressed before/after file versions used by `/back` |
|
|
247
307
|
|
|
248
308
|
That tree grows with every turn and every snapshot. There is no garbage
|
|
249
309
|
collection yet. Dead branches from `/back` stay on disk.
|
|
@@ -297,16 +357,29 @@ Shipped, not planned:
|
|
|
297
357
|
Repeated content, renamed completion claims and gate regressions do not reset
|
|
298
358
|
the three-segment stop rule. This is an anti-stall heuristic, not proof of
|
|
299
359
|
relevance or completion; [`research M.4`](docs/agent-harness-research.md) states its limits.
|
|
300
|
-
- A terminal UI using
|
|
301
|
-
Markdown,
|
|
302
|
-
projection across handoff boundaries. Includes tool diffs, historical
|
|
303
|
-
|
|
360
|
+
- A terminal UI using pinned OpenTUI, a native text editor, autocomplete,
|
|
361
|
+
Markdown, a command palette and searchable pickers. Durable history is a pure
|
|
362
|
+
log projection across handoff boundaries. Includes tool diffs, historical
|
|
363
|
+
folds, terminal colours, `keybindings.json`, `/settings` and `/hotkeys`.
|
|
304
364
|
|
|
305
365
|
- Context files: `AGENTS.md` / `CLAUDE.md` and the override chain, plus
|
|
306
366
|
`--system-prompt` / `--append-system-prompt` / `-nc`.
|
|
307
367
|
- Skills (`~/.xeno/skills/`, `/skill:name`) and prompt templates
|
|
308
|
-
|
|
368
|
+
support progressive loading, user visibility, explicit tool intersections
|
|
369
|
+
and isolated `context: fork`. Unsupported execution fields are diagnosed.
|
|
370
|
+
Loaded instructions keep frozen source hashes and recoverable artifacts.
|
|
371
|
+
Templates use `~/.xeno/prompts/` and `/name`. Untrusted projects do not load
|
|
309
372
|
project-level copies.
|
|
373
|
+
- `/tasks` shows dependency readiness and claims. Long tasks can use
|
|
374
|
+
`note(kind="next", work_id="b", depends_on=["a"], ...)`, then
|
|
375
|
+
`claim_work` / `release_work`; these are scheduling intent, separate from
|
|
376
|
+
the `done` command proof. Ordinary short tasks do not need a graph.
|
|
377
|
+
- `branch(background=true)` returns an owned task reference. `/branches`
|
|
378
|
+
lists its current capability; `/branches wait|cancel|status <node>` joins or
|
|
379
|
+
controls it without a model request. `resolve` exposes the same actions.
|
|
380
|
+
Restarted unfinished tasks remain unknown; sealed outcomes can be joined.
|
|
381
|
+
- `/processes` separates the last logged observation from a live handle.
|
|
382
|
+
Session exit drains its own processes and archives unread output.
|
|
310
383
|
- `/reload` refreshes local models, settings, keybindings, context files, skills,
|
|
311
384
|
templates and themes without losing the draft or active model. A changed system
|
|
312
385
|
prefix checkpoints first; unchanged resources do not create a new segment.
|
|
@@ -330,7 +403,11 @@ Shipped, not planned:
|
|
|
330
403
|
Copilot subscription flows. See Known gaps.
|
|
331
404
|
|
|
332
405
|
Images can be attached with `@path`, a dropped image path, or Ctrl+V (files,
|
|
333
|
-
image, then text).
|
|
406
|
+
image, then text). Pasted and dropped images become `[Image #N]` tags at the
|
|
407
|
+
caret. Left/Right cross a whole tag; Backspace/Delete remove it in one step,
|
|
408
|
+
and Undo restores it. Moving the caret next to a tag shows its preview above
|
|
409
|
+
the input (filename and dimensions when image display is unavailable).
|
|
410
|
+
`/attachments` remains available for removal; queue recall and history
|
|
334
411
|
forks retain them. The log stores immutable blob references. Provider adapters
|
|
335
412
|
load verified bytes for Anthropic, Chat Completions and Responses; text-only
|
|
336
413
|
models receive a visible placeholder. Custom models declare `input: ["text", "image"]`
|
|
@@ -360,27 +437,30 @@ unblocking affects new submissions, preserving historical request prefixes.
|
|
|
360
437
|
- **`/back` does not restore symlinks.**
|
|
361
438
|
- **`.xeno/` is not collected.** Snapshots and abandoned session branches
|
|
362
439
|
stay until you delete them.
|
|
363
|
-
- **No telemetry, `/share`,
|
|
364
|
-
refusals (`docs/PI_PARITY.md` D15, D16), not backlog.
|
|
440
|
+
- **No telemetry, `/share`, self-installing updates, or `/compact`.** Those
|
|
441
|
+
are refusals (`docs/PI_PARITY.md` D15, D16), not backlog. xeno does say
|
|
442
|
+
when a newer version is published, at most once a day and never installing
|
|
443
|
+
it; `docs/install.md` (Updates) lists what that request reveals and every
|
|
444
|
+
way to turn it off.
|
|
365
445
|
|
|
366
446
|
`docs/TUI_ARCHITECTURE.md` describes the active interaction path and verification.
|
|
367
447
|
`docs/PI_PARITY.md` records deliberate differences and remaining Pi features.
|
|
368
|
-
|
|
369
|
-
|
|
448
|
+
The native editor supports undo (Ctrl+_ or Ctrl+/) and redo (Ctrl+Shift+Z).
|
|
449
|
+
Registered image tags move and delete as whole units and share text undo history.
|
|
370
450
|
Ctrl+Z suspends the CLI on POSIX; `fg` restores the terminal and unsent draft.
|
|
371
451
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
452
|
+
Fullscreen is the default. Ctrl+P opens commands, Ctrl+L selects a model,
|
|
453
|
+
PgUp/PgDn scroll, and Ctrl+Home/End jump to the start or latest messages.
|
|
454
|
+
Ctrl+O expands tools, Ctrl+T expands thinking, and Ctrl+B toggles the sidebar.
|
|
455
|
+
Mouse selection copies plain text when `fullscreenCopyOnSelect` is enabled;
|
|
456
|
+
Ctrl+X copies a selection or the last reply. Exiting restores the terminal and
|
|
457
|
+
prints the conversation and a resume command. The composer stays at the bottom
|
|
458
|
+
and grows with the draft; pickers and forms use the same input area.
|
|
379
459
|
|
|
380
460
|
`/settings theme` previews built-in and custom themes from the config directory's
|
|
381
461
|
`themes/`. Escape restores the prior theme. Custom files reload after valid edits;
|
|
382
|
-
`auto` follows terminal light/dark changes
|
|
383
|
-
|
|
462
|
+
`auto` follows terminal light/dark changes. Built-in themes keep the terminal's
|
|
463
|
+
background and derive readable accents from its colour replies. Namespaced action IDs work in `keybindings.json`
|
|
384
464
|
alongside existing xeno names; an empty array disables an action in its own scope.
|
|
385
465
|
|
|
386
466
|
## Why this shape
|
|
@@ -575,10 +655,10 @@ Every path below was checked with `test -f` on this checkout.
|
|
|
575
655
|
| `runtime/benchmark.ts` | actual Session trials, bounded spending and cache diagnostics |
|
|
576
656
|
| `runtime/loop.ts` | orchestration; every judgement delegates to a pure function in core |
|
|
577
657
|
| `runtime/auth.ts` / `oauth.ts` | credential order and subscription flows |
|
|
578
|
-
| **`src/
|
|
579
|
-
|
|
|
580
|
-
|
|
|
581
|
-
|
|
|
658
|
+
| **`src/tui/`** | OpenTUI view components and pure display helpers; no harness imports |
|
|
659
|
+
| `tui/ui/composer.ts` / `atomic-draft.ts` / `messages.ts` / `screen.ts` | native editing, atomic image tags, messages and layout |
|
|
660
|
+
| **`src/apps/`** | `cli.ts` — OpenTUI for TTY, clean print/JSON output otherwise |
|
|
661
|
+
| `apps/ui/app.ts` / `project.ts` / `status-model.ts` | controller, transcript projection and status facts |
|
|
582
662
|
| `apps/startup.ts` / `shell.ts` / `commands.ts` | flags, session, slash commands |
|
|
583
663
|
|
|
584
664
|
## Properties the tests pin
|
package/docs/install.md
CHANGED
|
@@ -16,7 +16,7 @@ The entry point is `src/apps/cli.ts` with `#!/usr/bin/env bun`. The
|
|
|
16
16
|
process uses Bun APIs (`Bun.file`, `Bun.spawn`, `Bun.spawnSync`,
|
|
17
17
|
`for await (const line of console)`). Those are why the runtime is Bun.
|
|
18
18
|
|
|
19
|
-
`engines.bun` is `>=1.
|
|
19
|
+
`engines.bun` is `>=1.3.0` (required by the OpenTUI renderer, D20). This checkout was run on **Bun 1.3.10**.
|
|
20
20
|
Node is untested. That is a fact about this tree, not a promise that
|
|
21
21
|
Node support is coming.
|
|
22
22
|
|
|
@@ -30,16 +30,14 @@ cd xeno
|
|
|
30
30
|
bun install
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`bun install` pulls the provider
|
|
34
|
-
syntax highlighting, Mermaid, Photon image processing, and TypeScript tooling.
|
|
33
|
+
`bun install` pulls the provider SDKs, OpenTUI and its platform renderer,
|
|
34
|
+
Markdown, syntax highlighting, Mermaid, Photon image processing, and TypeScript tooling.
|
|
35
35
|
The exact versions are in `package.json` and `bun.lock`.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this machine; Linux/Windows payloads and routing are checked without claiming
|
|
42
|
-
native device acceptance.
|
|
37
|
+
OpenTUI supplies the native renderer through its platform dependencies.
|
|
38
|
+
Clipboard integration uses terminal OSC 52 and platform commands; the old
|
|
39
|
+
vendored Pi native helpers are no longer packaged. Release package checks run
|
|
40
|
+
on macOS arm64; they do not establish Linux or Windows device acceptance.
|
|
43
41
|
|
|
44
42
|
Run the CLI without linking:
|
|
45
43
|
|
|
@@ -68,10 +66,49 @@ Each invocation starts a new session. `xeno -c` continues the newest one;
|
|
|
68
66
|
and nothing is deleted. Resume restores the saved model. An explicit
|
|
69
67
|
`--model` switches at a checkpoint while keeping the original log intact.
|
|
70
68
|
|
|
71
|
-
Startup loads resources and
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
the intended project or pass
|
|
69
|
+
Startup loads resources and opens or restores the session. Workspace snapshots
|
|
70
|
+
are deferred until file tools need them, so opening an editor does not scan the
|
|
71
|
+
whole project. Slow initialization shows its current stage; Ctrl+C cancels it
|
|
72
|
+
and restores the terminal. Start inside the intended project or pass
|
|
73
|
+
`--workspace <path>`.
|
|
74
|
+
|
|
75
|
+
## Updates
|
|
76
|
+
|
|
77
|
+
xeno says when a newer version is published. It never installs one: the
|
|
78
|
+
notice names the command, and you run it.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
xeno 0.3.0 is available (you have 0.2.1)
|
|
82
|
+
bun add -g @visual-z/xeno@latest
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The command follows where this copy is installed: `bun add -g` under
|
|
86
|
+
`~/.bun/install/global/`, `npm i -g` under an npm global prefix
|
|
87
|
+
(`…/lib/node_modules/`, or `…\npm\node_modules\` on Windows). Anywhere else
|
|
88
|
+
the notice points to this file. A checkout updates with `git pull`.
|
|
89
|
+
|
|
90
|
+
- **Request.** `GET https://registry.npmjs.org/-/package/@visual-z%2fxeno/dist-tags`
|
|
91
|
+
(an 18-byte answer). If that fails, `GET …/@visual-z%2fxeno/latest`.
|
|
92
|
+
`npm_config_registry` replaces the host. The timeout is 3 seconds.
|
|
93
|
+
- **What the registry learns.** Your IP address, the time, the package name
|
|
94
|
+
and Bun's default `User-Agent` (`Bun/<version>`). No identifier, no query
|
|
95
|
+
string, nothing from your session.
|
|
96
|
+
- **How often.** At most once every 24 hours. The time and the version seen
|
|
97
|
+
are kept in `~/.xeno/update-check.json`. A failed attempt is recorded too,
|
|
98
|
+
so a closed network is not retried on every start.
|
|
99
|
+
- **When.** In the background after the editor opens. Startup never waits for
|
|
100
|
+
it, and quitting cancels it. A version found during a session is shown only
|
|
101
|
+
while that session is still empty. Otherwise it waits for the next start,
|
|
102
|
+
so it never interrupts a turn.
|
|
103
|
+
|
|
104
|
+
Nothing is sent when any of these holds:
|
|
105
|
+
|
|
106
|
+
- `--offline` or `XENO_OFFLINE=1`
|
|
107
|
+
- `XENO_NO_UPDATE_CHECK` or `NO_UPDATE_NOTIFIER` is set (to anything but empty, `0` or `false`)
|
|
108
|
+
- `"updateCheck": false` in `settings.json` (*Check for updates* in `/settings`)
|
|
109
|
+
- `CI` or `CONTINUOUS_INTEGRATION` is set, by the same rule
|
|
110
|
+
- the run is not the interactive app: `-p`, `--mode json`, `--export`, `--list-models`, `models --refresh`, package and `config` commands, `--version`
|
|
111
|
+
- xeno runs from a checkout: its package root is not under `node_modules`, or has a `.git` (`bun src/apps/cli.ts`, `bun run agent`, `bun link`)
|
|
75
112
|
|
|
76
113
|
## Credentials
|
|
77
114
|
|
|
@@ -129,6 +166,7 @@ settings.json theme, thinking, defaults
|
|
|
129
166
|
trust.json which directories may load project settings
|
|
130
167
|
models.json optional user provider/model configuration
|
|
131
168
|
models-cache.json generated catalog cache (written by `xeno models --refresh`)
|
|
169
|
+
update-check.json when the last update check ran, and the newest version it saw
|
|
132
170
|
keybindings.json optional
|
|
133
171
|
prompts/ skills/ optional
|
|
134
172
|
```
|
package/docs/providers.md
CHANGED
|
@@ -4,6 +4,17 @@ xeno 内置 Pi 0.99.1 和 OpenCode 所用 Models.dev 的固定源并集:226
|
|
|
4
4
|
|
|
5
5
|
来源固定为 Pi `1b347794e2a630e4359f2584f4eea388145d0ddf`、OpenCode `2fa3363c924c5c3e367b84a87ae478296a0ed59b`,Models.dev JSON SHA256 `7345de7358c471e78ac756aed556cd804677cd0d8748b71edc3931225440158f`。SDK 固定为 `@earendil-works/pi-ai@0.99.1`;引入它是为了复用维护中的原生协议和签名处理,避免在 xeno 重写十套 SDK。SDK 仅在 runtime;原有 TUI vendor 的版本与升级方式独立。
|
|
6
6
|
|
|
7
|
+
## 最快的接入:`/login`
|
|
8
|
+
|
|
9
|
+
在交互界面输入 `/login`(`/login <provider>` 直达;`/connect` 是同一命令的别名)。一个列表里包含全部接入方式:
|
|
10
|
+
|
|
11
|
+
- **订阅账户**:Claude Pro/Max、ChatGPT Plus/Pro(Codex)、GitHub Copilot,走浏览器或设备码登录。
|
|
12
|
+
- **API key**:选择目录中的供应商(Anthropic、OpenAI、Gemini、DeepSeek、OpenRouter、Kimi、智谱、硅基流动、通义、豆包等,可搜索),粘贴 key(掩码显示;`$MY_KEY` 读取环境变量)。
|
|
13
|
+
- **+ Custom endpoint…**:OpenAI Chat Completions / OpenAI Responses / Anthropic Messages / Gemini 兼容地址,或 Ollama、LM Studio。填 base URL(粘贴完整的 `…/chat/completions`、`…/v1/messages` 会自动截断)、ID 和 key 后,xeno 用这个 key 向端点列出模型(OpenAI `GET /models`;Anthropic `GET /v1/models`;Gemini `GET /v1beta/models`),勾选要启用的模型后保存。
|
|
14
|
+
- **⚙ Manage connected…**:重新列出并选择模型、更换 key 或断开。
|
|
15
|
+
|
|
16
|
+
`/model` 只显示已接入且凭据可用的模型,最近使用的排在最前。保存位置与手写配置相同:key 在 `~/.xeno/auth.json`(0600),端点和启用的模型在 `~/.xeno/models.json` 的 `providers.<id>`。
|
|
17
|
+
|
|
7
18
|
## 目录和选择
|
|
8
19
|
|
|
9
20
|
```sh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visual-z/xeno",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Segmented append-only agent log with pure context and handoff projections",
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"docs/providers.md"
|
|
16
16
|
],
|
|
17
17
|
"engines": {
|
|
18
|
-
"bun": ">=1.
|
|
18
|
+
"bun": ">=1.3.0"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
|
-
"type": "git"
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/visual-z/xeno.git"
|
|
22
23
|
},
|
|
23
24
|
"scripts": {
|
|
24
25
|
"test": "bun test",
|
|
@@ -37,8 +38,8 @@
|
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@anthropic-ai/sdk": "^0.127.0",
|
|
39
40
|
"@earendil-works/pi-ai": "0.99.1",
|
|
41
|
+
"@opentui/core": "0.5.14",
|
|
40
42
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
41
|
-
"get-east-asian-width": "1.6.0",
|
|
42
43
|
"grok-mermaid": "0.2.3",
|
|
43
44
|
"highlight.js": "10.7.3",
|
|
44
45
|
"marked": "18.0.11"
|
package/src/apps/approve.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { PermissionRequest } from "../core/permissions.ts";
|
|
11
11
|
import type { ApprovalChoice } from "../runtime/tools.ts";
|
|
12
|
-
import type {
|
|
12
|
+
import type { ListItem } from "../tui/ui/list-model.ts";
|
|
13
13
|
|
|
14
14
|
export function approvalHeading(request: PermissionRequest): string {
|
|
15
15
|
if (request.command !== undefined) {
|
|
@@ -24,8 +24,8 @@ export function approvalHeading(request: PermissionRequest): string {
|
|
|
24
24
|
return `${request.tool} wants to run. Approval is the last look, not a boundary.`;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function approvalItems(request: PermissionRequest):
|
|
28
|
-
const items:
|
|
27
|
+
export function approvalItems(request: PermissionRequest): ListItem[] {
|
|
28
|
+
const items: ListItem[] = [{ value: "once", label: "this once" }];
|
|
29
29
|
if (request.command !== undefined) {
|
|
30
30
|
items.push({ value: "command", label: "this command this session" });
|
|
31
31
|
}
|
package/src/apps/commands.ts
CHANGED
|
@@ -16,10 +16,16 @@ import { skillInvocationMessage, type Skill } from "../core/skills.ts";
|
|
|
16
16
|
|
|
17
17
|
export type Command =
|
|
18
18
|
| { kind: "extension"; name: string; args: string }
|
|
19
|
-
| { kind: "prompt"; text: string }
|
|
19
|
+
| { kind: "prompt"; text: string; skill?: Skill }
|
|
20
20
|
| { kind: "quit" }
|
|
21
21
|
| { kind: "usage" }
|
|
22
22
|
| { kind: "state" }
|
|
23
|
+
| { kind: "context" }
|
|
24
|
+
| { kind: "acceptance" }
|
|
25
|
+
| { kind: "tasks" }
|
|
26
|
+
| { kind: "branches"; action?: "wait" | "cancel" | "status"; node?: number }
|
|
27
|
+
| { kind: "processes" }
|
|
28
|
+
| { kind: "accept"; criterion: string }
|
|
23
29
|
| { kind: "model"; pattern?: string }
|
|
24
30
|
| { kind: "scoped-models" }
|
|
25
31
|
| { kind: "login"; provider?: string }
|
|
@@ -54,7 +60,7 @@ export const COMMANDS = [
|
|
|
54
60
|
{ name: "scoped-models", description: "Enable and order models for cycling", busy: "idle" },
|
|
55
61
|
{ name: "thinking", description: "Set reasoning effort", argumentHint: "[level]", busy: "idle" },
|
|
56
62
|
{ name: "settings", description: "Configure appearance, input and model behavior", argumentHint: "[key value]", busy: "idle" },
|
|
57
|
-
{ name: "login", description: "
|
|
63
|
+
{ name: "login", description: "Connect a provider: subscription, API key, gateway or local server", argumentHint: "[provider]", busy: "idle" },
|
|
58
64
|
{ name: "logout", description: "Remove a stored credential", argumentHint: "[provider]", busy: "idle" },
|
|
59
65
|
{ name: "new", description: "Start a new session", busy: "idle" },
|
|
60
66
|
{ name: "resume", description: "Search saved sessions", argumentHint: "[id]", busy: "idle" },
|
|
@@ -66,6 +72,12 @@ export const COMMANDS = [
|
|
|
66
72
|
{ name: "session", description: "Show session details", busy: "run" },
|
|
67
73
|
{ name: "usage", description: "Show token usage and costs", busy: "run" },
|
|
68
74
|
{ name: "state", description: "Show log and handoff state", busy: "run" },
|
|
75
|
+
{ name: "context", description: "Explain context sources, omissions and token estimates", busy: "run" },
|
|
76
|
+
{ name: "acceptance", description: "Show declared criteria and current evidence", busy: "idle" },
|
|
77
|
+
{ name: "tasks", description: "Show work dependencies, readiness and ownership", busy: "run" },
|
|
78
|
+
{ name: "branches", description: "Inspect or join owned background work", argumentHint: "[wait|cancel|status node]", busy: "idle" },
|
|
79
|
+
{ name: "processes", description: "Show process observations and available handles", busy: "run" },
|
|
80
|
+
{ name: "accept", description: "Record your acceptance of a human criterion", argumentHint: "<criterion ID>", busy: "idle" },
|
|
69
81
|
{ name: "export", description: "Export HTML or JSONL", argumentHint: "[file]", busy: "idle" },
|
|
70
82
|
{ name: "import", description: "Import a portable JSONL session", argumentHint: "<file>", busy: "idle" },
|
|
71
83
|
{ name: "debug", description: "Write local terminal diagnostics", argumentHint: "[file]", busy: "run" },
|
|
@@ -84,8 +96,15 @@ export const COMMANDS = [
|
|
|
84
96
|
|
|
85
97
|
export const BUILTIN_COMMAND_NAMES = COMMANDS.map((command) => command.name);
|
|
86
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Hidden spellings of a listed command. Not in COMMANDS, so the `/` menu,
|
|
101
|
+
* the startup hint and completion show one entry per job.
|
|
102
|
+
*/
|
|
103
|
+
const ALIASES: Readonly<Record<string, string>> = { connect: "login" };
|
|
104
|
+
|
|
87
105
|
export function commandDefinition(input: string): (typeof COMMANDS)[number] | undefined {
|
|
88
|
-
const
|
|
106
|
+
const typed = input.trim().split(/\s/, 1)[0]?.slice(1).toLowerCase() ?? "";
|
|
107
|
+
const name = ALIASES[typed] ?? typed;
|
|
89
108
|
return COMMANDS.find((command) => command.name === name);
|
|
90
109
|
}
|
|
91
110
|
|
|
@@ -95,6 +114,7 @@ export interface SlashCatalog {
|
|
|
95
114
|
readonly prompts: ReadonlyMap<string, PromptTemplate>;
|
|
96
115
|
readonly skills: ReadonlyMap<string, Skill>;
|
|
97
116
|
readonly skillCommands?: boolean;
|
|
117
|
+
readonly skillExecution?: boolean;
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
export function resolveSlashCatalog(
|
|
@@ -140,7 +160,8 @@ export function parseCommand(input: string, catalog?: SlashCatalog): Command {
|
|
|
140
160
|
if (!text.startsWith("/")) return { kind: "prompt", text };
|
|
141
161
|
|
|
142
162
|
const [head, ...rest] = text.split(/\s+/);
|
|
143
|
-
const
|
|
163
|
+
const typed = (head ?? "").toLowerCase();
|
|
164
|
+
const token = `/${ALIASES[typed.slice(1)] ?? typed.slice(1)}`;
|
|
144
165
|
const arg = rest.join(" ");
|
|
145
166
|
|
|
146
167
|
if (!commandDefinition(text)) return resolveRegistered(token, arg, text, rest.length, catalog);
|
|
@@ -157,6 +178,17 @@ export function parseCommand(input: string, catalog?: SlashCatalog): Command {
|
|
|
157
178
|
return { kind: "usage" };
|
|
158
179
|
case "/state":
|
|
159
180
|
return { kind: "state" };
|
|
181
|
+
case "/context": return { kind: "context" };
|
|
182
|
+
case "/acceptance": return { kind: "acceptance" };
|
|
183
|
+
case "/tasks": return { kind: "tasks" };
|
|
184
|
+
case "/processes": return { kind: "processes" };
|
|
185
|
+
case "/branches": {
|
|
186
|
+
if (!arg) return { kind: "branches" };
|
|
187
|
+
const action = rest[0], node = Number(rest[1]);
|
|
188
|
+
return (action === "wait" || action === "cancel" || action === "status") && Number.isSafeInteger(node) && node >= 0 && rest.length === 2
|
|
189
|
+
? { kind: "branches", action, node } : { kind: "unknown", input: text };
|
|
190
|
+
}
|
|
191
|
+
case "/accept": return { kind: "accept", criterion: arg };
|
|
160
192
|
case "/model":
|
|
161
193
|
return arg.length > 0 ? { kind: "model", pattern: arg } : { kind: "model" };
|
|
162
194
|
case "/login":
|
|
@@ -240,7 +272,8 @@ function resolveRegistered(
|
|
|
240
272
|
const name = token.slice("/skill:".length);
|
|
241
273
|
const skill = catalog.skills.get(name);
|
|
242
274
|
if (skill !== undefined) {
|
|
243
|
-
return { kind: "
|
|
275
|
+
if (skill.userInvocable === false) return { kind: "unknown", input: text };
|
|
276
|
+
return { kind: "prompt", text: skillInvocationMessage(skill, arg), ...(catalog.skillExecution ? { skill } : {}) };
|
|
244
277
|
}
|
|
245
278
|
}
|
|
246
279
|
const prompt = catalog.prompts.get(token.slice(1));
|
package/src/apps/completions.ts
CHANGED
|
@@ -1,31 +1,85 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What the composer offers for the text at the cursor. Pure given its
|
|
3
|
+
* sources, so the three cases are tested without a terminal:
|
|
4
|
+
* "/mo|" command names (the palette's own entries)
|
|
5
|
+
* "/thinking h|" the command's argument values
|
|
6
|
+
* "see @src/ap|" workspace paths (ranked fuzzy, quoted when they contain spaces)
|
|
7
|
+
* `@` inside a word (an e-mail) or after a backslash is not a reference.
|
|
8
|
+
*/
|
|
2
9
|
import type { App } from "./startup.ts";
|
|
3
|
-
import { WorkspaceAutocomplete } from "../tui/autocomplete.ts";
|
|
4
10
|
import { SUBSCRIPTION_OPTIONS } from "../runtime/oauth.ts";
|
|
5
|
-
import { modelKey } from "../core/session-position.ts";
|
|
6
11
|
import { supportedThinkingLevels } from "../core/thinking.ts";
|
|
7
12
|
import { SETTINGS_KEYS, DISPLAY_SETTINGS } from "../core/settings.ts";
|
|
13
|
+
import { fuzzyFilter } from "../tui/fuzzy.ts";
|
|
14
|
+
import { COMMANDS } from "./commands.ts";
|
|
15
|
+
import type { Completion } from "../tui/ui/composer.ts";
|
|
16
|
+
import type { ListItem } from "../tui/ui/list-model.ts";
|
|
17
|
+
|
|
18
|
+
export interface CompletionSources {
|
|
19
|
+
readonly commands: readonly { name: string; description: string }[];
|
|
20
|
+
readonly argumentValues: (command: string) => readonly string[];
|
|
21
|
+
readonly paths: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Twelve rows: enough to see siblings of a path without hiding the composer. Assumed. */
|
|
25
|
+
const MAX_ITEMS = 12;
|
|
8
26
|
|
|
9
|
-
export function
|
|
10
|
-
const
|
|
27
|
+
export function completionAt(text: string, cursor: number, sources: CompletionSources): Completion | undefined {
|
|
28
|
+
const before = text.slice(0, cursor);
|
|
29
|
+
const line = before.slice(before.lastIndexOf("\n") + 1);
|
|
30
|
+
const lineStart = cursor - line.length;
|
|
31
|
+
if (line.startsWith("/") && lineStart === 0) {
|
|
32
|
+
const space = line.indexOf(" ");
|
|
33
|
+
if (space < 0) {
|
|
34
|
+
const query = line.slice(1).toLowerCase();
|
|
35
|
+
const items = sources.commands
|
|
36
|
+
.filter(command => command.name.toLowerCase().startsWith(query) || (query.length > 1 && command.name.toLowerCase().includes(query)))
|
|
37
|
+
.map(command => ({ value: `/${command.name} `, label: `/${command.name}`, description: command.description }));
|
|
38
|
+
return items.length ? { start: 0, end: cursor, items: items.slice(0, MAX_ITEMS) } : undefined;
|
|
39
|
+
}
|
|
40
|
+
const command = line.slice(1, space);
|
|
41
|
+
const arg = line.slice(space + 1);
|
|
42
|
+
if (arg.includes(" ")) return undefined;
|
|
43
|
+
const values = sources.argumentValues(command);
|
|
44
|
+
if (!values.length) return undefined;
|
|
45
|
+
const items = fuzzyFilter(values, arg).slice(0, MAX_ITEMS).map(value => ({ value, label: value }));
|
|
46
|
+
return items.length ? { start: space + 1, end: cursor, items } : undefined;
|
|
47
|
+
}
|
|
48
|
+
const at = line.lastIndexOf("@");
|
|
49
|
+
if (at < 0) return undefined;
|
|
50
|
+
const prev = line[at - 1];
|
|
51
|
+
if (prev !== undefined && !/\s/.test(prev)) return undefined;
|
|
52
|
+
const query = line.slice(at + 1);
|
|
53
|
+
if (/\s/.test(query) && !query.startsWith("\"")) return undefined;
|
|
54
|
+
const bare = query.replace(/^"/, "");
|
|
55
|
+
const items: ListItem[] = fuzzyFilter(sources.paths, bare).slice(0, MAX_ITEMS)
|
|
56
|
+
.map(path => ({ value: `@${/\s/.test(path) ? JSON.stringify(path) : path} `, label: path }));
|
|
57
|
+
return items.length ? { start: lineStart + at, end: cursor, items } : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function completionSources(app: App, paths: readonly string[]): CompletionSources {
|
|
61
|
+
const providers = [...new Set([...app.catalog.map(m => m.provider), ...SUBSCRIPTION_OPTIONS.map(p => p.id)])];
|
|
11
62
|
const values: Record<string, () => string[]> = {
|
|
12
|
-
|
|
63
|
+
// Model identities belong to the searchable picker. The generic completer
|
|
64
|
+
// still handles arbitrary argument sources; this app supplies only one
|
|
65
|
+
// model selection surface, rather than a second list with different keys.
|
|
13
66
|
thinking: () => [...supportedThinkingLevels(app.providerConfig.spec)],
|
|
14
|
-
login: () => providers,
|
|
67
|
+
login: () => providers, connect: () => providers, logout: () => Object.keys(app.auth),
|
|
15
68
|
settings: () => [...new Set([...SETTINGS_KEYS, ...DISPLAY_SETTINGS.map(item => item.id)])],
|
|
16
69
|
models: () => ["refresh"], trust: () => ["once", "here", "parent", "never"],
|
|
17
70
|
};
|
|
18
|
-
|
|
19
|
-
...
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
...
|
|
71
|
+
return {
|
|
72
|
+
commands: [...COMMANDS.filter(command => command.name !== "exit"), ...commandEntries(app)],
|
|
73
|
+
argumentValues: command => values[command]?.() ?? [],
|
|
74
|
+
paths,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Built-ins, then extension commands, prompt templates and skills: the same names the palette lists. */
|
|
79
|
+
export function commandEntries(app: App): { name: string; description: string; section: string }[] {
|
|
80
|
+
return [
|
|
81
|
+
...[...app.extensions?.commands ?? []].map(([name, command]) => ({ name, description: command.description ?? "extension command", section: "Extensions" })),
|
|
82
|
+
...[...(app.slash?.prompts.values() ?? [])].map(p => ({ name: p.name, description: p.description ?? "prompt template", section: "Prompts" })),
|
|
83
|
+
...(app.slash?.skillCommands === false ? [] : [...(app.slash?.skills.values() ?? [])].map(s => ({ name: `skill:${s.name}`, description: s.description ?? "skill", section: "Skills" }))),
|
|
29
84
|
];
|
|
30
|
-
return new WorkspaceAutocomplete([...commands, ...registered], app.workspace, paths);
|
|
31
85
|
}
|