@multiclawcli/dashboard 0.12.0 → 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.
- package/README.md +5 -4
- package/dist/tui/commands.d.ts +14 -6
- package/dist/tui/components/interaction.d.ts +7 -10
- package/dist/tui/components/panels.d.ts +3 -3
- package/dist/tui/interaction.d.ts +2 -13
- package/dist/tui/layout.d.ts +0 -2
- package/dist/tui/state.d.ts +12 -20
- package/dist/tui.js +592 -485
- package/package.json +2 -2
package/dist/tui.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
describeResumePlan,
|
|
11
11
|
isAbortError,
|
|
12
12
|
Orchestrator,
|
|
13
|
+
resolvePermissionMode,
|
|
13
14
|
validateRuntimeQuestionContent
|
|
14
15
|
} from "@multiclawcli/core";
|
|
15
16
|
import { Box as Box6, Text as Text6, useApp, useInput, useWindowSize } from "ink";
|
|
@@ -57,149 +58,95 @@ function appendLines(target, lines) {
|
|
|
57
58
|
|
|
58
59
|
// src/tui/commands.ts
|
|
59
60
|
var COMMANDS = [
|
|
60
|
-
{ name: "
|
|
61
|
+
{ name: "new", description: "Start an empty session", availability: "idle" },
|
|
62
|
+
{ name: "sessions", description: "Switch or delete saved sessions", availability: "idle" },
|
|
61
63
|
{
|
|
62
64
|
name: "focus",
|
|
63
|
-
args: "
|
|
64
|
-
description: "
|
|
65
|
+
args: "[id]",
|
|
66
|
+
description: "Choose the focused agent",
|
|
65
67
|
availability: "always",
|
|
66
68
|
completes: "agent"
|
|
67
69
|
},
|
|
68
|
-
{ name: "clear", description: "Clear the focused agent's log", availability: "always" },
|
|
69
70
|
{
|
|
70
|
-
name: "
|
|
71
|
-
|
|
71
|
+
name: "mode",
|
|
72
|
+
args: "[auto | ask | deny]",
|
|
73
|
+
description: "Choose the permission mode",
|
|
72
74
|
availability: "always"
|
|
73
75
|
},
|
|
76
|
+
{ name: "verbose", description: "Toggle tool detail in the stream", availability: "always" },
|
|
74
77
|
{
|
|
75
|
-
name: "
|
|
76
|
-
|
|
78
|
+
name: "search",
|
|
79
|
+
args: "[text]",
|
|
80
|
+
description: "Search the focused stream",
|
|
77
81
|
availability: "always"
|
|
78
82
|
},
|
|
83
|
+
{ name: "latest", description: "Jump to latest output", availability: "always" },
|
|
79
84
|
{
|
|
80
85
|
name: "retry",
|
|
81
86
|
args: "[id]",
|
|
82
|
-
description: "
|
|
87
|
+
description: "Choose failed agents to retry",
|
|
83
88
|
availability: "idle"
|
|
84
89
|
},
|
|
85
90
|
{
|
|
86
91
|
name: "replay",
|
|
87
92
|
args: "[turn]",
|
|
88
|
-
description: "
|
|
93
|
+
description: "Choose a turn to run again",
|
|
89
94
|
availability: "idle"
|
|
90
95
|
},
|
|
91
96
|
{
|
|
92
|
-
name: "
|
|
97
|
+
name: "edit",
|
|
93
98
|
args: "[turn]",
|
|
94
|
-
description: "Edit
|
|
99
|
+
description: "Edit a previous requirement",
|
|
95
100
|
availability: "always"
|
|
96
101
|
},
|
|
97
102
|
{
|
|
98
103
|
name: "export",
|
|
99
104
|
args: "[turn] [path]",
|
|
100
|
-
description: "Export a
|
|
101
|
-
availability: "always"
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: "search",
|
|
105
|
-
args: "<text>",
|
|
106
|
-
description: "Search the focused stream",
|
|
105
|
+
description: "Export a turn as Markdown",
|
|
107
106
|
availability: "always"
|
|
108
107
|
},
|
|
109
|
-
{
|
|
110
|
-
name: "latest",
|
|
111
|
-
description: "Jump the stream to latest output",
|
|
112
|
-
availability: "always"
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
name: "cancel",
|
|
116
|
-
description: "Cancel the running turn and its queue",
|
|
117
|
-
availability: "running"
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
name: "new",
|
|
121
|
-
args: "<requirement>",
|
|
122
|
-
description: "Start a new session with fresh agent sessions",
|
|
123
|
-
availability: "idle"
|
|
124
|
-
},
|
|
125
108
|
{
|
|
126
109
|
name: "steer",
|
|
127
|
-
args: "
|
|
128
|
-
description: "Send a message
|
|
110
|
+
args: "[text]",
|
|
111
|
+
description: "Send a message to running agents",
|
|
129
112
|
availability: "running"
|
|
130
113
|
},
|
|
131
|
-
{
|
|
132
|
-
name: "answer",
|
|
133
|
-
args: "<text>",
|
|
134
|
-
description: "Answer the leader's pending question",
|
|
135
|
-
availability: "idle"
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "pause",
|
|
139
|
-
description: "Pause queued turns, not the active agent",
|
|
140
|
-
availability: "always"
|
|
141
|
-
},
|
|
114
|
+
{ name: "pause", description: "Pause queued turns", availability: "always" },
|
|
142
115
|
{
|
|
143
116
|
name: "continue",
|
|
144
|
-
description: "Resume
|
|
145
|
-
availability: "always"
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
name: "modify",
|
|
149
|
-
args: "<instruction>",
|
|
150
|
-
description: "Provide feedback at a checkpoint and continue",
|
|
151
|
-
availability: "running"
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: "respond",
|
|
155
|
-
args: "<question id> <JSON object>",
|
|
156
|
-
description: "Answer a question in one line of JSON",
|
|
157
|
-
availability: "always"
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
name: "skip",
|
|
161
|
-
args: "<question id>",
|
|
162
|
-
description: "Decline a structured tool question",
|
|
163
|
-
availability: "always"
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: "dismiss",
|
|
167
|
-
args: "<question id>",
|
|
168
|
-
description: "Cancel a structured tool question",
|
|
169
|
-
availability: "always"
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
name: "approve",
|
|
173
|
-
args: "[id] <number>",
|
|
174
|
-
description: "Approve the request on screen, or a named one",
|
|
175
|
-
availability: "always"
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: "deny",
|
|
179
|
-
args: "[id]",
|
|
180
|
-
description: "Deny the request on screen, or a named one",
|
|
117
|
+
description: "Resume the queue or continue a checkpoint",
|
|
181
118
|
availability: "always"
|
|
182
|
-
}
|
|
183
|
-
{ name: "help", description: "Show the key map", availability: "always" },
|
|
184
|
-
{ name: "quit", description: "Exit the console", availability: "always" }
|
|
119
|
+
}
|
|
185
120
|
];
|
|
186
|
-
function
|
|
121
|
+
function relevant(name, context) {
|
|
122
|
+
if (!context) return true;
|
|
123
|
+
if (context.checkpoint) return name === "continue";
|
|
124
|
+
if (["replay", "edit", "export"].includes(name)) return context.history;
|
|
125
|
+
if (name === "retry") return context.retry;
|
|
126
|
+
if (name === "pause") return context.queue > 0 && !context.paused;
|
|
127
|
+
if (name === "continue") return context.queue > 0 && context.paused && !context.awaitingAnswer;
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
function availableCommands(running, context) {
|
|
187
131
|
const phase = running ? "running" : "idle";
|
|
188
|
-
return COMMANDS.filter(
|
|
132
|
+
return COMMANDS.filter(
|
|
133
|
+
(c) => (c.availability === "always" || c.availability === phase) && relevant(c.name, context)
|
|
134
|
+
);
|
|
189
135
|
}
|
|
190
|
-
function canRun(name, running) {
|
|
136
|
+
function canRun(name, running, context) {
|
|
191
137
|
const command = COMMANDS.find((c) => c.name === name);
|
|
192
138
|
if (!command) return true;
|
|
193
|
-
return availableCommands(running).includes(command);
|
|
139
|
+
return availableCommands(running, context).includes(command);
|
|
194
140
|
}
|
|
195
|
-
function unavailableReason(name, running) {
|
|
141
|
+
function unavailableReason(name, running, context) {
|
|
196
142
|
const command = COMMANDS.find((c) => c.name === name);
|
|
197
|
-
if (!command || availableCommands(running).includes(command)) return void 0;
|
|
143
|
+
if (!command || availableCommands(running, context).includes(command)) return void 0;
|
|
144
|
+
if (!relevant(name, context)) return `/${name} has nothing to act on right now`;
|
|
198
145
|
return running ? `/${name} waits for the current turn to finish` : `/${name} only applies while a turn is running`;
|
|
199
146
|
}
|
|
200
|
-
function matchCommands(query, running) {
|
|
147
|
+
function matchCommands(query, running, context) {
|
|
201
148
|
const q = query.trim().toLowerCase();
|
|
202
|
-
const pool = availableCommands(running);
|
|
149
|
+
const pool = availableCommands(running, context);
|
|
203
150
|
if (!q) return pool;
|
|
204
151
|
const byName = [];
|
|
205
152
|
const byDescription = [];
|
|
@@ -459,16 +406,24 @@ function computeLayout(input) {
|
|
|
459
406
|
const compact = columns < COMPACT_COLUMNS;
|
|
460
407
|
const welcome = !input.overlay && input.turn === 0;
|
|
461
408
|
const showRail = !welcome && !input.overlay && rows >= RAIL_MIN_ROWS && input.hasRail;
|
|
462
|
-
const menuRows = Math.min(Math.max(0, input.menuRows), MAX_MENU_ROWS);
|
|
463
|
-
const queueRows = Math.min(Math.max(0, input.queueRows ?? 0), MAX_QUEUE_ROWS);
|
|
464
409
|
const promptRows = Math.max(1, input.promptRows ?? 1);
|
|
465
410
|
const railRows = showRail ? Math.min(MAX_RAIL_ROWS, Math.max(1, measureRailRows(input.railCells, columns))) : 0;
|
|
411
|
+
const fixedRows = 4 + MIN_BODY_ROWS + promptRows + (input.notice ? 1 : 0) + (railRows > 0 ? railRows + 1 : 0);
|
|
412
|
+
const queueRows = Math.min(
|
|
413
|
+
Math.max(0, input.queueRows ?? 0),
|
|
414
|
+
MAX_QUEUE_ROWS,
|
|
415
|
+
Math.max(0, rows - fixedRows - (input.menuRows > 0 ? 1 : 0))
|
|
416
|
+
);
|
|
417
|
+
const menuRows = Math.min(
|
|
418
|
+
Math.max(0, input.menuRows),
|
|
419
|
+
MAX_MENU_ROWS,
|
|
420
|
+
Math.max(1, rows - fixedRows - queueRows)
|
|
421
|
+
);
|
|
466
422
|
const chrome = 1 + // header
|
|
467
423
|
1 + // rule under the header
|
|
468
424
|
(railRows > 0 ? railRows + 1 : 0) + // pipeline rail plus the gap above it
|
|
469
425
|
1 + // gap before the body
|
|
470
|
-
(input.notice ? 1 : 0) +
|
|
471
|
-
queueRows + menuRows + promptRows + 1;
|
|
426
|
+
(input.notice ? 1 : 0) + queueRows + menuRows + promptRows + 1;
|
|
472
427
|
const barRoom = Math.max(0, rows - chrome - MIN_BODY_ROWS - 1);
|
|
473
428
|
const interactionRows2 = barRoom < MIN_INTERACTION_ROWS ? 0 : Math.min(Math.max(0, input.interactionRows ?? 0), MAX_INTERACTION_ROWS, barRoom);
|
|
474
429
|
const reserved = chrome + (interactionRows2 > 0 ? interactionRows2 + 1 : 0);
|
|
@@ -877,8 +832,9 @@ function reduceEvent(state, event) {
|
|
|
877
832
|
if (event.type === "agent:failed") {
|
|
878
833
|
if (next.logs.length === 0 && result.output)
|
|
879
834
|
next.logs = appendLog([], result.output, "text");
|
|
880
|
-
|
|
881
|
-
|
|
835
|
+
const error = result.error;
|
|
836
|
+
if (error && !next.logs.some((s) => s.text.includes(stripAnsi(error)))) {
|
|
837
|
+
next.logs = appendLog(next.logs, error, "system");
|
|
882
838
|
}
|
|
883
839
|
}
|
|
884
840
|
return { ...state, agents: { ...state.agents, [result.agentId]: next } };
|
|
@@ -920,14 +876,120 @@ function reduceEvent(state, event) {
|
|
|
920
876
|
return state;
|
|
921
877
|
}
|
|
922
878
|
}
|
|
879
|
+
var wrappedCache = /* @__PURE__ */ new WeakMap();
|
|
880
|
+
var MAX_CACHED_WIDTHS = 3;
|
|
923
881
|
function wrapLog(logs, width) {
|
|
924
882
|
if (logs.length === 0) return [];
|
|
883
|
+
let byWidth = wrappedCache.get(logs);
|
|
884
|
+
if (byWidth === void 0) {
|
|
885
|
+
byWidth = /* @__PURE__ */ new Map();
|
|
886
|
+
wrappedCache.set(logs, byWidth);
|
|
887
|
+
}
|
|
888
|
+
const cached = byWidth.get(width);
|
|
889
|
+
if (cached !== void 0) return cached;
|
|
925
890
|
const rendered = renderSegments(logs, width);
|
|
926
|
-
|
|
891
|
+
const rows = rendered.trimEnd().split("\n").flatMap((line) => wrapLine(line, width));
|
|
892
|
+
if (byWidth.size >= MAX_CACHED_WIDTHS) byWidth.clear();
|
|
893
|
+
byWidth.set(width, rows);
|
|
894
|
+
return rows;
|
|
895
|
+
}
|
|
896
|
+
function totalRows(parts) {
|
|
897
|
+
let total = 0;
|
|
898
|
+
for (const part of parts) total += part.length;
|
|
899
|
+
return total;
|
|
900
|
+
}
|
|
901
|
+
function windowParts(parts, viewport, scroll) {
|
|
902
|
+
const height = Math.max(1, viewport);
|
|
903
|
+
const total = totalRows(parts);
|
|
904
|
+
const maxScroll = Math.max(0, total - height);
|
|
905
|
+
const scrolledBack = Math.min(Math.max(0, scroll), maxScroll);
|
|
906
|
+
const end = total - scrolledBack;
|
|
907
|
+
const start = Math.max(0, end - height);
|
|
908
|
+
const rows = [];
|
|
909
|
+
let offset = 0;
|
|
910
|
+
for (const part of parts) {
|
|
911
|
+
const partEnd = offset + part.length;
|
|
912
|
+
if (partEnd <= start) {
|
|
913
|
+
offset = partEnd;
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
if (offset >= end) break;
|
|
917
|
+
const from = Math.max(0, start - offset);
|
|
918
|
+
const to = Math.min(part.length, end - offset);
|
|
919
|
+
for (let i = from; i < to; i++) rows.push(part[i]);
|
|
920
|
+
offset = partEnd;
|
|
921
|
+
}
|
|
922
|
+
return { rows, scrolledBack, following: scrolledBack === 0 };
|
|
923
|
+
}
|
|
924
|
+
function clipToColumns(text, max) {
|
|
925
|
+
if (max <= 0) return "";
|
|
926
|
+
if (displayWidth(text) <= max) return text;
|
|
927
|
+
let head = "";
|
|
928
|
+
let used = 0;
|
|
929
|
+
for (const char of text) {
|
|
930
|
+
const width = displayWidth(char);
|
|
931
|
+
if (used + width > max - 1) break;
|
|
932
|
+
head += char;
|
|
933
|
+
used += width;
|
|
934
|
+
}
|
|
935
|
+
return `${head}\u2026`;
|
|
936
|
+
}
|
|
937
|
+
function turnDivider(turn, width) {
|
|
938
|
+
const MAX_REQ = 45;
|
|
939
|
+
const raw = turn.requirement?.replace(/\s+/g, " ").trim();
|
|
940
|
+
const req = raw ? clipToColumns(raw, MAX_REQ) : void 0;
|
|
941
|
+
const label = req ? ` turn ${turn.turn} \xB7 ${req} ` : ` turn ${turn.turn} `;
|
|
942
|
+
const fill = Math.max(1, Math.floor((width - displayWidth(label)) / 2));
|
|
943
|
+
const rule = `${"\u2500".repeat(fill)}${label}${"\u2500".repeat(fill)}`;
|
|
944
|
+
return `\x1B[2m${clipToColumns(rule, width)}\x1B[0m`;
|
|
945
|
+
}
|
|
946
|
+
function pushRows(target, rows) {
|
|
947
|
+
for (const row of rows) target.push(row);
|
|
948
|
+
}
|
|
949
|
+
var historyCache = /* @__PURE__ */ new WeakMap();
|
|
950
|
+
function historyRows(history, agentId, width) {
|
|
951
|
+
if (history.length === 0) return [];
|
|
952
|
+
let byAgent = historyCache.get(history);
|
|
953
|
+
if (byAgent === void 0) {
|
|
954
|
+
byAgent = /* @__PURE__ */ new Map();
|
|
955
|
+
historyCache.set(history, byAgent);
|
|
956
|
+
}
|
|
957
|
+
let byWidth = byAgent.get(agentId);
|
|
958
|
+
if (byWidth === void 0) {
|
|
959
|
+
byWidth = /* @__PURE__ */ new Map();
|
|
960
|
+
byAgent.set(agentId, byWidth);
|
|
961
|
+
}
|
|
962
|
+
const cached = byWidth.get(width);
|
|
963
|
+
if (cached !== void 0) return cached;
|
|
964
|
+
const rows = [];
|
|
965
|
+
for (const turn of history) {
|
|
966
|
+
rows.push(turnDivider(turn, width));
|
|
967
|
+
const agent = turn.agents[agentId];
|
|
968
|
+
if (agent && agent.logs.length > 0) pushRows(rows, wrapLog(agent.logs, width));
|
|
969
|
+
}
|
|
970
|
+
if (byWidth.size >= MAX_CACHED_WIDTHS) byWidth.clear();
|
|
971
|
+
byWidth.set(width, rows);
|
|
972
|
+
return rows;
|
|
927
973
|
}
|
|
928
|
-
function
|
|
929
|
-
const agent =
|
|
930
|
-
|
|
974
|
+
function liveRows(state, agentId, width) {
|
|
975
|
+
const agent = state.agents[agentId];
|
|
976
|
+
if (!agent || agent.logs.length === 0) return [];
|
|
977
|
+
const rows = wrapLog(agent.logs, width);
|
|
978
|
+
if (state.history.length === 0) return rows;
|
|
979
|
+
const out = [turnDivider(state, width)];
|
|
980
|
+
pushRows(out, rows);
|
|
981
|
+
return out;
|
|
982
|
+
}
|
|
983
|
+
function timelineParts(state, agentId, width) {
|
|
984
|
+
const history = historyRows(state.history, agentId, width);
|
|
985
|
+
const live = liveRows(state, agentId, width);
|
|
986
|
+
if (live.length === 0) return history.length === 0 ? [] : [history];
|
|
987
|
+
return history.length === 0 ? [live] : [history, live];
|
|
988
|
+
}
|
|
989
|
+
function streamMatches(state, query, width) {
|
|
990
|
+
if (!state.focusId || !query.trim()) return [];
|
|
991
|
+
const needle = query.toLowerCase();
|
|
992
|
+
return timelineParts(state, state.focusId, width).flat().flatMap((row, index) => row.toLowerCase().includes(needle) ? [index] : []);
|
|
931
993
|
}
|
|
932
994
|
function snapshot(state) {
|
|
933
995
|
return {
|
|
@@ -954,14 +1016,6 @@ function snapshot(state) {
|
|
|
954
1016
|
reply: state.reply
|
|
955
1017
|
};
|
|
956
1018
|
}
|
|
957
|
-
function mapShown(state, fn) {
|
|
958
|
-
if (state.view) {
|
|
959
|
-
const next2 = fn(state.view);
|
|
960
|
-
return next2 === state.view ? state : { ...state, view: next2 };
|
|
961
|
-
}
|
|
962
|
-
const next = fn(state);
|
|
963
|
-
return next === state ? state : { ...state, ...next };
|
|
964
|
-
}
|
|
965
1019
|
function recordedTurns(state) {
|
|
966
1020
|
return [...state.history, ...state.phase === "complete" ? [snapshot(state)] : []];
|
|
967
1021
|
}
|
|
@@ -1035,68 +1089,55 @@ function tuiReducer(state, action) {
|
|
|
1035
1089
|
history,
|
|
1036
1090
|
// A new requirement also replaces the previous leader metadata; leaving
|
|
1037
1091
|
// it up would make the old answer or routing reason look like the
|
|
1038
|
-
// response to the new request.
|
|
1039
|
-
// reason — the panes belong to the run.
|
|
1092
|
+
// response to the new request.
|
|
1040
1093
|
reason: void 0,
|
|
1041
1094
|
reply: void 0,
|
|
1042
|
-
view: void 0,
|
|
1043
1095
|
// A console setting, carried across turns rather than reset with them.
|
|
1044
1096
|
verbose: state.verbose
|
|
1045
1097
|
};
|
|
1046
1098
|
}
|
|
1047
|
-
case "focus:cycle":
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1082
|
-
if (index === -1) return turn;
|
|
1083
|
-
const height = Math.max(1, action.viewport);
|
|
1084
|
-
const maxScroll = Math.max(0, rows.length - height);
|
|
1085
|
-
const scroll = Math.min(Math.max(0, rows.length - 1 - index), maxScroll);
|
|
1086
|
-
return scroll === turn.scroll && !turn.autoFollow ? turn : { ...turn, scroll, autoFollow: false };
|
|
1087
|
-
});
|
|
1099
|
+
case "focus:cycle": {
|
|
1100
|
+
const ids = Object.keys(state.agents);
|
|
1101
|
+
if (ids.length === 0) return state;
|
|
1102
|
+
const current = state.focusId ? ids.indexOf(state.focusId) : -1;
|
|
1103
|
+
const nextIndex = (current + action.delta + ids.length) % ids.length;
|
|
1104
|
+
if (nextIndex === current) return state;
|
|
1105
|
+
return { ...state, focusId: ids[nextIndex], autoFollow: false, scroll: 0 };
|
|
1106
|
+
}
|
|
1107
|
+
case "focus:set": {
|
|
1108
|
+
if (!state.agents[action.agentId]) return state;
|
|
1109
|
+
if (state.focusId === action.agentId && !state.autoFollow && state.scroll === 0) return state;
|
|
1110
|
+
return { ...state, focusId: action.agentId, autoFollow: false, scroll: 0 };
|
|
1111
|
+
}
|
|
1112
|
+
case "stream:scroll": {
|
|
1113
|
+
const focusId = state.focusId;
|
|
1114
|
+
if (!focusId) return state;
|
|
1115
|
+
const rows = totalRows(timelineParts(state, focusId, action.width));
|
|
1116
|
+
const maxScroll = Math.max(0, rows - Math.max(1, action.viewport));
|
|
1117
|
+
const next = Math.min(Math.max(0, state.scroll + action.delta), maxScroll);
|
|
1118
|
+
return next === state.scroll ? state : { ...state, scroll: next };
|
|
1119
|
+
}
|
|
1120
|
+
case "stream:search": {
|
|
1121
|
+
const focusId = state.focusId;
|
|
1122
|
+
if (!focusId || action.query === "") return state;
|
|
1123
|
+
const parts = timelineParts(state, focusId, action.width);
|
|
1124
|
+
const matches = streamMatches(state, action.query, action.width);
|
|
1125
|
+
const index = matches[action.match ?? matches.length - 1];
|
|
1126
|
+
if (index === void 0) return state;
|
|
1127
|
+
const total = totalRows(parts);
|
|
1128
|
+
const height = Math.max(1, action.viewport);
|
|
1129
|
+
const maxScroll = Math.max(0, total - height);
|
|
1130
|
+
const scroll = Math.min(Math.max(0, total - 1 - index), maxScroll);
|
|
1131
|
+
return scroll === state.scroll && !state.autoFollow ? state : { ...state, scroll, autoFollow: false };
|
|
1132
|
+
}
|
|
1088
1133
|
case "stream:latest":
|
|
1089
|
-
return
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
return
|
|
1095
|
-
|
|
1096
|
-
const agent = id ? turn.agents[id] : void 0;
|
|
1097
|
-
if (!id || !agent) return turn;
|
|
1098
|
-
return { ...turn, agents: { ...turn.agents, [id]: { ...agent, logs: [] } }, scroll: 0 };
|
|
1099
|
-
});
|
|
1134
|
+
return state.scroll === 0 && state.autoFollow ? state : { ...state, scroll: 0, autoFollow: true };
|
|
1135
|
+
case "stream:clear": {
|
|
1136
|
+
const id = state.focusId;
|
|
1137
|
+
const agent = id ? state.agents[id] : void 0;
|
|
1138
|
+
if (!id || !agent) return state;
|
|
1139
|
+
return { ...state, agents: { ...state.agents, [id]: { ...agent, logs: [] } }, scroll: 0 };
|
|
1140
|
+
}
|
|
1100
1141
|
case "stream:note": {
|
|
1101
1142
|
const agent = state.agents[action.agentId];
|
|
1102
1143
|
if (!agent) return state;
|
|
@@ -1108,12 +1149,6 @@ function tuiReducer(state, action) {
|
|
|
1108
1149
|
}
|
|
1109
1150
|
};
|
|
1110
1151
|
}
|
|
1111
|
-
case "view:open": {
|
|
1112
|
-
const record = state.history.find((turn) => turn.turn === action.turn);
|
|
1113
|
-
return record ? { ...state, view: record } : state;
|
|
1114
|
-
}
|
|
1115
|
-
case "view:close":
|
|
1116
|
-
return state.view ? { ...state, view: void 0 } : state;
|
|
1117
1152
|
case "verbose:toggle":
|
|
1118
1153
|
return { ...state, verbose: !state.verbose };
|
|
1119
1154
|
case "session:reset":
|
|
@@ -1148,7 +1183,6 @@ function seededState(state, agents, leaderIds, history) {
|
|
|
1148
1183
|
autoFollow: true,
|
|
1149
1184
|
scroll: 0,
|
|
1150
1185
|
history,
|
|
1151
|
-
view: void 0,
|
|
1152
1186
|
verbose: state.verbose
|
|
1153
1187
|
};
|
|
1154
1188
|
}
|
|
@@ -1184,18 +1218,8 @@ function turnElapsed(turn, now) {
|
|
|
1184
1218
|
const end = turn.phase === "running" ? now : turn.endedAt ?? now;
|
|
1185
1219
|
return Math.max(0, end - turn.startedAt);
|
|
1186
1220
|
}
|
|
1187
|
-
function
|
|
1188
|
-
|
|
1189
|
-
const all = agent?.logs ? wrapLog(agent.logs, width) : [];
|
|
1190
|
-
const height = Math.max(1, viewport);
|
|
1191
|
-
const maxScroll = Math.max(0, all.length - height);
|
|
1192
|
-
const scrolledBack = Math.min(Math.max(0, turn.scroll), maxScroll);
|
|
1193
|
-
const end = all.length - scrolledBack;
|
|
1194
|
-
return {
|
|
1195
|
-
rows: all.slice(Math.max(0, end - height), end),
|
|
1196
|
-
scrolledBack,
|
|
1197
|
-
following: scrolledBack === 0
|
|
1198
|
-
};
|
|
1221
|
+
function visibleTimeline(state, agentId, viewport, width, scroll) {
|
|
1222
|
+
return windowParts(timelineParts(state, agentId, width), viewport, scroll);
|
|
1199
1223
|
}
|
|
1200
1224
|
|
|
1201
1225
|
// src/tui/interaction.ts
|
|
@@ -1452,7 +1476,7 @@ function formComplete(fields, state) {
|
|
|
1452
1476
|
}
|
|
1453
1477
|
var MAX_FORM_FIELDS = 6;
|
|
1454
1478
|
function questionBarRows(fields) {
|
|
1455
|
-
return 2 + Math.min(fields.length, MAX_FORM_FIELDS) + 1 +
|
|
1479
|
+
return 2 + Math.min(fields.length, MAX_FORM_FIELDS) + 1 + 2;
|
|
1456
1480
|
}
|
|
1457
1481
|
function interactionRows(interactions, width) {
|
|
1458
1482
|
const front = interactions[0];
|
|
@@ -1480,11 +1504,11 @@ var HELP_GROUPS = [
|
|
|
1480
1504
|
{
|
|
1481
1505
|
title: "panes",
|
|
1482
1506
|
rows: [
|
|
1483
|
-
["tab
|
|
1507
|
+
["tab", "Next agent"],
|
|
1508
|
+
["\u21E7tab", "Toggle permission mode"],
|
|
1484
1509
|
["\u2191 \u2193", "Scroll the stream one line"],
|
|
1485
1510
|
["pgup pgdn", "Scroll the stream a page"],
|
|
1486
|
-
["ctrl+l", "Clear the
|
|
1487
|
-
["esc", "Stop reading a past turn"]
|
|
1511
|
+
["ctrl+l", "Clear the current turn's output"]
|
|
1488
1512
|
]
|
|
1489
1513
|
},
|
|
1490
1514
|
{
|
|
@@ -1583,18 +1607,22 @@ function PermissionBar({
|
|
|
1583
1607
|
others,
|
|
1584
1608
|
cursor,
|
|
1585
1609
|
released,
|
|
1586
|
-
width
|
|
1610
|
+
width,
|
|
1611
|
+
maxRows = Number.POSITIVE_INFINITY
|
|
1587
1612
|
}) {
|
|
1588
1613
|
const choices = permissionChoices(request);
|
|
1589
|
-
const
|
|
1590
|
-
const
|
|
1614
|
+
const capacity = Math.max(1, Math.min(choices.length, maxRows - 3));
|
|
1615
|
+
const start = listWindowStart(cursor ?? 0, choices.length, capacity);
|
|
1616
|
+
const extra = Math.max(0, maxRows - 3 - capacity);
|
|
1617
|
+
const impact = describeImpact(request, Math.max(8, width - 6)).slice(0, extra);
|
|
1618
|
+
const also = extra > impact.length ? waitingLine(others) : void 0;
|
|
1591
1619
|
const tool = clean(request.toolName ?? request.kind ?? "tool");
|
|
1592
1620
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1593
1621
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: COLOR.warn, wrap: "truncate-end", children: `permission \xB7 ${clean(request.agentId)} \xB7 ${tool}${others.length > 0 ? ` \xB7 ${others.length + 1} waiting` : ""}` }),
|
|
1594
1622
|
/* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: ` ${truncate(clean(request.title), Math.max(8, width - 4))}` }),
|
|
1595
1623
|
impact.map((line) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` ${line}` }, line)),
|
|
1596
|
-
choices.map((choice, index) => {
|
|
1597
|
-
const active = index === cursor;
|
|
1624
|
+
choices.slice(start, start + capacity).map((choice, index) => {
|
|
1625
|
+
const active = start + index === cursor;
|
|
1598
1626
|
return /* @__PURE__ */ jsx3(
|
|
1599
1627
|
Text3,
|
|
1600
1628
|
{
|
|
@@ -1607,7 +1635,7 @@ function PermissionBar({
|
|
|
1607
1635
|
);
|
|
1608
1636
|
}),
|
|
1609
1637
|
also === void 0 ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` ${also}` }),
|
|
1610
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: released ? " typing \xB7
|
|
1638
|
+
maxRows < 4 ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: released ? " typing \xB7 esc to return to permission options" : " \u2191\u2193 or 1-9 choose \xB7 enter confirm \xB7 esc to type instead" })
|
|
1611
1639
|
] });
|
|
1612
1640
|
}
|
|
1613
1641
|
function QuestionForm({
|
|
@@ -1616,18 +1644,26 @@ function QuestionForm({
|
|
|
1616
1644
|
fields,
|
|
1617
1645
|
form,
|
|
1618
1646
|
draft,
|
|
1619
|
-
width
|
|
1647
|
+
width,
|
|
1648
|
+
maxRows = Number.POSITIVE_INFINITY
|
|
1620
1649
|
}) {
|
|
1621
1650
|
const focused = fields[form.field];
|
|
1622
1651
|
const titleWidth = Math.min(
|
|
1623
1652
|
24,
|
|
1624
1653
|
Math.max(4, ...fields.map((field) => field.title.length + (field.required ? 2 : 0)))
|
|
1625
1654
|
);
|
|
1626
|
-
const
|
|
1627
|
-
const
|
|
1655
|
+
const actions = ["Submit answers", "Skip question", "Cancel request"];
|
|
1656
|
+
const actionIndex = form.field - fields.length;
|
|
1657
|
+
const compact = maxRows < 7;
|
|
1658
|
+
const capacity = compact ? focused ? 1 : 0 : Math.min(fields.length, MAX_FORM_FIELDS, maxRows - 5);
|
|
1659
|
+
const start = listWindowStart(
|
|
1660
|
+
Math.min(form.field, fields.length - 1),
|
|
1661
|
+
fields.length,
|
|
1662
|
+
Math.max(1, capacity)
|
|
1663
|
+
);
|
|
1628
1664
|
const visible = fields.slice(start, start + capacity);
|
|
1629
|
-
const range = fields.length > capacity ? ` \xB7 ${
|
|
1630
|
-
const detail = focused
|
|
1665
|
+
const range = fields.length > capacity && focused ? ` \xB7 field ${form.field + 1}/${fields.length}` : "";
|
|
1666
|
+
const detail = focused && (!compact || maxRows >= 5) ? detailLines(focused, width).slice(0, 1) : [];
|
|
1631
1667
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1632
1668
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: COLOR.warn, wrap: "truncate-end", children: `question \xB7 ${clean(request.agentId)}${others.length > 0 ? ` \xB7 ${others.length + 1} waiting` : ""}` }),
|
|
1633
1669
|
/* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: clean(request.message) }),
|
|
@@ -1648,7 +1684,8 @@ function QuestionForm({
|
|
|
1648
1684
|
);
|
|
1649
1685
|
}),
|
|
1650
1686
|
detail.map((line) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` ${line}` }, line)),
|
|
1651
|
-
/* @__PURE__ */ jsx3(Text3, {
|
|
1687
|
+
compact && focused ? null : /* @__PURE__ */ jsx3(Text3, { color: actionIndex >= 0 ? COLOR.accent : void 0, wrap: "truncate-end", children: compact || width < 72 ? actionIndex >= 0 ? `\u203A ${actions[actionIndex]}` : "tab to submit / skip / cancel" : actions.map((label, index) => (actionIndex === index ? "\u203A " : " ") + label).join(" \xB7 ") }),
|
|
1688
|
+
maxRows < 4 ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` tab next \xB7 \u2191\u2193 pick \xB7 space toggle \xB7 enter next${range} \xB7 esc clear` })
|
|
1652
1689
|
] });
|
|
1653
1690
|
}
|
|
1654
1691
|
function fieldValue(field, form, draft, focused) {
|
|
@@ -1766,17 +1803,19 @@ function Divider() {
|
|
|
1766
1803
|
);
|
|
1767
1804
|
}
|
|
1768
1805
|
function StreamPanel({
|
|
1769
|
-
|
|
1806
|
+
state,
|
|
1770
1807
|
height,
|
|
1771
1808
|
width,
|
|
1772
1809
|
now,
|
|
1773
1810
|
spinner
|
|
1774
1811
|
}) {
|
|
1775
|
-
const agent =
|
|
1812
|
+
const agent = state.focusId ? state.agents[state.focusId] : void 0;
|
|
1776
1813
|
const viewport = Math.max(1, height - 1);
|
|
1777
|
-
const window =
|
|
1814
|
+
const window = visibleTimeline(state, state.focusId ?? "", viewport, width, state.scroll);
|
|
1778
1815
|
const style = agent ? STATUS[agent.status] : void 0;
|
|
1779
1816
|
const elapsed = agent ? agentElapsed(agent, now) : void 0;
|
|
1817
|
+
const focusId = state.focusId;
|
|
1818
|
+
const turnCount = focusId ? recordedTurns(state).filter((turn) => (turn.agents[focusId]?.logs.length ?? 0) > 0).length : 0;
|
|
1780
1819
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, height, paddingLeft: 1, children: [
|
|
1781
1820
|
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
1782
1821
|
/* @__PURE__ */ jsx4(Box4, { flexShrink: 1, children: /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate-end", children: [
|
|
@@ -1803,6 +1842,11 @@ function StreamPanel({
|
|
|
1803
1842
|
agent?.costUsd !== void 0 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1804
1843
|
" \xB7 ",
|
|
1805
1844
|
formatCost(agent.costUsd)
|
|
1845
|
+
] }) : null,
|
|
1846
|
+
turnCount > 1 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1847
|
+
" \xB7 ",
|
|
1848
|
+
turnCount,
|
|
1849
|
+
" turns"
|
|
1806
1850
|
] }) : null
|
|
1807
1851
|
] }) }),
|
|
1808
1852
|
/* @__PURE__ */ jsxs4(Box4, { flexShrink: 0, children: [
|
|
@@ -1813,13 +1857,14 @@ function StreamPanel({
|
|
|
1813
1857
|
/* @__PURE__ */ jsx4(Text4, { color: window.following ? COLOR.accent : COLOR.warn, children: window.following ? GLYPH.live : `${GLYPH.paused} ${window.scrolledBack}\u2191` })
|
|
1814
1858
|
] })
|
|
1815
1859
|
] }),
|
|
1816
|
-
window.rows.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children:
|
|
1817
|
-
// The tail, already wrapped to `width` by `
|
|
1818
|
-
// rows, and one log line is several of them. `
|
|
1819
|
-
// which rows, so the content here is exactly the pane's
|
|
1820
|
-
// oldest rows are the ones already gone: nothing is left
|
|
1821
|
-
// is why this box is not bottom-pinned. Pinning it would
|
|
1822
|
-
// gap under the header whenever an agent has not said much
|
|
1860
|
+
window.rows.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: state.phase === "running" ? "waiting for output\u2026" : "no output yet" }) : (
|
|
1861
|
+
// The tail, already wrapped to `width` by `visibleTimeline` — the pane
|
|
1862
|
+
// draws rows, and one log line is several of them. `visibleTimeline`
|
|
1863
|
+
// also picks which rows, so the content here is exactly the pane's
|
|
1864
|
+
// height and the oldest rows are the ones already gone: nothing is left
|
|
1865
|
+
// to clip, which is why this box is not bottom-pinned. Pinning it would
|
|
1866
|
+
// only leave a gap under the header whenever an agent has not said much
|
|
1867
|
+
// yet.
|
|
1823
1868
|
//
|
|
1824
1869
|
// Rows are rendered with `truncate-end` rather than letting Ink wrap
|
|
1825
1870
|
// them again: they are supposed to fit, and if `width` was ever
|
|
@@ -2339,6 +2384,16 @@ var SESSION_SAVE_INTERVAL_MS = 2e3;
|
|
|
2339
2384
|
var INPUT_PLACEHOLDER = "describe what the team should do\u2026";
|
|
2340
2385
|
var RUNNING_PLACEHOLDER = "type to queue a follow-up\u2026";
|
|
2341
2386
|
var EMPTY_LINE = { text: "", cursor: 0 };
|
|
2387
|
+
function retryCandidates(state) {
|
|
2388
|
+
const turn = recordedTurns(state).slice(-1)[0];
|
|
2389
|
+
if (!turn?.requirement) return [];
|
|
2390
|
+
if (turn.acceptance && turn.acceptance.status !== "approved") {
|
|
2391
|
+
return [
|
|
2392
|
+
...new Set(turn.stages.flatMap((stage) => stage.agents.map((agent) => agent.id)))
|
|
2393
|
+
].filter((id) => id !== turn.acceptance?.reviewer);
|
|
2394
|
+
}
|
|
2395
|
+
return Object.values(turn.agents).filter((agent) => agent.status === "failed").map((agent) => agent.id);
|
|
2396
|
+
}
|
|
2342
2397
|
function pickRecordedTurn(state, raw, whenEmpty) {
|
|
2343
2398
|
const turns = recordedTurns(state);
|
|
2344
2399
|
if (turns.length === 0) return { error: whenEmpty };
|
|
@@ -2470,10 +2525,19 @@ function App({
|
|
|
2470
2525
|
queueRef.current = queue;
|
|
2471
2526
|
const [cancelled, setCancelled] = useState(false);
|
|
2472
2527
|
const [error, setError] = useState();
|
|
2528
|
+
const [permissionMode, setPermissionMode] = useState(
|
|
2529
|
+
// Same precedence the runner uses, so the header can never announce a mode
|
|
2530
|
+
// the agents are not actually running under. `ask` when nobody configured
|
|
2531
|
+
// one, because a human is sitting here to answer.
|
|
2532
|
+
resolvePermissionMode(config) ?? "ask"
|
|
2533
|
+
);
|
|
2473
2534
|
const [notice, setNotice] = useState(
|
|
2474
2535
|
launchNoticeRef.current ?? restored.notice
|
|
2475
2536
|
);
|
|
2476
2537
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
2538
|
+
const [picker, setPicker] = useState(null);
|
|
2539
|
+
const [operationInput, setOperationInput] = useState(null);
|
|
2540
|
+
const [searchResult, setSearchResult] = useState(null);
|
|
2477
2541
|
const [sessionsOpen, setSessionsOpen] = useState(false);
|
|
2478
2542
|
const [sessionCursor, setSessionCursor] = useState(0);
|
|
2479
2543
|
const [sessionList, setSessionList] = useState([]);
|
|
@@ -2488,8 +2552,6 @@ function App({
|
|
|
2488
2552
|
useEffect(() => {
|
|
2489
2553
|
if (initialSession?.kind !== "resume" || !("id" in initialSession)) return;
|
|
2490
2554
|
const history = restoredRef.current?.history ?? [];
|
|
2491
|
-
const last = history[history.length - 1];
|
|
2492
|
-
if (last) dispatch({ type: "view:open", turn: last.turn });
|
|
2493
2555
|
const resumable = [...history].reverse().find((turn) => Object.keys(turnSessions(turn)).length > 0);
|
|
2494
2556
|
continuationRef.current = resumable ? { turn: resumable.turn, sessions: turnSessions(resumable) } : null;
|
|
2495
2557
|
}, [initialSession]);
|
|
@@ -2563,16 +2625,21 @@ function App({
|
|
|
2563
2625
|
const handleCheckpoint = useCallback((info, signal) => {
|
|
2564
2626
|
if (closingRef.current || signal.aborted) return Promise.resolve({ action: "cancel" });
|
|
2565
2627
|
return new Promise((resolve2) => {
|
|
2566
|
-
|
|
2628
|
+
const settle = (response) => {
|
|
2629
|
+
signal.removeEventListener("abort", onAbort);
|
|
2630
|
+
if (checkpointResolveRef.current === settle) {
|
|
2631
|
+
checkpointResolveRef.current = null;
|
|
2632
|
+
checkpointLabelRef.current = "";
|
|
2633
|
+
}
|
|
2634
|
+
resolve2(response);
|
|
2635
|
+
};
|
|
2636
|
+
checkpointResolveRef.current = settle;
|
|
2567
2637
|
checkpointLabelRef.current = info.label;
|
|
2568
2638
|
const names = info.completedAgents.map((a) => a.name).join(", ");
|
|
2569
2639
|
setNotice(
|
|
2570
|
-
`checkpoint: ${info.label} \u2014 ${names} done \xB7 /continue
|
|
2640
|
+
`checkpoint: ${info.label} \u2014 ${names} done \xB7 /continue \xB7 type feedback \xB7 ctrl+c cancel`
|
|
2571
2641
|
);
|
|
2572
|
-
const onAbort = () => {
|
|
2573
|
-
checkpointResolveRef.current = null;
|
|
2574
|
-
resolve2({ action: "cancel" });
|
|
2575
|
-
};
|
|
2642
|
+
const onAbort = () => settle({ action: "cancel" });
|
|
2576
2643
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2577
2644
|
});
|
|
2578
2645
|
}, []);
|
|
@@ -2689,6 +2756,13 @@ function App({
|
|
|
2689
2756
|
setError(void 0);
|
|
2690
2757
|
setCancelled(false);
|
|
2691
2758
|
setNotice(void 0);
|
|
2759
|
+
if (opts?.newSession && !requirement) {
|
|
2760
|
+
busyRef.current = false;
|
|
2761
|
+
setRunning(false);
|
|
2762
|
+
setSearchResult(null);
|
|
2763
|
+
setNotice("new session \u2014 type a requirement to begin");
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2692
2766
|
const earlierTurns = opts?.newSession ? [] : conversationTurns(stateRef.current);
|
|
2693
2767
|
dispatch({
|
|
2694
2768
|
type: "turn:start",
|
|
@@ -2706,7 +2780,16 @@ function App({
|
|
|
2706
2780
|
// so the leader is handed the turns behind this one as well.
|
|
2707
2781
|
history: earlierTurns,
|
|
2708
2782
|
logDir,
|
|
2709
|
-
permissions:
|
|
2783
|
+
permissions: permissionMode === "ask" ? { mode: "ask", onRequest: requestPermission } : { mode: permissionMode },
|
|
2784
|
+
// The console's mode is a live decision, so it outranks any per-agent
|
|
2785
|
+
// `permissions` written in the file: otherwise shift+tab would relabel
|
|
2786
|
+
// the header while an agent that declares its own mode kept the old
|
|
2787
|
+
// behaviour. Per-agent modes stay a headless-run concern.
|
|
2788
|
+
agents: config.agents.map((agent) => ({
|
|
2789
|
+
...agent,
|
|
2790
|
+
permissions: { mode: permissionMode }
|
|
2791
|
+
})),
|
|
2792
|
+
...config.leader ? { leader: { ...config.leader, permissions: { mode: permissionMode } } } : {},
|
|
2710
2793
|
questions: config.questions?.onRequest ? config.questions : { onRequest: requestQuestion },
|
|
2711
2794
|
onCheckpoint: config.onCheckpoint ?? handleCheckpoint,
|
|
2712
2795
|
// A retry names the agents to redo; a continued conversation carries only
|
|
@@ -2771,7 +2854,8 @@ function App({
|
|
|
2771
2854
|
requestPermission,
|
|
2772
2855
|
requestQuestion,
|
|
2773
2856
|
handleCheckpoint,
|
|
2774
|
-
clearInteractions
|
|
2857
|
+
clearInteractions,
|
|
2858
|
+
permissionMode
|
|
2775
2859
|
]
|
|
2776
2860
|
);
|
|
2777
2861
|
useEffect(() => {
|
|
@@ -2791,6 +2875,7 @@ function App({
|
|
|
2791
2875
|
return;
|
|
2792
2876
|
}
|
|
2793
2877
|
if (closingRef.current) return;
|
|
2878
|
+
setSearchResult(null);
|
|
2794
2879
|
const restored2 = restoreConsole(config.workDir, id);
|
|
2795
2880
|
sessionIdRef.current = id;
|
|
2796
2881
|
persistedTurnRef.current = restored2.persistedTurn;
|
|
@@ -2802,10 +2887,6 @@ function App({
|
|
|
2802
2887
|
leaderIds: leaderAgentIds(config),
|
|
2803
2888
|
history: restored2.history
|
|
2804
2889
|
});
|
|
2805
|
-
const lastTurn = restored2.history[restored2.history.length - 1];
|
|
2806
|
-
if (lastTurn) {
|
|
2807
|
-
dispatch({ type: "view:open", turn: lastTurn.turn });
|
|
2808
|
-
}
|
|
2809
2890
|
setQueue(restored2.queue);
|
|
2810
2891
|
queuePausedRef.current = restored2.queue.length > 0;
|
|
2811
2892
|
setQueuePaused(restored2.queue.length > 0);
|
|
@@ -2885,28 +2966,32 @@ function App({
|
|
|
2885
2966
|
})();
|
|
2886
2967
|
return shutdownRef.current;
|
|
2887
2968
|
}, [config.workDir, exit, cleanup]);
|
|
2888
|
-
const
|
|
2889
|
-
|
|
2890
|
-
()
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2969
|
+
const commandContext = {
|
|
2970
|
+
history: recordedTurns(state).length > 0,
|
|
2971
|
+
retry: retryCandidates(state).length > 0,
|
|
2972
|
+
queue: queue.length,
|
|
2973
|
+
paused: queuePaused,
|
|
2974
|
+
awaitingAnswer,
|
|
2975
|
+
checkpoint: checkpointResolveRef.current !== null
|
|
2976
|
+
};
|
|
2977
|
+
const query = interactions[0]?.kind === "question" ? void 0 : menuQuery(line.text);
|
|
2978
|
+
const matches = query === void 0 ? [] : matchCommands(query, running, commandContext);
|
|
2979
|
+
const arg = interactions[0]?.kind === "question" ? void 0 : argQuery(line.text);
|
|
2980
|
+
const argRows = arg ? completeAgents(arg.partial, Object.keys(state.agents)).map((id) => ({
|
|
2896
2981
|
key: id,
|
|
2897
2982
|
label: id,
|
|
2898
2983
|
// The static role, not the per-turn task title: while completing, the
|
|
2899
2984
|
// user is asking "which agent is this", not "what is it doing now".
|
|
2900
|
-
description:
|
|
2985
|
+
description: state.agents[id]?.description ?? state.agents[id]?.name ?? id
|
|
2901
2986
|
})) : [];
|
|
2902
|
-
const allRows = arg ? argRows : paletteRows(matches);
|
|
2987
|
+
const allRows = picker ? picker.rows : arg ? argRows : paletteRows(matches);
|
|
2903
2988
|
const paletteKey = arg ? `${arg.command.name} ${arg.partial}` : query;
|
|
2904
2989
|
const paletteTotal = allRows.length;
|
|
2905
2990
|
const menuIndex = menuCursor.query === paletteKey ? menuCursor.index : 0;
|
|
2906
|
-
const selected = paletteTotal === 0 ? 0 : Math.min(menuIndex, paletteTotal - 1);
|
|
2907
|
-
const menuOpen = !helpOpen && !sessionsOpen && query !== void 0 && matches.length > 0;
|
|
2908
|
-
const argOpen = !helpOpen && !sessionsOpen && arg !== void 0 && argRows.length > 0;
|
|
2909
|
-
const paletteOpen = menuOpen || argOpen;
|
|
2991
|
+
const selected = paletteTotal === 0 ? 0 : Math.min(picker?.cursor ?? menuIndex, paletteTotal - 1);
|
|
2992
|
+
const menuOpen = !picker && !operationInput && !helpOpen && !sessionsOpen && query !== void 0 && matches.length > 0;
|
|
2993
|
+
const argOpen = !picker && !operationInput && !helpOpen && !sessionsOpen && arg !== void 0 && argRows.length > 0;
|
|
2994
|
+
const paletteOpen = picker !== null || menuOpen || argOpen;
|
|
2910
2995
|
const front = interactions[0];
|
|
2911
2996
|
const menu = front?.kind === "permission" && permissionMenu.id === front.shortId ? permissionMenu : { id: front?.shortId ?? "", cursor: 0, released: false };
|
|
2912
2997
|
const frontQuestion = front?.kind === "question" ? front : void 0;
|
|
@@ -2932,9 +3017,8 @@ function App({
|
|
|
2932
3017
|
interactionRows: interactionRows(interactions, Math.max(MIN_COLUMNS, columns)),
|
|
2933
3018
|
promptRows,
|
|
2934
3019
|
notice: notice !== void 0 || queuePaused && queue.length > 0,
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
railCells: railCellWidths(shown)
|
|
3020
|
+
hasRail: state.leaderIds.length > 0 || state.stages.length > 0,
|
|
3021
|
+
railCells: railCellWidths(state)
|
|
2938
3022
|
});
|
|
2939
3023
|
const steerRunningAgents = useCallback(async (text) => {
|
|
2940
3024
|
const manager = sessionsRef.current;
|
|
@@ -2959,11 +3043,11 @@ function App({
|
|
|
2959
3043
|
setNotice(describeSteering(reports));
|
|
2960
3044
|
}, []);
|
|
2961
3045
|
const runCommand = useCallback(
|
|
2962
|
-
(name, args
|
|
3046
|
+
(name, args) => {
|
|
2963
3047
|
setLine(EMPTY_LINE);
|
|
2964
|
-
if (checkpointResolveRef.current && name !== "continue"
|
|
3048
|
+
if (checkpointResolveRef.current && name !== "continue") {
|
|
2965
3049
|
setNotice(
|
|
2966
|
-
`checkpoint: ${checkpointLabelRef.current} \xB7 /continue
|
|
3050
|
+
`checkpoint: ${checkpointLabelRef.current} \xB7 /continue \xB7 type feedback \xB7 ctrl+c cancel`
|
|
2967
3051
|
);
|
|
2968
3052
|
return;
|
|
2969
3053
|
}
|
|
@@ -2971,111 +3055,86 @@ function App({
|
|
|
2971
3055
|
setNotice(unavailableReason(name, running));
|
|
2972
3056
|
return;
|
|
2973
3057
|
}
|
|
2974
|
-
const shown2 = state.view ?? state;
|
|
2975
3058
|
if (closingRef.current) return;
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
(entry2) => entry2.request.requestId === id || entry2.shortId === id
|
|
3059
|
+
const choose = (rows2) => {
|
|
3060
|
+
setPicker({ command: name, rows: rows2, cursor: 0 });
|
|
3061
|
+
setNotice(`choose ${name} \xB7 enter confirm \xB7 esc cancel`);
|
|
3062
|
+
};
|
|
3063
|
+
if (args.length === 0) {
|
|
3064
|
+
if (name === "mode") {
|
|
3065
|
+
choose(
|
|
3066
|
+
["ask", "auto", "deny"].map((mode) => ({
|
|
3067
|
+
key: mode,
|
|
3068
|
+
label: mode,
|
|
3069
|
+
description: mode === "ask" ? "Ask before tools run" : mode === "auto" ? "Allow tools automatically" : "Refuse tool requests"
|
|
3070
|
+
}))
|
|
2989
3071
|
);
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
setNotice("
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
if (name === "focus") {
|
|
3075
|
+
choose(
|
|
3076
|
+
Object.values(state.agents).map((agent) => ({
|
|
3077
|
+
key: agent.id,
|
|
3078
|
+
label: agent.name,
|
|
3079
|
+
description: agent.description ?? agent.id
|
|
3080
|
+
}))
|
|
3081
|
+
);
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
if (name === "retry") {
|
|
3085
|
+
const ids = retryCandidates(state);
|
|
3086
|
+
if (ids.length === 0) {
|
|
3087
|
+
setNotice("nothing failed");
|
|
3006
3088
|
return;
|
|
3007
3089
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3090
|
+
choose([
|
|
3091
|
+
{ key: "all", label: "All failed / repair agents", description: ids.join(", ") },
|
|
3092
|
+
...ids.map((id) => ({
|
|
3093
|
+
key: id,
|
|
3094
|
+
label: state.agents[id]?.name ?? id,
|
|
3095
|
+
description: state.agents[id]?.error ?? "Retry this agent"
|
|
3096
|
+
}))
|
|
3097
|
+
]);
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
if (["replay", "edit", "export"].includes(name)) {
|
|
3101
|
+
const turns = recordedTurns(state).slice().reverse();
|
|
3102
|
+
if (turns.length === 0) {
|
|
3103
|
+
setNotice(`nothing to ${name} yet`);
|
|
3012
3104
|
return;
|
|
3013
3105
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3106
|
+
choose(
|
|
3107
|
+
turns.map((turn) => ({
|
|
3108
|
+
key: String(turn.turn),
|
|
3109
|
+
label: `Turn ${turn.turn}`,
|
|
3110
|
+
description: turn.requirement?.replace(/\s+/g, " ") ?? "(no requirement)"
|
|
3111
|
+
}))
|
|
3112
|
+
);
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
if (name === "search" || name === "steer") {
|
|
3116
|
+
setOperationInput(name);
|
|
3117
|
+
setNotice(
|
|
3118
|
+
name === "search" ? "search the focused stream \xB7 enter find \xB7 esc cancel" : "message running agents \xB7 enter send \xB7 esc cancel"
|
|
3119
|
+
);
|
|
3019
3120
|
return;
|
|
3020
3121
|
}
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
);
|
|
3028
|
-
if (found.length !== 1) setNotice(`unknown or stale permission request: ${id}`);
|
|
3029
|
-
return found.length === 1 ? found[0] : void 0;
|
|
3030
|
-
};
|
|
3031
|
-
let entry;
|
|
3032
|
-
let number;
|
|
3033
|
-
if (name === "approve" && args.length === 1 && /^\d+$/.test(args[0])) {
|
|
3034
|
-
number = args[0];
|
|
3035
|
-
entry = onScreen;
|
|
3036
|
-
} else if (name === "approve" && args.length === 2) {
|
|
3037
|
-
number = args[1];
|
|
3038
|
-
entry = byId(args[0]);
|
|
3039
|
-
} else if (name === "deny" && args.length === 0) {
|
|
3040
|
-
entry = onScreen;
|
|
3041
|
-
} else if (name === "deny" && args.length === 1) {
|
|
3042
|
-
entry = byId(args[0]);
|
|
3122
|
+
}
|
|
3123
|
+
switch (name) {
|
|
3124
|
+
case "mode": {
|
|
3125
|
+
const m = args[0];
|
|
3126
|
+
if (m === "auto" || m === "ask" || m === "deny") {
|
|
3127
|
+
setPermissionMode(m);
|
|
3128
|
+
setNotice(`permission mode: ${m}`);
|
|
3043
3129
|
} else {
|
|
3044
|
-
setNotice(
|
|
3045
|
-
name === "approve" ? "usage: /approve [id] <option number>" : "usage: /deny [id]"
|
|
3046
|
-
);
|
|
3047
|
-
return;
|
|
3130
|
+
setNotice("usage: /mode auto | ask | deny");
|
|
3048
3131
|
}
|
|
3049
|
-
if (entry === void 0) {
|
|
3050
|
-
if (args.length === 0 || /^\d+$/.test(args[0] ?? "")) {
|
|
3051
|
-
setNotice("no permission request on screen");
|
|
3052
|
-
}
|
|
3053
|
-
return;
|
|
3054
|
-
}
|
|
3055
|
-
if (entry.kind !== "permission") {
|
|
3056
|
-
setNotice("the request on screen is a question, not a permission");
|
|
3057
|
-
return;
|
|
3058
|
-
}
|
|
3059
|
-
if (name === "deny") {
|
|
3060
|
-
entry.settle();
|
|
3061
|
-
setNotice(`denied ${entry.shortId}`);
|
|
3062
|
-
return;
|
|
3063
|
-
}
|
|
3064
|
-
const option = /^\d+$/.test(number ?? "") ? entry.request.options[Number(number) - 1] : void 0;
|
|
3065
|
-
if (!option) {
|
|
3066
|
-
setNotice(
|
|
3067
|
-
`invalid option for ${entry.shortId}; choose 1\u2013${entry.request.options.length}`
|
|
3068
|
-
);
|
|
3069
|
-
return;
|
|
3070
|
-
}
|
|
3071
|
-
entry.settle(option.optionId);
|
|
3072
|
-
setNotice(`selected ${clean(option.name)} for ${entry.shortId}`);
|
|
3073
3132
|
return;
|
|
3074
3133
|
}
|
|
3075
3134
|
case "new": {
|
|
3076
3135
|
const text = args.join(" ");
|
|
3077
|
-
if (
|
|
3078
|
-
setNotice("
|
|
3136
|
+
if (text) {
|
|
3137
|
+
setNotice("/new opens an empty session \u2014 enter the requirement afterwards");
|
|
3079
3138
|
return;
|
|
3080
3139
|
}
|
|
3081
3140
|
setQueue([]);
|
|
@@ -3084,15 +3143,6 @@ function App({
|
|
|
3084
3143
|
void startTurn(text, { newSession: true });
|
|
3085
3144
|
return;
|
|
3086
3145
|
}
|
|
3087
|
-
case "answer": {
|
|
3088
|
-
const text = args.join(" ");
|
|
3089
|
-
if (!awaitingAnswerRef.current || !text) {
|
|
3090
|
-
setNotice("usage: /answer <text> \u2014 only when the leader is waiting for an answer");
|
|
3091
|
-
return;
|
|
3092
|
-
}
|
|
3093
|
-
void startTurn(text);
|
|
3094
|
-
return;
|
|
3095
|
-
}
|
|
3096
3146
|
case "steer": {
|
|
3097
3147
|
const text = args.join(" ");
|
|
3098
3148
|
if (!text) {
|
|
@@ -3103,6 +3153,10 @@ function App({
|
|
|
3103
3153
|
return;
|
|
3104
3154
|
}
|
|
3105
3155
|
case "pause":
|
|
3156
|
+
if (queueRef.current.length === 0) {
|
|
3157
|
+
setNotice("no queued turns to pause");
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3106
3160
|
queuePausedRef.current = true;
|
|
3107
3161
|
setQueuePaused(true);
|
|
3108
3162
|
setNotice("queue paused \u2014 the active turn continues");
|
|
@@ -3120,56 +3174,31 @@ function App({
|
|
|
3120
3174
|
setNotice("answer the leader first; queued input is not an answer");
|
|
3121
3175
|
return;
|
|
3122
3176
|
}
|
|
3177
|
+
if (queueRef.current.length === 0) {
|
|
3178
|
+
setNotice("no queued turns to resume");
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3123
3181
|
queuePausedRef.current = false;
|
|
3124
3182
|
setQueuePaused(false);
|
|
3125
3183
|
setNotice("queue resumed");
|
|
3126
3184
|
return;
|
|
3127
3185
|
}
|
|
3128
|
-
case "modify": {
|
|
3129
|
-
const instruction = args.join(" ");
|
|
3130
|
-
if (!instruction) {
|
|
3131
|
-
setNotice("usage: /modify <instruction> \u2014 only at a checkpoint");
|
|
3132
|
-
return;
|
|
3133
|
-
}
|
|
3134
|
-
if (!checkpointResolveRef.current) {
|
|
3135
|
-
setNotice("no checkpoint is waiting");
|
|
3136
|
-
return;
|
|
3137
|
-
}
|
|
3138
|
-
const resolve2 = checkpointResolveRef.current;
|
|
3139
|
-
checkpointResolveRef.current = null;
|
|
3140
|
-
checkpointLabelRef.current = "";
|
|
3141
|
-
resolve2({ action: "modify", instruction });
|
|
3142
|
-
setNotice("modifying and continuing\u2026");
|
|
3143
|
-
return;
|
|
3144
|
-
}
|
|
3145
|
-
case "agents":
|
|
3146
|
-
setNotice(
|
|
3147
|
-
`team: ${plannedAgents(config).map((agent) => agent.id).join(", ")}`
|
|
3148
|
-
);
|
|
3149
|
-
return;
|
|
3150
3186
|
case "focus": {
|
|
3151
3187
|
const input = args[0];
|
|
3152
3188
|
if (!input) {
|
|
3153
3189
|
setNotice("usage: /focus <agent id>");
|
|
3154
3190
|
return;
|
|
3155
3191
|
}
|
|
3156
|
-
const resolved = resolveAgentId(input, Object.keys(
|
|
3192
|
+
const resolved = resolveAgentId(input, Object.keys(state.agents));
|
|
3157
3193
|
if ("error" in resolved) {
|
|
3158
3194
|
setNotice(resolved.error);
|
|
3159
3195
|
return;
|
|
3160
3196
|
}
|
|
3197
|
+
setSearchResult(null);
|
|
3161
3198
|
dispatch({ type: "focus:set", agentId: resolved.id });
|
|
3162
3199
|
setNotice(`focused ${resolved.id}`);
|
|
3163
3200
|
return;
|
|
3164
3201
|
}
|
|
3165
|
-
case "clear":
|
|
3166
|
-
if (!shown2.focusId) {
|
|
3167
|
-
setNotice("nothing focused");
|
|
3168
|
-
return;
|
|
3169
|
-
}
|
|
3170
|
-
dispatch({ type: "stream:clear" });
|
|
3171
|
-
setNotice(`cleared ${shown2.focusId}`);
|
|
3172
|
-
return;
|
|
3173
3202
|
case "sessions": {
|
|
3174
3203
|
const list = listSessions(config.workDir);
|
|
3175
3204
|
setSessionList(list);
|
|
@@ -3184,38 +3213,44 @@ function App({
|
|
|
3184
3213
|
setNotice(state.verbose ? "verbose off" : "verbose on \u2014 showing tool detail");
|
|
3185
3214
|
return;
|
|
3186
3215
|
case "retry": {
|
|
3187
|
-
|
|
3216
|
+
const turn = recordedTurns(state).slice(-1)[0];
|
|
3217
|
+
if (!turn?.requirement) {
|
|
3188
3218
|
setNotice("nothing to retry yet");
|
|
3189
3219
|
return;
|
|
3190
3220
|
}
|
|
3221
|
+
if (args[0] === "all") args = [];
|
|
3191
3222
|
let ids;
|
|
3192
|
-
if (
|
|
3223
|
+
if (turn.acceptance && turn.acceptance.status !== "approved" && (!args[0] || args[0] === turn.acceptance.reviewer)) {
|
|
3193
3224
|
ids = [
|
|
3194
|
-
...new Set(
|
|
3195
|
-
].filter((id) => id !==
|
|
3225
|
+
...new Set(turn.stages.flatMap((stage) => stage.agents.map((agent) => agent.id)))
|
|
3226
|
+
].filter((id) => id !== turn.acceptance?.reviewer);
|
|
3196
3227
|
} else if (args[0]) {
|
|
3197
|
-
const resolved = resolveAgentId(args[0], Object.keys(
|
|
3228
|
+
const resolved = resolveAgentId(args[0], Object.keys(turn.agents));
|
|
3198
3229
|
if ("error" in resolved) {
|
|
3199
3230
|
setNotice(resolved.error);
|
|
3200
3231
|
return;
|
|
3201
3232
|
}
|
|
3233
|
+
if (!retryCandidates(state).includes(resolved.id)) {
|
|
3234
|
+
setNotice(`agent has not failed or does not need repair: ${resolved.id}`);
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3202
3237
|
ids = [resolved.id];
|
|
3203
3238
|
} else {
|
|
3204
|
-
ids = Object.values(
|
|
3239
|
+
ids = Object.values(turn.agents).filter((agent) => agent.status === "failed").map((agent) => agent.id);
|
|
3205
3240
|
}
|
|
3206
3241
|
if (ids.length === 0) {
|
|
3207
3242
|
setNotice("nothing failed");
|
|
3208
3243
|
return;
|
|
3209
3244
|
}
|
|
3210
3245
|
const sessions = Object.fromEntries(
|
|
3211
|
-
ids.filter((id) =>
|
|
3246
|
+
ids.filter((id) => turn.agents[id]?.sessionId).map((id) => [id, turn.agents[id].sessionId])
|
|
3212
3247
|
);
|
|
3213
|
-
if (ids.some((id) =>
|
|
3214
|
-
void startTurn(
|
|
3248
|
+
if (ids.some((id) => turn.leaderIds.includes(id))) {
|
|
3249
|
+
void startTurn(turn.requirement);
|
|
3215
3250
|
setNotice("retrying leader routing");
|
|
3216
3251
|
return;
|
|
3217
3252
|
}
|
|
3218
|
-
void startTurn(
|
|
3253
|
+
void startTurn(turn.effectiveRequirement ?? turn.requirement, {
|
|
3219
3254
|
resume: {
|
|
3220
3255
|
agents: ids,
|
|
3221
3256
|
...Object.keys(sessions).length > 0 ? { sessions } : {}
|
|
@@ -3240,8 +3275,8 @@ function App({
|
|
|
3240
3275
|
setNotice(`replaying turn ${target.record.turn}`);
|
|
3241
3276
|
return;
|
|
3242
3277
|
}
|
|
3243
|
-
case "
|
|
3244
|
-
const target = pickTurn(state, args[0], "nothing to
|
|
3278
|
+
case "edit": {
|
|
3279
|
+
const target = pickTurn(state, args[0], "nothing to edit yet");
|
|
3245
3280
|
if ("error" in target) {
|
|
3246
3281
|
setNotice(target.error);
|
|
3247
3282
|
return;
|
|
@@ -3266,74 +3301,66 @@ function App({
|
|
|
3266
3301
|
setNotice("usage: /search <text>");
|
|
3267
3302
|
return;
|
|
3268
3303
|
}
|
|
3304
|
+
const matches2 = streamMatches(state, text, layout.streamWidth);
|
|
3305
|
+
if (matches2.length === 0) {
|
|
3306
|
+
setSearchResult(null);
|
|
3307
|
+
setNotice(`no matches for ${text}`);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
const index = matches2.length - 1;
|
|
3311
|
+
setSearchResult({ query: text, index });
|
|
3269
3312
|
dispatch({
|
|
3270
3313
|
type: "stream:search",
|
|
3271
3314
|
query: text,
|
|
3315
|
+
match: index,
|
|
3272
3316
|
viewport: layout.logViewport,
|
|
3273
3317
|
width: layout.streamWidth
|
|
3274
3318
|
});
|
|
3275
|
-
setNotice(
|
|
3319
|
+
setNotice(
|
|
3320
|
+
"searched for " + text + " \xB7 match " + (index + 1) + "/" + matches2.length + " \xB7 ctrl+n next \xB7 ctrl+p previous"
|
|
3321
|
+
);
|
|
3276
3322
|
return;
|
|
3277
3323
|
}
|
|
3278
3324
|
case "latest":
|
|
3325
|
+
setSearchResult(null);
|
|
3279
3326
|
dispatch({ type: "stream:latest" });
|
|
3280
3327
|
setNotice("showing latest output");
|
|
3281
3328
|
return;
|
|
3282
|
-
case "cancel": {
|
|
3283
|
-
if (checkpointResolveRef.current) {
|
|
3284
|
-
const resolve2 = checkpointResolveRef.current;
|
|
3285
|
-
checkpointResolveRef.current = null;
|
|
3286
|
-
checkpointLabelRef.current = "";
|
|
3287
|
-
resolve2({ action: "cancel" });
|
|
3288
|
-
setNotice("checkpoint cancelled");
|
|
3289
|
-
return;
|
|
3290
|
-
}
|
|
3291
|
-
if (!abortRef.current) {
|
|
3292
|
-
setNotice("nothing to cancel");
|
|
3293
|
-
return;
|
|
3294
|
-
}
|
|
3295
|
-
const dropped = queue.length;
|
|
3296
|
-
abortRef.current.abort();
|
|
3297
|
-
clearInteractions();
|
|
3298
|
-
setQueue([]);
|
|
3299
|
-
setCancelled(true);
|
|
3300
|
-
setNotice(dropped > 0 ? `cancelling\u2026 \xB7 dropped ${dropped} queued` : "cancelling\u2026");
|
|
3301
|
-
return;
|
|
3302
|
-
}
|
|
3303
|
-
case "help":
|
|
3304
|
-
setNotice(void 0);
|
|
3305
|
-
setHelpOpen(true);
|
|
3306
|
-
return;
|
|
3307
|
-
case "quit":
|
|
3308
|
-
void shutdown();
|
|
3309
|
-
return;
|
|
3310
3329
|
default:
|
|
3311
3330
|
setNotice(`unknown command: /${name}`);
|
|
3312
3331
|
}
|
|
3313
3332
|
},
|
|
3314
|
-
[
|
|
3315
|
-
config,
|
|
3316
|
-
shutdown,
|
|
3317
|
-
clearInteractions,
|
|
3318
|
-
layout.logViewport,
|
|
3319
|
-
layout.streamWidth,
|
|
3320
|
-
queue.length,
|
|
3321
|
-
running,
|
|
3322
|
-
state,
|
|
3323
|
-
startTurn,
|
|
3324
|
-
steerRunningAgents
|
|
3325
|
-
]
|
|
3333
|
+
[config, layout.logViewport, layout.streamWidth, running, state, startTurn, steerRunningAgents]
|
|
3326
3334
|
);
|
|
3327
3335
|
const submit = useCallback(() => {
|
|
3328
3336
|
const text = line.text.trim();
|
|
3329
3337
|
if (!text || closingRef.current) return;
|
|
3338
|
+
if (operationInput) {
|
|
3339
|
+
const operation = operationInput;
|
|
3340
|
+
setOperationInput(null);
|
|
3341
|
+
if (operation === "steer" && !running) {
|
|
3342
|
+
setLine(EMPTY_LINE);
|
|
3343
|
+
setQueue((current) => [...current, text]);
|
|
3344
|
+
queuePausedRef.current = true;
|
|
3345
|
+
setQueuePaused(true);
|
|
3346
|
+
setNotice("turn finished \u2014 message queued; /continue runs it");
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
runCommand(operation, [text]);
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3330
3352
|
const command = parseCommand(text);
|
|
3331
3353
|
if (command) {
|
|
3332
|
-
runCommand(command.name, command.args
|
|
3354
|
+
runCommand(command.name, command.args);
|
|
3333
3355
|
return;
|
|
3334
3356
|
}
|
|
3335
3357
|
if (checkpointResolveRef.current) {
|
|
3336
|
-
|
|
3358
|
+
const resolve2 = checkpointResolveRef.current;
|
|
3359
|
+
checkpointResolveRef.current = null;
|
|
3360
|
+
checkpointLabelRef.current = "";
|
|
3361
|
+
setLine(EMPTY_LINE);
|
|
3362
|
+
resolve2({ action: "modify", instruction: text });
|
|
3363
|
+
setNotice("modifying and continuing\u2026");
|
|
3337
3364
|
return;
|
|
3338
3365
|
}
|
|
3339
3366
|
setLine(EMPTY_LINE);
|
|
@@ -3343,7 +3370,7 @@ function App({
|
|
|
3343
3370
|
return;
|
|
3344
3371
|
}
|
|
3345
3372
|
void startTurn(text);
|
|
3346
|
-
}, [line.text, runCommand, running, startTurn]);
|
|
3373
|
+
}, [line.text, operationInput, runCommand, running, startTurn]);
|
|
3347
3374
|
const insert = useCallback((chunk) => {
|
|
3348
3375
|
setLine((l) => ({
|
|
3349
3376
|
text: l.text.slice(0, l.cursor) + chunk + l.text.slice(l.cursor),
|
|
@@ -3373,7 +3400,12 @@ function App({
|
|
|
3373
3400
|
const spinner = SPINNER[tick % SPINNER.length];
|
|
3374
3401
|
const handleKey = (char, key) => {
|
|
3375
3402
|
if (key.ctrl && char === "c") {
|
|
3376
|
-
if (
|
|
3403
|
+
if (picker || operationInput) {
|
|
3404
|
+
setNotice(void 0);
|
|
3405
|
+
setPicker(null);
|
|
3406
|
+
setOperationInput(null);
|
|
3407
|
+
setLine(EMPTY_LINE);
|
|
3408
|
+
} else if (helpOpen) {
|
|
3377
3409
|
setHelpOpen(false);
|
|
3378
3410
|
} else if (sessionsOpen) {
|
|
3379
3411
|
setSessionsOpen(false);
|
|
@@ -3438,13 +3470,61 @@ function App({
|
|
|
3438
3470
|
}
|
|
3439
3471
|
return;
|
|
3440
3472
|
}
|
|
3473
|
+
if (picker) {
|
|
3474
|
+
if (key.escape) {
|
|
3475
|
+
setPicker(null);
|
|
3476
|
+
setLine(EMPTY_LINE);
|
|
3477
|
+
setNotice(void 0);
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
if (key.upArrow || key.downArrow || key.tab) {
|
|
3481
|
+
const delta = key.upArrow || key.tab && key.shift ? -1 : 1;
|
|
3482
|
+
setPicker({
|
|
3483
|
+
...picker,
|
|
3484
|
+
cursor: (picker.cursor + delta + picker.rows.length) % picker.rows.length
|
|
3485
|
+
});
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
if (key.return) {
|
|
3489
|
+
const row = picker.rows[picker.cursor];
|
|
3490
|
+
setPicker(null);
|
|
3491
|
+
if (row) runCommand(picker.command, [row.key]);
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
if (!(key.ctrl && char === "d")) return;
|
|
3495
|
+
}
|
|
3496
|
+
if (operationInput && key.escape) {
|
|
3497
|
+
setOperationInput(null);
|
|
3498
|
+
setLine(EMPTY_LINE);
|
|
3499
|
+
setNotice(void 0);
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
if (searchResult && key.ctrl && (char === "n" || char === "p")) {
|
|
3503
|
+
const matches2 = streamMatches(state, searchResult.query, layout.streamWidth);
|
|
3504
|
+
if (matches2.length === 0) {
|
|
3505
|
+
setSearchResult(null);
|
|
3506
|
+
setNotice(`no matches for ${searchResult.query}`);
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
const index = (searchResult.index + (char === "n" ? 1 : -1) + matches2.length) % matches2.length;
|
|
3510
|
+
setSearchResult({ ...searchResult, index });
|
|
3511
|
+
dispatch({
|
|
3512
|
+
type: "stream:search",
|
|
3513
|
+
query: searchResult.query,
|
|
3514
|
+
match: index,
|
|
3515
|
+
viewport: layout.logViewport,
|
|
3516
|
+
width: layout.streamWidth
|
|
3517
|
+
});
|
|
3518
|
+
setNotice(`match ${index + 1}/${matches2.length} \xB7 ctrl+n next \xB7 ctrl+p previous`);
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3441
3521
|
if (key.ctrl && char === "d") {
|
|
3442
3522
|
if (!line.text) void shutdown();
|
|
3443
3523
|
return;
|
|
3444
3524
|
}
|
|
3445
3525
|
if (key.ctrl && char === "l") {
|
|
3446
3526
|
dispatch({ type: "stream:clear" });
|
|
3447
|
-
setNotice(
|
|
3527
|
+
setNotice(state.focusId ? `cleared ${state.focusId}` : "nothing focused");
|
|
3448
3528
|
return;
|
|
3449
3529
|
}
|
|
3450
3530
|
if (key.ctrl && char === "a") {
|
|
@@ -3455,9 +3535,16 @@ function App({
|
|
|
3455
3535
|
setLine((l) => ({ ...l, cursor: l.text.length }));
|
|
3456
3536
|
return;
|
|
3457
3537
|
}
|
|
3458
|
-
const formFront = front?.kind === "question" && !
|
|
3538
|
+
const formFront = front?.kind === "question" && !operationInput ? front : void 0;
|
|
3459
3539
|
if (key.tab && formFront === void 0 && !paletteOpen) {
|
|
3460
|
-
|
|
3540
|
+
if (key.shift) {
|
|
3541
|
+
const next = permissionMode === "auto" ? "ask" : "auto";
|
|
3542
|
+
setPermissionMode(next);
|
|
3543
|
+
setNotice(`permission mode: ${next}`);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
setSearchResult(null);
|
|
3547
|
+
dispatch({ type: "focus:cycle", delta: 1 });
|
|
3461
3548
|
return;
|
|
3462
3549
|
}
|
|
3463
3550
|
if (paletteOpen) {
|
|
@@ -3489,7 +3576,7 @@ function App({
|
|
|
3489
3576
|
return;
|
|
3490
3577
|
}
|
|
3491
3578
|
}
|
|
3492
|
-
if (front?.kind === "permission" && line.text === "" && !menu.released) {
|
|
3579
|
+
if (!operationInput && front?.kind === "permission" && line.text === "" && !menu.released) {
|
|
3493
3580
|
const total = permissionChoices(front.request).length;
|
|
3494
3581
|
if (key.upArrow || key.downArrow) {
|
|
3495
3582
|
setPermissionMenu({
|
|
@@ -3525,8 +3612,12 @@ function App({
|
|
|
3525
3612
|
if (draft !== void 0) setLine({ text: draft, cursor: draft.length });
|
|
3526
3613
|
};
|
|
3527
3614
|
const moveFocus = (index, state2 = committed) => {
|
|
3528
|
-
const
|
|
3529
|
-
save(
|
|
3615
|
+
const target = (index + total + 3) % (total + 3);
|
|
3616
|
+
if (target >= total) save({ ...state2, field: target }, "");
|
|
3617
|
+
else {
|
|
3618
|
+
const next = focusField(questionFields, state2, target);
|
|
3619
|
+
save(next.state, next.draft);
|
|
3620
|
+
}
|
|
3530
3621
|
};
|
|
3531
3622
|
const submit2 = (state2) => {
|
|
3532
3623
|
if (!formComplete(questionFields, state2)) {
|
|
@@ -3548,15 +3639,29 @@ function App({
|
|
|
3548
3639
|
setNotice(`answered ${front2.shortId}`);
|
|
3549
3640
|
};
|
|
3550
3641
|
const advance = (state2) => {
|
|
3551
|
-
|
|
3552
|
-
else moveFocus(form.field + 1, state2);
|
|
3642
|
+
moveFocus(form.field + 1, state2);
|
|
3553
3643
|
};
|
|
3554
3644
|
if (field === void 0) {
|
|
3555
|
-
|
|
3645
|
+
const action = Math.max(0, form.field - total);
|
|
3646
|
+
if (key.tab || key.leftArrow || key.rightArrow || key.upArrow || key.downArrow) {
|
|
3647
|
+
const delta = key.leftArrow || key.upArrow || key.tab && key.shift ? -1 : 1;
|
|
3648
|
+
moveFocus(form.field + delta);
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
if (key.return) {
|
|
3652
|
+
if (action === 0) submit2(committed);
|
|
3653
|
+
else {
|
|
3654
|
+
front2.settle({ action: action === 1 ? "decline" : "cancel" });
|
|
3655
|
+
setFormEntry({ id: "", state: initialForm() });
|
|
3656
|
+
setLine(EMPTY_LINE);
|
|
3657
|
+
setNotice(action === 1 ? "question skipped" : "question cancelled");
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3556
3660
|
return;
|
|
3557
3661
|
}
|
|
3558
3662
|
if (key.tab) {
|
|
3559
|
-
|
|
3663
|
+
const next = field.kind === "choice" ? commitField(questionFields, committed) : committed;
|
|
3664
|
+
moveFocus(key.shift ? form.field - 1 : form.field + 1, next);
|
|
3560
3665
|
return;
|
|
3561
3666
|
}
|
|
3562
3667
|
if (field.choices.length > 0 && (key.upArrow || key.downArrow)) {
|
|
@@ -3660,12 +3765,8 @@ function App({
|
|
|
3660
3765
|
setError(void 0);
|
|
3661
3766
|
return;
|
|
3662
3767
|
}
|
|
3663
|
-
if (state.view) {
|
|
3664
|
-
dispatch({ type: "view:close" });
|
|
3665
|
-
return;
|
|
3666
|
-
}
|
|
3667
3768
|
if (front !== void 0) {
|
|
3668
|
-
setPermissionMenu({ id: front.shortId, cursor:
|
|
3769
|
+
setPermissionMenu({ id: front.shortId, cursor: 0, released: !menu.released });
|
|
3669
3770
|
}
|
|
3670
3771
|
setLine(EMPTY_LINE);
|
|
3671
3772
|
setNotice(void 0);
|
|
@@ -3684,17 +3785,42 @@ function App({
|
|
|
3684
3785
|
const handlerRef = useRef(handleKey);
|
|
3685
3786
|
handlerRef.current = handleKey;
|
|
3686
3787
|
useInput((char, key) => handlerRef.current(char, key));
|
|
3687
|
-
const keys = helpOpen ? "esc close" : sessionsOpen ? "\u2191\u2193 select \xB7 enter open \xB7 d delete \xB7 esc close" : argOpen ? `\u2191\u2193 select \xB7 tab complete \xB7 enter ${arg?.command.name} \xB7 esc close` : menuOpen ? "\u2191\u2193 select \xB7 tab complete \xB7 enter run \xB7 esc close" : running ? checkpointResolveRef.current ? `/continue \xB7
|
|
3688
|
-
//
|
|
3689
|
-
"
|
|
3690
|
-
) : interactions[0].kind === "question" ? "tab field \xB7 \u2191\u2193 pick \xB7 enter
|
|
3691
|
-
const placeholder = frontQuestion !== void 0 ? formField === void 0 ? "enter to answer" : editingField !== void 0 ? `${editingField.title}${editingField.required ? " *" : ""} \u2014 enter for the next field` : `${formField.title} \u2014 \u2191\u2193 to pick \xB7 enter for the next field` : front?.kind === "permission" && !menu.released ? "choose above \xB7 esc to type here instead" : checkpointResolveRef.current ? `checkpoint: ${checkpointLabelRef.current} \u2014 type feedback or /continue` : running ? RUNNING_PLACEHOLDER : awaitingAnswer ? "answer the leader\u2026 \xB7 /
|
|
3788
|
+
const keys = picker ? "\u2191\u2193 select \xB7 enter confirm \xB7 esc cancel" : operationInput ? "enter submit \xB7 esc cancel" : helpOpen ? "esc close" : sessionsOpen ? "\u2191\u2193 select \xB7 enter open \xB7 d delete \xB7 esc close" : argOpen ? `\u2191\u2193 select \xB7 tab complete \xB7 enter ${arg?.command.name} \xB7 esc close` : menuOpen ? "\u2191\u2193 select \xB7 tab complete \xB7 enter run \xB7 esc close" : running ? checkpointResolveRef.current ? `/continue \xB7 type feedback \xB7 ctrl+c cancel` : interactions.length > 0 ? layout.interactionRows === 0 ? (
|
|
3789
|
+
// On short terminals the decision is shown in the body.
|
|
3790
|
+
"\u2191\u2193 choose \xB7 enter confirm \xB7 esc type \xB7 ctrl+c cancel"
|
|
3791
|
+
) : interactions[0].kind === "question" ? "tab field / actions \xB7 \u2191\u2193 pick \xB7 enter confirm" : menu.released ? "esc return to permission options" : "\u2191\u2193 or 1-9 choose \xB7 enter confirm \xB7 esc to type" : "enter queues \xB7 ctrl+c cancel" : state.reply ? "enter sends \xB7 / commands \xB7 ? help" : "tab switch \xB7 shift+tab mode \xB7 / commands \xB7 ? help";
|
|
3792
|
+
const placeholder = picker ? "choose an option above\u2026" : operationInput ? operationInput === "search" ? "search text\u2026" : "message for running agents\u2026" : frontQuestion !== void 0 ? formField === void 0 ? "enter to answer" : editingField !== void 0 ? `${editingField.title}${editingField.required ? " *" : ""} \u2014 enter for the next field` : `${formField.title} \u2014 \u2191\u2193 to pick \xB7 enter for the next field` : front?.kind === "permission" && !menu.released ? "choose above \xB7 esc to type here instead" : checkpointResolveRef.current ? `checkpoint: ${checkpointLabelRef.current} \u2014 type feedback or /continue` : running ? RUNNING_PLACEHOLDER : awaitingAnswer ? "answer the leader\u2026 \xB7 /new starts a new session" : INPUT_PLACEHOLDER;
|
|
3793
|
+
const renderInteraction = (maxRows) => {
|
|
3794
|
+
if (!front) return null;
|
|
3795
|
+
return front.kind === "permission" ? /* @__PURE__ */ jsx6(
|
|
3796
|
+
PermissionBar,
|
|
3797
|
+
{
|
|
3798
|
+
request: front.request,
|
|
3799
|
+
others: interactions.slice(1).flatMap((entry) => entry.kind === "permission" ? [entry.request] : []),
|
|
3800
|
+
cursor: menu.cursor,
|
|
3801
|
+
released: menu.released,
|
|
3802
|
+
width: layout.columns,
|
|
3803
|
+
maxRows
|
|
3804
|
+
}
|
|
3805
|
+
) : /* @__PURE__ */ jsx6(
|
|
3806
|
+
QuestionForm,
|
|
3807
|
+
{
|
|
3808
|
+
request: front.request,
|
|
3809
|
+
others: interactions.slice(1).flatMap((entry) => entry.kind === "question" ? [entry.request] : []),
|
|
3810
|
+
fields: questionFields,
|
|
3811
|
+
form,
|
|
3812
|
+
draft: line.text,
|
|
3813
|
+
width: layout.columns,
|
|
3814
|
+
maxRows
|
|
3815
|
+
}
|
|
3816
|
+
);
|
|
3817
|
+
};
|
|
3692
3818
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: layout.rows, children: [
|
|
3693
3819
|
/* @__PURE__ */ jsx6(
|
|
3694
3820
|
Header,
|
|
3695
3821
|
{
|
|
3696
3822
|
name: state.name || config.name,
|
|
3697
|
-
right:
|
|
3823
|
+
right: `turn ${state.turn} \xB7 ${running ? formatDuration(turnElapsed(state, now) ?? 0) : state.phase === "complete" ? "complete" : "ready"} \xB7 ${permissionMode}`
|
|
3698
3824
|
}
|
|
3699
3825
|
),
|
|
3700
3826
|
/* @__PURE__ */ jsx6(Rule, { width: layout.columns }),
|
|
@@ -3703,9 +3829,9 @@ function App({
|
|
|
3703
3829
|
// The rail wraps, so it can be taller than one row. Cap it at the height
|
|
3704
3830
|
// the budget reserved and clip, rather than letting the extra rows push
|
|
3705
3831
|
// the chrome below it off the screen.
|
|
3706
|
-
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, height: layout.railRows, overflow: "hidden", flexShrink: 0, children: /* @__PURE__ */ jsx6(PipelineRail, { turn:
|
|
3832
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, height: layout.railRows, overflow: "hidden", flexShrink: 0, children: /* @__PURE__ */ jsx6(PipelineRail, { turn: state, spinner }) })
|
|
3707
3833
|
) : null,
|
|
3708
|
-
|
|
3834
|
+
state.reason ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: ` reason: ${state.reason}` }) : null,
|
|
3709
3835
|
/* @__PURE__ */ jsx6(
|
|
3710
3836
|
Box6,
|
|
3711
3837
|
{
|
|
@@ -3724,20 +3850,20 @@ function App({
|
|
|
3724
3850
|
maxRows: layout.bodyHeight,
|
|
3725
3851
|
now
|
|
3726
3852
|
}
|
|
3727
|
-
) : layout.welcome ? /* @__PURE__ */ jsx6(WelcomePanel, { turn: state, width: layout.columns }) : error ? (
|
|
3853
|
+
) : front && layout.interactionRows === 0 && !paletteOpen ? renderInteraction(layout.bodyHeight) : layout.welcome ? /* @__PURE__ */ jsx6(WelcomePanel, { turn: state, width: layout.columns }) : error ? (
|
|
3728
3854
|
// The error wins over a reply: a run that stopped with an error is the
|
|
3729
3855
|
// most urgent thing on screen, and a stale answer underneath it would
|
|
3730
3856
|
// read as the outcome. The status line keeps a one-line version too.
|
|
3731
3857
|
/* @__PURE__ */ jsx6(ErrorPanel, { message: error, width: layout.columns })
|
|
3732
|
-
) :
|
|
3858
|
+
) : state.reply ? /* @__PURE__ */ jsx6(ReplyPanel, { reply: state.reply, width: layout.columns }) : /* @__PURE__ */ jsxs6(Box6, { height: layout.bodyHeight, flexShrink: 0, flexDirection: "row", children: [
|
|
3733
3859
|
layout.showTeam ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3734
|
-
/* @__PURE__ */ jsx6(TeamPanel, { turn:
|
|
3860
|
+
/* @__PURE__ */ jsx6(TeamPanel, { turn: state, width: layout.teamWidth, now, spinner }),
|
|
3735
3861
|
/* @__PURE__ */ jsx6(Divider, {})
|
|
3736
3862
|
] }) : null,
|
|
3737
3863
|
/* @__PURE__ */ jsx6(
|
|
3738
3864
|
StreamPanel,
|
|
3739
3865
|
{
|
|
3740
|
-
|
|
3866
|
+
state,
|
|
3741
3867
|
height: layout.bodyHeight,
|
|
3742
3868
|
width: layout.streamWidth,
|
|
3743
3869
|
now,
|
|
@@ -3747,7 +3873,6 @@ function App({
|
|
|
3747
3873
|
] })
|
|
3748
3874
|
}
|
|
3749
3875
|
),
|
|
3750
|
-
state.view ? /* @__PURE__ */ jsx6(Text6, { color: COLOR.warn, wrap: "truncate-end", children: ` viewing turn ${shown.turn} \xB7 ${shown.requirement ?? "(no requirement)"} \xB7 esc to return` }) : null,
|
|
3751
3876
|
notice || queuePaused && queue.length > 0 ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate-end", children: [
|
|
3752
3877
|
" ",
|
|
3753
3878
|
notice ?? (awaitingAnswer ? "queue paused \u2014 answer the leader first" : "queue paused \u2014 /continue resumes it")
|
|
@@ -3762,26 +3887,7 @@ function App({
|
|
|
3762
3887
|
overflow: "hidden",
|
|
3763
3888
|
flexShrink: 0,
|
|
3764
3889
|
flexDirection: "column",
|
|
3765
|
-
children:
|
|
3766
|
-
PermissionBar,
|
|
3767
|
-
{
|
|
3768
|
-
request: front.request,
|
|
3769
|
-
others: interactions.slice(1).flatMap((entry) => entry.kind === "permission" ? [entry.request] : []),
|
|
3770
|
-
cursor: menu.cursor,
|
|
3771
|
-
released: menu.released,
|
|
3772
|
-
width: layout.columns
|
|
3773
|
-
}
|
|
3774
|
-
) : /* @__PURE__ */ jsx6(
|
|
3775
|
-
QuestionForm,
|
|
3776
|
-
{
|
|
3777
|
-
request: front.request,
|
|
3778
|
-
others: interactions.slice(1).flatMap((entry) => entry.kind === "question" ? [entry.request] : []),
|
|
3779
|
-
fields: questionFields,
|
|
3780
|
-
form,
|
|
3781
|
-
draft: line.text,
|
|
3782
|
-
width: layout.columns
|
|
3783
|
-
}
|
|
3784
|
-
)
|
|
3890
|
+
children: renderInteraction(layout.interactionRows)
|
|
3785
3891
|
}
|
|
3786
3892
|
)
|
|
3787
3893
|
] }),
|
|
@@ -3881,8 +3987,9 @@ export {
|
|
|
3881
3987
|
seedAgents,
|
|
3882
3988
|
stageStatus,
|
|
3883
3989
|
startTui,
|
|
3990
|
+
streamMatches,
|
|
3884
3991
|
stripAnsi,
|
|
3885
3992
|
tuiReducer,
|
|
3886
3993
|
turnElapsed,
|
|
3887
|
-
|
|
3994
|
+
visibleTimeline
|
|
3888
3995
|
};
|