@goah/cli 0.11.4 → 0.13.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 (160) hide show
  1. package/dist/cli.js +165 -66
  2. package/dist/cli.js.map +1 -1
  3. package/dist/console/assets/index-3sYrDMmt.css +1 -0
  4. package/dist/console/assets/index-Rtc_B7cs.js +52 -0
  5. package/dist/console/assets/index-Rtc_B7cs.js.map +1 -0
  6. package/dist/console/index.html +2 -2
  7. package/dist/control.d.ts +13 -13
  8. package/dist/control.d.ts.map +1 -1
  9. package/dist/control.js +77 -50
  10. package/dist/control.js.map +1 -1
  11. package/dist/index.d.ts +6 -23
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +156 -28
  14. package/dist/index.js.map +1 -1
  15. package/dist/live-config.js +2 -2
  16. package/dist/live-config.js.map +1 -1
  17. package/dist/prompt-queue.d.ts.map +1 -1
  18. package/dist/prompt-queue.js +5 -0
  19. package/dist/prompt-queue.js.map +1 -1
  20. package/dist/setup-wizard.d.ts +13 -2
  21. package/dist/setup-wizard.d.ts.map +1 -1
  22. package/dist/setup-wizard.js +87 -63
  23. package/dist/setup-wizard.js.map +1 -1
  24. package/dist/thread-inspect.d.ts +47 -0
  25. package/dist/thread-inspect.d.ts.map +1 -0
  26. package/dist/thread-inspect.js +78 -0
  27. package/dist/thread-inspect.js.map +1 -0
  28. package/dist/transcript.d.ts +2 -0
  29. package/dist/transcript.d.ts.map +1 -0
  30. package/dist/transcript.js +2 -0
  31. package/dist/transcript.js.map +1 -0
  32. package/dist/tui-theme.d.ts +1 -0
  33. package/dist/tui-theme.d.ts.map +1 -1
  34. package/dist/tui-theme.js +1 -0
  35. package/dist/tui-theme.js.map +1 -1
  36. package/dist/tui.d.ts +16 -3
  37. package/dist/tui.d.ts.map +1 -1
  38. package/dist/tui.js +332 -80
  39. package/dist/tui.js.map +1 -1
  40. package/dist/update.d.ts +1 -0
  41. package/dist/update.d.ts.map +1 -1
  42. package/dist/update.js +15 -7
  43. package/dist/update.js.map +1 -1
  44. package/dist/web-console.d.ts +1 -1
  45. package/dist/web-console.d.ts.map +1 -1
  46. package/dist/web-console.js +27 -37
  47. package/dist/web-console.js.map +1 -1
  48. package/dist/welcome.d.ts +2 -2
  49. package/dist/welcome.d.ts.map +1 -1
  50. package/dist/welcome.js +24 -36
  51. package/dist/welcome.js.map +1 -1
  52. package/node_modules/goah-ledger-contract/dist/execution.d.ts +272 -130
  53. package/node_modules/goah-ledger-contract/dist/execution.d.ts.map +1 -1
  54. package/node_modules/goah-ledger-contract/dist/execution.js +31 -19
  55. package/node_modules/goah-ledger-contract/dist/execution.js.map +1 -1
  56. package/node_modules/goah-ledger-contract/dist/index.d.ts +1 -2
  57. package/node_modules/goah-ledger-contract/dist/index.d.ts.map +1 -1
  58. package/node_modules/goah-ledger-contract/dist/index.js +1 -2
  59. package/node_modules/goah-ledger-contract/dist/index.js.map +1 -1
  60. package/node_modules/goah-ledger-contract/dist/kernel.d.ts +1 -1
  61. package/node_modules/goah-ledger-contract/dist/kernel.d.ts.map +1 -1
  62. package/node_modules/goah-ledger-contract/dist/kernel.js +1 -1
  63. package/node_modules/goah-ledger-contract/dist/kernel.js.map +1 -1
  64. package/node_modules/goah-ledger-contract/dist/transcript.d.ts +63 -0
  65. package/node_modules/goah-ledger-contract/dist/transcript.d.ts.map +1 -0
  66. package/node_modules/goah-ledger-contract/dist/transcript.js +115 -0
  67. package/node_modules/goah-ledger-contract/dist/transcript.js.map +1 -0
  68. package/node_modules/goah-ledger-contract/package.json +3 -4
  69. package/node_modules/goah-ledger-sqlite/dist/index.d.ts +32 -23
  70. package/node_modules/goah-ledger-sqlite/dist/index.d.ts.map +1 -1
  71. package/node_modules/goah-ledger-sqlite/dist/index.js +764 -394
  72. package/node_modules/goah-ledger-sqlite/dist/index.js.map +1 -1
  73. package/node_modules/goah-ledger-sqlite/package.json +2 -2
  74. package/node_modules/goah-runner-pi/dist/configurator.d.ts.map +1 -1
  75. package/node_modules/goah-runner-pi/dist/configurator.js +186 -30
  76. package/node_modules/goah-runner-pi/dist/configurator.js.map +1 -1
  77. package/node_modules/goah-runner-pi/dist/credential-store.d.ts +2 -0
  78. package/node_modules/goah-runner-pi/dist/credential-store.d.ts.map +1 -1
  79. package/node_modules/goah-runner-pi/dist/credential-store.js +117 -23
  80. package/node_modules/goah-runner-pi/dist/credential-store.js.map +1 -1
  81. package/node_modules/goah-runner-pi/dist/index.d.ts +18 -6
  82. package/node_modules/goah-runner-pi/dist/index.d.ts.map +1 -1
  83. package/node_modules/goah-runner-pi/dist/index.js +97 -12
  84. package/node_modules/goah-runner-pi/dist/index.js.map +1 -1
  85. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts +6 -2
  86. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts.map +1 -1
  87. package/node_modules/goah-runner-pi/dist/pi-worker.js +232 -70
  88. package/node_modules/goah-runner-pi/dist/pi-worker.js.map +1 -1
  89. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts +2 -0
  90. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts.map +1 -0
  91. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js +3 -0
  92. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js.map +1 -0
  93. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts +2 -0
  94. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts.map +1 -0
  95. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js +5 -0
  96. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js.map +1 -0
  97. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts +2 -0
  98. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts.map +1 -0
  99. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js +5 -0
  100. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js.map +1 -0
  101. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts +1 -1
  102. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts.map +1 -1
  103. package/node_modules/goah-runner-pi/dist/test-helpers.js +3 -1
  104. package/node_modules/goah-runner-pi/dist/test-helpers.js.map +1 -1
  105. package/node_modules/goah-runner-pi/dist/verification-worker.d.ts.map +1 -1
  106. package/node_modules/goah-runner-pi/dist/verification-worker.js +3 -5
  107. package/node_modules/goah-runner-pi/dist/verification-worker.js.map +1 -1
  108. package/node_modules/goah-runner-pi/package.json +2 -2
  109. package/node_modules/goah-supervisor/dist/context-view.d.ts +4 -5
  110. package/node_modules/goah-supervisor/dist/context-view.d.ts.map +1 -1
  111. package/node_modules/goah-supervisor/dist/context-view.js +7 -20
  112. package/node_modules/goah-supervisor/dist/context-view.js.map +1 -1
  113. package/node_modules/goah-supervisor/dist/index.d.ts +38 -36
  114. package/node_modules/goah-supervisor/dist/index.d.ts.map +1 -1
  115. package/node_modules/goah-supervisor/dist/index.js +762 -575
  116. package/node_modules/goah-supervisor/dist/index.js.map +1 -1
  117. package/node_modules/goah-supervisor/dist/roles.d.ts +2 -1
  118. package/node_modules/goah-supervisor/dist/roles.d.ts.map +1 -1
  119. package/node_modules/goah-supervisor/dist/roles.js +20 -5
  120. package/node_modules/goah-supervisor/dist/roles.js.map +1 -1
  121. package/node_modules/goah-supervisor/dist/verification.d.ts +8 -29
  122. package/node_modules/goah-supervisor/dist/verification.d.ts.map +1 -1
  123. package/node_modules/goah-supervisor/dist/verification.js +71 -46
  124. package/node_modules/goah-supervisor/dist/verification.js.map +1 -1
  125. package/node_modules/goah-supervisor/package.json +2 -2
  126. package/node_modules/goah-testkit/dist/faux-runner-worker.js +16 -9
  127. package/node_modules/goah-testkit/dist/faux-runner-worker.js.map +1 -1
  128. package/node_modules/goah-testkit/dist/index.d.ts +13 -16
  129. package/node_modules/goah-testkit/dist/index.d.ts.map +1 -1
  130. package/node_modules/goah-testkit/dist/index.js +27 -44
  131. package/node_modules/goah-testkit/dist/index.js.map +1 -1
  132. package/node_modules/goah-testkit/package.json +5 -5
  133. package/package.json +7 -8
  134. package/dist/console/assets/index-BJTGvlCH.js +0 -52
  135. package/dist/console/assets/index-BJTGvlCH.js.map +0 -1
  136. package/dist/console/assets/index-msBn41Cl.css +0 -1
  137. package/dist/inspect.d.ts +0 -61
  138. package/dist/inspect.d.ts.map +0 -1
  139. package/dist/inspect.js +0 -110
  140. package/dist/inspect.js.map +0 -1
  141. package/dist/metrics.d.ts +0 -2
  142. package/dist/metrics.d.ts.map +0 -1
  143. package/dist/metrics.js +0 -2
  144. package/dist/metrics.js.map +0 -1
  145. package/dist/session.d.ts +0 -2
  146. package/dist/session.d.ts.map +0 -1
  147. package/dist/session.js +0 -2
  148. package/dist/session.js.map +0 -1
  149. package/node_modules/goah-ledger-contract/dist/metrics.d.ts +0 -40
  150. package/node_modules/goah-ledger-contract/dist/metrics.d.ts.map +0 -1
  151. package/node_modules/goah-ledger-contract/dist/metrics.js +0 -25
  152. package/node_modules/goah-ledger-contract/dist/metrics.js.map +0 -1
  153. package/node_modules/goah-ledger-contract/dist/session.d.ts +0 -63
  154. package/node_modules/goah-ledger-contract/dist/session.d.ts.map +0 -1
  155. package/node_modules/goah-ledger-contract/dist/session.js +0 -115
  156. package/node_modules/goah-ledger-contract/dist/session.js.map +0 -1
  157. package/node_modules/goah-testkit/dist/mock-connector-worker.d.ts +0 -2
  158. package/node_modules/goah-testkit/dist/mock-connector-worker.d.ts.map +0 -1
  159. package/node_modules/goah-testkit/dist/mock-connector-worker.js +0 -29
  160. package/node_modules/goah-testkit/dist/mock-connector-worker.js.map +0 -1
