@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,23 @@
1
+ import type { CopyController } from "./copy-controller.js";
2
+ import type { PasteController } from "./paste-controller.js";
3
+ import type { TranscriptWriter } from "./transcript-writer.js";
4
+ /**
5
+ * Surface used by the slash-command dispatcher. Every handler in this module
6
+ * is stateless and reaches its mutable state through the controllers wired up
7
+ * by `runTui`. The dispatcher returns true when the message was claimed so
8
+ * `submit()` can short-circuit before the message reaches `session.prompt()`.
9
+ */
10
+ export interface SlashCommandContext {
11
+ pasteController: PasteController;
12
+ copyController: CopyController;
13
+ transcriptWriter: TranscriptWriter;
14
+ appendBlock(label: string | null, body: string, fg: string): void;
15
+ }
16
+ /**
17
+ * Routes the message to a local handler when it begins with a slash command
18
+ * recognized by the TUI. Returns true when handled (caller must not forward
19
+ * the message to the runner) and false when the message should fall through
20
+ * to the normal prompt path.
21
+ */
22
+ export declare function tryDispatchSlashCommand(message: string, ctx: SlashCommandContext): boolean;
23
+ //# sourceMappingURL=slash-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slash-commands.d.ts","sourceRoot":"","sources":["../../../src/tui/slash-commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI/D;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACnE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CA2B1F"}
@@ -0,0 +1,79 @@
1
+ import { submitDuetFeedback } from "../lib/feedback.js";
2
+ import { COLORS } from "./theme.js";
3
+ /**
4
+ * Routes the message to a local handler when it begins with a slash command
5
+ * recognized by the TUI. Returns true when handled (caller must not forward
6
+ * the message to the runner) and false when the message should fall through
7
+ * to the normal prompt path.
8
+ */
9
+ export function tryDispatchSlashCommand(message, ctx) {
10
+ if (message.startsWith("/image ") || message === "/image") {
11
+ void handleImageSlashCommand(message, ctx);
12
+ return true;
13
+ }
14
+ if (message === "/paste") {
15
+ void ctx.pasteController.triggerClipboardProbe("slash");
16
+ return true;
17
+ }
18
+ if (message === "/clear-images") {
19
+ ctx.pasteController.clearPendingImages();
20
+ ctx.appendBlock("[paste]", "cleared pending image attachments", COLORS.system);
21
+ return true;
22
+ }
23
+ if (message === "/copy" || message.startsWith("/copy ")) {
24
+ void ctx.copyController.handleCopySlashCommand(message);
25
+ return true;
26
+ }
27
+ if (message === "/diag" || message.startsWith("/diag ")) {
28
+ handleDiagSlashCommand(message, ctx);
29
+ return true;
30
+ }
31
+ if (message === "/feedback" || message.startsWith("/feedback ")) {
32
+ void handleFeedbackSlashCommand(message, ctx);
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+ /**
38
+ * `/diag` toggles a key + selection event log so users can show us exactly
39
+ * what their terminal forwards when something silently fails (a keystroke
40
+ * that never reaches the handler, a selection event firing with empty text).
41
+ * Kept as a flag rather than a one-shot capture so we can layer additional
42
+ * diagnostic facets on the same surface without inventing new commands.
43
+ */
44
+ function handleDiagSlashCommand(raw, ctx) {
45
+ const argument = raw === "/diag" ? "" : raw.slice("/diag ".length).trim();
46
+ if (argument === "" || argument === "keys") {
47
+ const enabled = !ctx.transcriptWriter.isKeyDiagnosticsEnabled();
48
+ ctx.transcriptWriter.setKeyDiagnosticsEnabled(enabled);
49
+ ctx.appendBlock("[diag]", enabled
50
+ ? "key + selection event logging ON. Run /diag again to stop."
51
+ : "key + selection event logging OFF.", COLORS.system);
52
+ return;
53
+ }
54
+ ctx.appendBlock("[diag]", "Usage: /diag (or /diag keys) — toggles key + selection event logging", COLORS.system);
55
+ }
56
+ async function handleFeedbackSlashCommand(raw, ctx) {
57
+ const content = raw.slice("/feedback".length).trim();
58
+ if (!content) {
59
+ ctx.appendBlock("[feedback]", "Usage: /feedback <message> — send free-form feedback to the Duet team", COLORS.system);
60
+ return;
61
+ }
62
+ ctx.appendBlock("[feedback]", "sending…", COLORS.system);
63
+ try {
64
+ const { baseUrl } = await submitDuetFeedback({ content });
65
+ ctx.appendBlock("[feedback]", `Thanks! Feedback sent to ${baseUrl}.`, COLORS.system);
66
+ }
67
+ catch (error) {
68
+ ctx.appendBlock("[feedback]", error instanceof Error ? error.message : String(error), COLORS.error);
69
+ }
70
+ }
71
+ async function handleImageSlashCommand(raw, ctx) {
72
+ const rest = raw.slice("/image".length).trim();
73
+ if (!rest) {
74
+ ctx.appendBlock("[paste]", "Usage: /image <path> — attach a PNG/JPEG/GIF/WebP from disk", COLORS.system);
75
+ return;
76
+ }
77
+ await ctx.pasteController.attachImageFromPath(rest);
78
+ }
79
+ //# sourceMappingURL=slash-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../../src/tui/slash-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
+ import { type CliRenderer, type ScrollBoxRenderable, type TextareaRenderable } from "@opentui/core";
3
+ import type { TranscriptWriter } from "./transcript-writer.js";
4
+ export interface StarterSectionOptions {
5
+ /** Working directory used to seed cwd-aware starter prompts. */
6
+ workDir: string;
7
+ /** Current session id; excluded from the recent-sessions list so the
8
+ * "pick up the thread" rows do not advertise the live session. */
9
+ sessionId: string;
10
+ /** Optional resumed-conversation history fed into starter selection so
11
+ * the picker can surface a resume prompt synthesized from the last
12
+ * user turn. */
13
+ history?: AgentMessage[];
14
+ /** Composer renderable. The section watches `plainText.length` to decide
15
+ * whether to repaint the chrome on `syncVisibility`. */
16
+ inputField: TextareaRenderable;
17
+ /** Transcript scrollbox; each rendered starter row is appended to the
18
+ * scrollbox via `transcriptWriter.addLine` and removed on dismiss. */
19
+ transcript: ScrollBoxRenderable;
20
+ /** Transcript writer surfaces `addLine` for the rendered starter rows
21
+ * (so the scrollbox owns them) but the section never writes any
22
+ * bordered blocks. */
23
+ transcriptWriter: TranscriptWriter;
24
+ /** Renderer; only used to tear the TUI down when a "pick up the thread"
25
+ * row signals a resume to the outer dispatcher. */
26
+ renderer: CliRenderer;
27
+ /** Submit a typed prompt as if the user had hit Enter. Wired to
28
+ * `runTui`'s `submit` so non-resume starter rows kick off a real turn. */
29
+ submit: (text: string) => void;
30
+ /** When wired, recent-session rows route through this callback so the
31
+ * outer dispatcher can swap sessions; when unset (tests, playground)
32
+ * the section falls back to re-submitting the prior prompt. */
33
+ onResumeRequest?: (sessionId: string) => void;
34
+ }
35
+ /**
36
+ * Boot-screen starter picker. Offers a small set of context-aware prompts
37
+ * so first-time and returning users land on something concrete in <2
38
+ * seconds instead of staring at a blank input.
39
+ *
40
+ * Lifecycle:
41
+ *
42
+ * - `mount(skills)` runs once on boot and lays down the section.
43
+ * - `syncVisibility()` hides/restores the chrome as the composer fills
44
+ * and empties, until the user actually commits a prompt.
45
+ * - The first submit calls `destroyPermanently()` and the section
46
+ * never comes back this session.
47
+ *
48
+ * `runTui` skips construction entirely when `input.isResume` is set —
49
+ * the user explicitly asked to drop back into a known conversation and
50
+ * "what should we work on today?" before replaying history is noise.
51
+ */
52
+ export declare class StarterSection {
53
+ private readonly opts;
54
+ private readonly entries;
55
+ private readonly refs;
56
+ private readonly rowIndexes;
57
+ private highlightedIndex;
58
+ private visible;
59
+ private permanentlyDismissed;
60
+ private skillCount;
61
+ constructor(opts: StarterSectionOptions);
62
+ /**
63
+ * Build the entry list from cwd-aware starter selection plus the recent
64
+ * sessions on disk, then paint the chrome. Idempotent across the section
65
+ * lifecycle in the sense that calling it again with no entries is a
66
+ * no-op, but typical use is exactly once on boot.
67
+ */
68
+ mount(skills: ReadonlyArray<{
69
+ name: string;
70
+ }>): void;
71
+ isVisible(): boolean;
72
+ move(delta: number): void;
73
+ jump(targetIndex: number): boolean;
74
+ /**
75
+ * Transient hide. Tears down the rendered refs but keeps `entries` and
76
+ * `highlightedIndex` so `mountChrome()` can restore the exact same
77
+ * picker if the user backspaces the composer empty again.
78
+ */
79
+ dismiss(): void;
80
+ /**
81
+ * Permanent destruction: called when the user commits a prompt. After
82
+ * this the chrome never comes back, even on backspace-to-empty.
83
+ */
84
+ destroyPermanently(): void;
85
+ isPermanentlyDismissed(): boolean;
86
+ /**
87
+ * Toggle hook called from `inputField.onContentChange`. Hides the chrome
88
+ * as soon as the user starts composing, brings it back if they backspace
89
+ * the composer empty (but only until they actually submit — then
90
+ * `permanentlyDismissed` latches).
91
+ */
92
+ syncVisibility(): void;
93
+ submitHighlighted(): boolean;
94
+ private mountChrome;
95
+ private addLine;
96
+ private formatRow;
97
+ private paintHighlight;
98
+ }
99
+ //# sourceMappingURL=starter-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter-section.d.ts","sourceRoot":"","sources":["../../../src/tui/starter-section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAExB,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;AAKvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB;uEACmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB;;qBAEiB;IACjB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB;6DACyD;IACzD,UAAU,EAAE,kBAAkB,CAAC;IAC/B;2EACuE;IACvE,UAAU,EAAE,mBAAmB,CAAC;IAChC;;2BAEuB;IACvB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;wDACoD;IACpD,QAAQ,EAAE,WAAW,CAAC;IACtB;+EAC2E;IAC3E,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B;;oEAEgE;IAChE,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,cAAc;IAgBb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAfjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAG7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,OAAO,CAAS;IAIxB,OAAO,CAAC,oBAAoB,CAAS;IAGrC,OAAO,CAAC,UAAU,CAAK;gBAEM,IAAI,EAAE,qBAAqB;IAExD;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;IAmCpD,SAAS,IAAI,OAAO;IAIpB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOzB,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAQlC;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAWf;;;OAGG;IACH,kBAAkB,IAAI,IAAI;IAO1B,sBAAsB,IAAI,OAAO;IAIjC;;;;;OAKG;IACH,cAAc,IAAI,IAAI;IAWtB,iBAAiB,IAAI,OAAO;IA6B5B,OAAO,CAAC,WAAW;IAiEnB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,cAAc;CAQvB"}
@@ -0,0 +1,254 @@
1
+ import { listRecentSessions } from "./recent-sessions.js";
2
+ import { orderedSelectableStarters, selectStarters } from "./starters.js";
3
+ import { COLORS } from "./theme.js";
4
+ /**
5
+ * Boot-screen starter picker. Offers a small set of context-aware prompts
6
+ * so first-time and returning users land on something concrete in <2
7
+ * seconds instead of staring at a blank input.
8
+ *
9
+ * Lifecycle:
10
+ *
11
+ * - `mount(skills)` runs once on boot and lays down the section.
12
+ * - `syncVisibility()` hides/restores the chrome as the composer fills
13
+ * and empties, until the user actually commits a prompt.
14
+ * - The first submit calls `destroyPermanently()` and the section
15
+ * never comes back this session.
16
+ *
17
+ * `runTui` skips construction entirely when `input.isResume` is set —
18
+ * the user explicitly asked to drop back into a known conversation and
19
+ * "what should we work on today?" before replaying history is noise.
20
+ */
21
+ export class StarterSection {
22
+ opts;
23
+ entries = [];
24
+ refs = [];
25
+ // Indexes within `refs` that correspond to numbered rows; used to
26
+ // repaint highlight on arrow / digit navigation.
27
+ rowIndexes = [];
28
+ highlightedIndex = 0;
29
+ visible = false;
30
+ // Once the user commits a prompt, the chrome is gone for the session.
31
+ // Until then `dismiss()` is a reversible hide that `syncVisibility()`
32
+ // can restore when the composer empties again.
33
+ permanentlyDismissed = false;
34
+ // Cached so the chrome can re-render the trailing "✦ N skills · /help"
35
+ // line without re-running selectStarters.
36
+ skillCount = 0;
37
+ constructor(opts) {
38
+ this.opts = opts;
39
+ }
40
+ /**
41
+ * Build the entry list from cwd-aware starter selection plus the recent
42
+ * sessions on disk, then paint the chrome. Idempotent across the section
43
+ * lifecycle in the sense that calling it again with no entries is a
44
+ * no-op, but typical use is exactly once on boot.
45
+ */
46
+ mount(skills) {
47
+ // Read recent sessions off disk synchronously. The helper swallows fs
48
+ // errors and returns an empty list; a missing/empty ~/.duet/sessions
49
+ // directory is the common first-boot case.
50
+ const recentSessions = listRecentSessions({
51
+ excludeId: this.opts.sessionId,
52
+ limit: 4,
53
+ });
54
+ const result = selectStarters({
55
+ cwd: this.opts.workDir,
56
+ sessionHistory: this.opts.history,
57
+ recentSessions,
58
+ });
59
+ // Selectable rows in render order. Recent sessions lead so returning
60
+ // users hit "pick up the thread" first; new users see the cwd
61
+ // starters under the original "what should we work on today?" header.
62
+ const ordered = orderedSelectableStarters(result);
63
+ this.entries.length = 0;
64
+ for (const row of ordered) {
65
+ if (row.kind === "recent" && row.sessionId !== undefined) {
66
+ this.entries.push({
67
+ kind: "recent",
68
+ label: row.label,
69
+ submit: row.submit,
70
+ sessionId: row.sessionId,
71
+ });
72
+ }
73
+ else {
74
+ this.entries.push({ kind: "prompt", label: row.label, submit: row.submit });
75
+ }
76
+ }
77
+ this.skillCount = skills.length;
78
+ this.highlightedIndex = 0;
79
+ this.mountChrome();
80
+ }
81
+ isVisible() {
82
+ return this.visible;
83
+ }
84
+ move(delta) {
85
+ if (!this.visible || this.entries.length === 0)
86
+ return;
87
+ const next = (this.highlightedIndex + delta + this.entries.length) % this.entries.length;
88
+ this.highlightedIndex = next;
89
+ this.paintHighlight();
90
+ }
91
+ jump(targetIndex) {
92
+ if (!this.visible)
93
+ return false;
94
+ if (targetIndex < 0 || targetIndex >= this.entries.length)
95
+ return false;
96
+ this.highlightedIndex = targetIndex;
97
+ this.paintHighlight();
98
+ return true;
99
+ }
100
+ /**
101
+ * Transient hide. Tears down the rendered refs but keeps `entries` and
102
+ * `highlightedIndex` so `mountChrome()` can restore the exact same
103
+ * picker if the user backspaces the composer empty again.
104
+ */
105
+ dismiss() {
106
+ if (!this.visible && this.refs.length === 0)
107
+ return;
108
+ for (const ref of this.refs) {
109
+ this.opts.transcript.remove(ref.id);
110
+ ref.destroy();
111
+ }
112
+ this.refs.length = 0;
113
+ this.rowIndexes.length = 0;
114
+ this.visible = false;
115
+ }
116
+ /**
117
+ * Permanent destruction: called when the user commits a prompt. After
118
+ * this the chrome never comes back, even on backspace-to-empty.
119
+ */
120
+ destroyPermanently() {
121
+ this.dismiss();
122
+ this.entries.length = 0;
123
+ this.highlightedIndex = 0;
124
+ this.permanentlyDismissed = true;
125
+ }
126
+ isPermanentlyDismissed() {
127
+ return this.permanentlyDismissed;
128
+ }
129
+ /**
130
+ * Toggle hook called from `inputField.onContentChange`. Hides the chrome
131
+ * as soon as the user starts composing, brings it back if they backspace
132
+ * the composer empty (but only until they actually submit — then
133
+ * `permanentlyDismissed` latches).
134
+ */
135
+ syncVisibility() {
136
+ if (this.permanentlyDismissed)
137
+ return;
138
+ if (this.entries.length === 0)
139
+ return;
140
+ const empty = this.opts.inputField.plainText.length === 0;
141
+ if (!empty && this.visible) {
142
+ this.dismiss();
143
+ }
144
+ else if (empty && !this.visible) {
145
+ this.mountChrome();
146
+ }
147
+ }
148
+ submitHighlighted() {
149
+ if (!this.visible)
150
+ return false;
151
+ const entry = this.entries[this.highlightedIndex];
152
+ if (!entry)
153
+ return false;
154
+ this.destroyPermanently();
155
+ // Recent-session rows: when the host wires `onResumeRequest`, signal
156
+ // the outer dispatcher to swap sessions and tear down this renderer.
157
+ // The dispatcher disposes the placeholder, calls `manager.resume(id)`
158
+ // + `hydrate()` + `start()`, and re-enters `runTui` with the hydrated
159
+ // session + its full message history. End user lands on the same
160
+ // session id, same agent context, same transcript replayed inline.
161
+ //
162
+ // When no callback is wired (tests, playground), fall back to the
163
+ // legacy shortcut: re-submit the prior prompt in the current session.
164
+ // Agent has no context; the user lands on the same task with one
165
+ // keystroke instead of typing it again.
166
+ if (entry.kind === "recent" && this.opts.onResumeRequest) {
167
+ this.opts.onResumeRequest(entry.sessionId);
168
+ this.opts.renderer.destroy();
169
+ return true;
170
+ }
171
+ this.opts.submit(entry.submit);
172
+ return true;
173
+ }
174
+ // Paint the chrome (section headers, numbered rows, hint footer) from
175
+ // the current `entries` + `highlightedIndex`. Called on first boot and
176
+ // on every backspace-to-empty restoration. Idempotent: bails if the
177
+ // chrome is already mounted or no entries exist.
178
+ mountChrome() {
179
+ if (this.visible || this.entries.length === 0)
180
+ return;
181
+ const recentEntries = this.entries.filter((entry) => entry.kind === "recent");
182
+ const promptEntries = this.entries.filter((entry) => entry.kind === "prompt");
183
+ const hasRecent = recentEntries.length > 0;
184
+ // Every line we render here — spacers included — goes through
185
+ // `addLine` and gets pushed to `refs`. `dismiss()` iterates that list
186
+ // to destroy refs; spacers added via fire-and-forget `appendLine`
187
+ // would leak and accumulate above the next mount on each type →
188
+ // backspace cycle, pushing the chrome lower every time.
189
+ const spacer = () => {
190
+ this.refs.push(this.addLine(" ", COLORS.hint));
191
+ };
192
+ this.rowIndexes.length = 0;
193
+ spacer();
194
+ if (hasRecent) {
195
+ this.refs.push(this.addLine("pick up the thread", COLORS.agent));
196
+ spacer();
197
+ for (let i = 0; i < this.entries.length; i += 1) {
198
+ if (this.entries[i].kind !== "recent")
199
+ continue;
200
+ const ref = this.addLine(this.formatRow(i, false), COLORS.hint);
201
+ this.rowIndexes.push(this.refs.length);
202
+ this.refs.push(ref);
203
+ }
204
+ if (promptEntries.length > 0) {
205
+ spacer();
206
+ this.refs.push(this.addLine("or start something new", COLORS.agent));
207
+ spacer();
208
+ for (let i = 0; i < this.entries.length; i += 1) {
209
+ if (this.entries[i].kind !== "prompt")
210
+ continue;
211
+ const ref = this.addLine(this.formatRow(i, false), COLORS.hint);
212
+ this.rowIndexes.push(this.refs.length);
213
+ this.refs.push(ref);
214
+ }
215
+ }
216
+ }
217
+ else {
218
+ this.refs.push(this.addLine("what should we work on today?", COLORS.agent));
219
+ spacer();
220
+ for (let i = 0; i < this.entries.length; i += 1) {
221
+ const ref = this.addLine(this.formatRow(i, false), COLORS.hint);
222
+ this.rowIndexes.push(this.refs.length);
223
+ this.refs.push(ref);
224
+ }
225
+ }
226
+ spacer();
227
+ this.refs.push(this.addLine("type a number to run, ↑/↓ to highlight, or just start typing.", COLORS.hint));
228
+ this.refs.push(this.addLine(`✦ ${this.skillCount} skill${this.skillCount === 1 ? "" : "s"} · /help`, COLORS.hint));
229
+ this.visible = true;
230
+ if (this.highlightedIndex >= this.entries.length)
231
+ this.highlightedIndex = 0;
232
+ this.paintHighlight();
233
+ }
234
+ addLine(content, fg) {
235
+ return this.opts.transcriptWriter.addLine(content, fg);
236
+ }
237
+ formatRow(index, highlighted) {
238
+ const entry = this.entries[index];
239
+ const text = entry?.label ?? "";
240
+ const number = index + 1;
241
+ const arrow = highlighted ? "▶" : "→";
242
+ const numberCell = highlighted ? `[${number}]` : ` ${number} `;
243
+ return ` ${numberCell} ${arrow} ${text}`;
244
+ }
245
+ paintHighlight() {
246
+ for (let i = 0; i < this.rowIndexes.length; i += 1) {
247
+ const ref = this.refs[this.rowIndexes[i]];
248
+ const isHighlighted = i === this.highlightedIndex;
249
+ ref.content = this.formatRow(i, isHighlighted);
250
+ ref.fg = isHighlighted ? COLORS.user : COLORS.hint;
251
+ }
252
+ }
253
+ }
254
+ //# sourceMappingURL=starter-section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starter-section.js","sourceRoot":"","sources":["../../../src/tui/starter-section.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { type CliRenderer, type TextRenderable } from "@opentui/core";
2
+ import type { TurnTerminalEvent } from "../types/protocol.js";
3
+ export interface StatusControllerOptions {
4
+ /** Renderer that owns the chrome TextBuffers. Passed through so the
5
+ * controller can be constructed alongside the other chrome-touching
6
+ * controllers without re-deriving it from `status`/`hint`. */
7
+ renderer: CliRenderer;
8
+ /** Working-status line ("● working… (3s)"). Mutated by `setStatus` and the
9
+ * ticker; cleared when idle. */
10
+ status: TextRenderable;
11
+ /** Bottom hint row. Recomposed by `setHint` from the running/attachment/
12
+ * selection segments. */
13
+ hint: TextRenderable;
14
+ /**
15
+ * Re-paints every in-flight tool block header with a fresh spinner +
16
+ * elapsed counter. Invoked from the ticker so spinners advance in step
17
+ * with the working-status line. Provided externally because the tool
18
+ * block map lives with the step renderer, not the status controller.
19
+ */
20
+ refreshActiveToolBlocks: () => void;
21
+ }
22
+ /**
23
+ * Owns the "is a turn running?" surface of the TUI: the working-status line,
24
+ * the bottom hint row, and the 1 s ticker that advances both while a turn is
25
+ * in flight. Also remembers the most recent terminal event so `runTui` can
26
+ * return it to the caller after the renderer is destroyed.
27
+ *
28
+ * Preserves the v0.1.68 dual-mechanism guard against the OpenTUI race where
29
+ * child TextBuffers are destroyed synchronously *before* the `destroy` event
30
+ * fires: writes short-circuit on `destroyed`, and a `try/catch` around the
31
+ * actual buffer mutation swallows the late-arriving exception and tears down
32
+ * the ticker so the next callback does not throw again.
33
+ */
34
+ export declare class StatusController {
35
+ private readonly opts;
36
+ private destroyed;
37
+ private running;
38
+ private workingStartedAt;
39
+ private workingTicker;
40
+ private workingMessage;
41
+ private queuedFollowUps;
42
+ private pendingImageCount;
43
+ private lastSelectionText;
44
+ private terminal;
45
+ constructor(opts: StatusControllerOptions);
46
+ isRunning(): boolean;
47
+ lastTerminal(): TurnTerminalEvent | undefined;
48
+ /** Wall-clock start of the current turn, or undefined while idle. Read by
49
+ * the "● turn finished in Ns" and sleep banners which live outside this
50
+ * controller but want to show the same elapsed counter. */
51
+ getWorkingStartedAt(): number | undefined;
52
+ setWorkingMessage(message: string): void;
53
+ setQueuedFollowUps(count: number): void;
54
+ /** Mirror of the most recent drag-selection text. Stored here so the hint
55
+ * row can advertise the copy keystroke only while something is selectable;
56
+ * the canonical selection state lives in `runTui`. */
57
+ setSelectionText(text: string): void;
58
+ /** Mirror of `pendingImages.length`. Stored here so the hint row can show
59
+ * the attachment count without dragging the whole `PendingImage[]` into
60
+ * the controller. */
61
+ setPendingImageCount(count: number): void;
62
+ markRunning(): void;
63
+ /** Settle the working-status surface and optionally record the terminal
64
+ * event that ended the turn. The terminal is exposed via `lastTerminal()`
65
+ * so `runTui` can return it to its caller after renderer teardown. */
66
+ markIdle(terminal?: TurnTerminalEvent): void;
67
+ setStatus(text: string): void;
68
+ /** Recompose the bottom hint row from the controller's current running
69
+ * state plus any active attachment / selection segments. Called from any
70
+ * input that changes a hint segment (attachments, selection) and from
71
+ * the running-state transitions in `markRunning` / `markIdle`. */
72
+ refreshHint(): void;
73
+ refreshWorkingStatus(): void;
74
+ /** Flip the destroyed flag and stop the ticker. Invoked from the renderer
75
+ * destroy handler and from our own try/catch when a buffer write fires the
76
+ * destroyed-buffer guard mid-flight. Subsequent chrome writes no-op. */
77
+ shutdown(): void;
78
+ private startWorkingTicker;
79
+ private stopWorkingTicker;
80
+ private attachmentHint;
81
+ }
82
+ /**
83
+ * Renders an elapsed millisecond span as `${seconds}s` below a minute, and
84
+ * `${minutes}m ${seconds}s` past it. Exported because the tool-call finalizer
85
+ * and the post-turn banners share the same format.
86
+ */
87
+ export declare function formatElapsed(ms: number): string;
88
+ /**
89
+ * Spinner marker for an in-flight tool call. Sub-second runs drop the
90
+ * elapsed counter so a transcript of fast tools is not littered with "0s"
91
+ * markers.
92
+ */
93
+ export declare function runningMarker(elapsedMs: number): string;
94
+ //# sourceMappingURL=status-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-controller.d.ts","sourceRoot":"","sources":["../../../src/tui/status-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC;;mEAE+D;IAC/D,QAAQ,EAAE,WAAW,CAAC;IACtB;qCACiC;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB;8BAC0B;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;OAKG;IACH,uBAAuB,EAAE,MAAM,IAAI,CAAC;CACrC;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAgB;IAWf,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,cAAc,CAAc;IACpC,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,QAAQ,CAAgC;gBAEnB,IAAI,EAAE,uBAAuB;IAE1D,SAAS,IAAI,OAAO;IAIpB,YAAY,IAAI,iBAAiB,GAAG,SAAS;IAI7C;;gEAE4D;IAC5D,mBAAmB,IAAI,MAAM,GAAG,SAAS;IAIzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKxC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKvC;;2DAEuD;IACvD,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKpC;;0BAEsB;IACtB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKzC,WAAW,IAAI,IAAI;IASnB;;2EAEuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAU5C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAoB7B;;;uEAGmE;IACnE,WAAW,IAAI,IAAI;IAkBnB,oBAAoB,IAAI,IAAI;IAY5B;;6EAEyE;IACzE,QAAQ,IAAI,IAAI;IAKhB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,cAAc;CAIvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD"}