eve 0.6.0-beta.6 → 0.6.0-beta.7

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 (63) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/docs/public/advanced/dev-tui.md +16 -1
  3. package/dist/docs/public/advanced/sessions-runs-and-streaming.md +1 -1
  4. package/dist/src/chunks/{use-eve-agent-9vL56Fjy.js → use-eve-agent-DCZbkLG7.js} +10 -0
  5. package/dist/src/chunks/{use-eve-agent-DTWI5Lji.js → use-eve-agent-DoheC4_o.js} +10 -0
  6. package/dist/src/cli/dev/tui/agent-header.d.ts +26 -0
  7. package/dist/src/cli/dev/tui/agent-header.js +1 -0
  8. package/dist/src/cli/dev/tui/blocks.d.ts +72 -0
  9. package/dist/src/cli/dev/tui/blocks.js +6 -0
  10. package/dist/src/cli/dev/tui/errors.d.ts +52 -0
  11. package/dist/src/cli/dev/tui/errors.js +1 -0
  12. package/dist/src/cli/dev/tui/line-editor.d.ts +68 -0
  13. package/dist/src/cli/dev/tui/line-editor.js +1 -0
  14. package/dist/src/cli/dev/tui/live-region.d.ts +60 -0
  15. package/dist/src/cli/dev/tui/live-region.js +3 -0
  16. package/dist/src/cli/dev/tui/markdown.d.ts +0 -13
  17. package/dist/src/cli/dev/tui/runner.d.ts +77 -22
  18. package/dist/src/cli/dev/tui/runner.js +1 -1
  19. package/dist/src/cli/dev/tui/stream-format.d.ts +76 -0
  20. package/dist/src/cli/dev/tui/stream-format.js +2 -0
  21. package/dist/src/cli/dev/tui/terminal-renderer.d.ts +21 -69
  22. package/dist/src/cli/dev/tui/terminal-renderer.js +4 -12
  23. package/dist/src/cli/dev/tui/terminal-text.d.ts +6 -0
  24. package/dist/src/cli/dev/tui/terminal-text.js +1 -1
  25. package/dist/src/cli/dev/tui/test/mock-terminal.d.ts +7 -0
  26. package/dist/src/cli/dev/tui/test/mock-terminal.js +2 -2
  27. package/dist/src/cli/dev/tui/theme.d.ts +95 -0
  28. package/dist/src/cli/dev/tui/theme.js +1 -0
  29. package/dist/src/cli/dev/tui/tool-format.d.ts +31 -0
  30. package/dist/src/cli/dev/tui/tool-format.js +2 -0
  31. package/dist/src/cli/dev/tui/tui.d.ts +9 -12
  32. package/dist/src/client/client.d.ts +11 -1
  33. package/dist/src/client/client.js +1 -1
  34. package/dist/src/client/index.d.ts +1 -1
  35. package/dist/src/client/types.d.ts +161 -0
  36. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  37. package/dist/src/evals/scorers/autoevals.js +1 -1
  38. package/dist/src/execution/node-step.js +1 -1
  39. package/dist/src/harness/code-mode.js +1 -1
  40. package/dist/src/harness/tool-loop.js +1 -1
  41. package/dist/src/harness/tools.js +1 -1
  42. package/dist/src/internal/application/package.js +1 -1
  43. package/dist/src/internal/nitro/dev-runtime-source-snapshot.js +1 -1
  44. package/dist/src/internal/nitro/host/configure-nitro-routes.js +1 -1
  45. package/dist/src/internal/nitro/host/dev-authored-source-watcher.js +1 -1
  46. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.d.ts +153 -73
  47. package/dist/src/internal/nitro/routes/agent-info/build-agent-info-response.js +1 -6
  48. package/dist/src/internal/nitro/routes/info.d.ts +11 -13
  49. package/dist/src/internal/nitro/routes/info.js +1 -1
  50. package/dist/src/protocol/routes.d.ts +4 -8
  51. package/dist/src/public/channels/eve.js +1 -1
  52. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  53. package/dist/src/setup/scaffold/channels.js +1 -1
  54. package/dist/src/setup/scaffold/project.js +1 -1
  55. package/dist/src/svelte/index.js +1 -1
  56. package/dist/src/svelte/use-eve-agent.js +1 -1
  57. package/dist/src/vue/index.js +1 -1
  58. package/dist/src/vue/use-eve-agent.js +1 -1
  59. package/package.json +1 -1
  60. package/dist/src/cli/dev/tui/layout.d.ts +0 -24
  61. package/dist/src/cli/dev/tui/layout.js +0 -3
  62. package/dist/src/cli/dev/tui/terminal-frame-buffer.d.ts +0 -21
  63. package/dist/src/cli/dev/tui/terminal-frame-buffer.js +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # eve
2
2
 
3
+ ## 0.6.0-beta.7
4
+
5
+ ### Minor Changes
6
+
7
+ - edf9f1a: Redesign the `eve dev` terminal UI and add `Client.info()`.
8
+
9
+ The TUI now streams its transcript into the terminal's native scrollback instead of a bordered alt-screen, so scrolling, copy/paste, and the transcript all survive after you exit. The visual language follows the Vercel / Next.js CLI: a monochrome base with the `▲` brand mark, colored gutter glyphs per speaker, the `dots` spinner, and status icons (`✓` done, `⨯` error). Tool calls collapse to a one-line summary (`✓ get_weather city="SF" → 73°F`) that expands under `--tools full`, subagent work is indented beneath a `◆` header, and a startup header prints the connected agent's model, instructions, tools, skills, and subagents. Subagents now default to `auto-collapsed`.
10
+
11
+ The prompt input gains shell-style editing: `↑`/`↓` cycle through your previously sent messages, `←`/`→`/Home/End/`Ctrl+A`/`Ctrl+E` move the caret, and `Ctrl+U`/`Ctrl+K`/`Ctrl+W` kill the line/word. Escape sequences that arrive split across reads (common with PTYs) are reassembled, and pasted text is sanitized of stray control bytes.
12
+
13
+ Failures read more cleanly: a terminal `session.failed` (or a dead-socket transport error) no longer leaves you stuck — the TUI starts a fresh session before the next prompt and notes it inline, so you can keep going. A failure cascade (`step.failed` → `turn.failed` → `session.failed`) renders one error block instead of three, and code bugs escaping user code show their stack trace dim beneath the headline (capped to a dozen lines; recognized provider/config failures keep their curated one-line summary). Error blocks no longer double-print a `Code: Code:` prefix and render docs links in cyan, and denied tool approvals settle to a `→ denied` line instead of a frozen `?`.
14
+
15
+ Captured server `stdout`/`stderr` renders as quiet, indented log runs: consecutive lines from the same source coalesce behind one `stdout ·` / `stderr ·` label with a hanging indent, runs get a blank line of breathing room on both sides, and nothing is ever truncated — a transcript can't be clicked open, so the full output is always shown.
16
+
17
+ `Client.info()` fetches the agent inspection payload (`GET /eve/v1/info`) — model id, instructions source, tools, skills, subagents, schedules, and sandbox — for both local and remote targets, returning the new `AgentInfoResult` type.
18
+
3
19
  ## 0.6.0-beta.6
