zcode-acp-server 0.21.4 → 0.22.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 (51) hide show
  1. package/README.md +6 -7
  2. package/dist/cli.d.ts +8 -5
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +22 -14
  5. package/dist/cli.js.map +1 -1
  6. package/dist/handlers/session.d.ts +17 -1
  7. package/dist/handlers/session.d.ts.map +1 -1
  8. package/dist/handlers/session.js +92 -4
  9. package/dist/handlers/session.js.map +1 -1
  10. package/dist/remote/hub-server.d.ts +7 -7
  11. package/dist/remote/hub-server.d.ts.map +1 -1
  12. package/dist/remote/hub-server.js +33 -33
  13. package/dist/remote/hub-server.js.map +1 -1
  14. package/dist/server.d.ts +5 -0
  15. package/dist/server.d.ts.map +1 -1
  16. package/dist/server.js +6 -0
  17. package/dist/server.js.map +1 -1
  18. package/dist/tui.d.ts +38 -0
  19. package/dist/tui.d.ts.map +1 -0
  20. package/dist/tui.js +96 -0
  21. package/dist/tui.js.map +1 -0
  22. package/docs/CLI.md +33 -76
  23. package/docs/REMOTE-CLIENTS.md +13 -9
  24. package/docs/REMOTE.md +8 -11
  25. package/docs/TROUBLESHOOTING.md +5 -1
  26. package/docs/adr/0007-unified-cli-entry-and-bin-pruning.md +2 -0
  27. package/package.json +3 -7
  28. package/dist/repl/App.d.ts +0 -108
  29. package/dist/repl/App.d.ts.map +0 -1
  30. package/dist/repl/App.js +0 -732
  31. package/dist/repl/App.js.map +0 -1
  32. package/dist/repl/history.d.ts +0 -25
  33. package/dist/repl/history.d.ts.map +0 -1
  34. package/dist/repl/history.js +0 -73
  35. package/dist/repl/history.js.map +0 -1
  36. package/dist/repl/hub.d.ts +0 -87
  37. package/dist/repl/hub.d.ts.map +0 -1
  38. package/dist/repl/hub.js +0 -188
  39. package/dist/repl/hub.js.map +0 -1
  40. package/dist/repl/input-buffer.d.ts +0 -77
  41. package/dist/repl/input-buffer.d.ts.map +0 -1
  42. package/dist/repl/input-buffer.js +0 -174
  43. package/dist/repl/input-buffer.js.map +0 -1
  44. package/dist/repl/model.d.ts +0 -312
  45. package/dist/repl/model.d.ts.map +0 -1
  46. package/dist/repl/model.js +0 -674
  47. package/dist/repl/model.js.map +0 -1
  48. package/dist/repl/run.d.ts +0 -12
  49. package/dist/repl/run.d.ts.map +0 -1
  50. package/dist/repl/run.js +0 -1245
  51. package/dist/repl/run.js.map +0 -1
@@ -57,7 +57,7 @@ ACP editor ────── stdio ──────────┘
57
57
  | `POST /api/upgrade` | required | Trigger the hub's own staleness check — see [Hub self-upgrade](#hub-self-upgrade). |
58
58
  | `GET /api/projects` | required | Known-project list (remote session-create whitelist) — see below. |
59
59
  | `GET /api/projects/sessions?workspacePath=` | required | A project's full session store incl. closed ones — see [Resuming a closed session](#resuming-a-closed-session). |
60
- | `POST /api/instances {workspacePath[, sessionId]}` | required | Create a bridge for one known project — a visible terminal REPL (session-create) or one that boots into a closed session (resume, `sessionId`) — see below. |
60
+ | `POST /api/instances {workspacePath[, sessionId]}` | required | Create a bridge for one known project — a visible terminal TUI window (session-create) or one that boots into a closed session (resume, `sessionId`) — see below. |
61
61
 
62
62
  HTTP auth: `Authorization: Bearer <token>` or `?token=<token>`.
63
63
 
@@ -161,7 +161,7 @@ POST {hub}/api/instances body {"workspacePath":"/Users/me/proj"}
161
161
  bound, not a security boundary: a token holder can already run any
162
162
  editor-bridge session in an arbitrary cwd; the trust boundary is the
163
163
  token itself.
164
- - On create the hub incubates a VISIBLE interactive REPL in the machine's
164
+ - On create the hub incubates a VISIBLE interactive TUI (Martty) in the machine's
165
165
  terminal (ADR-0016, macOS): the project's owner gets a real local CLI
