@multiclawcli/dashboard 0.6.2 → 0.7.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 +772 -249
- 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
|
|
@@ -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;
|
|
@@ -881,11 +887,49 @@ function conversationTurns(state) {
|
|
|
881
887
|
for (const turn of recordedTurns(state)) {
|
|
882
888
|
if (turn.phase !== "complete" || turn.cancelled || !turn.requirement) continue;
|
|
883
889
|
turns.push(
|
|
884
|
-
turn.reply ? { requirement: turn.requirement, reply: turn.reply } : { requirement: turn.requirement }
|
|
890
|
+
turn.reply ? { requirement: turn.requirement, reply: turn.reply } : { requirement: turn.requirement, outcome: conversationOutcome(turn) }
|
|
885
891
|
);
|
|
886
892
|
}
|
|
887
893
|
return turns;
|
|
888
894
|
}
|
|
895
|
+
function conversationOutcome(turn) {
|
|
896
|
+
const reviewer = turn.acceptance?.reviewer;
|
|
897
|
+
const isWorker = (id) => id !== reviewer && !turn.leaderIds.includes(id);
|
|
898
|
+
const workers = Object.values(turn.agents).filter((agent) => isWorker(agent.id));
|
|
899
|
+
const completedAgents = workers.filter((agent) => agent.status === "success").map((agent) => agent.id);
|
|
900
|
+
const observed = workers.filter(
|
|
901
|
+
(agent) => agent.status !== "pending" && agent.status !== "skipped"
|
|
902
|
+
);
|
|
903
|
+
const failedAgents = Object.values(turn.agents).filter(
|
|
904
|
+
(agent) => agent.id !== reviewer && agent.status === "failed" && (isWorker(agent.id) || observed.length === 0)
|
|
905
|
+
).map((agent) => ({
|
|
906
|
+
agentId: agent.id,
|
|
907
|
+
...agent.error !== void 0 ? { reason: agent.error } : {}
|
|
908
|
+
}));
|
|
909
|
+
const selected = turn.stages.flatMap((stage) => stage.agents).filter((agent) => isWorker(agent.id));
|
|
910
|
+
const unresolved = selected.some((agent) => {
|
|
911
|
+
const status = turn.agents[agent.id]?.status;
|
|
912
|
+
return status === void 0 || status === "pending" || status === "running" || status === "retrying" || status === "skipped";
|
|
913
|
+
});
|
|
914
|
+
let execution = "unknown";
|
|
915
|
+
if (turn.interrupted) execution = "interrupted";
|
|
916
|
+
else if (failedAgents.length > 0) execution = "failed";
|
|
917
|
+
else if (observed.some((agent) => agent.status === "cancelled")) execution = "cancelled";
|
|
918
|
+
else if (observed.length > 0 && observed.every((agent) => agent.status === "success") && !unresolved)
|
|
919
|
+
execution = "success";
|
|
920
|
+
else if (observed.length === 0 && turn.success === false && !turn.acceptance) execution = "failed";
|
|
921
|
+
return {
|
|
922
|
+
execution,
|
|
923
|
+
completedAgents,
|
|
924
|
+
failedAgents,
|
|
925
|
+
...turn.acceptance ? {
|
|
926
|
+
acceptance: {
|
|
927
|
+
status: turn.acceptance.status,
|
|
928
|
+
...turn.acceptance.reason !== void 0 ? { reason: turn.acceptance.reason } : {}
|
|
929
|
+
}
|
|
930
|
+
} : {}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
889
933
|
function tuiReducer(state, action) {
|
|
890
934
|
switch (action.type) {
|
|
891
935
|
case "event":
|
|
@@ -1039,14 +1083,281 @@ function visibleLog(turn, viewport, width) {
|
|
|
1039
1083
|
};
|
|
1040
1084
|
}
|
|
1041
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
|
+
|
|
1042
1352
|
// src/tui/components/overlays.tsx
|
|
1353
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1043
1354
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1044
1355
|
var HELP_GROUPS = [
|
|
1045
1356
|
{
|
|
1046
1357
|
title: "prompt",
|
|
1047
1358
|
rows: [
|
|
1048
1359
|
["enter", "Run the requirement"],
|
|
1049
|
-
["enter (busy)", "Queue only;
|
|
1360
|
+
["enter (busy)", "Queue only; the bar above decides tools and questions"],
|
|
1050
1361
|
["esc", "Clear the line, close an overlay"],
|
|
1051
1362
|
["\u2190 \u2192 home end", "Move the caret"]
|
|
1052
1363
|
]
|
|
@@ -1101,95 +1412,6 @@ function HelpOverlay({ width, maxRows }) {
|
|
|
1101
1412
|
/* @__PURE__ */ jsx2(Box2, { marginTop: gap, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "esc or ? to close" }) })
|
|
1102
1413
|
] });
|
|
1103
1414
|
}
|
|
1104
|
-
function PermissionOverlay({
|
|
1105
|
-
request,
|
|
1106
|
-
shortId,
|
|
1107
|
-
queued,
|
|
1108
|
-
width,
|
|
1109
|
-
maxRows
|
|
1110
|
-
}) {
|
|
1111
|
-
const clean = (value) => stripAnsi(value).replace(/\p{Cc}/gu, " ");
|
|
1112
|
-
const input = request.input === void 0 ? "(no input)" : JSON.stringify(request.input) ?? "(no input)";
|
|
1113
|
-
const inputRows = Math.max(1, Math.min(3, maxRows - request.options.length - 7));
|
|
1114
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1115
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color: COLOR.warn, wrap: "truncate-end", children: `PERMISSION ${shortId} \xB7 ${queued} pending \xB7 ${clean(request.agentId)}` }),
|
|
1116
|
-
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: clean(request.title) }),
|
|
1117
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: `request ${clean(request.requestId)} \xB7 ${clean(request.toolName ?? request.kind ?? request.toolCallId)}` }),
|
|
1118
|
-
/* @__PURE__ */ jsx2(Text2, { color: COLOR.accent, wrap: "truncate-end", children: `/approve ${shortId} <number> \xB7 /deny ${shortId}` }),
|
|
1119
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: "Ordinary text queues a follow-up; it never grants permission." }),
|
|
1120
|
-
/* @__PURE__ */ jsx2(Box2, { height: inputRows, overflow: "hidden", flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { children: `input: ${clean(input)}` }) }),
|
|
1121
|
-
request.options.map((option, index) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: `${index + 1}. ${clean(option.name)} (${option.kind})` }, option.optionId))
|
|
1122
|
-
] });
|
|
1123
|
-
}
|
|
1124
|
-
function questionLines(request, width) {
|
|
1125
|
-
const lines = [request.message, `request ${request.requestId}`];
|
|
1126
|
-
for (const [key, field] of Object.entries(request.requestedSchema.properties ?? {})) {
|
|
1127
|
-
const schema = field;
|
|
1128
|
-
const items = schema.items;
|
|
1129
|
-
const choices = schema.oneOf ?? schema.enum ?? items?.anyOf ?? items?.enum;
|
|
1130
|
-
lines.push(
|
|
1131
|
-
`${key}${request.requestedSchema.required?.includes(key) ? " *" : ""}: ${typeof schema.title === "string" ? schema.title : key} (${field.type})`
|
|
1132
|
-
);
|
|
1133
|
-
if (typeof schema.description === "string") lines.push(schema.description);
|
|
1134
|
-
const limits = [
|
|
1135
|
-
"minimum",
|
|
1136
|
-
"maximum",
|
|
1137
|
-
"exclusiveMinimum",
|
|
1138
|
-
"exclusiveMaximum",
|
|
1139
|
-
"minLength",
|
|
1140
|
-
"maxLength",
|
|
1141
|
-
"minItems",
|
|
1142
|
-
"maxItems",
|
|
1143
|
-
"uniqueItems"
|
|
1144
|
-
].filter((limit) => schema[limit] !== void 0 && schema[limit] !== null).map((limit) => `${limit}=${schema[limit]}`);
|
|
1145
|
-
if (limits.length) lines.push(limits.join(" \xB7 "));
|
|
1146
|
-
for (const choice of choices ?? []) {
|
|
1147
|
-
lines.push(
|
|
1148
|
-
typeof choice === "string" ? JSON.stringify(choice) : `${JSON.stringify(choice.const)} \u2014 ${choice.title ?? choice.const}${choice.description ? `: ${choice.description}` : ""}`
|
|
1149
|
-
);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
return lines.flatMap(
|
|
1153
|
-
(line) => wrapLine(stripAnsi(line).replace(/\p{Cc}/gu, " "), Math.max(1, width - 2))
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
function QuestionOverlay({
|
|
1157
|
-
request,
|
|
1158
|
-
shortId,
|
|
1159
|
-
queued,
|
|
1160
|
-
width,
|
|
1161
|
-
maxRows,
|
|
1162
|
-
offset
|
|
1163
|
-
}) {
|
|
1164
|
-
const lines = questionLines(request, width);
|
|
1165
|
-
const capacity = Math.max(1, maxRows - 4);
|
|
1166
|
-
const start = Math.min(offset, Math.max(0, lines.length - capacity));
|
|
1167
|
-
const visible = lines.slice(start, start + capacity);
|
|
1168
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, width, children: [
|
|
1169
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, color: COLOR.warn, wrap: "truncate-end", children: `QUESTION ${shortId} \xB7 ${queued} pending \xB7 ${stripAnsi(request.agentId)}` }),
|
|
1170
|
-
/* @__PURE__ */ jsx2(Text2, { color: COLOR.accent, wrap: "truncate-end", children: `/respond ${shortId} <JSON object>` }),
|
|
1171
|
-
/* @__PURE__ */ jsx2(
|
|
1172
|
-
Text2,
|
|
1173
|
-
{
|
|
1174
|
-
dimColor: true,
|
|
1175
|
-
wrap: "truncate-end",
|
|
1176
|
-
children: `/skip ${shortId} declines \xB7 /dismiss ${shortId} cancels`
|
|
1177
|
-
}
|
|
1178
|
-
),
|
|
1179
|
-
visible.map((line, index) => (
|
|
1180
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: immutable text rows are identified by their absolute form offset.
|
|
1181
|
-
/* @__PURE__ */ jsx2(Text2, { wrap: "truncate-end", children: line }, `${start + index}-${line}`)
|
|
1182
|
-
)),
|
|
1183
|
-
/* @__PURE__ */ jsx2(
|
|
1184
|
-
Text2,
|
|
1185
|
-
{
|
|
1186
|
-
dimColor: true,
|
|
1187
|
-
wrap: "truncate-end",
|
|
1188
|
-
children: `\u2191\u2193 / pgup pgdn form \xB7 ${start + 1}\u2013${start + visible.length}/${lines.length} \xB7 ordinary text queues`
|
|
1189
|
-
}
|
|
1190
|
-
)
|
|
1191
|
-
] });
|
|
1192
|
-
}
|
|
1193
1415
|
function outcome(turn) {
|
|
1194
1416
|
if (turn.reply) {
|
|
1195
1417
|
return turn.reply.mode === "ask" ? { text: `${GLYPH.ask} needs input`, color: COLOR.warn } : { text: `${STATUS.success.glyph} answered`, color: COLOR.ok };
|
|
@@ -1266,9 +1488,110 @@ function HistoryOverlay({
|
|
|
1266
1488
|
] });
|
|
1267
1489
|
}
|
|
1268
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` })
|
|
1564
|
+
] });
|
|
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
|
+
}
|
|
1591
|
+
|
|
1269
1592
|
// src/tui/components/panels.tsx
|
|
1270
|
-
import { Box as
|
|
1271
|
-
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";
|
|
1272
1595
|
function rightColumn(agent, now) {
|
|
1273
1596
|
if (agent.status === "retrying" && agent.retry) {
|
|
1274
1597
|
return { text: `retry ${agent.retry.attempt}/${agent.retry.max}`, color: COLOR.warn };
|
|
@@ -1287,9 +1610,9 @@ function TeamRow({
|
|
|
1287
1610
|
const style = STATUS[agent.status];
|
|
1288
1611
|
const glyph = agent.status === "running" ? spinner : style.glyph;
|
|
1289
1612
|
const right = rightColumn(agent, now);
|
|
1290
|
-
return /* @__PURE__ */
|
|
1291
|
-
/* @__PURE__ */
|
|
1292
|
-
|
|
1613
|
+
return /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
1614
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 1, children: /* @__PURE__ */ jsxs4(
|
|
1615
|
+
Text4,
|
|
1293
1616
|
{
|
|
1294
1617
|
color: focused ? COLOR.accent : style.color,
|
|
1295
1618
|
dimColor: !focused && style.dim,
|
|
@@ -1301,14 +1624,14 @@ function TeamRow({
|
|
|
1301
1624
|
" ",
|
|
1302
1625
|
agent.icon ? `${agent.icon} ` : "",
|
|
1303
1626
|
agent.name,
|
|
1304
|
-
agent.taskTitle ? /* @__PURE__ */
|
|
1627
|
+
agent.taskTitle ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1305
1628
|
" ",
|
|
1306
1629
|
agent.taskTitle
|
|
1307
1630
|
] }) : null
|
|
1308
1631
|
]
|
|
1309
1632
|
}
|
|
1310
1633
|
) }),
|
|
1311
|
-
/* @__PURE__ */
|
|
1634
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { color: right.color, dimColor: right.color === void 0, children: right.text }) })
|
|
1312
1635
|
] });
|
|
1313
1636
|
}
|
|
1314
1637
|
function TeamPanel({
|
|
@@ -1319,16 +1642,16 @@ function TeamPanel({
|
|
|
1319
1642
|
}) {
|
|
1320
1643
|
const ids = Object.keys(turn.agents);
|
|
1321
1644
|
const { done, total } = progress(turn);
|
|
1322
|
-
return /* @__PURE__ */
|
|
1323
|
-
/* @__PURE__ */
|
|
1324
|
-
/* @__PURE__ */
|
|
1325
|
-
/* @__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: [
|
|
1326
1649
|
done,
|
|
1327
1650
|
"/",
|
|
1328
1651
|
total
|
|
1329
1652
|
] })
|
|
1330
1653
|
] }),
|
|
1331
|
-
ids.length === 0 ? /* @__PURE__ */
|
|
1654
|
+
ids.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "no agents configured" }) : ids.map((id) => /* @__PURE__ */ jsx4(
|
|
1332
1655
|
TeamRow,
|
|
1333
1656
|
{
|
|
1334
1657
|
agent: turn.agents[id],
|
|
@@ -1341,8 +1664,8 @@ function TeamPanel({
|
|
|
1341
1664
|
] });
|
|
1342
1665
|
}
|
|
1343
1666
|
function Divider() {
|
|
1344
|
-
return /* @__PURE__ */
|
|
1345
|
-
|
|
1667
|
+
return /* @__PURE__ */ jsx4(
|
|
1668
|
+
Box4,
|
|
1346
1669
|
{
|
|
1347
1670
|
flexShrink: 0,
|
|
1348
1671
|
borderStyle: "single",
|
|
@@ -1366,43 +1689,43 @@ function StreamPanel({
|
|
|
1366
1689
|
const window = visibleLog(turn, viewport, width);
|
|
1367
1690
|
const style = agent ? STATUS[agent.status] : void 0;
|
|
1368
1691
|
const elapsed = agent ? agentElapsed(agent, now) : void 0;
|
|
1369
|
-
return /* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1371
|
-
/* @__PURE__ */
|
|
1372
|
-
/* @__PURE__ */
|
|
1373
|
-
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: [
|
|
1374
1697
|
" \xB7 ",
|
|
1375
1698
|
agent.model
|
|
1376
1699
|
] }) : null,
|
|
1377
|
-
agent && style ? /* @__PURE__ */
|
|
1378
|
-
/* @__PURE__ */
|
|
1379
|
-
/* @__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 })
|
|
1380
1703
|
] }) : null,
|
|
1381
|
-
agent?.taskTitle ? /* @__PURE__ */
|
|
1704
|
+
agent?.taskTitle ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1382
1705
|
" \xB7 ",
|
|
1383
1706
|
agent.taskTitle
|
|
1384
1707
|
] }) : null,
|
|
1385
|
-
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: [
|
|
1386
1709
|
" ",
|
|
1387
1710
|
"\xB7 ctx ",
|
|
1388
1711
|
formatTokens(agent.usage.contextTokens),
|
|
1389
1712
|
"/",
|
|
1390
1713
|
formatTokens(agent.usage.contextSize)
|
|
1391
1714
|
] }) : null,
|
|
1392
|
-
agent?.costUsd !== void 0 ? /* @__PURE__ */
|
|
1715
|
+
agent?.costUsd !== void 0 ? /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1393
1716
|
" \xB7 ",
|
|
1394
1717
|
formatCost(agent.costUsd)
|
|
1395
1718
|
] }) : null
|
|
1396
1719
|
] }) }),
|
|
1397
|
-
/* @__PURE__ */
|
|
1398
|
-
elapsed === void 0 ? null : /* @__PURE__ */
|
|
1720
|
+
/* @__PURE__ */ jsxs4(Box4, { flexShrink: 0, children: [
|
|
1721
|
+
elapsed === void 0 ? null : /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1399
1722
|
formatDuration(elapsed),
|
|
1400
1723
|
" "
|
|
1401
1724
|
] }),
|
|
1402
|
-
/* @__PURE__ */
|
|
1725
|
+
/* @__PURE__ */ jsx4(Text4, { color: window.following ? COLOR.accent : COLOR.warn, children: window.following ? GLYPH.live : `${GLYPH.paused} ${window.scrolledBack}\u2191` })
|
|
1403
1726
|
] })
|
|
1404
1727
|
] }),
|
|
1405
|
-
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" }) : (
|
|
1406
1729
|
// The tail, already wrapped to `width` by `visibleLog` — the pane draws
|
|
1407
1730
|
// rows, and one log line is several of them. `visibleLog` also picks
|
|
1408
1731
|
// which rows, so the content here is exactly the pane's height and the
|
|
@@ -1418,10 +1741,10 @@ function StreamPanel({
|
|
|
1418
1741
|
// `flexShrink={0}` stops Yoga compressing the rows into each other,
|
|
1419
1742
|
// and `overflow="hidden"` is the backstop if any of the above is ever
|
|
1420
1743
|
// wrong.
|
|
1421
|
-
/* @__PURE__ */
|
|
1744
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexShrink: 0, height: viewport, overflow: "hidden", children: window.rows.map((row, index) => (
|
|
1422
1745
|
// Rows are an append-only tail; position is the identity.
|
|
1423
1746
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional log tail
|
|
1424
|
-
/* @__PURE__ */
|
|
1747
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { wrap: "truncate-end", children: row }) }, index)
|
|
1425
1748
|
)) })
|
|
1426
1749
|
)
|
|
1427
1750
|
] });
|
|
@@ -1432,14 +1755,14 @@ function ReplyPanel({ reply, width }) {
|
|
|
1432
1755
|
// `flexShrink={0}`: the body must clip this, not compress it. A shrunken
|
|
1433
1756
|
// column box folds its own text rows into each other before the body ever
|
|
1434
1757
|
// gets a chance to hide the overflow.
|
|
1435
|
-
/* @__PURE__ */
|
|
1436
|
-
/* @__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: [
|
|
1437
1760
|
asking ? GLYPH.ask : GLYPH.leader,
|
|
1438
1761
|
" ",
|
|
1439
1762
|
asking ? "the team needs a decision" : "answered"
|
|
1440
1763
|
] }),
|
|
1441
|
-
/* @__PURE__ */
|
|
1442
|
-
/* @__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." }) })
|
|
1443
1766
|
] })
|
|
1444
1767
|
);
|
|
1445
1768
|
}
|
|
@@ -1447,12 +1770,12 @@ function ErrorPanel({ message, width }) {
|
|
|
1447
1770
|
return (
|
|
1448
1771
|
// `flexShrink={0}` — see ReplyPanel. A compressed column box folds its own
|
|
1449
1772
|
// text rows into each other before the body gets a chance to clip them.
|
|
1450
|
-
/* @__PURE__ */
|
|
1451
|
-
/* @__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: [
|
|
1452
1775
|
GLYPH.warn,
|
|
1453
1776
|
" the run stopped"
|
|
1454
1777
|
] }),
|
|
1455
|
-
/* @__PURE__ */
|
|
1778
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: COLOR.error, children: message }) })
|
|
1456
1779
|
] })
|
|
1457
1780
|
);
|
|
1458
1781
|
}
|
|
@@ -1463,62 +1786,108 @@ function WelcomePanel({ turn, width }) {
|
|
|
1463
1786
|
return (
|
|
1464
1787
|
// `flexShrink={0}` — see ReplyPanel. Compressing the roster folds one agent's
|
|
1465
1788
|
// description onto the next row's, which reads as a corrupted screen.
|
|
1466
|
-
/* @__PURE__ */
|
|
1467
|
-
/* @__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: [
|
|
1468
1791
|
GLYPH.brand,
|
|
1469
1792
|
" multiclaw"
|
|
1470
1793
|
] }),
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
/* @__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) => {
|
|
1473
1796
|
const focused = turn.focusId === agent.id;
|
|
1474
1797
|
const leader = agent.id === leaderId;
|
|
1475
|
-
return /* @__PURE__ */
|
|
1476
|
-
/* @__PURE__ */
|
|
1477
|
-
/* @__PURE__ */
|
|
1478
|
-
/* @__PURE__ */
|
|
1479
|
-
/* @__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: [
|
|
1480
1803
|
" ",
|
|
1481
1804
|
padRight(truncate(agent.name, nameWidth - 2), nameWidth - 1)
|
|
1482
1805
|
] })
|
|
1483
1806
|
] }),
|
|
1484
|
-
/* @__PURE__ */
|
|
1807
|
+
/* @__PURE__ */ jsx4(Box4, { flexShrink: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, wrap: "truncate-end", children: agent.taskTitle ?? agent.description ?? "" }) })
|
|
1485
1808
|
] }, agent.id);
|
|
1486
1809
|
}) }),
|
|
1487
|
-
/* @__PURE__ */
|
|
1810
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Type a requirement and press enter." }) })
|
|
1488
1811
|
] })
|
|
1489
1812
|
);
|
|
1490
1813
|
}
|
|
1491
1814
|
|
|
1492
1815
|
// src/tui/components/prompt.tsx
|
|
1493
|
-
import { Box as
|
|
1494
|
-
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
|
+
}
|
|
1495
1851
|
function Prompt({
|
|
1496
1852
|
value,
|
|
1497
1853
|
cursor,
|
|
1498
1854
|
caretVisible,
|
|
1499
|
-
placeholder
|
|
1855
|
+
placeholder,
|
|
1856
|
+
width
|
|
1500
1857
|
}) {
|
|
1501
|
-
const
|
|
1858
|
+
const textWidth = Math.max(1, width - MARKER_WIDTH);
|
|
1859
|
+
const lines = promptLines(value, cursor, textWidth);
|
|
1502
1860
|
const ghost = value === "";
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
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
|
+
}) });
|
|
1515
1884
|
}
|
|
1516
1885
|
function CommandMenu({ rows, selected }) {
|
|
1517
1886
|
const labelWidth = Math.max(1, ...rows.map((row) => row.label.length));
|
|
1518
|
-
return /* @__PURE__ */
|
|
1887
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", paddingLeft: 2, children: rows.map((row, index) => {
|
|
1519
1888
|
const active = index === selected;
|
|
1520
|
-
return /* @__PURE__ */
|
|
1521
|
-
|
|
1889
|
+
return /* @__PURE__ */ jsxs5(
|
|
1890
|
+
Text5,
|
|
1522
1891
|
{
|
|
1523
1892
|
color: active ? COLOR.accent : void 0,
|
|
1524
1893
|
dimColor: !active,
|
|
@@ -1768,7 +2137,7 @@ function restoreConsole(workDir) {
|
|
|
1768
2137
|
}
|
|
1769
2138
|
|
|
1770
2139
|
// src/tui/App.tsx
|
|
1771
|
-
import { Fragment as
|
|
2140
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1772
2141
|
var FLUSH_INTERVAL_MS = 80;
|
|
1773
2142
|
var SPINNER_INTERVAL_MS = 110;
|
|
1774
2143
|
var BLINK_INTERVAL_MS = 530;
|
|
@@ -1800,19 +2169,19 @@ function StatusLine({
|
|
|
1800
2169
|
}) {
|
|
1801
2170
|
const { done, total } = progress(state);
|
|
1802
2171
|
if (error)
|
|
1803
|
-
return /* @__PURE__ */
|
|
2172
|
+
return /* @__PURE__ */ jsx6(Text6, { color: COLOR.error, wrap: "truncate-end", children: error });
|
|
1804
2173
|
if (state.warning && state.phase !== "complete")
|
|
1805
|
-
return /* @__PURE__ */
|
|
2174
|
+
return /* @__PURE__ */ jsx6(Text6, { color: COLOR.warn, wrap: "truncate-end", children: state.warning });
|
|
1806
2175
|
if ((cancelled || state.cancelled) && !running)
|
|
1807
|
-
return /* @__PURE__ */
|
|
2176
|
+
return /* @__PURE__ */ jsx6(Text6, { color: COLOR.warn, children: "cancelled \xB7 workspace changes are retained" });
|
|
1808
2177
|
if (state.reply) {
|
|
1809
2178
|
const asking = state.reply.mode === "ask";
|
|
1810
|
-
return /* @__PURE__ */
|
|
2179
|
+
return /* @__PURE__ */ jsx6(Text6, { color: asking ? COLOR.warn : COLOR.ok, wrap: "truncate-end", children: asking ? `${GLYPH.ask} needs your input` : `${STATUS.success.glyph} answered` });
|
|
1811
2180
|
}
|
|
1812
2181
|
if (running) {
|
|
1813
2182
|
const elapsed = turnElapsed(state, now);
|
|
1814
2183
|
const time = elapsed === void 0 ? "" : ` ${formatDuration(elapsed)}`;
|
|
1815
|
-
return /* @__PURE__ */
|
|
2184
|
+
return /* @__PURE__ */ jsxs6(Text6, { color: COLOR.accent, wrap: "truncate-end", children: [
|
|
1816
2185
|
spinner,
|
|
1817
2186
|
" running",
|
|
1818
2187
|
time,
|
|
@@ -1828,14 +2197,14 @@ function StatusLine({
|
|
|
1828
2197
|
const time = state.totalDuration === void 0 ? "" : ` ${formatDuration(state.totalDuration)}`;
|
|
1829
2198
|
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}`;
|
|
1830
2199
|
const cost = state.totalCostUsd === void 0 ? "" : ` \xB7 ${formatCost(state.totalCostUsd)}`;
|
|
1831
|
-
return /* @__PURE__ */
|
|
2200
|
+
return /* @__PURE__ */ jsxs6(Text6, { color: state.success ? COLOR.ok : COLOR.error, wrap: "truncate-end", children: [
|
|
1832
2201
|
outcome2,
|
|
1833
2202
|
cost,
|
|
1834
2203
|
state.warning ? ` \xB7 ${GLYPH.warn} ${state.warning}` : ""
|
|
1835
2204
|
] });
|
|
1836
2205
|
}
|
|
1837
2206
|
const stages = state.stages.length;
|
|
1838
|
-
return /* @__PURE__ */
|
|
2207
|
+
return /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate-end", children: [
|
|
1839
2208
|
"ready \xB7 ",
|
|
1840
2209
|
total,
|
|
1841
2210
|
" agents",
|
|
@@ -1892,7 +2261,11 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
1892
2261
|
const mountedRef = useRef(true);
|
|
1893
2262
|
const interactionsRef = useRef([]);
|
|
1894
2263
|
const [interactions, setInteractions] = useState([]);
|
|
1895
|
-
const [
|
|
2264
|
+
const [permissionMenu, setPermissionMenu] = useState({ id: "", cursor: void 0, released: false });
|
|
2265
|
+
const [formEntry, setFormEntry] = useState({
|
|
2266
|
+
id: "",
|
|
2267
|
+
state: initialForm()
|
|
2268
|
+
});
|
|
1896
2269
|
const requestPermission = useCallback((request, signal) => {
|
|
1897
2270
|
if (closingRef.current || signal.aborted) return Promise.resolve(void 0);
|
|
1898
2271
|
const shortId = `p${++permissionSerialRef.current}`;
|
|
@@ -2191,6 +2564,21 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2191
2564
|
const menuOpen = !helpOpen && !historyOpen && query !== void 0 && matches.length > 0;
|
|
2192
2565
|
const argOpen = !helpOpen && !historyOpen && arg !== void 0 && argRows.length > 0;
|
|
2193
2566
|
const paletteOpen = menuOpen || argOpen;
|
|
2567
|
+
const front = interactions[0];
|
|
2568
|
+
const menu = front?.kind === "permission" && permissionMenu.id === front.shortId ? permissionMenu : { id: front?.shortId ?? "", cursor: void 0, released: false };
|
|
2569
|
+
const frontQuestion = front?.kind === "question" ? front : void 0;
|
|
2570
|
+
const questionFields = useMemo(
|
|
2571
|
+
() => frontQuestion ? formFields(frontQuestion.request) : [],
|
|
2572
|
+
[frontQuestion]
|
|
2573
|
+
);
|
|
2574
|
+
const form = frontQuestion !== void 0 && formEntry.id === frontQuestion.shortId ? formEntry.state : initialForm();
|
|
2575
|
+
const formField = frontQuestion === void 0 ? void 0 : questionFields[form.field];
|
|
2576
|
+
const editingField = formField !== void 0 && (formField.kind === "text" || formField.kind === "number") ? formField : void 0;
|
|
2577
|
+
const promptRows = promptRowCount(
|
|
2578
|
+
line.text,
|
|
2579
|
+
line.cursor,
|
|
2580
|
+
Math.max(MIN_COLUMNS, columns) - MARKER_WIDTH
|
|
2581
|
+
);
|
|
2194
2582
|
const layout = computeLayout({
|
|
2195
2583
|
columns,
|
|
2196
2584
|
rows,
|
|
@@ -2198,6 +2586,8 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2198
2586
|
overlay: helpOpen || historyOpen,
|
|
2199
2587
|
menuRows: paletteOpen ? paletteTotal : 0,
|
|
2200
2588
|
queueRows: queue.length,
|
|
2589
|
+
interactionRows: interactionRows(interactions, Math.max(MIN_COLUMNS, columns)),
|
|
2590
|
+
promptRows,
|
|
2201
2591
|
notice: notice !== void 0 || queuePaused && queue.length > 0,
|
|
2202
2592
|
viewing: state.view !== void 0,
|
|
2203
2593
|
hasRail: shown.leaderIds.length > 0 || shown.stages.length > 0,
|
|
@@ -2281,23 +2671,40 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2281
2671
|
}
|
|
2282
2672
|
case "approve":
|
|
2283
2673
|
case "deny": {
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2674
|
+
const onScreen = interactionsRef.current[0];
|
|
2675
|
+
const byId = (id) => {
|
|
2676
|
+
const found = interactionsRef.current.filter(
|
|
2677
|
+
(item) => item.request.requestId === id || item.shortId === id
|
|
2678
|
+
);
|
|
2679
|
+
if (found.length !== 1) setNotice(`unknown or stale permission request: ${id}`);
|
|
2680
|
+
return found.length === 1 ? found[0] : void 0;
|
|
2681
|
+
};
|
|
2682
|
+
let entry;
|
|
2683
|
+
let number;
|
|
2684
|
+
if (name === "approve" && args.length === 1 && /^\d+$/.test(args[0])) {
|
|
2685
|
+
number = args[0];
|
|
2686
|
+
entry = onScreen;
|
|
2687
|
+
} else if (name === "approve" && args.length === 2) {
|
|
2688
|
+
number = args[1];
|
|
2689
|
+
entry = byId(args[0]);
|
|
2690
|
+
} else if (name === "deny" && args.length === 0) {
|
|
2691
|
+
entry = onScreen;
|
|
2692
|
+
} else if (name === "deny" && args.length === 1) {
|
|
2693
|
+
entry = byId(args[0]);
|
|
2694
|
+
} else {
|
|
2286
2695
|
setNotice(
|
|
2287
|
-
name === "approve" ? "usage: /approve
|
|
2696
|
+
name === "approve" ? "usage: /approve [id] <option number>" : "usage: /deny [id]"
|
|
2288
2697
|
);
|
|
2289
2698
|
return;
|
|
2290
2699
|
}
|
|
2291
|
-
|
|
2292
|
-
(
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
setNotice(`unknown or stale permission request: ${id}`);
|
|
2700
|
+
if (entry === void 0) {
|
|
2701
|
+
if (args.length === 0 || /^\d+$/.test(args[0] ?? "")) {
|
|
2702
|
+
setNotice("no permission request on screen");
|
|
2703
|
+
}
|
|
2296
2704
|
return;
|
|
2297
2705
|
}
|
|
2298
|
-
const entry = matches2[0];
|
|
2299
2706
|
if (entry.kind !== "permission") {
|
|
2300
|
-
setNotice(
|
|
2707
|
+
setNotice("the request on screen is a question, not a permission");
|
|
2301
2708
|
return;
|
|
2302
2709
|
}
|
|
2303
2710
|
if (name === "deny") {
|
|
@@ -2313,7 +2720,7 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2313
2720
|
return;
|
|
2314
2721
|
}
|
|
2315
2722
|
entry.settle(option.optionId);
|
|
2316
|
-
setNotice(`selected ${option.name} for ${entry.shortId}`);
|
|
2723
|
+
setNotice(`selected ${clean(option.name)} for ${entry.shortId}`);
|
|
2317
2724
|
return;
|
|
2318
2725
|
}
|
|
2319
2726
|
case "task": {
|
|
@@ -2638,7 +3045,8 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2638
3045
|
setLine((l) => ({ ...l, cursor: l.text.length }));
|
|
2639
3046
|
return;
|
|
2640
3047
|
}
|
|
2641
|
-
|
|
3048
|
+
const formFront = front?.kind === "question" && !isCommandLine(line.text) ? front : void 0;
|
|
3049
|
+
if (key.tab && formFront === void 0) {
|
|
2642
3050
|
dispatch({ type: "focus:cycle", delta: key.shift ? -1 : 1 });
|
|
2643
3051
|
return;
|
|
2644
3052
|
}
|
|
@@ -2661,19 +3069,111 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2661
3069
|
return;
|
|
2662
3070
|
}
|
|
2663
3071
|
}
|
|
2664
|
-
if (
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
3072
|
+
if (front?.kind === "permission" && line.text === "" && !menu.released) {
|
|
3073
|
+
const total = permissionChoices(front.request).length;
|
|
3074
|
+
if (key.upArrow || key.downArrow) {
|
|
3075
|
+
setPermissionMenu({
|
|
3076
|
+
id: front.shortId,
|
|
3077
|
+
cursor: moveCursor(menu.cursor, key.upArrow ? -1 : 1, total),
|
|
3078
|
+
released: false
|
|
3079
|
+
});
|
|
3080
|
+
return;
|
|
3081
|
+
}
|
|
3082
|
+
if (/^[1-9]$/.test(char) && !key.ctrl && !key.meta) {
|
|
3083
|
+
const cursor = jumpCursor(char, total);
|
|
3084
|
+
if (cursor !== void 0) {
|
|
3085
|
+
setPermissionMenu({ id: front.shortId, cursor, released: false });
|
|
3086
|
+
return;
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
if (key.return) {
|
|
3090
|
+
const choice = menu.cursor === void 0 ? void 0 : permissionChoices(front.request)[menu.cursor];
|
|
3091
|
+
if (choice !== void 0) {
|
|
3092
|
+
front.settle(choice.optionId);
|
|
3093
|
+
setNotice(`chose ${choice.label} \xB7 ${clean(front.request.agentId)}`);
|
|
3094
|
+
}
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
if (formFront !== void 0) {
|
|
3099
|
+
const front2 = formFront;
|
|
3100
|
+
const total = questionFields.length;
|
|
3101
|
+
const field = questionFields[form.field];
|
|
3102
|
+
const committed = withDraft(questionFields, form, line.text);
|
|
3103
|
+
const save = (state2, draft) => {
|
|
3104
|
+
setFormEntry({ id: front2.shortId, state: state2 });
|
|
3105
|
+
if (draft !== void 0) setLine({ text: draft, cursor: draft.length });
|
|
3106
|
+
};
|
|
3107
|
+
const moveFocus = (index, state2 = committed) => {
|
|
3108
|
+
const next = focusField(questionFields, state2, index);
|
|
3109
|
+
save(next.state, next.draft);
|
|
3110
|
+
};
|
|
3111
|
+
const submit2 = (state2) => {
|
|
3112
|
+
if (!formComplete(questionFields, state2)) {
|
|
3113
|
+
const missing = questionFields.findIndex((item) => !formComplete([item], state2));
|
|
3114
|
+
if (missing >= 0) {
|
|
3115
|
+
setNotice(`${questionFields[missing].title} still needs a value`);
|
|
3116
|
+
if (missing !== form.field) moveFocus(missing, state2);
|
|
3117
|
+
}
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
const content = formContent(questionFields, state2);
|
|
3121
|
+
if (!validateRuntimeQuestionContent(front2.request.requestedSchema, content)) {
|
|
3122
|
+
setNotice("answer does not match the question schema");
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
front2.settle({ action: "accept", content });
|
|
3126
|
+
setFormEntry({ id: "", state: initialForm() });
|
|
3127
|
+
setLine(EMPTY_LINE);
|
|
3128
|
+
setNotice(`answered ${front2.shortId}`);
|
|
3129
|
+
};
|
|
3130
|
+
const advance = (state2) => {
|
|
3131
|
+
if (form.field >= total - 1) submit2(state2);
|
|
3132
|
+
else moveFocus(form.field + 1, state2);
|
|
3133
|
+
};
|
|
3134
|
+
if (field === void 0) {
|
|
3135
|
+
if (key.return) submit2(committed);
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
if (key.tab) {
|
|
3139
|
+
moveFocus(key.shift ? form.field - 1 : form.field + 1);
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
if (field.choices.length > 0 && (key.upArrow || key.downArrow)) {
|
|
3143
|
+
save(movePick(committed, field, key.upArrow ? -1 : 1));
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
if (field.choices.length > 0 && (key.leftArrow || key.rightArrow)) {
|
|
3147
|
+
save(movePick(committed, field, key.leftArrow ? -1 : 1));
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
if (key.upArrow || key.downArrow) {
|
|
3151
|
+
moveFocus(key.upArrow ? form.field - 1 : form.field + 1);
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
if (char === " " && field.kind === "multi") {
|
|
3155
|
+
save(toggleChoice(committed, field));
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
if (field.kind === "boolean" && (char === " " || key.leftArrow || key.rightArrow)) {
|
|
3159
|
+
save(toggleBoolean(committed, field));
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
if (field.choices.length > 0 && /^[1-9]$/.test(char) && !key.ctrl && !key.meta) {
|
|
3163
|
+
save(jumpPick(committed, field, char));
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
if (key.return) {
|
|
3167
|
+
advance(field.kind === "choice" ? commitField(questionFields, committed) : committed);
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
if (key.escape) {
|
|
3171
|
+
if (field.kind === "text" || field.kind === "number") {
|
|
3172
|
+
save({ ...committed, values: { ...committed.values, [field.key]: "" } });
|
|
3173
|
+
setLine(EMPTY_LINE);
|
|
3174
|
+
}
|
|
3175
|
+
return;
|
|
3176
|
+
}
|
|
2677
3177
|
}
|
|
2678
3178
|
if (key.upArrow) {
|
|
2679
3179
|
dispatch({
|
|
@@ -2744,6 +3244,9 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2744
3244
|
dispatch({ type: "view:close" });
|
|
2745
3245
|
return;
|
|
2746
3246
|
}
|
|
3247
|
+
if (front !== void 0) {
|
|
3248
|
+
setPermissionMenu({ id: front.shortId, cursor: void 0, released: true });
|
|
3249
|
+
}
|
|
2747
3250
|
setLine(EMPTY_LINE);
|
|
2748
3251
|
setNotice(void 0);
|
|
2749
3252
|
return;
|
|
@@ -2761,33 +3264,37 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2761
3264
|
const handlerRef = useRef(handleKey);
|
|
2762
3265
|
handlerRef.current = handleKey;
|
|
2763
3266
|
useInput((char, key) => handlerRef.current(char, key));
|
|
2764
|
-
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 ?
|
|
2765
|
-
|
|
2766
|
-
|
|
3267
|
+
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 ? (
|
|
3268
|
+
// Too short to dock the bar, so the commands are the only way in.
|
|
3269
|
+
"decide with /approve 1 \xB7 /deny \xB7 /respond"
|
|
3270
|
+
) : 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";
|
|
3271
|
+
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;
|
|
3272
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: layout.rows, children: [
|
|
3273
|
+
/* @__PURE__ */ jsx6(
|
|
2767
3274
|
Header,
|
|
2768
3275
|
{
|
|
2769
3276
|
name: state.name || config.name,
|
|
2770
3277
|
right: state.view ? `turn ${shown.turn} \xB7 viewing` : `turn ${shown.turn} \xB7 ${running ? formatDuration(turnElapsed(state, now) ?? 0) : state.phase === "complete" ? "complete" : "ready"}`
|
|
2771
3278
|
}
|
|
2772
3279
|
),
|
|
2773
|
-
/* @__PURE__ */
|
|
2774
|
-
config.useLeader === false ? /* @__PURE__ */
|
|
3280
|
+
/* @__PURE__ */ jsx6(Rule, { width: layout.columns }),
|
|
3281
|
+
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,
|
|
2775
3282
|
layout.showRail ? (
|
|
2776
3283
|
// The rail wraps, so it can be taller than one row. Cap it at the height
|
|
2777
3284
|
// the budget reserved and clip, rather than letting the extra rows push
|
|
2778
3285
|
// the chrome below it off the screen.
|
|
2779
|
-
/* @__PURE__ */
|
|
3286
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, height: layout.railRows, overflow: "hidden", flexShrink: 0, children: /* @__PURE__ */ jsx6(PipelineRail, { turn: shown, spinner }) })
|
|
2780
3287
|
) : null,
|
|
2781
|
-
shown.reason ? /* @__PURE__ */
|
|
2782
|
-
/* @__PURE__ */
|
|
2783
|
-
|
|
3288
|
+
shown.reason ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: ` reason: ${shown.reason}` }) : null,
|
|
3289
|
+
/* @__PURE__ */ jsx6(
|
|
3290
|
+
Box6,
|
|
2784
3291
|
{
|
|
2785
3292
|
marginTop: 1,
|
|
2786
3293
|
height: layout.bodyHeight,
|
|
2787
3294
|
flexShrink: 0,
|
|
2788
3295
|
flexDirection: "column",
|
|
2789
3296
|
overflow: "hidden",
|
|
2790
|
-
children: helpOpen ? /* @__PURE__ */
|
|
3297
|
+
children: helpOpen ? /* @__PURE__ */ jsx6(HelpOverlay, { width: layout.columns, maxRows: layout.bodyHeight }) : historyOpen ? /* @__PURE__ */ jsx6(
|
|
2791
3298
|
HistoryOverlay,
|
|
2792
3299
|
{
|
|
2793
3300
|
turns: historyTurns,
|
|
@@ -2795,36 +3302,17 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2795
3302
|
width: layout.columns,
|
|
2796
3303
|
maxRows: layout.bodyHeight
|
|
2797
3304
|
}
|
|
2798
|
-
) :
|
|
2799
|
-
QuestionOverlay,
|
|
2800
|
-
{
|
|
2801
|
-
request: interactions[0].request,
|
|
2802
|
-
shortId: interactions[0].shortId,
|
|
2803
|
-
queued: interactions.length,
|
|
2804
|
-
width: layout.columns,
|
|
2805
|
-
maxRows: layout.bodyHeight,
|
|
2806
|
-
offset: questionScroll.id === interactions[0].shortId ? questionScroll.offset : 0
|
|
2807
|
-
}
|
|
2808
|
-
) : interactions[0]?.kind === "permission" ? /* @__PURE__ */ jsx5(
|
|
2809
|
-
PermissionOverlay,
|
|
2810
|
-
{
|
|
2811
|
-
request: interactions[0].request,
|
|
2812
|
-
shortId: interactions[0].shortId,
|
|
2813
|
-
queued: interactions.length,
|
|
2814
|
-
width: layout.columns,
|
|
2815
|
-
maxRows: layout.bodyHeight
|
|
2816
|
-
}
|
|
2817
|
-
) : layout.welcome ? /* @__PURE__ */ jsx5(WelcomePanel, { turn: state, width: layout.columns }) : error ? (
|
|
3305
|
+
) : layout.welcome ? /* @__PURE__ */ jsx6(WelcomePanel, { turn: state, width: layout.columns }) : error ? (
|
|
2818
3306
|
// The error wins over a reply: a run that stopped with an error is the
|
|
2819
3307
|
// most urgent thing on screen, and a stale answer underneath it would
|
|
2820
3308
|
// read as the outcome. The status line keeps a one-line version too.
|
|
2821
|
-
/* @__PURE__ */
|
|
2822
|
-
) : shown.reply ? /* @__PURE__ */
|
|
2823
|
-
layout.showTeam ? /* @__PURE__ */
|
|
2824
|
-
/* @__PURE__ */
|
|
2825
|
-
/* @__PURE__ */
|
|
3309
|
+
/* @__PURE__ */ jsx6(ErrorPanel, { message: error, width: layout.columns })
|
|
3310
|
+
) : shown.reply ? /* @__PURE__ */ jsx6(ReplyPanel, { reply: shown.reply, width: layout.columns }) : /* @__PURE__ */ jsxs6(Box6, { height: layout.bodyHeight, flexShrink: 0, flexDirection: "row", children: [
|
|
3311
|
+
layout.showTeam ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3312
|
+
/* @__PURE__ */ jsx6(TeamPanel, { turn: shown, width: layout.teamWidth, now, spinner }),
|
|
3313
|
+
/* @__PURE__ */ jsx6(Divider, {})
|
|
2826
3314
|
] }) : null,
|
|
2827
|
-
/* @__PURE__ */
|
|
3315
|
+
/* @__PURE__ */ jsx6(
|
|
2828
3316
|
StreamPanel,
|
|
2829
3317
|
{
|
|
2830
3318
|
turn: shown,
|
|
@@ -2837,26 +3325,59 @@ function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }
|
|
|
2837
3325
|
] })
|
|
2838
3326
|
}
|
|
2839
3327
|
),
|
|
2840
|
-
state.view ? /* @__PURE__ */
|
|
2841
|
-
notice || queuePaused && queue.length > 0 ? /* @__PURE__ */
|
|
3328
|
+
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,
|
|
3329
|
+
notice || queuePaused && queue.length > 0 ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate-end", children: [
|
|
2842
3330
|
" ",
|
|
2843
3331
|
notice ?? (awaitingAnswer ? "queue paused \u2014 answer the leader first" : "queue paused \u2014 /continue resumes it")
|
|
2844
3332
|
] }) : null,
|
|
2845
|
-
/* @__PURE__ */
|
|
2846
|
-
|
|
2847
|
-
|
|
3333
|
+
/* @__PURE__ */ jsx6(QueueStrip, { items: queue, max: layout.queueRows }),
|
|
3334
|
+
front === void 0 || layout.interactionRows === 0 ? null : /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3335
|
+
/* @__PURE__ */ jsx6(Rule, { width: layout.columns }),
|
|
3336
|
+
/* @__PURE__ */ jsx6(
|
|
3337
|
+
Box6,
|
|
3338
|
+
{
|
|
3339
|
+
height: layout.interactionRows,
|
|
3340
|
+
overflow: "hidden",
|
|
3341
|
+
flexShrink: 0,
|
|
3342
|
+
flexDirection: "column",
|
|
3343
|
+
children: front.kind === "permission" ? /* @__PURE__ */ jsx6(
|
|
3344
|
+
PermissionBar,
|
|
3345
|
+
{
|
|
3346
|
+
request: front.request,
|
|
3347
|
+
others: interactions.slice(1).flatMap((entry) => entry.kind === "permission" ? [entry.request] : []),
|
|
3348
|
+
cursor: menu.cursor,
|
|
3349
|
+
released: menu.released,
|
|
3350
|
+
width: layout.columns
|
|
3351
|
+
}
|
|
3352
|
+
) : /* @__PURE__ */ jsx6(
|
|
3353
|
+
QuestionForm,
|
|
3354
|
+
{
|
|
3355
|
+
request: front.request,
|
|
3356
|
+
others: interactions.slice(1).flatMap((entry) => entry.kind === "question" ? [entry.request] : []),
|
|
3357
|
+
fields: questionFields,
|
|
3358
|
+
form,
|
|
3359
|
+
draft: line.text,
|
|
3360
|
+
width: layout.columns
|
|
3361
|
+
}
|
|
3362
|
+
)
|
|
3363
|
+
}
|
|
3364
|
+
)
|
|
3365
|
+
] }),
|
|
3366
|
+
paletteOpen ? /* @__PURE__ */ jsx6(CommandMenu, { rows: paletteWindow, selected: paletteSelected }) : null,
|
|
3367
|
+
/* @__PURE__ */ jsx6(
|
|
2848
3368
|
Prompt,
|
|
2849
3369
|
{
|
|
2850
3370
|
value: line.text,
|
|
2851
3371
|
cursor: line.cursor,
|
|
2852
3372
|
caretVisible: caretOn,
|
|
2853
|
-
|
|
3373
|
+
width: layout.columns,
|
|
3374
|
+
placeholder
|
|
2854
3375
|
}
|
|
2855
3376
|
),
|
|
2856
|
-
/* @__PURE__ */
|
|
3377
|
+
/* @__PURE__ */ jsx6(
|
|
2857
3378
|
HintBar,
|
|
2858
3379
|
{
|
|
2859
|
-
status: /* @__PURE__ */
|
|
3380
|
+
status: /* @__PURE__ */ jsx6(
|
|
2860
3381
|
StatusLine,
|
|
2861
3382
|
{
|
|
2862
3383
|
state,
|
|
@@ -2907,11 +3428,13 @@ async function startTui(options) {
|
|
|
2907
3428
|
export {
|
|
2908
3429
|
COMPACT_COLUMNS,
|
|
2909
3430
|
MAX_HISTORY,
|
|
3431
|
+
MAX_INTERACTION_ROWS,
|
|
2910
3432
|
MAX_LOG_CHARS,
|
|
2911
3433
|
MAX_MENU_ROWS,
|
|
2912
3434
|
MAX_QUEUE_ROWS,
|
|
2913
3435
|
MAX_RAIL_ROWS,
|
|
2914
3436
|
MIN_COLUMNS,
|
|
3437
|
+
MIN_INTERACTION_ROWS,
|
|
2915
3438
|
MIN_ROWS,
|
|
2916
3439
|
RAIL_MIN_ROWS,
|
|
2917
3440
|
agentElapsed,
|