4
20
 
5
21
  ### Minor Changes
@@ -9,7 +9,22 @@ description: "Drive an Eve agent locally in an interactive terminal UI: chat, st
9
9
  eve dev
10
10
  ```
11
11
 
12
- The screen has three parts. A header shows the app name and live response stats. The middle is a scrollable transcript of everything that happened: your prompts, streamed messages, reasoning, tool calls, subagent sections, connection-authorization prompts, and any captured `stdout`/`stderr`. The bottom is an input and status bar. Press `Enter` to send and `↑`/`↓` to scroll. `q` quits when the agent is idle, and `Ctrl+C` quits at any time. Two slash commands: `/new` starts a fresh session and `/exit` quits.
12
+ On startup the TUI prints a header for the connected agent the model, instructions prompt, and the tools, skills, and subagents it has available:
13
+
14
+ ```text
15
+ ▲ Weather Agent
16
+ · Model openai/gpt-5
17
+ · Instructions agent/instructions.md
18
+ · Tools get_weather, get_forecast, geocode
19
+ · Subagents researcher
20
+ · Server http://localhost:3000
21
+
22
+ Type to chat · ↑ history · /new reset session · /exit quit · Ctrl+C interrupt
23
+ ```
24
+
25
+ From there the conversation streams straight into your terminal's normal scrollback — your prompts, the agent's replies, reasoning, tool calls, nested subagents, connection-authorization prompts, and any captured `stdout`/`stderr` — so you keep native scrolling, copy/paste, and a transcript that persists after you exit. Each turn is rendered without boxes: a colored gutter glyph marks who's speaking, tool calls collapse to a one-line summary (`✓ get_weather city="SF" → 73°F`), and a subagent's work is indented beneath its `◆` header. A sticky line at the bottom shows the input prompt or the live status (spinner, token usage). Press `Enter` to send; `Ctrl+C` interrupts a running turn or quits at the prompt. Two slash commands: `/new` starts a fresh session and `/exit` quits.
26
+
27
+ The prompt input behaves like a shell line editor: `↑`/`↓` cycle through the messages you've sent this session, `←`/`→`, Home/End, and `Ctrl+A`/`Ctrl+E` move the caret, and `Ctrl+U`/`Ctrl+K`/`Ctrl+W` kill the line, the rest of the line, or the previous word. If a turn fails terminally — the server session dies or the connection drops — the TUI starts a fresh session and notes it inline so you can keep going (server-side context resets with the old session). Errors render compactly, with docs links highlighted, and a code bug escaping your agent's own code shows its stack trace dim beneath the error headline. Captured server `stdout`/`stderr` renders as dim, indented log runs behind a `│` rule — consecutive lines from the same source share one label, and nothing is ever hidden.
13
28
 
14
29
  The agent will sometimes need something from you, and the TUI asks inline. Tool approvals are a `y`/`n`. Option questions let you pick with `↑`/`↓` and `Enter`, or you can type a freeform answer. If a tool needs an authorized [connection](../connections), the URL shows up right in the transcript, and the turn picks back up once you've finished the flow.
15
30
 
@@ -100,7 +100,7 @@ Start with the [TypeScript Client](../client/overview) guide. It covers basic us
100
100
 
101
101
  ## Inspect the agent over HTTP
102
102
 
103
- `GET /eve/v1/info` returns a JSON inspection payload for the running agent: model id, instructions preview, skills, schedules, subagents, tools, sandbox, and the runtime's endpoint catalog. It's the `eve info` CLI, served over HTTP.
103
+ `GET /eve/v1/info` returns a JSON inspection snapshot for the running agent: model, instructions, authored and framework tools, skills, channels, schedules, subagents, sandbox, connections, hooks, workflow, and workspace metadata. Local development accepts loopback requests; deployed Vercel targets require the route's OIDC auth.
104
104
 
105
105
  ```bash
106
106
  curl http://127.0.0.1:3000/eve/v1/info
@@ -434,6 +434,16 @@ var Client = class {
434
434
  }
435
435
  return await response.json();
436
436
  }