package/dist/tui.js CHANGED
@@ -1,14 +1,14 @@
1
1
  /** Full-screen goah TUI: streaming CEO transcript over the resident Supervisor control socket. */
2
2
  import { TuiAltScreen, Text, Markdown, Editor, ProcessTerminal, ScrollView, VStack } from "@earendil-works/pi-tui";
3
3
  import { controlAvailable, requestControl, streamControl } from "./control.js";
4
- import { readConsoleMetadata } from "./index.js";
4
+ import { loadConfig, readConsoleMetadata, readDefaultRunnerProfile } from "./index.js";
5
5
  import { switchModel, reloadDaemon, readRunnerDisplay } from "./live-config.js";
6
6
  import { welcomeSnapshot, renderWelcome } from "./welcome.js";
7
- import { runSetupWizard, applyWizardResult } from "./setup-wizard.js";
7
+ import { chooseSetupSection, runRunnerCommandWizard, runSetupWizard, applyWizardResult } from "./setup-wizard.js";
8
8
  import { installedVersion } from "./update.js";
9
9
  import { tuiTheme } from "./tui-theme.js";
10
10
  import { spawn } from "node:child_process";
11
- import { closeSync, mkdirSync, openSync } from "node:fs";
11
+ import { closeSync, existsSync, mkdirSync, openSync } from "node:fs";
12
12
  import { join, resolve } from "node:path";