166
166
  window, and its bridge registers with the hub like any serve instance (it
167
167
  appears in `/api/instances` with `origin:"serve"` — allow up to ~20s for
@@ -190,7 +190,7 @@ POST {hub}/api/instances body {"workspacePath":"/Users/me/proj"}
190
190
  spawn instead of racing a duplicate. A client that wants a HEADLESS attach
191
191
  should not POST at all — attach to the instance id the history listing
192
192
  already returns.
193
- - Lifetime depends on the surface: a terminal-REPL instance lives while its
193
+ - Lifetime depends on the surface: a terminal-TUI instance lives while its
194
194
  window lives (the owner closing it retires the bridge — re-create on
195
195
  demand); the headless fallback exists for remote interest only and exits
196
196
  ~10 minutes after the last client detaches AND the last running turn
@@ -249,16 +249,20 @@ POST {hub}/api/instances body {"workspacePath":"/Users/me/proj",
249
249
  replays and the conversation continues with `session/prompt`, entirely in
250
250
  the client. A cold project (no listing yet) lists first — the listing IS
251
251
  the headless incubator.
252
- - **Resume on the desktop (ADR-0017)**: `POST /api/instances` with the
253
- `sessionId` too — the hub incubates a VISIBLE terminal REPL that boots
254
- straight into that conversation (history replays in the window; the same
252
+ - **Resume on the desktop (ADR-0017, amended by ADR-0020)**: `POST
253
+ /api/instances` with the `sessionId` too — the hub incubates a VISIBLE
254
+ terminal TUI window that boots straight into that conversation (the same
255
255
  terminal/`ZCODE_ACP_HUB_TERMINAL` selection and headless fallback as
256
- session-create). This happens EVEN when a serve bridge is already live
256
+ session-create). The window lands on the right session — later prompts run
257
+ with the full history in the backend — but the TUI does not render the
258
+ previous transcript (a fresh boot is a session/new and `/resume` prefers
259
+ session/resume, which carries no history by protocol design; its status
260
+ line says `resumed <id> — previous transcript was not replayed`). This happens EVEN when a serve bridge is already live
257
261
  (the listing incubated one) — the answer is always the NEW instance, the
258
262
  bridge the window runs on; attach to it and `session/load` the same id to
259
263
  follow along in the client (both surfaces then share one bridge, one
260
- backend process). A bogus id still opens the window: the REPL shows the
261
- load failure and falls back to a fresh session. Resuming the same session
264
+ backend process). A bogus id still opens the window: the bridge logs the
265
+ load failure and starts a fresh session instead. Resuming the same session
262
266
  twice pops two windows; only identical concurrent requests share one
263
267
  incubation.
264
268
  - `sessionId` here is the backend store id (`sess_…`), which `session/load`
package/docs/REMOTE.md CHANGED
@@ -73,7 +73,8 @@ directories filtered out), newest activity first. The list gates the POST —
73
73
  paths outside it get 403 (a convenience bound, not a security boundary: a
74
74
  token holder can already drive an editor-bridge session in any cwd; the
75
75
  trust boundary is the token). On create the hub incubates a VISIBLE
76
- interactive REPL in the machine's terminal (ADR-0016, macOS; headless/SSH or
76
+ interactive TUI window in the machine's terminal (ADR-0016 as amended by
77
+ ADR-0020, macOS; headless/SSH or
77
78
  `ZCODE_ACP_HUB_TERMINAL=0` falls back to the detached headless
78
79
  `zcode-acp serve`); its bridge registers back within seconds (budget ~20s)
79
80
  and is reachable like any other instance. The hub itself is a background
