@duetso/agent 0.1.80 → 0.1.82

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 (96) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/memory/observational.d.ts +8 -1
  3. package/dist/src/memory/observational.d.ts.map +1 -1
  4. package/dist/src/memory/observational.js +9 -3
  5. package/dist/src/memory/observational.js.map +1 -1
  6. package/dist/src/session/session.d.ts +31 -13
  7. package/dist/src/session/session.d.ts.map +1 -1
  8. package/dist/src/session/session.js +63 -23
  9. package/dist/src/session/session.js.map +1 -1
  10. package/dist/src/tui/app.d.ts +25 -35
  11. package/dist/src/tui/app.d.ts.map +1 -1
  12. package/dist/src/tui/app.js +195 -2308
  13. package/dist/src/tui/app.js.map +1 -1
  14. package/dist/src/tui/autocomplete-controller.d.ts +83 -0
  15. package/dist/src/tui/autocomplete-controller.d.ts.map +1 -0
  16. package/dist/src/tui/autocomplete-controller.js +308 -0
  17. package/dist/src/tui/autocomplete-controller.js.map +1 -0
  18. package/dist/src/tui/boot-screen.d.ts +64 -0
  19. package/dist/src/tui/boot-screen.d.ts.map +1 -0
  20. package/dist/src/tui/boot-screen.js +137 -0
  21. package/dist/src/tui/boot-screen.js.map +1 -0
  22. package/dist/src/tui/controllers.d.ts +39 -0
  23. package/dist/src/tui/controllers.d.ts.map +1 -0
  24. package/dist/src/tui/controllers.js +53 -0
  25. package/dist/src/tui/controllers.js.map +1 -0
  26. package/dist/src/tui/copy-controller.d.ts +75 -0
  27. package/dist/src/tui/copy-controller.d.ts.map +1 -0
  28. package/dist/src/tui/copy-controller.js +140 -0
  29. package/dist/src/tui/copy-controller.js.map +1 -0
  30. package/dist/src/tui/history-replay.d.ts +33 -0
  31. package/dist/src/tui/history-replay.d.ts.map +1 -0
  32. package/dist/src/tui/history-replay.js +47 -0
  33. package/dist/src/tui/history-replay.js.map +1 -0
  34. package/dist/src/tui/initial-prompt.d.ts +24 -0
  35. package/dist/src/tui/initial-prompt.d.ts.map +1 -0
  36. package/dist/src/tui/initial-prompt.js +29 -0
  37. package/dist/src/tui/initial-prompt.js.map +1 -0
  38. package/dist/src/tui/key-handlers.d.ts +49 -0
  39. package/dist/src/tui/key-handlers.d.ts.map +1 -0
  40. package/dist/src/tui/key-handlers.js +192 -0
  41. package/dist/src/tui/key-handlers.js.map +1 -0
  42. package/dist/src/tui/layout.d.ts +71 -0
  43. package/dist/src/tui/layout.d.ts.map +1 -0
  44. package/dist/src/tui/layout.js +245 -0
  45. package/dist/src/tui/layout.js.map +1 -0
  46. package/dist/src/tui/paste-controller.d.ts +90 -0
  47. package/dist/src/tui/paste-controller.d.ts.map +1 -0
  48. package/dist/src/tui/paste-controller.js +234 -0
  49. package/dist/src/tui/paste-controller.js.map +1 -0
  50. package/dist/src/tui/question-picker.d.ts +111 -0
  51. package/dist/src/tui/question-picker.d.ts.map +1 -0
  52. package/dist/src/tui/question-picker.js +308 -0
  53. package/dist/src/tui/question-picker.js.map +1 -0
  54. package/dist/src/tui/renderer-lifecycle.d.ts +20 -0
  55. package/dist/src/tui/renderer-lifecycle.d.ts.map +1 -0
  56. package/dist/src/tui/renderer-lifecycle.js +50 -0
  57. package/dist/src/tui/renderer-lifecycle.js.map +1 -0
  58. package/dist/src/tui/session-subscription.d.ts +30 -0
  59. package/dist/src/tui/session-subscription.d.ts.map +1 -0
  60. package/dist/src/tui/session-subscription.js +78 -0
  61. package/dist/src/tui/session-subscription.js.map +1 -0
  62. package/dist/src/tui/sidebar.d.ts +2 -2
  63. package/dist/src/tui/sidebar.d.ts.map +1 -1
  64. package/dist/src/tui/sidebar.js +1 -1
  65. package/dist/src/tui/sidebar.js.map +1 -1
  66. package/dist/src/tui/slash-commands.d.ts +23 -0
  67. package/dist/src/tui/slash-commands.d.ts.map +1 -0
  68. package/dist/src/tui/slash-commands.js +79 -0
  69. package/dist/src/tui/slash-commands.js.map +1 -0
  70. package/dist/src/tui/starter-section.d.ts +99 -0
  71. package/dist/src/tui/starter-section.d.ts.map +1 -0
  72. package/dist/src/tui/starter-section.js +254 -0
  73. package/dist/src/tui/starter-section.js.map +1 -0
  74. package/dist/src/tui/status-controller.d.ts +94 -0
  75. package/dist/src/tui/status-controller.d.ts.map +1 -0
  76. package/dist/src/tui/status-controller.js +186 -0
  77. package/dist/src/tui/status-controller.js.map +1 -0
  78. package/dist/src/tui/step-renderer.d.ts +55 -0
  79. package/dist/src/tui/step-renderer.d.ts.map +1 -0
  80. package/dist/src/tui/step-renderer.js +246 -0
  81. package/dist/src/tui/step-renderer.js.map +1 -0
  82. package/dist/src/tui/transcript-writer.d.ts +63 -0
  83. package/dist/src/tui/transcript-writer.d.ts.map +1 -0
  84. package/dist/src/tui/transcript-writer.js +130 -0
  85. package/dist/src/tui/transcript-writer.js.map +1 -0
  86. package/dist/src/turn-runner/turn-runner.d.ts +77 -1
  87. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  88. package/dist/src/turn-runner/turn-runner.js +150 -12
  89. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  90. package/dist/src/turn-runner/usage-accounting.d.ts +18 -1
  91. package/dist/src/turn-runner/usage-accounting.d.ts.map +1 -1
  92. package/dist/src/turn-runner/usage-accounting.js +48 -27
  93. package/dist/src/turn-runner/usage-accounting.js.map +1 -1
  94. package/dist/src/types/protocol.d.ts +48 -17
  95. package/dist/src/types/protocol.d.ts.map +1 -1
  96. package/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ import type { Session } from "../session/session.js";