13
13
  class HeaderBar {
14
14
  runner;
@@ -43,23 +43,79 @@ const markdownTheme = {
43
43
  class ConversationView {
44
44
  entries;
45
45
  liveMarkdown = "";
46
+ liveThinking = "";
47
+ thinkingActive = false;
46
48
  constructor(initial) { this.entries = [{ kind: "text", content: initial.join("\n") }]; }
47
49
  addText(content) { this.entries.push({ kind: "text", content }); this.trim(); }
48
- addMarkdown(content) { this.liveMarkdown = ""; this.entries.push({ kind: "markdown", content }); this.trim(); }
50
+ addUser(content) { this.entries.push({ kind: "user", content }); this.trim(); }
51
+ addMarkdown(content) {
52
+ this.liveMarkdown = "";
53
+ content = content.trim();
54
+ if (!content)
55
+ return;
56
+ const previous = this.entries.at(-1);
57
+ if (previous?.kind === "markdown" && previous.content === content)
58
+ return;
59
+ this.entries.push({ kind: "markdown", content });
60
+ this.trim();
61
+ }
49
62
  setLiveMarkdown(content) { this.liveMarkdown = content; }
50
63
  clearLiveMarkdown() { this.liveMarkdown = ""; }
64
+ updateThinking(activity) {
65
+ if (activity.phase === "start") {
66
+ this.liveThinking = "";
67
+ this.thinkingActive = true;
68
+ return;
69
+ }
70
+ if (activity.phase === "delta") {
71
+ this.liveThinking += activity.text;
72
+ this.thinkingActive = true;
73
+ return;
74
+ }
75
+ if (activity.phase === "clear") {
76
+ this.liveThinking = "";
77
+ this.thinkingActive = false;
78
+ return;
79
+ }
80
+ const content = (activity.text || this.liveThinking).trim();
81
+ this.liveThinking = "";
82
+ this.thinkingActive = false;
83
+ const previous = this.entries.at(-1);
84
+ if (content && !(previous?.kind === "thinking" && previous.content === content))
85
+ this.entries.push({ kind: "thinking", content });
86
+ this.trim();
87
+ }
88
+ updateTool(activity) {
89
+ const previous = this.entries.findLast((entry) => entry.kind === "tool" && entry.callId === activity.callId);
90
+ if (previous?.kind === "tool")
91
+ Object.assign(previous, { ...activity, detail: activity.detail || previous.detail });
92
+ else
93
+ this.entries.push(activity);
94
+ this.trim();
95
+ }
51
96
  trim() { if (this.entries.length > 240)
52
97
  this.entries.splice(1, this.entries.length - 240); }
53
98
  render(width) {
54
99
  const rendered = this.entries.flatMap((entry) => entry.kind === "markdown"
55
- ? [tuiTheme.accent(" goah"), ...new Markdown(entry.content, 2, 0, markdownTheme).render(width), ""]
56
- : new Text(entry.content, 2, 0).render(width));
100
+ ? [...new Markdown(entry.content, 2, 0, markdownTheme).render(width), ""]
101
+ : entry.kind === "user"
102
+ ? renderUserMessage(entry.content, width)
103
+ : entry.kind === "thinking"
104
+ ? [tuiTheme.muted(" thinking"), ...new Markdown(entry.content, 2, 0, markdownTheme, { color: tuiTheme.muted, italic: true }).render(width), ""]
105
+ : entry.kind === "tool"
106
+ ? new Text(toolActivityLine(entry), 2, 0).render(width)
107
+ : new Text(entry.content, 2, 0).render(width));
108
+ if (this.liveThinking)
109
+ rendered.push(tuiTheme.muted(" thinking"), ...new Markdown(this.liveThinking, 2, 0, markdownTheme, { color: tuiTheme.muted, italic: true }).render(width));
110
+ else if (this.thinkingActive)
111
+ rendered.push(tuiTheme.muted(" thinking…"));
57
112
  if (this.liveMarkdown)
58
- rendered.push(tuiTheme.accent(" goah"), ...new Markdown(this.liveMarkdown, 2, 0, markdownTheme).render(width));
113
+ rendered.push(...new Markdown(this.liveMarkdown, 2, 0, markdownTheme).render(width));
59
114
  return rendered;
60
115
  }
61
116
  invalidate() { }
62
117
  }
118
+ export function renderUserMessage(content, width) { return new Text(content, 2, 1, tuiTheme.userMessage).render(width); }
63
119
  export function renderTuiHeader(width, runner, target, version) {
64
120
  const brand = " GOAH ";
65
121
  const release = width >= 34 ? ` v${version} ` : "";
@@ -94,13 +150,17 @@ export function classifyTuiInput(value, busy) {
94
150
  return { action: "stop", text };
95
151
  if (text === "/model" || text.startsWith("/model "))
96
152
  return { action: "model", text };
97
- if (text === "/setup")
153
+ if (text === "/login" || text.startsWith("/login "))
154
+ return { action: "login", text };
155
+ if (text === "/logout" || text.startsWith("/logout "))
156
+ return { action: "logout", text };
157
+ if (text === "/setup" || text.startsWith("/setup "))
98
158
  return { action: "setup", text };
99
159
  if (text.startsWith("/goal ") || text.startsWith("/observe "))
100
160
  return { action: "goal", text };
101
- if (text.startsWith("/approve ") || text.startsWith("/reject "))
102
- return { action: "approval", text };
103
- return { action: busy ? "queue" : "send", text };
161
+ if (text.startsWith("/"))
162
+ return { action: "unknown", text };
163
+ return { action: busy ? "steer" : "send", text };
104
164
  }
105
165
  export async function runGoahTui(configPath, stateDir, initialMessage) {
106
166
  if (!process.stdout.isTTY || !process.stdin.isTTY)
@@ -109,11 +169,19 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
109
169
  const snapshot = welcomeSnapshot(stateDir, runner);
110
170
  const welcome = renderWelcome(snapshot, Boolean(snapshot.root || snapshot.handoffs.length || snapshot.conversation.length));
111
171
  await ensureDaemon(configPath, stateDir);
172
+ const liveSnapshot = await requestControl(stateDir, { op: "status" }).catch(() => null);
173
+ const liveTurns = liveSnapshot && typeof liveSnapshot === "object" && !Array.isArray(liveSnapshot) && Array.isArray(liveSnapshot.turns) ? liveSnapshot.turns : [];
174
+ const liveInteractionTurnId = findLiveTurnId(liveTurns);
112
175
  const terminal = new ProcessTerminal();
113
176
  const tui = new TuiAltScreen(terminal, true, undefined, { mouse: true });
114
177
  terminal.setTitle(`Goah · ${runner.target}`);
115
178
  const headerView = new HeaderBar(runner.runner, runner.target, installedVersion());
116
179
  const transcriptView = new ConversationView(welcome);
180
+ for (const row of snapshot.conversation)
181
+ if (row.speaker === "You")
182
+ transcriptView.addUser(row.text);
183
+ else
184
+ transcriptView.addMarkdown(row.text);
117
185
  const conversationScroll = new ScrollView(transcriptView, { follow: "end", primary: true, scrollbar: "auto", scrollbarStyle: tuiTheme.muted });
118
186
  const goalView = new GoalBar(snapshot.root);
119
187
  const input = new Editor(tui, {
@@ -130,7 +198,11 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
130
198
  ]);
131
199
  const busy = { active: false };
132
200
  const queued = [];
201
+ const queuedTurnIds = [];
133
202
  let activeStream = null;
203
+ let activeInteractionTurnId = null;
204
+ let steeringTail = Promise.resolve();
205
+ let configuring = false;
134
206
  let exiting = false;
135
207
  const push = (line) => {
136
208
  transcriptView.addText(line);
@@ -138,20 +210,29 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
138
210
  };
139
211
  let liveText = "";
140
212
  const appendLive = (text) => { liveText += text; transcriptView.setLiveMarkdown(liveText); tui.requestRender(); };
141
- const commitLive = (text) => { liveText = ""; if (text)
213
+ const commitLive = (text) => { liveText = ""; transcriptView.clearLiveMarkdown(); if (text)
142
214
  transcriptView.addMarkdown(text); tui.requestRender(); };
143
215
  const pushResponse = (text) => { transcriptView.addMarkdown(text); tui.requestRender(); };
144
- const send = async (message) => {
216
+ const updateTool = (activity) => { transcriptView.updateTool(activity); tui.requestRender(); };
217
+ const updateThinking = (activity) => { transcriptView.updateThinking(activity); tui.requestRender(); };
218
+ const setWakeState = (mode) => { statusView.setText(statusText(mode, mode === "queued" ? 1 : queued.length)); tui.requestRender(); };
219
+ const send = async (message, showUser = true) => {
145
220
  busy.active = true;
146
221
  const controller = new AbortController();
147
222
  activeStream = controller;
148
223
  statusView.setText(statusText("working", queued.length));
149
- push(`${tuiTheme.human("you")} ${message}`);
224
+ if (showUser) {
225
+ transcriptView.addUser(message);
226
+ tui.requestRender();
227
+ }
150
228
  try {
151
- await streamControl(stateDir, { op: "interact", message }, (frame) => renderFrame(frame, push, appendLive, commitLive, pushResponse), controller.signal);
229
+ await streamControl(stateDir, { op: "interact", message }, (frame) => { if (frame.type === "accepted")
230
+ activeInteractionTurnId = frame.turnId; if (frame.type === "result" || frame.type === "error")
231
+ activeInteractionTurnId = null; renderFrame(frame, push, appendLive, commitLive, pushResponse, updateTool, updateThinking, setWakeState, () => commitLive("")); }, controller.signal);
152
232
  }
153
233
  catch (error) {
154
234
  transcriptView.clearLiveMarkdown();
235
+ transcriptView.updateThinking({ phase: "clear", text: "" });
155
236
  if (!controller.signal.aborted)
156
237
  push(errorLine(error));
157
238
  }
@@ -160,30 +241,153 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
160
241
  activeStream = null;
161
242
  busy.active = false;
162
243
  await refreshGoalBar(stateDir, goalView, tui);
163
- statusView.setText(statusText(queued.length ? "queued" : "ready", queued.length));
164
- const next = exiting ? undefined : queued.shift();
165
- if (next)
166
- void send(next);
244
+ statusView.setText(statusText(queuedTurnIds.length || queued.length ? "queued" : "ready", queuedTurnIds.length + queued.length));
245
+ continuePending();
167
246
  }
168
247
  };
169
- const launchSetup = async () => {
170
- if (busy.active) {
171
- push("Wait for the current wake or use /stop before changing Runner configuration.");
248
+ const attachTurn = async (turnId) => {
249
+ busy.active = true;
250
+ activeInteractionTurnId = turnId;
251
+ const controller = new AbortController();
252
+ activeStream = controller;
253
+ statusView.setText(statusText("queued", 1));
254
+ try {
255
+ await streamControl(stateDir, { op: "turn.attach", turnId }, (frame) => { if (frame.type === "result" || frame.type === "error")
256
+ activeInteractionTurnId = null; renderFrame(frame, push, appendLive, commitLive, pushResponse, updateTool, updateThinking, setWakeState, () => commitLive("")); }, controller.signal);
257
+ }
258
+ catch (error) {
259
+ if (!controller.signal.aborted)
260
+ push(errorLine(error));
261
+ }
262
+ finally {
263
+ if (activeStream === controller)
264
+ activeStream = null;
265
+ busy.active = false;
266
+ await refreshGoalBar(stateDir, goalView, tui);
267
+ statusView.setText(statusText("ready"));
268
+ continuePending();
269
+ }
270
+ };
271
+ const continuePending = () => {
272
+ if (exiting || configuring || busy.active)
273
+ return;
274
+ const turnId = queuedTurnIds.shift();
275
+ if (turnId) {
276
+ void attachTurn(turnId);
172
277
  return;
173
278
  }
279
+ const next = queued.shift();
280
+ if (next)
281
+ void send(next, false);
282
+ };
283
+ const steer = async (message) => {
284
+ try {
285
+ const outcome = await requestControl(stateDir, { op: "turn.steer", message });
286
+ const value = outcome && typeof outcome === "object" && !Array.isArray(outcome) ? outcome : {};
287
+ const queuedBySupervisor = value.steered === false && typeof value.turnId === "string";
288
+ if (queuedBySupervisor) {
289
+ queuedTurnIds.push(String(value.turnId));
290
+ activeStream?.abort();
291
+ }
292
+ statusView.setText(queuedBySupervisor ? `${tuiTheme.warning("new Turn")} ${tuiTheme.muted("Runner stopped accepting steering; continuing in a fresh Turn")}` : `${tuiTheme.accent("working")} ${tuiTheme.muted("your update is steering this turn · /stop cancels")}`);
293
+ tui.requestRender();
294
+ if (queuedBySupervisor && !busy.active)
295
+ continuePending();
296
+ }
297
+ catch (error) {
298
+ const reason = error instanceof Error ? error.message : String(error);
299
+ if (reason.includes("no longer accepting steering messages")) {
300
+ queued.push(message);
301
+ statusView.setText(statusText("working", queued.length));
302
+ if (!busy.active)
303
+ continuePending();
304
+ }
305
+ else
306
+ push(errorLine(error));
307
+ }
308
+ };
309
+ const submitSteer = (message) => {
310
+ transcriptView.addUser(message);
311
+ tui.requestRender();
312
+ steeringTail = steeringTail.then(() => exiting ? undefined : steer(message));
313
+ };
314
+ const withConfigurationScreen = async (work) => {
315
+ if (busy.active) {
316
+ const snapshot = await requestControl(stateDir, { op: "status" }).catch(() => null);
317
+ if (!snapshot) {
318
+ push("Cannot inspect the current turn; use /stop before changing Runner configuration.");
319
+ return;
320
+ }
321
+ const turns = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot) && Array.isArray(snapshot.turns) ? snapshot.turns : [];
322
+ if (turns.some((turn) => turn.status === "in_progress")) {
323
+ push("Wait for the current Turn or use /stop before changing Runner configuration.");
324
+ return;
325
+ }
326
+ }
327
+ else
328
+ configuring = true;
174
329
  statusView.setText(statusText("setup"));
175
330
  tui.stop();
176
331
  try {
177
- await runSetupReload(configPath, stateDir, push);
332
+ await work();
333
+ }
334
+ catch (error) {
335
+ push(errorLine(error));
178
336
  }
179
337
  finally {
338
+ configuring = false;
180
339
  if (!exiting) {
181
340
  tui.start();
182
341
  tui.requestRender(true);
183
342
  statusView.setText(statusText("ready"));
343
+ continuePending();
184
344
  }
185
345
  }
186
346
  };
347
+ const launchRunnerCommand = async (command, commandArgs = []) => withConfigurationScreen(async () => {
348
+ const current = configuredRunnerProfile(configPath);
349
+ if (!current)
350
+ throw new Error("No Runner Profile is configured; use /setup first.");
351
+ const result = await runRunnerCommandWizard(current, command, commandArgs);
352
+ try {
353
+ await applyWizardResult({ profile: result.profile }, existsSync(configPath) ? configPath : null);
354
+ }
355
+ catch (error) {
356
+ await result.rollback?.();
357
+ throw error;
358
+ }
359
+ for (const line of result.output)
360
+ push(line);
361
+ push(await reloadDaemon(stateDir, configPath) ? "Configuration updated — applies to the next Turn." : "Configuration saved — restart Goah to apply it.");
362
+ });
363
+ const launchSetup = async (text) => withConfigurationScreen(async () => {
364
+ const current = configuredRunnerProfile(configPath);
365
+ const requested = text.slice("/setup".length).trim();
366
+ if (requested && !["runner", "model", "auth"].includes(requested))
367
+ throw new Error("usage: /setup [runner|model|auth]");
368
+ const section = requested || (current ? await chooseSetupSection(current) : "runner");
369
+ if (!section)
370
+ return;
371
+ if (section === "runner" || !current) {
372
+ const result = await runSetupWizard(current);
373
+ if (!result.profile)
374
+ return;
375
+ await applyWizardResult(result, existsSync(configPath) ? configPath : null);
376
+ push(await reloadDaemon(stateDir, configPath) ? "Runner profile updated — applies to the next Turn." : "Runner profile saved — restart Goah to apply it.");
377
+ return;
378
+ }
379
+ const result = await runRunnerCommandWizard(current, section === "model" ? "model" : "auth");
380
+ try {
381
+ await applyWizardResult({ profile: result.profile }, existsSync(configPath) ? configPath : null);
382
+ }
383
+ catch (error) {
384
+ await result.rollback?.();
385
+ throw error;
386
+ }
387
+ for (const line of result.output)
388
+ push(line);
389
+ push(await reloadDaemon(stateDir, configPath) ? "Configuration updated — applies to the next Turn." : "Configuration saved — restart Goah to apply it.");
390
+ });
187
391
  input.onSubmit = (line) => {
188
392
  const { action, text } = classifyTuiInput(line, busy.active);
189
393
  input.setText("");
@@ -195,7 +399,7 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
195
399
  return;
196
400
  }
197
401
  if (action === "help") {
198
- push(`${tuiTheme.strong("Commands")}\n /model /setup /status /records /history\n /goal /observe /approve /reject /stop /quit\n`);
402
+ push(`${tuiTheme.strong("Commands")}\n /model /login /logout /setup /status\n /records /history /goal /observe /stop /quit\n`);
199
403
  return;
200
404
  }
201
405
  if (action === "status") {
@@ -212,27 +416,33 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
212
416
  }
213
417
  if (action === "model") {
214
418
  if (text === "/model")
215
- void launchSetup();
419
+ void launchRunnerCommand("model");
216
420
  else
217
421
  void switchModelCommand(text, configPath, stateDir, push);
218
422
  return;
219
423
  }
424
+ if (action === "login") {
425
+ void launchRunnerCommand("auth", ["login", text.slice("/login".length).trim()].filter(Boolean));
426
+ return;
427
+ }
428
+ if (action === "logout") {
429
+ void launchRunnerCommand("auth", ["logout", text.slice("/logout".length).trim()].filter(Boolean));
430
+ return;
431
+ }
220
432
  if (action === "setup") {
221
- void launchSetup();
433
+ void launchSetup(text);
222
434
  return;
223
435
  }
224
436
  if (action === "goal") {
225
437
  void slashGoal(text, stateDir, push).finally(() => refreshGoalBar(stateDir, goalView, tui));
226
438
  return;
227
439
  }
228
- if (action === "approval") {
229
- void slashApprove(text, stateDir, push);
440
+ if (action === "unknown") {
441
+ push(errorLine(`Unknown command: ${text.split(/\s+/, 1)[0]}. Use /help to list commands.`));
230
442
  return;
231
443
  }
232
- if (action === "queue") {
233
- queued.push(text);
234
- push(tuiTheme.muted(`queued ${text}`));
235
- statusView.setText(statusText("working", queued.length));
444
+ if (action === "steer") {
445
+ submitSteer(text);
236
446
  return;
237
447
  }
238
448
  if (action === "send")
@@ -245,7 +455,7 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
245
455
  if (data !== "\x03")
246
456
  return undefined;
247
457
  exiting = true;
248
- push(busy.active ? "Detached — the current wake continues in the daemon. Use /stop before detaching when you intend to cancel it." : "Detached.");
458
+ push(busy.active ? "Detached — the current Turn continues in the daemon. Use /stop before detaching when you intend to cancel it." : "Detached.");
249
459
  activeStream?.abort();
250
460
  tui.stop();
251
461
  resolveExit();
@@ -254,8 +464,14 @@ export async function runGoahTui(configPath, stateDir, initialMessage) {
254
464
  tui.start();
255
465
  if (initialMessage)
256
466
  void send(initialMessage);
467
+ else if (typeof liveInteractionTurnId === "string")
468
+ void attachTurn(liveInteractionTurnId);
257
469
  await exited;
258
470
  }
471
+ export function findLiveTurnId(turns) {
472
+ const id = [...turns].reverse().find((turn) => turn.status === "in_progress" && turn.source === "human")?.id;
473
+ return typeof id === "string" ? id : null;
474
+ }
259
475
  async function runNonInteractive(configPath, stateDir, initialMessage) {
260
476
  await ensureDaemon(configPath, stateDir);
261
477
  console.log(`Console: ${(await waitForConsoleMetadata(stateDir)).url}`);
@@ -293,19 +509,29 @@ function frameToLine(frame) {
293
509
  renderFrame(frame, (line) => lines.push(line));
294
510
  return lines.length ? lines.join("\n") : null;
295
511
  }
296
- /** Render one control-protocol frame into a transcript line. */
297
- export function renderFrame(frame, push, appendLive = push, commitLive = push, pushResponse = push) {
512
+ /** Render one structured control-protocol frame into its own transcript channel. */
513
+ export function renderFrame(frame, push, appendLive = push, commitLive = push, pushResponse = push, updateTool = (activity) => push(toolActivityLine(activity)), updateThinking = () => { }, setWakeState = () => { }, clearLive = () => { }) {
298
514
  if (frame.type === "error") {
515
+ updateThinking({ phase: "clear", text: "" });
516
+ clearLive();
299
517
  push(`${tuiTheme.error("error")} ${safeError(frame.error)}\n`);
300
518
  return;
301
519
  }
302
- if (frame.type === "accepted")
520
+ if (frame.type === "accepted") {
521
+ const value = frame.value && typeof frame.value === "object" && !Array.isArray(frame.value) ? frame.value : {};
522
+ setWakeState(value.steered === true ? "working" : "queued");
303
523
  return;
524
+ }
304
525
  if (frame.type === "result") {
305
526
  const value = frame.value && typeof frame.value === "object" && !Array.isArray(frame.value) ? frame.value : {};
306
527
  const response = value.response && typeof value.response === "object" && !Array.isArray(value.response) ? value.response : {};
528
+ const turn = value.turn && typeof value.turn === "object" && !Array.isArray(value.turn) ? value.turn : {};
529
+ const error = turn.error && typeof turn.error === "object" && !Array.isArray(turn.error) ? turn.error : {};
530
+ updateThinking({ phase: "done", text: "" });
307
531
  if (typeof response.content === "string" && response.content.trim())
308
532
  pushResponse(response.content.trim());
533
+ else if (turn.status === "failed" && typeof error.message === "string")
534
+ push(`${tuiTheme.error("error")} ${safeError(error.message)}\n`);
309
535
  return;
310
536
  }
311
537
  if (frame.type !== "event")
@@ -315,40 +541,84 @@ export function renderFrame(frame, push, appendLive = push, commitLive = push, p
315
541
  return;
316
542
  const record = event;
317
543
  const data = record.data && typeof record.data === "object" && !Array.isArray(record.data) ? record.data : {};
318
- if (record.type === "tool.called") {
544
+ if (record.type === "wake.enqueued") {
545
+ setWakeState("queued");
319
546
  return;
320
547
  }
548
+ if (record.type === "turn.started" || record.type === "transcript.started") {
549
+ setWakeState("working");
550
+ return;
551
+ }
552
+ if (record.type === "tool.called") {
553
+ updateThinking({ phase: "done", text: "" });
554
+ updateTool({ kind: "tool", callId: String(data.callId ?? "tool"), name: String(data.name ?? "tool"), detail: toolDetail(data.arguments), status: "running" });
555
+ }
321
556
  else if (record.type === "tool.completed") {
322
- push(` ${tuiTheme.muted(String(data.name ?? "tool"))} ${data.isError ? tuiTheme.error("failed") : tuiTheme.success("done")}`);
557
+ updateTool({ kind: "tool", callId: String(data.callId ?? "tool"), name: String(data.name ?? "tool"), detail: "", status: data.isError ? "failed" : "done" });
323
558
  }
324
559
  else if (record.type === "message.assistant.delta") {
325
560
  const delta = data.delta && typeof data.delta === "object" && !Array.isArray(data.delta) ? data.delta : {};
326
- if (typeof delta.delta === "string")
327
- appendLive(delta.delta);
561
+ if (delta.type === "thinking_start")
562
+ updateThinking({ phase: "start", text: "" });
563
+ else if (delta.type === "thinking_delta" && typeof delta.delta === "string")
564
+ updateThinking({ phase: "delta", text: delta.delta });
565
+ else if (delta.type === "thinking_end")
566
+ updateThinking({ phase: "done", text: typeof delta.content === "string" ? delta.content : "" });
567
+ else if (delta.type === "text_start" || delta.type === "text_delta") {
568
+ updateThinking({ phase: "done", text: "" });
569
+ if (delta.type === "text_delta" && typeof delta.delta === "string")
570
+ appendLive(delta.delta);
571
+ }
572
+ else if (delta.type === "toolcall_start" || delta.type === "toolcall_delta")
573
+ updateThinking({ phase: "done", text: "" });
328
574
  }
329
575
  else if (record.type === "message.assistant.completed") {
576
+ updateThinking({ phase: "done", text: "" });
330
577
  const message = data.message && typeof data.message === "object" ? data.message : {};
578
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
579
+ clearLive();
580
+ return;
581
+ }
331
582
  const text = messageText(message.content);
583
+ if (data.completionIntent === "handoff") {
584
+ clearLive();
585
+ return;
586
+ }
332
587
  if (text)
333
588
  commitLive(text);
334
589
  }
590
+ else if (record.type === "response.committed") {
591
+ if (typeof data.text === "string" && data.text.trim())
592
+ commitLive(data.text.trim());
593
+ }
335
594
  else if (record.type === "handoff.recorded") {
336
595
  if (typeof data.goalId === "string")
337
596
  push(`${data.outcome === "blocked" ? tuiTheme.error("goal blocked") : tuiTheme.success("goal saved")} ${tuiTheme.muted(`${String(data.outcome).replaceAll("_", " ")} · record r${String(data.recordRevision)}`)}`);
338
- else {
339
- const results = Array.isArray(data.results) ? data.results : [];
340
- for (const result of results)
341
- if (typeof result === "string")
342
- push(`${tuiTheme.success("done")} ${result}`);
343
- }
344
597
  }
345
598
  else if (record.type === "ceo.human_requested") {
346
599
  push(`${tuiTheme.warning("needs you")} ${safeError(compact(record.data ?? {}))}`);
347
600
  }
348
- else if (record.type === "wake.abnormal_reason") {
349
- push(`${tuiTheme.error("error")} ${safeError(typeof data.reason === "string" ? data.reason : "Wake failed")}\n`);
601
+ else if (record.type === "transcript.interrupted") {
602
+ updateThinking({ phase: "clear", text: "" });
603
+ clearLive();
604
+ push(`${tuiTheme.error("error")} ${safeError(typeof data.reason === "string" ? data.reason : "Turn failed")}\n`);
350
605
  }
351
606
  }
607
+ function toolDetail(value) {
608
+ if (!value || typeof value !== "object" || Array.isArray(value))
609
+ return "";
610
+ const args = value;
611
+ const candidate = [args.path, args.file_path, args.query, args.command, args.goalId].find((item) => typeof item === "string");
612
+ if (!candidate)
613
+ return "";
614
+ const oneLine = candidate.replace(/\s+/g, " ").trim();
615
+ return oneLine.length > 72 ? `${oneLine.slice(0, 69)}…` : oneLine;
616
+ }
617
+ function toolActivityLine(activity) {
618
+ const state = activity.status === "running" ? tuiTheme.accent("running") : activity.status === "failed" ? tuiTheme.error("failed") : tuiTheme.success("done");
619
+ const detail = activity.detail ? ` ${tuiTheme.muted(activity.detail)}` : "";
620
+ return ` ${state} ${tuiTheme.strong(activity.name)}${detail}`;
621
+ }
352
622
  async function printStatus(stateDir, push) {
353
623
  try {
354
624
  const status = await requestControl(stateDir, { op: "ceo.status" });
@@ -356,7 +626,7 @@ async function printStatus(stateDir, push) {
356
626
  const roots = Array.isArray(value.roots) ? value.roots : [];
357
627
  const team = Array.isArray(value.team) ? value.team : [];
358
628
  const pending = Array.isArray(value.pendingHuman) ? value.pendingHuman.length : 0;
359
- push([tuiTheme.strong("Status"), roots[0] ? ` ${tuiTheme.active(" GOAL ")} ${String(roots[0].objective)} ${tuiTheme.muted(String(roots[0].phase))}` : ` ${tuiTheme.muted("No active Goal")}`, ` ${tuiTheme.muted(team.length ? team.map((member) => `${String(member.agent)} ${String(member.status)}`).join(" · ") : "No Goal Agents")}`, ...(pending ? [` ${tuiTheme.warning(`${pending} decision${pending === 1 ? "" : "s"} need you`)}`] : []), ""].join("\n"));
629
+ push([tuiTheme.strong("Status"), roots[0] ? ` ${tuiTheme.active(" GOAL ")} ${String(roots[0].objective)} ${tuiTheme.muted(String(roots[0].phase))}` : ` ${tuiTheme.muted("No active Goal")}`, ` ${tuiTheme.muted(team.length ? team.map((member) => `${String(member.agent)} ${String(member.motion)}${member.lastOutcome ? `/${String(member.lastOutcome)}` : ""}`).join(" · ") : "No Goal Agents")}`, ...(pending ? [` ${tuiTheme.warning(`${pending} decision${pending === 1 ? "" : "s"} need you`)}`] : []), ""].join("\n"));
360
630
  }
361
631
  catch (error) {
362
632
  push(errorLine(error));
@@ -396,8 +666,15 @@ async function printRecords(text, stateDir, push) {
396
666
  }
397
667
  async function stopCeoWake(stateDir, push) {
398
668
  try {
399
- const wake = await requestControl(stateDir, { op: "wake.stop", agent: "ceo" });
400
- push(wake ? `${tuiTheme.warning("stopping")} Current run will close as interrupted.` : tuiTheme.muted("No active run."));
669
+ const snapshot = await requestControl(stateDir, { op: "status" });
670
+ const turns = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot) && Array.isArray(snapshot.turns) ? snapshot.turns : [];
671
+ const turnId = findLiveTurnId(turns);
672
+ if (!turnId) {
673
+ push(tuiTheme.muted("No active Turn."));
674
+ return;
675
+ }
676
+ await requestControl(stateDir, { op: "turn.interrupt", turnId });
677
+ push(`${tuiTheme.warning("stopping")} Current Turn was interrupted.`);
401
678
  }
402
679
  catch (error) {
403
680
  push(errorLine(error));
@@ -429,25 +706,6 @@ async function slashGoal(text, stateDir, push) {
429
706
  push(errorLine(error));
430
707
  }
431
708
  }
432
- /** /approve ID --reason TEXT --evidence SEQ[,SEQ] (and /reject with the same shape). */
433
- async function slashApprove(text, stateDir, push) {
434
- const approve = text.startsWith("/approve ");
435
- const rest = text.slice(approve ? 9 : 8).match(/^(\S+)(?:\s+--reason\s+(.*?))?(?:\s+--evidence\s+([\d,]+))?$/);
436
- if (!rest) {
437
- push(errorLine("usage: /approve ACTION_ID --reason TEXT --evidence SEQ[,SEQ]"));
438
- return;
439
- }
440
- const [, id = "", reason = "terminal approval", evidence = ""] = rest;
441
- try {
442
- const op = approve ? { op: "action.approve", id, reason, evidence: evidence ? evidence.split(",").map(Number) : [] }
443
- : { op: "action.reject", id, reason, evidence: evidence ? evidence.split(",").map(Number) : [] };
444
- await requestControl(stateDir, op);
445
- push(`${tuiTheme.success(approve ? "approved" : "rejected")} ${id}`);
446
- }
447
- catch (error) {
448
- push(errorLine(error));
449
- }
450
- }
451
709
  async function ensureDaemon(configPath, stateDir) {
452
710
  if (await controlAvailable(stateDir)) {
453
711
  const version = await requestControl(stateDir, { op: "daemon.version" }).catch(() => null);
@@ -493,7 +751,7 @@ function safeError(value) {
493
751
  const named = sanitized.match(/^(?:TypeError|RangeError|ReferenceError|SyntaxError|Error):\s*[^\n]+/m)?.[0];
494
752
  if (named)
495
753
  return named;
496
- return sanitized.split("\n").map((line) => line.trim()).find((line) => line && !line.startsWith("file://") && !line.startsWith("at ") && line !== "^") ?? "Wake failed";
754
+ return sanitized.split("\n").map((line) => line.trim()).find((line) => line && !line.startsWith("file://") && !line.startsWith("at ") && line !== "^") ?? "Turn failed";
497
755
  }
498
756
  function messageText(value) {
499
757
  if (typeof value === "string")
@@ -519,14 +777,8 @@ async function switchModelCommand(text, configPath, stateDir, push) {
519
777
  push(errorLine(error));
520
778
  }
521
779
  }
522
- /** /setup — re-enter the wizard, then reload the daemon so changes apply to the next wake. */
523
- async function runSetupReload(configPath, stateDir, push) {
524
- const result = await runSetupWizard();
525
- if (!result.profile) {
526
- push("setup cancelled; nothing changed");
527
- return;
528
- }
529
- applyWizardResult(result, configPath);
530
- push(await reloadDaemon(stateDir, configPath) ? "config reloaded — applies to the next wake" : `saved to ${resolve(configPath)}; daemon will pick it up on next start`);
780
+ function configuredRunnerProfile(configPath) {
781
+ const workspace = existsSync(configPath) ? loadConfig(configPath) : null;
782
+ return workspace?.runnerProfiles?.find((profile) => profile.id === "default") ?? workspace?.runnerProfiles?.[0] ?? readDefaultRunnerProfile();
531
783
  }
532
784
  //# sourceMappingURL=tui.js.map