@@ -86,7 +87,7 @@ cannot execute scripts or commands programmatically (warpdotdev/warp#1917,
86
87
  Create NEVER reuses a live serve-origin instance (ADR-0016 as amended) —
87
88
  every create incubates its own window, and identical concurrent requests
88
89
  join the same in-flight spawn. A
89
- terminal-REPL instance lives while its window lives; the headless fallback
90
+ terminal-TUI instance lives while its window lives; the headless fallback
90
91
  exits ~10 minutes after the last client detaches and the last turn
91
92
  finishes. Session roots are pinned to the project cwd in both surfaces
92
93
  regardless of what a client sends.
@@ -110,15 +111,11 @@ editor uses — attaching by id joins the conversation's live notification
110
111
  stream, and the hub dedupes sessions shared by several bridges of the same
111
112
  project.
112
113
 
113
- **The local REPL is a hub client too** (ADR-0018). Bare `zcode-acp` in a
114
- shell with the hub env (`ZCODE_ACP_REMOTE_TOKEN` + `ZCODE_ACP_HUB_PORT`)
115
- merges sessions that are live on hub instances into its `/sessions` picker
116
- (marked `live`, `●` while a turn runs) and attaches to them through the hub's
117
- WS proxy as a second ACP client — replay on attach, then live updates, so a
118
- conversation being driven from the phone advances on the desktop in real
119
- time. Without a hub configured or reachable, the picker and resumes behave
120
- exactly as before. Attaching never touches the owning bridge: leaving the
121
- REPL (or `/new`) only detaches this client.
114
+ **The local TUI used to be a hub client too** (ADR-0018, removed with the
115
+ in-house REPL by ADR-0020). The Martty TUI boots its own bridge per window
116
+ and does not merge hub-live sessions into its picker; watching a session
117
+ driven from another client stays a remote-App capability (the hub's WS proxy
118
+ and the `/api` surface are unchanged).
122
119
 
123
120
  Auth is `Authorization: Bearer <token>` or `?token=` (browsers cannot set WS
124
121
  headers); `/api/*` sends `Access-Control-Allow-Origin: *` — the token is the
@@ -405,7 +405,11 @@ requests themselves.
405
405
  `~/.zcode/v2/config.json` marks it `enabled`). The bridge answers the captcha
406
406
  request with `headersApplied:false` and the backend surfaces a clear error;
407
407
  full Start Plan support headless would require solving the Aliyun captcha
408
- outside a browser, which this bridge does not do.
408
+ outside a browser, which this bridge does not do. That boundary is
409
+ deliberate: unofficial clients or proxies that impersonate the desktop app or
410
+ bypass the captcha check won't be shipped or supported here — if the provider
411
+ ever offers an official headless credential path, this bridge will adopt it
412
+ (see #123).
409
413
 
410
414
  ### Interactive TUI / `script` / `expect` fails with `Operation not permitted` under the sandbox
411
415
 
@@ -1,5 +1,7 @@
1
1
  # Unified CLI entry and bin pruning
2
2
 
3
+ > Amended by ADR-0020 (`.zcode/docs/adr/`): bare invocation now launches the Martty TUI — the in-house Ink REPL is deleted; the subcommand surface is unchanged.
4
+
3
5
  The package grew three separate bins (`zcode-acp-server`, `zcode-acp-hub`,
4
6
  `zcode-quota`) plus a planned terminal chat surface. Installing four sibling
5
7
  commands for one tool is noisy, and each new surface would add another. We
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcode-acp-server",
3
- "version": "0.21.4",
3
+ "version": "0.22.0",
4
4
  "description": "Agent Client Protocol (ACP) server bridging headless ZCode to editors like Zed and JetBrains.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -43,15 +43,12 @@
43
43
  "format": "prettier --write src",
44
44
  "lint": "eslint .",
45
45
  "smoke": "node dist/index.js < /dev/null",
46
+ "smoke:tui": "node dist/cli.js tui --check",
46
47
  "prepublishOnly": "pnpm run build && pnpm run test"
47
48
  },
48
49
  "dependencies": {
49
50
  "@agentclientprotocol/sdk": "^1.3.0",
50
- "chalk": "^6.0.0",
51
- "ink": "^7.1.1",
52
- "ink-spinner": "^5.0.0",
53
- "react": "^19.2.8",
54
- "string-width": "^8.2.2",
51
+ "martty": "^0.2.34",
55
52
  "ws": "^8.21.3"
56
53
  },
