experimental-ash 0.55.0 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/docs/public/advanced/cli-build-and-debugging.md +1 -1
  3. package/dist/docs/public/advanced/dev-tui.md +202 -0
  4. package/dist/docs/public/advanced/meta.json +1 -0
  5. package/dist/docs/public/advanced/vercel-deployment.md +3 -3
  6. package/dist/docs/public/getting-started.mdx +1 -1
  7. package/dist/docs/public/human-in-the-loop.mdx +4 -4
  8. package/dist/docs/public/onboarding.md +2 -2
  9. package/dist/src/cli/dev/{repl.d.ts → repl/repl.d.ts} +1 -1
  10. package/dist/src/cli/dev/{repl.js → repl/repl.js} +1 -1
  11. package/dist/src/cli/dev/tui/layout.d.ts +24 -0
  12. package/dist/src/cli/dev/tui/layout.js +3 -0
  13. package/dist/src/cli/dev/tui/markdown.d.ts +14 -0
  14. package/dist/src/cli/dev/tui/markdown.js +3 -0
  15. package/dist/src/cli/dev/tui/runner.d.ts +205 -0
  16. package/dist/src/cli/dev/tui/runner.js +1 -0
  17. package/dist/src/cli/dev/tui/terminal-frame-buffer.d.ts +21 -0
  18. package/dist/src/cli/dev/tui/terminal-frame-buffer.js +2 -0
  19. package/dist/src/cli/dev/tui/terminal-renderer.d.ts +111 -0
  20. package/dist/src/cli/dev/tui/terminal-renderer.js +12 -0
  21. package/dist/src/cli/dev/tui/terminal-text.d.ts +6 -0
  22. package/dist/src/cli/dev/tui/terminal-text.js +1 -0
  23. package/dist/src/cli/dev/tui/test/index.d.ts +10 -0
  24. package/dist/src/cli/dev/tui/test/index.js +1 -0
  25. package/dist/src/cli/dev/tui/test/mock-terminal.d.ts +28 -0
  26. package/dist/src/cli/dev/tui/test/mock-terminal.js +3 -0
  27. package/dist/src/cli/dev/tui/tui.d.ts +32 -0
  28. package/dist/src/cli/dev/tui/tui.js +1 -0
  29. package/dist/src/cli/dev/tui/types.d.ts +68 -0
  30. package/dist/src/cli/dev/tui/types.js +1 -0
  31. package/dist/src/cli/run.d.ts +66 -0
  32. package/dist/src/cli/run.js +2 -2
  33. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  34. package/dist/src/evals/scorers/autoevals.js +1 -1
  35. package/dist/src/execution/node-step.js +1 -1
  36. package/dist/src/harness/code-mode.js +1 -1
  37. package/dist/src/harness/tool-loop.js +1 -1
  38. package/dist/src/internal/application/package.js +1 -1
  39. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  40. package/dist/src/public/channels/ash.js +1 -1
  41. package/package.json +6 -1
  42. /package/dist/src/cli/dev/{input-requests.d.ts → repl/input-requests.d.ts} +0 -0
  43. /package/dist/src/cli/dev/{input-requests.js → repl/input-requests.js} +0 -0
  44. /package/dist/src/cli/dev/{input.d.ts → repl/input.d.ts} +0 -0
  45. /package/dist/src/cli/dev/{input.js → repl/input.js} +0 -0
  46. /package/dist/src/cli/dev/{terminal.d.ts → repl/terminal.d.ts} +0 -0
  47. /package/dist/src/cli/dev/{terminal.js → repl/terminal.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.55.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 93c1d3c: feat(ash): add new TUI for `ash dev`, replacing the previous REPL by default
8
+ - 6a8ec9f: Code-mode nested tool calls now return raw tool outputs inside the VM and expose those raw outputs to Ash lifecycle handlers.
9
+
3
10
  ## 0.55.0
4
11
 
5
12
  ### Minor Changes
@@ -13,7 +13,7 @@ From your app root:
13
13
  - `ash info` - inspect the discovered authored surface and runtime details
14
14
  - `ash build` - compile `.ash/` artifacts and build the host output
15
15
  - `ash start` - serve the built `.output/` app
16
- - `ash dev` - start the local runtime and open the REPL
16
+ - `ash dev` - start the local runtime and open the interactive [terminal UI](./dev-tui.md)
17
17
  - `ash eval` - run eval suites against the current app or a remote target
18
18
 
19
19
  ## Recommended Loop
@@ -0,0 +1,202 @@
1
+ ---
2
+ title: "Dev TUI (`ash dev`)"
3
+ description: "Run and chat with your agent locally in an interactive terminal UI."
4
+ url: /dev-tui
5
+ ---
6
+
7
+ `ash dev` starts the local runtime and opens an interactive terminal UI (the TUI) where you can
8
+ chat with your agent, watch it stream, approve tool calls, and answer its questions — all without
9
+ leaving the terminal. It is the fastest way to exercise an agent while you iterate.
10
+
11
+ ## Quick Start
12
+
13
+ From your app root:
14
+
15
+ ```bash
16
+ ash dev
17
+ ```
18
+
19
+ This boots the local runtime, starts a dev server, and drops you into the TUI connected to it. Type
20
+ a prompt, press `Enter`, and the agent's response streams in. Press `Ctrl+C` to
21
+ quit; this also shuts the dev server down.
22
+
23
+ ## The Layout
24
+
25
+ The TUI looks something like this (with the different sections being color-coded in reality):
26
+
27
+ ```
28
+ ┌ Ash ────────────────────────────────────────────────────────────────── 7,421 tokens ┐
29
+ │ ╭ User ───────────────────────────────────────────────────────────────────────────╮ │
30
+ │ │ what's the weather in San Francisco? │ │
31
+ │ ╰─────────────────────────────────────────────────────────────────────────────────╯ │
32
+ │ ╭ Log · stdout ···································································╮ │
33
+ │ │ weather-agent session started { sessionId: 'wrun_a1b2c3d4e5f6g7h8i9', │ │
34
+ │ │ channel: 'http' } │ │
35
+ │ ╰·················································································╯ │
36
+ │ ╭ Reasoning ······································································╮ │
37
+ │ │ The user is asking about weather, so I need to load the get-weather skill │ │
38
+ │ │ first. │ │
39
+ │ ╰·················································································╯ │
40
+ │ ╭ Tool · load_skill ──────────────────────────────────────────────────────── done ╮ │
41
+ │ ╰─────────────────────────────────────────────────────────────────────────────────╯ │
42
+ │ ╭ Log · stdout ···································································╮ │
43
+ │ │ weather lookup { city: 'San Francisco', temperatureF: 72, condition: 'Sunny' } │ │
44
+ │ ╰·················································································╯ │
45
+ │ ╭ Tool · get_weather ─────────────────────────────────────────────────────── done ╮ │
46
+ │ ╰─────────────────────────────────────────────────────────────────────────────────╯ │
47
+ │ ╭ Assistant ──────────────────────────────────────────────────────────────────────╮ │
48
+ │ │ Using the local weather tool: San Francisco is currently 72°F and sunny, with a │ │
49
+ │ │ light breeze. │ │
50
+ │ ╰─────────────────────────────────────────────────────────────────────────────────╯ │
51
+ │ ╭ Log · stdout ···································································╮ │
52
+ │ │ weather-agent turn completed { │ │
53
+ │ │ sessionId: 'wrun_a1b2c3d4e5f6g7h8i9', │ │
54
+ │ │ turnId: 'turn_0', │ │
55
+ │ │ sequence: 0 │ │
56
+ │ │ } │ │
57
+ │ ╰·················································································╯ │
58
+ └─────────────────────────────────────────────────────────────────────────────────────┘
59
+ ┌ Input ──────────────────────────────────────────────────────────────────────────────┐
60
+ │ > █ │
61
+ └─────────────────────────────────────────────────────────────────────────────────────┘
62
+ ```
63
+
64
+ The TUI is made up of three regions:
65
+
66
+ - **Header** — the app name and, while a turn is running, live response stats (e.g. tokens per second,
67
+ or total tokens).
68
+ - **Transcript** — the scrollable conversation: your prompts, the agent's streamed messages,
69
+ reasoning, tool calls, subagent activity, connection-authorization prompts, and server/agent logs.
70
+ - **Input / status bar** — where you type, plus a contextual hint line showing the current state
71
+ (`Streaming…`, `Executing tools…`, `Done`, an approval prompt, and so on) and the keys available.
72
+
73
+ ## Keyboard Shortcuts
74
+
75
+ | Key | Action |
76
+ | --------- | ---------------------------------------------------------- |
77
+ | `Enter` | Send the prompt / confirm the current answer |
78
+ | `↑` / `↓` | Scroll the transcript |
79
+ | `Esc` | Back out of (or clear) the current input without answering |
80
+ | `Ctrl+R` | Repaint the screen |
81
+ | `q` | Quit when the agent is idle (a turn has finished) |
82
+ | `Ctrl+C` | Quit at any time, interrupting a running turn |
83
+
84
+ ## Slash Commands
85
+
86
+ Type these as a prompt:
87
+
88
+ - `/new` — start a fresh session. Clears the transcript and the durable session cursor so the next
89
+ turn begins a new server-side conversation.
90
+ - `/exit` (or `/quit`) — leave the TUI and shut the dev server down, the same as `Ctrl+C`.
91
+
92
+ ## Human In The Loop
93
+
94
+ When the agent needs you, the TUI prompts inline and the status bar tells you which keys apply:
95
+
96
+ - **Tool approvals** — answer `y` / `n` to approve or deny.
97
+ - **Questions** — for option questions, use `↑` / `↓` to select and `Enter`
98
+ to confirm; for freeform answers, type and press `Enter`. `Esc` backs out.
99
+
100
+ If you press `Esc` on a pending request and then send a normal message, the request is
101
+ ignored. See [Human In The Loop](../human-in-the-loop.mdx) for how these requests are authored.
102
+
103
+ ## Connection Authorization
104
+
105
+ When a tool needs an authorized [connection](../connections.mdx) — for example an MCP server behind
106
+ OAuth — the TUI renders the authorization challenge inline: the URL to visit and any user/device
107
+ code to enter. The status bar shows a `Waiting for connection authorization` hint until you complete
108
+ the flow in your browser, then the turn resumes automatically.
109
+
110
+ ## Subagents
111
+
112
+ When the agent dispatches a [subagent](../subagents.mdx), its activity renders as its own section in
113
+ the transcript, with a live status in the section header. By default subagent sections are shown in
114
+ full; use `--subagents` to collapse or hide them (see below).
115
+
116
+ ## Logs
117
+
118
+ `stdout` and `stderr` from the dev server and the agent are captured and shown inline in the
119
+ transcript, so you can correlate log output with the turn that produced it. Control how much is shown
120
+ with `--logs`.
121
+
122
+ ## Display Options
123
+
124
+ The TUI's density is configurable. Each of these flags accepts one of `full`, `collapsed`,
125
+ `auto-collapsed`, or `hidden` unless noted otherwise. `auto-collapsed` shows a section in full while
126
+ it is active and collapses it once finished.
127
+
128
+ | Flag | Controls | Default |
129
+ | ----------------------------------- | ----------------------------------------------------------- | ----------------- |
130
+ | `--tools <mode>` | How tool calls render | `auto-collapsed` |
131
+ | `--reasoning <mode>` | How reasoning renders | `full` |
132
+ | `--subagents <mode>` | How subagent sections render | `full` |
133
+ | `--connection-auth <mode>` | How connection-authorization prompts render | `full` |
134
+ | `--assistant-response-stats <mode>` | Header statistic: `tokens` or `tokensPerSecond` | `tokensPerSecond` |
135
+ | `--context-size <tokens>` | Model context window size, shown as a usage percentage | _unset_ |
136
+ | `--logs <mode>` | Which server/agent logs to show: `all`, `stderr`, or `none` | `all` |
137
+
138
+ For example, to keep tool calls fully expanded and show absolute token counts:
139
+
140
+ ```bash
141
+ ash dev --tools full --assistant-response-stats tokens
142
+ ```
143
+
144
+ Set `--context-size` to your model's context window to see how full it is getting:
145
+
146
+ ```bash
147
+ ash dev --context-size 200000
148
+ ```
149
+
150
+ ## Server Options
151
+
152
+ By default `ash dev` binds the dev server to a local interface and uses `$PORT`, falling back to
153
+ `3000`. Override either:
154
+
155
+ ```bash
156
+ ash dev --host 0.0.0.0 --port 4000
157
+ ```
158
+
159
+ ## Connecting To A Remote Server
160
+
161
+ Instead of starting a local server, point the TUI at an already-running one — for example a Vercel
162
+ preview or production deployment:
163
+
164
+ ```bash
165
+ ash dev https://<your-app>
166
+ ```
167
+
168
+ The bare URL is shorthand for `--url https://<your-app>`. When connecting to a remote server, the
169
+ `--host`, `--port`, and `--no-ui` options do not apply.
170
+
171
+ If the deployment uses Vercel preview protection, set `VERCEL_AUTOMATION_BYPASS_SECRET` locally
172
+ before launching so the TUI can authenticate against it. See
173
+ [Vercel Deployment](./vercel-deployment.md) for details.
174
+
175
+ ## Headless Mode
176
+
177
+ Pass `--no-ui` to start the dev server and keep it running without an interactive UI — useful when
178
+ another process drives the HTTP routes:
179
+
180
+ ```bash
181
+ ash dev --no-ui
182
+ ```
183
+
184
+ `ash dev` also falls back to headless automatically when the terminal is not a TTY (for example in
185
+ CI or when output is piped), printing a note so the missing UI is not mistaken for a hang.
186
+
187
+ ## The Classic REPL
188
+
189
+ Before the TUI, `ash dev` opened a simpler line-based REPL. It is still available behind a flag
190
+ during the transition and will be removed in a future release:
191
+
192
+ ```bash
193
+ ash dev --repl
194
+ ```
195
+
196
+ Prefer the default TUI for day-to-day work.
197
+
198
+ ## What To Read Next
199
+
200
+ - [CLI, Build, And Debugging](./cli-build-and-debugging.md)
201
+ - [Human In The Loop](../human-in-the-loop.mdx)
202
+ - [Vercel Deployment](./vercel-deployment.md)
@@ -11,6 +11,7 @@
11
11
  "workspace",
12
12
  "evals",
13
13
  "instrumentation",
14
+ "dev-tui",
14
15
  "cli-build-and-debugging",
15
16
  "typescript-api",
16
17
  "...",
@@ -33,7 +33,7 @@ Typical Vercel env vars for an Ash app include:
33
33
  - optional `ASH_SANDBOX_BACKEND`
34
34
 
35
35
  If your deployment uses Vercel preview protection and you want to connect to it with `ash dev`, set
36
- `VERCEL_AUTOMATION_BYPASS_SECRET` locally before launching the REPL against the deployed URL.
36
+ `VERCEL_AUTOMATION_BYPASS_SECRET` locally before launching the dev TUI against the deployed URL.
37
37
 
38
38
  ## Sandbox Backend
39
39
 
@@ -88,9 +88,9 @@ Then attach to the returned stream URL pattern:
88
88
  curl https://<your-app>/ash/v1/session/<sessionId>/stream
89
89
  ```
90
90
 
91
- ## Remote REPL
91
+ ## Remote Dev
92
92
 
93
- You can point the Ash REPL at a deployed app:
93
+ You can point the Ash dev [TUI](./dev-tui.md) at a deployed app:
94
94
 
95
95
  ```bash
96
96
  ash dev https://<your-app>
@@ -98,7 +98,7 @@ Useful commands:
98
98
  - `ash info`: show the active routes and compiled artifacts
99
99
  - `ash build`: compile the agent into `.ash/` and build the host output
100
100
  - `ash start`: serve the built `.output/` app
101
- - `ash dev`: start the local runtime and open the REPL
101
+ - `ash dev`: start the local runtime and open the interactive [terminal UI](./advanced/dev-tui.md)
102
102
 
103
103
  ## Send A Message
104
104
 
@@ -244,11 +244,11 @@ webhook URL — no separate interactivity endpoint needed.
244
244
 
245
245
  ## CLI Behavior
246
246
 
247
- `ash dev` surfaces pending HITL requests directly in the REPL:
247
+ `ash dev` surfaces pending HITL requests directly in the [terminal UI](./advanced/dev-tui.md):
248
248
 
249
- - tool approvals prompt for yes/no
250
- - questions prompt for an option number or freeform answer
251
- - `Escape` returns to the normal `you>` prompt without answering
249
+ - tool approvals prompt for `y` / `n`
250
+ - option questions are answered with `↑` / `↓` to select and `Enter` to confirm; freeform questions accept typed input
251
+ - `Escape` backs out without answering
252
252
 
253
253
  If you press `Escape` and then send a normal message, the pending requests are ignored.
254
254
 
@@ -24,7 +24,7 @@ Collect these up front. A capable agent should use a structured question UI (suc
24
24
 
25
25
  1. **Name** — the project / directory name (kebab-case). Also used as the Slack connector slug.
26
26
  2. **Model** — for example `anthropic/claude-sonnet-4.6` or `openai/gpt-5-mini`. Baked into `agent/agent.ts`.
27
- 3. **Channels** — `web` can be scaffolded headlessly. `slack` is an interactive follow-up because Vercel Connect opens a browser OAuth flow. The local REPL is always available via `ash dev`, regardless of channel choice.
27
+ 3. **Channels** — `web` can be scaffolded headlessly. `slack` is an interactive follow-up because Vercel Connect opens a browser OAuth flow. The local terminal UI is always available via `ash dev`, regardless of channel choice.
28
28
  4. **Model provider** — how the agent reaches a model:
29
29
  - Vercel project (default) — create a project, or pass `--project <slug>` to link an existing one, and use AI Gateway via OIDC.
30
30
  - API key override — pass `--gateway-api-key <key>` to write `AI_GATEWAY_API_KEY` to `.env.local`.
@@ -103,7 +103,7 @@ Treat setup as successful only when `ash info --json` reports `status: "ready"`
103
103
  ## Step 4 — Run and test locally
104
104
 
105
105
  - Web chat: `pnpm dev`, open `http://localhost:3000`, send a message.
106
- - REPL: `ash dev`, chat in the terminal.
106
+ - Terminal UI: `ash dev`, chat in the terminal.
107
107
 
108
108
  The agent replies only if it can reach a model: either `AI_GATEWAY_API_KEY` is set in `.env.local`, or the Vercel project is linked and you have run `vercel env pull --yes`.
109
109
 
@@ -1,7 +1,7 @@
1
1
  import { type HandleMessageStreamEvent } from "#protocol/message.js";
2
2
  import type { InputRequest } from "#runtime/input/types.js";
3
3
  import { type CliTheme } from "#cli/ui/output.js";
4
- import { createDevelopmentTerminal } from "#cli/dev/terminal.js";
4
+ import { createDevelopmentTerminal } from "#cli/dev/repl/terminal.js";
5
5
  export type ReplDisplayBlockKind = "content" | "meta";
6
6
  export type LiveContentKind = "message" | "reasoning";
7
7
  export interface FormattedContentEvent {
@@ -1,2 +1,2 @@
1
- import{ASH_CONTINUE_SESSION_ROUTE_PATTERN,ASH_CREATE_SESSION_ROUTE_PATH,ASH_MESSAGE_STREAM_ROUTE_PATTERN,createAshMessageStreamRoutePath}from"#protocol/routes.js";import{createInterface,emitKeypressEvents}from"node:readline";import{createCliTheme,renderCliBanner,renderCliSection,renderCliSpeakerLine,renderCliTaggedLine}from"#cli/ui/output.js";import{openStreamIterable}from"#client/open-stream.js";import{isCurrentTurnBoundaryEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{createDevelopmentRequestHeadersAsync}from"#services/dev-client/request-headers.js";import{extractCurrentTurnBoundaryEvent}from"#services/dev-client/stream.js";import{resolveDevelopmentServerResourceUrl}from"#services/dev-client/url.js";import{formatVercelAuthChallengeMessage,isVercelAuthChallenge}from"#services/dev-client/vercel-auth-error.js";import{createDevClient}from"#services/dev-client.js";import{parseDevReplInput}from"#cli/dev/input.js";import{ESCAPED_RUNTIME_INPUT_PROMPT,extractPendingRuntimeInputRequests,promptForRuntimeInputRequests}from"#cli/dev/input-requests.js";import{createDevelopmentTerminal}from"#cli/dev/terminal.js";function renderConnectionRows(r){let i=[{label:`Server`,tone:`info`,value:r.serverUrl},{label:`Create`,tone:`info`,value:`POST ${ASH_CREATE_SESSION_ROUTE_PATH}`},{label:`Continue`,tone:`info`,value:`POST ${ASH_CONTINUE_SESSION_ROUTE_PATTERN}`},{label:`Stream`,tone:`info`,value:`GET ${ASH_MESSAGE_STREAM_ROUTE_PATTERN}`}];return i.push({label:`Session`,value:`Follow-up messages reuse the active continuation token.`}),i}function renderCommandRows(){return[{label:`/help`,value:`Print the connection contract and available commands.`},{label:`/new`,value:`Clear the current durable session cursor.`},{label:`/exit`,value:`Exit the REPL.`}]}function renderIntro(e,t){return[renderCliBanner(e,{subtitle:`Interactive development REPL for the active Ash server.`,title:`Ash Dev`}),``,renderCliSection(e,{rows:renderConnectionRows(t),title:`Connection`}),``,renderCliSection(e,{rows:renderCommandRows(),title:`Commands`})].join(`
1
+ import{ASH_CONTINUE_SESSION_ROUTE_PATTERN,ASH_CREATE_SESSION_ROUTE_PATH,ASH_MESSAGE_STREAM_ROUTE_PATTERN,createAshMessageStreamRoutePath}from"#protocol/routes.js";import{createInterface,emitKeypressEvents}from"node:readline";import{createCliTheme,renderCliBanner,renderCliSection,renderCliSpeakerLine,renderCliTaggedLine}from"#cli/ui/output.js";import{openStreamIterable}from"#client/open-stream.js";import{isCurrentTurnBoundaryEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{createDevelopmentRequestHeadersAsync}from"#services/dev-client/request-headers.js";import{extractCurrentTurnBoundaryEvent}from"#services/dev-client/stream.js";import{resolveDevelopmentServerResourceUrl}from"#services/dev-client/url.js";import{formatVercelAuthChallengeMessage,isVercelAuthChallenge}from"#services/dev-client/vercel-auth-error.js";import{createDevClient}from"#services/dev-client.js";import{parseDevReplInput}from"#cli/dev/repl/input.js";import{ESCAPED_RUNTIME_INPUT_PROMPT,extractPendingRuntimeInputRequests,promptForRuntimeInputRequests}from"#cli/dev/repl/input-requests.js";import{createDevelopmentTerminal}from"#cli/dev/repl/terminal.js";function renderConnectionRows(r){let i=[{label:`Server`,tone:`info`,value:r.serverUrl},{label:`Create`,tone:`info`,value:`POST ${ASH_CREATE_SESSION_ROUTE_PATH}`},{label:`Continue`,tone:`info`,value:`POST ${ASH_CONTINUE_SESSION_ROUTE_PATTERN}`},{label:`Stream`,tone:`info`,value:`GET ${ASH_MESSAGE_STREAM_ROUTE_PATTERN}`}];return i.push({label:`Session`,value:`Follow-up messages reuse the active continuation token.`}),i}function renderCommandRows(){return[{label:`/help`,value:`Print the connection contract and available commands.`},{label:`/new`,value:`Clear the current durable session cursor.`},{label:`/exit`,value:`Exit the REPL.`}]}function renderIntro(e,t){return[renderCliBanner(e,{subtitle:`Interactive development REPL for the active Ash server.`,title:`Ash Dev`}),``,renderCliSection(e,{rows:renderConnectionRows(t),title:`Connection`}),``,renderCliSection(e,{rows:renderCommandRows(),title:`Commands`})].join(`
2
2
  `)}function normalizeStepMessage(e){let t=e.trim();return t.length>0?t:null}function getRenderTag(e){return e.options?.isSubagent===!0?`subagent`:e.fallback}function getRenderTone(e){return e.options?.isSubagent===!0?`subagent`:e.fallback}function prefixSourceLabel(e){return e.options?.sourceLabel===void 0?e.message:`${e.options.sourceLabel}${e.separator??` `}${e.message}`}function formatContentEvent(e,t,n){switch(t.type){case`message.appended`:return{finalized:!1,kind:`message`,line:renderCliSpeakerLine(e,{message:t.data.messageSoFar,speaker:n?.sourceLabel??`agent`,tone:getRenderTone({fallback:`accent`,options:n})})};case`message.completed`:if(t.data.message===null)return;if(t.data.finishReason===`tool-calls`){let r=normalizeStepMessage(t.data.message);return r===null?void 0:{finalized:!0,kind:`message`,line:renderCliTaggedLine(e,{message:prefixSourceLabel({message:r,options:n,separator:`: `}),tag:getRenderTag({fallback:`step`,options:n}),tone:getRenderTone({fallback:`accent`,options:n})})}}return{finalized:!0,kind:`message`,line:renderCliSpeakerLine(e,{message:t.data.message,speaker:n?.sourceLabel??`agent`,tone:getRenderTone({fallback:`accent`,options:n})})};case`reasoning.appended`:return{finalized:!1,kind:`reasoning`,line:renderCliTaggedLine(e,{message:prefixSourceLabel({message:t.data.reasoningSoFar,options:n,separator:`: `}),tag:getRenderTag({fallback:`reasoning`,options:n}),tone:getRenderTone({fallback:`info`,options:n})})};case`reasoning.completed`:return{finalized:!0,kind:`reasoning`,line:renderCliTaggedLine(e,{message:prefixSourceLabel({message:t.data.reasoning,options:n,separator:`: `}),tag:getRenderTag({fallback:`reasoning`,options:n}),tone:getRenderTone({fallback:`info`,options:n})})};default:return}}function formatEvent(e,t,n){let r=formatContentEvent(e,t,n);if(r!==void 0)return r.line;switch(t.type){case`message.received`:return;case`actions.requested`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.actions.length} action${t.data.actions.length===1?``:`s`})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`muted`,options:n})});case`input.requested`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.requests.length} request${t.data.requests.length===1?``:`s`})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`info`,options:n})});case`action.result`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${formatActionResultLabel(t.data.result)})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`muted`,options:n})});case`session.waiting`:case`session.completed`:return;case`authorization.required`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.name}: ${t.data.description}${formatAuthorizationChallengeSuffix(t.data.authorization)})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`warning`,options:n})});case`compaction.requested`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`compacting conversation history`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`muted`,options:n})});case`compaction.completed`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`conversation history compacted`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`muted`,options:n})});case`step.failed`:case`turn.failed`:case`session.failed`:return;case`subagent.called`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.name} -> ${t.data.childSessionId})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`info`,options:n})});case`subagent.started`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.subagentName})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`info`,options:n})});case`subagent.event`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.subagentName}: ${formatNestedSubagentEventLabel(t.data.event)})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`muted`,options:n})});case`subagent.completed`:return renderCliTaggedLine(e,{message:prefixSourceLabel({message:`${t.type} (${t.data.subagentName})`,options:n}),tag:getRenderTag({fallback:`event`,options:n}),tone:getRenderTone({fallback:`info`,options:n})});default:return}}function getEventDisplayBlockKind(e){switch(e.type){case`message.appended`:case`message.completed`:case`reasoning.appended`:case`reasoning.completed`:return`content`;default:return`meta`}}function createTurnDisplayState(){return{activeLiveContentKind:null,lastPrintedBlockKind:null}}function printDisplayLine(e){let t=e.state;return t.activeLiveContentKind!==null&&(e.terminal.commitLive(),t={activeLiveContentKind:null,lastPrintedBlockKind:`content`}),t.lastPrintedBlockKind!==null&&t.lastPrintedBlockKind!==e.kind&&e.terminal.print(``),e.terminal.print(e.line),{activeLiveContentKind:null,lastPrintedBlockKind:e.kind}}function renderTurnEvent(e){let t=formatContentEvent(e.theme,e.event,e.options);if(t!==void 0){let n=e.state;return n.activeLiveContentKind!==null&&n.activeLiveContentKind!==t.kind&&(e.terminal.commitLive(),n={activeLiveContentKind:null,lastPrintedBlockKind:`content`}),n.lastPrintedBlockKind!==null&&n.lastPrintedBlockKind!==`content`&&e.terminal.print(``),e.terminal.updateLive(t.line),t.finalized?(e.terminal.commitLive(),{activeLiveContentKind:null,lastPrintedBlockKind:`content`}):{activeLiveContentKind:t.kind,lastPrintedBlockKind:`content`}}let n=formatEvent(e.theme,e.event,e.options);return n===void 0?e.state:printDisplayLine({kind:getEventDisplayBlockKind(e.event),line:n,state:e.state,terminal:e.terminal})}function isAbortLikeError(e){return(e instanceof DOMException||e instanceof Error)&&e.name===`AbortError`}function shouldDrainSubagentStreamsOnBoundary(e){return e.length===0}var ReplSubagentStreamManager=class{#e=new Map;#t;#n;#r;#i;constructor(e){this.#t=e.displayStateRef,this.#n=e.serverUrl,this.#r=e.terminal,this.#i=e.theme}subscribe(e){if(this.#e.has(e.sessionId))return;let t=new AbortController,n=this.#a({controller:t,sessionId:e.sessionId,subagentName:e.subagentName}).finally(()=>{this.#e.delete(e.sessionId)});this.#e.set(e.sessionId,{controller:t,done:n,label:e.subagentName})}async waitForIdle(){for(;this.#e.size>0;)await Promise.all([...this.#e.values()].map(e=>e.done))}async close(){let e=[...this.#e.values()];for(let t of e)t.controller.abort();await Promise.allSettled(e.map(e=>e.done))}async#a(e){let t=resolveDevelopmentServerResourceUrl({resource:createAshMessageStreamRoutePath(e.sessionId),serverUrl:this.#n});try{for await(let n of openStreamIterable({host:this.#n,maxReconnectAttempts:3,resolveHeaders:async()=>await createDevelopmentRequestHeadersAsync({resourceUrl:t}),sessionId:e.sessionId,signal:e.controller.signal,startIndex:0}))if(this.#t.current=renderTurnEvent({event:n,options:{isSubagent:!0,sourceLabel:e.subagentName},state:this.#t.current,terminal:this.#r,theme:this.#i}),n.type===`subagent.called`&&this.subscribe({sessionId:n.data.childSessionId,subagentName:n.data.name}),isCurrentTurnBoundaryEvent(n))return}catch(t){if(isAbortLikeError(t))return;let n=toErrorMessage(t);this.#t.current=printDisplayLine({kind:`meta`,line:renderCliTaggedLine(this.#i,{message:`${e.subagentName} stream failed: ${n}`,tag:`subagent`,tone:`danger`}),state:this.#t.current,terminal:this.#r})}finally{e.controller.abort()}}};function formatActionResultLabel(e){switch(e.kind){case`load-skill-result`:return e.kind;case`subagent-result`:return`${e.kind}:${e.subagentName}`;case`tool-result`:return`${e.kind}:${e.toolName}`}}function formatAuthorizationChallengeSuffix(e){if(e===void 0)return``;let t=[];return e.url!==void 0&&t.push(e.url),e.userCode!==void 0&&t.push(`code ${e.userCode}`),e.instructions!==void 0&&t.push(e.instructions),t.length===0?``:` — ${t.join(`, `)}`}function formatNestedSubagentEventLabel(e){switch(e.type){case`actions.requested`:{let t=e.data.actions,n=t.map(e=>e.kind===`tool-call`?e.toolName:e.kind).join(`,`);return`${e.type} (${t.length} action${t.length===1?``:`s`}${n.length>0?`: ${n}`:``})`}case`action.result`:return`${e.type} (${formatActionResultLabel(e.data.result)})`;case`input.requested`:return`${e.type} (${e.data.requests.length} request${e.data.requests.length===1?``:`s`})`;default:return e.type}}function formatDispatch(e,t){return t.continuationToken?renderCliTaggedLine(e,{message:`resuming session ${t.continuationToken}`,tag:`session`,tone:`info`}):renderCliTaggedLine(e,{message:`starting a new session`,tag:`session`,tone:`info`})}function formatTurnDispatch(e,t){return t.turn.inputResponses!==void 0&&t.turn.message===void 0?renderCliTaggedLine(e,{message:`responding to pending input request${t.turn.inputResponses.length===1?``:`s`}`,tag:`session`,tone:`info`}):formatDispatch(e,t.session)}function formatSessionBoundary(e,t){let n=extractCurrentTurnBoundaryEvent(t),r=extractPendingRuntimeInputRequests(t);switch(n?.type){case`session.waiting`:return[renderCliTaggedLine(e,{message:r.length>0?`waiting for input approval/answer or the next message`:`waiting for the next message`,tag:`session`,tone:`success`})];case`session.completed`:return[renderCliTaggedLine(e,{message:`session completed; the next input starts a new session`,tag:`session`,tone:`success`})];case`session.failed`:{let t=n.data.details&&typeof n.data.details.name==`string`?n.data.details.name:void 0;return[renderCliTaggedLine(e,{message:t?`session failed (${t}): ${n.data.message}`:`session failed: ${n.data.message}`,tag:`session`,tone:`danger`}),renderCliTaggedLine(e,{message:`cleared; the next input starts a new session`,tag:`session`,tone:`warning`})]}default:return[]}}async function waitForInputLine(e,t,n={}){return await new Promise(r=>{let i=e.input,cleanup=()=>{e.off(`close`,handleClose),e.off(`line`,handleLine),i.off(`keypress`,handleKeypress)},handleClose=()=>{cleanup(),r(void 0)},handleLine=e=>{cleanup(),r(e)},handleKeypress=(t,i)=>{n.allowEscape!==!0||i.name!==`escape`||(cleanup(),e.write(null,{ctrl:!0,name:`u`}),r(ESCAPED_RUNTIME_INPUT_PROMPT))};e.setPrompt(t),e.once(`close`,handleClose),e.once(`line`,handleLine),n.allowEscape&&(emitKeypressEvents(i,e),i.on(`keypress`,handleKeypress)),e.prompt()})}async function runTurn(e){let t=e.turn,n={current:createTurnDisplayState()},i=new ReplSubagentStreamManager({displayStateRef:n,serverUrl:e.serverUrl,terminal:e.terminal,theme:e.theme}),ask=async t=>{e.terminal.startPrompt(e.rl,t);let n=await waitForInputLine(e.rl,t,{allowEscape:!0});return e.terminal.stopPrompt(),n};try{for(;;){let a=e.client.getSession();n.current=printDisplayLine({kind:`meta`,line:formatTurnDispatch(e.theme,{session:a,turn:t}),state:n.current,terminal:e.terminal});let o=await e.client.send({inputResponses:t.inputResponses,message:t.message,onEvent(t){n.current=renderTurnEvent({event:t,state:n.current,terminal:e.terminal,theme:e.theme}),t.type===`subagent.called`&&i.subscribe({sessionId:t.data.childSessionId,subagentName:t.data.name})},onResponseStart(t){t.sessionId&&a.sessionId!==t.sessionId&&(n.current=printDisplayLine({kind:`meta`,line:renderCliTaggedLine(e.theme,{message:t.sessionId,tag:`session`,tone:`accent`}),state:n.current,terminal:e.terminal}),n.current=printDisplayLine({kind:`meta`,line:renderCliTaggedLine(e.theme,{message:resolveDevelopmentServerResourceUrl({resource:createAshMessageStreamRoutePath(t.sessionId),serverUrl:e.serverUrl}).toString(),tag:`stream`,tone:`info`}),state:n.current,terminal:e.terminal}))}}),s=extractPendingRuntimeInputRequests(o.events);if(shouldDrainSubagentStreamsOnBoundary(s)){await i.waitForIdle();for(let t of formatSessionBoundary(e.theme,o.events))n.current=printDisplayLine({kind:`meta`,line:t,state:n.current,terminal:e.terminal});return`continue`}let c=await promptForRuntimeInputRequests({ask,print(t){n.current=printDisplayLine({kind:`meta`,line:t,state:n.current,terminal:e.terminal})},requests:s,theme:e.theme});if(c.kind===`aborted`)return`exit`;if(c.kind===`deferred`)return n.current=printDisplayLine({kind:`meta`,line:renderCliTaggedLine(e.theme,{message:`left pending input requests unresolved; send a new message to ignore them`,tag:`session`,tone:`warning`}),state:n.current,terminal:e.terminal}),`continue`;t={inputResponses:c.inputResponses}}}finally{await i.close()}}async function runDevelopmentRepl(e){let t=createDevelopmentTerminal(),n=createCliTheme({color:!0}),r=createInterface({input:process.stdin,output:t.output,terminal:!0});r.on(`SIGINT`,()=>{r.close()});let a=createDevClient({serverUrl:e.serverUrl});try{for(t.print(renderIntro(n,e)),t.print(``);;){t.print(``),t.startPrompt(r,`you> `);let i=await waitForInputLine(r,`you> `);if(t.stopPrompt(),i===void 0)return;let o=parseDevReplInput(i);switch(o.kind!==`empty`&&o.kind!==`exit`&&t.print(``),o.kind){case`empty`:continue;case`help`:t.print(renderIntro(n,e)),t.print(``);continue;case`exit`:return;case`new`:await a.clear(),t.print(renderCliTaggedLine(n,{message:`cleared; the next input starts a new session`,tag:`session`,tone:`warning`})),t.print(``);continue;case`message`:try{if(await runTurn({client:a,rl:r,serverUrl:e.serverUrl,terminal:t,theme:n,turn:{message:o.message}})===`exit`)return}catch(r){isVercelAuthChallenge(r)?t.printError(renderCliTaggedLine(n,{message:formatVercelAuthChallengeMessage({serverUrl:e.serverUrl}),tag:`auth`,tone:`warning`})):t.printError(renderCliTaggedLine(n,{message:toErrorMessage(r),tag:`error`,tone:`danger`}))}t.print(``)}}}finally{await a.close(),r.close(),t.dispose()}}export{createTurnDisplayState,formatContentEvent,formatEvent,renderTurnEvent,runDevelopmentRepl,shouldDrainSubagentStreamsOnBoundary};
@@ -0,0 +1,24 @@
1
+ export { sliceVisible, stripAnsi, visibleLength } from "./terminal-text.js";
2
+ export type TUIScreenState = {
3
+ width: number;
4
+ height: number;
5
+ title: string;
6
+ rightTitle?: string;
7
+ body: string;
8
+ input: string;
9
+ inputActive: boolean;
10
+ inputCursorVisible?: boolean;
11
+ scrollOffset: number;
12
+ status?: string;
13
+ };
14
+ export type TUIScreenLinesState = Omit<TUIScreenState, "body"> & {
15
+ bodyLines: string[];
16
+ };
17
+ export type TUIScreenViewportState = Omit<TUIScreenLinesState, "bodyLines" | "scrollOffset"> & {
18
+ visibleBodyLines: string[];
19
+ };
20
+ export declare function renderScreen(state: TUIScreenState): string;
21
+ export declare function renderScreenLines(state: TUIScreenLinesState): string;
22
+ export declare function renderScreenViewport(state: TUIScreenViewportState): string;
23
+ export declare function wrapText(input: string, width: number): string[];
24
+ export declare function clampScrollOffset(scrollOffset: number, body: string, bodyHeight: number, width: number): number;
@@ -0,0 +1,3 @@
1
+ import{ansiPrefixPattern,codePointWidth,sliceVisible,stripAnsi,visibleLength}from"./terminal-text.js";import{renderMarkdown}from"./markdown.js";function renderScreen(e){let t=Math.max(20,e.width)-4,n=wrapText(renderMarkdown(e.body),t);return renderScreenLines({...e,bodyLines:n})}function renderScreenLines(e){let t=Math.max(8,e.height)-3-2,n=Math.max(0,e.bodyLines.length-t),r=Math.min(Math.max(0,e.scrollOffset),n),i=Math.max(0,e.bodyLines.length-t-r),a=e.bodyLines.slice(i,i+t);return renderScreenViewport({...e,visibleBodyLines:a})}function renderScreenViewport(e){let t=Math.max(20,e.width),n=Math.max(8,e.height)-3-2,r=e.visibleBodyLines.slice(0,n);for(;r.length<n;)r.push(``);return[topBorder(t,e.title,e.rightTitle),...r.map(e=>boxLine(e,t)),bottomBorder(t),topBorder(t,e.inputActive?`Input`:`Status`),boxLine(e.inputActive?`> ${e.input}${e.inputCursorVisible===!1?` `:`█`}`:e.status??`Streaming... ↑/↓ scroll · Ctrl+C quit`,t),bottomBorder(t)].join(`
2
+ `)}function wrapText(e,t){if(t<=0)return[``];let n=[];for(let r of e.split(`
3
+ `)){if(r.length===0){n.push(``);continue}let e=r;for(;visibleLength(e)>t;){let r=findBreakPoint(e,t);n.push(e.slice(0,r).trimEnd()),e=e.slice(r).trimStart()}n.push(e)}return n}function clampScrollOffset(e,t,n,r){let i=Math.max(1,n-2),o=wrapText(renderMarkdown(t),Math.max(1,r-4)),s=Math.max(0,o.length-i);return Math.min(Math.max(0,e),s)}function findBreakPoint(n,r){let i=0,a=0,o=-1;for(;i<n.length&&a<r;){let s=n.slice(i).match(ansiPrefixPattern);if(s){i+=s[0].length;continue}let c=n.codePointAt(i);if(c==null)break;let l=String.fromCodePoint(c),u=codePointWidth(c);if(u>0&&a+u>r)break;l===` `&&(o=i),i+=l.length,a+=u}let s=indexAfterAnsiSequences(n,i);return a===r&&n.codePointAt(s)===32?s:o>0?o:indexAtVisibleWidth(n,r)}function topBorder(e,t,r){let a=Math.max(0,e-2),o=sliceVisible(` ${t} `,a),s=r?sliceVisible(` ${r} `,Math.max(0,a-visibleLength(o))):``,c=Math.max(0,a-visibleLength(o)-visibleLength(s));return`┌${o}${`─`.repeat(c)}${s}┐`}function bottomBorder(e){return`└${`─`.repeat(e-2)}┘`}function boxLine(e,t){let r=t-4,a=sliceVisible(e,r);return`│ ${a}${` `.repeat(Math.max(0,r-visibleLength(a)))} │`}function indexAtVisibleWidth(n,r){let i=0,a=0;for(;i<n.length&&a<r;){let o=n.slice(i).match(ansiPrefixPattern);if(o){i+=o[0].length;continue}let s=n.codePointAt(i);if(s==null)break;let c=String.fromCodePoint(s),l=codePointWidth(s);if(l>0&&a+l>r)break;i+=c.length,a+=l}return i}function indexAfterAnsiSequences(t,n){let r=n;for(;r<t.length;){let n=t.slice(r).match(ansiPrefixPattern);if(!n)break;r+=n[0].length}return r}export{clampScrollOffset,renderScreen,renderScreenLines,renderScreenViewport,sliceVisible,stripAnsi,visibleLength,wrapText};
@@ -0,0 +1,14 @@
1
+ export type MarkdownToken = {
2
+ type: "text";
3
+ text: string;
4
+ } | {
5
+ type: "bold";
6
+ text: string;
7
+ } | {
8
+ type: "italic";
9
+ text: string;
10
+ } | {
11
+ type: "code";
12
+ text: string;
13
+ };
14
+ export declare function renderMarkdown(input: string): string;
@@ -0,0 +1,3 @@
1
+ import{visibleLength}from"./terminal-text.js";const ansi={bold:`\x1B[1m`,boldOff:`\x1B[22m`,italic:`\x1B[3m`,italicOff:`\x1B[23m`};function renderMarkdown(e){let t=e.split(`
2
+ `),n=[];for(let e=0;e<t.length;e+=1){let r=parseTable(t,e);if(r!=null){n.push(...renderTable(r)),e=r.endIndex-1;continue}n.push(renderMarkdownLine(t[e]??``))}return n.join(`
3
+ `)}function renderMarkdownLine(e){if(e.startsWith(`### `))return renderInlineMarkdown(`▶ ${e.slice(4)}`);if(e.startsWith(`## `))return renderInlineMarkdown(`■ ${e.slice(3)}`);if(e.startsWith(`# `))return renderInlineMarkdown(`█ ${e.slice(2)}`);let t=e.match(/^(\s*)[-+*]\s+(.*)$/);if(t){let[,e,n=``]=t;return renderInlineMarkdown(`${e}•${n.length>0?` ${n}`:``}`)}return/^\d+\. /.test(e)?renderInlineMarkdown(e.replace(/^(\d+)\. /,`$1. `)):e.startsWith(`> `)?renderInlineMarkdown(`│ ${e.slice(2)}`):renderInlineMarkdown(e)}function renderInlineMarkdown(e){return e.replaceAll(/`([^`]+)`/g,`$1`).replaceAll(/\*\*([^*\n]+)\*\*/g,`${ansi.bold}$1${ansi.boldOff}`).replaceAll(/__([^_\n]+)__/g,`${ansi.bold}$1${ansi.boldOff}`).replaceAll(/\*([^*\n]+)\*/g,`${ansi.italic}$1${ansi.italicOff}`).replaceAll(/_([^_\n]+)_/g,`${ansi.italic}$1${ansi.italicOff}`)}function parseTable(e,t){let n=parseTableCells(e[t]??``);if(n==null||n.length<2)return;let r=parseTableCells(e[t+1]??``);if(r==null||r.length!==n.length)return;let i=parseTableAlignments(r);if(i==null)return;let a=[],o=t+2;for(;o<e.length;){let t=parseTableCells(e[o]??``);if(t==null)break;a.push(normalizeTableRow(t,n.length)),o+=1}return{alignments:i,endIndex:o,header:n,rows:a}}function parseTableCells(e){if(!e.includes(`|`))return;let t=e.trim();t.startsWith(`|`)&&(t=t.slice(1)),t.endsWith(`|`)&&!t.endsWith(`\\|`)&&(t=t.slice(0,-1));let n=[],r=``;for(let e=0;e<t.length;e+=1){let i=t[e],a=t[e+1];if(i===`\\`&&a===`|`){r+=`|`,e+=1;continue}if(i===`|`){n.push(r.trim()),r=``;continue}r+=i}return n.push(r.trim()),n}function parseTableAlignments(e){let t=[];for(let n of e){let e=n.match(/^(:)?-{3,}(:)?$/);if(e==null)return;let[,r,i]=e;t.push(r!=null&&i!=null?`center`:i==null?`left`:`right`)}return t}function normalizeTableRow(e,t){return Array.from({length:t},(t,n)=>e[n]??``)}function renderTable(n){let r=n.header.map(e=>`${ansi.bold}${renderInlineMarkdown(e)}${ansi.boldOff}`),i=n.rows.map(e=>e.map(renderInlineMarkdown)),a=[r,...i],o=n.alignments.map((t,n)=>Math.max(3,...a.map(t=>visibleLength(t[n]??``))));return[formatTableRow(r,o,n.alignments),o.map(e=>`─`.repeat(e)).join(` `),...i.map(e=>formatTableRow(e,o,n.alignments))]}function formatTableRow(e,t,n){return e.map((e,r)=>alignTableCell(e,t[r]??0,n[r]??`left`)).join(` `)}function alignTableCell(t,n,r){let i=Math.max(0,n-visibleLength(t));if(r===`right`)return`${` `.repeat(i)}${t}`;if(r===`center`){let e=Math.floor(i/2),n=i-e;return`${` `.repeat(e)}${t}${` `.repeat(n)}`}return`${t}${` `.repeat(i)}`}export{renderMarkdown};
@@ -0,0 +1,205 @@
1
+ import { type ConnectionAuthorizationOutcome, Client, ClientSession } from "#client/index.js";
2
+ import { type UIMessage, type UIMessageChunk } from "ai";
3
+ import type { AssistantResponseStatsMode, TerminalPartDisplayMode, TuiDisplayOptions } from "./types.js";
4
+ import { type TerminalInput, type TerminalOutput } from "./terminal-renderer.js";
5
+ export type AgentTUIStreamResult = {
6
+ uiMessageStream: AsyncIterable<UIMessageChunk> | ReadableStream<UIMessageChunk>;
7
+ message?: UIMessage;
8
+ abort?: () => void;
9
+ };
10
+ export type AgentTUIStreamOptions = {
11
+ messages: UIMessage[];
12
+ };
13
+ export type AgentTUISessionOptions = {
14
+ title?: string;
15
+ initialPrompt?: string;
16
+ submittedPrompt?: string;
17
+ waitForExit?: boolean;
18
+ continueSession?: boolean;
19
+ tools?: TerminalPartDisplayMode;
20
+ reasoning?: TerminalPartDisplayMode;
21
+ subagents?: TerminalPartDisplayMode;
22
+ connectionAuth?: TerminalPartDisplayMode;
23
+ assistantResponseStats?: AssistantResponseStatsMode;
24
+ contextSize?: number;
25
+ };
26
+ export type AgentTUIToolApprovalRequest = {
27
+ approvalId: string;
28
+ toolCallId: string;
29
+ toolName: string;
30
+ title?: string;
31
+ input: unknown;
32
+ providerExecuted?: boolean;
33
+ messageId: string;
34
+ partIndex: number;
35
+ };
36
+ export type AgentTUIToolApprovalResponse = {
37
+ approved: boolean;
38
+ reason?: string;
39
+ };
40
+ export type AgentTUIInputOption = {
41
+ id: string;
42
+ label: string;
43
+ description?: string;
44
+ style?: "primary" | "danger" | "default";
45
+ };
46
+ export type AgentTUIInputQuestion = {
47
+ requestId: string;
48
+ prompt: string;
49
+ display: "select" | "text";
50
+ options?: ReadonlyArray<AgentTUIInputOption>;
51
+ allowFreeform?: boolean;
52
+ };
53
+ export type AgentTUIInputQuestionResponse = {
54
+ optionId?: string;
55
+ text?: string;
56
+ };
57
+ export type AgentTUIRenderer = {
58
+ readPrompt?(options?: AgentTUISessionOptions): Promise<string | undefined>;
59
+ readToolApproval?(request: AgentTUIToolApprovalRequest, options?: AgentTUISessionOptions): Promise<AgentTUIToolApprovalResponse>;
60
+ readInputQuestion?(question: AgentTUIInputQuestion, options?: AgentTUISessionOptions): Promise<AgentTUIInputQuestionResponse | undefined>;
61
+ renderStream(result: AgentTUIStreamResult, options?: AgentTUISessionOptions): Promise<UIMessage | undefined>;
62
+ /**
63
+ * Out-of-band update for one child step (reasoning + message text) of a
64
+ * subagent dispatch. Called by the runner as child-session stream events
65
+ * arrive. The renderer renders this as a body section colored by the
66
+ * subagent palette.
67
+ */
68
+ upsertSubagentStep?(update: SubagentStepUpdate): void;
69
+ /**
70
+ * Out-of-band update for one child tool call of a subagent dispatch.
71
+ */
72
+ upsertSubagentTool?(update: SubagentToolUpdate): void;
73
+ /**
74
+ * Registers a tool call id as originating from a subagent's child
75
+ * session. The renderer must skip rendering parent UIMessage tool parts
76
+ * for these ids — they are surfaced via {@link upsertSubagentTool}
77
+ * instead.
78
+ */
79
+ markChildToolCallId?(callId: string): void;
80
+ /**
81
+ * Out-of-band update for one MCP connection authorization lifecycle.
82
+ * Called by the runner as `authorization.*` events arrive.
83
+ * The renderer renders this as a persistent body section per
84
+ * connection that transitions through `required` → `pending` →
85
+ * one of the terminal `ConnectionAuthorizationOutcome` states.
86
+ */
87
+ upsertConnectionAuth?(update: ConnectionAuthUpdate): void;
88
+ /**
89
+ * Sets the number of connections currently awaiting an OAuth
90
+ * callback. The renderer overrides its bottom status bar with a
91
+ * "waiting for connection authorization" hint while this is > 0,
92
+ * so the user understands the agent is parked, not hung.
93
+ */
94
+ setConnectionAuthPendingCount?(count: number): void;
95
+ /**
96
+ * Clears the rendered transcript and resets per-conversation display
97
+ * state, leaving the UI interactive on a fresh screen. Used by the
98
+ * `/new` command to start a new session with a clean slate.
99
+ */
100
+ reset?(): void;
101
+ /**
102
+ * Tears down interactive mode and restores the terminal, as if the user
103
+ * pressed Ctrl+C. Used by the `/exit` command.
104
+ */
105
+ shutdown?(): void;
106
+ };
107
+ export type AshTUIRunnerOptions = TuiDisplayOptions & {
108
+ session: ClientSession;
109
+ /**
110
+ * Optional client used to attach to child sessions for live subagent
111
+ * stream observation. When omitted, the TUI still shows the subagent
112
+ * section but cannot surface the subagent's reasoning / response /
113
+ * intermediate events — only the parent-stream `called` and
114
+ * `completed` transitions.
115
+ */
116
+ client?: Client;
117
+ renderer?: AgentTUIRenderer;
118
+ screen?: TerminalOutput;
119
+ userInput?: TerminalInput;
120
+ /**
121
+ * Formats an error thrown while dispatching a turn (the initial
122
+ * `session.send()` POST — e.g. a transport failure or a Vercel
123
+ * Deployment Protection challenge) into the text rendered in the
124
+ * inline error region. Defaults to the error's message. Callers that
125
+ * know about transport-specific challenges (the `ash dev` glue) inject
126
+ * a richer formatter here.
127
+ */
128
+ formatTransportError?: (error: unknown) => string;
129
+ };
130
+ export declare class AshTUIRunner {
131
+ #private;
132
+ constructor(options: AshTUIRunnerOptions);
133
+ run(): Promise<void>;
134
+ }
135
+ type ResponseMetadata = {
136
+ usage?: {
137
+ totalTokens?: number;
138
+ outputTokens?: number;
139
+ };
140
+ };
141
+ type SubagentChildStep = {
142
+ reasoning: string;
143
+ message: string;
144
+ finalized: boolean;
145
+ };
146
+ type SubagentToolState = {
147
+ toolName: string;
148
+ input: unknown;
149
+ status: "approval-requested" | "executing" | "done" | "failed";
150
+ output?: unknown;
151
+ errorText?: string;
152
+ };
153
+ export type SubagentRun = {
154
+ name: string;
155
+ /**
156
+ * One entry per logical "child message" — independent of the child's
157
+ * `stepIndex` field, which the harness can reuse across multiple
158
+ * assistant messages within a turn (e.g. a message before a tool call
159
+ * and another message after the tool result both arrive under
160
+ * `stepIndex: 0`). The key is a monotonic counter so each
161
+ * `message.completed` opens a new box on the next inbound delta.
162
+ */
163
+ steps: Map<number, SubagentChildStep>;
164
+ /**
165
+ * Section currently accepting reasoning/message deltas. `null` means
166
+ * the next delta opens a new section.
167
+ */
168
+ currentSectionKey: number | null;
169
+ /** Monotonic counter for new section keys. */
170
+ nextSectionKey: number;
171
+ tools: Map<string, SubagentToolState>;
172
+ };
173
+ export type SubagentStepUpdate = {
174
+ callId: string;
175
+ subagentName: string;
176
+ sectionKey: number;
177
+ reasoning: string;
178
+ message: string;
179
+ finalized: boolean;
180
+ };
181
+ export type SubagentToolUpdate = {
182
+ callId: string;
183
+ subagentName: string;
184
+ childCallId: string;
185
+ toolName: string;
186
+ input: unknown;
187
+ status: "approval-requested" | "executing" | "done" | "failed";
188
+ output?: unknown;
189
+ errorText?: string;
190
+ };
191
+ export type ConnectionAuthChallenge = {
192
+ url?: string;
193
+ userCode?: string;
194
+ expiresAt?: string;
195
+ instructions?: string;
196
+ };
197
+ export type ConnectionAuthState = "required" | "pending" | ConnectionAuthorizationOutcome;
198
+ export type ConnectionAuthUpdate = {
199
+ name: string;
200
+ description: string;
201
+ state: ConnectionAuthState;
202
+ challenge?: ConnectionAuthChallenge;
203
+ reason?: string;
204
+ };
205
+ export type { ResponseMetadata as _ResponseMetadata };