chivgent 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +71 -1
  2. package/README.zh-CN.md +56 -1
  3. package/dist/cli-options.d.ts +2 -0
  4. package/dist/cli-options.d.ts.map +1 -1
  5. package/dist/cli-options.js +6 -0
  6. package/dist/cli-options.js.map +1 -1
  7. package/dist/cli.js +86 -23
  8. package/dist/cli.js.map +1 -1
  9. package/dist/evals/runner.d.ts.map +1 -1
  10. package/dist/evals/runner.js +1 -19
  11. package/dist/evals/runner.js.map +1 -1
  12. package/dist/remote/framing.d.ts +3 -1
  13. package/dist/remote/framing.d.ts.map +1 -1
  14. package/dist/remote/framing.js +23 -9
  15. package/dist/remote/framing.js.map +1 -1
  16. package/dist/repl.d.ts +1 -0
  17. package/dist/repl.d.ts.map +1 -1
  18. package/dist/repl.js +89 -65
  19. package/dist/repl.js.map +1 -1
  20. package/dist/shell/output.d.ts.map +1 -1
  21. package/dist/shell/output.js +3 -14
  22. package/dist/shell/output.js.map +1 -1
  23. package/dist/shell/truncate.d.ts +2 -0
  24. package/dist/shell/truncate.d.ts.map +1 -1
  25. package/dist/shell/truncate.js +17 -6
  26. package/dist/shell/truncate.js.map +1 -1
  27. package/dist/tools/target.d.ts +10 -0
  28. package/dist/tools/target.d.ts.map +1 -0
  29. package/dist/tools/target.js +27 -0
  30. package/dist/tools/target.js.map +1 -0
  31. package/dist/tui/input.d.ts +24 -0
  32. package/dist/tui/input.d.ts.map +1 -0
  33. package/dist/tui/input.js +45 -0
  34. package/dist/tui/input.js.map +1 -0
  35. package/dist/tui/live.d.ts +38 -0
  36. package/dist/tui/live.d.ts.map +1 -0
  37. package/dist/tui/live.js +116 -0
  38. package/dist/tui/live.js.map +1 -0
  39. package/dist/tui/paint.d.ts +38 -0
  40. package/dist/tui/paint.d.ts.map +1 -0
  41. package/dist/tui/paint.js +86 -0
  42. package/dist/tui/paint.js.map +1 -0
  43. package/dist/tui/state.d.ts +49 -0
  44. package/dist/tui/state.d.ts.map +1 -0
  45. package/dist/tui/state.js +105 -0
  46. package/dist/tui/state.js.map +1 -0
  47. package/dist/tui/text.d.ts +7 -0
  48. package/dist/tui/text.d.ts.map +1 -0
  49. package/dist/tui/text.js +31 -0
  50. package/dist/tui/text.js.map +1 -0
  51. package/dist/tui/view.d.ts +24 -0
  52. package/dist/tui/view.d.ts.map +1 -0
  53. package/dist/tui/view.js +83 -0
  54. package/dist/tui/view.js.map +1 -0
  55. package/dist/tui/welcome.d.ts +12 -0
  56. package/dist/tui/welcome.d.ts.map +1 -0
  57. package/dist/tui/welcome.js +28 -0
  58. package/dist/tui/welcome.js.map +1 -0
  59. package/package.json +4 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/tui/live.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AASjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,iEAAiE;IACjE,IAAI,IAAI,IAAI,CAAC;IACb,0DAA0D;IAC1D,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA+FvE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGlE"}