437
+ async info() {
438
+ const url = createClientUrl(this.#host, EVE_INFO_ROUTE_PATH);
439
+ const headers = await this.#resolveHeaders();
440
+ const response = await fetch(url, { headers });
441
+ if (!response.ok) {
442
+ const body = await response.text();
443
+ throw new ClientError(response.status, body);
444
+ }
445
+ return await response.json();
446
+ }
437
447
  session(state) {
438
448
  let resolved;
439
449
  if (typeof state === "string") resolved = {
@@ -434,6 +434,16 @@ var Client = class {
434
434
  }
435
435
  return await response.json();
436
436
  }
437
+ async info() {
438
+ const url = createClientUrl(this.#host, EVE_INFO_ROUTE_PATH);
439
+ const headers = await this.#resolveHeaders();
440
+ const response = await fetch(url, { headers });
441
+ if (!response.ok) {
442
+ const body = await response.text();
443
+ throw new ClientError(response.status, body);
444
+ }
445
+ return await response.json();
446
+ }
437
447
  session(state) {
438
448
  let resolved;
439
449
  if (typeof state === "string") resolved = {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Builds the startup header the dev TUI commits to scrollback before the first
3
+ * prompt. Styled after the Next.js dev banner: a leading `▲` brand mark, then
4
+ * an aligned `·` list of the agent's resolved configuration (model,
5
+ * instructions, authored tools, skills, subagents, server). When the
6
+ * `/eve/v1/info` payload is unavailable it degrades to a minimal name +
7
+ * server banner.
8
+ */
9
+ import type { AgentInfoResult } from "#client/index.js";
10
+ import type { Theme } from "./theme.js";
11
+ export interface AgentHeaderInput {
12
+ /** Resolved display name (e.g. "Weather Agent"). */
13
+ name: string;
14
+ /** Server URL the TUI is connected to. */
15
+ serverUrl: string;
16
+ /** Agent inspection payload, or `undefined` when it could not be fetched. */
17
+ info?: AgentInfoResult;
18
+ theme: Theme;
19
+ /** Available terminal width. */
20
+ width: number;
21
+ }
22
+ /**
23
+ * Returns the styled rows of the startup header (no trailing blank line is
24
+ * added by callers other than the one separating it from the transcript).
25
+ */
26
+ export declare function buildAgentHeader(input: AgentHeaderInput): string[];
@@ -0,0 +1 @@
1
+ import{truncate}from"./tool-format.js";function buildAgentHeader(t){let{theme:n,info:r,name:i,serverUrl:a,width:o}=t,s=n.colors,c=[];if(r){c.push({label:`Model`,value:r.agent.model.id}),r.instructions.static&&c.push({label:`Instructions`,value:r.instructions.static.logicalPath});let e=[...r.tools.authored,...r.tools.dynamic.filter(e=>e.origin===`authored`).map(e=>({name:e.slug}))];e.length>0&&c.push({label:`Tools`,value:joinNames(e)});let t=[...r.skills.static,...r.skills.dynamic.filter(e=>e.origin===`authored`).map(e=>({name:e.slug}))];t.length>0&&c.push({label:`Skills`,value:joinNames(t)}),r.subagents.local.length>0&&c.push({label:`Subagents`,value:joinNames(r.subagents.local)}),r.schedules.length>0&&c.push({label:`Schedules`,value:joinNames(r.schedules)}),r.sandbox&&c.push({label:`Sandbox`,value:r.sandbox.logicalPath})}c.push({label:`Server`,value:a,link:!0});let l=c.reduce((e,t)=>Math.max(e,t.label.length),0),u=Math.max(8,o-l-6),d=[];if(d.push(` ${s.bold(`${n.glyph.brand} ${i}`)}`),r&&(r.diagnostics.discoveryErrors>0||r.diagnostics.discoveryWarnings>0)){let e=[];r.diagnostics.discoveryErrors>0&&e.push(s.red(`${r.diagnostics.discoveryErrors} error${plural(r.diagnostics.discoveryErrors)}`)),r.diagnostics.discoveryWarnings>0&&e.push(s.yellow(`${r.diagnostics.discoveryWarnings} warning${plural(r.diagnostics.discoveryWarnings)}`)),d.push(` ${s.dim(n.glyph.warning)} ${e.join(s.dim(` · `))}`)}for(let t of c){let r=s.dim(n.glyph.dot)+` `+s.gray(t.label.padEnd(l)),i=truncate(t.value,u);d.push(` ${r} ${t.link?s.cyan(i):i}`)}return d.push(``),d.push(` ${s.dim(`Type to chat ${n.glyph.dot} ↑ history ${n.glyph.dot} /new reset session ${n.glyph.dot} /exit quit ${n.glyph.dot} Ctrl+C interrupt`)}`),d}function joinNames(e){return e.map(e=>e.name).join(`, `)}function plural(e){return e===1?``:`s`}export{buildAgentHeader};
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The transcript block model and its renderer.
3
+ *
4
+ * A {@link Block} is one logical unit of the conversation — a user message, a
5
+ * streamed assistant reply, a reasoning trace, a tool call, a nested subagent
6
+ * step, a log line, and so on. {@link renderBlockLines} turns a block into the
7
+ * exact terminal rows it occupies: a colored gutter glyph, brand-aligned
8
+ * indentation, nesting rules for subagents, and word-wrapped content — with no
9
+ * boxes anywhere. Every returned row is already styled and fits within the
10
+ * given width, so the live region can place rows verbatim.
11
+ */
12
+ import type { Theme } from "./theme.js";
13
+ export type ToolStatus = "running" | "done" | "error" | "denied" | "approval";
14
+ export type BlockKind = "user" | "assistant" | "reasoning" | "tool" | "error" | "notice" | "question" | "subagent" | "subagent-step" | "subagent-tool" | "connection-auth" | "log";
15
+ /**
16
+ * One renderable transcript unit. Fields are interpreted per `kind`; unset
17
+ * fields are simply omitted from the rendered output.
18
+ */
19
+ export interface Block {
20
+ kind: BlockKind;
21
+ /** Stable id for in-place updates while the block is live. */
22
+ id?: string;
23
+ /** Nesting depth: 0 = top level, 1 = inside a subagent, etc. */
24
+ depth?: number;
25
+ /** Whether the block is still streaming / mutating (drives the spinner). */
26
+ live?: boolean;
27
+ /** Primary label — tool name, subagent name, log source, error title. */
28
+ title?: string;
29
+ /** Compact secondary text — summarized tool args. */
30
+ subtitle?: string;
31
+ /** Main multi-line content (markdown for prose, plain for logs). */
32
+ body?: string;
33
+ /** Reasoning trace shown above `body` (subagent steps). */
34
+ reasoning?: string;
35
+ /** One-line summarized result shown after a tool resolves. */
36
+ result?: string;
37
+ /**
38
+ * Errors only: multi-line diagnostic dump (stack trace, cause chain)
39
+ * rendered dim beneath the headline, capped to a handful of lines.
40
+ */
41
+ detail?: string;
42
+ /** Tool / connection lifecycle status. */
43
+ status?: ToolStatus;
44
+ /** When true, treat `body` as pre-styled and only wrap + indent it. */
45
+ preformatted?: boolean;
46
+ /** Reasoning only: collapse the trace to a single "thinking" line. */
47
+ collapsed?: boolean;
48
+ /** When true, expand tool input/output instead of summarizing. */
49
+ expanded?: boolean;
50
+ /** Raw tool input / output for the expanded view. */
51
+ toolInput?: unknown;
52
+ toolOutput?: unknown;
53
+ }
54
+ export interface RenderBlockContext {
55
+ /** Current spinner frame for live blocks. */
56
+ spinner: string;
57
+ /**
58
+ * Kind and title of the block rendered immediately above this one. Lets a
59
+ * log block detect that it continues a same-source run (label suppressed,
60
+ * lines hang under the previous block's label) without any mutable run
61
+ * state — each captured write stays its own immediately-committed block.
62
+ */
63
+ previous?: {
64
+ kind: BlockKind;
65
+ title?: string;
66
+ };
67
+ }
68
+ /**
69
+ * Renders a block to its terminal rows. Each row is fully styled and clipped
70
+ * to `width` visible columns.
71
+ */
72
+ export declare function renderBlockLines(block: Block, width: number, theme: Theme, context: RenderBlockContext): string[];
@@ -0,0 +1,6 @@
1
+ import{formatValuePretty,truncate}from"./tool-format.js";import{sliceVisible,visibleLength,wrapVisibleLine}from"./terminal-text.js";import{renderMarkdown}from"./markdown.js";function renderBlockLines(e,t,n,i){let a=nestingPrefix(e.depth??0,n);return renderBody(e,Math.max(8,t-visibleLength(a)),n,i).map(e=>`${a}${e}`)}function nestingPrefix(e,t){return e<=0?``:`${t.colors.orange(t.glyph.rule)} `.repeat(e)}function renderBody(e,t,n,r){switch(e.kind){case`user`:return renderUser(e,t,n);case`assistant`:case`subagent-step`:return renderProse(e,t,n);case`reasoning`:return renderReasoning(e,t,n);case`tool`:case`subagent-tool`:return renderTool(e,t,n,r);case`error`:return renderError(e,t,n);case`notice`:return renderNotice(e,t,n);case`question`:case`connection-auth`:return renderPreformatted(e,t,n);case`log`:return renderLog(e,t,n,r);case`subagent`:return renderSubagentHeader(e,t,n)}}function renderUser(e,t,n){let r=n.colors.cyan(n.glyph.user);return wrap(e.body??``,t-2).map(e=>`${r} ${e}`)}function renderProse(e,t,n){let r=[],o=e.kind===`subagent-step`,s=o?``:`${n.colors.bold(n.colors.white(n.glyph.brand))} `,c=o?``:` `;e.reasoning&&e.reasoning.trim().length>0&&r.push(...renderReasoningLines(e.reasoning,t,n));let l=(e.body??``).trim();return l.length===0&&r.length===0?[`${s}${n.colors.dim(`thinking${n.glyph.ellipsis}`)}`]:(l.length>0&&renderMarkdown(l).split(`
2
+ `).flatMap(e=>wrapVisibleLine(e,t-c.length)).forEach((e,t)=>{t===0&&!o&&r.length===0?r.push(`${s}${e}`):r.push(`${c}${e}`)}),r.length>0?r:[`${s}`])}function renderReasoning(e,t,n){return e.collapsed?[`${n.colors.gray(n.glyph.reasoning)} ${n.colors.dim(`thinking`)}`]:renderReasoningLines(e.body??``,t,n,n.glyph.reasoning)}function renderReasoningLines(e,t,n,r){let i=r?2:0,a=wrap(e.trim(),t-i);return a.length===0?[]:a.map((e,t)=>`${r?t===0?`${n.colors.gray(r)} `:` `:``}${n.colors.dim(n.colors.italic(e))}`)}function renderTool(e,n,r,i){let{icon:a,accent:o}=toolGlyph(e.status??`running`,r,i),s=e.title??`tool`,c=n-2,l=truncatePlain(s,c),u=`${a} ${r.colors.bold(l)}`,d=c-l.length-2,f=e.subtitle??``;f.length>0&&d>=6&&(u+=` ${r.colors.gray(truncate(f,d))}`);let p=[u];return e.expanded?p.push(...renderToolExpanded(e,n,r)):e.status===`done`&&e.result&&e.result.length>0?p.push(resultLine(r.glyph.arrow,e.result,n,r,o)):e.status===`error`&&e.result?p.push(resultLine(r.glyph.arrow,e.result,n,r,r.colors.red)):e.status===`denied`&&p.push(resultLine(r.glyph.arrow,`denied`,n,r,r.colors.yellow)),p}function renderToolExpanded(t,n,r){let i=[],push=(t,a,o)=>{if(a!==void 0){i.push(` ${r.colors.dim(t)}`);for(let t of wrap(formatValuePretty(a),n-4))i.push(` ${o(t)}`)}};return push(`input`,t.toolInput,r.colors.gray),t.status===`error`&&t.result?push(`error`,t.result,r.colors.red):push(`output`,t.toolOutput,r.colors.gray),i}function resultLine(e,n,r,i,a){let o=r-4;return` ${i.colors.dim(e)} ${a(truncate(n,o))}`}function toolGlyph(e,t,n){switch(e){case`done`:return{icon:t.colors.green(t.glyph.success),accent:t.colors.gray};case`error`:return{icon:t.colors.red(t.glyph.error),accent:t.colors.red};case`denied`:return{icon:t.colors.yellow(t.glyph.warning),accent:t.colors.yellow};case`approval`:return{icon:t.colors.yellow(t.glyph.question),accent:t.colors.yellow};default:return{icon:t.colors.yellow(n.spinner),accent:t.colors.gray}}}function renderError(e,t,n){let r=n.colors.red(n.colors.bold(n.glyph.error)),i=e.title??`Error`,a=[`${r} ${n.colors.red(n.colors.bold(i))}`];for(let r of wrap(e.body??``,t-2))a.push(` ${colorizeError(r,n)}`);return a.push(...renderErrorDetail(e.detail,t,n)),a}function renderErrorDetail(e,t,n){if(e===void 0||e.trim().length===0)return[];let r=e.split(`
3
+ `),i=r.slice(0,12),a=i.map(e=>` ${n.colors.dim(truncatePlain(e,Math.max(1,t-2)))}`),o=r.length-i.length;return o>0&&a.push(` ${n.colors.dim(`${n.glyph.ellipsis} +${o} more line${o===1?``:`s`}`)}`),a}const URL_PATTERN=/(https?:\/\/\S+)/u;function colorizeError(e,t){return URL_PATTERN.test(e)?e.split(URL_PATTERN).map((e,n)=>n%2==1?t.colors.cyan(e):t.colors.red(e)).join(``):t.colors.red(e)}function renderNotice(e,t,n){let r=n.colors.dim(n.glyph.dot),i=wrap(e.body??``,t-2);return i.length===0?[r]:i.map(e=>`${r} ${n.colors.dim(e)}`)}function renderPreformatted(e,t,n){let r=e.kind===`connection-auth`?n.colors.yellow(n.glyph.connection):n.colors.yellow(n.colors.bold(n.glyph.question)),a=e.title??``,o=[`${r} ${n.colors.bold(a)}`];for(let n of(e.body??``).split(`
4
+ `))for(let e of wrapVisibleLine(n,t-2))o.push(` ${e}`);return o}function renderLog(e,t,n,a){let o=e.title===`stderr`,s=o?n.colors.red:n.colors.gray,c=n.colors.dim(n.glyph.rule),l=o?`stderr`:`stdout`,u=n.colors.dim(`${l} ${n.glyph.dot} `),d=visibleLength(u),f=` `.repeat(d),p=a.previous?.kind===`log`&&a.previous.title===e.title,m=(e.body??``).split(`
5
+ `),h=[];for(let e of m){let r=wrapVisibleLine(e,Math.max(1,t-2-d));for(let e of r){let t=h.length===0&&!p?u:f;h.push(`${c} ${t}${n.colors.dim(s(e))}`)}}return h.length>0?h:[`${c}`]}function renderSubagentHeader(e,t,n){let r=truncatePlain(e.title??`subagent`,Math.max(8,t-14));return[`${n.colors.orange(n.glyph.subagent)} ${n.colors.bold(r)} ${n.colors.dim(`subagent`)}`]}function wrap(e,t){return e.trim().length===0?[]:e.split(`
6
+ `).flatMap(e=>wrapVisibleLine(e,Math.max(1,t)))}function truncatePlain(e,t){return visibleLength(e)<=t?e:sliceVisible(e,t)}export{renderBlockLines};
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Error classification and display formatting shared by the TUI runner and
3
+ * terminal renderer. One module owns the interrupt sentinel and the
4
+ * failure-event projections so the two sides cannot drift apart.
5
+ */
6
+ import type { SessionFailedStreamEvent, StepFailedStreamEvent, TurnFailedStreamEvent } from "#client/index.js";
7
+ /**
8
+ * One of the failure events a session stream can carry. All three share the
9
+ * same `{ code, message, details? }` payload shape — the harness emits them
10
+ * as a cascade (`step.failed` → `turn.failed` → `session.failed` /
11
+ * `session.waiting`) describing a single underlying failure.
12
+ */
13
+ export type FailureStreamEvent = StepFailedStreamEvent | TurnFailedStreamEvent | SessionFailedStreamEvent;
14
+ /**
15
+ * Thrown when the user interrupts the TUI (Ctrl+C, or Ctrl+D on an empty
16
+ * prompt). The runner treats it as a clean exit, never as a failure.
17
+ */
18
+ export declare class InterruptedError extends Error {
19
+ constructor();
20
+ }
21
+ export declare function interruptedError(): InterruptedError;
22
+ export declare function isInterruptedError(error: unknown): boolean;
23
+ /**
24
+ * Recognizes errors raised by aborting an in-flight fetch/stream (e.g. the
25
+ * subagent child-session pump being cancelled). These are expected shutdown
26
+ * noise, not failures to surface.
27
+ */
28
+ export declare function isAbortLikeError(error: unknown): boolean;
29
+ /**
30
+ * Stable identity for one failure cascade entry. The harness emits the same
31
+ * `{ code, message }` payload on `step.failed`, `turn.failed`, and (for
32
+ * terminal failures) `session.failed`; keying on both lets the stream
33
+ * translator render the underlying failure exactly once.
34
+ */
35
+ export declare function failureKey(event: FailureStreamEvent): string;
36
+ /**
37
+ * One-line headline for a failure event: `code: message`, except when the
38
+ * message already carries its own class-name prefix (e.g. a
39
+ * `HookConflictError` whose message starts with `HookConflictError:`), in
40
+ * which case the message stands alone instead of reading `Code: Code: …`.
41
+ */
42
+ export declare function formatFailureMessage(event: FailureStreamEvent): string;
43
+ /**
44
+ * Extracts the diagnostic dump attached to a failure event, if any.
45
+ *
46
+ * `details.detail` is the `util.inspect` rendering (stack trace and cause
47
+ * chain included) that `formatError` attaches to *unrecognized* failures —
48
+ * i.e. code bugs escaping user code. Recognized provider/config failures
49
+ * deliberately ship a curated summary without the dump, so this returns
50
+ * `undefined` for them and the headline stands alone.
51
+ */
52
+ export declare function formatFailureDetail(event: FailureStreamEvent): string | undefined;
@@ -0,0 +1 @@
1
+ var InterruptedError=class extends Error{constructor(){super(`Interrupted`),this.name=`InterruptedError`}};function interruptedError(){return new InterruptedError}function isInterruptedError(e){return e instanceof InterruptedError}function isAbortLikeError(e){return e instanceof Error?e.name===`AbortError`||/\babort(?:ed)?\b/iu.test(e.message):!1}function failureKey(e){return`${e.data.code}:${e.data.message}`}function formatFailureMessage(e){let{code:t,message:n}=e.data;return!t||n===t||n.startsWith(`${t}:`)||n.startsWith(`${t} `)?n:`${t}: ${n}`}function formatFailureDetail(e){let t=e.data.details;if(typeof t!=`object`||!t)return;let n=t.detail;if(typeof n!=`string`)return;let r=n.trim();if(!(r.length===0||r===e.data.message.trim()))return r}export{InterruptedError,failureKey,formatFailureDetail,formatFailureMessage,interruptedError,isAbortLikeError,isInterruptedError};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * A tiny, dependency-free line-editing model for the prompt input.
3
+ *
4
+ * The renderer owns the terminal; this module owns the *text* — a single
5
+ * logical line plus a caret position — and exposes pure transforms for the
6
+ * common readline-style edits (insert, delete, word/line kill, cursor moves)
7
+ * and a {@link visibleLine} helper that windows a long line around the caret
8
+ * so it stays on screen. Keeping it pure makes the editing rules trivial to
9
+ * unit test without a TTY.
10
+ */
11
+ /** One logical input line: its text and the caret's index within it. */
12
+ export interface LineState {
13
+ readonly text: string;
14
+ readonly cursor: number;
15
+ }
16
+ /** The empty line with the caret at column 0. */
17
+ export declare const EMPTY_LINE: LineState;
18
+ /** Builds a line from `text` with the caret placed at the end. */
19
+ export declare function lineOf(text: string): LineState;
20
+ /** Inserts `value` at the caret and advances the caret past it. */
21
+ export declare function insert(state: LineState, value: string): LineState;
22
+ /** Deletes the character before the caret (Backspace). */
23
+ export declare function backspace(state: LineState): LineState;
24
+ /** Deletes the character at the caret (Delete / Ctrl+D mid-line). */
25
+ export declare function deleteForward(state: LineState): LineState;
26
+ /** Moves the caret one column left. */
27
+ export declare function moveLeft(state: LineState): LineState;
28
+ /** Moves the caret one column right. */
29
+ export declare function moveRight(state: LineState): LineState;
30
+ /** Moves the caret to the start of the line (Home / Ctrl+A). */
31
+ export declare function moveHome(state: LineState): LineState;
32
+ /** Moves the caret to the end of the line (End / Ctrl+E). */
33
+ export declare function moveEnd(state: LineState): LineState;
34
+ /** Deletes from the caret to the end of the line (Ctrl+K). */
35
+ export declare function killToEnd(state: LineState): LineState;
36
+ /** Deletes from the start of the line to the caret (Ctrl+U). */
37
+ export declare function killToStart(state: LineState): LineState;
38
+ /** Deletes the whitespace-delimited word before the caret (Ctrl+W). */
39
+ export declare function deleteWord(state: LineState): LineState;
40
+ /**
41
+ * The portion of `state.text` to draw within `budget` columns, split at the
42
+ * caret so the renderer can place its caret glyph between `before` and
43
+ * `after`. When the line is wider than `budget` it is windowed around the
44
+ * caret, marking truncated ends with `…` so the caret is always visible.
45
+ */
46
+ export interface VisibleLine {
47
+ readonly before: string;
48
+ readonly after: string;
49
+ }
50
+ export declare function visibleLine(state: LineState, budget: number, ellipsis?: string): VisibleLine;
51
+ /**
52
+ * In-memory, append-only prompt history with shell-style up/down navigation.
53
+ *
54
+ * Navigation is non-destructive: stepping back from the live line stashes the
55
+ * in-progress draft and restores it when the user steps forward past the
56
+ * newest entry. Consecutive duplicate submissions are collapsed.
57
+ */
58
+ export declare class PromptHistory {
59
+ #private;
60
+ /** Records a submitted prompt (skips blanks and consecutive duplicates). */
61
+ add(entry: string): void;
62
+ /** Resets navigation to the live line, stashing `draft` as the in-progress text. */
63
+ begin(draft: string): void;
64
+ /** The previous (older) entry, or `undefined` at the oldest entry. */
65
+ previous(currentDraft: string): string | undefined;
66
+ /** The next (newer) entry, or the stashed draft once past the newest entry. */
67
+ next(): string | undefined;
68
+ }
@@ -0,0 +1 @@
1
+ const EMPTY_LINE={text:``,cursor:0};function lineOf(e){return{text:e,cursor:e.length}}function insert(e,t){return t.length===0?e:{text:e.text.slice(0,e.cursor)+t+e.text.slice(e.cursor),cursor:e.cursor+t.length}}function backspace(e){return e.cursor===0?e:{text:e.text.slice(0,e.cursor-1)+e.text.slice(e.cursor),cursor:e.cursor-1}}function deleteForward(e){return e.cursor>=e.text.length?e:{text:e.text.slice(0,e.cursor)+e.text.slice(e.cursor+1),cursor:e.cursor}}function moveLeft(e){return e.cursor===0?e:{text:e.text,cursor:e.cursor-1}}function moveRight(e){return e.cursor>=e.text.length?e:{text:e.text,cursor:e.cursor+1}}function moveHome(e){return e.cursor===0?e:{text:e.text,cursor:0}}function moveEnd(e){return e.cursor===e.text.length?e:{text:e.text,cursor:e.text.length}}function killToEnd(e){return e.cursor>=e.text.length?e:{text:e.text.slice(0,e.cursor),cursor:e.cursor}}function killToStart(e){return e.cursor===0?e:{text:e.text.slice(e.cursor),cursor:0}}function deleteWord(e){if(e.cursor===0)return e;let t=e.cursor;for(;t>0&&isWhitespace(e.text[t-1]);)--t;for(;t>0&&!isWhitespace(e.text[t-1]);)--t;return{text:e.text.slice(0,t)+e.text.slice(e.cursor),cursor:t}}function isWhitespace(e){return e!==void 0&&/\s/u.test(e)}function visibleLine(e,t,n=`…`){let r=Math.max(1,t),{text:i,cursor:a}=e;if(i.length<=r)return{before:i.slice(0,a),after:i.slice(a)};let o=a<r?0:a-r+1;o=Math.min(o,i.length-r),o=Math.max(0,o);let s=o+r,c=i.slice(o,s),l=a-o;return o>0&&l>0&&(c=n+c.slice(n.length)),s<i.length&&l<c.length&&(c=c.slice(0,c.length-n.length)+n),{before:c.slice(0,l),after:c.slice(l)}}var PromptHistory=class{#e=[];#t=0;#n=``;add(e){if(e.trim().length!==0){if(this.#e.at(-1)===e){this.#r();return}this.#e.push(e),this.#r()}}begin(e){this.#t=this.#e.length,this.#n=e}previous(e){if(this.#e.length!==0&&(this.#t===this.#e.length&&(this.#n=e),this.#t!==0))return--this.#t,this.#e[this.#t]}next(){if(!(this.#t>=this.#e.length))return this.#t+=1,this.#t===this.#e.length?this.#n:this.#e[this.#t]}#r(){this.#t=this.#e.length,this.#n=``}};export{EMPTY_LINE,PromptHistory,backspace,deleteForward,deleteWord,insert,killToEnd,killToStart,lineOf,moveEnd,moveHome,moveLeft,moveRight,visibleLine};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The inline scrollback engine.
3
+ *
4
+ * Unlike a full-screen alt-buffer UI, the dev TUI streams its transcript into
5
+ * the terminal's *native* scrollback so the user keeps real scrolling, copy /
6
+ * paste, and a persistent transcript after exit. Two regions are maintained:
7
+ *
8
+ * - **Committed scrollback** — finalized rows printed once and owned by the
9
+ * terminal thereafter (never repainted).
10
+ * - **Live region** — the still-streaming rows plus the sticky footer, redrawn
11
+ * in place on every update.
12
+ *
13
+ * Redrawing moves the cursor to the top of the previous live region, clears to
14
+ * the end of the screen, and reprints. {@link flush} additionally writes a run
15
+ * of newly-finalized rows above the live region so they scroll away for good.
16
+ *
17
+ * Writes go through the terminal's original `write` captured at construction,
18
+ * so the renderer's foreign-output capture (which monkeypatches
19
+ * `process.stdout.write`) never mistakes the engine's own paint for agent log
20
+ * output.
21
+ */
22
+ export interface LiveRegionOutput {
23
+ write(chunk: string): boolean;
24
+ }
25
+ export interface LiveRegionOptions {
26
+ /** Wrap each paint in synchronized-update markers to avoid flicker. */
27
+ synchronized?: boolean;
28
+ }
29
+ export declare class LiveRegion {
30
+ #private;
31
+ constructor(output: LiveRegionOutput, options?: LiveRegionOptions);
32
+ /** Hides the hardware cursor; the renderer draws its own caret. */
33
+ hideCursor(): void;
34
+ showCursor(): void;
35
+ /** Writes a newline through the bound (original) write. */
36
+ newline(): void;
37
+ /**
38
+ * Repaints the live region in place from `liveRows`. Each row must already
39
+ * be styled and fit within the terminal width (one row == one screen line).
40
+ */
41
+ update(liveRows: readonly string[]): void;
42
+ /**
43
+ * Commits `committedRows` to scrollback above the live region, then repaints
44
+ * `liveRows`. Committed rows are permanent and scroll with the terminal.
45
+ */
46
+ flush(committedRows: readonly string[], liveRows: readonly string[]): void;
47
+ /**
48
+ * Erases the live region, leaving the cursor at its former top. Committed
49
+ * scrollback is untouched. Used on teardown before restoring the cursor.
50
+ */
51
+ clear(): void;
52
+ /** Clears the visible transcript and, where supported, terminal scrollback. */
53
+ clearAll(): void;
54
+ /**
55
+ * Forgets the live-region row count without moving the cursor. Call after
56
+ * the cursor position is known to be a fresh column-0 line that the engine
57
+ * did not itself paint (e.g. immediately after teardown).
58
+ */
59
+ reset(): void;
60
+ }
@@ -0,0 +1,3 @@
1
+ const CLEAR_TO_END=`\x1B[0J`;var LiveRegion=class{#e;#t;#n=0;constructor(e,t){this.#e=e.write.bind(e),this.#t=t?.synchronized??!0}hideCursor(){this.#e(`\x1B[?25l`)}showCursor(){this.#e(`\x1B[?25h`)}newline(){this.#e(`
2
+ `)}update(e){this.#r([],e)}flush(e,t){this.#r(e,t)}clear(){if(this.#n===0){this.#e(`\r`),this.#e(CLEAR_TO_END);return}this.#e(`${this.#i()}${CLEAR_TO_END}`),this.#n=0}clearAll(){this.#e(`\x1B[3J\x1B[2J\x1B[H`),this.#n=0}reset(){this.#n=0}#r(t,n){let r=this.#i()+CLEAR_TO_END+t.map(e=>`${e}\n`).join(``)+n.join(`
3
+ `);this.#e(this.#t?`[?2026h${r}[?2026l`:r),this.#n=n.length}#i(){return this.#n<=1?`\r`:`[${this.#n-1}F`}};export{LiveRegion};
@@ -1,14 +1 @@
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
1
  export declare function renderMarkdown(input: string): string;
@@ -1,20 +1,69 @@
1
- import { type ConnectionAuthorizationOutcome, Client, ClientSession } from "#client/index.js";
2
- import { type UIMessage, type UIMessageChunk } from "ai";
1
+ import { type AgentInfoResult, type ConnectionAuthorizationOutcome, type InputRequest, Client, ClientSession } from "#client/index.js";
3
2
  import type { AssistantResponseStatsMode, TerminalPartDisplayMode, TuiDisplayOptions } from "./types.js";
4
3
  import { type TerminalInput, type TerminalOutput } from "./terminal-renderer.js";
5
4
  export type AgentTUIStreamResult = {
6
- uiMessageStream: AsyncIterable<UIMessageChunk> | ReadableStream<UIMessageChunk>;
7
- message?: UIMessage;
5
+ events: AsyncIterable<AgentTUIStreamEvent> | ReadableStream<AgentTUIStreamEvent>;
8
6
  abort?: () => void;
7
+ turnState?: AgentTUITurnState;
9
8
  };
10
- export type AgentTUIStreamOptions = {
11
- messages: UIMessage[];
9
+ export type AgentTUIStreamUsage = {
10
+ inputTokens?: number;
11
+ outputTokens?: number;
12
+ };
13
+ export type AgentTUIStreamEvent = {
14
+ type: "step-start";
15
+ } | {
16
+ type: "step-finish";
17
+ usage?: AgentTUIStreamUsage;
18
+ } | {
19
+ type: "assistant-delta";
20
+ id: string;
21
+ delta: string;
22
+ } | {
23
+ type: "assistant-complete";
24
+ id: string;
25
+ text?: string | null;
26
+ } | {
27
+ type: "reasoning-delta";
28
+ id: string;
29
+ delta: string;
30
+ } | {
31
+ type: "reasoning-complete";
32
+ id: string;
33
+ } | {
34
+ type: "tool-call";
35
+ toolCallId: string;
36
+ toolName: string;
37
+ input: unknown;
38
+ } | {
39
+ type: "tool-approval-request";
40
+ approvalId: string;
41
+ toolCallId: string;
42
+ } | {
43
+ type: "tool-result";
44
+ toolCallId: string;
45
+ output: unknown;
46
+ } | {
47
+ type: "tool-error";
48
+ toolCallId: string;
49
+ errorText: string;
50
+ } | {
51
+ type: "error";
52
+ errorText: string;
53
+ detail?: string;
54
+ } | {
55
+ type: "finish";
56
+ usage?: AgentTUIStreamUsage;
57
+ };
58
+ export type AgentTUITurnState = {
59
+ boundaryEvent?: "session.completed" | "session.failed" | "session.waiting";
60
+ pendingApprovals: AgentTUIToolApprovalRequest[];
61
+ pendingQuestions: InputRequest[];
62
+ sawSessionFailure: boolean;
12
63
  };
13
64
  export type AgentTUISessionOptions = {
14
65
  title?: string;
15
- initialPrompt?: string;
16
66
  submittedPrompt?: string;
17
- waitForExit?: boolean;
18
67
  continueSession?: boolean;
19
68
  tools?: TerminalPartDisplayMode;
20
69
  reasoning?: TerminalPartDisplayMode;
@@ -29,9 +78,6 @@ export type AgentTUIToolApprovalRequest = {
29
78
  toolName: string;
30
79
  title?: string;
31
80
  input: unknown;
32
- providerExecuted?: boolean;
33
- messageId: string;
34
- partIndex: number;
35
81
  };
36
82
  export type AgentTUIToolApprovalResponse = {
37
83
  approved: boolean;
@@ -54,11 +100,27 @@ export type AgentTUIInputQuestionResponse = {
54
100
  optionId?: string;
55
101
  text?: string;
56
102
  };
103
+ export type AgentTUIAgentHeader = {
104
+ name: string;
105
+ serverUrl: string;
106
+ info?: AgentInfoResult;
107
+ };
57
108
  export type AgentTUIRenderer = {
109
+ /**
110
+ * Commits a startup header describing the connected agent (brand mark,
111
+ * model, instructions, tools, skills, subagents) to the transcript before
112
+ * the first prompt. Optional — renderers without a header simply skip it.
113
+ */
114
+ renderAgentHeader?(header: AgentTUIAgentHeader): void;
115
+ /**
116
+ * Commits a single dim informational line to the transcript. Used to
117
+ * announce session recovery after a terminal server failure. Optional.
118
+ */
119
+ renderNotice?(text: string): void;
58
120
  readPrompt?(options?: AgentTUISessionOptions): Promise<string | undefined>;
59
121
  readToolApproval?(request: AgentTUIToolApprovalRequest, options?: AgentTUISessionOptions): Promise<AgentTUIToolApprovalResponse>;
60
122
  readInputQuestion?(question: AgentTUIInputQuestion, options?: AgentTUISessionOptions): Promise<AgentTUIInputQuestionResponse | undefined>;
61
- renderStream(result: AgentTUIStreamResult, options?: AgentTUISessionOptions): Promise<UIMessage | undefined>;
123
+ renderStream(result: AgentTUIStreamResult, options?: AgentTUISessionOptions): Promise<void>;
62
124
  /**
63
125
  * Out-of-band update for one child step (reasoning + message text) of a
64
126
  * subagent dispatch. Called by the runner as child-session stream events
@@ -72,9 +134,8 @@ export type AgentTUIRenderer = {
72
134
  upsertSubagentTool?(update: SubagentToolUpdate): void;
73
135
  /**
74
136
  * 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.
137
+ * session. The renderer must skip or remove parent-level tool blocks for
138
+ * these ids — they are surfaced via {@link upsertSubagentTool} instead.
78
139
  */
79
140
  markChildToolCallId?(callId: string): void;
80
141
  /**
@@ -138,12 +199,6 @@ export declare class EveTUIRunner {
138
199
  constructor(options: EveTUIRunnerOptions);
139
200
  run(): Promise<void>;
140
201
  }
141
- type ResponseMetadata = {
142
- usage?: {
143
- totalTokens?: number;
144
- outputTokens?: number;
145
- };
146
- };
147
202
  type SubagentChildStep = {
148
203
  reasoning: string;
149
204
  message: string;
@@ -208,4 +263,4 @@ export type ConnectionAuthUpdate = {
208
263
  challenge?: ConnectionAuthChallenge;
209
264
  reason?: string;
210
265
  };
211
- export type { ResponseMetadata as _ResponseMetadata };
266
+ export {};