57
54
  "peerDependencies": {
@@ -60,7 +57,6 @@
60
57
  "devDependencies": {
61
58
  "@eslint/js": "^9.0.0",
62
59
  "@types/node": "^22.0.0",
63
- "@types/react": "^19.2.18",
64
60
  "@types/ws": "^8.18.1",
65
61
  "@typescript-eslint/eslint-plugin": "^8.0.0",
66
62
  "@typescript-eslint/parser": "^8.0.0",
@@ -1,108 +0,0 @@
1
- /**
2
- * Ink UI for the interactive REPL (bare `zcode-acp`).
3
- *
4
- * Rendering model: the whole app draws in a fixed-height frame managed by
5
- * run.ts (full-screen mode) — the transcript is an in-app viewport, and the
6
- * live turn (streaming text, thinking buffer, tool rows), prompt line, and
7
- * pickers render inside it. A pending permission request takes over the
8
- * input area with an arrow-key picker.
9
- */
10
- import { type ReactElement } from "react";
11
- import { type ReplEntry, type ReplStatus, type SessionSummary, type TurnState } from "./model.js";
12
- import { type LineEditor } from "./input-buffer.js";
13
- /** A permission request awaiting the user's choice. */
14
- export interface PermissionPrompt {
15
- /** Box heading ("permission requested" / "plan approval"). */
16
- heading: string;
17
- title: string;
18
- /** Long body shown under the title (e.g. the plan text for ExitPlanMode). */
19
- detail?: string;
20
- options: Array<{
21
- id: string;
22
- name: string;
23
- }>;
24
- }
25
- /** A question awaiting the user's answer (AskUserQuestion via elicitation). */
26
- export interface QuestionPrompt {
27
- title: string;
28
- multiSelect: boolean;
29
- options: Array<{
30
- value: string;
31
- label: string;
32
- }>;
33
- }
34
- /** The interactive `/sessions` picker: resumable project sessions. */
35
- export interface SessionPick {
36
- items: SessionSummary[];
37
- }
38
- /** A picked question answer: chosen option values plus an optional custom text. */
39
- export interface QuestionAnswer {
40
- picked: string[];
41
- custom: string;
42
- }
43
- /** Display state of the question picker (selection, toggles, custom typing). */
44
- export interface PickerState {
45
- index: number;
46
- /** null = option list active; string = custom-answer input in progress. */
47
- custom: string | null;
48
- toggled: string[];
49
- }
50
- export interface AppProps {
51
- /** Completed transcript entries — printed once into native scrollback. */
52
- entries: ReplEntry[];
53
- /** Live turn in progress, null when idle. */
54
- turn: TurnState | null;
55
- /** Pending permission request, null when none. */
56
- permission: PermissionPrompt | null;
57
- /** Pending AskUserQuestion, null when none (renders instead of the input). */
58
- question: QuestionPrompt | null;
59
- /** Pending /sessions picker, null when closed. */
60
- sessionPick: SessionPick | null;
61
- /** Prompts accepted but waiting for the running turn to finish. */
62
- queued: string[];
63
- /** Session status: command menu + model/mode/thought selects. */
64
- status: ReplStatus;
65
- /** True while the bridge/session is starting up. */
66
- busy: boolean;
67
- /**
68
- * True while a /sessions resume is replaying history. Replayed updates
69
- * fold silently into run.ts's buffers — rendering per message made big
70
- * sessions freeze the UI, so App just shows this one-line progress hint.
71
- */
72
- replaying: boolean;
73
- /**
74
- * The prompt line's editor state (text + caret), owned by run.ts's
75
- * external store so submit paths can reset it and rerender snapshots
76
- * stay pure functions of that state.
77
- */
78
- editor: LineEditor;
79
- /** Apply one pure editor op to the prompt line and repaint. */
80
- applyEdit: (op: (e: LineEditor) => LineEditor) => void;
81
- /** Compact plan-quota suffix for the status row (null = not fetched yet). */
82
- quotaLine: string | null;
83
- /**
84
- * Live completion-menu visibility. InputLine mirrors it out of its React
85
- * state on every render (same external-store rationale as the editor), so
86
- * the app-level key handler can give the menu first claim on esc.
87
- */
88
- isMenuOpen: () => boolean;
89
- /** InputLine → external store: publish the current menu visibility. */
90
- onMenuOpenChange: (open: boolean) => void;
91
- onSubmit: (text: string) => void;
92
- onCancelTurn: () => void;
93
- onAnswerPermission: (optionId: string | null) => void;
94
- onAnswerQuestion: (answer: QuestionAnswer | null) => void;
95
- /** null = dismiss the picker without resuming. */
96
- onPickSession: (sessionId: string | null) => void;
97
- /** Prompt-history recall (ADR-0008); run.ts no-ops when nothing to walk. */
98
- onHistoryUp: () => void;
99
- onHistoryDown: () => void;
100
- onExit: () => void;
101
- }
102
- /**
103
- * Colorize markdown code fences: lines between ``` fences render cyan. Line
104
- * level state machine — exported for unit tests.
105
- */
106
- export declare function colorizeCodeFences(text: string): string;
107
- export declare function App(props: AppProps): ReactElement;
108
- //# sourceMappingURL=App.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/repl/App.tsx"],"names":[],"mappings":"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"}