@@ -0,0 +1,116 @@
1
+ import { formatTokens } from "../providers/usage.js";
2
+ import { terminalText, fitLine } from "./text.js";
3
+ import { Painter } from "./paint.js";
4
+ import { EMPTY_STATE, reduce } from "./state.js";
5
+ import { transcriptLines, view } from "./view.js";
6
+ /** How often the region redraws on its own, so elapsed time advances. */
7
+ const TICK_MS = 1000;
8
+ const DEFAULT_WIDTH = 80;
9
+ /**
10
+ * Drives the live region from the event stream.
11
+ *
12
+ * The three pure pieces do the work — `reduce` holds the state, `view` turns it
13
+ * into lines, `Painter` writes only what changed. This is the wiring between
14
+ * them, and the only part that owns a timer or a stream.
15
+ */
16
+ export function createLiveRegion(options) {
17
+ const clock = options.now ?? (() => Date.now());
18
+ const painter = new Painter({ stream: options.stream });
19
+ let state = EMPTY_STATE;
20
+ let timer;
21
+ const paint = () => {
22
+ painter.render(view(state, {
23
+ // Leave one cell spare to avoid a terminal's pending autowrap state.
24
+ width: Math.max(1, options.width() - 1),
25
+ height: Math.max(1, (options.height?.() ?? 24) - 2),
26
+ now: clock(),
27
+ }));
28
+ };
29
+ const startClock = () => {
30
+ if (timer !== undefined) {
31
+ return;
32
+ }
33
+ timer = setInterval(paint, options.tickMs ?? TICK_MS);
34
+ // Never a reason to keep the process alive: the region describes work that
35
+ // is happening elsewhere.
36
+ timer.unref?.();
37
+ };
38
+ const stopClock = () => {
39
+ if (timer !== undefined) {
40
+ clearInterval(timer);
41
+ timer = undefined;
42
+ }
43
+ };
44
+ return {
45
+ listener: (event) => {
46
+ if (event.type === "turn_end") {
47
+ // The finished turn moves into the terminal's own scrollback: the
48
+ // region is erased first so the transcript lands where it was, then
49
+ // the region is drawn again below it.
50
+ painter.clear();
51
+ const lines = transcriptLines(options.answerStream === undefined ? event : {
52
+ ...event,
53
+ message: { ...event.message, content: "" },
54
+ });
55
+ if (lines.length > 0) {
56
+ options.stream.write(`${terminalText(lines.join("\n"))}\n`);
57
+ }
58
+ if (options.answerStream !== undefined && event.message.content.trim().length > 0) {
59
+ options.answerStream.write(`${event.message.content}\n`);
60
+ }
61
+ }
62
+ const previous = state;
63
+ state = reduce(state, event, clock());
64
+ if (event.type === "agent_start") {
65
+ startClock();
66
+ }
67
+ if (event.type === "agent_end") {
68
+ stopClock();
69
+ painter.clear();
70
+ // Cancellation can end a streamed turn before turn_end arrives.
71
+ if (previous.run?.text) {
72
+ (options.answerStream ?? options.stream).write(`${terminalText(previous.run.text)}\n`);
73
+ }
74
+ const label = event.status === "completed" ? "Completed"
75
+ : event.status === "aborted" ? "Stopped"
76
+ : event.status === "max_turns" ? "Turn limit reached" : "Failed";
77
+ const parts = [label, `${event.turnCount} turn(s)`];
78
+ if (previous.run !== undefined) {
79
+ parts.push(`${Math.max(0, Math.round((clock() - previous.run.startedAt) / 1000))}s`);
80
+ }
81
+ if (event.usage !== undefined) {
82
+ parts.push(`${formatTokens(event.usage.usage.totalTokens)} tokens${event.usage.complete ? "" : "+"}`);
83
+ }
84
+ options.stream.write(`${fitLine(parts.join(" · "), options.width() - 1)}\n`);
85
+ if (event.error !== undefined) {
86
+ options.stream.write(`${terminalText(event.error)}\n`);
87
+ }
88
+ return;
89
+ }
90
+ paint();
91
+ },
92
+ stop: () => {
93
+ stopClock();
94
+ painter.clear();
95
+ state = EMPTY_STATE;
96
+ },
97
+ resized: () => {
98
+ if (state.run === undefined)
99
+ return;
100
+ painter.invalidate(options.width());
101
+ paint();
102
+ },
103
+ };
104
+ }
105
+ /**
106
+ * The usable width, or a sane default.
107
+ *
108
+ * `columns` is not merely absent off a terminal — it can be `0`, which `??`
109
+ * lets through and which then collapses every line to the minimum. A width has
110
+ * to be positive to mean anything.
111
+ */
112
+ export function terminalWidth(stream) {
113
+ const columns = stream.columns;
114
+ return columns !== undefined && columns > 0 ? columns : DEFAULT_WIDTH;
115
+ }
116
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/tui/live.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { OutputStream } from "../render.js";
2
+ export interface PainterOptions {
3
+ readonly stream: OutputStream;
4
+ }
5
+ /**
6
+ * Draws the live region and keeps it up to date.
7
+ *
8
+ * Rewrites only the lines whose content changed. The cost model here is bytes
9
+ * written to a terminal, and line granularity already turns the common case —
10
+ * a timer ticking in the status line — from repainting the block into
11
+ * rewriting one line. Cell-level diffing buys the rest, and in a region of
12
+ * five or six lines it is not worth what it costs to build.
13
+ */
14
+ export declare class Painter {
15
+ private readonly options;
16
+ /**
17
+ * The lines currently on screen. The cursor is always on the blank line
18
+ * just below them, which is what lets the caller keep its own prompt line
19
+ * without the painter knowing anything about it.
20
+ */
21
+ private previous;
22
+ private invalidated;
23
+ private previousRows;
24
+ constructor(options: PainterOptions);
25
+ /**
26
+ * Discards the diff baseline.
27
+ *
28
+ * After a resize the previous line array no longer describes what is on
29
+ * screen — the terminal has reflowed it — so comparing against it would keep
30
+ * lines that are no longer there. This is the one moment a full repaint is
31
+ * the correct answer rather than a lazy one.
32
+ */
33
+ invalidate(columns?: number): void;
34
+ render(lines: readonly string[]): void;
35
+ /** Erases the region entirely, leaving the cursor where it began. */
36
+ clear(): void;
37
+ }
38
+ //# sourceMappingURL=paint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paint.d.ts","sourceRoot":"","sources":["../../src/tui/paint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAMjD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,qBAAa,OAAO;IAUN,OAAO,CAAC,QAAQ,CAAC,OAAO;IATpC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAK;IAEzB,YAA6B,OAAO,EAAE,cAAc,EAAI;IAExD;;;;;;;OAOG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAQjC;IAED,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CA4BrC;IAED,qEAAqE;IACrE,KAAK,IAAI,IAAI,CAcZ;CACF"}
@@ -0,0 +1,86 @@
1
+ import { displayWidth } from "./text.js";
2
+ const CURSOR_UP = "\u001B[A";
3
+ const CLEAR_LINE = "\u001B[2K";
4
+ const CARRIAGE_RETURN = "\r";
5
+ /**
6
+ * Draws the live region and keeps it up to date.
7
+ *
8
+ * Rewrites only the lines whose content changed. The cost model here is bytes
9
+ * written to a terminal, and line granularity already turns the common case —
10
+ * a timer ticking in the status line — from repainting the block into
11
+ * rewriting one line. Cell-level diffing buys the rest, and in a region of
12
+ * five or six lines it is not worth what it costs to build.
13
+ */
14
+ export class Painter {
15
+ options;
16
+ /**
17
+ * The lines currently on screen. The cursor is always on the blank line
18
+ * just below them, which is what lets the caller keep its own prompt line
19
+ * without the painter knowing anything about it.
20
+ */
21
+ previous = [];
22
+ invalidated = false;
23
+ previousRows = 0;
24
+ constructor(options) {
25
+ this.options = options;
26
+ }
27
+ /**
28
+ * Discards the diff baseline.
29
+ *
30
+ * After a resize the previous line array no longer describes what is on
31
+ * screen — the terminal has reflowed it — so comparing against it would keep
32
+ * lines that are no longer there. This is the one moment a full repaint is
33
+ * the correct answer rather than a lazy one.
34
+ */
35
+ invalidate(columns) {
36
+ this.invalidated = true;
37
+ if (columns !== undefined && columns > 0) {
38
+ this.previousRows = this.previous.reduce((rows, line) => rows + Math.max(1, Math.ceil(displayWidth(line) / columns)), 0);
39
+ }
40
+ }
41
+ render(lines) {
42
+ if (!this.invalidated && sameLines(this.previous, lines)) {
43
+ return;
44
+ }
45
+ // Back to the top of the region, then forward a line at a time, so the
46
+ // cursor ends where it began and the caller's prompt line is undisturbed.
47
+ let out = CURSOR_UP.repeat(this.previousRows) + CARRIAGE_RETURN;
48
+ const height = Math.max(this.previousRows, lines.length);
49
+ for (let index = 0; index < height; index += 1) {
50
+ const next = lines[index];
51
+ if (next === undefined) {
52
+ // The region shrank: erase the leftover rather than leaving it behind.
53
+ out += `${CLEAR_LINE}\n`;
54
+ continue;
55
+ }
56
+ out += !this.invalidated && this.previous[index] === next ? "\n" : `${CLEAR_LINE}${next}\n`;
57
+ }
58
+ // A shrunken region left the cursor below its new last line.
59
+ if (height > lines.length) {
60
+ out += CURSOR_UP.repeat(height - lines.length);
61
+ }
62
+ this.options.stream.write(out);
63
+ this.previous = [...lines];
64
+ this.previousRows = lines.length;
65
+ this.invalidated = false;
66
+ }
67
+ /** Erases the region entirely, leaving the cursor where it began. */
68
+ clear() {
69
+ const height = this.previousRows;
70
+ if (height === 0) {
71
+ return;
72
+ }
73
+ this.options.stream.write(CURSOR_UP.repeat(height) +
74
+ CARRIAGE_RETURN +
75
+ `${CLEAR_LINE}\n`.repeat(height) +
76
+ CURSOR_UP.repeat(height));
77
+ this.previous = [];
78
+ this.previousRows = 0;
79
+ this.invalidated = false;
80
+ }
81
+ }
82
+ function sameLines(left, right) {
83
+ return (left.length === right.length &&
84
+ left.every((line, index) => line === right[index]));
85
+ }
86
+ //# sourceMappingURL=paint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paint.js","sourceRoot":"","sources":["../../src/tui/paint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { AgentEvent } from "../events.js";
2
+ import { type UsageTotal } from "../providers/usage.js";
3
+ /**
4
+ * What the live region knows.
5
+ *
6
+ * Every consumer of the event stream before this one was append-only: an event
7
+ * arrived, it was printed, it was forgotten. This is the first that has to
8
+ * rebuild "what is happening now" from the same stream and keep it correct as
9
+ * more arrives.
10
+ *
11
+ * State is deliberately bounded to the run in progress. A finished turn is
12
+ * flushed to the terminal's own scrollback and dropped from here, so a long
13
+ * session does not grow this object — which is the reason the alternate screen
14
+ * buffer is not taken (see docs/stage-14-tui.md §2.1).
15
+ */
16
+ export interface ViewState {
17
+ /** Absent between runs; the live region is then just the prompt. */
18
+ readonly run?: RunState;
19
+ }
20
+ export interface RunState {
21
+ readonly turn: number;
22
+ readonly maxTurns: number;
23
+ readonly startedAt: number;
24
+ /** Assistant text for the turn in progress. Cleared when the turn ends. */
25
+ readonly text: string;
26
+ /** Tools running right now, in the order they started. */
27
+ readonly running: readonly RunningTool[];
28
+ readonly usage?: UsageTotal;
29
+ readonly status: RunStatus;
30
+ }
31
+ export type RunStatus = "thinking" | "running-tools";
32
+ export interface RunningTool {
33
+ readonly toolCallId: string;
34
+ readonly name: string;
35
+ /** The path or command the call was aimed at, when it has one. */
36
+ readonly target?: string;
37
+ /** The tool's latest progress snapshot. Replaced, never appended to. */
38
+ readonly progress: string;
39
+ readonly startedAt: number;
40
+ }
41
+ export declare const EMPTY_STATE: ViewState;
42
+ /**
43
+ * Folds one event into the view state.
44
+ *
45
+ * Pure, and takes `now` rather than reading the clock, so the whole model can
46
+ * be tested without a terminal and without a fake timer.
47
+ */
48
+ export declare function reduce(state: ViewState, event: AgentEvent, now: number): ViewState;
49
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/tui/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGlE;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACxB,oEAAoE;IACpE,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,eAAe,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,WAAW,EAAE,SAAc,CAAC;AAazC;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,MAAM,GACV,SAAS,CAkGX"}
@@ -0,0 +1,105 @@
1
+ import { addUsage } from "../providers/usage.js";
2
+ import { callTarget } from "../tools/target.js";
3
+ export const EMPTY_STATE = {};
4
+ function startRun(maxTurns, now) {
5
+ return {
6
+ turn: 0,
7
+ maxTurns,
8
+ startedAt: now,
9
+ text: "",
10
+ running: [],
11
+ status: "thinking",
12
+ };
13
+ }
14
+ /**
15
+ * Folds one event into the view state.
16
+ *
17
+ * Pure, and takes `now` rather than reading the clock, so the whole model can
18
+ * be tested without a terminal and without a fake timer.
19
+ */
20
+ export function reduce(state, event, now) {
21
+ if (event.type === "agent_start") {
22
+ return { run: startRun(event.maxTurns, now) };
23
+ }
24
+ if (event.type === "agent_end") {
25
+ // The run's transcript has already gone to scrollback; nothing is left to
26
+ // keep alive.
27
+ return EMPTY_STATE;
28
+ }
29
+ const run = state.run;
30
+ if (run === undefined) {
31
+ // An event outside a run. Dropping it is right: the live region describes
32
+ // a run, and inventing one from a mid-stream event would show a run whose
33
+ // beginning was never seen.
34
+ return state;
35
+ }
36
+ switch (event.type) {
37
+ case "turn_start":
38
+ return { run: { ...run, turn: event.turn, status: "thinking" } };
39
+ case "message_start":
40
+ return { run: { ...run, status: "thinking" } };
41
+ case "message_update":
42
+ // A DELTA. Appending is the only correct move: replacing would leave the
43
+ // last token and lose the answer.
44
+ return { run: { ...run, text: run.text + event.delta } };
45
+ case "message_end":
46
+ // The authoritative message, after any deltas. Taking it wholesale also
47
+ // covers the non-streaming case, where no delta ever arrived.
48
+ return {
49
+ run: {
50
+ ...run,
51
+ text: event.message.content,
52
+ ...(event.usage === undefined && run.usage === undefined
53
+ ? {}
54
+ : { usage: addUsage(run.usage, event.usage) }),
55
+ },
56
+ };
57
+ case "tool_execution_start": {
58
+ const target = callTarget(event.arguments);
59
+ const tool = {
60
+ toolCallId: event.toolCallId,
61
+ name: event.toolName,
62
+ ...(target === undefined ? {} : { target }),
63
+ progress: "",
64
+ startedAt: now,
65
+ };
66
+ return {
67
+ run: {
68
+ ...run,
69
+ running: [...run.running, tool],
70
+ status: "running-tools",
71
+ },
72
+ };
73
+ }
74
+ case "tool_execution_update":
75
+ // A SNAPSHOT, already truncated to a bounded window by the tool.
76
+ // Appending would stack the same output repeatedly, and because the
77
+ // window has had its front dropped the result would not even be the
78
+ // real output. Replace.
79
+ return {
80
+ run: {
81
+ ...run,
82
+ running: run.running.map((tool) => tool.toolCallId === event.toolCallId
83
+ ? { ...tool, progress: event.content }
84
+ : tool),
85
+ },
86
+ };
87
+ case "tool_execution_end": {
88
+ const running = run.running.filter((tool) => tool.toolCallId !== event.toolCallId);
89
+ return {
90
+ run: {
91
+ ...run,
92
+ running,
93
+ status: running.length === 0 ? "thinking" : "running-tools",
94
+ },
95
+ };
96
+ }
97
+ case "turn_end":
98
+ // The turn is flushed to scrollback by the caller; what is left alive is
99
+ // only the fact that the run continues.
100
+ return { run: { ...run, text: "", running: [], status: "thinking" } };
101
+ default:
102
+ return state;
103
+ }
104
+ }
105
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/tui/state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import stringWidth from "string-width";
2
+ /** Terminal content may contain cursor controls; only our painter owns those. */
3
+ export declare function terminalText(text: string): string;
4
+ export { stringWidth as displayWidth };
5
+ /** Fit terminal cells, preserving whole graphemes (including joined emoji). */
6
+ export declare function fitLine(text: string, columns: number): string;
7
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/tui/text.ts"],"names":[],"mappings":"AACA,OAAO,WAAW,MAAM,cAAc,CAAC;AAIvC,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,CAAC;AAEvC,+EAA+E;AAC/E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAc7D"}
@@ -0,0 +1,31 @@
1
+ import { stripVTControlCharacters } from "node:util";
2
+ import stringWidth from "string-width";
3
+ const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
4
+ /** Terminal content may contain cursor controls; only our painter owns those. */
5
+ export function terminalText(text) {
6
+ return stripVTControlCharacters(text)
7
+ .replace(/\r\n/g, "\n")
8
+ .replace(/\t/g, " ")
9
+ .replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, "");
10
+ }
11
+ export { stringWidth as displayWidth };
12
+ /** Fit terminal cells, preserving whole graphemes (including joined emoji). */
13
+ export function fitLine(text, columns) {
14
+ const width = Math.max(0, Math.floor(columns));
15
+ const clean = terminalText(text).replace(/\n/g, " ");
16
+ if (width === 0)
17
+ return "";
18
+ if (stringWidth(clean) <= width)
19
+ return clean;
20
+ let result = "";
21
+ let used = 0;
22
+ for (const { segment } of graphemes.segment(clean)) {
23
+ const size = stringWidth(segment);
24
+ if (used + size > width - 1)
25
+ break;
26
+ result += segment;
27
+ used += size;
28
+ }
29
+ return `${result}…`;
30
+ }
31
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/tui/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,WAAW,MAAM,cAAc,CAAC;AAEvC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;AAE7E,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,wBAAwB,CAAC,IAAI,CAAC;SAClC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;SACtB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;SACpB,OAAO,CAAC,4CAA4C,EAAE,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,OAAO,EAAE,WAAW,IAAI,YAAY,EAAE,CAAC;AAEvC,+EAA+E;AAC/E,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,OAAe;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACrD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IAC9C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC;YAAE,MAAM;QACnC,MAAM,IAAI,OAAO,CAAC;QAClB,IAAI,IAAI,IAAI,CAAC;IACf,CAAC;IACD,OAAO,GAAG,MAAM,GAAG,CAAC;AACtB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { TurnEndEvent } from "../events.js";
2
+ import type { ViewState } from "./state.js";
3
+ export interface ViewOptions {
4
+ readonly width: number;
5
+ readonly now: number;
6
+ readonly height?: number;
7
+ }
8
+ /**
9
+ * Renders the live region. Pure: no terminal, no clock, no colour.
10
+ *
11
+ * Colour is left to the painter so this stays comparable line by line — an
12
+ * escape sequence in the middle of a string makes "did this line change" a
13
+ * question about bytes rather than about content.
14
+ */
15
+ export declare function view(state: ViewState, options: ViewOptions): readonly string[];
16
+ /**
17
+ * What a finished turn leaves behind in the terminal's own scrollback.
18
+ *
19
+ * Derived from the event rather than from the state, because by the time this
20
+ * is wanted the state has already been cleared — and because the event carries
21
+ * the authoritative message either way.
22
+ */
23
+ export declare function transcriptLines(event: TurnEndEvent): readonly string[];
24
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/tui/view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,YAAY,CAAC;AAQzD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAmBD;;;;;;GAMG;AACH,wBAAgB,IAAI,CAClB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,WAAW,GACnB,SAAS,MAAM,EAAE,CAqCnB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,MAAM,EAAE,CActE"}
@@ -0,0 +1,83 @@
1
+ import { formatTokens } from "../providers/usage.js";
2
+ import { callTarget } from "../tools/target.js";
3
+ import { fitLine } from "./text.js";
4
+ /** Tools listed individually before the rest are summarised as a count. */
5
+ const MAX_LISTED_TOOLS = 3;
6
+ /** Lines of the answer kept visible while it streams. */
7
+ const MAX_TEXT_LINES = 3;
8
+ function elapsed(from, now) {
9
+ const seconds = Math.max(0, Math.round((now - from) / 1000));
10
+ if (seconds < 60) {
11
+ return `${seconds}s`;
12
+ }
13
+ return `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
14
+ }
15
+ function describeTool(tool, now) {
16
+ const head = tool.target === undefined ? tool.name : `${tool.name} ${tool.target}`;
17
+ // The newest line of a tool's output says more than its first, and a running
18
+ // command's interesting output is at the end.
19
+ const latest = tool.progress.split("\n").filter((line) => line.length > 0).at(-1);
20
+ const suffix = latest === undefined ? "" : ` ${latest}`;
21
+ return ` ${head} (${elapsed(tool.startedAt, now)})${suffix}`;
22
+ }
23
+ /**
24
+ * Renders the live region. Pure: no terminal, no clock, no colour.
25
+ *
26
+ * Colour is left to the painter so this stays comparable line by line — an
27
+ * escape sequence in the middle of a string makes "did this line change" a
28
+ * question about bytes rather than about content.
29
+ */
30
+ export function view(state, options) {
31
+ const run = state.run;
32
+ if (run === undefined) {
33
+ return [];
34
+ }
35
+ const lines = [];
36
+ const tail = run.text.split("\n").slice(-MAX_TEXT_LINES);
37
+ for (const line of tail) {
38
+ if (line.length > 0) {
39
+ lines.push(fitLine(line, options.width));
40
+ }
41
+ }
42
+ for (const tool of run.running.slice(0, MAX_LISTED_TOOLS)) {
43
+ lines.push(fitLine(describeTool(tool, options.now), options.width));
44
+ }
45
+ const hidden = run.running.length - MAX_LISTED_TOOLS;
46
+ if (hidden > 0) {
47
+ lines.push(fitLine(` and ${hidden} more`, options.width));
48
+ }
49
+ const status = [
50
+ run.status === "thinking" ? "thinking" : "running tools",
51
+ `turn ${run.turn}/${run.maxTurns}`,
52
+ elapsed(run.startedAt, options.now),
53
+ ];
54
+ if (run.usage !== undefined && run.usage.usage.totalTokens > 0) {
55
+ status.push(`${formatTokens(run.usage.usage.totalTokens)} tokens${run.usage.complete ? "" : "+"}`);
56
+ }
57
+ status.push("ctrl+c to stop");
58
+ lines.push(fitLine(status.join(" · "), options.width));
59
+ return lines.slice(-Math.max(1, options.height ?? lines.length));
60
+ }
61
+ /**
62
+ * What a finished turn leaves behind in the terminal's own scrollback.
63
+ *
64
+ * Derived from the event rather than from the state, because by the time this
65
+ * is wanted the state has already been cleared — and because the event carries
66
+ * the authoritative message either way.
67
+ */
68
+ export function transcriptLines(event) {
69
+ const lines = [];
70
+ // A result carries no arguments, so the target comes from the call that
71
+ // produced it — the same join by call id the eval runner makes.
72
+ const calls = new Map(event.message.toolCalls.map((call) => [call.id, call]));
73
+ for (const result of event.toolResults) {
74
+ const target = callTarget(calls.get(result.toolCallId)?.arguments);
75
+ const head = target === undefined ? result.toolName : `${result.toolName} ${target}`;
76
+ lines.push(` ${head}${result.isError ? " (failed)" : ""}`);
77
+ }
78
+ if (event.message.content.trim().length > 0) {
79
+ lines.push(event.message.content);
80
+ }
81
+ return lines;
82
+ }
83
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/tui/view.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface WelcomeOptions {
2
+ readonly version: string;
3
+ readonly provider: string;
4
+ readonly model: string;
5
+ readonly cwd: string;
6
+ readonly sessionId: string;
7
+ readonly resumed: boolean;
8
+ readonly signedOut: boolean;
9
+ readonly width: number;
10
+ }
11
+ export declare function welcome(options: WelcomeOptions): string;
12
+ //# sourceMappingURL=welcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../../src/tui/welcome.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAyBvD"}