2
+ import type { TranscriptEntry } from "./transcript-log.js";
3
+ import type { StatusController } from "./status-controller.js";
4
+ import type { StepRenderer } from "./step-renderer.js";
5
+ export interface BootstrapInitialPromptDeps {
6
+ session: Session;
7
+ initialPrompt?: string;
8
+ statusController: StatusController;
9
+ stepRenderer: StepRenderer;
10
+ appendBlock(label: string | null, body: string, fg: string): void;
11
+ recordTranscriptEntry(kind: TranscriptEntry["kind"], text: string): void;
12
+ reportError(error: unknown): void;
13
+ }
14
+ /**
15
+ * Kicks off the first turn (when `--prompt` was supplied) or paints the
16
+ * appropriate idle/sleep banner when no initial prompt was provided.
17
+ *
18
+ * A resumed sleeping session emitted its `sleep` terminal during
19
+ * `session.hydrate()` — before any TUI subscriber attached — so we read
20
+ * the last terminal here and surface it explicitly so the user can see
21
+ * when the next wake will fire.
22
+ */
23
+ export declare function bootstrapInitialPrompt(deps: BootstrapInitialPromptDeps): void;
24
+ //# sourceMappingURL=initial-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initial-prompt.d.ts","sourceRoot":"","sources":["../../../src/tui/initial-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClE,qBAAqB,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzE,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,0BAA0B,GAAG,IAAI,CAwB7E"}
@@ -0,0 +1,29 @@
1
+ import { COLORS } from "./theme.js";
2
+ /**
3
+ * Kicks off the first turn (when `--prompt` was supplied) or paints the
4
+ * appropriate idle/sleep banner when no initial prompt was provided.
5
+ *
6
+ * A resumed sleeping session emitted its `sleep` terminal during
7
+ * `session.hydrate()` — before any TUI subscriber attached — so we read
8
+ * the last terminal here and surface it explicitly so the user can see
9
+ * when the next wake will fire.
10
+ */
11
+ export function bootstrapInitialPrompt(deps) {
12
+ const { session, initialPrompt, statusController, stepRenderer, appendBlock, recordTranscriptEntry, reportError, } = deps;
13
+ if (initialPrompt) {
14
+ recordTranscriptEntry("user", initialPrompt);
15
+ appendBlock("you:", initialPrompt, COLORS.user);
16
+ void session.prompt({ message: initialPrompt, behavior: "follow_up" }).catch(reportError);
17
+ statusController.markRunning();
18
+ return;
19
+ }
20
+ const pending = session.getLastTerminal();
21
+ if (pending?.type === "sleep") {
22
+ stepRenderer.renderSleeping(pending.wakeAt);
23
+ statusController.markIdle(pending);
24
+ }
25
+ else {
26
+ statusController.markIdle();
27
+ }
28
+ }
29
+ //# sourceMappingURL=initial-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initial-prompt.js","sourceRoot":"","sources":["../../../src/tui/initial-prompt.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAYpC;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAgC;IACrE,MAAM,EACJ,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,WAAW,GACZ,GAAG,IAAI,CAAC;IACT,IAAI,aAAa,EAAE,CAAC;QAClB,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAC7C,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC1F,gBAAgB,CAAC,WAAW,EAAE,CAAC;QAC/B,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAC1C,IAAI,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;QAC9B,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5C,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,gBAAgB,CAAC,QAAQ,EAAE,CAAC;IAC9B,CAAC;AACH,CAAC"}
@@ -0,0 +1,49 @@
1
+ import type { CliRenderer, ScrollBoxRenderable, TextareaRenderable } from "@opentui/core";
2
+ import type { AutocompleteController } from "./autocomplete-controller.js";
3
+ import type { CopyController } from "./copy-controller.js";
4
+ import type { PasteController } from "./paste-controller.js";
5
+ import type { QuestionPicker } from "./question-picker.js";
6
+ import type { StarterSection } from "./starter-section.js";
7
+ import type { TranscriptWriter } from "./transcript-writer.js";
8
+ /**
9
+ * Mutable flag shared between Esc-closing controllers (autocomplete, question
10
+ * picker) and the global Esc handler. The controllers set `suppress = true`
11
+ * when they consume Escape so the next bubbled Escape does not also cancel
12
+ * the active turn. The flag is reset on the very next Escape press.
13
+ */
14
+ export interface EscapeSuppressionFlag {
15
+ suppress: boolean;
16
+ }
17
+ export interface KeyHandlerDeps {
18
+ renderer: CliRenderer;
19
+ inputField: TextareaRenderable;
20
+ transcript: ScrollBoxRenderable;
21
+ autocomplete: AutocompleteController;
22
+ questionPicker: QuestionPicker;
23
+ pasteController: PasteController;
24
+ copyController: CopyController;
25
+ starters: StarterSection | undefined;
26
+ transcriptWriter: TranscriptWriter;
27
+ escapeState: EscapeSuppressionFlag;
28
+ /** Submit the current composer contents as a regular follow_up turn. */
29
+ onSubmit(value: string): void;
30
+ /** Cancel any in-flight turn (Escape when running). */
31
+ onEscape(): void;
32
+ /** Dispatch the composer text with behavior:"steer" (Ctrl+Enter). */
33
+ onSteer(): void;
34
+ }
35
+ /**
36
+ * Wires up the two keyboard event surfaces the TUI listens on:
37
+ *
38
+ * - The renderer's global keypress hook, used for keystrokes that fire
39
+ * regardless of focus (Esc cancel, Cmd/Ctrl+Shift+C copy when focus
40
+ * has drifted off the textarea during drag-select).
41
+ * - The composer `TextareaRenderable`'s `onKeyDown`, which intercepts
42
+ * keys before the textarea's own bindings (Esc would otherwise be
43
+ * consumed by Textarea internals).
44
+ *
45
+ * All input keystroke routing lives here so `runTui` stays a wiring
46
+ * sketch and key handling can be reasoned about in one place.
47
+ */
48
+ export declare function installKeyHandlers(deps: KeyHandlerDeps): void;
49
+ //# sourceMappingURL=key-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-handlers.d.ts","sourceRoot":"","sources":["../../../src/tui/key-handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAY,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAMD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,UAAU,EAAE,mBAAmB,CAAC;IAChC,YAAY,EAAE,sBAAsB,CAAC;IACrC,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAC;IACrC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,EAAE,qBAAqB,CAAC;IACnC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,uDAAuD;IACvD,QAAQ,IAAI,IAAI,CAAC;IACjB,qEAAqE;IACrE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAkM7D"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Wires up the two keyboard event surfaces the TUI listens on:
3
+ *
4
+ * - The renderer's global keypress hook, used for keystrokes that fire
5
+ * regardless of focus (Esc cancel, Cmd/Ctrl+Shift+C copy when focus
6
+ * has drifted off the textarea during drag-select).
7
+ * - The composer `TextareaRenderable`'s `onKeyDown`, which intercepts
8
+ * keys before the textarea's own bindings (Esc would otherwise be
9
+ * consumed by Textarea internals).
10
+ *
11
+ * All input keystroke routing lives here so `runTui` stays a wiring
12
+ * sketch and key handling can be reasoned about in one place.
13
+ */
14
+ export function installKeyHandlers(deps) {
15
+ const { renderer, inputField, transcript, autocomplete, questionPicker, pasteController, copyController, starters, transcriptWriter, escapeState, onSubmit, onEscape, onSteer, } = deps;
16
+ const keyHandler = renderer._keyHandler;
17
+ keyHandler.onInternal("keypress", (key) => {
18
+ transcriptWriter.logKey("global", key);
19
+ // Copy keystroke. Lives on the global handler (not
20
+ // inputField.onKeyDown) because the mousedown that starts a
21
+ // drag-select moves focus off the textarea — the focused-renderable
22
+ // path stops firing right when the user has something to copy. The
23
+ // global handler always fires regardless of focus.
24
+ if (copyController.handleCopyKeystroke(key))
25
+ return;
26
+ if (key.name !== "escape")
27
+ return;
28
+ if (escapeState.suppress) {
29
+ escapeState.suppress = false;
30
+ key.preventDefault();
31
+ return;
32
+ }
33
+ if (autocomplete.isSkillPickerOpen() || autocomplete.isFilePickerOpen()) {
34
+ key.preventDefault();
35
+ autocomplete.hideAll();
36
+ return;
37
+ }
38
+ if (questionPicker.isOpen()) {
39
+ key.preventDefault();
40
+ questionPicker.hide();
41
+ return;
42
+ }
43
+ key.preventDefault();
44
+ onEscape();
45
+ });
46
+ // Keyboard scroll bindings for the transcript. Mirrors the mouse wheel
47
+ // for terminals that swallow mouse events (tmux without mouse mode, ssh
48
+ // sessions where the local terminal owns the wheel, screen readers).
49
+ // Page = one viewport; Shift+arrow = three lines, matching wheel cadence.
50
+ function scrollByLines(delta) {
51
+ transcript.scrollBy({ x: 0, y: delta });
52
+ }
53
+ function scrollByPage(direction) {
54
+ // Subtract 2 to account for the top+bottom border rows; padding lives
55
+ // inside the scroll viewport and does not need to be deducted.
56
+ const viewport = Math.max(1, transcript.height - 2);
57
+ transcript.scrollBy({ x: 0, y: direction * viewport });
58
+ }
59
+ // Attach directly to the focused InputRenderable. The Textarea-based input
60
+ // consumes escape via its own keybindings before any global keypress handler
61
+ // fires, so we intercept at the Renderable's onKeyDown hook which runs first.
62
+ inputField.onKeyDown = (key) => {
63
+ transcriptWriter.logKey("keydown", key);
64
+ if (key.name === "pageup") {
65
+ scrollByPage(-1);
66
+ key.preventDefault();
67
+ return;
68
+ }
69
+ if (key.name === "pagedown") {
70
+ scrollByPage(1);
71
+ key.preventDefault();
72
+ return;
73
+ }
74
+ if (key.shift && key.name === "up" && !key.ctrl && !key.meta && !key.super) {
75
+ scrollByLines(-3);
76
+ key.preventDefault();
77
+ return;
78
+ }
79
+ if (key.shift && key.name === "down" && !key.ctrl && !key.meta && !key.super) {
80
+ scrollByLines(3);
81
+ key.preventDefault();
82
+ return;
83
+ }
84
+ // Boot starter navigation. Only intercepts when the starter section is
85
+ // still on screen; once dismissed (by composition or first submit) all
86
+ // of these branches no-op and keys flow normally to the input.
87
+ if (starters?.isVisible() && inputField.plainText.length === 0) {
88
+ if (key.name === "up") {
89
+ starters.move(-1);
90
+ key.preventDefault();
91
+ return;
92
+ }
93
+ if (key.name === "down") {
94
+ starters.move(1);
95
+ key.preventDefault();
96
+ return;
97
+ }
98
+ if (key.name &&
99
+ key.name.length === 1 &&
100
+ key.name >= "1" &&
101
+ key.name <= "9" &&
102
+ !key.ctrl &&
103
+ !key.meta &&
104
+ !key.super) {
105
+ const target = Number.parseInt(key.name, 10) - 1;
106
+ if (starters.jump(target)) {
107
+ key.preventDefault();
108
+ return;
109
+ }
110
+ }
111
+ }
112
+ // Cmd+V / Ctrl+V keystroke trigger. Many terminals (Warp in particular,
113
+ // and macOS Terminal.app for binary clipboards) do not forward a paste
114
+ // event to TUI programs on Cmd+V — they handle the clipboard at the app
115
+ // level and only deliver the resulting text. We catch the keystroke here
116
+ // and probe the OS clipboard directly so image attach works regardless of
117
+ // whether the terminal cooperates with bracketed paste for binary data.
118
+ //
119
+ // The keystroke handler only fires on terminals that actually deliver
120
+ // Cmd+V as a keypress (kitty-keyboard-aware terminals: kitty, Ghostty,
121
+ // recent iTerm2, WezTerm). For terminals that swallow Cmd+V entirely,
122
+ // the `/paste` slash command below provides a guaranteed fallback.
123
+ if (key.name === "v" && (key.super || key.meta || key.ctrl) && !key.shift) {
124
+ key.preventDefault();
125
+ void pasteController.triggerClipboardProbe("keystroke");
126
+ return;
127
+ }
128
+ if (autocomplete.handleKey(key))
129
+ return;
130
+ if (questionPicker.handleKey(key))
131
+ return;
132
+ if (key.name === "return" || key.name === "enter") {
133
+ // Three modifier flavors on the Enter key. The modifier is only
134
+ // distinguishable when the terminal speaks the kitty-keyboard
135
+ // protocol (or modifyOtherKeys); in a legacy terminal Ctrl+Enter
136
+ // and Shift+Enter collapse to plain Enter. We accept that
137
+ // tradeoff for the same reason Shift+Enter accepts it: modern
138
+ // terminals are the target.
139
+ //
140
+ // Plain Enter → submit (idle = fresh turn, running = soft queue
141
+ // via follow_up).
142
+ // Shift+Enter → insert a literal newline at the cursor, matching
143
+ // every modern chat composer (Slack, ChatGPT,
144
+ // Discord, Cursor, Claude Code).
145
+ // Ctrl+Enter → steer: dispatch with behavior:"steer" so the
146
+ // runner hands it to agent.steer() at the next
147
+ // inference boundary instead of waiting for the
148
+ // full turn to wrap up.
149
+ key.preventDefault();
150
+ if (key.shift) {
151
+ inputField.insertText("\n");
152
+ return;
153
+ }
154
+ if (key.ctrl) {
155
+ onSteer();
156
+ return;
157
+ }
158
+ const value = inputField.plainText.trim();
159
+ // Pre-latch the starter chrome before clearing the composer so the
160
+ // clear-induced `onContentChange` doesn't briefly re-mount the chrome
161
+ // (it would see empty composer + starters hidden and call
162
+ // mountStarterChrome before submit got a chance to destroy them).
163
+ // Only pre-latch when we know a typed-submit is about to fire —
164
+ // empty-Enter into the picker / starter-row branches still need the
165
+ // chrome state intact so they can dispatch.
166
+ if (value && starters && !starters.isPermanentlyDismissed()) {
167
+ starters.destroyPermanently();
168
+ }
169
+ inputField.clear();
170
+ if (value) {
171
+ onSubmit(value);
172
+ }
173
+ else if (questionPicker.isOpen()) {
174
+ questionPicker.confirmSelection();
175
+ }
176
+ else if (starters?.isVisible()) {
177
+ starters.submitHighlighted();
178
+ }
179
+ return;
180
+ }
181
+ if (key.name === "escape") {
182
+ // Suppress the next global keypress for this Escape press so the
183
+ // renderer-level handler does not also call onEscape — the textarea
184
+ // hook runs first and consumes the keystroke here.
185
+ key.preventDefault();
186
+ escapeState.suppress = true;
187
+ onEscape();
188
+ return;
189
+ }
190
+ };
191
+ }
192
+ //# sourceMappingURL=key-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-handlers.js","sourceRoot":"","sources":["../../../src/tui/key-handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { BoxRenderable, type CliRenderer, ScrollBoxRenderable, TextRenderable, TextareaRenderable } from "@opentui/core";
2
+ import { createSidebar } from "./sidebar.js";
3
+ /**
4
+ * Sidebar handle returned by {@link createSidebar}; exposed on
5
+ * {@link LayoutRefs} so the turn controller can push todo / state-machine /
6
+ * context-usage updates without re-importing the sidebar factory.
7
+ */
8
+ type SidebarHandle = ReturnType<typeof createSidebar>;
9
+ /**
10
+ * Renderable references for the static TUI chrome built by
11
+ * {@link buildLayout}. Every field is mounted into `renderer.root` before
12
+ * this object is returned, so callers only need to wire behavior (event
13
+ * listeners, content updates, visibility flips).
14
+ */
15
+ export interface LayoutRefs {
16
+ /** Outer row container holding the main column and the sidebar. */
17
+ root: BoxRenderable;
18
+ /** Main column: transcript, status, hint, pickers, input. */
19
+ layout: BoxRenderable;
20
+ /** Right-side panel with todos, follow-up queue, state machine, and context usage. */
21
+ sidebar: SidebarHandle;
22
+ /** Scrollable transcript that pins to the bottom while the user has not manually scrolled. */
23
+ transcript: ScrollBoxRenderable;
24
+ /** Single-line working-status row below the transcript (spinner, elapsed, queue size). */
25
+ status: TextRenderable;
26
+ /** Single-line hint row showing the active keystrokes (Enter / Esc / copy). */
27
+ hint: TextRenderable;
28
+ /** Slash + skill autocomplete panel; toggled via `visible` from the autocomplete controller. */
29
+ skillAutocompletePanel: BoxRenderable;
30
+ /** Header row for the "commands" section of the slash/skill picker. */
31
+ commandHeader: TextRenderable;
32
+ /** Fixed pool of selectable rows for built-in slash commands. */
33
+ commandRows: TextRenderable[];
34
+ /** Header row for the "skills" section of the slash/skill picker. */
35
+ skillHeader: TextRenderable;
36
+ /** Fixed pool of selectable rows for matched skills. */
37
+ skillRows: TextRenderable[];
38
+ /** `@`-file picker panel; mirrors the slash panel's structure. */
39
+ fileAutocompletePanel: BoxRenderable;
40
+ /** Static "files" title row for the file picker. */
41
+ fileAutocompleteTitle: TextRenderable;
42
+ /** Fixed pool of selectable rows for matched files. */
43
+ fileAutocompleteRows: TextRenderable[];
44
+ /** Pending question panel; shown while the runner has questions awaiting answers. */
45
+ questionPanel: BoxRenderable;
46
+ /** Title row for the active question (prefix, prompt, navigation hint). */
47
+ questionTitle: TextRenderable;
48
+ /** Blank spacer between the question title and the option rows. */
49
+ questionSpacer: TextRenderable;
50
+ /** Fixed pool of selectable rows for the active question's options. */
51
+ questionRows: TextRenderable[];
52
+ /** Bordered row containing the prompt sigil and the input textarea. */
53
+ inputBox: BoxRenderable;
54
+ /** Leading "> " sigil rendered before the textarea; excluded from drag-select. */
55
+ prompt: TextRenderable;
56
+ /** Multi-line input textarea; focused on mount, soft-wraps long messages. */
57
+ inputField: TextareaRenderable;
58
+ }
59
+ /**
60
+ * Builds the static TUI chrome (containers, transcript, status / hint rows,
61
+ * autocomplete + question panels, input box) and attaches everything to
62
+ * `renderer.root`. The returned {@link LayoutRefs} are stable for the
63
+ * lifetime of the renderer; callers wire behavior on top without
64
+ * re-creating any of these nodes.
65
+ *
66
+ * Focus is set on `inputField` before returning so the user can type
67
+ * immediately on mount.
68
+ */
69
+ export declare function buildLayout(renderer: CliRenderer): LayoutRefs;
70
+ export {};
71
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/tui/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,KAAK,WAAW,EAChB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAO7C;;;;GAIG;AACH,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,IAAI,EAAE,aAAa,CAAC;IACpB,6DAA6D;IAC7D,MAAM,EAAE,aAAa,CAAC;IACtB,sFAAsF;IACtF,OAAO,EAAE,aAAa,CAAC;IACvB,8FAA8F;IAC9F,UAAU,EAAE,mBAAmB,CAAC;IAChC,0FAA0F;IAC1F,MAAM,EAAE,cAAc,CAAC;IACvB,+EAA+E;IAC/E,IAAI,EAAE,cAAc,CAAC;IACrB,gGAAgG;IAChG,sBAAsB,EAAE,aAAa,CAAC;IACtC,uEAAuE;IACvE,aAAa,EAAE,cAAc,CAAC;IAC9B,iEAAiE;IACjE,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,qEAAqE;IACrE,WAAW,EAAE,cAAc,CAAC;IAC5B,wDAAwD;IACxD,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,kEAAkE;IAClE,qBAAqB,EAAE,aAAa,CAAC;IACrC,oDAAoD;IACpD,qBAAqB,EAAE,cAAc,CAAC;IACtC,uDAAuD;IACvD,oBAAoB,EAAE,cAAc,EAAE,CAAC;IACvC,qFAAqF;IACrF,aAAa,EAAE,aAAa,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,cAAc,CAAC;IAC9B,mEAAmE;IACnE,cAAc,EAAE,cAAc,CAAC;IAC/B,uEAAuE;IACvE,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,EAAE,aAAa,CAAC;IACxB,kFAAkF;IAClF,MAAM,EAAE,cAAc,CAAC;IACvB,6EAA6E;IAC7E,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,WAAW,GAAG,UAAU,CAiP7D"}
@@ -0,0 +1,245 @@
1
+ import { BoxRenderable, ScrollBoxRenderable, TextRenderable, TextareaRenderable, } from "@opentui/core";
2
+ import { AUTOCOMPLETE_LIMITS, BUILT_IN_SLASH_COMMANDS } from "./autocomplete.js";
3
+ import { createSidebar } from "./sidebar.js";
4
+ import { COLORS, HINT_IDLE } from "./theme.js";
5
+ const SKILL_AUTOCOMPLETE_LIMIT = AUTOCOMPLETE_LIMITS.skill;
6
+ const FILE_AUTOCOMPLETE_LIMIT = AUTOCOMPLETE_LIMITS.file;
7
+ const QUESTION_OPTION_LIMIT = AUTOCOMPLETE_LIMITS.questionOption;
8
+ /**
9
+ * Builds the static TUI chrome (containers, transcript, status / hint rows,
10
+ * autocomplete + question panels, input box) and attaches everything to
11
+ * `renderer.root`. The returned {@link LayoutRefs} are stable for the
12
+ * lifetime of the renderer; callers wire behavior on top without
13
+ * re-creating any of these nodes.
14
+ *
15
+ * Focus is set on `inputField` before returning so the user can type
16
+ * immediately on mount.
17
+ */
18
+ export function buildLayout(renderer) {
19
+ // Outer row wraps the main column and a right-side sidebar that surfaces
20
+ // the runner's current todo list and state-machine progress.
21
+ const root = new BoxRenderable(renderer, {
22
+ flexDirection: "row",
23
+ width: "100%",
24
+ height: "100%",
25
+ });
26
+ const layout = new BoxRenderable(renderer, {
27
+ flexDirection: "column",
28
+ flexGrow: 1,
29
+ flexShrink: 1,
30
+ height: "100%",
31
+ });
32
+ const sidebar = createSidebar(renderer);
33
+ const transcript = new ScrollBoxRenderable(renderer, {
34
+ flexGrow: 1,
35
+ flexShrink: 1,
36
+ scrollY: true,
37
+ // Pin to bottom as new lines arrive, but only while the user has not
38
+ // manually scrolled away. ScrollBoxRenderable flips `_hasManualScroll`
39
+ // the moment the user scrolls up, which pauses pinning until they
40
+ // return to the bottom — without this, new output yanks the viewport
41
+ // down while the user is reading history.
42
+ stickyScroll: true,
43
+ stickyStart: "bottom",
44
+ border: true,
45
+ borderColor: COLORS.border,
46
+ padding: 1,
47
+ });
48
+ // Status and hint chrome are excluded from drag-select so a highlight that
49
+ // sweeps the bottom of the screen does not pull the spinner / hint text
50
+ // into the clipboard alongside the transcript content the user wanted.
51
+ const status = new TextRenderable(renderer, {
52
+ content: "",
53
+ fg: COLORS.status,
54
+ height: 1,
55
+ flexShrink: 0,
56
+ selectable: false,
57
+ });
58
+ const hint = new TextRenderable(renderer, {
59
+ content: HINT_IDLE,
60
+ fg: COLORS.hint,
61
+ height: 1,
62
+ flexShrink: 0,
63
+ selectable: false,
64
+ });
65
+ const skillAutocompletePanel = new BoxRenderable(renderer, {
66
+ flexDirection: "column",
67
+ border: true,
68
+ borderColor: COLORS.border,
69
+ paddingLeft: 1,
70
+ paddingRight: 1,
71
+ flexShrink: 0,
72
+ });
73
+ skillAutocompletePanel.visible = false;
74
+ // Two ordered sections: built-in commands first, skills second. Each
75
+ // section has its own header row plus a fixed pool of item rows. Selection
76
+ // navigates the flat ordered list of visible items across both sections.
77
+ // Row height is assigned per render based on the wrapped description
78
+ // length so a one-line description doesn't leave an empty trailing line
79
+ // beneath the name. The renderer sets `height` whenever it writes
80
+ // `content`.
81
+ // Autocomplete and panel chrome are not part of the transcript content,
82
+ // so exclude them from drag-select to keep the clipboard focused on
83
+ // assistant/user messages.
84
+ const makeItemRow = () => {
85
+ const row = new TextRenderable(renderer, {
86
+ content: "",
87
+ fg: COLORS.hint,
88
+ flexShrink: 0,
89
+ selectable: false,
90
+ });
91
+ row.visible = false;
92
+ return row;
93
+ };
94
+ const makeHeaderRow = (label) => new TextRenderable(renderer, {
95
+ content: label,
96
+ fg: COLORS.status,
97
+ height: 1,
98
+ flexShrink: 0,
99
+ selectable: false,
100
+ });
101
+ const commandHeader = makeHeaderRow("commands");
102
+ const commandRows = Array.from({ length: BUILT_IN_SLASH_COMMANDS.length }, makeItemRow);
103
+ const skillHeader = makeHeaderRow("skills");
104
+ const skillRows = Array.from({ length: SKILL_AUTOCOMPLETE_LIMIT }, makeItemRow);
105
+ skillAutocompletePanel.add(commandHeader);
106
+ for (const row of commandRows)
107
+ skillAutocompletePanel.add(row);
108
+ skillAutocompletePanel.add(skillHeader);
109
+ for (const row of skillRows)
110
+ skillAutocompletePanel.add(row);
111
+ // The @-file picker mirrors the slash picker's structure so the renderer
112
+ // logic and key handling can stay parallel between the two pickers.
113
+ const fileAutocompletePanel = new BoxRenderable(renderer, {
114
+ flexDirection: "column",
115
+ border: true,
116
+ borderColor: COLORS.border,
117
+ paddingLeft: 1,
118
+ paddingRight: 1,
119
+ flexShrink: 0,
120
+ });
121
+ fileAutocompletePanel.visible = false;
122
+ const fileAutocompleteTitle = new TextRenderable(renderer, {
123
+ content: "files",
124
+ fg: COLORS.status,
125
+ height: 1,
126
+ flexShrink: 0,
127
+ selectable: false,
128
+ });
129
+ const fileAutocompleteRows = Array.from({ length: FILE_AUTOCOMPLETE_LIMIT }, () => {
130
+ const row = new TextRenderable(renderer, {
131
+ content: "",
132
+ fg: COLORS.hint,
133
+ height: 1,
134
+ flexShrink: 0,
135
+ selectable: false,
136
+ });
137
+ row.visible = false;
138
+ return row;
139
+ });
140
+ fileAutocompletePanel.add(fileAutocompleteTitle);
141
+ for (const row of fileAutocompleteRows) {
142
+ fileAutocompletePanel.add(row);
143
+ }
144
+ const questionPanel = new BoxRenderable(renderer, {
145
+ flexDirection: "column",
146
+ border: true,
147
+ borderColor: COLORS.border,
148
+ paddingLeft: 1,
149
+ paddingRight: 1,
150
+ flexShrink: 0,
151
+ });
152
+ questionPanel.visible = false;
153
+ const questionTitle = new TextRenderable(renderer, {
154
+ content: "question",
155
+ fg: COLORS.agent,
156
+ wrapMode: "word",
157
+ flexShrink: 0,
158
+ selectable: false,
159
+ });
160
+ const questionSpacer = new TextRenderable(renderer, {
161
+ content: "",
162
+ height: 1,
163
+ flexShrink: 0,
164
+ selectable: false,
165
+ });
166
+ const questionRows = Array.from({ length: QUESTION_OPTION_LIMIT }, () => {
167
+ const row = new TextRenderable(renderer, {
168
+ content: "",
169
+ fg: COLORS.hint,
170
+ wrapMode: "word",
171
+ flexShrink: 0,
172
+ selectable: false,
173
+ });
174
+ row.visible = false;
175
+ return row;
176
+ });
177
+ questionPanel.add(questionTitle);
178
+ questionPanel.add(questionSpacer);
179
+ for (const row of questionRows) {
180
+ questionPanel.add(row);
181
+ }
182
+ const inputBox = new BoxRenderable(renderer, {
183
+ flexDirection: "row",
184
+ border: true,
185
+ borderColor: COLORS.border,
186
+ paddingLeft: 1,
187
+ paddingRight: 1,
188
+ flexShrink: 0,
189
+ });
190
+ // The leading "> " sigil is decoration, not content; excluding it from
191
+ // selection means a drag that starts at the input row does not pull the
192
+ // sigil into the clipboard alongside the highlighted text.
193
+ const prompt = new TextRenderable(renderer, {
194
+ content: "> ",
195
+ fg: COLORS.user,
196
+ width: 2,
197
+ selectable: false,
198
+ });
199
+ // Textarea (rather than Input) so long messages soft-wrap visually. Enter
200
+ // is intercepted in onKeyDown below to submit instead of inserting a newline.
201
+ const inputField = new TextareaRenderable(renderer, {
202
+ placeholder: "Type a message and press Enter…",
203
+ flexGrow: 1,
204
+ minHeight: 1,
205
+ maxHeight: 10,
206
+ wrapMode: "word",
207
+ });
208
+ inputBox.add(prompt);
209
+ inputBox.add(inputField);
210
+ layout.add(transcript);
211
+ layout.add(status);
212
+ layout.add(hint);
213
+ layout.add(skillAutocompletePanel);
214
+ layout.add(fileAutocompletePanel);
215
+ layout.add(questionPanel);
216
+ layout.add(inputBox);
217
+ root.add(layout);
218
+ root.add(sidebar.view);
219
+ renderer.root.add(root);
220
+ inputField.focus();
221
+ return {
222
+ root,
223
+ layout,
224
+ sidebar,
225
+ transcript,
226
+ status,
227
+ hint,
228
+ skillAutocompletePanel,
229
+ commandHeader,
230
+ commandRows,
231
+ skillHeader,
232
+ skillRows,
233
+ fileAutocompletePanel,
234
+ fileAutocompleteTitle,
235
+ fileAutocompleteRows,
236
+ questionPanel,
237
+ questionTitle,
238
+ questionSpacer,
239
+ questionRows,
240
+ inputBox,
241
+ prompt,
242
+ inputField,
243
+ };
244
+ }
245
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../src/tui/layout.ts"],"names":[],"mappings":"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"}