@multiclawcli/dashboard 0.6.3 → 0.8.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/dist/tui/components/interaction.d.ts +44 -0
- package/dist/tui/components/overlays.d.ts +0 -17
- package/dist/tui/components/prompt.d.ts +53 -2
- package/dist/tui/interaction.d.ts +173 -0
- package/dist/tui/layout.d.ts +35 -0
- package/dist/tui.js +771 -252
- package/package.json +2 -2
package/dist/tui.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
Orchestrator,
|
|
13
13
|
validateRuntimeQuestionContent
|
|
14
14
|
} from "@multiclawcli/core";
|
|
15
|
-
import { Box as
|
|
15
|
+
import { Box as Box6, Text as Text6, useApp, useInput, useWindowSize } from "ink";
|
|
16
16
|
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
17
17
|
|
|
18
18
|
// src/tui/atomic.ts
|
|
@@ -52,7 +52,7 @@ var COMMANDS = [
|
|
|
52
52
|
{ name: "clear", description: "Clear the focused agent's log", availability: "always" },
|
|
53
53
|
{
|
|
54
54
|
name: "history",
|
|
55
|
-
description: "Browse finished turns",
|
|
55
|
+
description: "Browse finished turns, or continue one",
|
|
56
56
|
availability: "always"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
@@ -127,7 +127,7 @@ var COMMANDS = [
|
|
|
127
127
|
{
|
|
128
128
|
name: "respond",
|
|
129
129
|
args: "<question id> <JSON object>",
|
|
130
|
-
description: "
|
|
130
|
+
description: "Answer a question in one line of JSON",
|
|
131
131
|
availability: "always"
|
|
132
132
|
},
|
|
133
133
|
{
|
|
@@ -144,14 +144,14 @@ var COMMANDS = [
|
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
name: "approve",
|
|
147
|
-
args: "
|
|
148
|
-
description: "
|
|
147
|
+
args: "[id] <number>",
|
|
148
|
+
description: "Approve the request on screen, or a named one",
|
|
149
149
|
availability: "always"
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
name: "deny",
|
|
153
|
-
args: "
|
|
154
|
-
description: "Deny a
|
|
153
|
+
args: "[id]",
|
|
154
|
+
description: "Deny the request on screen, or a named one",
|
|
155
155
|
availability: "always"
|
|
156
156
|
},
|
|
157
157
|
{ name: "help", description: "Show the key map", availability: "always" },
|
|
@@ -371,6 +371,8 @@ var RAIL_MIN_ROWS = 16;
|
|
|
371
371
|
var MAX_RAIL_ROWS = 2;
|
|
372
372
|
var MAX_MENU_ROWS = 7;
|
|
373
373
|
var MAX_QUEUE_ROWS = 3;
|
|
374
|
+
var MAX_INTERACTION_ROWS = 10;
|
|
375
|
+
var MIN_INTERACTION_ROWS = 4;
|
|
374
376
|
var MIN_BODY_ROWS = 3;
|
|
375
377
|
function measureRailRows(cells, columns) {
|
|
376
378
|
if (!cells || cells.length === 0) return 0;
|
|
@@ -396,14 +398,17 @@ function computeLayout(input) {
|
|
|
396
398
|
const showRail = !welcome && !input.overlay && rows >= RAIL_MIN_ROWS && input.hasRail;
|
|
397
399
|
const menuRows = Math.min(Math.max(0, input.menuRows), MAX_MENU_ROWS);
|
|
398
400
|
const queueRows = Math.min(Math.max(0, input.queueRows ?? 0), MAX_QUEUE_ROWS);
|
|
401
|
+
const promptRows = Math.max(1, input.promptRows ?? 1);
|
|
399
402
|
const railRows = showRail ? Math.min(MAX_RAIL_ROWS, Math.max(1, measureRailRows(input.railCells, columns))) : 0;
|
|
400
|
-
const
|
|
403
|
+
const chrome = 1 + // header
|
|
401
404
|
1 + // rule under the header
|
|
402
405
|
(railRows > 0 ? railRows + 1 : 0) + // pipeline rail plus the gap above it
|
|
403
406
|
1 + // gap before the body
|
|
404
407
|
(input.notice ? 1 : 0) + (input.viewing ? 1 : 0) + // "viewing turn N" banner
|
|
405
|
-
queueRows + menuRows +
|
|
406
|
-
1;
|
|
408
|
+
queueRows + menuRows + promptRows + 1;
|
|
409
|
+
const barRoom = Math.max(0, rows - chrome - MIN_BODY_ROWS - 1);
|
|
410
|
+
const interactionRows2 = barRoom < MIN_INTERACTION_ROWS ? 0 : Math.min(Math.max(0, input.interactionRows ?? 0), MAX_INTERACTION_ROWS, barRoom);
|
|
411
|
+
const reserved = chrome + (interactionRows2 > 0 ? interactionRows2 + 1 : 0);
|
|
407
412
|
const bodyHeight = Math.max(MIN_BODY_ROWS, rows - reserved);
|
|
408
413
|
const showTeam = !compact;
|
|
409
414
|
const teamWidth = Math.min(30, Math.max(18, Math.floor(columns * 0.24)));
|
|
@@ -423,7 +428,8 @@ function computeLayout(input) {
|
|
|
423
428
|
teamWidth,
|
|
424
429
|
streamWidth,
|
|
425
430
|
menuRows,
|
|
426
|
-
queueRows
|
|
431
|
+
queueRows,
|
|
432
|
+
interactionRows: interactionRows2
|
|
427
433
|
};
|
|
428
434
|
}
|
|
429
435
|
|
|
@@ -582,8 +588,8 @@ function QueueStrip({ items, max = MAX_QUEUE_ROWS }) {
|
|
|
582
588
|
)) });
|
|
583
589
|
}
|
|
584
590
|
|
|
585
|
-
// src/tui/components/
|
|
586
|
-
import { Box as
|
|
591
|
+
// src/tui/components/interaction.tsx
|
|
592
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
587
593
|
|
|
588
594
|
// src/tui/state.ts
|
|
589
595
|
var MAX_LOG_CHARS = 8e3;
|
|
@@ -1077,14 +1083,281 @@ function visibleLog(turn, viewport, width) {
|
|
|
1077
1083
|
};
|
|
1078
1084
|
}
|
|
1079
1085
|
|
|
1086
|
+
// src/tui/interaction.ts
|
|
1087
|
+
function clean(value) {
|
|
1088
|
+
return stripAnsi(value).replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim();
|
|
1089
|
+
}
|
|
1090
|
+
var KIND_LABEL = {
|
|
1091
|
+
allow_once: "allow once",
|
|
1092
|
+
allow_always: "always",
|
|
1093
|
+
reject_once: "deny",
|
|
1094
|
+
reject_always: "deny always"
|
|
1095
|
+
};
|
|
1096
|
+
function permissionChoices(request) {
|
|
1097
|
+
return request.options.slice(0, 9).map((option, index) => ({
|
|
1098
|
+
key: String(index + 1),
|
|
1099
|
+
optionId: option.optionId,
|
|
1100
|
+
label: clean(option.name) || KIND_LABEL[option.kind],
|
|
1101
|
+
// The one option with an effect that outlives this request.
|
|
1102
|
+
...option.kind === "allow_always" || option.kind === "reject_always" ? { note: "this session" } : {}
|
|
1103
|
+
}));
|
|
1104
|
+
}
|
|
1105
|
+
var MAX_IMPACT_ROWS = 2;
|
|
1106
|
+
function isRecord(value) {
|
|
1107
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1108
|
+
}
|
|
1109
|
+
function lineCount(text) {
|
|
1110
|
+
return text.split("\n").length;
|
|
1111
|
+
}
|
|
1112
|
+
function describeImpact(request, width) {
|
|
1113
|
+
const input = request.input;
|
|
1114
|
+
if (!isRecord(input)) return [];
|
|
1115
|
+
const room = Math.max(8, width);
|
|
1116
|
+
const command = typeof input.command === "string" ? input.command : void 0;
|
|
1117
|
+
if (command !== void 0) {
|
|
1118
|
+
const total = lineCount(command);
|
|
1119
|
+
return total > 1 ? [truncate(`${total} lines`, room)] : [];
|
|
1120
|
+
}
|
|
1121
|
+
const file = ["path", "file_path", "file", "filename"].map((key) => input[key]).find((value) => typeof value === "string");
|
|
1122
|
+
if (file === void 0) return [];
|
|
1123
|
+
const before = typeof input.old_string === "string" ? lineCount(input.old_string) : void 0;
|
|
1124
|
+
const after = typeof input.new_string === "string" ? lineCount(input.new_string) : void 0;
|
|
1125
|
+
if (before !== void 0 && after !== void 0) {
|
|
1126
|
+
return [truncate(`replaces ${before} line${before === 1 ? "" : "s"} with ${after}`, room)];
|
|
1127
|
+
}
|
|
1128
|
+
if (typeof input.content === "string") {
|
|
1129
|
+
const written = lineCount(input.content);
|
|
1130
|
+
return [truncate(`writes ${written} line${written === 1 ? "" : "s"}`, room)];
|
|
1131
|
+
}
|
|
1132
|
+
return [];
|
|
1133
|
+
}
|
|
1134
|
+
function moveCursor(cursor, delta, total) {
|
|
1135
|
+
if (total <= 0) return void 0;
|
|
1136
|
+
if (cursor === void 0) return delta > 0 ? 0 : total - 1;
|
|
1137
|
+
return (cursor + delta + total) % total;
|
|
1138
|
+
}
|
|
1139
|
+
function jumpCursor(digit, total) {
|
|
1140
|
+
if (!/^[1-9]$/.test(digit)) return void 0;
|
|
1141
|
+
const index = Number(digit) - 1;
|
|
1142
|
+
return index < total ? index : void 0;
|
|
1143
|
+
}
|
|
1144
|
+
function waitingLine(rest) {
|
|
1145
|
+
if (rest.length === 0) return void 0;
|
|
1146
|
+
const named = rest.slice(0, 3).map(
|
|
1147
|
+
(request) => `${clean(request.agentId)} (${clean(request.toolName ?? request.kind ?? "tool")})`
|
|
1148
|
+
);
|
|
1149
|
+
const more = rest.length - named.length;
|
|
1150
|
+
return `also waiting \xB7 ${named.join(", ")}${more > 0 ? ` \xB7 +${more} more` : ""}`;
|
|
1151
|
+
}
|
|
1152
|
+
function permissionBarRows(request, width, rest = []) {
|
|
1153
|
+
const impact = Math.min(MAX_IMPACT_ROWS, describeImpact(request, width).length);
|
|
1154
|
+
return 1 + 1 + impact + permissionChoices(request).length + (rest.length > 0 ? 1 : 0) + 1;
|
|
1155
|
+
}
|
|
1156
|
+
function choiceFrom(value) {
|
|
1157
|
+
if (typeof value === "string") return { value, title: value };
|
|
1158
|
+
if (!isRecord(value) || typeof value.const !== "string") return void 0;
|
|
1159
|
+
const title = typeof value.title === "string" && value.title !== "" ? clean(value.title) : value.const;
|
|
1160
|
+
const description = typeof value.description === "string" ? clean(value.description) : void 0;
|
|
1161
|
+
return { value: value.const, title, ...description ? { description } : {} };
|
|
1162
|
+
}
|
|
1163
|
+
function fieldChoices(field) {
|
|
1164
|
+
const items = isRecord(field.items) ? field.items : void 0;
|
|
1165
|
+
const raw = field.oneOf ?? field.enum ?? items?.anyOf ?? items?.enum;
|
|
1166
|
+
if (!Array.isArray(raw)) return [];
|
|
1167
|
+
return raw.map(choiceFrom).filter((choice) => choice !== void 0);
|
|
1168
|
+
}
|
|
1169
|
+
function bound(value) {
|
|
1170
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1171
|
+
}
|
|
1172
|
+
function fieldBounds(field) {
|
|
1173
|
+
return {
|
|
1174
|
+
min: bound(field.minimum),
|
|
1175
|
+
max: bound(field.maximum),
|
|
1176
|
+
minItems: bound(field.minItems),
|
|
1177
|
+
maxItems: bound(field.maxItems),
|
|
1178
|
+
minLength: bound(field.minLength),
|
|
1179
|
+
maxLength: bound(field.maxLength)
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function formFields(request) {
|
|
1183
|
+
const schema = request.requestedSchema;
|
|
1184
|
+
const required = schema.required ?? [];
|
|
1185
|
+
return Object.entries(schema.properties ?? {}).map(([key, raw]) => {
|
|
1186
|
+
const field = isRecord(raw) ? raw : {};
|
|
1187
|
+
const choices = fieldChoices(field);
|
|
1188
|
+
const type = field.type;
|
|
1189
|
+
const kind = type === "boolean" ? "boolean" : type === "number" || type === "integer" ? "number" : type === "array" ? "multi" : choices.length > 0 ? "choice" : "text";
|
|
1190
|
+
const title = typeof field.title === "string" && field.title !== "" ? clean(field.title) : key;
|
|
1191
|
+
const description = typeof field.description === "string" ? clean(field.description) : void 0;
|
|
1192
|
+
return {
|
|
1193
|
+
key,
|
|
1194
|
+
title,
|
|
1195
|
+
required: required.includes(key),
|
|
1196
|
+
kind,
|
|
1197
|
+
choices,
|
|
1198
|
+
bounds: fieldBounds(field),
|
|
1199
|
+
...description === void 0 ? {} : { description }
|
|
1200
|
+
};
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
function boundsText(field) {
|
|
1204
|
+
const span = (low, high, unit = "") => {
|
|
1205
|
+
if (low !== void 0 && high !== void 0) return `${low}\u2013${high}${unit}`;
|
|
1206
|
+
if (low !== void 0) return `min ${low}${unit}`;
|
|
1207
|
+
if (high !== void 0) return `max ${high}${unit}`;
|
|
1208
|
+
return void 0;
|
|
1209
|
+
};
|
|
1210
|
+
const { min, max, minItems, maxItems, minLength, maxLength } = field.bounds;
|
|
1211
|
+
if (field.kind === "multi") return span(minItems, maxItems);
|
|
1212
|
+
if (field.kind === "number") return span(min, max);
|
|
1213
|
+
if (field.kind === "text" || field.kind === "choice") return span(minLength, maxLength, " chars");
|
|
1214
|
+
return void 0;
|
|
1215
|
+
}
|
|
1216
|
+
function initialForm() {
|
|
1217
|
+
return { field: 0, picks: {}, values: {} };
|
|
1218
|
+
}
|
|
1219
|
+
function formValueText(field, state) {
|
|
1220
|
+
const value = state.values[field.key];
|
|
1221
|
+
if (value === void 0) return "";
|
|
1222
|
+
if (field.kind === "boolean") return value === true ? "yes" : "no";
|
|
1223
|
+
const title = (entry) => field.choices.find((choice) => choice.value === entry)?.title ?? entry;
|
|
1224
|
+
if (field.kind === "multi") {
|
|
1225
|
+
const picked = Array.isArray(value) ? value : [];
|
|
1226
|
+
return picked.map(title).join(", ");
|
|
1227
|
+
}
|
|
1228
|
+
const text = typeof value === "string" ? value : "";
|
|
1229
|
+
return field.kind === "choice" ? title(text) : text;
|
|
1230
|
+
}
|
|
1231
|
+
function pickFor(state, field) {
|
|
1232
|
+
const pick = state.picks[field.key] ?? 0;
|
|
1233
|
+
return Math.min(Math.max(0, pick), Math.max(0, field.choices.length - 1));
|
|
1234
|
+
}
|
|
1235
|
+
function movePick(state, field, delta) {
|
|
1236
|
+
const total = field.choices.length;
|
|
1237
|
+
if (total === 0) return state;
|
|
1238
|
+
const next = (pickFor(state, field) + delta + total) % total;
|
|
1239
|
+
return { ...state, picks: { ...state.picks, [field.key]: next } };
|
|
1240
|
+
}
|
|
1241
|
+
function jumpPick(state, field, digit) {
|
|
1242
|
+
if (!/^[1-9]$/.test(digit)) return state;
|
|
1243
|
+
const index = Number(digit) - 1;
|
|
1244
|
+
if (index >= field.choices.length) return state;
|
|
1245
|
+
return { ...state, picks: { ...state.picks, [field.key]: index } };
|
|
1246
|
+
}
|
|
1247
|
+
function toggleChoice(state, field) {
|
|
1248
|
+
if (field.kind !== "multi" || field.choices.length === 0) return state;
|
|
1249
|
+
const value = field.choices[pickFor(state, field)].value;
|
|
1250
|
+
const current = Array.isArray(state.values[field.key]) ? state.values[field.key] : [];
|
|
1251
|
+
const next = current.includes(value) ? current.filter((entry) => entry !== value) : [...current, value];
|
|
1252
|
+
return { ...state, values: { ...state.values, [field.key]: next } };
|
|
1253
|
+
}
|
|
1254
|
+
function toggleBoolean(state, field) {
|
|
1255
|
+
if (field.kind !== "boolean") return state;
|
|
1256
|
+
return {
|
|
1257
|
+
...state,
|
|
1258
|
+
values: { ...state.values, [field.key]: state.values[field.key] !== true }
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
function withDraft(fields, state, draft) {
|
|
1262
|
+
const field = fields[state.field];
|
|
1263
|
+
if (!field || field.kind !== "text" && field.kind !== "number") return state;
|
|
1264
|
+
return { ...state, values: { ...state.values, [field.key]: draft } };
|
|
1265
|
+
}
|
|
1266
|
+
function focusField(fields, state, index) {
|
|
1267
|
+
if (fields.length === 0) return { state, draft: "" };
|
|
1268
|
+
const next = Math.min(Math.max(0, index), fields.length - 1);
|
|
1269
|
+
const field = fields[next];
|
|
1270
|
+
const value = state.values[field.key];
|
|
1271
|
+
return {
|
|
1272
|
+
state: { ...state, field: next },
|
|
1273
|
+
draft: typeof value === "string" ? value : ""
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function commitField(fields, state) {
|
|
1277
|
+
const field = fields[state.field];
|
|
1278
|
+
if (field?.kind !== "choice" || field.choices.length === 0) return state;
|
|
1279
|
+
const value = field.choices[pickFor(state, field)].value;
|
|
1280
|
+
return { ...state, values: { ...state.values, [field.key]: value } };
|
|
1281
|
+
}
|
|
1282
|
+
function formContent(fields, state) {
|
|
1283
|
+
const content = {};
|
|
1284
|
+
for (const field of fields) {
|
|
1285
|
+
const value = state.values[field.key];
|
|
1286
|
+
if (value === void 0) continue;
|
|
1287
|
+
if (field.kind === "boolean") {
|
|
1288
|
+
content[field.key] = value === true;
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (field.kind === "multi") {
|
|
1292
|
+
content[field.key] = Array.isArray(value) ? value : [];
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
const text = typeof value === "string" ? value : "";
|
|
1296
|
+
if (text.trim() === "") continue;
|
|
1297
|
+
if (field.kind === "number") {
|
|
1298
|
+
const parsed = Number(text.trim());
|
|
1299
|
+
if (Number.isFinite(parsed)) content[field.key] = parsed;
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
content[field.key] = text;
|
|
1303
|
+
}
|
|
1304
|
+
return content;
|
|
1305
|
+
}
|
|
1306
|
+
function fieldComplete(field, state) {
|
|
1307
|
+
const value = state.values[field.key];
|
|
1308
|
+
if (field.kind === "boolean") return typeof value === "boolean";
|
|
1309
|
+
if (field.kind === "multi") {
|
|
1310
|
+
const picked = Array.isArray(value) ? value : [];
|
|
1311
|
+
if (value === void 0) return !field.required;
|
|
1312
|
+
if (field.required && picked.length === 0) return false;
|
|
1313
|
+
const { minItems, maxItems } = field.bounds;
|
|
1314
|
+
if (minItems !== void 0 && picked.length < minItems) return false;
|
|
1315
|
+
if (maxItems !== void 0 && picked.length > maxItems) return false;
|
|
1316
|
+
return true;
|
|
1317
|
+
}
|
|
1318
|
+
if (field.kind === "number") {
|
|
1319
|
+
const text2 = typeof value === "string" ? value.trim() : "";
|
|
1320
|
+
if (text2 === "") return !field.required;
|
|
1321
|
+
const parsed = Number(text2);
|
|
1322
|
+
if (!Number.isFinite(parsed)) return false;
|
|
1323
|
+
const { min, max } = field.bounds;
|
|
1324
|
+
if (min !== void 0 && parsed < min) return false;
|
|
1325
|
+
if (max !== void 0 && parsed > max) return false;
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1328
|
+
const text = typeof value === "string" ? value : "";
|
|
1329
|
+
if (text.trim() === "") return !field.required;
|
|
1330
|
+
const { minLength, maxLength } = field.bounds;
|
|
1331
|
+
if (minLength !== void 0 && text.length < minLength) return false;
|
|
1332
|
+
if (maxLength !== void 0 && text.length > maxLength) return false;
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
function formComplete(fields, state) {
|
|
1336
|
+
return fields.every((field) => fieldComplete(field, state));
|
|
1337
|
+
}
|
|
1338
|
+
var MAX_FORM_FIELDS = 6;
|
|
1339
|
+
function questionBarRows(fields) {
|
|
1340
|
+
return 2 + Math.min(fields.length, MAX_FORM_FIELDS) + 1 + 1;
|
|
1341
|
+
}
|
|
1342
|
+
function interactionRows(interactions, width) {
|
|
1343
|
+
const front = interactions[0];
|
|
1344
|
+
if (!front) return 0;
|
|
1345
|
+
if (front.kind === "permission") {
|
|
1346
|
+
const rest = interactions.slice(1).map((entry) => entry.request).filter((request) => "options" in request);
|
|
1347
|
+
return permissionBarRows(front.request, width, rest);
|
|
1348
|
+
}
|
|
1349
|
+
return questionBarRows(formFields(front.request));
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1080
1352
|
// src/tui/components/overlays.tsx
|
|
1353
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1081
1354
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1082
1355
|
var HELP_GROUPS = [
|
|
1083
1356
|
{
|
|
1084
1357
|
title: "prompt",
|
|
1085
1358
|
rows: [
|
|
1086
1359
|
["enter", "Run the requirement"],
|
|
1087
|
-
["enter (busy)", "Queue only;
|
|
1360
|
+
["enter (busy)", "Queue only; the bar above decides tools and questions"],
|
|
1088
1361
|
["esc", "Clear the line, close an overlay"],
|
|
1089
1362
|
["\u2190 \u2192 home end", "Move the caret"]
|
|
1090
1363
|
]
|
|
@@ -1139,95 +1412,6 @@ function HelpOverlay({ width, maxRows }) {
|
|
|
1139
1412
|
/* @__PURE__ */ jsx2(Box2, { marginTop: gap, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "esc or ? to close" }) })
|
|
1140
1413
|
] });
|
|
1141
1414
|
}
|
|
1142
|
-
function PermissionOverlay({
|
|
1143
|
-
request,
|
|
1144
|
-
shortId,
|
|
1145
|
-
queued,
|
|
1146
|
-
width,
|
|
1147
|
-
maxRows
|
|
1148
|
-
}) {
|
|
1149
|
-
const clean = (value) => stripAnsi(value).replace(/\p{Cc}/gu, " ");
|
|
1150
|
-
const input = request.input === void 0 ? "(no input)" : JSON.stringify(request.input) ?? "(no input)";
|
|
1151
|
-
const inputRows = Math.max(1, Math.min(3, maxRows - request.options.length - 7));
|
|
1152
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1153
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color: COLOR.warn, wrap: "truncate-end", children: `PERMISSION ${shortId} \xB7 ${queued} pending \xB7 ${clean(request.agentId)}` }),
|
|
1154
|
-
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: clean(request.title) }),
|
|
1155
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: `request ${clean(request.requestId)} \xB7 ${clean(request.toolName ?? request.kind ?? request.toolCallId)}` }),
|
|
1156
|
-
/* @__PURE__ */ jsx2(Text2, { color: COLOR.accent, wrap: "truncate-end", children: `/approve ${shortId} <number> \xB7 /deny ${shortId}` }),
|
|
1157
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: "Ordinary text queues a follow-up; it never grants permission." }),
|
|
1158
|
-
/* @__PURE__ */ jsx2(Box2, { height: inputRows, overflow: "hidden", flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { children: `input: ${clean(input)}` }) }),
|
|
1159
|
-
request.options.map((option, index) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: `${index + 1}. ${clean(option.name)} (${option.kind})` }, option.optionId))
|
|
1160
|
-
] });
|
|
1161
|
-
}
|
|
1162
|
-
function questionLines(request, width) {
|
|
1163
|
-
const lines = [request.message, `request ${request.requestId}`];
|
|
1164
|
-
for (const [key, field] of Object.entries(request.requestedSchema.properties ?? {})) {
|
|
1165
|
-
const schema = field;
|
|
1166
|
-
const items = schema.items;
|
|
1167
|
-
const choices = schema.oneOf ?? schema.enum ?? items?.anyOf ?? items?.enum;
|
|
1168
|
-
lines.push(
|
|
1169
|
-
`${key}${request.requestedSchema.required?.includes(key) ? " *" : ""}: ${typeof schema.title === "string" ? schema.title : key} (${field.type})`
|
|
1170
|
-
);
|
|
1171
|
-
if (typeof schema.description === "string") lines.push(schema.description);
|
|
1172
|
-
const limits = [
|
|
1173
|
-
"minimum",
|
|
1174
|
-
"maximum",
|
|
1175
|
-
"exclusiveMinimum",
|
|
1176
|
-
"exclusiveMaximum",
|
|
1177
|
-
"minLength",
|
|
1178
|
-
"maxLength",
|
|
1179
|
-
"minItems",
|
|
1180
|
-
"maxItems",
|
|
1181
|
-
"uniqueItems"
|
|
1182
|
-
].filter((limit) => schema[limit] !== void 0 && schema[limit] !== null).map((limit) => `${limit}=${schema[limit]}`);
|
|
1183
|
-
if (limits.length) lines.push(limits.join(" \xB7 "));
|
|
1184
|
-
for (const choice of choices ?? []) {
|
|
1185
|
-
lines.push(
|
|
1186
|
-
typeof choice === "string" ? JSON.stringify(choice) : `${JSON.stringify(choice.const)} \u2014 ${choice.title ?? choice.const}${choice.description ? `: ${choice.description}` : ""}`
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
return lines.flatMap(
|
|
1191
|
-
(line) => wrapLine(stripAnsi(line).replace(/\p{Cc}/gu, " "), Math.max(1, width - 2))
|
|
1192
|
-
);
|
|
1193
|
-
}
|
|
1194
|
-
function QuestionOverlay({
|
|
1195
|
-
request,
|
|
1196
|
-
shortId,
|
|
1197
|
-
queued,
|
|
1198
|
-
width,
|
|
1199
|
-
maxRows,
|
|
1200
|
-
offset
|
|
1201
|
-
}) {
|
|
1202
|
-
const lines = questionLines(request, width);
|
|
1203
|
-
const capacity = Math.max(1, maxRows - 4);
|
|
1204
|
-
const start = Math.min(offset, Math.max(0, lines.length - capacity));
|
|
1205
|
-
const visible = lines.slice(start, start + capacity);
|
|
1206
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1207
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color: COLOR.warn, wrap: "truncate-end", children: `QUESTION ${shortId} \xB7 ${queued} pending \xB7 ${stripAnsi(request.agentId)}` }),
|
|
1208
|
-
/* @__PURE__ */ jsx2(Text2, { color: COLOR.accent, wrap: "truncate-end", children: `/respond ${shortId} <JSON object>` }),
|
|
1209
|
-
/* @__PURE__ */ jsx2(
|
|
1210
|
-
Text2,
|
|
1211
|
-
{
|
|
1212
|
-
dimColor: true,
|
|
1213
|
-
wrap: "truncate-end",
|
|
1214
|
-
children: `/skip ${shortId} declines \xB7 /dismiss ${shortId} cancels`
|
|
1215
|
-
}
|
|
1216
|
-
),
|
|
1217
|
-
visible.map((line, index) => (
|
|
1218
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: immutable text rows are identified by their absolute form offset.
|
|
1219
|
-
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: line }, `${start + index}-${line}`)
|
|
1220
|
-
)),
|
|
1221
|
-
/* @__PURE__ */ jsx2(
|
|
1222
|
-
Text2,
|
|
1223
|
-
{
|
|
1224
|
-
dimColor: true,
|
|
1225
|
-
wrap: "truncate-end",
|
|
1226
|
-
children: `\u2191\u2193 / pgup pgdn form \xB7 ${start + 1}\u2013${start + visible.length}/${lines.length} \xB7 ordinary text queues`
|
|
1227
|
-
}
|
|
1228
|
-
)
|
|
1229
|
-
] });
|
|
1230
|
-
}
|
|
1231
1415
|
function outcome(turn) {
|
|
1232
1416
|
if (turn.reply) {
|
|
1233
1417
|
return turn.reply.mode === "ask" ? { text: `${GLYPH.ask} needs input`, color: COLOR.warn } : { text: `${STATUS.success.glyph} answered`, color: COLOR.ok };
|
|
@@ -1300,13 +1484,114 @@ function HistoryOverlay({
|
|
|
1300
1484
|
] })
|
|
1301
1485
|
] }, turn.turn);
|
|
1302
1486
|
}) }),
|
|
1303
|
-
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: `\u2191\u2193 select \xB7 enter open \xB7 esc close${range}` }) })
|
|
1487
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: `\u2191\u2193 select \xB7 enter open \xB7 c continue \xB7 esc close${range}` }) })
|
|
1488
|
+
] });
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// src/tui/components/interaction.tsx
|
|
1492
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1493
|
+
function PermissionBar({
|
|
1494
|
+
request,
|
|
1495
|
+
others,
|
|
1496
|
+
cursor,
|
|
1497
|
+
released,
|
|
1498
|
+
width
|
|
1499
|
+
}) {
|
|
1500
|
+
const choices = permissionChoices(request);
|
|
1501
|
+
const impact = describeImpact(request, Math.max(8, width - 6));
|
|
1502
|
+
const also = waitingLine(others);
|
|
1503
|
+
const tool = clean(request.toolName ?? request.kind ?? "tool");
|
|
1504
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1505
|
+
/* @__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` : ""}` }),
|
|
1506
|
+
/* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: ` ${truncate(clean(request.title), Math.max(8, width - 4))}` }),
|
|
1507
|
+
impact.map((line) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` ${line}` }, line)),
|
|
1508
|
+
choices.map((choice, index) => {
|
|
1509
|
+
const active = index === cursor;
|
|
1510
|
+
return /* @__PURE__ */ jsx3(
|
|
1511
|
+
Text3,
|
|
1512
|
+
{
|
|
1513
|
+
color: active ? COLOR.accent : void 0,
|
|
1514
|
+
dimColor: !active,
|
|
1515
|
+
wrap: "truncate-end",
|
|
1516
|
+
children: `${active ? GLYPH.caret : " "} ${choice.key} ${choice.label}${choice.note ? ` \xB7 ${choice.note}` : ""}`
|
|
1517
|
+
},
|
|
1518
|
+
choice.optionId
|
|
1519
|
+
);
|
|
1520
|
+
}),
|
|
1521
|
+
also === void 0 ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` ${also}` }),
|
|
1522
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: released ? " typing \xB7 /approve 1 or /deny still reach it" : " \u2191\u2193 or 1-9 choose \xB7 enter confirm \xB7 esc to type instead" })
|
|
1523
|
+
] });
|
|
1524
|
+
}
|
|
1525
|
+
function QuestionForm({
|
|
1526
|
+
request,
|
|
1527
|
+
others,
|
|
1528
|
+
fields,
|
|
1529
|
+
form,
|
|
1530
|
+
draft,
|
|
1531
|
+
width
|
|
1532
|
+
}) {
|
|
1533
|
+
const focused = fields[form.field];
|
|
1534
|
+
const titleWidth = Math.min(
|
|
1535
|
+
24,
|
|
1536
|
+
Math.max(4, ...fields.map((field) => field.title.length + (field.required ? 2 : 0)))
|
|
1537
|
+
);
|
|
1538
|
+
const capacity = Math.min(fields.length, MAX_FORM_FIELDS);
|
|
1539
|
+
const start = listWindowStart(form.field, fields.length, Math.max(1, capacity));
|
|
1540
|
+
const visible = fields.slice(start, start + capacity);
|
|
1541
|
+
const range = fields.length > capacity ? ` \xB7 ${start + 1}\u2013${start + visible.length}/${fields.length}` : "";
|
|
1542
|
+
const detail = focused === void 0 ? [] : detailLines(focused, width);
|
|
1543
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1544
|
+
/* @__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` : ""}` }),
|
|
1545
|
+
/* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: clean(request.message) }),
|
|
1546
|
+
visible.map((field, offset) => {
|
|
1547
|
+
const index = start + offset;
|
|
1548
|
+
const active = index === form.field;
|
|
1549
|
+
const marker = field.required ? " *" : "";
|
|
1550
|
+
const value = fieldValue(field, form, draft, index === form.field);
|
|
1551
|
+
return /* @__PURE__ */ jsx3(
|
|
1552
|
+
Text3,
|
|
1553
|
+
{
|
|
1554
|
+
color: active ? COLOR.accent : void 0,
|
|
1555
|
+
dimColor: !active,
|
|
1556
|
+
wrap: "truncate-end",
|
|
1557
|
+
children: `${active ? GLYPH.caret : " "} ${`${field.title}${marker}`.padEnd(titleWidth)} ${value}`
|
|
1558
|
+
},
|
|
1559
|
+
field.key
|
|
1560
|
+
);
|
|
1561
|
+
}),
|
|
1562
|
+
detail.map((line) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, wrap: "truncate-end", children: ` ${line}` }, line)),
|
|
1563
|
+
/* @__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` })
|
|
1304
1564
|
] });
|
|
1305
1565
|
}
|
|
1566
|
+
function fieldValue(field, form, draft, focused) {
|
|
1567
|
+
if (focused && field.kind === "choice" && field.choices.length > 0) {
|
|
1568
|
+
return field.choices[pickFor(form, field)].title;
|
|
1569
|
+
}
|
|
1570
|
+
const editing = focused && (field.kind === "text" || field.kind === "number");
|
|
1571
|
+
const text = editing ? draft : formValueText(field, form);
|
|
1572
|
+
if (text !== "") return text;
|
|
1573
|
+
if (field.kind === "boolean") return "(unset)";
|
|
1574
|
+
return "(empty)";
|
|
1575
|
+
}
|
|
1576
|
+
function detailLines(field, width) {
|
|
1577
|
+
const room = Math.max(8, width - 6);
|
|
1578
|
+
const limits = boundsText(field);
|
|
1579
|
+
const suffix = limits === void 0 ? "" : ` \xB7 ${limits}`;
|
|
1580
|
+
if (field.choices.length > 0) {
|
|
1581
|
+
const listed = field.choices.map(
|
|
1582
|
+
(choice, index) => `${index + 1} ${choice.title}${choice.description ? ` \u2014 ${choice.description}` : ""}`
|
|
1583
|
+
).join(" ");
|
|
1584
|
+
return [truncate(`${listed}${suffix}`, room)];
|
|
1585
|
+
}
|
|
1586
|
+
const editing = field.kind === "text" || field.kind === "number";
|
|
1587
|
+
const hint = field.description ?? (editing ? "type below, enter to continue" : "");
|
|
1588
|
+
if (hint === "") return limits === void 0 ? [] : [`${limits}`];
|
|
1589
|
+
return [truncate(`${hint}${suffix}`, room)];
|
|
1590
|
+
}
|
|
1306
1591
|
|
|
1307
1592
|
// src/tui/components/panels.tsx
|
|
1308
|
-
import { Box as
|
|
1309
|
-
import { Fragment, jsx as
|
|
1593
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1594
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1310
1595
|
function rightColumn(agent, now) {
|
|
1311
1596
|
if (agent.status === "retrying" && agent.retry) {
|
|
1312
1597
|
return { text: `retry ${agent.retry.attempt}/${agent.retry.max}`, color: COLOR.warn };
|
|
@@ -1325,9 +1610,9 @@ function TeamRow({
|
|
|
1325
1610
|
const style = STATUS[agent.status];
|
|
1326
1611
|
const glyph = agent.status === "running" ? spinner : style.glyph;
|
|
1327
1612
|
const right = rightColumn(agent, now);
|
|
1328
|
-
return /* @__PURE__ */
|
|
1329
|
-
/* @__PURE__ */
|
|
1330
|
-
|
|
1613
|
+
return /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
1614
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 1, children: /* @__PURE__ */ jsxs4(
|
|
1615
|
+
Text4,
|
|
1331
1616
|
{
|
|
1332
1617
|
color: focused ? COLOR.accent : style.color,
|
|
1333
1618
|
dimColor: !focused && style.dim,
|
|
@@ -1339,14 +1624,14 @@ function TeamRow({
|
|
|
1339
1624
|
" ",
|
|
1340
1625
|
agent.icon ? `${agent.icon} ` : "",
|
|
1341
1626
|
agent.name,
|
|
1342
|
-
agent.taskTitle ? /* @__PURE__ */
|
|
1627
|
+
agent.taskTitle ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1343
1628
|
" ",
|
|
1344
1629
|
agent.taskTitle
|
|
1345
1630
|
] }) : null
|
|
1346
1631
|
]
|
|
1347
1632
|
}
|
|
1348
1633
|
) }),
|
|
1349
|
-
/* @__PURE__ */
|
|
1634
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { color: right.color, dimColor: right.color === void 0, children: right.text }) })
|
|
1350
1635
|
] });
|
|
1351
1636
|
}
|
|
1352
1637
|
function TeamPanel({
|
|
@@ -1357,16 +1642,16 @@ function TeamPanel({
|
|
|
1357
1642
|
}) {
|
|
1358
1643
|
const ids = Object.keys(turn.agents);
|
|
1359
1644
|
const { done, total } = progress(turn);
|
|
1360
|
-
return /* @__PURE__ */
|
|
1361
|
-
/* @__PURE__ */
|
|
1362
|
-
/* @__PURE__ */
|
|
1363
|
-
/* @__PURE__ */
|
|
1645
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, flexShrink: 0, paddingRight: 1, children: [
|
|
1646
|
+
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
1647
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "TEAM" }),
|
|
1648
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1364
1649
|
done,
|
|
1365
1650
|
"/",
|
|
1366
1651
|
total
|
|
1367
1652
|
] })
|
|
1368
1653
|
] }),
|
|
1369
|
-
ids.length === 0 ? /* @__PURE__ */
|
|
1654
|
+
ids.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "no agents configured" }) : ids.map((id) => /* @__PURE__ */ jsx4(
|
|
1370
1655
|
TeamRow,
|
|
1371
1656
|
{
|
|
1372
1657
|
agent: turn.agents[id],
|
|
@@ -1379,8 +1664,8 @@ function TeamPanel({
|
|
|
1379
1664
|
] });
|
|
1380
1665
|
}
|
|
1381
1666
|
function Divider() {
|
|
1382
|
-
return /* @__PURE__ */
|
|
1383
|
-
|
|
1667
|
+
return /* @__PURE__ */ jsx4(
|
|
1668
|
+
Box4,
|
|
1384
1669
|
{
|
|
1385
1670
|
flexShrink: 0,
|
|
1386
1671
|
borderStyle: "single",
|
|
@@ -1404,43 +1689,43 @@ function StreamPanel({
|
|
|
1404
1689
|
const window = visibleLog(turn, viewport, width);
|
|
1405
1690
|
const style = agent ? STATUS[agent.status] : void 0;
|
|
1406
1691
|
const elapsed = agent ? agentElapsed(agent, now) : void 0;
|
|
1407
|
-
return /* @__PURE__ */
|
|
1408
|
-
/* @__PURE__ */
|
|
1409
|
-
/* @__PURE__ */
|
|
1410
|
-
/* @__PURE__ */
|
|
1411
|
-
agent?.model ? /* @__PURE__ */
|
|
1692
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, height, paddingLeft: 1, children: [
|
|
1693
|
+
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
1694
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 1, children: /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate-end", children: [
|
|
1695
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: agent?.name ?? "\u2014" }),
|
|
1696
|
+
agent?.model ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1412
1697
|
" \xB7 ",
|
|
1413
1698
|
agent.model
|
|
1414
1699
|
] }) : null,
|
|
1415
|
-
agent && style ? /* @__PURE__ */
|
|
1416
|
-
/* @__PURE__ */
|
|
1417
|
-
/* @__PURE__ */
|
|
1700
|
+
agent && style ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1701
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \xB7 " }),
|
|
1702
|
+
/* @__PURE__ */ jsx4(Text4, { color: style.color, dimColor: style.dim, children: agent.status === "running" ? `${spinner} ${style.label}` : style.label })
|
|
1418
1703
|
] }) : null,
|
|
1419
|
-
agent?.taskTitle ? /* @__PURE__ */
|
|
1704
|
+
agent?.taskTitle ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1420
1705
|
" \xB7 ",
|
|
1421
1706
|
agent.taskTitle
|
|
1422
1707
|
] }) : null,
|
|
1423
|
-
agent?.usage?.contextTokens !== void 0 && agent.usage.contextSize !== void 0 ? /* @__PURE__ */
|
|
1708
|
+
agent?.usage?.contextTokens !== void 0 && agent.usage.contextSize !== void 0 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1424
1709
|
" ",
|
|
1425
1710
|
"\xB7 ctx ",
|
|
1426
1711
|
formatTokens(agent.usage.contextTokens),
|
|
1427
1712
|
"/",
|
|
1428
1713
|
formatTokens(agent.usage.contextSize)
|
|
1429
1714
|
] }) : null,
|
|
1430
|
-
agent?.costUsd !== void 0 ? /* @__PURE__ */
|
|
1715
|
+
agent?.costUsd !== void 0 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1431
1716
|
" \xB7 ",
|
|
1432
1717
|
formatCost(agent.costUsd)
|
|
1433
1718
|
] }) : null
|
|
1434
1719
|
] }) }),
|
|
1435
|
-
/* @__PURE__ */
|
|
1436
|
-
elapsed === void 0 ? null : /* @__PURE__ */
|
|
1720
|
+
/* @__PURE__ */ jsxs4(Box4, { flexShrink: 0, children: [
|
|
1721
|
+
elapsed === void 0 ? null : /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1437
1722
|
formatDuration(elapsed),
|
|
1438
1723
|
" "
|
|
1439
1724
|
] }),
|
|
1440
|
-
/* @__PURE__ */
|
|
1725
|
+
/* @__PURE__ */ jsx4(Text4, { color: window.following ? COLOR.accent : COLOR.warn, children: window.following ? GLYPH.live : `${GLYPH.paused} ${window.scrolledBack}\u2191` })
|
|
1441
1726
|
] })
|
|
1442
1727
|
] }),
|
|
1443
|
-
window.rows.length === 0 ? /* @__PURE__ */
|
|
1728
|
+
window.rows.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: turn.phase === "running" ? "waiting for output\u2026" : "no output yet" }) : (
|
|
1444
1729
|
// The tail, already wrapped to `width` by `visibleLog` — the pane draws
|
|
1445
1730
|
// rows, and one log line is several of them. `visibleLog` also picks
|
|
1446
1731
|
// which rows, so the content here is exactly the pane's height and the
|
|
@@ -1456,10 +1741,10 @@ function StreamPanel({
|
|
|
1456
1741
|
// `flexShrink={0}` stops Yoga compressing the rows into each other,
|
|
1457
1742
|
// and `overflow="hidden"` is the backstop if any of the above is ever
|
|
1458
1743
|
// wrong.
|
|
1459
|
-
/* @__PURE__ */
|
|
1744
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexShrink: 0, height: viewport, overflow: "hidden", children: window.rows.map((row, index) => (
|
|
1460
1745
|
// Rows are an append-only tail; position is the identity.
|
|
1461
1746
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional log tail
|
|
1462
|
-
/* @__PURE__ */
|
|
1747
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: row }) }, index)
|
|
1463
1748
|
)) })
|
|
1464
1749
|
)
|
|
1465
1750
|
] });
|
|
@@ -1470,14 +1755,14 @@ function ReplyPanel({ reply, width }) {
|
|
|
1470
1755
|
// `flexShrink={0}`: the body must clip this, not compress it. A shrunken
|
|
1471
1756
|
// column box folds its own text rows into each other before the body ever
|
|
1472
1757
|
// gets a chance to hide the overflow.
|
|
1473
|
-
/* @__PURE__ */
|
|
1474
|
-
/* @__PURE__ */
|
|
1758
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, paddingTop: 1, width, children: [
|
|
1759
|
+
/* @__PURE__ */ jsxs4(Text4, { color: asking ? COLOR.warn : COLOR.accent, bold: true, children: [
|
|
1475
1760
|
asking ? GLYPH.ask : GLYPH.leader,
|
|
1476
1761
|
" ",
|
|
1477
1762
|
asking ? "the team needs a decision" : "answered"
|
|
1478
1763
|
] }),
|
|
1479
|
-
/* @__PURE__ */
|
|
1480
|
-
/* @__PURE__ */
|
|
1764
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { children: reply.message }) }),
|
|
1765
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: asking ? "Answer below and press enter." : "Type a requirement and press enter." }) })
|
|
1481
1766
|
] })
|
|
1482
1767
|
);
|
|
1483
1768
|
}
|
|
@@ -1485,12 +1770,12 @@ function ErrorPanel({ message, width }) {
|
|
|
1485
1770
|
return (
|
|
1486
1771
|
// `flexShrink={0}` — see ReplyPanel. A compressed column box folds its own
|
|
1487
1772
|
// text rows into each other before the body gets a chance to clip them.
|
|
1488
|
-
/* @__PURE__ */
|
|
1489
|
-
/* @__PURE__ */
|
|
1773
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, paddingTop: 1, width, children: [
|
|
1774
|
+
/* @__PURE__ */ jsxs4(Text4, { color: COLOR.error, bold: true, children: [
|
|
1490
1775
|
GLYPH.warn,
|
|
1491
1776
|
" the run stopped"
|
|
1492
1777
|
] }),
|
|
1493
|
-
/* @__PURE__ */
|
|
1778
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: COLOR.error, children: message }) })
|
|
1494
1779
|
] })
|
|
1495
1780
|
);
|
|
1496
1781
|
}
|
|
@@ -1501,62 +1786,108 @@ function WelcomePanel({ turn, width }) {
|
|
|
1501
1786
|
return (
|
|
1502
1787
|
// `flexShrink={0}` — see ReplyPanel. Compressing the roster folds one agent's
|
|
1503
1788
|
// description onto the next row's, which reads as a corrupted screen.
|
|
1504
|
-
/* @__PURE__ */
|
|
1505
|
-
/* @__PURE__ */
|
|
1789
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, paddingTop: 1, width, children: [
|
|
1790
|
+
/* @__PURE__ */ jsxs4(Text4, { color: COLOR.accent, bold: true, children: [
|
|
1506
1791
|
GLYPH.brand,
|
|
1507
1792
|
" multiclaw"
|
|
1508
1793
|
] }),
|
|
1509
|
-
/* @__PURE__ */
|
|
1510
|
-
/* @__PURE__ */
|
|
1794
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "A team of agents, one prompt away." }) }),
|
|
1795
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: agents.map((agent) => {
|
|
1511
1796
|
const focused = turn.focusId === agent.id;
|
|
1512
1797
|
const leader = agent.id === leaderId;
|
|
1513
|
-
return /* @__PURE__ */
|
|
1514
|
-
/* @__PURE__ */
|
|
1515
|
-
/* @__PURE__ */
|
|
1516
|
-
/* @__PURE__ */
|
|
1517
|
-
/* @__PURE__ */
|
|
1798
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
1799
|
+
/* @__PURE__ */ jsxs4(Box4, { flexShrink: 0, children: [
|
|
1800
|
+
/* @__PURE__ */ jsx4(Text4, { color: focused ? COLOR.accent : void 0, children: focused ? `${GLYPH.caret} ` : " " }),
|
|
1801
|
+
/* @__PURE__ */ jsx4(Text4, { color: leader ? COLOR.accent : COLOR.muted, children: leader ? GLYPH.leader : GLYPH.agent }),
|
|
1802
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: focused, color: focused ? COLOR.accent : void 0, children: [
|
|
1518
1803
|
" ",
|
|
1519
1804
|
padRight(truncate(agent.name, nameWidth - 2), nameWidth - 1)
|
|
1520
1805
|
] })
|
|
1521
1806
|
] }),
|
|
1522
|
-
/* @__PURE__ */
|
|
1807
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate-end", children: agent.taskTitle ?? agent.description ?? "" }) })
|
|
1523
1808
|
] }, agent.id);
|
|
1524
1809
|
}) }),
|
|
1525
|
-
/* @__PURE__ */
|
|
1810
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Type a requirement and press enter." }) })
|
|
1526
1811
|
] })
|
|
1527
1812
|
);
|
|
1528
1813
|
}
|
|
1529
1814
|
|
|
1530
1815
|
// src/tui/components/prompt.tsx
|
|
1531
|
-
import { Box as
|
|
1532
|
-
import { jsx as
|
|
1816
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1817
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1818
|
+
var MARKER = `${GLYPH.caret} `;
|
|
1819
|
+
var MARKER_WIDTH = displayWidth(MARKER);
|
|
1820
|
+
var INDENT = " ".repeat(MARKER_WIDTH);
|
|
1821
|
+
var MAX_PROMPT_ROWS = 5;
|
|
1822
|
+
function promptLines(value, cursor, width) {
|
|
1823
|
+
const shown = value.replace(/\r/g, "").replace(/\n/g, GLYPH.newline);
|
|
1824
|
+
const limit = Math.max(1, width);
|
|
1825
|
+
const lines = [{ text: "" }];
|
|
1826
|
+
let col = 0;
|
|
1827
|
+
let offset = 0;
|
|
1828
|
+
const breakRow = () => {
|
|
1829
|
+
lines.push({ text: "" });
|
|
1830
|
+
col = 0;
|
|
1831
|
+
};
|
|
1832
|
+
for (const char of shown) {
|
|
1833
|
+
const charWidth = displayWidth(char);
|
|
1834
|
+
if (col > 0 && col + charWidth > limit) breakRow();
|
|
1835
|
+
const line = lines[lines.length - 1];
|
|
1836
|
+
if (offset === cursor) line.caret = line.text.length;
|
|
1837
|
+
line.text += char;
|
|
1838
|
+
col += charWidth;
|
|
1839
|
+
offset += char.length;
|
|
1840
|
+
}
|
|
1841
|
+
if (cursor >= offset) {
|
|
1842
|
+
if (col >= limit) breakRow();
|
|
1843
|
+
const line = lines[lines.length - 1];
|
|
1844
|
+
line.caret = line.text.length;
|
|
1845
|
+
}
|
|
1846
|
+
return lines;
|
|
1847
|
+
}
|
|
1848
|
+
function promptRowCount(value, cursor, width) {
|
|
1849
|
+
return Math.min(MAX_PROMPT_ROWS, promptLines(value, cursor, width).length);
|
|
1850
|
+
}
|
|
1533
1851
|
function Prompt({
|
|
1534
1852
|
value,
|
|
1535
1853
|
cursor,
|
|
1536
1854
|
caretVisible,
|
|
1537
|
-
placeholder
|
|
1855
|
+
placeholder,
|
|
1856
|
+
width
|
|
1538
1857
|
}) {
|
|
1539
|
-
const
|
|
1858
|
+
const textWidth = Math.max(1, width - MARKER_WIDTH);
|
|
1859
|
+
const lines = promptLines(value, cursor, textWidth);
|
|
1540
1860
|
const ghost = value === "";
|
|
1541
|
-
const
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1861
|
+
const hint = truncate(placeholder, textWidth);
|
|
1862
|
+
const caretRow = Math.max(
|
|
1863
|
+
0,
|
|
1864
|
+
lines.findIndex((line) => line.caret !== void 0)
|
|
1865
|
+
);
|
|
1866
|
+
const start = lines.length <= MAX_PROMPT_ROWS ? 0 : Math.max(0, Math.min(caretRow - (MAX_PROMPT_ROWS - 1), lines.length - MAX_PROMPT_ROWS));
|
|
1867
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", flexShrink: 0, children: lines.slice(start, start + MAX_PROMPT_ROWS).map((line, index) => {
|
|
1868
|
+
const caret = line.caret;
|
|
1869
|
+
const rest = caret === void 0 ? "" : line.text.slice(caret);
|
|
1870
|
+
const cell = [...ghost ? hint : rest][0] ?? " ";
|
|
1871
|
+
const tail2 = ghost ? [...hint].slice(1).join("") : rest.slice(cell.length);
|
|
1872
|
+
return (
|
|
1873
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: a text row has no identity beyond its position — the whole prompt is rebuilt on every keystroke anyway
|
|
1874
|
+
/* @__PURE__ */ jsxs5(Box5, { flexShrink: 0, children: [
|
|
1875
|
+
/* @__PURE__ */ jsx5(Text5, { color: COLOR.accent, children: start + index === 0 ? MARKER : INDENT }),
|
|
1876
|
+
caret === void 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children: line.text }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1877
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children: line.text.slice(0, caret) }),
|
|
1878
|
+
/* @__PURE__ */ jsx5(Text5, { inverse: caretVisible, dimColor: !caretVisible && ghost, children: cell }),
|
|
1879
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children: tail2 })
|
|
1880
|
+
] })
|
|
1881
|
+
] }, start + index)
|
|
1882
|
+
);
|
|
1883
|
+
}) });
|
|
1553
1884
|
}
|
|
1554
1885
|
function CommandMenu({ rows, selected }) {
|
|
1555
1886
|
const labelWidth = Math.max(1, ...rows.map((row) => row.label.length));
|
|
1556
|
-
return /* @__PURE__ */
|
|
1887
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", paddingLeft: 2, children: rows.map((row, index) => {
|
|
1557
1888
|
const active = index === selected;
|
|
1558
|
-
return /* @__PURE__ */
|
|
1559
|
-
|
|
1889
|
+
return /* @__PURE__ */ jsxs5(
|
|
1890
|
+
Text5,
|
|
1560
1891
|
{
|
|
1561
1892
|
color: active ? COLOR.accent : void 0,
|
|
1562
1893
|
dimColor: !active,
|
|
@@ -1806,7 +2137,7 @@ function restoreConsole(workDir) {
|
|
|
1806
2137
|
}
|
|
1807
2138
|
|
|
1808
2139
|
// src/tui/App.tsx
|
|
1809
|
-
import { Fragment as
|
|
2140
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1810
2141
|
var FLUSH_INTERVAL_MS = 80;
|
|
1811
2142
|
var SPINNER_INTERVAL_MS = 110;
|
|
1812
2143
|
var BLINK_INTERVAL_MS = 530;
|
|
@@ -1827,6 +2158,20 @@ function pickTurn(state, raw, whenEmpty) {
|
|
|
1827
2158
|
if (!target.record.requirement) return { error: `turn ${target.record.turn} has no requirement` };
|
|
1828
2159
|
return { record: target.record, requirement: target.record.requirement };
|
|
1829
2160
|
}
|
|
2161
|
+
function turnSessions(turn) {
|
|
2162
|
+
const sessions = {};
|
|
2163
|
+
for (const agent of Object.values(turn.agents)) {
|
|
2164
|
+
if (agent.sessionId) sessions[agent.id] = agent.sessionId;
|
|
2165
|
+
}
|
|
2166
|
+
return sessions;
|
|
2167
|
+
}
|
|
2168
|
+
function continuationResume(continuation, live) {
|
|
2169
|
+
if (!continuation) return void 0;
|
|
2170
|
+
const sessions = Object.fromEntries(
|
|
2171
|
+
Object.entries(continuation.sessions).filter(([id]) => !live?.has(id))
|
|
2172
|
+
);
|
|
2173
|
+
return Object.keys(sessions).length > 0 ? { sessions } : void 0;
|
|
2174
|
+
}
|
|
1830
2175
|
function StatusLine({
|
|
1831
2176
|
state,
|
|
1832
2177
|
running,
|
|
@@ -1838,19 +2183,19 @@ function StatusLine({
|
|
|
1838
2183
|
}) {
|
|
1839
2184
|
const { done, total } = progress(state);
|
|
1840
2185
|
if (error)
|
|
1841
|
-
return /* @__PURE__ */
|
|
2186
|
+
return /* @__PURE__ */ jsx6(Text6, { color: COLOR.error, wrap: "truncate-end", children: error });
|
|
1842
2187
|
if (state.warning && state.phase !== "complete")
|
|
1843
|
-
return /* @__PURE__ */
|
|
2188
|
+
return /* @__PURE__ */ jsx6(Text6, { color: COLOR.warn, wrap: "truncate-end", children: state.warning });
|
|
1844
2189
|
if ((cancelled || state.cancelled) && !running)
|
|
1845
|
-
return /* @__PURE__ */
|
|
2190
|
+
return /* @__PURE__ */ jsx6(Text6, { color: COLOR.warn, children: "cancelled \xB7 workspace changes are retained" });
|
|
1846
2191
|
if (state.reply) {
|
|
1847
2192
|
const asking = state.reply.mode === "ask";
|
|
1848
|
-
return /* @__PURE__ */
|
|
2193
|
+
return /* @__PURE__ */ jsx6(Text6, { color: asking ? COLOR.warn : COLOR.ok, wrap: "truncate-end", children: asking ? `${GLYPH.ask} needs your input` : `${STATUS.success.glyph} answered` });
|
|
1849
2194
|
}
|
|
1850
2195
|
if (running) {
|
|
1851
2196
|
const elapsed = turnElapsed(state, now);
|
|
1852
2197
|
const time = elapsed === void 0 ? "" : ` ${formatDuration(elapsed)}`;
|
|
1853
|
-
return /* @__PURE__ */
|
|
2198
|
+
return /* @__PURE__ */ jsxs6(Text6, { color: COLOR.accent, wrap: "truncate-end", children: [
|
|
1854
2199
|
spinner,
|
|
1855
2200
|
" running",
|
|
1856
2201
|
time,
|
|
@@ -1866,14 +2211,14 @@ function StatusLine({
|
|
|
1866
2211
|
const time = state.totalDuration === void 0 ? "" : ` ${formatDuration(state.totalDuration)}`;
|
|
1867
2212
|
const outcome2 = state.acceptance ? `${state.success ? STATUS.success.glyph : STATUS.failed.glyph} ${state.acceptance.status === "approved" ? "delivery approved" : state.acceptance.status === "changes_required" ? "changes required" : "acceptance failed"}${time}` : state.success ? `${STATUS.success.glyph} done${time}` : `${STATUS.failed.glyph} finished with failures${time}`;
|
|
1868
2213
|
const cost = state.totalCostUsd === void 0 ? "" : ` \xB7 ${formatCost(state.totalCostUsd)}`;
|
|
1869
|
-
return /* @__PURE__ */
|
|
2214
|
+
return /* @__PURE__ */ jsxs6(Text6, { color: state.success ? COLOR.ok : COLOR.error, wrap: "truncate-end", children: [
|
|
1870
2215
|
outcome2,
|
|
1871
2216
|
cost,
|
|
1872
2217
|
state.warning ? ` \xB7 ${GLYPH.warn} ${state.warning}` : ""
|
|
1873
2218
|
] });
|
|
1874
2219
|
}
|
|
1875
2220
|
const stages = state.stages.length;
|
|
1876
|
-
return /* @__PURE__ */
|
|
2221
|
+
return /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate-end", children: [
|
|
1877
2222
|
"ready \xB7 ",
|
|
1878
2223
|
total,
|
|
1879
2224
|
" agents",
|
|
@@ -1913,6 +2258,7 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
1913
2258
|
const abortRef = useRef(null);
|
|
1914
2259
|
const sessionsRef = useRef(null);
|
|
1915
2260
|
if (!sessionsRef.current) sessionsRef.current = providedSessions ?? new AgentSessionManager();
|
|
2261
|
+
const continuationRef = useRef(null);
|
|
1916
2262
|
const activeTurnRef = useRef(null);
|
|
1917
2263
|
const reporterErrorRef = useRef(null);
|
|
1918
2264
|
const closingRef = useRef(false);
|
|
@@ -1930,7 +2276,11 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
1930
2276
|
const mountedRef = useRef(true);
|
|
1931
2277
|
const interactionsRef = useRef([]);
|
|
1932
2278
|
const [interactions, setInteractions] = useState([]);
|
|
1933
|
-
const [
|
|
2279
|
+
const [permissionMenu, setPermissionMenu] = useState({ id: "", cursor: void 0, released: false });
|
|
2280
|
+
const [formEntry, setFormEntry] = useState({
|
|
2281
|
+
id: "",
|
|
2282
|
+
state: initialForm()
|
|
2283
|
+
});
|
|
1934
2284
|
const requestPermission = useCallback((request, signal) => {
|
|
1935
2285
|
if (closingRef.current || signal.aborted) return Promise.resolve(void 0);
|
|
1936
2286
|
const shortId = `p${++permissionSerialRef.current}`;
|
|
@@ -2046,6 +2396,9 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2046
2396
|
if (closingRef.current || busyRef.current) return;
|
|
2047
2397
|
busyRef.current = true;
|
|
2048
2398
|
setRunning(true);
|
|
2399
|
+
const continuation = opts?.newTask ? null : continuationRef.current;
|
|
2400
|
+
continuationRef.current = null;
|
|
2401
|
+
const resume = opts?.resume ?? continuationResume(continuation, sessionsRef.current);
|
|
2049
2402
|
if (opts?.newTask) {
|
|
2050
2403
|
try {
|
|
2051
2404
|
await sessionsRef.current?.reset();
|
|
@@ -2095,8 +2448,9 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2095
2448
|
logDir,
|
|
2096
2449
|
permissions: !config.permissions || config.permissions.mode === "ask" ? { mode: "ask", onRequest: requestPermission } : config.permissions,
|
|
2097
2450
|
questions: config.questions?.onRequest ? config.questions : { onRequest: requestQuestion },
|
|
2098
|
-
//
|
|
2099
|
-
|
|
2451
|
+
// A retry names the agents to redo; a continued conversation carries only
|
|
2452
|
+
// sessions and still lets the leader route. Both arrive here.
|
|
2453
|
+
...resume ? { resume } : {}
|
|
2100
2454
|
};
|
|
2101
2455
|
const controller = new AbortController();
|
|
2102
2456
|
abortRef.current = controller;
|
|
@@ -2229,6 +2583,21 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2229
2583
|
const menuOpen = !helpOpen && !historyOpen && query !== void 0 && matches.length > 0;
|
|
2230
2584
|
const argOpen = !helpOpen && !historyOpen && arg !== void 0 && argRows.length > 0;
|
|
2231
2585
|
const paletteOpen = menuOpen || argOpen;
|
|
2586
|
+
const front = interactions[0];
|
|
2587
|
+
const menu = front?.kind === "permission" && permissionMenu.id === front.shortId ? permissionMenu : { id: front?.shortId ?? "", cursor: void 0, released: false };
|
|
2588
|
+
const frontQuestion = front?.kind === "question" ? front : void 0;
|
|
2589
|
+
const questionFields = useMemo(
|
|
2590
|
+
() => frontQuestion ? formFields(frontQuestion.request) : [],
|
|
2591
|
+
[frontQuestion]
|
|
2592
|
+
);
|
|
2593
|
+
const form = frontQuestion !== void 0 && formEntry.id === frontQuestion.shortId ? formEntry.state : initialForm();
|
|
2594
|
+
const formField = frontQuestion === void 0 ? void 0 : questionFields[form.field];
|
|
2595
|
+
const editingField = formField !== void 0 && (formField.kind === "text" || formField.kind === "number") ? formField : void 0;
|
|
2596
|
+
const promptRows = promptRowCount(
|
|
2597
|
+
line.text,
|
|
2598
|
+
line.cursor,
|
|
2599
|
+
Math.max(MIN_COLUMNS, columns) - MARKER_WIDTH
|
|
2600
|
+
);
|
|
2232
2601
|
const layout = computeLayout({
|
|
2233
2602
|
columns,
|
|
2234
2603
|
rows,
|
|
@@ -2236,6 +2605,8 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2236
2605
|
overlay: helpOpen || historyOpen,
|
|
2237
2606
|
menuRows: paletteOpen ? paletteTotal : 0,
|
|
2238
2607
|
queueRows: queue.length,
|
|
2608
|
+
interactionRows: interactionRows(interactions, Math.max(MIN_COLUMNS, columns)),
|
|
2609
|
+
promptRows,
|
|
2239
2610
|
notice: notice !== void 0 || queuePaused && queue.length > 0,
|
|
2240
2611
|
viewing: state.view !== void 0,
|
|
2241
2612
|
hasRail: shown.leaderIds.length > 0 || shown.stages.length > 0,
|
|
@@ -2319,23 +2690,40 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2319
2690
|
}
|
|
2320
2691
|
case "approve":
|
|
2321
2692
|
case "deny": {
|
|
2322
|
-
const
|
|
2323
|
-
|
|
2693
|
+
const onScreen = interactionsRef.current[0];
|
|
2694
|
+
const byId = (id) => {
|
|
2695
|
+
const found = interactionsRef.current.filter(
|
|
2696
|
+
(item) => item.request.requestId === id || item.shortId === id
|
|
2697
|
+
);
|
|
2698
|
+
if (found.length !== 1) setNotice(`unknown or stale permission request: ${id}`);
|
|
2699
|
+
return found.length === 1 ? found[0] : void 0;
|
|
2700
|
+
};
|
|
2701
|
+
let entry;
|
|
2702
|
+
let number;
|
|
2703
|
+
if (name === "approve" && args.length === 1 && /^\d+$/.test(args[0])) {
|
|
2704
|
+
number = args[0];
|
|
2705
|
+
entry = onScreen;
|
|
2706
|
+
} else if (name === "approve" && args.length === 2) {
|
|
2707
|
+
number = args[1];
|
|
2708
|
+
entry = byId(args[0]);
|
|
2709
|
+
} else if (name === "deny" && args.length === 0) {
|
|
2710
|
+
entry = onScreen;
|
|
2711
|
+
} else if (name === "deny" && args.length === 1) {
|
|
2712
|
+
entry = byId(args[0]);
|
|
2713
|
+
} else {
|
|
2324
2714
|
setNotice(
|
|
2325
|
-
name === "approve" ? "usage: /approve
|
|
2715
|
+
name === "approve" ? "usage: /approve [id] <option number>" : "usage: /deny [id]"
|
|
2326
2716
|
);
|
|
2327
2717
|
return;
|
|
2328
2718
|
}
|
|
2329
|
-
|
|
2330
|
-
(
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
setNotice(`unknown or stale permission request: ${id}`);
|
|
2719
|
+
if (entry === void 0) {
|
|
2720
|
+
if (args.length === 0 || /^\d+$/.test(args[0] ?? "")) {
|
|
2721
|
+
setNotice("no permission request on screen");
|
|
2722
|
+
}
|
|
2334
2723
|
return;
|
|
2335
2724
|
}
|
|
2336
|
-
const entry = matches2[0];
|
|
2337
2725
|
if (entry.kind !== "permission") {
|
|
2338
|
-
setNotice(
|
|
2726
|
+
setNotice("the request on screen is a question, not a permission");
|
|
2339
2727
|
return;
|
|
2340
2728
|
}
|
|
2341
2729
|
if (name === "deny") {
|
|
@@ -2351,7 +2739,7 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2351
2739
|
return;
|
|
2352
2740
|
}
|
|
2353
2741
|
entry.settle(option.optionId);
|
|
2354
|
-
setNotice(`selected ${option.name} for ${entry.shortId}`);
|
|
2742
|
+
setNotice(`selected ${clean(option.name)} for ${entry.shortId}`);
|
|
2355
2743
|
return;
|
|
2356
2744
|
}
|
|
2357
2745
|
case "task": {
|
|
@@ -2657,6 +3045,21 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2657
3045
|
setHistoryOpen(false);
|
|
2658
3046
|
return;
|
|
2659
3047
|
}
|
|
3048
|
+
if (char === "c") {
|
|
3049
|
+
const target = historyTurns[historyIndex];
|
|
3050
|
+
const sessions = turnSessions(target);
|
|
3051
|
+
const count = Object.keys(sessions).length;
|
|
3052
|
+
if (count === 0) {
|
|
3053
|
+
setNotice(`turn ${target.turn} has no agent sessions to continue`);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
continuationRef.current = { turn: target.turn, sessions };
|
|
3057
|
+
setHistoryOpen(false);
|
|
3058
|
+
setNotice(
|
|
3059
|
+
`continuing turn ${target.turn} \u2014 ${count} ${count === 1 ? "session" : "sessions"} \xB7 type your next instruction`
|
|
3060
|
+
);
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
2660
3063
|
return;
|
|
2661
3064
|
}
|
|
2662
3065
|
if (key.ctrl && char === "d") {
|
|
@@ -2676,7 +3079,8 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2676
3079
|
setLine((l) => ({ ...l, cursor: l.text.length }));
|
|
2677
3080
|
return;
|
|
2678
3081
|
}
|
|
2679
|
-
|
|
3082
|
+
const formFront = front?.kind === "question" && !isCommandLine(line.text) ? front : void 0;
|
|
3083
|
+
if (key.tab && formFront === void 0) {
|
|
2680
3084
|
dispatch({ type: "focus:cycle", delta: key.shift ? -1 : 1 });
|
|
2681
3085
|
return;
|
|
2682
3086
|
}
|
|
@@ -2699,19 +3103,111 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2699
3103
|
return;
|
|
2700
3104
|
}
|
|
2701
3105
|
}
|
|
2702
|
-
if (
|
|
2703
|
-
const
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
3106
|
+
if (front?.kind === "permission" && line.text === "" && !menu.released) {
|
|
3107
|
+
const total = permissionChoices(front.request).length;
|
|
3108
|
+
if (key.upArrow || key.downArrow) {
|
|
3109
|
+
setPermissionMenu({
|
|
3110
|
+
id: front.shortId,
|
|
3111
|
+
cursor: moveCursor(menu.cursor, key.upArrow ? -1 : 1, total),
|
|
3112
|
+
released: false
|
|
3113
|
+
});
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
if (/^[1-9]$/.test(char) && !key.ctrl && !key.meta) {
|
|
3117
|
+
const cursor = jumpCursor(char, total);
|
|
3118
|
+
if (cursor !== void 0) {
|
|
3119
|
+
setPermissionMenu({ id: front.shortId, cursor, released: false });
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
if (key.return) {
|
|
3124
|
+
const choice = menu.cursor === void 0 ? void 0 : permissionChoices(front.request)[menu.cursor];
|
|
3125
|
+
if (choice !== void 0) {
|
|
3126
|
+
front.settle(choice.optionId);
|
|
3127
|
+
setNotice(`chose ${choice.label} \xB7 ${clean(front.request.agentId)}`);
|
|
3128
|
+
}
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
if (formFront !== void 0) {
|
|
3133
|
+
const front2 = formFront;
|
|
3134
|
+
const total = questionFields.length;
|
|
3135
|
+
const field = questionFields[form.field];
|
|
3136
|
+
const committed = withDraft(questionFields, form, line.text);
|
|
3137
|
+
const save = (state2, draft) => {
|
|
3138
|
+
setFormEntry({ id: front2.shortId, state: state2 });
|
|
3139
|
+
if (draft !== void 0) setLine({ text: draft, cursor: draft.length });
|
|
3140
|
+
};
|
|
3141
|
+
const moveFocus = (index, state2 = committed) => {
|
|
3142
|
+
const next = focusField(questionFields, state2, index);
|
|
3143
|
+
save(next.state, next.draft);
|
|
3144
|
+
};
|
|
3145
|
+
const submit2 = (state2) => {
|
|
3146
|
+
if (!formComplete(questionFields, state2)) {
|
|
3147
|
+
const missing = questionFields.findIndex((item) => !formComplete([item], state2));
|
|
3148
|
+
if (missing >= 0) {
|
|
3149
|
+
setNotice(`${questionFields[missing].title} still needs a value`);
|
|
3150
|
+
if (missing !== form.field) moveFocus(missing, state2);
|
|
3151
|
+
}
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
const content = formContent(questionFields, state2);
|
|
3155
|
+
if (!validateRuntimeQuestionContent(front2.request.requestedSchema, content)) {
|
|
3156
|
+
setNotice("answer does not match the question schema");
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
front2.settle({ action: "accept", content });
|
|
3160
|
+
setFormEntry({ id: "", state: initialForm() });
|
|
3161
|
+
setLine(EMPTY_LINE);
|
|
3162
|
+
setNotice(`answered ${front2.shortId}`);
|
|
3163
|
+
};
|
|
3164
|
+
const advance = (state2) => {
|
|
3165
|
+
if (form.field >= total - 1) submit2(state2);
|
|
3166
|
+
else moveFocus(form.field + 1, state2);
|
|
3167
|
+
};
|
|
3168
|
+
if (field === void 0) {
|
|
3169
|
+
if (key.return) submit2(committed);
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
if (key.tab) {
|
|
3173
|
+
moveFocus(key.shift ? form.field - 1 : form.field + 1);
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
if (field.choices.length > 0 && (key.upArrow || key.downArrow)) {
|
|
3177
|
+
save(movePick(committed, field, key.upArrow ? -1 : 1));
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
if (field.choices.length > 0 && (key.leftArrow || key.rightArrow)) {
|
|
3181
|
+
save(movePick(committed, field, key.leftArrow ? -1 : 1));
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
if (key.upArrow || key.downArrow) {
|
|
3185
|
+
moveFocus(key.upArrow ? form.field - 1 : form.field + 1);
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3188
|
+
if (char === " " && field.kind === "multi") {
|
|
3189
|
+
save(toggleChoice(committed, field));
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
if (field.kind === "boolean" && (char === " " || key.leftArrow || key.rightArrow)) {
|
|
3193
|
+
save(toggleBoolean(committed, field));
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
if (field.choices.length > 0 && /^[1-9]$/.test(char) && !key.ctrl && !key.meta) {
|
|
3197
|
+
save(jumpPick(committed, field, char));
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
if (key.return) {
|
|
3201
|
+
advance(field.kind === "choice" ? commitField(questionFields, committed) : committed);
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
if (key.escape) {
|
|
3205
|
+
if (field.kind === "text" || field.kind === "number") {
|
|
3206
|
+
save({ ...committed, values: { ...committed.values, [field.key]: "" } });
|
|
3207
|
+
setLine(EMPTY_LINE);
|
|
3208
|
+
}
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
2715
3211
|
}
|
|
2716
3212
|
if (key.upArrow) {
|
|
2717
3213
|
dispatch({
|
|
@@ -2782,6 +3278,9 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2782
3278
|
dispatch({ type: "view:close" });
|
|
2783
3279
|
return;
|
|
2784
3280
|
}
|
|
3281
|
+
if (front !== void 0) {
|
|
3282
|
+
setPermissionMenu({ id: front.shortId, cursor: void 0, released: true });
|
|
3283
|
+
}
|
|
2785
3284
|
setLine(EMPTY_LINE);
|
|
2786
3285
|
setNotice(void 0);
|
|
2787
3286
|
return;
|
|
@@ -2799,33 +3298,37 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2799
3298
|
const handlerRef = useRef(handleKey);
|
|
2800
3299
|
handlerRef.current = handleKey;
|
|
2801
3300
|
useInput((char, key) => handlerRef.current(char, key));
|
|
2802
|
-
const keys = helpOpen ? "esc close" : historyOpen ? "\u2191\u2193 select \xB7 enter open \xB7 esc close" : argOpen ? `\u2191\u2193 select \xB7 enter ${arg?.command.name} \xB7 esc close` : menuOpen ? "\u2191\u2193 select \xB7 enter run \xB7 esc close" : running ? interactions.length > 0 ?
|
|
2803
|
-
|
|
2804
|
-
|
|
3301
|
+
const keys = helpOpen ? "esc close" : historyOpen ? "\u2191\u2193 select \xB7 enter open \xB7 esc close" : argOpen ? `\u2191\u2193 select \xB7 enter ${arg?.command.name} \xB7 esc close` : menuOpen ? "\u2191\u2193 select \xB7 enter run \xB7 esc close" : running ? interactions.length > 0 ? layout.interactionRows === 0 ? (
|
|
3302
|
+
// Too short to dock the bar, so the commands are the only way in.
|
|
3303
|
+
"decide with /approve 1 \xB7 /deny \xB7 /respond"
|
|
3304
|
+
) : interactions[0].kind === "question" ? "tab field \xB7 \u2191\u2193 pick \xB7 enter next \xB7 /dismiss" : menu.released ? "/approve 1 and /deny still reach it" : "\u2191\u2193 or 1-9 choose \xB7 enter confirm \xB7 esc to type" : "enter queues \xB7 ctrl+c cancel" : state.view ? "esc live \xB7 tab agent \xB7 ? help" : state.reply ? "enter sends \xB7 / commands \xB7 ? help" : "tab switch \xB7 / commands \xB7 ? help";
|
|
3305
|
+
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" : running ? RUNNING_PLACEHOLDER : awaitingAnswer ? "answer the leader\u2026 \xB7 /task starts a new task" : INPUT_PLACEHOLDER;
|
|
3306
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: layout.rows, children: [
|
|
3307
|
+
/* @__PURE__ */ jsx6(
|
|
2805
3308
|
Header,
|
|
2806
3309
|
{
|
|
2807
3310
|
name: state.name || config.name,
|
|
2808
3311
|
right: state.view ? `turn ${shown.turn} \xB7 viewing` : `turn ${shown.turn} \xB7 ${running ? formatDuration(turnElapsed(state, now) ?? 0) : state.phase === "complete" ? "complete" : "ready"}`
|
|
2809
3312
|
}
|
|
2810
3313
|
),
|
|
2811
|
-
/* @__PURE__ */
|
|
2812
|
-
config.useLeader === false ? /* @__PURE__ */
|
|
3314
|
+
/* @__PURE__ */ jsx6(Rule, { width: layout.columns }),
|
|
3315
|
+
config.useLeader === false ? /* @__PURE__ */ jsx6(Text6, { color: COLOR.warn, dimColor: true, wrap: "truncate-end", children: " \u26A0 leader off \u2014 every message runs the full team" }) : null,
|
|
2813
3316
|
layout.showRail ? (
|
|
2814
3317
|
// The rail wraps, so it can be taller than one row. Cap it at the height
|
|
2815
3318
|
// the budget reserved and clip, rather than letting the extra rows push
|
|
2816
3319
|
// the chrome below it off the screen.
|
|
2817
|
-
/* @__PURE__ */
|
|
3320
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, height: layout.railRows, overflow: "hidden", flexShrink: 0, children: /* @__PURE__ */ jsx6(PipelineRail, { turn: shown, spinner }) })
|
|
2818
3321
|
) : null,
|
|
2819
|
-
shown.reason ? /* @__PURE__ */
|
|
2820
|
-
/* @__PURE__ */
|
|
2821
|
-
|
|
3322
|
+
shown.reason ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: ` reason: ${shown.reason}` }) : null,
|
|
3323
|
+
/* @__PURE__ */ jsx6(
|
|
3324
|
+
Box6,
|
|
2822
3325
|
{
|
|
2823
3326
|
marginTop: 1,
|
|
2824
3327
|
height: layout.bodyHeight,
|
|
2825
3328
|
flexShrink: 0,
|
|
2826
3329
|
flexDirection: "column",
|
|
2827
3330
|
overflow: "hidden",
|
|
2828
|
-
children: helpOpen ? /* @__PURE__ */
|
|
3331
|
+
children: helpOpen ? /* @__PURE__ */ jsx6(HelpOverlay, { width: layout.columns, maxRows: layout.bodyHeight }) : historyOpen ? /* @__PURE__ */ jsx6(
|
|
2829
3332
|
HistoryOverlay,
|
|
2830
3333
|
{
|
|
2831
3334
|
turns: historyTurns,
|
|
@@ -2833,36 +3336,17 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2833
3336
|
width: layout.columns,
|
|
2834
3337
|
maxRows: layout.bodyHeight
|
|
2835
3338
|
}
|
|
2836
|
-
) :
|
|
2837
|
-
QuestionOverlay,
|
|
2838
|
-
{
|
|
2839
|
-
request: interactions[0].request,
|
|
2840
|
-
shortId: interactions[0].shortId,
|
|
2841
|
-
queued: interactions.length,
|
|
2842
|
-
width: layout.columns,
|
|
2843
|
-
maxRows: layout.bodyHeight,
|
|
2844
|
-
offset: questionScroll.id === interactions[0].shortId ? questionScroll.offset : 0
|
|
2845
|
-
}
|
|
2846
|
-
) : interactions[0]?.kind === "permission" ? /* @__PURE__ */ jsx5(
|
|
2847
|
-
PermissionOverlay,
|
|
2848
|
-
{
|
|
2849
|
-
request: interactions[0].request,
|
|
2850
|
-
shortId: interactions[0].shortId,
|
|
2851
|
-
queued: interactions.length,
|
|
2852
|
-
width: layout.columns,
|
|
2853
|
-
maxRows: layout.bodyHeight
|
|
2854
|
-
}
|
|
2855
|
-
) : layout.welcome ? /* @__PURE__ */ jsx5(WelcomePanel, { turn: state, width: layout.columns }) : error ? (
|
|
3339
|
+
) : layout.welcome ? /* @__PURE__ */ jsx6(WelcomePanel, { turn: state, width: layout.columns }) : error ? (
|
|
2856
3340
|
// The error wins over a reply: a run that stopped with an error is the
|
|
2857
3341
|
// most urgent thing on screen, and a stale answer underneath it would
|
|
2858
3342
|
// read as the outcome. The status line keeps a one-line version too.
|
|
2859
|
-
/* @__PURE__ */
|
|
2860
|
-
) : shown.reply ? /* @__PURE__ */
|
|
2861
|
-
layout.showTeam ? /* @__PURE__ */
|
|
2862
|
-
/* @__PURE__ */
|
|
2863
|
-
/* @__PURE__ */
|
|
3343
|
+
/* @__PURE__ */ jsx6(ErrorPanel, { message: error, width: layout.columns })
|
|
3344
|
+
) : shown.reply ? /* @__PURE__ */ jsx6(ReplyPanel, { reply: shown.reply, width: layout.columns }) : /* @__PURE__ */ jsxs6(Box6, { height: layout.bodyHeight, flexShrink: 0, flexDirection: "row", children: [
|
|
3345
|
+
layout.showTeam ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3346
|
+
/* @__PURE__ */ jsx6(TeamPanel, { turn: shown, width: layout.teamWidth, now, spinner }),
|
|
3347
|
+
/* @__PURE__ */ jsx6(Divider, {})
|
|
2864
3348
|
] }) : null,
|
|
2865
|
-
/* @__PURE__ */
|
|
3349
|
+
/* @__PURE__ */ jsx6(
|
|
2866
3350
|
StreamPanel,
|
|
2867
3351
|
{
|
|
2868
3352
|
turn: shown,
|
|
@@ -2875,26 +3359,59 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2875
3359
|
] })
|
|
2876
3360
|
}
|
|
2877
3361
|
),
|
|
2878
|
-
state.view ? /* @__PURE__ */
|
|
2879
|
-
notice || queuePaused && queue.length > 0 ? /* @__PURE__ */
|
|
3362
|
+
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,
|
|
3363
|
+
notice || queuePaused && queue.length > 0 ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate-end", children: [
|
|
2880
3364
|
" ",
|
|
2881
3365
|
notice ?? (awaitingAnswer ? "queue paused \u2014 answer the leader first" : "queue paused \u2014 /continue resumes it")
|
|
2882
3366
|
] }) : null,
|
|
2883
|
-
/* @__PURE__ */
|
|
2884
|
-
|
|
2885
|
-
|
|
3367
|
+
/* @__PURE__ */ jsx6(QueueStrip, { items: queue, max: layout.queueRows }),
|
|
3368
|
+
front === void 0 || layout.interactionRows === 0 ? null : /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3369
|
+
/* @__PURE__ */ jsx6(Rule, { width: layout.columns }),
|
|
3370
|
+
/* @__PURE__ */ jsx6(
|
|
3371
|
+
Box6,
|
|
3372
|
+
{
|
|
3373
|
+
height: layout.interactionRows,
|
|
3374
|
+
overflow: "hidden",
|
|
3375
|
+
flexShrink: 0,
|
|
3376
|
+
flexDirection: "column",
|
|
3377
|
+
children: front.kind === "permission" ? /* @__PURE__ */ jsx6(
|
|
3378
|
+
PermissionBar,
|
|
3379
|
+
{
|
|
3380
|
+
request: front.request,
|
|
3381
|
+
others: interactions.slice(1).flatMap((entry) => entry.kind === "permission" ? [entry.request] : []),
|
|
3382
|
+
cursor: menu.cursor,
|
|
3383
|
+
released: menu.released,
|
|
3384
|
+
width: layout.columns
|
|
3385
|
+
}
|
|
3386
|
+
) : /* @__PURE__ */ jsx6(
|
|
3387
|
+
QuestionForm,
|
|
3388
|
+
{
|
|
3389
|
+
request: front.request,
|
|
3390
|
+
others: interactions.slice(1).flatMap((entry) => entry.kind === "question" ? [entry.request] : []),
|
|
3391
|
+
fields: questionFields,
|
|
3392
|
+
form,
|
|
3393
|
+
draft: line.text,
|
|
3394
|
+
width: layout.columns
|
|
3395
|
+
}
|
|
3396
|
+
)
|
|
3397
|
+
}
|
|
3398
|
+
)
|
|
3399
|
+
] }),
|
|
3400
|
+
paletteOpen ? /* @__PURE__ */ jsx6(CommandMenu, { rows: paletteWindow, selected: paletteSelected }) : null,
|
|
3401
|
+
/* @__PURE__ */ jsx6(
|
|
2886
3402
|
Prompt,
|
|
2887
3403
|
{
|
|
2888
3404
|
value: line.text,
|
|
2889
3405
|
cursor: line.cursor,
|
|
2890
3406
|
caretVisible: caretOn,
|
|
2891
|
-
|
|
3407
|
+
width: layout.columns,
|
|
3408
|
+
placeholder
|
|
2892
3409
|
}
|
|
2893
3410
|
),
|
|
2894
|
-
/* @__PURE__ */
|
|
3411
|
+
/* @__PURE__ */ jsx6(
|
|
2895
3412
|
HintBar,
|
|
2896
3413
|
{
|
|
2897
|
-
status: /* @__PURE__ */
|
|
3414
|
+
status: /* @__PURE__ */ jsx6(
|
|
2898
3415
|
StatusLine,
|
|
2899
3416
|
{
|
|
2900
3417
|
state,
|
|
@@ -2945,11 +3462,13 @@ async function startTui(options) {
|
|
|
2945
3462
|
export {
|
|
2946
3463
|
COMPACT_COLUMNS,
|
|
2947
3464
|
MAX_HISTORY,
|
|
3465
|
+
MAX_INTERACTION_ROWS,
|
|
2948
3466
|
MAX_LOG_CHARS,
|
|
2949
3467
|
MAX_MENU_ROWS,
|
|
2950
3468
|
MAX_QUEUE_ROWS,
|
|
2951
3469
|
MAX_RAIL_ROWS,
|
|
2952
3470
|
MIN_COLUMNS,
|
|
3471
|
+
MIN_INTERACTION_ROWS,
|
|
2953
3472
|
MIN_ROWS,
|
|
2954
3473
|
RAIL_MIN_ROWS,
|
|
2955
3474
|
agentElapsed,
|