@higherdev/cli 0.1.4 → 0.2.1
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/index.js +2412 -682
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -342,18 +342,6 @@ var init_blocked = __esm({
|
|
|
342
342
|
});
|
|
343
343
|
|
|
344
344
|
// ../../packages/db/src/budgets.ts
|
|
345
|
-
function parseOptionalCap(raw, opts = {}) {
|
|
346
|
-
const text = String(raw ?? "").trim();
|
|
347
|
-
if (text === "") return { ok: true, value: null };
|
|
348
|
-
const n = Number(text);
|
|
349
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
350
|
-
return { ok: false, error: "Budget caps must be a positive number, or empty for unlimited." };
|
|
351
|
-
}
|
|
352
|
-
if (opts.integer && !Number.isInteger(n)) {
|
|
353
|
-
return { ok: false, error: "Runs per hour must be a whole number, or empty for unlimited." };
|
|
354
|
-
}
|
|
355
|
-
return { ok: true, value: n };
|
|
356
|
-
}
|
|
357
345
|
var init_budgets = __esm({
|
|
358
346
|
"../../packages/db/src/budgets.ts"() {
|
|
359
347
|
"use strict";
|
|
@@ -522,6 +510,88 @@ var init_commands = __esm({
|
|
|
522
510
|
}
|
|
523
511
|
});
|
|
524
512
|
|
|
513
|
+
// ../../packages/db/src/status.ts
|
|
514
|
+
function statusLabel(status) {
|
|
515
|
+
return status.replaceAll("_", " ");
|
|
516
|
+
}
|
|
517
|
+
function statusTone(status) {
|
|
518
|
+
switch (status) {
|
|
519
|
+
case "queued":
|
|
520
|
+
case "running":
|
|
521
|
+
case "in_review":
|
|
522
|
+
return "blue";
|
|
523
|
+
case "approved":
|
|
524
|
+
case "merged":
|
|
525
|
+
case "ready":
|
|
526
|
+
return "success";
|
|
527
|
+
case "blocked":
|
|
528
|
+
case "needs_decision":
|
|
529
|
+
case "changes_requested":
|
|
530
|
+
return "warning";
|
|
531
|
+
case "failed":
|
|
532
|
+
case "cancelled":
|
|
533
|
+
return "danger";
|
|
534
|
+
default:
|
|
535
|
+
return "muted";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function resolveTicketStatus(requested, blockerStatuses2) {
|
|
539
|
+
if (requested === "cancelled") return "cancelled";
|
|
540
|
+
const open = blockerStatuses2.filter((status) => status !== "merged" && status !== "cancelled");
|
|
541
|
+
if (open.length > 0) return "blocked";
|
|
542
|
+
if (requested === "blocked") return "backlog";
|
|
543
|
+
return requested;
|
|
544
|
+
}
|
|
545
|
+
function initialHumanStatus(opts) {
|
|
546
|
+
const groomed = Boolean(opts.acceptanceMd.trim() && opts.area && opts.assigned);
|
|
547
|
+
return resolveTicketStatus(groomed ? "ready" : "backlog", opts.blockerStatuses);
|
|
548
|
+
}
|
|
549
|
+
function statusAfterAnswer(opts) {
|
|
550
|
+
return opts.assigned && Boolean(opts.acceptanceMd.trim()) ? "queued" : "ready";
|
|
551
|
+
}
|
|
552
|
+
function handBackStatus(opts) {
|
|
553
|
+
if (opts.acceptanceMd.trim() && opts.assigned) {
|
|
554
|
+
return resolveTicketStatus(statusAfterAnswer(opts), opts.blockerStatuses);
|
|
555
|
+
}
|
|
556
|
+
return initialHumanStatus(opts);
|
|
557
|
+
}
|
|
558
|
+
function cancelledSuffix(cancelled) {
|
|
559
|
+
return cancelled > 0 ? ` +${cancelled} cancelled` : "";
|
|
560
|
+
}
|
|
561
|
+
function epicProgressCaption(merged, total, cancelled = 0) {
|
|
562
|
+
if (total === 0 && cancelled === 0) return "No tickets";
|
|
563
|
+
return `${merged}/${total} merged${cancelledSuffix(cancelled)}`;
|
|
564
|
+
}
|
|
565
|
+
var BOARD_COLUMNS;
|
|
566
|
+
var init_status = __esm({
|
|
567
|
+
"../../packages/db/src/status.ts"() {
|
|
568
|
+
"use strict";
|
|
569
|
+
init_enums();
|
|
570
|
+
BOARD_COLUMNS = [
|
|
571
|
+
"backlog",
|
|
572
|
+
"ready",
|
|
573
|
+
"blocked",
|
|
574
|
+
"queued",
|
|
575
|
+
"running",
|
|
576
|
+
"failed",
|
|
577
|
+
"needs_decision",
|
|
578
|
+
"in_review",
|
|
579
|
+
"changes_requested",
|
|
580
|
+
"approved",
|
|
581
|
+
"merged",
|
|
582
|
+
"cancelled"
|
|
583
|
+
];
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// ../../packages/db/src/cockpit.ts
|
|
588
|
+
var init_cockpit = __esm({
|
|
589
|
+
"../../packages/db/src/cockpit.ts"() {
|
|
590
|
+
"use strict";
|
|
591
|
+
init_status();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
525
595
|
// ../../packages/db/src/defaults.ts
|
|
526
596
|
function slugify(name) {
|
|
527
597
|
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -549,6 +619,14 @@ var init_defaults = __esm({
|
|
|
549
619
|
}
|
|
550
620
|
});
|
|
551
621
|
|
|
622
|
+
// ../../packages/db/src/effective-config.ts
|
|
623
|
+
var init_effective_config = __esm({
|
|
624
|
+
"../../packages/db/src/effective-config.ts"() {
|
|
625
|
+
"use strict";
|
|
626
|
+
init_enums();
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
552
630
|
// ../../packages/db/src/epic-progress.ts
|
|
553
631
|
function round4(value) {
|
|
554
632
|
return Math.round(value * 1e4) / 1e4;
|
|
@@ -722,6 +800,10 @@ var init_schemas = __esm({
|
|
|
722
800
|
timeout_minutes: z.number().int().positive(),
|
|
723
801
|
max_attempts: z.number().int().positive(),
|
|
724
802
|
pr_size_target: z.number().int().positive(),
|
|
803
|
+
orchestrator_max_cycles: z.number().int().min(1).max(12).optional(),
|
|
804
|
+
orchestrator_max_total_turns: z.number().int().min(1).max(1e3).optional(),
|
|
805
|
+
orchestrator_sweep_interval_minutes: z.number().int().min(1).max(60).optional(),
|
|
806
|
+
orchestrator_stall_minutes: z.number().int().min(5).max(1440).optional(),
|
|
725
807
|
webhook_url: z.union([z.url(), z.literal("")]).optional(),
|
|
726
808
|
smoke_url: z.union([z.url(), z.literal("")]).optional(),
|
|
727
809
|
quiet_hours: z.object({
|
|
@@ -927,80 +1009,6 @@ var init_schemas = __esm({
|
|
|
927
1009
|
}
|
|
928
1010
|
});
|
|
929
1011
|
|
|
930
|
-
// ../../packages/db/src/status.ts
|
|
931
|
-
function statusLabel(status) {
|
|
932
|
-
return status.replaceAll("_", " ");
|
|
933
|
-
}
|
|
934
|
-
function statusTone(status) {
|
|
935
|
-
switch (status) {
|
|
936
|
-
case "queued":
|
|
937
|
-
case "running":
|
|
938
|
-
case "in_review":
|
|
939
|
-
return "blue";
|
|
940
|
-
case "approved":
|
|
941
|
-
case "merged":
|
|
942
|
-
case "ready":
|
|
943
|
-
return "success";
|
|
944
|
-
case "blocked":
|
|
945
|
-
case "needs_decision":
|
|
946
|
-
case "changes_requested":
|
|
947
|
-
return "warning";
|
|
948
|
-
case "failed":
|
|
949
|
-
case "cancelled":
|
|
950
|
-
return "danger";
|
|
951
|
-
default:
|
|
952
|
-
return "muted";
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
function resolveTicketStatus(requested, blockerStatuses2) {
|
|
956
|
-
if (requested === "cancelled") return "cancelled";
|
|
957
|
-
const open = blockerStatuses2.filter((status) => status !== "merged" && status !== "cancelled");
|
|
958
|
-
if (open.length > 0) return "blocked";
|
|
959
|
-
if (requested === "blocked") return "backlog";
|
|
960
|
-
return requested;
|
|
961
|
-
}
|
|
962
|
-
function initialHumanStatus(opts) {
|
|
963
|
-
const groomed = Boolean(opts.acceptanceMd.trim() && opts.area && opts.assigned);
|
|
964
|
-
return resolveTicketStatus(groomed ? "ready" : "backlog", opts.blockerStatuses);
|
|
965
|
-
}
|
|
966
|
-
function statusAfterAnswer(opts) {
|
|
967
|
-
return opts.assigned && Boolean(opts.acceptanceMd.trim()) ? "queued" : "ready";
|
|
968
|
-
}
|
|
969
|
-
function handBackStatus(opts) {
|
|
970
|
-
if (opts.acceptanceMd.trim() && opts.assigned) {
|
|
971
|
-
return resolveTicketStatus(statusAfterAnswer(opts), opts.blockerStatuses);
|
|
972
|
-
}
|
|
973
|
-
return initialHumanStatus(opts);
|
|
974
|
-
}
|
|
975
|
-
function cancelledSuffix(cancelled) {
|
|
976
|
-
return cancelled > 0 ? ` +${cancelled} cancelled` : "";
|
|
977
|
-
}
|
|
978
|
-
function epicProgressCaption(merged, total, cancelled = 0) {
|
|
979
|
-
if (total === 0 && cancelled === 0) return "No tickets";
|
|
980
|
-
return `${merged}/${total} merged${cancelledSuffix(cancelled)}`;
|
|
981
|
-
}
|
|
982
|
-
var BOARD_COLUMNS;
|
|
983
|
-
var init_status = __esm({
|
|
984
|
-
"../../packages/db/src/status.ts"() {
|
|
985
|
-
"use strict";
|
|
986
|
-
init_enums();
|
|
987
|
-
BOARD_COLUMNS = [
|
|
988
|
-
"backlog",
|
|
989
|
-
"ready",
|
|
990
|
-
"blocked",
|
|
991
|
-
"queued",
|
|
992
|
-
"running",
|
|
993
|
-
"failed",
|
|
994
|
-
"needs_decision",
|
|
995
|
-
"in_review",
|
|
996
|
-
"changes_requested",
|
|
997
|
-
"approved",
|
|
998
|
-
"merged",
|
|
999
|
-
"cancelled"
|
|
1000
|
-
];
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
1012
|
// ../../packages/db/src/stuck.ts
|
|
1005
1013
|
function stuckReason(opts) {
|
|
1006
1014
|
const { ticket } = opts;
|
|
@@ -1173,125 +1181,6 @@ var init_ticket_writes = __esm({
|
|
|
1173
1181
|
}
|
|
1174
1182
|
});
|
|
1175
1183
|
|
|
1176
|
-
// ../../packages/db/src/timeline.ts
|
|
1177
|
-
function formatDuration(ms) {
|
|
1178
|
-
const totalSeconds = Math.max(0, Math.round(ms / 1e3));
|
|
1179
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
1180
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1181
|
-
const seconds = totalSeconds % 60;
|
|
1182
|
-
if (hours > 0) return minutes ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
1183
|
-
if (minutes > 0) return seconds ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
1184
|
-
return `${seconds}s`;
|
|
1185
|
-
}
|
|
1186
|
-
function msBetween(from, to) {
|
|
1187
|
-
const start = Date.parse(from);
|
|
1188
|
-
const end = Date.parse(to);
|
|
1189
|
-
if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
|
|
1190
|
-
return end - start;
|
|
1191
|
-
}
|
|
1192
|
-
function clip(text, max = 160) {
|
|
1193
|
-
const one2 = text.trim().replace(/\s+/g, " ");
|
|
1194
|
-
if (one2.length <= max) return one2;
|
|
1195
|
-
return `${one2.slice(0, max - 3)}...`;
|
|
1196
|
-
}
|
|
1197
|
-
function clock(iso) {
|
|
1198
|
-
if (!iso) return "";
|
|
1199
|
-
const date = new Date(iso);
|
|
1200
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
1201
|
-
return date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
1202
|
-
}
|
|
1203
|
-
function runSpan(run5) {
|
|
1204
|
-
const start = clock(run5.started_at ?? run5.created_at);
|
|
1205
|
-
const end = clock(run5.ended_at);
|
|
1206
|
-
if (start && end) return `${start}-${end}`;
|
|
1207
|
-
if (start && !run5.ended_at) return `${start}-`;
|
|
1208
|
-
return start;
|
|
1209
|
-
}
|
|
1210
|
-
function runDurationMs(run5, nowIso) {
|
|
1211
|
-
const start = run5.started_at ?? run5.created_at;
|
|
1212
|
-
if (run5.ended_at) return msBetween(start, run5.ended_at);
|
|
1213
|
-
if (run5.status === "running" || run5.status === "queued") return msBetween(start, nowIso);
|
|
1214
|
-
return null;
|
|
1215
|
-
}
|
|
1216
|
-
function buildTimeline(input) {
|
|
1217
|
-
const nowIso = typeof input.now === "string" ? input.now : new Date(input.now ?? Date.now()).toISOString();
|
|
1218
|
-
const items = [];
|
|
1219
|
-
const statuses = [...input.statusEvents].sort((a, b) => a.at.localeCompare(b.at) || a.id.localeCompare(b.id));
|
|
1220
|
-
for (let i = 0; i < statuses.length; i++) {
|
|
1221
|
-
const event = statuses[i];
|
|
1222
|
-
const next = statuses[i + 1];
|
|
1223
|
-
const from = event.from_status ? statusLabel(event.from_status) : "new";
|
|
1224
|
-
const to = statusLabel(event.to_status);
|
|
1225
|
-
items.push({
|
|
1226
|
-
id: `status:${event.id}`,
|
|
1227
|
-
at: event.at,
|
|
1228
|
-
kind: "status",
|
|
1229
|
-
title: `${from} to ${to}`,
|
|
1230
|
-
durationMs: msBetween(event.at, next?.at ?? nowIso),
|
|
1231
|
-
endedAt: next?.at ?? null
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
for (const run5 of input.runs) {
|
|
1235
|
-
const at = run5.started_at ?? run5.created_at;
|
|
1236
|
-
const span = runSpan(run5);
|
|
1237
|
-
const kind = run5.kind === "review" ? "verdict" : "run";
|
|
1238
|
-
items.push({
|
|
1239
|
-
id: `run:${run5.id}`,
|
|
1240
|
-
at,
|
|
1241
|
-
kind,
|
|
1242
|
-
title: [
|
|
1243
|
-
run5.kind,
|
|
1244
|
-
run5.provider,
|
|
1245
|
-
run5.status,
|
|
1246
|
-
run5.reviewed_sha ? `@ ${run5.reviewed_sha.slice(0, 12)}` : "",
|
|
1247
|
-
span
|
|
1248
|
-
].filter(Boolean).join(" "),
|
|
1249
|
-
detail: run5.summary ? clip(run5.summary) : void 0,
|
|
1250
|
-
durationMs: runDurationMs(run5, nowIso),
|
|
1251
|
-
endedAt: run5.ended_at
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
for (const message of input.messages) {
|
|
1255
|
-
const kind = message.from_role === "reviewer" ? "verdict" : "message";
|
|
1256
|
-
items.push({
|
|
1257
|
-
id: `message:${message.id}`,
|
|
1258
|
-
at: message.created_at,
|
|
1259
|
-
kind,
|
|
1260
|
-
title: `${message.from_name} to ${message.to_role}`,
|
|
1261
|
-
detail: clip(message.body_md),
|
|
1262
|
-
durationMs: null
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
for (const decision of input.decisions) {
|
|
1266
|
-
const answered = Boolean(decision.answered_at);
|
|
1267
|
-
items.push({
|
|
1268
|
-
id: `decision:${decision.id}`,
|
|
1269
|
-
at: decision.created_at,
|
|
1270
|
-
kind: "decision",
|
|
1271
|
-
title: answered ? `answered (${decision.asked_by_role})` : `asked by ${decision.asked_by_role}`,
|
|
1272
|
-
detail: clip(answered ? decision.answer_md || decision.question_md : decision.question_md),
|
|
1273
|
-
durationMs: msBetween(decision.created_at, decision.answered_at ?? nowIso),
|
|
1274
|
-
endedAt: decision.answered_at
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
items.sort((a, b) => a.at.localeCompare(b.at) || KIND_RANK[a.kind] - KIND_RANK[b.kind] || a.id.localeCompare(b.id));
|
|
1278
|
-
return items;
|
|
1279
|
-
}
|
|
1280
|
-
var KIND_RANK;
|
|
1281
|
-
var init_timeline = __esm({
|
|
1282
|
-
"../../packages/db/src/timeline.ts"() {
|
|
1283
|
-
"use strict";
|
|
1284
|
-
init_status();
|
|
1285
|
-
KIND_RANK = {
|
|
1286
|
-
status: 0,
|
|
1287
|
-
run: 1,
|
|
1288
|
-
verdict: 2,
|
|
1289
|
-
message: 3,
|
|
1290
|
-
decision: 4
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
1184
|
// ../../packages/db/src/transcript.ts
|
|
1296
1185
|
function asRecord(value) {
|
|
1297
1186
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -1355,12 +1244,12 @@ function codexItemLine(event, item) {
|
|
|
1355
1244
|
return {
|
|
1356
1245
|
id: event.id,
|
|
1357
1246
|
kind: failed ? "error" : "tool",
|
|
1358
|
-
title: `${name}${
|
|
1359
|
-
body: failed ?
|
|
1247
|
+
title: `${name}${clip(args, 120)}${status && status !== "completed" ? ` (${status})` : ""}`,
|
|
1248
|
+
body: failed ? clip(str(asRecord(item.error).message ?? item.error), 400) || void 0 : void 0
|
|
1360
1249
|
};
|
|
1361
1250
|
}
|
|
1362
1251
|
if (kind === "command_execution") {
|
|
1363
|
-
const command =
|
|
1252
|
+
const command = clip(str(item.command ?? item.cmd), 120);
|
|
1364
1253
|
const code = item.exit_code ?? item.exitCode;
|
|
1365
1254
|
return {
|
|
1366
1255
|
id: event.id,
|
|
@@ -1375,7 +1264,7 @@ function codexItemLine(event, item) {
|
|
|
1375
1264
|
if (kind === "reasoning" || kind === "todo_list") return SKIP;
|
|
1376
1265
|
return { id: event.id, kind: "status", title: kind.replaceAll("_", " ") };
|
|
1377
1266
|
}
|
|
1378
|
-
function
|
|
1267
|
+
function clip(text, max) {
|
|
1379
1268
|
const flat = String(text ?? "").trim();
|
|
1380
1269
|
return flat.length <= max ? flat : `${flat.slice(0, Math.max(0, max - 3))}...`;
|
|
1381
1270
|
}
|
|
@@ -1448,6 +1337,185 @@ var init_transcript = __esm({
|
|
|
1448
1337
|
}
|
|
1449
1338
|
});
|
|
1450
1339
|
|
|
1340
|
+
// ../../packages/db/src/timeline.ts
|
|
1341
|
+
function formatDuration(ms) {
|
|
1342
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1e3));
|
|
1343
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
1344
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1345
|
+
const seconds = totalSeconds % 60;
|
|
1346
|
+
if (hours > 0) return minutes ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
1347
|
+
if (minutes > 0) return seconds ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
1348
|
+
return `${seconds}s`;
|
|
1349
|
+
}
|
|
1350
|
+
function msBetween(from, to) {
|
|
1351
|
+
const start = Date.parse(from);
|
|
1352
|
+
const end = Date.parse(to);
|
|
1353
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
|
|
1354
|
+
return end - start;
|
|
1355
|
+
}
|
|
1356
|
+
function clip2(text, max = 160) {
|
|
1357
|
+
const one2 = text.trim().replace(/\s+/g, " ");
|
|
1358
|
+
if (one2.length <= max) return one2;
|
|
1359
|
+
return `${one2.slice(0, max - 3)}...`;
|
|
1360
|
+
}
|
|
1361
|
+
function clock(iso) {
|
|
1362
|
+
if (!iso) return "";
|
|
1363
|
+
const date = new Date(iso);
|
|
1364
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
1365
|
+
return date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
1366
|
+
}
|
|
1367
|
+
function runSpan(run5) {
|
|
1368
|
+
const start = clock(run5.started_at ?? run5.created_at);
|
|
1369
|
+
const end = clock(run5.ended_at);
|
|
1370
|
+
if (start && end) return `${start}-${end}`;
|
|
1371
|
+
if (start && !run5.ended_at) return `${start}-`;
|
|
1372
|
+
return start;
|
|
1373
|
+
}
|
|
1374
|
+
function runDurationMs(run5, nowIso) {
|
|
1375
|
+
const start = run5.started_at ?? run5.created_at;
|
|
1376
|
+
if (run5.ended_at) return msBetween(start, run5.ended_at);
|
|
1377
|
+
if (run5.status === "running" || run5.status === "queued") return msBetween(start, nowIso);
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
function record(value) {
|
|
1381
|
+
return value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1382
|
+
}
|
|
1383
|
+
function auditBelongsToTicket(event, ticket) {
|
|
1384
|
+
const payload = record(event.payload);
|
|
1385
|
+
if (!payload) return false;
|
|
1386
|
+
const ticketId = typeof payload.ticket_id === "string" ? payload.ticket_id : null;
|
|
1387
|
+
const ticketKey = typeof payload.ticket_key === "string" ? payload.ticket_key : null;
|
|
1388
|
+
if (ticketId != null && ticketId !== ticket.id) return false;
|
|
1389
|
+
if (ticketKey != null && ticketKey !== ticket.key) return false;
|
|
1390
|
+
return ticketId === ticket.id || ticketKey === ticket.key;
|
|
1391
|
+
}
|
|
1392
|
+
function buildTimeline(input) {
|
|
1393
|
+
const nowIso = typeof input.now === "string" ? input.now : new Date(input.now ?? Date.now()).toISOString();
|
|
1394
|
+
const items = [];
|
|
1395
|
+
const statuses = [...input.statusEvents].sort((a, b) => a.at.localeCompare(b.at) || a.id.localeCompare(b.id));
|
|
1396
|
+
for (let i = 0; i < statuses.length; i++) {
|
|
1397
|
+
const event = statuses[i];
|
|
1398
|
+
const next = statuses[i + 1];
|
|
1399
|
+
const from = event.from_status ? statusLabel(event.from_status) : "new";
|
|
1400
|
+
const to = statusLabel(event.to_status);
|
|
1401
|
+
items.push({
|
|
1402
|
+
id: `status:${event.id}`,
|
|
1403
|
+
at: event.at,
|
|
1404
|
+
kind: "status",
|
|
1405
|
+
title: `${from} to ${to}`,
|
|
1406
|
+
durationMs: msBetween(event.at, next?.at ?? nowIso),
|
|
1407
|
+
endedAt: next?.at ?? null
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
for (const run5 of input.runs) {
|
|
1411
|
+
const at = run5.started_at ?? run5.created_at;
|
|
1412
|
+
const span = runSpan(run5);
|
|
1413
|
+
const kind = run5.kind === "review" ? "verdict" : "run";
|
|
1414
|
+
items.push({
|
|
1415
|
+
id: `run:${run5.id}`,
|
|
1416
|
+
at,
|
|
1417
|
+
kind,
|
|
1418
|
+
title: [
|
|
1419
|
+
run5.kind,
|
|
1420
|
+
run5.provider,
|
|
1421
|
+
run5.status,
|
|
1422
|
+
run5.reviewed_sha ? `@ ${run5.reviewed_sha.slice(0, 12)}` : "",
|
|
1423
|
+
run5.cost_usd != null && Number.isFinite(Number(run5.cost_usd)) ? formatCostUsd(Number(run5.cost_usd)) : "",
|
|
1424
|
+
span
|
|
1425
|
+
].filter(Boolean).join(" "),
|
|
1426
|
+
detail: run5.summary ? clip2(run5.summary) : void 0,
|
|
1427
|
+
durationMs: runDurationMs(run5, nowIso),
|
|
1428
|
+
endedAt: run5.ended_at
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
const runsById = new Map(input.runs.map((run5) => [run5.id, run5]));
|
|
1432
|
+
const readableEvents = [...input.runEvents ?? []].filter((event) => runsById.has(event.run_id)).sort((left, right) => left.at.localeCompare(right.at) || left.seq - right.seq || left.id.localeCompare(right.id));
|
|
1433
|
+
for (const event of readableEvents) {
|
|
1434
|
+
const run5 = runsById.get(event.run_id);
|
|
1435
|
+
for (const line of transcriptLines([event])) {
|
|
1436
|
+
items.push({
|
|
1437
|
+
id: `transcript:${event.id}:${line.id}`,
|
|
1438
|
+
at: event.at,
|
|
1439
|
+
kind: "transcript",
|
|
1440
|
+
title: `${run5?.kind ?? "run"} ${line.kind}: ${clip2(line.title)}`,
|
|
1441
|
+
detail: line.body ? clip2(line.body) : void 0,
|
|
1442
|
+
durationMs: null
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
for (const message of input.messages) {
|
|
1447
|
+
const kind = message.from_role === "reviewer" ? "verdict" : "message";
|
|
1448
|
+
items.push({
|
|
1449
|
+
id: `message:${message.id}`,
|
|
1450
|
+
at: message.created_at,
|
|
1451
|
+
kind,
|
|
1452
|
+
title: `${message.from_name} to ${message.to_role}`,
|
|
1453
|
+
detail: clip2(message.body_md),
|
|
1454
|
+
durationMs: null
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
for (const decision of input.decisions) {
|
|
1458
|
+
const answered = Boolean(decision.answered_at);
|
|
1459
|
+
items.push({
|
|
1460
|
+
id: `decision:${decision.id}`,
|
|
1461
|
+
at: decision.created_at,
|
|
1462
|
+
kind: "decision",
|
|
1463
|
+
title: answered ? `answered (${decision.asked_by_role})` : `asked by ${decision.asked_by_role}`,
|
|
1464
|
+
detail: clip2(answered ? decision.answer_md || decision.question_md : decision.question_md),
|
|
1465
|
+
durationMs: msBetween(decision.created_at, decision.answered_at ?? nowIso),
|
|
1466
|
+
endedAt: decision.answered_at
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
if (input.ticket) {
|
|
1470
|
+
for (const event of input.auditEvents ?? []) {
|
|
1471
|
+
if (!auditBelongsToTicket(event, input.ticket)) continue;
|
|
1472
|
+
const payload = record(event.payload);
|
|
1473
|
+
const summary = typeof payload?.summary === "string" ? payload.summary : null;
|
|
1474
|
+
items.push({
|
|
1475
|
+
id: `audit:${event.id}`,
|
|
1476
|
+
at: event.at,
|
|
1477
|
+
kind: "audit",
|
|
1478
|
+
title: clip2(summary ?? `${event.actor} ${event.action.replaceAll("_", " ")}`),
|
|
1479
|
+
durationMs: null
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (input.pr && Number.isFinite(Date.parse(input.pr.updatedAt))) {
|
|
1484
|
+
const review = input.pr.review ? input.pr.review.replaceAll("_", " ") : "no review";
|
|
1485
|
+
items.push({
|
|
1486
|
+
id: `delivery:pr:${input.pr.number}`,
|
|
1487
|
+
at: input.pr.updatedAt,
|
|
1488
|
+
kind: "delivery",
|
|
1489
|
+
title: `current PR #${input.pr.number} ${input.pr.headSha ? `@ ${input.pr.headSha.slice(0, 12)} ` : ""}checks ${input.pr.checks}; ${review}`,
|
|
1490
|
+
detail: `${input.pr.changedFiles} files +${input.pr.additions}/-${input.pr.deletions}; mergeable ${input.pr.mergeable == null ? "unknown" : input.pr.mergeable ? "yes" : "no"}`,
|
|
1491
|
+
durationMs: null,
|
|
1492
|
+
href: input.pr.url,
|
|
1493
|
+
previewHref: input.pr.previewUrl ?? void 0
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
items.sort((a, b) => a.at.localeCompare(b.at) || KIND_RANK[a.kind] - KIND_RANK[b.kind] || a.id.localeCompare(b.id));
|
|
1497
|
+
return items;
|
|
1498
|
+
}
|
|
1499
|
+
var KIND_RANK;
|
|
1500
|
+
var init_timeline = __esm({
|
|
1501
|
+
"../../packages/db/src/timeline.ts"() {
|
|
1502
|
+
"use strict";
|
|
1503
|
+
init_status();
|
|
1504
|
+
init_transcript();
|
|
1505
|
+
init_cost();
|
|
1506
|
+
KIND_RANK = {
|
|
1507
|
+
status: 0,
|
|
1508
|
+
run: 1,
|
|
1509
|
+
verdict: 2,
|
|
1510
|
+
transcript: 3,
|
|
1511
|
+
message: 4,
|
|
1512
|
+
decision: 5,
|
|
1513
|
+
audit: 6,
|
|
1514
|
+
delivery: 7
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1451
1519
|
// ../../packages/db/src/waves.ts
|
|
1452
1520
|
function ticketWaves(tickets) {
|
|
1453
1521
|
const ids = new Set(tickets.map((ticket) => ticket.id));
|
|
@@ -1500,7 +1568,9 @@ var init_src = __esm({
|
|
|
1500
1568
|
init_budgets();
|
|
1501
1569
|
init_cost();
|
|
1502
1570
|
init_commands();
|
|
1571
|
+
init_cockpit();
|
|
1503
1572
|
init_defaults();
|
|
1573
|
+
init_effective_config();
|
|
1504
1574
|
init_enums();
|
|
1505
1575
|
init_epic_progress();
|
|
1506
1576
|
init_filter();
|
|
@@ -2157,6 +2227,12 @@ async function loadRunEvents(ctx, runId, afterSeq = -1) {
|
|
|
2157
2227
|
if (error) fail(error.message);
|
|
2158
2228
|
return data ?? [];
|
|
2159
2229
|
}
|
|
2230
|
+
async function loadLiveEvents(ctx, runIds, limit = 80) {
|
|
2231
|
+
if (runIds.length === 0) return [];
|
|
2232
|
+
const { data, error } = await ctx.db.from("run_events").select("*").in("run_id", runIds).order("at", { ascending: false }).limit(limit);
|
|
2233
|
+
if (error) fail(error.message);
|
|
2234
|
+
return (data ?? []).slice().reverse();
|
|
2235
|
+
}
|
|
2160
2236
|
async function loadMessages(ctx, opts = {}) {
|
|
2161
2237
|
let query = ctx.db.from("messages").select("*").eq("workspace_id", ctx.workspace.id).order("created_at", { ascending: true }).limit(opts.limit ?? 200);
|
|
2162
2238
|
if (opts.ticketId) query = query.eq("ticket_id", opts.ticketId);
|
|
@@ -2517,9 +2593,9 @@ async function runAgent(opts) {
|
|
|
2517
2593
|
opts.onEvent?.({ kind: "tool", name: call.name, args });
|
|
2518
2594
|
const tool = byName.get(call.name);
|
|
2519
2595
|
let output;
|
|
2520
|
-
let
|
|
2596
|
+
let ok3 = true;
|
|
2521
2597
|
if (!tool) {
|
|
2522
|
-
|
|
2598
|
+
ok3 = false;
|
|
2523
2599
|
output = `Error: no tool named ${call.name}.`;
|
|
2524
2600
|
} else {
|
|
2525
2601
|
try {
|
|
@@ -2527,11 +2603,11 @@ async function runAgent(opts) {
|
|
|
2527
2603
|
output = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
2528
2604
|
if (!output) output = "(no result)";
|
|
2529
2605
|
} catch (error) {
|
|
2530
|
-
|
|
2606
|
+
ok3 = false;
|
|
2531
2607
|
output = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
2532
2608
|
}
|
|
2533
2609
|
}
|
|
2534
|
-
opts.onEvent?.({ kind: "tool_result", name: call.name, ok:
|
|
2610
|
+
opts.onEvent?.({ kind: "tool_result", name: call.name, ok: ok3, detail: output.slice(0, 200) });
|
|
2535
2611
|
input.push({
|
|
2536
2612
|
type: "function_call",
|
|
2537
2613
|
name: call.name,
|
|
@@ -2734,6 +2810,95 @@ var init_group = __esm({
|
|
|
2734
2810
|
}
|
|
2735
2811
|
});
|
|
2736
2812
|
|
|
2813
|
+
// src/argv-parsers.ts
|
|
2814
|
+
import { InvalidArgumentError } from "commander";
|
|
2815
|
+
function oneOf(label, values) {
|
|
2816
|
+
return (value) => {
|
|
2817
|
+
const normalized = value.trim().toLowerCase();
|
|
2818
|
+
if (!values.includes(normalized)) {
|
|
2819
|
+
throw new InvalidArgumentError(`${label} must be one of: ${values.join(", ")}.`);
|
|
2820
|
+
}
|
|
2821
|
+
return normalized;
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
function integer(label) {
|
|
2825
|
+
return (value) => {
|
|
2826
|
+
if (!/^-?(?:0|[1-9]\d*)$/.test(value)) {
|
|
2827
|
+
throw new InvalidArgumentError(`${label} must be an integer.`);
|
|
2828
|
+
}
|
|
2829
|
+
const parsed = Number(value);
|
|
2830
|
+
if (!Number.isSafeInteger(parsed)) throw new InvalidArgumentError(`${label} must be an integer.`);
|
|
2831
|
+
return parsed;
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
function positiveNumber(label) {
|
|
2835
|
+
return (value) => {
|
|
2836
|
+
if (!/^(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) {
|
|
2837
|
+
throw new InvalidArgumentError(`${label} must be greater than zero.`);
|
|
2838
|
+
}
|
|
2839
|
+
const parsed = Number(value);
|
|
2840
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
2841
|
+
throw new InvalidArgumentError(`${label} must be greater than zero.`);
|
|
2842
|
+
}
|
|
2843
|
+
return parsed;
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function positiveInteger(label) {
|
|
2847
|
+
return (value) => {
|
|
2848
|
+
if (!/^(?:0|[1-9]\d*)$/.test(value)) {
|
|
2849
|
+
throw new InvalidArgumentError(`${label} must be a positive integer.`);
|
|
2850
|
+
}
|
|
2851
|
+
const parsed = Number(value);
|
|
2852
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
2853
|
+
throw new InvalidArgumentError(`${label} must be a positive integer.`);
|
|
2854
|
+
}
|
|
2855
|
+
return parsed;
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
function rangedInteger(label, minimum, maximum) {
|
|
2859
|
+
const parse = positiveInteger(label);
|
|
2860
|
+
return (value) => {
|
|
2861
|
+
const parsed = parse(value);
|
|
2862
|
+
if (parsed < minimum || parsed > maximum) {
|
|
2863
|
+
throw new InvalidArgumentError(`${label} must be between ${minimum} and ${maximum}.`);
|
|
2864
|
+
}
|
|
2865
|
+
return parsed;
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
function optionalPositiveInteger(label) {
|
|
2869
|
+
const parse = positiveInteger(label);
|
|
2870
|
+
return (value) => value === "none" ? null : parse(value);
|
|
2871
|
+
}
|
|
2872
|
+
function optionalPositiveNumber(label) {
|
|
2873
|
+
const parse = positiveNumber(label);
|
|
2874
|
+
return (value) => value === "none" ? null : parse(value);
|
|
2875
|
+
}
|
|
2876
|
+
function providerCaps(values) {
|
|
2877
|
+
return (value, previous = []) => {
|
|
2878
|
+
const match = value.match(/^([^=]+)=((?:0|[1-9]\d*))$/);
|
|
2879
|
+
if (!match || !values.includes(match[1])) {
|
|
2880
|
+
throw new InvalidArgumentError(`cap must be provider=whole-number for: ${values.join(", ")}.`);
|
|
2881
|
+
}
|
|
2882
|
+
const cap = Number(match[2]);
|
|
2883
|
+
if (!Number.isSafeInteger(cap)) {
|
|
2884
|
+
throw new InvalidArgumentError(`cap must be provider=whole-number for: ${values.join(", ")}.`);
|
|
2885
|
+
}
|
|
2886
|
+
return [...previous, { provider: match[1], cap }];
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
function uuid(value) {
|
|
2890
|
+
const normalized = value.trim().toLowerCase();
|
|
2891
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(normalized)) {
|
|
2892
|
+
throw new InvalidArgumentError("request id must be a UUID.");
|
|
2893
|
+
}
|
|
2894
|
+
return normalized;
|
|
2895
|
+
}
|
|
2896
|
+
var init_argv_parsers = __esm({
|
|
2897
|
+
"src/argv-parsers.ts"() {
|
|
2898
|
+
"use strict";
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
|
|
2737
2902
|
// src/out/format.ts
|
|
2738
2903
|
function truncate(text, max) {
|
|
2739
2904
|
const flat = String(text ?? "").replace(/\s+/g, " ").trim();
|
|
@@ -3200,7 +3365,7 @@ function registerReadCommands(program) {
|
|
|
3200
3365
|
${statusView(board, pausedAll)}`
|
|
3201
3366
|
);
|
|
3202
3367
|
});
|
|
3203
|
-
program.command("ls").description("list tickets").option("-s, --status <status>", "filter by status").option("-a, --agent <name>", "filter by agent display name").option("-p, --provider <provider>", "filter by provider").option("--area <area>", "filter by area").option("-q, --query <text>", "search titles, bodies, and messages").option("--stuck", "only tickets with a reason they are not moving").action(async function() {
|
|
3368
|
+
program.command("ls").description("list tickets").option("-s, --status <status>", "filter by status", oneOf("status", ticketStatuses)).option("-a, --agent <name>", "filter by agent display name").option("-p, --provider <provider>", "filter by provider", oneOf("provider", providers)).option("--area <area>", "filter by area").option("-q, --query <text>", "search titles, bodies, and messages").option("--stuck", "only tickets with a reason they are not moving").action(async function() {
|
|
3204
3369
|
const opts = this.opts();
|
|
3205
3370
|
const ctx = await requireWorkspace(slugOf(this));
|
|
3206
3371
|
const board = await loadBoard(ctx);
|
|
@@ -3249,7 +3414,7 @@ ${statusView(board, pausedAll)}`
|
|
|
3249
3414
|
const board = await loadBoard(ctx);
|
|
3250
3415
|
emit(board.hosts, () => hostsView(board.hosts));
|
|
3251
3416
|
});
|
|
3252
|
-
program.command("cost").description("spend by day, provider, ticket, and epic").option("-d, --days <n>", "window in days", "
|
|
3417
|
+
program.command("cost").description("spend by day, provider, ticket, and epic").option("-d, --days <n>", "window in days", positiveInteger("days"), 14).action(async function() {
|
|
3253
3418
|
const ctx = await requireWorkspace(slugOf(this));
|
|
3254
3419
|
const days = Number(this.opts().days) || 14;
|
|
3255
3420
|
const [daily, tickets, epics] = await Promise.all([
|
|
@@ -3303,6 +3468,7 @@ var init_commands2 = __esm({
|
|
|
3303
3468
|
init_theme();
|
|
3304
3469
|
init_queries();
|
|
3305
3470
|
init_group();
|
|
3471
|
+
init_argv_parsers();
|
|
3306
3472
|
init_views();
|
|
3307
3473
|
}
|
|
3308
3474
|
});
|
|
@@ -3343,31 +3509,46 @@ async function postMessage(ctx, input) {
|
|
|
3343
3509
|
await ctx.audit(ctx.workspace.id, "message", { to, ticket_key: input.ticketKey });
|
|
3344
3510
|
return data;
|
|
3345
3511
|
}
|
|
3346
|
-
|
|
3512
|
+
function decisionOptions(decision) {
|
|
3513
|
+
return Array.isArray(decision.options) ? decision.options.map(String) : [];
|
|
3514
|
+
}
|
|
3515
|
+
function resolveAnswer(decision, answer) {
|
|
3516
|
+
const options = decisionOptions(decision);
|
|
3517
|
+
const index = Number(answer.trim());
|
|
3518
|
+
return options.length && Number.isInteger(index) && index >= 1 && index <= options.length ? options[index - 1] : answer;
|
|
3519
|
+
}
|
|
3520
|
+
async function resolveDecision(ctx, decisionId, answer, opts = {}) {
|
|
3347
3521
|
const { data: rows, error: loadError } = await ctx.db.from("decisions").select("*").eq("workspace_id", ctx.workspace.id).is("answered_at", null).is("dismissed_at", null);
|
|
3348
|
-
if (loadError)
|
|
3522
|
+
if (loadError) return { ok: false, error: loadError.message };
|
|
3349
3523
|
const matches = (rows ?? []).filter((row) => row.id.startsWith(decisionId));
|
|
3350
|
-
if (matches.length === 0)
|
|
3351
|
-
if (matches.length > 1)
|
|
3524
|
+
if (matches.length === 0) return { ok: false, error: `No open decision matching ${decisionId}.` };
|
|
3525
|
+
if (matches.length > 1) {
|
|
3526
|
+
return { ok: false, error: `${decisionId} matches ${matches.length} decisions. Use more of the id.` };
|
|
3527
|
+
}
|
|
3352
3528
|
const decision = matches[0];
|
|
3353
3529
|
if (opts.dismiss) {
|
|
3354
3530
|
const { error: error2 } = await ctx.db.from("decisions").update({ dismissed_at: (/* @__PURE__ */ new Date()).toISOString(), answered_by: ctx.email }).eq("id", decision.id);
|
|
3355
|
-
if (error2)
|
|
3531
|
+
if (error2) return { ok: false, error: error2.message };
|
|
3356
3532
|
await ctx.audit(ctx.workspace.id, "dismiss", { decision_id: decision.id });
|
|
3357
|
-
return { decision, dismissed: true };
|
|
3533
|
+
return { ok: true, decision, answer: "", dismissed: true };
|
|
3534
|
+
}
|
|
3535
|
+
const resolved = resolveAnswer(decision, answer);
|
|
3536
|
+
if (!resolved.trim()) {
|
|
3537
|
+
return { ok: false, error: "An answer needs text, or the number of an option." };
|
|
3358
3538
|
}
|
|
3359
|
-
const options = Array.isArray(decision.options) ? decision.options : [];
|
|
3360
|
-
const index = Number(answer);
|
|
3361
|
-
const resolved = options.length && Number.isInteger(index) && index >= 1 && index <= options.length ? options[index - 1] : answer;
|
|
3362
|
-
if (!resolved.trim()) fail("An answer needs text, or the number of an option.");
|
|
3363
3539
|
const { error } = await ctx.db.from("decisions").update({
|
|
3364
3540
|
answer_md: resolved,
|
|
3365
3541
|
answered_by: ctx.email,
|
|
3366
3542
|
answered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3367
3543
|
}).eq("id", decision.id);
|
|
3368
|
-
if (error)
|
|
3544
|
+
if (error) return { ok: false, error: error.message };
|
|
3369
3545
|
await ctx.audit(ctx.workspace.id, "answer", { decision_id: decision.id });
|
|
3370
|
-
return { decision, answer: resolved, dismissed: false };
|
|
3546
|
+
return { ok: true, decision, answer: resolved, dismissed: false };
|
|
3547
|
+
}
|
|
3548
|
+
async function answerDecision(ctx, decisionId, answer, opts = {}) {
|
|
3549
|
+
const result = await resolveDecision(ctx, decisionId, answer, opts);
|
|
3550
|
+
if (!result.ok) fail(result.error);
|
|
3551
|
+
return { decision: result.decision, answer: result.answer, dismissed: result.dismissed };
|
|
3371
3552
|
}
|
|
3372
3553
|
async function awaitReply(ctx, since, timeoutMs) {
|
|
3373
3554
|
return new Promise((resolve) => {
|
|
@@ -3396,7 +3577,7 @@ async function awaitReply(ctx, since, timeoutMs) {
|
|
|
3396
3577
|
});
|
|
3397
3578
|
}
|
|
3398
3579
|
function registerMessagingCommands(program) {
|
|
3399
|
-
program.command("msg").argument("[key]", "ticket key, when the message is about one").argument("<body...>", "what to say").description("post a message on the board").option("-t, --to <role>", "orchestrator, reviewer, builder, operator, human, or all", "orchestrator").option("-i, --interrupt", "kill the running session and deliver now").action(async function(key2, body) {
|
|
3580
|
+
program.command("msg").argument("[key]", "ticket key, when the message is about one").argument("<body...>", "what to say").description("post a message on the board").option("-t, --to <role>", "orchestrator, reviewer, builder, operator, human, or all", oneOf("target", messageToRoles), "orchestrator").option("-i, --interrupt", "kill the running session and deliver now").action(async function(key2, body) {
|
|
3400
3581
|
const opts = this.opts();
|
|
3401
3582
|
const ctx = await requireWorkspace(slugOf2(this));
|
|
3402
3583
|
const looksLikeKey = key2 && /^HD-\d+$/i.test(key2);
|
|
@@ -3409,7 +3590,7 @@ function registerMessagingCommands(program) {
|
|
|
3409
3590
|
});
|
|
3410
3591
|
ok(`Sent to ${c.bold(opts.to)}${looksLikeKey ? ` on ${key2}` : ""}.`, { message });
|
|
3411
3592
|
});
|
|
3412
|
-
program.command("ask").argument("<question...>", "what to ask").description("ask the orchestrator about the work, and wait for its reply").option("-k, --ticket <key>", "ask about one ticket").option("-t, --to <role>", "ask a different role", "orchestrator").option("--no-wait", "post the question and return immediately").option("--timeout <seconds>", "how long to wait for the reply", "
|
|
3593
|
+
program.command("ask").argument("<question...>", "what to ask").description("ask the orchestrator about the work, and wait for its reply").option("-k, --ticket <key>", "ask about one ticket").option("-t, --to <role>", "ask a different role", oneOf("target", messageToRoles), "orchestrator").option("--no-wait", "post the question and return immediately").option("--timeout <seconds>", "how long to wait for the reply", positiveNumber("timeout"), 180).action(async function(question) {
|
|
3413
3594
|
const opts = this.opts();
|
|
3414
3595
|
const ctx = await requireWorkspace(slugOf2(this));
|
|
3415
3596
|
const since = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3465,6 +3646,7 @@ var init_commands3 = __esm({
|
|
|
3465
3646
|
init_queries();
|
|
3466
3647
|
init_views();
|
|
3467
3648
|
init_subscribe();
|
|
3649
|
+
init_argv_parsers();
|
|
3468
3650
|
}
|
|
3469
3651
|
});
|
|
3470
3652
|
|
|
@@ -3719,6 +3901,160 @@ var init_epics = __esm({
|
|
|
3719
3901
|
}
|
|
3720
3902
|
});
|
|
3721
3903
|
|
|
3904
|
+
// src/control/config.ts
|
|
3905
|
+
function parseField(parse, value) {
|
|
3906
|
+
try {
|
|
3907
|
+
return ok2(parse(value));
|
|
3908
|
+
} catch (error) {
|
|
3909
|
+
return no(error instanceof Error ? error.message : String(error));
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
function checkProvider(provider) {
|
|
3913
|
+
return providers.includes(provider) ? ok2(provider) : no(`Unknown provider ${provider}. One of: ${providers.join(", ")}.`);
|
|
3914
|
+
}
|
|
3915
|
+
function agentChanges(input) {
|
|
3916
|
+
const fields = {};
|
|
3917
|
+
if (input.name !== void 0) {
|
|
3918
|
+
const name = input.name.trim();
|
|
3919
|
+
if (!name) return no("An agent needs a name.");
|
|
3920
|
+
fields.display_name = name;
|
|
3921
|
+
}
|
|
3922
|
+
if (input.model !== void 0) {
|
|
3923
|
+
const model = input.model.trim();
|
|
3924
|
+
if (!model) return no("An agent needs a model.");
|
|
3925
|
+
fields.model = model;
|
|
3926
|
+
}
|
|
3927
|
+
if (input.provider !== void 0) {
|
|
3928
|
+
const provider = checkProvider(input.provider.trim());
|
|
3929
|
+
if (!provider.ok) return no(provider.error);
|
|
3930
|
+
fields.provider = provider.value;
|
|
3931
|
+
}
|
|
3932
|
+
if (input.effort !== void 0) fields.effort = asEffort(input.effort);
|
|
3933
|
+
if (input.notes !== void 0) fields.routing_notes = input.notes;
|
|
3934
|
+
if (input.prompt !== void 0) fields.prompt_addendum = input.prompt;
|
|
3935
|
+
if (input.enabled !== void 0) fields.enabled = input.enabled;
|
|
3936
|
+
if (input.runsPerHour !== void 0) fields.runs_per_hour = input.runsPerHour;
|
|
3937
|
+
if (input.dailySpend !== void 0) fields.daily_spend_usd = input.dailySpend;
|
|
3938
|
+
if (Object.keys(fields).length === 0) return no("Nothing to change.");
|
|
3939
|
+
return ok2(fields);
|
|
3940
|
+
}
|
|
3941
|
+
async function findAgent(ctx, name) {
|
|
3942
|
+
const { data, error } = await ctx.db.from("agents").select("*").eq("workspace_id", ctx.workspace.id);
|
|
3943
|
+
if (error) return no(error.message);
|
|
3944
|
+
const wanted = name.trim().toLowerCase();
|
|
3945
|
+
const matches = (data ?? []).filter(
|
|
3946
|
+
(row) => row.id === name || row.display_name.toLowerCase() === wanted || row.role.toLowerCase() === wanted
|
|
3947
|
+
);
|
|
3948
|
+
if (matches.length === 0) return no(`No agent named ${name}.`);
|
|
3949
|
+
if (matches.length > 1) return no(`${name} matches ${matches.length} agents. Use a display name.`);
|
|
3950
|
+
return ok2(matches[0]);
|
|
3951
|
+
}
|
|
3952
|
+
async function updateAgent(ctx, name, input) {
|
|
3953
|
+
const found = await findAgent(ctx, name);
|
|
3954
|
+
if (!found.ok) return found;
|
|
3955
|
+
const changes = agentChanges(input);
|
|
3956
|
+
if (!changes.ok) return changes;
|
|
3957
|
+
if (changes.value.enabled === true && (found.value.role === "orchestrator" || found.value.role === "reviewer")) {
|
|
3958
|
+
const { data: other } = await ctx.db.from("agents").select("id, display_name").eq("workspace_id", ctx.workspace.id).eq("role", found.value.role).eq("enabled", true).neq("id", found.value.id).maybeSingle();
|
|
3959
|
+
if (other) return no(`${other.display_name} is already the enabled ${found.value.role}. Disable it first.`);
|
|
3960
|
+
}
|
|
3961
|
+
const { data, error } = await ctx.db.from("agents").update(changes.value).eq("id", found.value.id).eq("workspace_id", ctx.workspace.id).select("*").single();
|
|
3962
|
+
if (error) return no(error.message);
|
|
3963
|
+
await ctx.audit(ctx.workspace.id, "configure", { subject: `agent ${data.display_name}` });
|
|
3964
|
+
return ok2(data);
|
|
3965
|
+
}
|
|
3966
|
+
async function createBuilder(ctx, name, input) {
|
|
3967
|
+
const display = name.trim();
|
|
3968
|
+
if (!display) return no("An agent needs a name.");
|
|
3969
|
+
const provider = checkProvider(input.provider.trim());
|
|
3970
|
+
if (!provider.ok) return no(provider.error);
|
|
3971
|
+
const model = input.model.trim();
|
|
3972
|
+
if (!model) return no("An agent needs a model.");
|
|
3973
|
+
const { data, error } = await ctx.db.from("agents").insert({
|
|
3974
|
+
workspace_id: ctx.workspace.id,
|
|
3975
|
+
role: "builder",
|
|
3976
|
+
provider: provider.value,
|
|
3977
|
+
model,
|
|
3978
|
+
display_name: display,
|
|
3979
|
+
effort: asEffort(input.effort ?? DEFAULT_EFFORT),
|
|
3980
|
+
routing_notes: input.notes ?? ""
|
|
3981
|
+
}).select("*").single();
|
|
3982
|
+
if (error) return no(error.message);
|
|
3983
|
+
await ctx.audit(ctx.workspace.id, "create", { subject: `agent ${data.display_name}` });
|
|
3984
|
+
return ok2(data);
|
|
3985
|
+
}
|
|
3986
|
+
function mergeCaps(existing, pairs) {
|
|
3987
|
+
const caps = {};
|
|
3988
|
+
for (const [key2, value] of Object.entries(existing ?? {})) {
|
|
3989
|
+
if (typeof value === "number") caps[key2] = value;
|
|
3990
|
+
}
|
|
3991
|
+
for (const { provider, cap } of pairs) caps[provider] = cap;
|
|
3992
|
+
return caps;
|
|
3993
|
+
}
|
|
3994
|
+
function workspaceChanges(workspace, input) {
|
|
3995
|
+
const fields = {};
|
|
3996
|
+
if (input.branch !== void 0) {
|
|
3997
|
+
const branch = input.branch.trim();
|
|
3998
|
+
if (!branch) return no("A workspace needs a default branch.");
|
|
3999
|
+
fields.default_branch = branch;
|
|
4000
|
+
}
|
|
4001
|
+
if (input.host !== void 0) {
|
|
4002
|
+
const host = input.host.trim();
|
|
4003
|
+
if (!host) return no("A workspace needs a default host.");
|
|
4004
|
+
fields.default_host = host;
|
|
4005
|
+
}
|
|
4006
|
+
if (input.autoMerge !== void 0) fields.auto_merge = input.autoMerge;
|
|
4007
|
+
if (input.caps?.length) {
|
|
4008
|
+
fields.provider_caps = mergeCaps(
|
|
4009
|
+
workspace.provider_caps ?? {},
|
|
4010
|
+
input.caps
|
|
4011
|
+
);
|
|
4012
|
+
}
|
|
4013
|
+
if (Object.keys(fields).length === 0) return no("Nothing to change.");
|
|
4014
|
+
return ok2(fields);
|
|
4015
|
+
}
|
|
4016
|
+
async function updateWorkspace(ctx, input) {
|
|
4017
|
+
const changes = workspaceChanges(ctx.workspace, input);
|
|
4018
|
+
if (!changes.ok) return changes;
|
|
4019
|
+
const { data, error } = await ctx.db.from("workspaces").update(changes.value).eq("id", ctx.workspace.id).select("*").single();
|
|
4020
|
+
if (error) return no(error.message);
|
|
4021
|
+
await ctx.audit(ctx.workspace.id, "configure", { subject: "workspace settings" });
|
|
4022
|
+
return ok2(data);
|
|
4023
|
+
}
|
|
4024
|
+
function checkRepo(repo) {
|
|
4025
|
+
const trimmed = repo.trim();
|
|
4026
|
+
return /^[^/\s]+\/[^/\s]+$/.test(trimmed) ? ok2(trimmed) : no("Repo looks like owner/name.");
|
|
4027
|
+
}
|
|
4028
|
+
async function createWorkspace(ctx, name, repo, input = {}) {
|
|
4029
|
+
const display = name.trim();
|
|
4030
|
+
if (!display) return no("A workspace needs a name.");
|
|
4031
|
+
const checked = checkRepo(repo);
|
|
4032
|
+
if (!checked.ok) return no(checked.error);
|
|
4033
|
+
const { data, error } = await ctx.db.from("workspaces").insert({
|
|
4034
|
+
name: display,
|
|
4035
|
+
slug: slugify(display),
|
|
4036
|
+
repo: checked.value,
|
|
4037
|
+
default_branch: input.branch ?? "main",
|
|
4038
|
+
default_host: input.host ?? "box"
|
|
4039
|
+
}).select("*").single();
|
|
4040
|
+
if (error) return no(error.message);
|
|
4041
|
+
const { error: agentsError } = await ctx.db.from("agents").insert(DEFAULT_AGENTS.map((agent) => ({ ...agent, workspace_id: data.id })));
|
|
4042
|
+
if (agentsError) {
|
|
4043
|
+
return no(`Workspace created, but seeding its agents failed: ${agentsError.message}`);
|
|
4044
|
+
}
|
|
4045
|
+
await ctx.audit(data.id, "create", { subject: `workspace ${data.slug}` });
|
|
4046
|
+
return ok2({ workspace: data, agents: DEFAULT_AGENTS.length });
|
|
4047
|
+
}
|
|
4048
|
+
var ok2, no;
|
|
4049
|
+
var init_config2 = __esm({
|
|
4050
|
+
"src/control/config.ts"() {
|
|
4051
|
+
"use strict";
|
|
4052
|
+
init_src();
|
|
4053
|
+
ok2 = (value) => ({ ok: true, value });
|
|
4054
|
+
no = (error) => ({ ok: false, error });
|
|
4055
|
+
}
|
|
4056
|
+
});
|
|
4057
|
+
|
|
3722
4058
|
// src/write/editor.ts
|
|
3723
4059
|
import { spawn as spawn2 } from "child_process";
|
|
3724
4060
|
import { mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -3756,6 +4092,10 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
3756
4092
|
function slugOf3(command) {
|
|
3757
4093
|
return command.optsWithGlobals().workspace;
|
|
3758
4094
|
}
|
|
4095
|
+
function must(result) {
|
|
4096
|
+
if (!result.ok) fail(result.error);
|
|
4097
|
+
return result.value;
|
|
4098
|
+
}
|
|
3759
4099
|
async function setWorkspacePaused(ctx, paused) {
|
|
3760
4100
|
const { error } = await ctx.db.from("workspaces").update({ paused }).eq("id", ctx.workspace.id);
|
|
3761
4101
|
if (error) fail(error.message);
|
|
@@ -3780,7 +4120,7 @@ async function setProviderPaused(ctx, provider, paused) {
|
|
|
3780
4120
|
await ctx.audit(ctx.workspace.id, paused ? "pause" : "resume", { subject: provider });
|
|
3781
4121
|
}
|
|
3782
4122
|
function registerControlCommands(program) {
|
|
3783
|
-
program.command("pause").description("stop dispatching new work").option("--all", "pause every workspace").option("-p, --provider <provider>", "pause one provider by setting its cap to zero").action(async function() {
|
|
4123
|
+
program.command("pause").description("stop dispatching new work").option("--all", "pause every workspace").option("-p, --provider <provider>", "pause one provider by setting its cap to zero", oneOf("provider", providers)).action(async function() {
|
|
3784
4124
|
const opts = this.opts();
|
|
3785
4125
|
if (opts.all) {
|
|
3786
4126
|
const ctx2 = await requireCtx();
|
|
@@ -3797,7 +4137,7 @@ function registerControlCommands(program) {
|
|
|
3797
4137
|
await setWorkspacePaused(ctx, true);
|
|
3798
4138
|
ok(c.yellow(`${ctx.workspace.slug} is paused.`));
|
|
3799
4139
|
});
|
|
3800
|
-
program.command("resume").description("start dispatching again").option("--all", "resume every workspace").option("-p, --provider <provider>", "resume one provider").action(async function() {
|
|
4140
|
+
program.command("resume").description("start dispatching again").option("--all", "resume every workspace").option("-p, --provider <provider>", "resume one provider", oneOf("provider", providers)).action(async function() {
|
|
3801
4141
|
const opts = this.opts();
|
|
3802
4142
|
if (opts.all) {
|
|
3803
4143
|
const ctx2 = await requireCtx();
|
|
@@ -3850,7 +4190,7 @@ function registerAgentCommands(program) {
|
|
|
3850
4190
|
});
|
|
3851
4191
|
agent.command("show").argument("<name>", "agent display name").description("one agent in full").action(async function(name) {
|
|
3852
4192
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3853
|
-
const found = await findAgent(ctx, name);
|
|
4193
|
+
const found = must(await findAgent(ctx, name));
|
|
3854
4194
|
emit(
|
|
3855
4195
|
found,
|
|
3856
4196
|
() => [
|
|
@@ -3867,80 +4207,43 @@ ${found.prompt_addendum}` : ""
|
|
|
3867
4207
|
].filter(Boolean).join("\n")
|
|
3868
4208
|
);
|
|
3869
4209
|
});
|
|
3870
|
-
agent.command("set").argument("<name>", "agent display name").description("change an agent's model, effort, routing, prompt, or caps").option("--model <model>", "model id").option("--effort <level>", "low, medium, or high").option("--provider <provider>", "claude, codex, gemini, or grok").option("--name <text>", "new display name").option("--notes <text>", "routing notes the orchestrator reads").option("--prompt <text>", "prompt addendum prepended on every run").option("--edit-prompt", "edit the prompt addendum in $EDITOR").option("--enable", "enable the agent").option("--disable", "disable the agent").option("--runs-per-hour <n>", "cap runs per hour, or 'none'").option("--daily-spend <usd>", "cap spend per day, or 'none'").action(async function(name) {
|
|
4210
|
+
agent.command("set").argument("<name>", "agent display name").description("change an agent's model, effort, routing, prompt, or caps").option("--model <model>", "model id").option("--effort <level>", "low, medium, or high", oneOf("effort", efforts)).option("--provider <provider>", "claude, codex, gemini, or grok", oneOf("provider", providers)).option("--name <text>", "new display name").option("--notes <text>", "routing notes the orchestrator reads").option("--prompt <text>", "prompt addendum prepended on every run").option("--edit-prompt", "edit the prompt addendum in $EDITOR").option("--enable", "enable the agent").option("--disable", "disable the agent").option("--runs-per-hour <n>", "cap runs per hour, or 'none'", optionalPositiveInteger("runs per hour")).option("--daily-spend <usd>", "cap spend per day, or 'none'", optionalPositiveNumber("daily spend")).action(async function(name) {
|
|
3871
4211
|
const opts = this.opts();
|
|
3872
4212
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3873
|
-
|
|
3874
|
-
const fields = {};
|
|
3875
|
-
if (opts.name) fields.display_name = String(opts.name).trim();
|
|
3876
|
-
if (opts.model) fields.model = String(opts.model).trim();
|
|
3877
|
-
if (opts.effort) fields.effort = asEffort(opts.effort);
|
|
3878
|
-
if (opts.provider) {
|
|
3879
|
-
if (!providers.includes(opts.provider)) {
|
|
3880
|
-
fail(`Unknown provider ${opts.provider}. One of: ${providers.join(", ")}.`);
|
|
3881
|
-
}
|
|
3882
|
-
fields.provider = opts.provider;
|
|
3883
|
-
}
|
|
3884
|
-
if (opts.notes !== void 0) fields.routing_notes = opts.notes;
|
|
3885
|
-
if (opts.prompt !== void 0) fields.prompt_addendum = opts.prompt;
|
|
4213
|
+
let prompt2 = opts.prompt;
|
|
3886
4214
|
if (opts.editPrompt) {
|
|
3887
|
-
|
|
4215
|
+
const found = must(await findAgent(ctx, name));
|
|
4216
|
+
prompt2 = (await editInEditor(found.prompt_addendum ?? "", "hd-prompt.md")).trim();
|
|
3888
4217
|
}
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
}
|
|
3903
|
-
if (Object.keys(fields).length === 0) fail("Nothing to change.");
|
|
3904
|
-
if (fields.enabled === true && (found.role === "orchestrator" || found.role === "reviewer")) {
|
|
3905
|
-
const { data: other } = await ctx.db.from("agents").select("id, display_name").eq("workspace_id", ctx.workspace.id).eq("role", found.role).eq("enabled", true).neq("id", found.id).maybeSingle();
|
|
3906
|
-
if (other) fail(`${other.display_name} is already the enabled ${found.role}. Disable it first.`);
|
|
3907
|
-
}
|
|
3908
|
-
const { data, error } = await ctx.db.from("agents").update(fields).eq("id", found.id).eq("workspace_id", ctx.workspace.id).select("*").single();
|
|
3909
|
-
if (error) fail(error.message);
|
|
3910
|
-
await ctx.audit(ctx.workspace.id, "configure", { subject: `agent ${data.display_name}` });
|
|
4218
|
+
const data = must(
|
|
4219
|
+
await updateAgent(ctx, name, {
|
|
4220
|
+
name: opts.name,
|
|
4221
|
+
model: opts.model,
|
|
4222
|
+
provider: opts.provider,
|
|
4223
|
+
effort: opts.effort,
|
|
4224
|
+
notes: opts.notes,
|
|
4225
|
+
prompt: prompt2,
|
|
4226
|
+
enabled: opts.enable ? true : opts.disable ? false : void 0,
|
|
4227
|
+
runsPerHour: opts.runsPerHour,
|
|
4228
|
+
dailySpend: opts.dailySpend
|
|
4229
|
+
})
|
|
4230
|
+
);
|
|
3911
4231
|
ok(`${c.bold(data.display_name)} updated.`, { agent: data });
|
|
3912
4232
|
});
|
|
3913
|
-
agent.command("new").argument("<name>", "display name").description("add a builder").requiredOption("--provider <provider>", "claude, codex, gemini, or grok").requiredOption("--model <model>", "model id").option("--effort <level>", "low, medium, or high", DEFAULT_EFFORT).option("--notes <text>", "routing notes").action(async function(name) {
|
|
4233
|
+
agent.command("new").argument("<name>", "display name").description("add a builder").requiredOption("--provider <provider>", "claude, codex, gemini, or grok", oneOf("provider", providers)).requiredOption("--model <model>", "model id").option("--effort <level>", "low, medium, or high", oneOf("effort", efforts), DEFAULT_EFFORT).option("--notes <text>", "routing notes").action(async function(name) {
|
|
3914
4234
|
const opts = this.opts();
|
|
3915
4235
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
display_name: name.trim(),
|
|
3925
|
-
effort: asEffort(opts.effort),
|
|
3926
|
-
routing_notes: opts.notes ?? ""
|
|
3927
|
-
}).select("*").single();
|
|
3928
|
-
if (error) fail(error.message);
|
|
3929
|
-
await ctx.audit(ctx.workspace.id, "create", { subject: `agent ${data.display_name}` });
|
|
4236
|
+
const data = must(
|
|
4237
|
+
await createBuilder(ctx, name, {
|
|
4238
|
+
provider: opts.provider,
|
|
4239
|
+
model: opts.model,
|
|
4240
|
+
effort: opts.effort,
|
|
4241
|
+
notes: opts.notes
|
|
4242
|
+
})
|
|
4243
|
+
);
|
|
3930
4244
|
ok(`${c.bold(data.display_name)} added.`, { agent: data });
|
|
3931
4245
|
});
|
|
3932
4246
|
}
|
|
3933
|
-
async function findAgent(ctx, name) {
|
|
3934
|
-
const { data, error } = await ctx.db.from("agents").select("*").eq("workspace_id", ctx.workspace.id);
|
|
3935
|
-
if (error) fail(error.message);
|
|
3936
|
-
const wanted = name.trim().toLowerCase();
|
|
3937
|
-
const matches = (data ?? []).filter(
|
|
3938
|
-
(row) => row.id === name || row.display_name.toLowerCase() === wanted || row.role.toLowerCase() === wanted
|
|
3939
|
-
);
|
|
3940
|
-
if (matches.length === 0) fail(`No agent named ${name}. Run \`hd agent ls\`.`);
|
|
3941
|
-
if (matches.length > 1) fail(`${name} matches ${matches.length} agents. Use a display name.`);
|
|
3942
|
-
return matches[0];
|
|
3943
|
-
}
|
|
3944
4247
|
function registerWorkspaceCommands(program) {
|
|
3945
4248
|
const workspace = program.command("workspace").description("workspaces and their settings");
|
|
3946
4249
|
workspace.command("ls", { isDefault: true }).description("every workspace").action(async function() {
|
|
@@ -3986,45 +4289,25 @@ function registerWorkspaceCommands(program) {
|
|
|
3986
4289
|
workspace.command("new").argument("<name>", "workspace name").argument("<repo>", "owner/name on GitHub").description("create a workspace").option("--branch <name>", "default branch", "main").option("--host <id>", "default host", "box").action(async function(name, repo) {
|
|
3987
4290
|
const opts = this.opts();
|
|
3988
4291
|
const ctx = await requireCtx();
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
slug: slugify(name),
|
|
3993
|
-
repo,
|
|
3994
|
-
default_branch: opts.branch,
|
|
3995
|
-
default_host: opts.host
|
|
3996
|
-
}).select("*").single();
|
|
3997
|
-
if (error) fail(error.message);
|
|
3998
|
-
const { error: agentsError } = await ctx.db.from("agents").insert(DEFAULT_AGENTS.map((agent) => ({ ...agent, workspace_id: data.id })));
|
|
3999
|
-
if (agentsError) fail(`Workspace created, but seeding its agents failed: ${agentsError.message}`);
|
|
4000
|
-
await ctx.audit(data.id, "create", { subject: `workspace ${data.slug}` });
|
|
4292
|
+
const made = must(
|
|
4293
|
+
await createWorkspace(ctx, name, repo, { branch: opts.branch, host: opts.host })
|
|
4294
|
+
);
|
|
4001
4295
|
ok(
|
|
4002
|
-
`${c.bold(
|
|
4003
|
-
{ workspace:
|
|
4296
|
+
`${c.bold(made.workspace.slug)} created with ${made.agents} agents. Run \`hd use ${made.workspace.slug}\`.`,
|
|
4297
|
+
{ workspace: made.workspace }
|
|
4004
4298
|
);
|
|
4005
4299
|
});
|
|
4006
|
-
workspace.command("set").description("change workspace settings").option("--branch <name>", "default branch").option("--host <id>", "default host").option("--auto-merge", "merge approved PRs automatically").option("--no-auto-merge", "wait for a human to merge").option("--cap <provider=n...>", "set a provider cap, e.g. --cap claude=2").action(async function() {
|
|
4300
|
+
workspace.command("set").description("change workspace settings").option("--branch <name>", "default branch").option("--host <id>", "default host").option("--auto-merge", "merge approved PRs automatically").option("--no-auto-merge", "wait for a human to merge").option("--cap <provider=n...>", "set a provider cap, e.g. --cap claude=2", providerCaps(providers)).action(async function() {
|
|
4007
4301
|
const opts = this.opts();
|
|
4008
4302
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
4009
|
-
const
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
if (!providers.includes(provider)) fail(`Unknown provider ${provider}.`);
|
|
4018
|
-
const n = Number(value);
|
|
4019
|
-
if (!Number.isInteger(n) || n < 0) fail(`Cap for ${provider} must be a whole number.`);
|
|
4020
|
-
caps[provider] = n;
|
|
4021
|
-
}
|
|
4022
|
-
fields.provider_caps = caps;
|
|
4023
|
-
}
|
|
4024
|
-
if (Object.keys(fields).length === 0) fail("Nothing to change.");
|
|
4025
|
-
const { data, error } = await ctx.db.from("workspaces").update(fields).eq("id", ctx.workspace.id).select("*").single();
|
|
4026
|
-
if (error) fail(error.message);
|
|
4027
|
-
await ctx.audit(ctx.workspace.id, "configure", { subject: "workspace settings" });
|
|
4303
|
+
const data = must(
|
|
4304
|
+
await updateWorkspace(ctx, {
|
|
4305
|
+
branch: opts.branch,
|
|
4306
|
+
host: opts.host,
|
|
4307
|
+
autoMerge: opts.autoMerge === void 0 ? void 0 : Boolean(opts.autoMerge),
|
|
4308
|
+
caps: opts.cap
|
|
4309
|
+
})
|
|
4310
|
+
);
|
|
4028
4311
|
ok(`${c.bold(data.slug)} updated.`, { workspace: data });
|
|
4029
4312
|
});
|
|
4030
4313
|
workspace.command("templates").description("ticket templates for this workspace").action(async function() {
|
|
@@ -4114,7 +4397,7 @@ function registerAttachCommands(program) {
|
|
|
4114
4397
|
paths: uploaded
|
|
4115
4398
|
});
|
|
4116
4399
|
});
|
|
4117
|
-
attach.command("ls").argument("<key>", "ticket key").description("signed URLs for a ticket's images").option("--ttl <seconds>", "how long the URLs stay valid").action(async function(key2) {
|
|
4400
|
+
attach.command("ls").argument("<key>", "ticket key").description("signed URLs for a ticket's images").option("--ttl <seconds>", "how long the URLs stay valid", rangedInteger("ttl", 30, 3600)).action(async function(key2) {
|
|
4118
4401
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
4119
4402
|
const { data: ticket } = await ctx.db.from("tickets").select("id, key").eq("workspace_id", ctx.workspace.id).eq("key", key2.trim().toUpperCase()).maybeSingle();
|
|
4120
4403
|
if (!ticket) fail(`No ticket ${key2} in ${ctx.workspace.slug}.`);
|
|
@@ -4137,12 +4420,14 @@ var init_commands4 = __esm({
|
|
|
4137
4420
|
"use strict";
|
|
4138
4421
|
init_src();
|
|
4139
4422
|
init_context();
|
|
4423
|
+
init_config2();
|
|
4140
4424
|
init_json();
|
|
4141
4425
|
init_theme();
|
|
4142
4426
|
init_format();
|
|
4143
4427
|
init_queries();
|
|
4144
4428
|
init_views();
|
|
4145
4429
|
init_editor();
|
|
4430
|
+
init_argv_parsers();
|
|
4146
4431
|
DEFAULT_CAPS = { claude: 2, codex: 3, gemini: 1, grok: 2 };
|
|
4147
4432
|
}
|
|
4148
4433
|
});
|
|
@@ -4717,7 +5002,7 @@ async function architectTurn(opts) {
|
|
|
4717
5002
|
if (runError) fail(runError.message);
|
|
4718
5003
|
let seq = 0;
|
|
4719
5004
|
const events = [];
|
|
4720
|
-
const
|
|
5005
|
+
const record2 = (type, payload) => events.push({ run_id: runRow.id, seq: seq++, type, payload });
|
|
4721
5006
|
const flush = async () => {
|
|
4722
5007
|
if (events.length) await ctx.db.from("run_events").insert(events);
|
|
4723
5008
|
};
|
|
@@ -4731,14 +5016,14 @@ async function architectTurn(opts) {
|
|
|
4731
5016
|
sessionId: session.sessionId,
|
|
4732
5017
|
signal: opts.signal,
|
|
4733
5018
|
onEvent: (event) => {
|
|
4734
|
-
if (event.kind === "tool")
|
|
5019
|
+
if (event.kind === "tool") record2("tool_use", { name: event.name, input: event.args });
|
|
4735
5020
|
else if (event.kind === "tool_result") {
|
|
4736
|
-
|
|
5021
|
+
record2("tool_result", { name: event.name, ok: event.ok, content: event.detail });
|
|
4737
5022
|
}
|
|
4738
5023
|
opts.onEvent(event);
|
|
4739
5024
|
}
|
|
4740
5025
|
});
|
|
4741
|
-
if (result.text)
|
|
5026
|
+
if (result.text) record2("text", { text: result.text });
|
|
4742
5027
|
session.history.push({ role: "user", content: opts.request });
|
|
4743
5028
|
session.history.push({ role: "assistant", content: result.text });
|
|
4744
5029
|
await flush();
|
|
@@ -4753,7 +5038,7 @@ async function architectTurn(opts) {
|
|
|
4753
5038
|
return { text: result.text, runId: runRow.id, toolCalls: result.toolCalls };
|
|
4754
5039
|
} catch (error) {
|
|
4755
5040
|
const message = error instanceof Error ? error.message : String(error);
|
|
4756
|
-
|
|
5041
|
+
record2("error", { error: message });
|
|
4757
5042
|
await flush();
|
|
4758
5043
|
await ctx.db.from("runs").update({
|
|
4759
5044
|
status: "failed",
|
|
@@ -4832,297 +5117,1335 @@ var init_theme2 = __esm({
|
|
|
4832
5117
|
});
|
|
4833
5118
|
|
|
4834
5119
|
// src/tui/Banner.tsx
|
|
4835
|
-
import { useEffect, useState } from "react";
|
|
4836
|
-
import { Box, Text } from "ink";
|
|
5120
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5121
|
+
import { Box, Text, useStdout } from "ink";
|
|
4837
5122
|
import { jsx } from "react/jsx-runtime";
|
|
4838
|
-
function
|
|
4839
|
-
|
|
4840
|
-
|
|
5123
|
+
function bannerSize(columns, rows) {
|
|
5124
|
+
if (columns >= BANNER_WIDTH.large + 2 && rows >= BANNER_HEIGHT.large + 26) return "large";
|
|
5125
|
+
if (columns >= BANNER_WIDTH.small + 2 && rows >= BANNER_HEIGHT.small + 8) return "small";
|
|
5126
|
+
return "text";
|
|
5127
|
+
}
|
|
5128
|
+
function pixelRows(word = WORD, gap = 1) {
|
|
5129
|
+
const rows = Array.from({ length: PIXEL_ROWS }, () => "");
|
|
5130
|
+
const letters = [...word].filter((letter) => GLYPHS[letter]);
|
|
5131
|
+
letters.forEach((letter, index) => {
|
|
4841
5132
|
const glyph = GLYPHS[letter];
|
|
4842
|
-
if (!glyph) continue;
|
|
4843
5133
|
const width = Math.max(...glyph.map((row) => row.length));
|
|
4844
|
-
|
|
4845
|
-
|
|
5134
|
+
const spacer = index === letters.length - 1 ? 0 : gap;
|
|
5135
|
+
for (let i = 0; i < PIXEL_ROWS; i += 1) {
|
|
5136
|
+
rows[i] += (glyph[i] ?? "").padEnd(width, ".") + ".".repeat(spacer);
|
|
5137
|
+
}
|
|
5138
|
+
});
|
|
5139
|
+
return rows;
|
|
5140
|
+
}
|
|
5141
|
+
function bannerRows(size = "small", word = WORD) {
|
|
5142
|
+
if (size === "text") return [word];
|
|
5143
|
+
if (size === "large") {
|
|
5144
|
+
return pixelRows(word, 1).map(
|
|
5145
|
+
(row) => [...row].map((pixel) => pixel === "#" ? "\u2588\u2588" : " ").join("")
|
|
5146
|
+
);
|
|
5147
|
+
}
|
|
5148
|
+
const pixels = pixelRows(word, 1);
|
|
5149
|
+
const rows = [];
|
|
5150
|
+
for (let i = 0; i < pixels.length; i += 2) {
|
|
5151
|
+
const top = pixels[i] ?? "";
|
|
5152
|
+
const bottom = pixels[i + 1] ?? "";
|
|
5153
|
+
let row = "";
|
|
5154
|
+
for (let x = 0; x < top.length; x += 1) {
|
|
5155
|
+
const key2 = `${top[x] === "#" ? "1" : "0"}${bottom[x] === "#" ? "1" : "0"}`;
|
|
5156
|
+
row += HALF[key2];
|
|
4846
5157
|
}
|
|
5158
|
+
rows.push(row);
|
|
4847
5159
|
}
|
|
4848
|
-
return rows
|
|
5160
|
+
return rows;
|
|
4849
5161
|
}
|
|
4850
|
-
function
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
5162
|
+
function mulberry32(seed) {
|
|
5163
|
+
let a = seed >>> 0;
|
|
5164
|
+
return () => {
|
|
5165
|
+
a = a + 1831565813 | 0;
|
|
5166
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
5167
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
5168
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
5169
|
+
};
|
|
5170
|
+
}
|
|
5171
|
+
function stormSchedule(opts = {}) {
|
|
5172
|
+
const durationMs = opts.durationMs ?? STORM_MS;
|
|
5173
|
+
const random = mulberry32(opts.seed ?? 1);
|
|
5174
|
+
const between = (min, max) => min + Math.floor(random() * (max - min + 1));
|
|
5175
|
+
const frames = [];
|
|
5176
|
+
let elapsed2 = 0;
|
|
5177
|
+
const push = (frame) => {
|
|
5178
|
+
frames.push(frame);
|
|
5179
|
+
elapsed2 += frame.ms;
|
|
5180
|
+
};
|
|
5181
|
+
while (elapsed2 < durationMs) {
|
|
5182
|
+
for (let strike = 0, strikes = between(2, 5); strike < strikes; strike += 1) {
|
|
5183
|
+
push({ level: 3, ms: between(35, 70) });
|
|
5184
|
+
push({ level: 1, ms: between(25, 55) });
|
|
5185
|
+
if (random() < 0.35) push({ level: 2, ms: between(30, 60) });
|
|
5186
|
+
push({ level: 0, ms: between(45, 160) });
|
|
4856
5187
|
}
|
|
4857
|
-
|
|
5188
|
+
push({ level: 0, ms: between(2200, 13e3), quiet: true });
|
|
5189
|
+
}
|
|
5190
|
+
const last = frames[frames.length - 1];
|
|
5191
|
+
if (last && elapsed2 > durationMs) last.ms = Math.max(1, last.ms - (elapsed2 - durationMs));
|
|
5192
|
+
return frames;
|
|
5193
|
+
}
|
|
5194
|
+
function Banner({
|
|
5195
|
+
animate = true,
|
|
5196
|
+
onDone,
|
|
5197
|
+
columns,
|
|
5198
|
+
rows,
|
|
5199
|
+
seed,
|
|
5200
|
+
durationMs
|
|
5201
|
+
}) {
|
|
5202
|
+
const { stdout } = useStdout();
|
|
5203
|
+
const wide = columns ?? (stdout?.columns && stdout.columns > 0 ? stdout.columns : 80);
|
|
5204
|
+
const tall = rows ?? (stdout?.rows && stdout.rows > 0 ? stdout.rows : 24);
|
|
5205
|
+
const size = bannerSize(wide, tall);
|
|
5206
|
+
const storm = useMemo(
|
|
5207
|
+
() => stormSchedule({ seed: seed ?? Math.floor(Math.random() * 2 ** 31), durationMs }),
|
|
5208
|
+
[seed, durationMs]
|
|
5209
|
+
);
|
|
5210
|
+
const [frame, setFrame] = useState(0);
|
|
5211
|
+
const announced = useRef(false);
|
|
5212
|
+
useEffect(() => {
|
|
5213
|
+
if (announced.current) return;
|
|
5214
|
+
if (!animate || storm.length === 0 || storm[frame]?.quiet) {
|
|
5215
|
+
announced.current = true;
|
|
4858
5216
|
onDone?.();
|
|
4859
|
-
return;
|
|
4860
5217
|
}
|
|
4861
|
-
|
|
5218
|
+
}, [animate, storm, frame, onDone]);
|
|
5219
|
+
useEffect(() => {
|
|
5220
|
+
if (!animate || frame >= storm.length) return;
|
|
5221
|
+
const timer = setTimeout(() => setFrame((current) => current + 1), storm[frame].ms);
|
|
4862
5222
|
return () => clearTimeout(timer);
|
|
4863
|
-
}, [
|
|
4864
|
-
const
|
|
4865
|
-
const
|
|
4866
|
-
|
|
5223
|
+
}, [animate, storm, frame]);
|
|
5224
|
+
const over = !animate || frame >= storm.length;
|
|
5225
|
+
const style = over ? SETTLED : LEVELS[storm[frame].level];
|
|
5226
|
+
const lines = bannerRows(size);
|
|
5227
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: lines.map((line, index) => /* @__PURE__ */ jsx(Text, { color: style.color, dimColor: style.dimColor, bold: style.bold, children: line }, index)) });
|
|
4867
5228
|
}
|
|
4868
|
-
var GLYPHS,
|
|
5229
|
+
var WORD, GLYPHS, PIXEL_ROWS, BANNER_WIDTH, BANNER_HEIGHT, HALF, LEVELS, SETTLED, STORM_MS;
|
|
4869
5230
|
var init_Banner = __esm({
|
|
4870
5231
|
"src/tui/Banner.tsx"() {
|
|
4871
5232
|
"use strict";
|
|
4872
5233
|
init_theme2();
|
|
5234
|
+
WORD = "HigherDEV";
|
|
4873
5235
|
GLYPHS = {
|
|
4874
|
-
H: [
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
5236
|
+
H: [
|
|
5237
|
+
"##...##",
|
|
5238
|
+
"##...##",
|
|
5239
|
+
"##...##",
|
|
5240
|
+
"##...##",
|
|
5241
|
+
"#######",
|
|
5242
|
+
"#######",
|
|
5243
|
+
"##...##",
|
|
5244
|
+
"##...##",
|
|
5245
|
+
"##...##",
|
|
5246
|
+
"##...##",
|
|
5247
|
+
".......",
|
|
5248
|
+
"......."
|
|
5249
|
+
],
|
|
5250
|
+
i: ["##", "##", "..", "##", "##", "##", "##", "##", "##", "##", "..", ".."],
|
|
5251
|
+
g: [
|
|
5252
|
+
".......",
|
|
5253
|
+
".......",
|
|
5254
|
+
".......",
|
|
5255
|
+
".######",
|
|
5256
|
+
"##...##",
|
|
5257
|
+
"##...##",
|
|
5258
|
+
"##...##",
|
|
5259
|
+
"##...##",
|
|
5260
|
+
"##...##",
|
|
5261
|
+
".######",
|
|
5262
|
+
".....##",
|
|
5263
|
+
".#####."
|
|
5264
|
+
],
|
|
5265
|
+
h: [
|
|
5266
|
+
"##.....",
|
|
5267
|
+
"##.....",
|
|
5268
|
+
"##.....",
|
|
5269
|
+
"##.###.",
|
|
5270
|
+
"###..##",
|
|
5271
|
+
"##...##",
|
|
5272
|
+
"##...##",
|
|
5273
|
+
"##...##",
|
|
5274
|
+
"##...##",
|
|
5275
|
+
"##...##",
|
|
5276
|
+
".......",
|
|
5277
|
+
"......."
|
|
5278
|
+
],
|
|
5279
|
+
e: [
|
|
5280
|
+
".......",
|
|
5281
|
+
".......",
|
|
5282
|
+
".......",
|
|
5283
|
+
".#####.",
|
|
5284
|
+
"##...##",
|
|
5285
|
+
"##...##",
|
|
5286
|
+
"#######",
|
|
5287
|
+
"##.....",
|
|
5288
|
+
"##...##",
|
|
5289
|
+
".#####.",
|
|
5290
|
+
".......",
|
|
5291
|
+
"......."
|
|
5292
|
+
],
|
|
5293
|
+
r: [
|
|
5294
|
+
"......",
|
|
5295
|
+
"......",
|
|
5296
|
+
"......",
|
|
5297
|
+
"######",
|
|
5298
|
+
"#####.",
|
|
5299
|
+
"###...",
|
|
5300
|
+
"##....",
|
|
5301
|
+
"##....",
|
|
5302
|
+
"##....",
|
|
5303
|
+
"##....",
|
|
5304
|
+
"......",
|
|
5305
|
+
"......"
|
|
5306
|
+
],
|
|
5307
|
+
D: [
|
|
5308
|
+
"#####..",
|
|
5309
|
+
"##..##.",
|
|
5310
|
+
"##...##",
|
|
5311
|
+
"##...##",
|
|
5312
|
+
"##...##",
|
|
5313
|
+
"##...##",
|
|
5314
|
+
"##...##",
|
|
5315
|
+
"##...##",
|
|
5316
|
+
"##..##.",
|
|
5317
|
+
"#####..",
|
|
5318
|
+
".......",
|
|
5319
|
+
"......."
|
|
5320
|
+
],
|
|
5321
|
+
E: [
|
|
5322
|
+
"######",
|
|
5323
|
+
"######",
|
|
5324
|
+
"##....",
|
|
5325
|
+
"##....",
|
|
5326
|
+
"#####.",
|
|
5327
|
+
"#####.",
|
|
5328
|
+
"##....",
|
|
5329
|
+
"##....",
|
|
5330
|
+
"######",
|
|
5331
|
+
"######",
|
|
5332
|
+
"......",
|
|
5333
|
+
"......"
|
|
5334
|
+
],
|
|
5335
|
+
V: [
|
|
5336
|
+
"##...##",
|
|
5337
|
+
"##...##",
|
|
5338
|
+
"##...##",
|
|
5339
|
+
"##...##",
|
|
5340
|
+
"##...##",
|
|
5341
|
+
".##.##.",
|
|
5342
|
+
".##.##.",
|
|
5343
|
+
".##.##.",
|
|
5344
|
+
"..###..",
|
|
5345
|
+
"..###..",
|
|
5346
|
+
".......",
|
|
5347
|
+
"......."
|
|
5348
|
+
],
|
|
5349
|
+
" ": ["..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", ".."]
|
|
4884
5350
|
};
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
5351
|
+
PIXEL_ROWS = 12;
|
|
5352
|
+
BANNER_WIDTH = {
|
|
5353
|
+
// 56 pixels of letter and 8 of gap, doubled, with the gaps left single.
|
|
5354
|
+
large: 128,
|
|
5355
|
+
small: 64,
|
|
5356
|
+
text: WORD.length
|
|
5357
|
+
};
|
|
5358
|
+
BANNER_HEIGHT = {
|
|
5359
|
+
large: PIXEL_ROWS,
|
|
5360
|
+
small: PIXEL_ROWS / 2,
|
|
5361
|
+
text: 1
|
|
5362
|
+
};
|
|
5363
|
+
HALF = { "00": " ", "10": "\u2580", "01": "\u2584", "11": "\u2588" };
|
|
5364
|
+
LEVELS = [
|
|
5365
|
+
{ color: UI.dim, dimColor: true, bold: false },
|
|
5366
|
+
{ color: UI.accent, dimColor: false, bold: false },
|
|
5367
|
+
{ color: UI.text, dimColor: false, bold: false },
|
|
5368
|
+
{ color: UI.text, dimColor: false, bold: true }
|
|
4895
5369
|
];
|
|
4896
|
-
|
|
5370
|
+
SETTLED = { color: UI.text, dimColor: false, bold: false };
|
|
5371
|
+
STORM_MS = 24e4;
|
|
4897
5372
|
}
|
|
4898
5373
|
});
|
|
4899
5374
|
|
|
4900
|
-
// src/tui/
|
|
5375
|
+
// src/tui/height.ts
|
|
5376
|
+
function wrappedRows(text, width) {
|
|
5377
|
+
if (width <= 0) return 1;
|
|
5378
|
+
return text.split("\n").reduce((total, line) => total + Math.max(1, Math.ceil(line.length / width)), 0);
|
|
5379
|
+
}
|
|
5380
|
+
function bubbleRows(message, width) {
|
|
5381
|
+
const inner = Math.max(1, width - 4);
|
|
5382
|
+
const body = message.body.trim() ? wrappedRows(message.body.trim(), inner) : message.pending ? 1 : 0;
|
|
5383
|
+
return 2 + 1 + (message.steps?.length ?? 0) + body + 1;
|
|
5384
|
+
}
|
|
5385
|
+
function helpNameColumn(commands) {
|
|
5386
|
+
return commands.reduce(
|
|
5387
|
+
(widest, command) => Math.max(widest, `${command.name}${command.args ? ` ${command.args}` : ""}`.length + 1),
|
|
5388
|
+
12
|
|
5389
|
+
);
|
|
5390
|
+
}
|
|
5391
|
+
function helpRows(commands, width) {
|
|
5392
|
+
const inner = Math.max(1, width - 6);
|
|
5393
|
+
const column = helpNameColumn(commands);
|
|
5394
|
+
const lines = commands.reduce(
|
|
5395
|
+
(total, command) => total + Math.max(
|
|
5396
|
+
wrappedRows(command.help, Math.max(1, inner - column)),
|
|
5397
|
+
// The name itself can be wider than the room left for it.
|
|
5398
|
+
Math.ceil(column / Math.max(1, inner))
|
|
5399
|
+
),
|
|
5400
|
+
0
|
|
5401
|
+
);
|
|
5402
|
+
return 2 + 1 + lines + wrappedRows(HELP_FOOTER, inner);
|
|
5403
|
+
}
|
|
5404
|
+
var FRAME_CHROME, PANEL_GAP, HELP_HINT_ROWS;
|
|
5405
|
+
var init_height = __esm({
|
|
5406
|
+
"src/tui/height.ts"() {
|
|
5407
|
+
"use strict";
|
|
5408
|
+
init_Help();
|
|
5409
|
+
FRAME_CHROME = 3;
|
|
5410
|
+
PANEL_GAP = 1;
|
|
5411
|
+
HELP_HINT_ROWS = 2;
|
|
5412
|
+
}
|
|
5413
|
+
});
|
|
5414
|
+
|
|
5415
|
+
// src/tui/Help.tsx
|
|
4901
5416
|
import "react";
|
|
4902
5417
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
4903
5418
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
4904
|
-
function
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
5419
|
+
function HelpHint({ width }) {
|
|
5420
|
+
return /* @__PURE__ */ jsx2(Box2, { width, marginBottom: 1, flexWrap: "nowrap", children: /* @__PURE__ */ jsx2(Text2, { color: UI.dim, wrap: "truncate", children: `${COMMANDS.length} commands. Type /help for the list, or just say what you want.` }) });
|
|
5421
|
+
}
|
|
5422
|
+
function Help({ width }) {
|
|
5423
|
+
return /* @__PURE__ */ jsxs(
|
|
4908
5424
|
Box2,
|
|
4909
5425
|
{
|
|
4910
|
-
borderStyle:
|
|
4911
|
-
borderColor:
|
|
4912
|
-
...style.backgroundColor ? { backgroundColor: style.backgroundColor } : {},
|
|
5426
|
+
borderStyle: "single",
|
|
5427
|
+
borderColor: UI.text,
|
|
4913
5428
|
flexDirection: "column",
|
|
4914
|
-
paddingX:
|
|
5429
|
+
paddingX: 2,
|
|
5430
|
+
width,
|
|
4915
5431
|
children: [
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
5432
|
+
/* @__PURE__ */ jsx2(Text2, { color: UI.text, bold: true, children: "Commands" }),
|
|
5433
|
+
COMMANDS.map((command) => /* @__PURE__ */ jsxs(Box2, { flexWrap: "nowrap", children: [
|
|
5434
|
+
/* @__PURE__ */ jsx2(Box2, { width: helpNameColumn(COMMANDS), flexShrink: 0, children: /* @__PURE__ */ jsxs(Text2, { color: UI.text, wrap: "truncate", children: [
|
|
5435
|
+
command.name,
|
|
5436
|
+
command.args ? ` ${command.args}` : ""
|
|
5437
|
+
] }) }),
|
|
5438
|
+
/* @__PURE__ */ jsx2(Text2, { color: UI.dim, wrap: "truncate", children: command.help })
|
|
5439
|
+
] }, command.name)),
|
|
5440
|
+
/* @__PURE__ */ jsx2(Text2, { color: UI.dim, children: HELP_FOOTER })
|
|
4919
5441
|
]
|
|
4920
5442
|
}
|
|
4921
|
-
)
|
|
5443
|
+
);
|
|
5444
|
+
}
|
|
5445
|
+
var HELP_FOOTER, COMMANDS;
|
|
5446
|
+
var init_Help = __esm({
|
|
5447
|
+
"src/tui/Help.tsx"() {
|
|
5448
|
+
"use strict";
|
|
5449
|
+
init_theme2();
|
|
5450
|
+
init_height();
|
|
5451
|
+
HELP_FOOTER = "Anything not starting with / goes to whoever you are talking to. Ctrl-C leaves.";
|
|
5452
|
+
COMMANDS = [
|
|
5453
|
+
{ name: "/architect", help: "talk to your own model, which can do anything in the platform" },
|
|
5454
|
+
{ name: "/orchestrator", help: "talk to the agent that gets work in flight finished" },
|
|
5455
|
+
{ name: "/browse", help: "put a cursor on the board; up and down move it, enter opens" },
|
|
5456
|
+
{ name: "/workspace", args: "[slug]", help: "switch workspace, or list the ones you can reach" },
|
|
5457
|
+
{ name: "/workspace new", args: "name owner/repo", help: "create a workspace, seeded with agents" },
|
|
5458
|
+
{ name: "/settings", help: "change workspace and agent settings, with the arrow keys" },
|
|
5459
|
+
{ name: "/agent new", args: "name provider model", help: "add a builder" },
|
|
5460
|
+
{ name: "/board", help: "the kanban board" },
|
|
5461
|
+
{ name: "/agents", help: "every agent and what it is doing" },
|
|
5462
|
+
{ name: "/feed", help: "what just happened" },
|
|
5463
|
+
{ name: "/inbox", help: "decisions and messages waiting on you" },
|
|
5464
|
+
{ name: "/decide", args: "2 | text", help: "answer the decision on screen, or --skip it" },
|
|
5465
|
+
{ name: "/ticket", args: "HD-12", help: "open one ticket" },
|
|
5466
|
+
{ name: "/refresh", help: "reload the board now" },
|
|
5467
|
+
{ name: "/help", help: "this list" },
|
|
5468
|
+
{ name: "/exit", help: "leave" }
|
|
5469
|
+
];
|
|
5470
|
+
}
|
|
5471
|
+
});
|
|
5472
|
+
|
|
5473
|
+
// src/tui/Splash.tsx
|
|
5474
|
+
import "react";
|
|
5475
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5476
|
+
function Splash({
|
|
5477
|
+
columns,
|
|
5478
|
+
rows,
|
|
5479
|
+
width,
|
|
5480
|
+
ready,
|
|
5481
|
+
helpFull,
|
|
5482
|
+
animate,
|
|
5483
|
+
onDone
|
|
5484
|
+
}) {
|
|
5485
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
5486
|
+
/* @__PURE__ */ jsx3(Banner, { animate, onDone, columns, rows }),
|
|
5487
|
+
ready ? helpFull ? /* @__PURE__ */ jsx3(Help, { width }) : /* @__PURE__ */ jsx3(HelpHint, { width }) : null
|
|
5488
|
+
] });
|
|
5489
|
+
}
|
|
5490
|
+
var init_Splash = __esm({
|
|
5491
|
+
"src/tui/Splash.tsx"() {
|
|
5492
|
+
"use strict";
|
|
5493
|
+
init_Banner();
|
|
5494
|
+
init_Help();
|
|
5495
|
+
}
|
|
5496
|
+
});
|
|
5497
|
+
|
|
5498
|
+
// src/tui/layout.ts
|
|
5499
|
+
function planLayout(input) {
|
|
5500
|
+
const banner = input.splash ? BANNER_HEIGHT[bannerSize(input.columns, input.rows)] + 1 : 0;
|
|
5501
|
+
const notice = input.notice ? 2 : 0;
|
|
5502
|
+
const other = input.inFlight + notice + FRAME_CHROME + 1;
|
|
5503
|
+
const wantHelp = input.splash && input.ready ? helpRows(COMMANDS, input.width) : 0;
|
|
5504
|
+
const hint = input.splash && input.ready ? HELP_HINT_ROWS : 0;
|
|
5505
|
+
const helpFull = wantHelp > 0 && banner + wantHelp + input.decision + other <= input.rows;
|
|
5506
|
+
const help = wantHelp === 0 ? 0 : helpFull ? wantHelp : hint;
|
|
5507
|
+
const decision = input.decision === 0 ? 0 : Math.max(1, Math.min(input.decision, input.rows - banner - help - other));
|
|
5508
|
+
const fixed = banner + help + decision + input.inFlight + notice;
|
|
5509
|
+
const empty = { cockpit: 0, stream: 0, panels: 0 };
|
|
5510
|
+
const finish = (parts) => {
|
|
5511
|
+
const total = fixed + parts.panels + FRAME_CHROME;
|
|
5512
|
+
return { banner, help, helpFull, decision, ...parts, total, fits: total < input.rows };
|
|
5513
|
+
};
|
|
5514
|
+
if (input.splash) return finish(empty);
|
|
5515
|
+
const gap = input.home ? PANEL_GAP : 0;
|
|
5516
|
+
const room = input.rows - fixed - FRAME_CHROME - gap - 1;
|
|
5517
|
+
if (room < 4) return finish(empty);
|
|
5518
|
+
const stream = Math.max(2, Math.min(14, Math.round(room * 0.4)));
|
|
5519
|
+
const cockpit = Math.max(2, room - stream);
|
|
5520
|
+
return finish({ cockpit, stream, panels: input.home ? cockpit + stream + gap : room });
|
|
5521
|
+
}
|
|
5522
|
+
var init_layout = __esm({
|
|
5523
|
+
"src/tui/layout.ts"() {
|
|
5524
|
+
"use strict";
|
|
5525
|
+
init_Banner();
|
|
5526
|
+
init_Help();
|
|
5527
|
+
init_height();
|
|
5528
|
+
}
|
|
5529
|
+
});
|
|
5530
|
+
|
|
5531
|
+
// src/tui/bounded.tsx
|
|
5532
|
+
import "react";
|
|
5533
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
5534
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
5535
|
+
function BoundedPanel({
|
|
5536
|
+
width,
|
|
5537
|
+
rows,
|
|
5538
|
+
children
|
|
5539
|
+
}) {
|
|
5540
|
+
return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", width, children: children.filter(Boolean).slice(0, Math.max(0, rows)) });
|
|
5541
|
+
}
|
|
5542
|
+
function contentRows(rows, items) {
|
|
5543
|
+
const forContent = Math.max(0, rows - 1);
|
|
5544
|
+
return items <= forContent ? forContent : Math.max(0, forContent - 1);
|
|
5545
|
+
}
|
|
5546
|
+
function More({ count }) {
|
|
5547
|
+
if (count <= 0) return null;
|
|
5548
|
+
return /* @__PURE__ */ jsx4(Text3, { color: UI.dim, children: ` +${count} more` });
|
|
5549
|
+
}
|
|
5550
|
+
function Heading({ text, note }) {
|
|
5551
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexWrap: "nowrap", children: [
|
|
5552
|
+
/* @__PURE__ */ jsx4(Text3, { color: UI.text, bold: true, wrap: "truncate", children: text }),
|
|
5553
|
+
note ? /* @__PURE__ */ jsxs3(Text3, { color: UI.dim, wrap: "truncate", children: [
|
|
5554
|
+
" ",
|
|
5555
|
+
note
|
|
5556
|
+
] }) : null
|
|
5557
|
+
] });
|
|
5558
|
+
}
|
|
5559
|
+
var init_bounded = __esm({
|
|
5560
|
+
"src/tui/bounded.tsx"() {
|
|
5561
|
+
"use strict";
|
|
5562
|
+
init_theme2();
|
|
5563
|
+
}
|
|
5564
|
+
});
|
|
5565
|
+
|
|
5566
|
+
// src/tui/Dashboard.tsx
|
|
5567
|
+
import "react";
|
|
5568
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
5569
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
5570
|
+
function splitWidths(width) {
|
|
5571
|
+
if (width < SPLIT_AT) return null;
|
|
5572
|
+
const agents = Math.max(30, Math.min(42, Math.round(width * 0.4)));
|
|
5573
|
+
return { board: width - agents - 1, agents };
|
|
5574
|
+
}
|
|
5575
|
+
function boardEntries(board) {
|
|
5576
|
+
const entries = [];
|
|
5577
|
+
for (const status of BOARD_COLUMNS) {
|
|
5578
|
+
const tickets = board.tickets.filter((ticket) => ticket.status === status);
|
|
5579
|
+
if (tickets.length === 0) continue;
|
|
5580
|
+
entries.push({ key: `h:${status}`, kind: "heading", status, count: tickets.length });
|
|
5581
|
+
for (const ticket of tickets) entries.push({ key: ticket.id, kind: "ticket", ticket });
|
|
5582
|
+
}
|
|
5583
|
+
return entries;
|
|
5584
|
+
}
|
|
5585
|
+
function boardTicketIds(board) {
|
|
5586
|
+
return boardEntries(board).filter((entry) => entry.kind === "ticket").map((entry) => entry.key);
|
|
5587
|
+
}
|
|
5588
|
+
function nextCursor(order, current, delta) {
|
|
5589
|
+
if (order.length === 0) return null;
|
|
5590
|
+
const at = current ? order.indexOf(current) : -1;
|
|
5591
|
+
if (at < 0) return delta > 0 ? order[0] : order[order.length - 1];
|
|
5592
|
+
return order[Math.min(order.length - 1, Math.max(0, at + delta))];
|
|
5593
|
+
}
|
|
5594
|
+
function scrollWindow(count, rows, focus) {
|
|
5595
|
+
if (rows >= count) return { start: 0, end: count };
|
|
5596
|
+
if (rows <= 0) return { start: 0, end: 0 };
|
|
5597
|
+
if (focus < 0) return { start: 0, end: rows };
|
|
5598
|
+
const start = Math.max(0, Math.min(focus - Math.floor(rows / 2), count - rows));
|
|
5599
|
+
return { start, end: start + rows };
|
|
5600
|
+
}
|
|
5601
|
+
function hiddenTickets(entries, start, end) {
|
|
5602
|
+
const tickets = (from, to) => entries.slice(from, to).filter((entry) => entry.kind === "ticket").length;
|
|
5603
|
+
return { above: tickets(0, start), below: tickets(end, entries.length) };
|
|
5604
|
+
}
|
|
5605
|
+
function hiddenNote(total, hidden2) {
|
|
5606
|
+
if (hidden2.above === 0 && hidden2.below === 0) return `${total}`;
|
|
5607
|
+
return `${total} ${hidden2.above > 0 ? `${hidden2.above}\u2191 ` : ""}${hidden2.below > 0 ? `${hidden2.below}\u2193` : ""}`.trimEnd();
|
|
5608
|
+
}
|
|
5609
|
+
function BoardColumn({
|
|
5610
|
+
board,
|
|
5611
|
+
width,
|
|
5612
|
+
rows,
|
|
5613
|
+
cursor
|
|
5614
|
+
}) {
|
|
5615
|
+
const entries = boardEntries(board);
|
|
5616
|
+
const inner = Math.max(0, rows - 1);
|
|
5617
|
+
const focus = cursor ? entries.findIndex((entry) => entry.key === cursor) : -1;
|
|
5618
|
+
const { start, end } = scrollWindow(entries.length, inner, focus);
|
|
5619
|
+
const title = Math.max(8, width - 10);
|
|
5620
|
+
return /* @__PURE__ */ jsx5(BoundedPanel, { width, rows, children: [
|
|
5621
|
+
/* @__PURE__ */ jsx5(
|
|
5622
|
+
Heading,
|
|
5623
|
+
{
|
|
5624
|
+
text: "Board",
|
|
5625
|
+
note: hiddenNote(board.tickets.length, hiddenTickets(entries, start, end))
|
|
5626
|
+
},
|
|
5627
|
+
"h"
|
|
5628
|
+
),
|
|
5629
|
+
...entries.length === 0 ? [
|
|
5630
|
+
/* @__PURE__ */ jsx5(Text4, { color: UI.dim, children: "No tickets yet." }, "empty")
|
|
5631
|
+
] : entries.slice(start, end).map(
|
|
5632
|
+
(entry) => entry.kind === "heading" ? /* @__PURE__ */ jsxs4(Text4, { color: inkColor(statusTone(entry.status)), wrap: "truncate", children: [
|
|
5633
|
+
statusLabel(entry.status),
|
|
5634
|
+
" ",
|
|
5635
|
+
/* @__PURE__ */ jsxs4(Text4, { color: UI.dim, children: [
|
|
5636
|
+
"(",
|
|
5637
|
+
entry.count,
|
|
5638
|
+
")"
|
|
5639
|
+
] })
|
|
5640
|
+
] }, entry.key) : /* @__PURE__ */ jsxs4(Box4, { flexWrap: "nowrap", children: [
|
|
5641
|
+
/* @__PURE__ */ jsx5(Box4, { width: 2, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text4, { color: UI.accent, children: entry.key === cursor ? "\u203A" : " " }) }),
|
|
5642
|
+
/* @__PURE__ */ jsx5(Box4, { width: 8, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text4, { color: UI.text, bold: true, wrap: "truncate", children: entry.ticket.key }) }),
|
|
5643
|
+
/* @__PURE__ */ jsx5(
|
|
5644
|
+
Text4,
|
|
5645
|
+
{
|
|
5646
|
+
color: entry.ticket.stuck ? UI.warn : UI.text,
|
|
5647
|
+
inverse: entry.key === cursor,
|
|
5648
|
+
wrap: "truncate",
|
|
5649
|
+
children: truncate(entry.ticket.title, title - 2)
|
|
5650
|
+
}
|
|
5651
|
+
)
|
|
5652
|
+
] }, entry.key)
|
|
5653
|
+
)
|
|
5654
|
+
] });
|
|
5655
|
+
}
|
|
5656
|
+
function AgentsColumn({
|
|
5657
|
+
board,
|
|
5658
|
+
width,
|
|
5659
|
+
rows
|
|
5660
|
+
}) {
|
|
5661
|
+
const ordered = [...board.agents].sort((left, right) => {
|
|
5662
|
+
const busy = (id) => board.runs.some((run5) => run5.agent_id === id && run5.status === "running") ? 0 : 1;
|
|
5663
|
+
return busy(left.id) - busy(right.id) || left.display_name.localeCompare(right.display_name);
|
|
5664
|
+
});
|
|
5665
|
+
const shown = ordered.slice(0, contentRows(rows, ordered.length));
|
|
5666
|
+
return /* @__PURE__ */ jsx5(BoundedPanel, { width, rows, children: [
|
|
5667
|
+
/* @__PURE__ */ jsx5(Heading, { text: "Agents", note: `${board.agents.filter((a) => a.enabled).length} on` }, "h"),
|
|
5668
|
+
...board.agents.length === 0 ? [
|
|
5669
|
+
/* @__PURE__ */ jsx5(Text4, { color: UI.dim, children: "No agents yet." }, "empty")
|
|
5670
|
+
] : [],
|
|
5671
|
+
...shown.map((agent) => {
|
|
5672
|
+
const run5 = board.runs.find((row) => row.agent_id === agent.id && row.status === "running");
|
|
5673
|
+
const ticket = run5?.ticket_id ? board.tickets.find((item) => item.id === run5.ticket_id) : void 0;
|
|
5674
|
+
const availability = board.availability.find((row) => row.provider === agent.provider);
|
|
5675
|
+
const blocked = availability && !availability.available ? availabilityLabel(availability) : "";
|
|
5676
|
+
const tone2 = !agent.enabled ? "muted" : run5 ? "blue" : blocked ? "warning" : "muted";
|
|
5677
|
+
const name = Math.max(8, Math.min(22, width - 14));
|
|
5678
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexWrap: "nowrap", children: [
|
|
5679
|
+
/* @__PURE__ */ jsxs4(Text4, { color: inkColor(tone2), children: [
|
|
5680
|
+
DOT2,
|
|
5681
|
+
" "
|
|
5682
|
+
] }),
|
|
5683
|
+
/* @__PURE__ */ jsx5(Box4, { width: name, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text4, { color: UI.text, wrap: "truncate", children: truncate(agent.display_name, name - 1) }) }),
|
|
5684
|
+
/* @__PURE__ */ jsx5(Text4, { color: UI.dim, wrap: "truncate", children: run5 ? `${ticket ? `${ticket.key} ` : ""}${elapsed(run5.started_at ?? run5.created_at)}` : blocked || (agent.enabled ? "idle" : "off") })
|
|
5685
|
+
] }, agent.id);
|
|
5686
|
+
}),
|
|
5687
|
+
/* @__PURE__ */ jsx5(More, { count: ordered.length - shown.length }, "more")
|
|
5688
|
+
] });
|
|
5689
|
+
}
|
|
5690
|
+
function StreamPanel({
|
|
5691
|
+
lines,
|
|
5692
|
+
width,
|
|
5693
|
+
rows,
|
|
5694
|
+
live
|
|
5695
|
+
}) {
|
|
5696
|
+
const name = Math.max(8, Math.min(18, Math.round(width / 5)));
|
|
5697
|
+
const budget = Math.max(0, rows - 1);
|
|
5698
|
+
return /* @__PURE__ */ jsx5(BoundedPanel, { width, rows, children: [
|
|
5699
|
+
/* @__PURE__ */ jsx5(Heading, { text: "Activity", note: live ? "" : "idle" }, "h"),
|
|
5700
|
+
...lines.length === 0 ? [
|
|
5701
|
+
/* @__PURE__ */ jsx5(Text4, { color: UI.dim, children: live ? "Waiting for the first step." : "Nothing running." }, "empty")
|
|
5702
|
+
] : [],
|
|
5703
|
+
...lines.slice(-budget).map((line) => /* @__PURE__ */ jsxs4(Box4, { flexWrap: "nowrap", children: [
|
|
5704
|
+
/* @__PURE__ */ jsx5(Box4, { width: name, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text4, { color: UI.dim, wrap: "truncate", children: truncate(line.agent, name - 1) }) }),
|
|
5705
|
+
/* @__PURE__ */ jsxs4(Text4, { color: KIND_COLOR[line.kind], wrap: "truncate", children: [
|
|
5706
|
+
line.kind === "tool" ? "\xB7 " : "",
|
|
5707
|
+
truncate(line.title, Math.max(12, width - name - 3))
|
|
5708
|
+
] })
|
|
5709
|
+
] }, line.id))
|
|
5710
|
+
] });
|
|
5711
|
+
}
|
|
5712
|
+
function epicsRows(board, cap = 3) {
|
|
5713
|
+
const open = epicProgress(board).filter((row) => row.epic.status !== "done");
|
|
5714
|
+
return open.length === 0 ? 0 : Math.min(open.length, cap) + 1;
|
|
5715
|
+
}
|
|
5716
|
+
function EpicsStrip({ board, width, rows }) {
|
|
5717
|
+
const open = epicProgress(board).filter((row) => row.epic.status !== "done");
|
|
5718
|
+
if (open.length === 0 || rows < 2) return null;
|
|
5719
|
+
const shown = open.slice(0, contentRows(rows, open.length));
|
|
5720
|
+
const title = Math.max(12, Math.min(52, width - 24));
|
|
5721
|
+
return /* @__PURE__ */ jsx5(BoundedPanel, { width, rows, children: [
|
|
5722
|
+
/* @__PURE__ */ jsx5(Heading, { text: "Epics", note: `${open.length}` }, "h"),
|
|
5723
|
+
...shown.map((row) => /* @__PURE__ */ jsxs4(Box4, { flexWrap: "nowrap", children: [
|
|
5724
|
+
/* @__PURE__ */ jsx5(Box4, { width: title, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text4, { color: UI.text, wrap: "truncate", children: truncate(row.epic.title, title - 1) }) }),
|
|
5725
|
+
/* @__PURE__ */ jsx5(Text4, { color: UI.dim, wrap: "truncate", children: epicProgressCaption(row.merged, row.total, row.cancelled) })
|
|
5726
|
+
] }, row.epic.id)),
|
|
5727
|
+
/* @__PURE__ */ jsx5(More, { count: open.length - shown.length }, "more")
|
|
5728
|
+
] });
|
|
5729
|
+
}
|
|
5730
|
+
function Cockpit({
|
|
5731
|
+
board,
|
|
5732
|
+
width,
|
|
5733
|
+
rows,
|
|
5734
|
+
cursor
|
|
5735
|
+
}) {
|
|
5736
|
+
const epics = Math.min(epicsRows(board), Math.max(0, rows - 4));
|
|
5737
|
+
const rest = rows - (epics > 0 ? epics + 1 : 0);
|
|
5738
|
+
const columns = renderColumns(board, width, rest, cursor);
|
|
5739
|
+
if (epics === 0) return columns;
|
|
5740
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
5741
|
+
/* @__PURE__ */ jsx5(EpicsStrip, { board, width, rows: epics }),
|
|
5742
|
+
/* @__PURE__ */ jsx5(Box4, { height: 1 }),
|
|
5743
|
+
columns
|
|
5744
|
+
] });
|
|
5745
|
+
}
|
|
5746
|
+
function renderColumns(board, width, rows, cursor) {
|
|
5747
|
+
const split = splitWidths(width);
|
|
5748
|
+
if (!split) {
|
|
5749
|
+
const agents = Math.max(1, Math.min(board.agents.length + 1, Math.floor(rows / 3)));
|
|
5750
|
+
const boardRows = Math.max(0, rows - agents - 1);
|
|
5751
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
5752
|
+
boardRows > 0 ? /* @__PURE__ */ jsx5(BoardColumn, { board, width, rows: boardRows, cursor }) : null,
|
|
5753
|
+
boardRows > 0 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
5754
|
+
/* @__PURE__ */ jsx5(AgentsColumn, { board, width, rows: agents })
|
|
5755
|
+
] });
|
|
5756
|
+
}
|
|
5757
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "row", flexWrap: "nowrap", children: [
|
|
5758
|
+
/* @__PURE__ */ jsx5(BoardColumn, { board, width: split.board, rows, cursor }),
|
|
5759
|
+
/* @__PURE__ */ jsx5(Box4, { width: 1, flexShrink: 0 }),
|
|
5760
|
+
/* @__PURE__ */ jsx5(AgentsColumn, { board, width: split.agents, rows })
|
|
5761
|
+
] });
|
|
5762
|
+
}
|
|
5763
|
+
var DOT2, SPLIT_AT, KIND_COLOR;
|
|
5764
|
+
var init_Dashboard = __esm({
|
|
5765
|
+
"src/tui/Dashboard.tsx"() {
|
|
5766
|
+
"use strict";
|
|
5767
|
+
init_src();
|
|
5768
|
+
init_format();
|
|
5769
|
+
init_theme();
|
|
5770
|
+
init_queries();
|
|
5771
|
+
init_theme2();
|
|
5772
|
+
init_bounded();
|
|
5773
|
+
DOT2 = "\u25CF";
|
|
5774
|
+
SPLIT_AT = 96;
|
|
5775
|
+
KIND_COLOR = {
|
|
5776
|
+
error: UI.danger,
|
|
5777
|
+
tool: UI.dim,
|
|
5778
|
+
status: UI.dim,
|
|
5779
|
+
text: UI.text
|
|
5780
|
+
};
|
|
5781
|
+
}
|
|
5782
|
+
});
|
|
5783
|
+
|
|
5784
|
+
// src/tui/Settings.tsx
|
|
5785
|
+
import "react";
|
|
5786
|
+
import { Text as Text5 } from "ink";
|
|
5787
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
5788
|
+
function SettingsPanel({
|
|
5789
|
+
entries,
|
|
5790
|
+
width,
|
|
5791
|
+
rows,
|
|
5792
|
+
cursor,
|
|
5793
|
+
editing
|
|
5794
|
+
}) {
|
|
5795
|
+
const inner = Math.max(0, rows - 1);
|
|
5796
|
+
const focus = cursor ? entries.findIndex((row) => row.key === cursor) : -1;
|
|
5797
|
+
const { start, end } = scrollWindow(entries.length, inner, focus);
|
|
5798
|
+
const label = Math.max(10, Math.min(18, Math.round(width / 4)));
|
|
5799
|
+
return /* @__PURE__ */ jsx6(BoundedPanel, { width, rows, children: [
|
|
5800
|
+
/* @__PURE__ */ jsx6(Heading, { text: "Settings", note: hidden(entries.length, start, end) }, "h"),
|
|
5801
|
+
...entries.slice(start, end).map((row) => {
|
|
5802
|
+
if (row.kind === "heading") {
|
|
5803
|
+
return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
|
|
5804
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.text, bold: true, children: row.label }),
|
|
5805
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.dim, children: ` ${row.value}` })
|
|
5806
|
+
] }, row.key);
|
|
5807
|
+
}
|
|
5808
|
+
const selected = row.key === cursor;
|
|
5809
|
+
const typing = editing?.key === row.key;
|
|
5810
|
+
return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
|
|
5811
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.accent, children: selected ? " \u203A " : " " }),
|
|
5812
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.dim, children: pad(truncate(row.label, label - 1), label) }),
|
|
5813
|
+
/* @__PURE__ */ jsx6(Text5, { color: typing ? UI.cream : UI.text, inverse: selected && !typing, children: truncate(typing ? `${editing.draft}\u258F` : row.value || "-", Math.max(8, width - label - 6)) })
|
|
5814
|
+
] }, row.key);
|
|
5815
|
+
})
|
|
5816
|
+
] });
|
|
5817
|
+
}
|
|
5818
|
+
function hidden(count, start, end) {
|
|
5819
|
+
const above = start;
|
|
5820
|
+
const below = count - end;
|
|
5821
|
+
if (above === 0 && below === 0) return `${count}`;
|
|
5822
|
+
return `${count} ${above > 0 ? `${above}\u2191 ` : ""}${below > 0 ? `${below}\u2193` : ""}`.trimEnd();
|
|
5823
|
+
}
|
|
5824
|
+
var init_Settings = __esm({
|
|
5825
|
+
"src/tui/Settings.tsx"() {
|
|
5826
|
+
"use strict";
|
|
5827
|
+
init_format();
|
|
5828
|
+
init_bounded();
|
|
5829
|
+
init_Dashboard();
|
|
5830
|
+
init_theme2();
|
|
5831
|
+
}
|
|
5832
|
+
});
|
|
5833
|
+
|
|
5834
|
+
// src/tui/settings-model.ts
|
|
5835
|
+
function capLabel(value) {
|
|
5836
|
+
return value == null ? UNLIMITED : String(value);
|
|
5837
|
+
}
|
|
5838
|
+
function settingsRows(workspace, agents) {
|
|
5839
|
+
const caps = workspace.provider_caps ?? {};
|
|
5840
|
+
const rows = [
|
|
5841
|
+
{ key: "h:workspace", kind: "heading", label: workspace.slug, value: workspace.repo },
|
|
5842
|
+
{ key: "w:branch", kind: "text", label: "branch", value: workspace.default_branch },
|
|
5843
|
+
{ key: "w:host", kind: "text", label: "host", value: workspace.default_host },
|
|
5844
|
+
{
|
|
5845
|
+
key: "w:auto_merge",
|
|
5846
|
+
kind: "toggle",
|
|
5847
|
+
label: "auto merge",
|
|
5848
|
+
value: workspace.auto_merge ? "yes" : "no"
|
|
5849
|
+
}
|
|
5850
|
+
];
|
|
5851
|
+
for (const provider of providers) {
|
|
5852
|
+
const cap = caps[provider];
|
|
5853
|
+
rows.push({
|
|
5854
|
+
key: `w:cap:${provider}`,
|
|
5855
|
+
kind: "number",
|
|
5856
|
+
label: `cap ${provider}`,
|
|
5857
|
+
value: typeof cap === "number" ? String(cap) : "0",
|
|
5858
|
+
hint: "a whole number, 0 to stop using it"
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
for (const agent of agents) {
|
|
5862
|
+
rows.push({
|
|
5863
|
+
key: `h:${agent.id}`,
|
|
5864
|
+
kind: "heading",
|
|
5865
|
+
label: agent.display_name,
|
|
5866
|
+
value: agent.role
|
|
5867
|
+
});
|
|
5868
|
+
rows.push({
|
|
5869
|
+
key: `a:${agent.id}:enabled`,
|
|
5870
|
+
kind: "toggle",
|
|
5871
|
+
label: "enabled",
|
|
5872
|
+
value: agent.enabled ? "yes" : "no",
|
|
5873
|
+
agent: agent.display_name
|
|
5874
|
+
});
|
|
5875
|
+
rows.push({
|
|
5876
|
+
key: `a:${agent.id}:provider`,
|
|
5877
|
+
kind: "choice",
|
|
5878
|
+
label: "provider",
|
|
5879
|
+
value: agent.provider,
|
|
5880
|
+
choices: providers,
|
|
5881
|
+
agent: agent.display_name
|
|
5882
|
+
});
|
|
5883
|
+
rows.push({
|
|
5884
|
+
key: `a:${agent.id}:model`,
|
|
5885
|
+
kind: "text",
|
|
5886
|
+
label: "model",
|
|
5887
|
+
value: agent.model,
|
|
5888
|
+
agent: agent.display_name
|
|
5889
|
+
});
|
|
5890
|
+
rows.push({
|
|
5891
|
+
key: `a:${agent.id}:effort`,
|
|
5892
|
+
kind: "choice",
|
|
5893
|
+
label: "effort",
|
|
5894
|
+
value: asEffort(agent.effort),
|
|
5895
|
+
choices: efforts,
|
|
5896
|
+
agent: agent.display_name
|
|
5897
|
+
});
|
|
5898
|
+
rows.push({
|
|
5899
|
+
key: `a:${agent.id}:runs_per_hour`,
|
|
5900
|
+
kind: "number",
|
|
5901
|
+
label: "runs per hour",
|
|
5902
|
+
value: capLabel(agent.runs_per_hour),
|
|
5903
|
+
agent: agent.display_name,
|
|
5904
|
+
hint: `a number, or ${UNLIMITED}`
|
|
5905
|
+
});
|
|
5906
|
+
rows.push({
|
|
5907
|
+
key: `a:${agent.id}:daily_spend_usd`,
|
|
5908
|
+
kind: "number",
|
|
5909
|
+
label: "usd per day",
|
|
5910
|
+
value: capLabel(agent.daily_spend_usd),
|
|
5911
|
+
agent: agent.display_name,
|
|
5912
|
+
hint: `a number, or ${UNLIMITED}`
|
|
5913
|
+
});
|
|
5914
|
+
rows.push({
|
|
5915
|
+
key: `a:${agent.id}:routing_notes`,
|
|
5916
|
+
kind: "text",
|
|
5917
|
+
label: "routing notes",
|
|
5918
|
+
value: agent.routing_notes,
|
|
5919
|
+
agent: agent.display_name
|
|
5920
|
+
});
|
|
5921
|
+
}
|
|
5922
|
+
return rows;
|
|
5923
|
+
}
|
|
5924
|
+
function editableKeys(rows) {
|
|
5925
|
+
return rows.filter((row) => row.kind !== "heading" && row.kind !== "readonly").map((row) => row.key);
|
|
5926
|
+
}
|
|
5927
|
+
function nextValue(row) {
|
|
5928
|
+
if (row.kind === "toggle") return row.value === "yes" ? "no" : "yes";
|
|
5929
|
+
if (row.kind === "choice" && row.choices?.length) {
|
|
5930
|
+
const at = row.choices.indexOf(row.value);
|
|
5931
|
+
return row.choices[(at + 1) % row.choices.length];
|
|
5932
|
+
}
|
|
5933
|
+
return null;
|
|
5934
|
+
}
|
|
5935
|
+
function seedFor(row) {
|
|
5936
|
+
if (row.value === UNLIMITED || row.value === "-" || row.value === "") return "";
|
|
5937
|
+
return row.value;
|
|
5938
|
+
}
|
|
5939
|
+
function editFor(row, raw) {
|
|
5940
|
+
const value = raw.trim();
|
|
5941
|
+
const [scope, id, field] = row.key.split(":");
|
|
5942
|
+
if (scope === "w") {
|
|
5943
|
+
if (id === "branch") return ok2({ target: "workspace", input: { branch: value } });
|
|
5944
|
+
if (id === "host") return ok2({ target: "workspace", input: { host: value } });
|
|
5945
|
+
if (id === "auto_merge") return ok2({ target: "workspace", input: { autoMerge: value === "yes" } });
|
|
5946
|
+
if (id === "cap") {
|
|
5947
|
+
const cap = parseField(optionalPositiveInteger(`cap ${field}`), value === "0" ? "none" : value);
|
|
5948
|
+
if (!cap.ok) return no(cap.error);
|
|
5949
|
+
return ok2({
|
|
5950
|
+
target: "workspace",
|
|
5951
|
+
input: { caps: [{ provider: field, cap: cap.value ?? 0 }] }
|
|
5952
|
+
});
|
|
5953
|
+
}
|
|
5954
|
+
return no(`Nothing to change on ${row.label}.`);
|
|
5955
|
+
}
|
|
5956
|
+
const name = row.agent;
|
|
5957
|
+
if (scope !== "a" || !name) return no(`Nothing to change on ${row.label}.`);
|
|
5958
|
+
switch (field) {
|
|
5959
|
+
case "enabled":
|
|
5960
|
+
return ok2({ target: "agent", name, input: { enabled: value === "yes" } });
|
|
5961
|
+
case "provider": {
|
|
5962
|
+
const provider = parseField(oneOf("provider", providers), value);
|
|
5963
|
+
return provider.ok ? ok2({ target: "agent", name, input: { provider: provider.value } }) : no(provider.error);
|
|
5964
|
+
}
|
|
5965
|
+
case "model":
|
|
5966
|
+
return ok2({ target: "agent", name, input: { model: value } });
|
|
5967
|
+
case "effort": {
|
|
5968
|
+
const effort = parseField(oneOf("effort", efforts), value);
|
|
5969
|
+
return effort.ok ? ok2({ target: "agent", name, input: { effort: effort.value } }) : no(effort.error);
|
|
5970
|
+
}
|
|
5971
|
+
case "runs_per_hour": {
|
|
5972
|
+
const cap = parseField(optionalPositiveInteger("runs per hour"), unlimitedAsNone(value));
|
|
5973
|
+
return cap.ok ? ok2({ target: "agent", name, input: { runsPerHour: cap.value } }) : no(cap.error);
|
|
5974
|
+
}
|
|
5975
|
+
case "daily_spend_usd": {
|
|
5976
|
+
const cap = parseField(optionalPositiveNumber("usd per day"), unlimitedAsNone(value));
|
|
5977
|
+
return cap.ok ? ok2({ target: "agent", name, input: { dailySpend: cap.value } }) : no(cap.error);
|
|
5978
|
+
}
|
|
5979
|
+
case "routing_notes":
|
|
5980
|
+
return ok2({ target: "agent", name, input: { notes: raw } });
|
|
5981
|
+
default:
|
|
5982
|
+
return no(`Nothing to change on ${row.label}.`);
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
function unlimitedAsNone(value) {
|
|
5986
|
+
return value.toLowerCase() === UNLIMITED || value === "" ? "none" : value;
|
|
5987
|
+
}
|
|
5988
|
+
var UNLIMITED;
|
|
5989
|
+
var init_settings_model = __esm({
|
|
5990
|
+
"src/tui/settings-model.ts"() {
|
|
5991
|
+
"use strict";
|
|
5992
|
+
init_src();
|
|
5993
|
+
init_argv_parsers();
|
|
5994
|
+
init_config2();
|
|
5995
|
+
UNLIMITED = "unlimited";
|
|
5996
|
+
}
|
|
5997
|
+
});
|
|
5998
|
+
|
|
5999
|
+
// src/tui/Panels.tsx
|
|
6000
|
+
import "react";
|
|
6001
|
+
import { Box as Box5, Text as Text6 } from "ink";
|
|
6002
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
6003
|
+
function AgentsPanel({
|
|
6004
|
+
board,
|
|
6005
|
+
width = 80,
|
|
6006
|
+
rows = 12
|
|
6007
|
+
}) {
|
|
6008
|
+
const shown = board.agents.slice(0, contentRows(rows, board.agents.length));
|
|
6009
|
+
return /* @__PURE__ */ jsx7(BoundedPanel, { width, rows, children: [
|
|
6010
|
+
/* @__PURE__ */ jsx7(Heading, { text: "Agents", note: `${board.agents.length}` }, "h"),
|
|
6011
|
+
...board.agents.length === 0 ? [
|
|
6012
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: "No agents yet." }, "empty")
|
|
6013
|
+
] : [],
|
|
6014
|
+
...shown.map((agent) => {
|
|
6015
|
+
const run5 = board.runs.find((row) => row.agent_id === agent.id && row.status === "running");
|
|
6016
|
+
const ticket = run5?.ticket_id ? board.tickets.find((item) => item.id === run5.ticket_id) : void 0;
|
|
6017
|
+
const availability = board.availability.find((row) => row.provider === agent.provider);
|
|
6018
|
+
const blocked = availability && !availability.available ? availabilityLabel(availability) : "";
|
|
6019
|
+
const tone2 = !agent.enabled ? "muted" : run5 ? "blue" : blocked ? "warning" : "muted";
|
|
6020
|
+
return (
|
|
6021
|
+
// One truncating line, with the columns padded inside it. A row of
|
|
6022
|
+
// fixed width boxes wraps once they add up to more than the
|
|
6023
|
+
// terminal, however the text inside them is set to wrap.
|
|
6024
|
+
/* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
6025
|
+
/* @__PURE__ */ jsxs6(Text6, { color: inkColor(tone2), children: [
|
|
6026
|
+
DOT3,
|
|
6027
|
+
" "
|
|
6028
|
+
] }),
|
|
6029
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, children: pad(truncate(agent.display_name, 13), 14) }),
|
|
6030
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: pad(agent.role, 13) }),
|
|
6031
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: pad(truncate(agent.model, 23), 24) }),
|
|
6032
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, children: pad(run5 ? "running" : blocked ? "blocked" : "idle", 9) }),
|
|
6033
|
+
/* @__PURE__ */ jsxs6(Text6, { color: UI.dim, children: [
|
|
6034
|
+
ticket ? `${ticket.key} ` : "",
|
|
6035
|
+
run5 ? elapsed(run5.started_at ?? run5.created_at) : blocked
|
|
6036
|
+
] })
|
|
6037
|
+
] }, agent.id)
|
|
6038
|
+
);
|
|
6039
|
+
}),
|
|
6040
|
+
/* @__PURE__ */ jsx7(More, { count: board.agents.length - shown.length }, "more")
|
|
6041
|
+
] });
|
|
6042
|
+
}
|
|
6043
|
+
function BoardPanel({
|
|
6044
|
+
board,
|
|
6045
|
+
width = 80,
|
|
6046
|
+
rows = 12,
|
|
6047
|
+
cursor
|
|
6048
|
+
}) {
|
|
6049
|
+
const active = BOARD_COLUMNS.filter(
|
|
6050
|
+
(status) => board.tickets.some((ticket) => ticket.status === status)
|
|
6051
|
+
);
|
|
6052
|
+
const budget = contentRows(rows, board.tickets.length + active.length);
|
|
6053
|
+
const lines = [];
|
|
6054
|
+
let dropped = 0;
|
|
6055
|
+
for (const status of active) {
|
|
6056
|
+
const tickets = board.tickets.filter((ticket) => ticket.status === status);
|
|
6057
|
+
if (lines.length + 1 >= budget) {
|
|
6058
|
+
dropped += tickets.length;
|
|
6059
|
+
continue;
|
|
6060
|
+
}
|
|
6061
|
+
lines.push(
|
|
6062
|
+
/* @__PURE__ */ jsxs6(Text6, { color: inkColor(statusTone(status)), wrap: "truncate", children: [
|
|
6063
|
+
statusLabel(status),
|
|
6064
|
+
" ",
|
|
6065
|
+
/* @__PURE__ */ jsxs6(Text6, { color: UI.dim, children: [
|
|
6066
|
+
"(",
|
|
6067
|
+
tickets.length,
|
|
6068
|
+
")"
|
|
6069
|
+
] })
|
|
6070
|
+
] }, status)
|
|
6071
|
+
);
|
|
6072
|
+
for (const ticket of tickets) {
|
|
6073
|
+
if (lines.length >= budget) {
|
|
6074
|
+
dropped += 1;
|
|
6075
|
+
continue;
|
|
6076
|
+
}
|
|
6077
|
+
lines.push(
|
|
6078
|
+
/* @__PURE__ */ jsx7(TicketLine, { ticket, width, selected: ticket.id === cursor }, ticket.id)
|
|
6079
|
+
);
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
return /* @__PURE__ */ jsx7(BoundedPanel, { width, rows, children: [
|
|
6083
|
+
/* @__PURE__ */ jsx7(Heading, { text: "Board", note: `${board.tickets.length}` }, "h"),
|
|
6084
|
+
...board.tickets.length === 0 ? [
|
|
6085
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: "No tickets yet." }, "empty")
|
|
6086
|
+
] : lines,
|
|
6087
|
+
/* @__PURE__ */ jsx7(More, { count: dropped }, "more")
|
|
6088
|
+
] });
|
|
6089
|
+
}
|
|
6090
|
+
function TicketLine({
|
|
6091
|
+
ticket,
|
|
6092
|
+
width,
|
|
6093
|
+
selected
|
|
6094
|
+
}) {
|
|
6095
|
+
return /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
6096
|
+
/* @__PURE__ */ jsx7(Text6, { color: selected ? UI.accent : UI.dim, children: selected ? " > " : " " }),
|
|
6097
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, bold: true, children: pad(ticket.key, 8) }),
|
|
6098
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, children: pad(truncate(ticket.title, 43), 44) }),
|
|
6099
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: pad(ticket.agent_name ?? "", 14) }),
|
|
6100
|
+
ticket.stuck ? /* @__PURE__ */ jsx7(Text6, { color: UI.warn, children: truncate(ticket.stuck, Math.max(8, width - 70)) }) : null
|
|
6101
|
+
] });
|
|
6102
|
+
}
|
|
6103
|
+
function FeedPanel({
|
|
6104
|
+
entries,
|
|
6105
|
+
width = 80,
|
|
6106
|
+
rows = 12
|
|
6107
|
+
}) {
|
|
6108
|
+
const shown = entries.slice(0, contentRows(rows, entries.length));
|
|
6109
|
+
return /* @__PURE__ */ jsx7(BoundedPanel, { width, rows, children: [
|
|
6110
|
+
/* @__PURE__ */ jsx7(Heading, { text: "Feed", note: `${entries.length}` }, "h"),
|
|
6111
|
+
...entries.length === 0 ? [
|
|
6112
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: "Nothing yet." }, "empty")
|
|
6113
|
+
] : [],
|
|
6114
|
+
...shown.map((row, index) => /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
6115
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: pad(relativeTime(row.at), 10) }),
|
|
6116
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: pad(row.kind ?? "", 18) }),
|
|
6117
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, children: truncate(row.summary ?? "", Math.max(8, width - 30)) })
|
|
6118
|
+
] }, row.id ?? index)),
|
|
6119
|
+
/* @__PURE__ */ jsx7(More, { count: entries.length - shown.length }, "more")
|
|
6120
|
+
] });
|
|
6121
|
+
}
|
|
6122
|
+
function InboxPanel({
|
|
6123
|
+
board,
|
|
6124
|
+
width = 80,
|
|
6125
|
+
rows = 12
|
|
6126
|
+
}) {
|
|
6127
|
+
const shown = board.decisions.slice(0, contentRows(rows, board.decisions.length));
|
|
6128
|
+
return /* @__PURE__ */ jsx7(BoundedPanel, { width, rows, children: [
|
|
6129
|
+
/* @__PURE__ */ jsx7(Heading, { text: "Decisions", note: `${board.decisions.length}` }, "h"),
|
|
6130
|
+
...board.decisions.length === 0 ? [
|
|
6131
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: "Nothing waiting on you." }, "empty")
|
|
6132
|
+
] : [],
|
|
6133
|
+
...shown.map((decision, index) => {
|
|
6134
|
+
const ticket = board.tickets.find((row) => row.id === decision.ticket_id);
|
|
6135
|
+
return /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
|
|
6136
|
+
/* @__PURE__ */ jsx7(Text6, { color: index === 0 ? UI.warn : UI.dim, children: pad(`${index + 1})`, 3) }),
|
|
6137
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.dim, children: pad(ticket?.key ?? decision.id.slice(0, 8), 9) }),
|
|
6138
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, children: truncate(decision.question_md, Math.max(12, width - 14)) })
|
|
6139
|
+
] }, decision.id);
|
|
6140
|
+
}),
|
|
6141
|
+
/* @__PURE__ */ jsx7(More, { count: board.decisions.length - shown.length }, "more")
|
|
6142
|
+
] });
|
|
6143
|
+
}
|
|
6144
|
+
function TicketPanel({
|
|
6145
|
+
ticket,
|
|
6146
|
+
width = 80,
|
|
6147
|
+
rows = 24
|
|
6148
|
+
}) {
|
|
6149
|
+
const lines = [
|
|
6150
|
+
/* @__PURE__ */ jsxs6(Text6, { color: UI.text, bold: true, wrap: "truncate", children: [
|
|
6151
|
+
ticket.key,
|
|
6152
|
+
" ",
|
|
6153
|
+
ticket.title
|
|
6154
|
+
] }, "title"),
|
|
6155
|
+
/* @__PURE__ */ jsxs6(Box5, { flexWrap: "nowrap", children: [
|
|
6156
|
+
/* @__PURE__ */ jsx7(Text6, { color: inkColor(statusTone(ticket.status)), wrap: "truncate", children: statusLabel(ticket.status) }),
|
|
6157
|
+
/* @__PURE__ */ jsxs6(Text6, { color: UI.dim, wrap: "truncate", children: [
|
|
6158
|
+
ticket.area ? ` ${ticket.area}` : "",
|
|
6159
|
+
ticket.agent_name ? ` ${ticket.agent_name}` : " unassigned",
|
|
6160
|
+
ticket.attempts ? ` attempt ${ticket.attempts}` : ""
|
|
6161
|
+
] })
|
|
6162
|
+
] }, "status")
|
|
6163
|
+
];
|
|
6164
|
+
if (ticket.stuck) {
|
|
6165
|
+
lines.push(
|
|
6166
|
+
/* @__PURE__ */ jsxs6(Text6, { color: UI.warn, wrap: "truncate", children: [
|
|
6167
|
+
"why: ",
|
|
6168
|
+
ticket.stuck
|
|
6169
|
+
] }, "stuck")
|
|
6170
|
+
);
|
|
6171
|
+
}
|
|
6172
|
+
if (ticket.blocker_keys.length) {
|
|
6173
|
+
lines.push(
|
|
6174
|
+
/* @__PURE__ */ jsxs6(Text6, { color: UI.dim, wrap: "truncate", children: [
|
|
6175
|
+
"blocked by ",
|
|
6176
|
+
ticket.blocker_keys.join(", ")
|
|
6177
|
+
] }, "blocked")
|
|
6178
|
+
);
|
|
6179
|
+
}
|
|
6180
|
+
if (ticket.pr_url) {
|
|
6181
|
+
lines.push(
|
|
6182
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.accent, wrap: "truncate", children: ticket.pr_url }, "pr")
|
|
6183
|
+
);
|
|
6184
|
+
}
|
|
6185
|
+
const drawable = Math.max(0, rows);
|
|
6186
|
+
const body = clipToRows(
|
|
6187
|
+
ticket.body_md.trim(),
|
|
6188
|
+
Math.max(20, width),
|
|
6189
|
+
Math.max(0, drawable - lines.length - 1)
|
|
6190
|
+
);
|
|
6191
|
+
if (body) {
|
|
6192
|
+
lines.push(/* @__PURE__ */ jsx7(Box5, { height: 1 }, "gap"));
|
|
6193
|
+
lines.push(
|
|
6194
|
+
/* @__PURE__ */ jsx7(Text6, { color: UI.text, wrap: "wrap", children: body }, "body")
|
|
6195
|
+
);
|
|
6196
|
+
}
|
|
6197
|
+
return /* @__PURE__ */ jsx7(BoundedPanel, { width, rows: drawable, children: lines });
|
|
6198
|
+
}
|
|
6199
|
+
function clipToRows(text, width, rows) {
|
|
6200
|
+
if (rows <= 0 || !text || width <= 0) return "";
|
|
6201
|
+
const out2 = [];
|
|
6202
|
+
let dropped = false;
|
|
6203
|
+
for (const paragraph of text.split("\n")) {
|
|
6204
|
+
if (out2.length >= rows) {
|
|
6205
|
+
dropped = true;
|
|
6206
|
+
break;
|
|
6207
|
+
}
|
|
6208
|
+
let line = "";
|
|
6209
|
+
for (const word of paragraph.split(/\s+/).filter(Boolean)) {
|
|
6210
|
+
const candidate = line ? `${line} ${word}` : word;
|
|
6211
|
+
if (candidate.length <= width) {
|
|
6212
|
+
line = candidate;
|
|
6213
|
+
continue;
|
|
6214
|
+
}
|
|
6215
|
+
if (line) {
|
|
6216
|
+
out2.push(line);
|
|
6217
|
+
line = "";
|
|
6218
|
+
if (out2.length >= rows) break;
|
|
6219
|
+
}
|
|
6220
|
+
let rest = word;
|
|
6221
|
+
while (rest.length > width) {
|
|
6222
|
+
if (out2.length >= rows) break;
|
|
6223
|
+
out2.push(rest.slice(0, width));
|
|
6224
|
+
rest = rest.slice(width);
|
|
6225
|
+
}
|
|
6226
|
+
if (out2.length >= rows) break;
|
|
6227
|
+
line = rest;
|
|
6228
|
+
}
|
|
6229
|
+
if (out2.length >= rows) {
|
|
6230
|
+
dropped = true;
|
|
6231
|
+
break;
|
|
6232
|
+
}
|
|
6233
|
+
out2.push(line);
|
|
6234
|
+
}
|
|
6235
|
+
if (out2.length > rows) {
|
|
6236
|
+
out2.length = rows;
|
|
6237
|
+
dropped = true;
|
|
6238
|
+
}
|
|
6239
|
+
if (dropped && out2.length > 0) {
|
|
6240
|
+
const last = out2[out2.length - 1];
|
|
6241
|
+
out2[out2.length - 1] = last.length >= width ? `${last.slice(0, width - 1)}\u2026` : `${last}\u2026`;
|
|
6242
|
+
}
|
|
6243
|
+
return out2.join("\n");
|
|
4922
6244
|
}
|
|
4923
|
-
var
|
|
4924
|
-
|
|
6245
|
+
var DOT3;
|
|
6246
|
+
var init_Panels = __esm({
|
|
6247
|
+
"src/tui/Panels.tsx"() {
|
|
4925
6248
|
"use strict";
|
|
6249
|
+
init_src();
|
|
6250
|
+
init_format();
|
|
6251
|
+
init_theme();
|
|
4926
6252
|
init_theme2();
|
|
6253
|
+
init_bounded();
|
|
6254
|
+
DOT3 = "\u25CF";
|
|
4927
6255
|
}
|
|
4928
6256
|
});
|
|
4929
6257
|
|
|
4930
|
-
// src/tui/
|
|
6258
|
+
// src/tui/Decision.tsx
|
|
4931
6259
|
import "react";
|
|
4932
|
-
import { Box as
|
|
4933
|
-
import { jsx as
|
|
4934
|
-
function
|
|
4935
|
-
|
|
4936
|
-
|
|
6260
|
+
import { Box as Box6, Text as Text7 } from "ink";
|
|
6261
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
6262
|
+
function decisionRows(decisions, cap = 9) {
|
|
6263
|
+
if (decisions.length === 0) return 0;
|
|
6264
|
+
return Math.min(cap, DECISION_CHROME + 2 + decisionOptions(decisions[0]).length);
|
|
6265
|
+
}
|
|
6266
|
+
function DecisionPanel({
|
|
6267
|
+
decisions,
|
|
6268
|
+
board,
|
|
6269
|
+
width,
|
|
6270
|
+
rows
|
|
6271
|
+
}) {
|
|
6272
|
+
if (decisions.length === 0) return null;
|
|
6273
|
+
const decision = decisions[0];
|
|
6274
|
+
const ticket = board?.tickets.find((row) => row.id === decision.ticket_id);
|
|
6275
|
+
const options = decisionOptions(decision);
|
|
6276
|
+
const inner = Math.max(8, width - 4);
|
|
6277
|
+
const forBody = rows - DECISION_CHROME;
|
|
6278
|
+
if (forBody < 1) {
|
|
6279
|
+
return /* @__PURE__ */ jsxs7(Box6, { width, flexWrap: "nowrap", children: [
|
|
6280
|
+
/* @__PURE__ */ jsx8(Text7, { color: UI.warn, bold: true, wrap: "truncate", children: decisions.length > 1 ? `${decisions.length} decisions waiting` : "Decision waiting" }),
|
|
6281
|
+
/* @__PURE__ */ jsx8(Text7, { color: UI.dim, wrap: "truncate", children: ` ${truncate(decision.question_md, Math.max(8, width - 26))} /decide` })
|
|
6282
|
+
] });
|
|
6283
|
+
}
|
|
6284
|
+
const optionRows = Math.min(options.length, Math.max(0, forBody - 1));
|
|
6285
|
+
const question = clipToRows(decision.question_md.trim(), inner, forBody - optionRows);
|
|
6286
|
+
return /* @__PURE__ */ jsxs7(
|
|
6287
|
+
Box6,
|
|
4937
6288
|
{
|
|
4938
6289
|
borderStyle: "single",
|
|
4939
|
-
borderColor: UI.
|
|
6290
|
+
borderColor: UI.warn,
|
|
4940
6291
|
flexDirection: "column",
|
|
4941
|
-
paddingX:
|
|
4942
|
-
paddingY: 1,
|
|
6292
|
+
paddingX: 1,
|
|
4943
6293
|
width,
|
|
6294
|
+
marginBottom: 1,
|
|
4944
6295
|
children: [
|
|
4945
|
-
/* @__PURE__ */
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
6296
|
+
/* @__PURE__ */ jsxs7(Box6, { flexWrap: "nowrap", children: [
|
|
6297
|
+
/* @__PURE__ */ jsx8(Text7, { color: UI.warn, bold: true, wrap: "truncate", children: decisions.length > 1 ? `Decision 1 of ${decisions.length}` : "Decision" }),
|
|
6298
|
+
/* @__PURE__ */ jsxs7(Text7, { color: UI.dim, wrap: "truncate", children: [
|
|
6299
|
+
ticket ? ` ${ticket.key}` : "",
|
|
6300
|
+
` asked by ${decision.asked_by_role}`,
|
|
6301
|
+
` ${relativeTime(decision.created_at)}`
|
|
6302
|
+
] })
|
|
6303
|
+
] }),
|
|
6304
|
+
/* @__PURE__ */ jsx8(Text7, { color: UI.text, wrap: "wrap", children: question }),
|
|
6305
|
+
options.slice(0, optionRows).map((option, index) => /* @__PURE__ */ jsxs7(Box6, { flexWrap: "nowrap", children: [
|
|
6306
|
+
/* @__PURE__ */ jsx8(Box6, { width: 3, flexShrink: 0, children: /* @__PURE__ */ jsxs7(Text7, { color: UI.accent, children: [
|
|
6307
|
+
index + 1,
|
|
6308
|
+
")"
|
|
4951
6309
|
] }) }),
|
|
4952
|
-
/* @__PURE__ */
|
|
4953
|
-
] },
|
|
4954
|
-
/* @__PURE__ */
|
|
4955
|
-
/* @__PURE__ */ jsx3(Text3, { color: UI.dim, children: "Anything not starting with / goes to whoever you are talking to. Ctrl-C leaves." })
|
|
6310
|
+
/* @__PURE__ */ jsx8(Text7, { color: UI.text, wrap: "truncate", children: truncate(option, Math.max(8, inner - 4)) })
|
|
6311
|
+
] }, index)),
|
|
6312
|
+
/* @__PURE__ */ jsx8(Text7, { color: UI.dim, wrap: "truncate", children: options.length ? `/decide 1 to ${options.length}, or /decide <your answer>, or /decide --skip` : "/decide <your answer>, or /decide --skip" })
|
|
4956
6313
|
]
|
|
4957
6314
|
}
|
|
4958
6315
|
);
|
|
4959
6316
|
}
|
|
4960
|
-
var
|
|
4961
|
-
var
|
|
4962
|
-
"src/tui/
|
|
6317
|
+
var DECISION_CHROME;
|
|
6318
|
+
var init_Decision = __esm({
|
|
6319
|
+
"src/tui/Decision.tsx"() {
|
|
4963
6320
|
"use strict";
|
|
6321
|
+
init_format();
|
|
6322
|
+
init_Panels();
|
|
6323
|
+
init_commands3();
|
|
4964
6324
|
init_theme2();
|
|
4965
|
-
|
|
4966
|
-
{ name: "/architect", help: "talk to your own model, which can do anything in the platform" },
|
|
4967
|
-
{ name: "/orchestrator", help: "talk to the agent that gets work in flight finished" },
|
|
4968
|
-
{ name: "/browse", help: "stop talking, look around" },
|
|
4969
|
-
{ name: "/workspace", args: "[slug]", help: "switch workspace, or list the ones you can reach" },
|
|
4970
|
-
{ name: "/board", help: "the kanban board" },
|
|
4971
|
-
{ name: "/agents", help: "every agent and what it is doing" },
|
|
4972
|
-
{ name: "/feed", help: "what just happened" },
|
|
4973
|
-
{ name: "/inbox", help: "decisions and messages waiting on you" },
|
|
4974
|
-
{ name: "/ticket", args: "HD-12", help: "open one ticket" },
|
|
4975
|
-
{ name: "/refresh", help: "reload the board now" },
|
|
4976
|
-
{ name: "/help", help: "this list" },
|
|
4977
|
-
{ name: "/exit", help: "leave" }
|
|
4978
|
-
];
|
|
6325
|
+
DECISION_CHROME = 5;
|
|
4979
6326
|
}
|
|
4980
6327
|
});
|
|
4981
6328
|
|
|
4982
|
-
// src/tui/
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
return
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
6329
|
+
// src/tui/alert.ts
|
|
6330
|
+
function alertOnce(stream = process.stdout, env = process.env) {
|
|
6331
|
+
if (env.HIGHERDEV_NO_BELL) return false;
|
|
6332
|
+
if (!stream.isTTY) return false;
|
|
6333
|
+
stream.write(BELL);
|
|
6334
|
+
return true;
|
|
6335
|
+
}
|
|
6336
|
+
var BELL;
|
|
6337
|
+
var init_alert = __esm({
|
|
6338
|
+
"src/tui/alert.ts"() {
|
|
6339
|
+
"use strict";
|
|
6340
|
+
BELL = String.fromCharCode(7);
|
|
6341
|
+
}
|
|
6342
|
+
});
|
|
6343
|
+
|
|
6344
|
+
// src/tui/stream.ts
|
|
6345
|
+
function toStreamLines(events, runs) {
|
|
6346
|
+
const lines = [];
|
|
6347
|
+
for (const event of events) {
|
|
6348
|
+
const run5 = runs.get(event.run_id);
|
|
6349
|
+
if (!run5) continue;
|
|
6350
|
+
const produced = transcriptLines([{ id: event.id, type: event.type, payload: event.payload }]);
|
|
6351
|
+
produced.forEach((line, index) => {
|
|
6352
|
+
lines.push({
|
|
6353
|
+
id: `${event.id}:${index}`,
|
|
6354
|
+
runId: event.run_id,
|
|
6355
|
+
agent: run5.agent,
|
|
6356
|
+
at: String(event.at),
|
|
6357
|
+
seq: event.seq,
|
|
6358
|
+
kind: line.kind,
|
|
6359
|
+
title: line.title
|
|
6360
|
+
});
|
|
6361
|
+
});
|
|
6362
|
+
}
|
|
6363
|
+
return lines;
|
|
5014
6364
|
}
|
|
5015
|
-
function
|
|
5016
|
-
|
|
5017
|
-
|
|
6365
|
+
function appendLines(prior, incoming, limit = STREAM_LIMIT) {
|
|
6366
|
+
if (incoming.length === 0) return prior;
|
|
6367
|
+
const seen = new Set(prior.map((line) => line.id));
|
|
6368
|
+
const fresh = incoming.filter((line) => !seen.has(line.id));
|
|
6369
|
+
if (fresh.length === 0) return prior;
|
|
6370
|
+
const next = [...prior, ...fresh].sort(
|
|
6371
|
+
(left, right) => left.at.localeCompare(right.at) || left.seq - right.seq
|
|
5018
6372
|
);
|
|
5019
|
-
|
|
5020
|
-
return /* @__PURE__ */ jsx4(Panel, { title: "Board", children: /* @__PURE__ */ jsx4(Text4, { color: UI.dim, children: "No tickets yet." }) });
|
|
5021
|
-
}
|
|
5022
|
-
return /* @__PURE__ */ jsx4(Panel, { title: "Board", children: active.map((status) => {
|
|
5023
|
-
const rows = board.tickets.filter((ticket) => ticket.status === status);
|
|
5024
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
5025
|
-
/* @__PURE__ */ jsxs3(Text4, { color: inkColor(statusTone(status)), children: [
|
|
5026
|
-
statusLabel(status),
|
|
5027
|
-
" ",
|
|
5028
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.dim, children: [
|
|
5029
|
-
"(",
|
|
5030
|
-
rows.length,
|
|
5031
|
-
")"
|
|
5032
|
-
] })
|
|
5033
|
-
] }),
|
|
5034
|
-
rows.map((ticket) => /* @__PURE__ */ jsx4(TicketLine, { ticket, selected: ticket.id === cursor }, ticket.id))
|
|
5035
|
-
] }, status);
|
|
5036
|
-
}) });
|
|
5037
|
-
}
|
|
5038
|
-
function TicketLine({ ticket, selected }) {
|
|
5039
|
-
return /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
5040
|
-
/* @__PURE__ */ jsx4(Text4, { color: selected ? UI.accent : UI.dim, children: selected ? " > " : " " }),
|
|
5041
|
-
/* @__PURE__ */ jsx4(Box4, { width: 8, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, bold: true, children: ticket.key }) }),
|
|
5042
|
-
/* @__PURE__ */ jsx4(Box4, { width: 44, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, children: truncate(ticket.title, 43) }) }),
|
|
5043
|
-
/* @__PURE__ */ jsx4(Box4, { width: 14, children: /* @__PURE__ */ jsx4(Text4, { color: UI.dim, children: ticket.agent_name ?? "" }) }),
|
|
5044
|
-
ticket.stuck ? /* @__PURE__ */ jsx4(Text4, { color: UI.warn, children: truncate(ticket.stuck, 30) }) : null
|
|
5045
|
-
] });
|
|
6373
|
+
return next.length > limit ? next.slice(next.length - limit) : next;
|
|
5046
6374
|
}
|
|
5047
|
-
function
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
6375
|
+
async function backfill(opts) {
|
|
6376
|
+
if (opts.runIds.length === 0) return;
|
|
6377
|
+
const runs = new Map(opts.runs);
|
|
6378
|
+
let events;
|
|
6379
|
+
try {
|
|
6380
|
+
events = await opts.load(opts.runIds);
|
|
6381
|
+
} catch {
|
|
6382
|
+
return;
|
|
6383
|
+
}
|
|
6384
|
+
if (!opts.isCurrent()) return;
|
|
6385
|
+
const lines = toStreamLines(events, runs);
|
|
6386
|
+
if (lines.length === 0) return;
|
|
6387
|
+
opts.apply((prior) => appendLines(prior, lines));
|
|
5058
6388
|
}
|
|
5059
|
-
function
|
|
5060
|
-
|
|
5061
|
-
|
|
6389
|
+
function runLabels(board) {
|
|
6390
|
+
const map = /* @__PURE__ */ new Map();
|
|
6391
|
+
for (const run5 of board.runs) {
|
|
6392
|
+
if (run5.status !== "running") continue;
|
|
6393
|
+
const agent = board.agents.find((row) => row.id === run5.agent_id);
|
|
6394
|
+
const ticket = board.tickets.find((row) => row.id === run5.ticket_id);
|
|
6395
|
+
map.set(run5.id, {
|
|
6396
|
+
runId: run5.id,
|
|
6397
|
+
agent: agent?.display_name ?? run5.kind,
|
|
6398
|
+
ticket: ticket?.key ?? null
|
|
6399
|
+
});
|
|
5062
6400
|
}
|
|
5063
|
-
return
|
|
5064
|
-
const ticket = board.tickets.find((row) => row.id === decision.ticket_id);
|
|
5065
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
5066
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.warn, children: [
|
|
5067
|
-
decision.id.slice(0, 8),
|
|
5068
|
-
ticket ? ` ${ticket.key}` : ""
|
|
5069
|
-
] }),
|
|
5070
|
-
/* @__PURE__ */ jsx4(Text4, { color: UI.text, children: truncate(decision.question_md, 100) })
|
|
5071
|
-
] }, decision.id);
|
|
5072
|
-
}) });
|
|
5073
|
-
}
|
|
5074
|
-
function EpicsPanel({ board }) {
|
|
5075
|
-
const rows = epicProgress(board).filter((row) => row.epic.status !== "done");
|
|
5076
|
-
if (rows.length === 0) return null;
|
|
5077
|
-
return /* @__PURE__ */ jsx4(Panel, { title: "Epics", children: rows.map((row) => /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
5078
|
-
/* @__PURE__ */ jsx4(Box4, { width: 40, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, children: truncate(row.epic.title, 39) }) }),
|
|
5079
|
-
/* @__PURE__ */ jsx4(Text4, { color: UI.dim, children: epicProgressCaption(row.merged, row.total, row.cancelled) })
|
|
5080
|
-
] }, row.epic.id)) });
|
|
5081
|
-
}
|
|
5082
|
-
function TicketPanel({ ticket }) {
|
|
5083
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
5084
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.text, bold: true, children: [
|
|
5085
|
-
ticket.key,
|
|
5086
|
-
" ",
|
|
5087
|
-
ticket.title
|
|
5088
|
-
] }),
|
|
5089
|
-
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
5090
|
-
/* @__PURE__ */ jsx4(Text4, { color: inkColor(statusTone(ticket.status)), children: statusLabel(ticket.status) }),
|
|
5091
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.dim, children: [
|
|
5092
|
-
ticket.area ? ` ${ticket.area}` : "",
|
|
5093
|
-
ticket.agent_name ? ` ${ticket.agent_name}` : " unassigned",
|
|
5094
|
-
ticket.attempts ? ` attempt ${ticket.attempts}` : ""
|
|
5095
|
-
] })
|
|
5096
|
-
] }),
|
|
5097
|
-
ticket.stuck ? /* @__PURE__ */ jsxs3(Text4, { color: UI.warn, children: [
|
|
5098
|
-
"why: ",
|
|
5099
|
-
ticket.stuck
|
|
5100
|
-
] }) : null,
|
|
5101
|
-
ticket.blocker_keys.length ? /* @__PURE__ */ jsxs3(Text4, { color: UI.dim, children: [
|
|
5102
|
-
"blocked by ",
|
|
5103
|
-
ticket.blocker_keys.join(", ")
|
|
5104
|
-
] }) : null,
|
|
5105
|
-
ticket.pr_url ? /* @__PURE__ */ jsx4(Text4, { color: UI.accent, children: ticket.pr_url }) : null,
|
|
5106
|
-
ticket.body_md.trim() ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, wrap: "wrap", children: ticket.body_md.trim().slice(0, 1200) }) }) : null
|
|
5107
|
-
] });
|
|
6401
|
+
return map;
|
|
5108
6402
|
}
|
|
5109
|
-
var
|
|
5110
|
-
var
|
|
5111
|
-
"src/tui/
|
|
6403
|
+
var STREAM_LIMIT;
|
|
6404
|
+
var init_stream = __esm({
|
|
6405
|
+
"src/tui/stream.ts"() {
|
|
5112
6406
|
"use strict";
|
|
5113
6407
|
init_src();
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
6408
|
+
STREAM_LIMIT = 200;
|
|
6409
|
+
}
|
|
6410
|
+
});
|
|
6411
|
+
|
|
6412
|
+
// src/tui/Bubble.tsx
|
|
6413
|
+
import "react";
|
|
6414
|
+
import { Box as Box7, Text as Text8 } from "ink";
|
|
6415
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
6416
|
+
function Bubble({ message, width }) {
|
|
6417
|
+
const style = speakerStyle(message.speaker);
|
|
6418
|
+
const body = message.body.replace(/\s+$/, "");
|
|
6419
|
+
return /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginBottom: 1, width, children: /* @__PURE__ */ jsxs8(
|
|
6420
|
+
Box7,
|
|
6421
|
+
{
|
|
6422
|
+
borderStyle: style.borderStyle,
|
|
6423
|
+
borderColor: style.borderColor,
|
|
6424
|
+
...style.backgroundColor ? { backgroundColor: style.backgroundColor } : {},
|
|
6425
|
+
flexDirection: "column",
|
|
6426
|
+
paddingX: 1,
|
|
6427
|
+
children: [
|
|
6428
|
+
style.label ? /* @__PURE__ */ jsx9(Text8, { color: UI.text, bold: true, children: style.label }) : null,
|
|
6429
|
+
(message.steps ?? []).map((step, index) => /* @__PURE__ */ jsx9(Text8, { color: UI.dim, children: step }, index)),
|
|
6430
|
+
body ? /* @__PURE__ */ jsx9(Text8, { color: UI.text, wrap: "wrap", children: body }) : message.pending ? /* @__PURE__ */ jsx9(Text8, { color: UI.dim, children: "thinking\u2026" }) : null
|
|
6431
|
+
]
|
|
6432
|
+
}
|
|
6433
|
+
) });
|
|
6434
|
+
}
|
|
6435
|
+
var init_Bubble = __esm({
|
|
6436
|
+
"src/tui/Bubble.tsx"() {
|
|
6437
|
+
"use strict";
|
|
5117
6438
|
init_theme2();
|
|
5118
|
-
DOT2 = "\u25CF";
|
|
5119
6439
|
}
|
|
5120
6440
|
});
|
|
5121
6441
|
|
|
5122
6442
|
// src/tui/TextInput.tsx
|
|
5123
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
5124
|
-
import { Text as
|
|
5125
|
-
import { Fragment, jsx as
|
|
6443
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
6444
|
+
import { Text as Text9, useInput, usePaste } from "ink";
|
|
6445
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
6446
|
+
function printableOf(text) {
|
|
6447
|
+
return text.replace(/[\x00-\x1f\x7f]/g, "");
|
|
6448
|
+
}
|
|
5126
6449
|
function TextInput({
|
|
5127
6450
|
value,
|
|
5128
6451
|
onChange,
|
|
@@ -5136,12 +6459,19 @@ function TextInput({
|
|
|
5136
6459
|
color
|
|
5137
6460
|
}) {
|
|
5138
6461
|
const [cursor, setCursor] = useState2(value.length);
|
|
6462
|
+
const ours = useRef2(value);
|
|
5139
6463
|
useEffect2(() => {
|
|
5140
|
-
|
|
6464
|
+
if (value === ours.current) return;
|
|
6465
|
+
ours.current = value;
|
|
6466
|
+
setCursor(value.length);
|
|
5141
6467
|
}, [value]);
|
|
6468
|
+
const change = (next) => {
|
|
6469
|
+
ours.current = next;
|
|
6470
|
+
onChange(next);
|
|
6471
|
+
};
|
|
5142
6472
|
const insert = (text) => {
|
|
5143
6473
|
const next = value.slice(0, cursor) + text + value.slice(cursor);
|
|
5144
|
-
|
|
6474
|
+
change(next);
|
|
5145
6475
|
setCursor(cursor + text.length);
|
|
5146
6476
|
};
|
|
5147
6477
|
usePaste((text) => {
|
|
@@ -5153,18 +6483,31 @@ function TextInput({
|
|
|
5153
6483
|
if (key2.escape) return onCancel?.();
|
|
5154
6484
|
if (key2.upArrow) return onUp?.();
|
|
5155
6485
|
if (key2.downArrow) return onDown?.();
|
|
6486
|
+
if (input.length > 1 && /[\r\n]/.test(input)) {
|
|
6487
|
+
const parts = input.split(/\r\n|\r|\n/);
|
|
6488
|
+
const submits = parts.length === 2 && parts[1] === "";
|
|
6489
|
+
if (submits) {
|
|
6490
|
+
const next = value.slice(0, cursor) + printableOf(parts[0]) + value.slice(cursor);
|
|
6491
|
+
change(next);
|
|
6492
|
+
setCursor(next.length);
|
|
6493
|
+
return onSubmit?.(next);
|
|
6494
|
+
}
|
|
6495
|
+
const flat = printableOf(input.replace(/[\r\n]+/g, " "));
|
|
6496
|
+
change(value.slice(0, cursor) + flat + value.slice(cursor));
|
|
6497
|
+
return setCursor(cursor + flat.length);
|
|
6498
|
+
}
|
|
5156
6499
|
if (key2.leftArrow) return setCursor((c2) => Math.max(0, c2 - 1));
|
|
5157
6500
|
if (key2.rightArrow) return setCursor((c2) => Math.min(value.length, c2 + 1));
|
|
5158
6501
|
if (key2.home) return setCursor(0);
|
|
5159
6502
|
if (key2.end) return setCursor(value.length);
|
|
5160
6503
|
if (key2.backspace) {
|
|
5161
6504
|
if (cursor === 0) return;
|
|
5162
|
-
|
|
6505
|
+
change(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
5163
6506
|
return setCursor(cursor - 1);
|
|
5164
6507
|
}
|
|
5165
6508
|
if (key2.delete) {
|
|
5166
6509
|
if (cursor >= value.length) return;
|
|
5167
|
-
return
|
|
6510
|
+
return change(value.slice(0, cursor) + value.slice(cursor + 1));
|
|
5168
6511
|
}
|
|
5169
6512
|
if (key2.ctrl) {
|
|
5170
6513
|
switch (input) {
|
|
@@ -5173,14 +6516,14 @@ function TextInput({
|
|
|
5173
6516
|
case "e":
|
|
5174
6517
|
return setCursor(value.length);
|
|
5175
6518
|
case "u":
|
|
5176
|
-
|
|
6519
|
+
change(value.slice(cursor));
|
|
5177
6520
|
return setCursor(0);
|
|
5178
6521
|
case "k":
|
|
5179
|
-
return
|
|
6522
|
+
return change(value.slice(0, cursor));
|
|
5180
6523
|
case "w": {
|
|
5181
6524
|
const head = value.slice(0, cursor);
|
|
5182
6525
|
const trimmed = head.replace(/\s*\S*$/, "");
|
|
5183
|
-
|
|
6526
|
+
change(trimmed + value.slice(cursor));
|
|
5184
6527
|
return setCursor(trimmed.length);
|
|
5185
6528
|
}
|
|
5186
6529
|
default:
|
|
@@ -5188,7 +6531,7 @@ function TextInput({
|
|
|
5188
6531
|
}
|
|
5189
6532
|
}
|
|
5190
6533
|
if (key2.meta || !input) return;
|
|
5191
|
-
const printable = input
|
|
6534
|
+
const printable = printableOf(input);
|
|
5192
6535
|
if (printable) insert(printable);
|
|
5193
6536
|
},
|
|
5194
6537
|
{ isActive }
|
|
@@ -5197,15 +6540,15 @@ function TextInput({
|
|
|
5197
6540
|
const before = value.slice(0, cursor);
|
|
5198
6541
|
const at = value[cursor] ?? " ";
|
|
5199
6542
|
const after = value.slice(cursor + 1);
|
|
5200
|
-
return /* @__PURE__ */
|
|
6543
|
+
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
5201
6544
|
prompt2,
|
|
5202
|
-
showPlaceholder ? /* @__PURE__ */
|
|
5203
|
-
isActive ? /* @__PURE__ */
|
|
5204
|
-
/* @__PURE__ */
|
|
5205
|
-
] }) : /* @__PURE__ */
|
|
5206
|
-
/* @__PURE__ */
|
|
5207
|
-
isActive ? /* @__PURE__ */
|
|
5208
|
-
/* @__PURE__ */
|
|
6545
|
+
showPlaceholder ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
6546
|
+
isActive ? /* @__PURE__ */ jsx10(Text9, { inverse: true, children: placeholder[0] }) : /* @__PURE__ */ jsx10(Text9, { children: placeholder[0] }),
|
|
6547
|
+
/* @__PURE__ */ jsx10(Text9, { color: UI.dim, children: placeholder.slice(1) })
|
|
6548
|
+
] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
6549
|
+
/* @__PURE__ */ jsx10(Text9, { color, children: before }),
|
|
6550
|
+
isActive ? /* @__PURE__ */ jsx10(Text9, { inverse: true, color, children: at }) : /* @__PURE__ */ jsx10(Text9, { color, children: at === " " ? "" : at }),
|
|
6551
|
+
/* @__PURE__ */ jsx10(Text9, { color, children: after })
|
|
5209
6552
|
] })
|
|
5210
6553
|
] });
|
|
5211
6554
|
}
|
|
@@ -5234,13 +6577,42 @@ function parseLine(raw) {
|
|
|
5234
6577
|
case "agents":
|
|
5235
6578
|
case "feed":
|
|
5236
6579
|
case "inbox":
|
|
5237
|
-
case "
|
|
6580
|
+
case "settings":
|
|
5238
6581
|
return { kind: "view", view: word.toLowerCase() };
|
|
6582
|
+
case "help":
|
|
6583
|
+
return { kind: "help" };
|
|
6584
|
+
case "agent": {
|
|
6585
|
+
const [verb, name, provider, model] = argument.split(/\s+/).filter(Boolean);
|
|
6586
|
+
if (verb?.toLowerCase() !== "new") {
|
|
6587
|
+
return { kind: "unknown", command: "agent, try /agent new <name> <provider> <model>" };
|
|
6588
|
+
}
|
|
6589
|
+
if (!name || !provider || !model) {
|
|
6590
|
+
return { kind: "unknown", command: "agent new needs a name, a provider and a model" };
|
|
6591
|
+
}
|
|
6592
|
+
return { kind: "agent-new", name, provider, model };
|
|
6593
|
+
}
|
|
5239
6594
|
case "workspace":
|
|
5240
|
-
case "ws":
|
|
6595
|
+
case "ws": {
|
|
6596
|
+
const parts = argument.split(/\s+/).filter(Boolean);
|
|
6597
|
+
if (parts[0]?.toLowerCase() === "new") {
|
|
6598
|
+
const [, name, repo] = parts;
|
|
6599
|
+
if (!name || !repo) {
|
|
6600
|
+
return { kind: "unknown", command: "workspace new needs a name and owner/repo" };
|
|
6601
|
+
}
|
|
6602
|
+
return { kind: "workspace-new", name, repo };
|
|
6603
|
+
}
|
|
5241
6604
|
return { kind: "workspace", slug: argument || null };
|
|
6605
|
+
}
|
|
5242
6606
|
case "ticket":
|
|
5243
6607
|
return argument ? { kind: "ticket", key: argument.toUpperCase() } : { kind: "unknown", command: "ticket needs a key" };
|
|
6608
|
+
case "decide": {
|
|
6609
|
+
const dismiss = /(^|\s)--(skip|dismiss)(\s|$)/.test(argument);
|
|
6610
|
+
return {
|
|
6611
|
+
kind: "decide",
|
|
6612
|
+
answer: argument.replace(/(^|\s)--(skip|dismiss)(\s|$)/g, " ").trim(),
|
|
6613
|
+
dismiss
|
|
6614
|
+
};
|
|
6615
|
+
}
|
|
5244
6616
|
case "refresh":
|
|
5245
6617
|
return { kind: "refresh" };
|
|
5246
6618
|
case "exit":
|
|
@@ -5287,9 +6659,9 @@ __export(App_exports, {
|
|
|
5287
6659
|
App: () => App,
|
|
5288
6660
|
COMMANDS: () => COMMANDS
|
|
5289
6661
|
});
|
|
5290
|
-
import { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
|
|
5291
|
-
import { Box as
|
|
5292
|
-
import { jsx as
|
|
6662
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
|
|
6663
|
+
import { Box as Box8, Static, Text as Text10, useApp, useInput as useInput2, useStdout as useStdout2 } from "ink";
|
|
6664
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
5293
6665
|
function App({
|
|
5294
6666
|
ctx,
|
|
5295
6667
|
workspace,
|
|
@@ -5297,8 +6669,9 @@ function App({
|
|
|
5297
6669
|
brainLabel
|
|
5298
6670
|
}) {
|
|
5299
6671
|
const { exit } = useApp();
|
|
5300
|
-
const { stdout } =
|
|
6672
|
+
const { stdout } = useStdout2();
|
|
5301
6673
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
6674
|
+
const rows = stdout?.rows && stdout.rows > 0 ? stdout.rows : 24;
|
|
5302
6675
|
const width = Math.max(48, Math.min(columns - 1, 120));
|
|
5303
6676
|
const [current, setCurrent] = useState3(workspace);
|
|
5304
6677
|
const [available, setAvailable] = useState3(workspaces);
|
|
@@ -5314,14 +6687,87 @@ function App({
|
|
|
5314
6687
|
const [notice, setNotice] = useState3(null);
|
|
5315
6688
|
const [ticketKey, setTicketKey] = useState3(null);
|
|
5316
6689
|
const [ready, setReady] = useState3(false);
|
|
5317
|
-
const
|
|
5318
|
-
const
|
|
5319
|
-
const
|
|
5320
|
-
const
|
|
5321
|
-
|
|
6690
|
+
const [stream, setStream] = useState3([]);
|
|
6691
|
+
const [cursor, setCursor] = useState3(null);
|
|
6692
|
+
const selectedRef = useRef3(null);
|
|
6693
|
+
const [started, setStarted] = useState3(false);
|
|
6694
|
+
useEffect3(() => {
|
|
6695
|
+
if (messages.length > 0 || view !== "home") setStarted(true);
|
|
6696
|
+
}, [messages.length, view]);
|
|
6697
|
+
const workspaceCtx = useMemo2(() => ({ ...ctx, workspace: current }), [ctx, current]);
|
|
6698
|
+
const session = useRef3(null);
|
|
6699
|
+
const workspaceLoads = useRef3(new WorkspaceLoads(current.id));
|
|
6700
|
+
const refreshRef = useRef3(null);
|
|
6701
|
+
const history = useRef3([]);
|
|
6702
|
+
const historyAt = useRef3(-1);
|
|
5322
6703
|
const say = useCallback((speaker, body, steps) => {
|
|
5323
|
-
setMessages((prior) => [...prior, { id: nextId(), speaker, body, steps }]);
|
|
6704
|
+
setMessages((prior) => [...prior, { id: nextId(), speaker, body, steps, done: true }]);
|
|
5324
6705
|
}, []);
|
|
6706
|
+
const labels = useRef3(/* @__PURE__ */ new Map());
|
|
6707
|
+
labels.current = useMemo2(
|
|
6708
|
+
() => board ? runLabels(board) : /* @__PURE__ */ new Map(),
|
|
6709
|
+
[board]
|
|
6710
|
+
);
|
|
6711
|
+
const liveRunIds = [...labels.current.keys()].sort().join(",");
|
|
6712
|
+
const order = useMemo2(() => board ? boardTicketIds(board) : [], [board]);
|
|
6713
|
+
const settings = useMemo2(
|
|
6714
|
+
() => board ? settingsRows(current, board.agents) : [],
|
|
6715
|
+
[current, board]
|
|
6716
|
+
);
|
|
6717
|
+
const settingsOrder = useMemo2(() => editableKeys(settings), [settings]);
|
|
6718
|
+
const [field, setField] = useState3(null);
|
|
6719
|
+
const [editing, setEditing] = useState3(null);
|
|
6720
|
+
const fieldRef = useRef3(null);
|
|
6721
|
+
const editingRef = useRef3(null);
|
|
6722
|
+
editingRef.current = editing;
|
|
6723
|
+
const browsing = mode === "browse" && draft.length === 0 && cursor !== null;
|
|
6724
|
+
const configuring = view === "settings" && !editing;
|
|
6725
|
+
const moveField = useCallback(
|
|
6726
|
+
(delta) => {
|
|
6727
|
+
const next = nextCursor(settingsOrder, fieldRef.current, delta);
|
|
6728
|
+
if (!next) return false;
|
|
6729
|
+
fieldRef.current = next;
|
|
6730
|
+
setField(next);
|
|
6731
|
+
return true;
|
|
6732
|
+
},
|
|
6733
|
+
[settingsOrder]
|
|
6734
|
+
);
|
|
6735
|
+
const moveCursor = useCallback(
|
|
6736
|
+
(delta) => {
|
|
6737
|
+
if (order.length === 0) return false;
|
|
6738
|
+
const next = nextCursor(order, selectedRef.current, delta);
|
|
6739
|
+
if (!next) return false;
|
|
6740
|
+
selectedRef.current = next;
|
|
6741
|
+
setCursor(next);
|
|
6742
|
+
return true;
|
|
6743
|
+
},
|
|
6744
|
+
[order]
|
|
6745
|
+
);
|
|
6746
|
+
const applyEdit = useCallback(
|
|
6747
|
+
async (key2, raw) => {
|
|
6748
|
+
const row = settings.find((entry) => entry.key === key2);
|
|
6749
|
+
if (!row) return;
|
|
6750
|
+
const edit = editFor(row, raw);
|
|
6751
|
+
if (!edit.ok) {
|
|
6752
|
+
setNotice(edit.error);
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
setBusy(true);
|
|
6756
|
+
try {
|
|
6757
|
+
const result = edit.value.target === "workspace" ? await updateWorkspace(workspaceCtx, edit.value.input) : await updateAgent(workspaceCtx, edit.value.name, edit.value.input);
|
|
6758
|
+
if (!result.ok) {
|
|
6759
|
+
setNotice(result.error);
|
|
6760
|
+
return;
|
|
6761
|
+
}
|
|
6762
|
+
setNotice(null);
|
|
6763
|
+
if (edit.value.target === "workspace") setCurrent(result.value);
|
|
6764
|
+
await refreshRef.current?.();
|
|
6765
|
+
} finally {
|
|
6766
|
+
setBusy(false);
|
|
6767
|
+
}
|
|
6768
|
+
},
|
|
6769
|
+
[settings, workspaceCtx]
|
|
6770
|
+
);
|
|
5325
6771
|
const refresh = useCallback(async () => {
|
|
5326
6772
|
const loadToken = workspaceLoads.current.start(workspaceCtx.workspace.id);
|
|
5327
6773
|
try {
|
|
@@ -5339,12 +6785,23 @@ function App({
|
|
|
5339
6785
|
setNotice(error instanceof Error ? error.message : String(error));
|
|
5340
6786
|
}
|
|
5341
6787
|
}, [workspaceCtx]);
|
|
6788
|
+
refreshRef.current = refresh;
|
|
5342
6789
|
useEffect3(() => {
|
|
5343
6790
|
void refresh();
|
|
5344
6791
|
const repaint = debounce(() => void refresh(), 250);
|
|
5345
6792
|
const sub = subscribe({
|
|
5346
6793
|
db: ctx.db,
|
|
5347
|
-
tables: [
|
|
6794
|
+
tables: [
|
|
6795
|
+
"tickets",
|
|
6796
|
+
"runs",
|
|
6797
|
+
"run_events",
|
|
6798
|
+
"messages",
|
|
6799
|
+
"decisions",
|
|
6800
|
+
"agents",
|
|
6801
|
+
"hosts",
|
|
6802
|
+
"provider_pauses",
|
|
6803
|
+
"runtime"
|
|
6804
|
+
],
|
|
5348
6805
|
filter: {
|
|
5349
6806
|
tickets: `workspace_id=eq.${current.id}`,
|
|
5350
6807
|
runs: `workspace_id=eq.${current.id}`,
|
|
@@ -5352,7 +6809,16 @@ function App({
|
|
|
5352
6809
|
decisions: `workspace_id=eq.${current.id}`,
|
|
5353
6810
|
agents: `workspace_id=eq.${current.id}`
|
|
5354
6811
|
},
|
|
5355
|
-
onChange: () =>
|
|
6812
|
+
onChange: (table2, payload) => {
|
|
6813
|
+
if (table2 === "run_events") {
|
|
6814
|
+
const row = payload.new;
|
|
6815
|
+
if (!row?.run_id) return;
|
|
6816
|
+
const lines = toStreamLines([row], labels.current);
|
|
6817
|
+
if (lines.length > 0) setStream((prior) => appendLines(prior, lines));
|
|
6818
|
+
return;
|
|
6819
|
+
}
|
|
6820
|
+
repaint.trigger();
|
|
6821
|
+
},
|
|
5356
6822
|
onState: setLive
|
|
5357
6823
|
});
|
|
5358
6824
|
return () => {
|
|
@@ -5360,6 +6826,31 @@ function App({
|
|
|
5360
6826
|
void sub.close();
|
|
5361
6827
|
};
|
|
5362
6828
|
}, [ctx, current.id, refresh]);
|
|
6829
|
+
useEffect3(() => {
|
|
6830
|
+
const ids = liveRunIds ? liveRunIds.split(",") : [];
|
|
6831
|
+
if (ids.length === 0) return;
|
|
6832
|
+
const token = workspaceLoads.current.start(workspaceCtx.workspace.id);
|
|
6833
|
+
void backfill({
|
|
6834
|
+
runIds: ids,
|
|
6835
|
+
runs: labels.current,
|
|
6836
|
+
load: (runIds) => loadLiveEvents(workspaceCtx, runIds),
|
|
6837
|
+
isCurrent: () => workspaceLoads.current.isCurrent(token),
|
|
6838
|
+
apply: setStream
|
|
6839
|
+
});
|
|
6840
|
+
}, [liveRunIds]);
|
|
6841
|
+
const decisions = board?.decisions ?? [];
|
|
6842
|
+
const waiting = decisions.length;
|
|
6843
|
+
const announced = useRef3(0);
|
|
6844
|
+
useEffect3(() => {
|
|
6845
|
+
if (waiting > announced.current) alertOnce();
|
|
6846
|
+
announced.current = waiting;
|
|
6847
|
+
}, [waiting]);
|
|
6848
|
+
useEffect3(() => {
|
|
6849
|
+
if (cursor && order.length > 0 && !order.includes(cursor)) {
|
|
6850
|
+
setCursor(null);
|
|
6851
|
+
selectedRef.current = null;
|
|
6852
|
+
}
|
|
6853
|
+
}, [cursor, order]);
|
|
5363
6854
|
useEffect3(() => {
|
|
5364
6855
|
session.current = null;
|
|
5365
6856
|
}, [current.id]);
|
|
@@ -5375,7 +6866,8 @@ function App({
|
|
|
5375
6866
|
setBoard(null);
|
|
5376
6867
|
setFeed([]);
|
|
5377
6868
|
setPausedAll2(false);
|
|
5378
|
-
|
|
6869
|
+
setStream([]);
|
|
6870
|
+
setCursor(null);
|
|
5379
6871
|
setNotice(null);
|
|
5380
6872
|
setCurrent(found);
|
|
5381
6873
|
setView("home");
|
|
@@ -5425,6 +6917,7 @@ function App({
|
|
|
5425
6917
|
(prior) => prior.map((m) => m.id === id ? { ...m, body: message, pending: false } : m)
|
|
5426
6918
|
);
|
|
5427
6919
|
} finally {
|
|
6920
|
+
setMessages((prior) => prior.map((m) => m.id === id ? { ...m, done: true } : m));
|
|
5428
6921
|
setBusy(false);
|
|
5429
6922
|
}
|
|
5430
6923
|
},
|
|
@@ -5453,6 +6946,7 @@ function App({
|
|
|
5453
6946
|
const message = error instanceof Error ? error.message : String(error);
|
|
5454
6947
|
setMessages((prior) => prior.map((m) => m.id === id ? { ...m, body: message, pending: false } : m));
|
|
5455
6948
|
} finally {
|
|
6949
|
+
setMessages((prior) => prior.map((m) => m.id === id ? { ...m, done: true } : m));
|
|
5456
6950
|
setBusy(false);
|
|
5457
6951
|
}
|
|
5458
6952
|
},
|
|
@@ -5461,7 +6955,41 @@ function App({
|
|
|
5461
6955
|
const run5 = useCallback(
|
|
5462
6956
|
async (raw) => {
|
|
5463
6957
|
const text = raw.trim();
|
|
5464
|
-
if (
|
|
6958
|
+
if (view === "settings") {
|
|
6959
|
+
const open = editingRef.current;
|
|
6960
|
+
if (open) {
|
|
6961
|
+
setEditing(null);
|
|
6962
|
+
setDraft("");
|
|
6963
|
+
await applyEdit(open.key, raw);
|
|
6964
|
+
return;
|
|
6965
|
+
}
|
|
6966
|
+
if (!text) {
|
|
6967
|
+
const key2 = fieldRef.current;
|
|
6968
|
+
const row = settings.find((entry) => entry.key === key2);
|
|
6969
|
+
if (!row || !key2) return;
|
|
6970
|
+
const flipped = nextValue(row);
|
|
6971
|
+
if (flipped !== null) {
|
|
6972
|
+
await applyEdit(key2, flipped);
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
const seed = seedFor(row);
|
|
6976
|
+
setEditing({ key: key2, draft: seed });
|
|
6977
|
+
setDraft(seed);
|
|
6978
|
+
setNotice(row.hint ?? null);
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6982
|
+
if (!text) {
|
|
6983
|
+
const at = selectedRef.current;
|
|
6984
|
+
if (browsing && at) {
|
|
6985
|
+
const selected2 = board?.tickets.find((row) => row.id === at);
|
|
6986
|
+
if (selected2) {
|
|
6987
|
+
setTicketKey(selected2.key);
|
|
6988
|
+
setView("ticket");
|
|
6989
|
+
}
|
|
6990
|
+
}
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
5465
6993
|
history.current.push(text);
|
|
5466
6994
|
historyAt.current = -1;
|
|
5467
6995
|
setDraft("");
|
|
@@ -5477,14 +7005,105 @@ function App({
|
|
|
5477
7005
|
switch (action.kind) {
|
|
5478
7006
|
case "mode":
|
|
5479
7007
|
setMode(action.mode);
|
|
5480
|
-
if (action.mode !== "browse")
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
7008
|
+
if (action.mode !== "browse") {
|
|
7009
|
+
setView("home");
|
|
7010
|
+
setCursor(null);
|
|
7011
|
+
say(
|
|
7012
|
+
"system",
|
|
7013
|
+
action.mode === "architect" ? "Talking to the architect. It can do anything in the platform." : "Talking to the orchestrator. It moves work already in flight."
|
|
7014
|
+
);
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
setView("home");
|
|
7018
|
+
setTicketKey(null);
|
|
7019
|
+
setStarted(true);
|
|
7020
|
+
if (order.length === 0) {
|
|
7021
|
+
setNotice("Nothing on the board to browse yet.");
|
|
7022
|
+
return;
|
|
7023
|
+
}
|
|
7024
|
+
setCursor((prior) => {
|
|
7025
|
+
const next = prior && order.includes(prior) ? prior : order[0];
|
|
7026
|
+
selectedRef.current = next;
|
|
7027
|
+
return next;
|
|
7028
|
+
});
|
|
5485
7029
|
return;
|
|
5486
7030
|
case "view":
|
|
5487
7031
|
setView(action.view);
|
|
7032
|
+
if (action.view === "settings") {
|
|
7033
|
+
const first = settingsOrder[0] ?? null;
|
|
7034
|
+
fieldRef.current = first;
|
|
7035
|
+
setField(first);
|
|
7036
|
+
setEditing(null);
|
|
7037
|
+
}
|
|
7038
|
+
return;
|
|
7039
|
+
case "agent-new": {
|
|
7040
|
+
setBusy(true);
|
|
7041
|
+
try {
|
|
7042
|
+
const made = await createBuilder(workspaceCtx, action.name, {
|
|
7043
|
+
provider: action.provider,
|
|
7044
|
+
model: action.model
|
|
7045
|
+
});
|
|
7046
|
+
if (!made.ok) {
|
|
7047
|
+
setNotice(made.error);
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
say("system", `Added ${made.value.display_name}. Open /settings to configure it.`);
|
|
7051
|
+
await refresh();
|
|
7052
|
+
} finally {
|
|
7053
|
+
setBusy(false);
|
|
7054
|
+
}
|
|
7055
|
+
return;
|
|
7056
|
+
}
|
|
7057
|
+
case "workspace-new": {
|
|
7058
|
+
setBusy(true);
|
|
7059
|
+
try {
|
|
7060
|
+
const made = await createWorkspace(ctx, action.name, action.repo);
|
|
7061
|
+
if (!made.ok) {
|
|
7062
|
+
setNotice(made.error);
|
|
7063
|
+
return;
|
|
7064
|
+
}
|
|
7065
|
+
const { workspace: created, agents } = made.value;
|
|
7066
|
+
setAvailable((prior) => [...prior, created]);
|
|
7067
|
+
say("system", `Created ${created.slug} with ${agents} agents. /workspace ${created.slug} to open it.`);
|
|
7068
|
+
} finally {
|
|
7069
|
+
setBusy(false);
|
|
7070
|
+
}
|
|
7071
|
+
return;
|
|
7072
|
+
}
|
|
7073
|
+
case "decide": {
|
|
7074
|
+
const open = board?.decisions ?? [];
|
|
7075
|
+
if (open.length === 0) {
|
|
7076
|
+
setNotice("Nothing is waiting on a decision.");
|
|
7077
|
+
return;
|
|
7078
|
+
}
|
|
7079
|
+
if (!action.answer && !action.dismiss) {
|
|
7080
|
+
setNotice("Answer it with /decide 1, /decide <your answer>, or /decide --skip.");
|
|
7081
|
+
return;
|
|
7082
|
+
}
|
|
7083
|
+
setBusy(true);
|
|
7084
|
+
try {
|
|
7085
|
+
const result = await resolveDecision(workspaceCtx, open[0].id, action.answer, {
|
|
7086
|
+
dismiss: action.dismiss
|
|
7087
|
+
});
|
|
7088
|
+
if (!result.ok) {
|
|
7089
|
+
setNotice(result.error);
|
|
7090
|
+
return;
|
|
7091
|
+
}
|
|
7092
|
+
say(
|
|
7093
|
+
"system",
|
|
7094
|
+
result.dismissed ? `Skipped the decision on ${open[0].id.slice(0, 8)}.` : `Answered: ${result.answer}`
|
|
7095
|
+
);
|
|
7096
|
+
await refresh();
|
|
7097
|
+
} finally {
|
|
7098
|
+
setBusy(false);
|
|
7099
|
+
}
|
|
7100
|
+
return;
|
|
7101
|
+
}
|
|
7102
|
+
case "help":
|
|
7103
|
+
setMessages((prior) => [
|
|
7104
|
+
...prior,
|
|
7105
|
+
{ id: nextId(), speaker: "system", body: "", panel: "help", done: true }
|
|
7106
|
+
]);
|
|
5488
7107
|
return;
|
|
5489
7108
|
case "workspace":
|
|
5490
7109
|
if (!action.slug) {
|
|
@@ -5509,85 +7128,183 @@ function App({
|
|
|
5509
7128
|
return;
|
|
5510
7129
|
}
|
|
5511
7130
|
},
|
|
5512
|
-
[
|
|
7131
|
+
[
|
|
7132
|
+
mode,
|
|
7133
|
+
available,
|
|
7134
|
+
order,
|
|
7135
|
+
browsing,
|
|
7136
|
+
board,
|
|
7137
|
+
view,
|
|
7138
|
+
settings,
|
|
7139
|
+
applyEdit,
|
|
7140
|
+
ctx,
|
|
7141
|
+
workspaceCtx,
|
|
7142
|
+
settingsOrder,
|
|
7143
|
+
refresh,
|
|
7144
|
+
askArchitect,
|
|
7145
|
+
askOrchestrator,
|
|
7146
|
+
switchWorkspace,
|
|
7147
|
+
refresh,
|
|
7148
|
+
say,
|
|
7149
|
+
exit
|
|
7150
|
+
]
|
|
5513
7151
|
);
|
|
5514
7152
|
useInput2((input, key2) => {
|
|
5515
7153
|
if (key2.ctrl && input === "c") exit();
|
|
5516
7154
|
});
|
|
5517
7155
|
const ticket = ticketKey ? board?.tickets.find((row) => row.key === ticketKey) : null;
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
/* @__PURE__ */
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
"No ticket ",
|
|
5552
|
-
ticketKey,
|
|
5553
|
-
" here."
|
|
5554
|
-
] }) : null,
|
|
5555
|
-
view === "home" && board ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
5556
|
-
/* @__PURE__ */ jsx6(AgentsPanel, { board }),
|
|
5557
|
-
/* @__PURE__ */ jsx6(EpicsPanel, { board })
|
|
5558
|
-
] }) : null,
|
|
5559
|
-
messages.map((message) => /* @__PURE__ */ jsx6(Bubble, { message, width }, message.id)),
|
|
5560
|
-
notice ? /* @__PURE__ */ jsx6(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: UI.warn, children: notice }) }) : null,
|
|
5561
|
-
/* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsx6(
|
|
5562
|
-
TextInput,
|
|
7156
|
+
const settled = messages.filter((message) => message.done);
|
|
7157
|
+
const inFlight = messages.filter((message) => !message.done);
|
|
7158
|
+
const splash = !started;
|
|
7159
|
+
const scrollback = splash ? [] : [
|
|
7160
|
+
{ key: "banner" },
|
|
7161
|
+
{ key: "help", message: { id: "help", speaker: "system", body: "", panel: "help" } },
|
|
7162
|
+
...settled.map((message) => ({ key: message.id, message }))
|
|
7163
|
+
];
|
|
7164
|
+
const plan = planLayout({
|
|
7165
|
+
rows,
|
|
7166
|
+
columns,
|
|
7167
|
+
width,
|
|
7168
|
+
splash,
|
|
7169
|
+
ready,
|
|
7170
|
+
decision: decisionRows(decisions),
|
|
7171
|
+
inFlight: inFlight.reduce((total, message) => total + bubbleRows(message, width), 0),
|
|
7172
|
+
notice: Boolean(notice),
|
|
7173
|
+
home: view === "home"
|
|
7174
|
+
});
|
|
7175
|
+
const budget = plan.panels;
|
|
7176
|
+
const cockpitRows = plan.cockpit;
|
|
7177
|
+
const streamRows = plan.stream;
|
|
7178
|
+
const fits = plan.fits;
|
|
7179
|
+
const running = board?.runs.filter((run6) => run6.status === "running").length ?? 0;
|
|
7180
|
+
const selected = cursor ? board?.tickets.find((row) => row.id === cursor) : null;
|
|
7181
|
+
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
7182
|
+
/* @__PURE__ */ jsx11(Static, { items: scrollback, children: (item) => {
|
|
7183
|
+
if (!item.message) return /* @__PURE__ */ jsx11(Banner, { animate: false }, item.key);
|
|
7184
|
+
if (item.message.panel === "help") return /* @__PURE__ */ jsx11(Help, { width }, item.key);
|
|
7185
|
+
return /* @__PURE__ */ jsx11(Bubble, { message: item.message, width }, item.key);
|
|
7186
|
+
} }),
|
|
7187
|
+
splash ? /* @__PURE__ */ jsx11(
|
|
7188
|
+
Splash,
|
|
5563
7189
|
{
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
7190
|
+
columns,
|
|
7191
|
+
rows,
|
|
7192
|
+
width,
|
|
7193
|
+
ready,
|
|
7194
|
+
helpFull: plan.helpFull,
|
|
7195
|
+
animate: fits,
|
|
7196
|
+
onDone: () => setReady(true)
|
|
7197
|
+
}
|
|
7198
|
+
) : null,
|
|
7199
|
+
/* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", width, children: [
|
|
7200
|
+
view === "board" && board && budget > 0 ? /* @__PURE__ */ jsx11(BoardPanel, { board, width, rows: budget, cursor }) : null,
|
|
7201
|
+
view === "agents" && board && budget > 0 ? /* @__PURE__ */ jsx11(AgentsPanel, { board, width, rows: budget }) : null,
|
|
7202
|
+
view === "feed" && budget > 0 ? /* @__PURE__ */ jsx11(FeedPanel, { entries: feed, width, rows: budget }) : null,
|
|
7203
|
+
view === "settings" && budget > 0 ? /* @__PURE__ */ jsx11(
|
|
7204
|
+
SettingsPanel,
|
|
7205
|
+
{
|
|
7206
|
+
entries: settings,
|
|
7207
|
+
width,
|
|
7208
|
+
rows: budget,
|
|
7209
|
+
cursor: field,
|
|
7210
|
+
editing
|
|
7211
|
+
}
|
|
7212
|
+
) : null,
|
|
7213
|
+
view === "inbox" && board && budget > 0 ? /* @__PURE__ */ jsx11(InboxPanel, { board, width, rows: budget }) : null,
|
|
7214
|
+
view === "ticket" && budget > 0 ? ticket ? /* @__PURE__ */ jsx11(TicketPanel, { ticket, width, rows: budget }) : /* @__PURE__ */ jsxs10(Text10, { color: UI.warn, children: [
|
|
7215
|
+
"No ticket ",
|
|
7216
|
+
ticketKey,
|
|
7217
|
+
" here."
|
|
7218
|
+
] }) : null,
|
|
7219
|
+
view === "home" && board && plan.cockpit > 0 ? /* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
7220
|
+
/* @__PURE__ */ jsx11(Cockpit, { board, width, rows: cockpitRows, cursor }),
|
|
7221
|
+
/* @__PURE__ */ jsx11(Box8, { height: 1 }),
|
|
7222
|
+
/* @__PURE__ */ jsx11(StreamPanel, { lines: stream, width, rows: streamRows, live: running > 0 })
|
|
7223
|
+
] }) : null,
|
|
7224
|
+
inFlight.map((message) => /* @__PURE__ */ jsx11(Bubble, { message, width }, message.id)),
|
|
7225
|
+
/* @__PURE__ */ jsx11(DecisionPanel, { decisions, board, width, rows: plan.decision }),
|
|
7226
|
+
notice ? /* @__PURE__ */ jsx11(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text10, { color: UI.warn, children: notice }) }) : null,
|
|
7227
|
+
/* @__PURE__ */ jsxs10(Box8, { marginTop: 1, children: [
|
|
7228
|
+
/* @__PURE__ */ jsx11(Text10, { color: UI.text, bold: true, children: current.slug }),
|
|
7229
|
+
/* @__PURE__ */ jsxs10(Text10, { color: UI.dim, children: [
|
|
7230
|
+
" ",
|
|
7231
|
+
current.repo,
|
|
5571
7232
|
" "
|
|
5572
7233
|
] }),
|
|
5573
|
-
color: UI.
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
},
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
7234
|
+
/* @__PURE__ */ jsx11(Text10, { color: live === "live" ? UI.accent : UI.dim, children: "\u25CF " }),
|
|
7235
|
+
/* @__PURE__ */ jsxs10(Text10, { color: UI.dim, children: [
|
|
7236
|
+
live,
|
|
7237
|
+
" "
|
|
7238
|
+
] }),
|
|
7239
|
+
/* @__PURE__ */ jsx11(Text10, { color: UI.dim, children: running ? `${running} running ` : "" }),
|
|
7240
|
+
board?.decisions.length ? /* @__PURE__ */ jsxs10(Text10, { color: UI.warn, children: [
|
|
7241
|
+
board.decisions.length,
|
|
7242
|
+
" decisions "
|
|
7243
|
+
] }) : null,
|
|
7244
|
+
pausedAll || current.paused ? /* @__PURE__ */ jsx11(Text10, { color: UI.warn, children: "paused " }) : null,
|
|
7245
|
+
/* @__PURE__ */ jsxs10(Text10, { color: UI.dim, wrap: "truncate", children: [
|
|
7246
|
+
"\xB7 ",
|
|
7247
|
+
mode,
|
|
7248
|
+
cursor && selected ? ` ${selected.key} \u2191\u2193 move \xB7 enter opens \xB7 esc leaves` : ` \xB7 ${brainLabel}`
|
|
7249
|
+
] })
|
|
7250
|
+
] }),
|
|
7251
|
+
/* @__PURE__ */ jsx11(Box8, { children: /* @__PURE__ */ jsx11(
|
|
7252
|
+
TextInput,
|
|
7253
|
+
{
|
|
7254
|
+
value: draft,
|
|
7255
|
+
onChange: (next) => {
|
|
7256
|
+
setDraft(next);
|
|
7257
|
+
if (editingRef.current) setEditing({ key: editingRef.current.key, draft: next });
|
|
7258
|
+
},
|
|
7259
|
+
onSubmit: (value) => void run5(value),
|
|
7260
|
+
isActive: !busy,
|
|
7261
|
+
placeholder: busy ? "working\u2026" : "message, or /help",
|
|
7262
|
+
prompt: /* @__PURE__ */ jsxs10(Text10, { color: mode === "browse" ? UI.dim : UI.cream, children: [
|
|
7263
|
+
promptFor(mode),
|
|
7264
|
+
" "
|
|
7265
|
+
] }),
|
|
7266
|
+
color: UI.text,
|
|
7267
|
+
onCancel: () => {
|
|
7268
|
+
if (editing) {
|
|
7269
|
+
setEditing(null);
|
|
7270
|
+
setDraft("");
|
|
7271
|
+
setNotice(null);
|
|
7272
|
+
return;
|
|
7273
|
+
}
|
|
7274
|
+
if (view === "settings") {
|
|
7275
|
+
setView("home");
|
|
7276
|
+
return;
|
|
7277
|
+
}
|
|
7278
|
+
if (view === "ticket") {
|
|
7279
|
+
setView("home");
|
|
7280
|
+
setTicketKey(null);
|
|
7281
|
+
return;
|
|
7282
|
+
}
|
|
7283
|
+
setCursor(null);
|
|
7284
|
+
selectedRef.current = null;
|
|
7285
|
+
},
|
|
7286
|
+
onUp: () => {
|
|
7287
|
+
if (configuring && moveField(-1)) return;
|
|
7288
|
+
if (browsing && moveCursor(-1)) return;
|
|
7289
|
+
if (history.current.length === 0) return;
|
|
7290
|
+
historyAt.current = historyAt.current < 0 ? history.current.length - 1 : Math.max(0, historyAt.current - 1);
|
|
7291
|
+
setDraft(history.current[historyAt.current] ?? "");
|
|
7292
|
+
},
|
|
7293
|
+
onDown: () => {
|
|
7294
|
+
if (configuring && moveField(1)) return;
|
|
7295
|
+
if (browsing && moveCursor(1)) return;
|
|
7296
|
+
if (historyAt.current < 0) return;
|
|
7297
|
+
historyAt.current += 1;
|
|
7298
|
+
if (historyAt.current >= history.current.length) {
|
|
7299
|
+
historyAt.current = -1;
|
|
7300
|
+
setDraft("");
|
|
7301
|
+
return;
|
|
7302
|
+
}
|
|
7303
|
+
setDraft(history.current[historyAt.current] ?? "");
|
|
5586
7304
|
}
|
|
5587
|
-
setDraft(history.current[historyAt.current] ?? "");
|
|
5588
7305
|
}
|
|
5589
|
-
}
|
|
5590
|
-
|
|
7306
|
+
) })
|
|
7307
|
+
] })
|
|
5591
7308
|
] });
|
|
5592
7309
|
}
|
|
5593
7310
|
function promptFor(mode) {
|
|
@@ -5615,6 +7332,16 @@ var init_App = __esm({
|
|
|
5615
7332
|
init_plan();
|
|
5616
7333
|
init_commands3();
|
|
5617
7334
|
init_Banner();
|
|
7335
|
+
init_Splash();
|
|
7336
|
+
init_height();
|
|
7337
|
+
init_layout();
|
|
7338
|
+
init_Dashboard();
|
|
7339
|
+
init_Settings();
|
|
7340
|
+
init_settings_model();
|
|
7341
|
+
init_config2();
|
|
7342
|
+
init_Decision();
|
|
7343
|
+
init_alert();
|
|
7344
|
+
init_stream();
|
|
5618
7345
|
init_Bubble();
|
|
5619
7346
|
init_Help();
|
|
5620
7347
|
init_Panels();
|
|
@@ -5635,7 +7362,7 @@ __export(launch_exports, {
|
|
|
5635
7362
|
async function launchApp(slug) {
|
|
5636
7363
|
if (!canLaunchApp()) return;
|
|
5637
7364
|
const ctx = await requireWorkspace(slug);
|
|
5638
|
-
const [{ render },
|
|
7365
|
+
const [{ render }, React12, { App: App2 }] = await Promise.all([
|
|
5639
7366
|
import("ink"),
|
|
5640
7367
|
import("react"),
|
|
5641
7368
|
Promise.resolve().then(() => (init_App(), App_exports))
|
|
@@ -5643,7 +7370,7 @@ async function launchApp(slug) {
|
|
|
5643
7370
|
const { data: workspaces } = await ctx.db.from("workspaces").select("*").order("name");
|
|
5644
7371
|
const config = await loadConfig();
|
|
5645
7372
|
const instance = render(
|
|
5646
|
-
|
|
7373
|
+
React12.createElement(App2, {
|
|
5647
7374
|
ctx,
|
|
5648
7375
|
workspace: ctx.workspace,
|
|
5649
7376
|
workspaces: workspaces ?? [],
|
|
@@ -5808,7 +7535,7 @@ function loadConfig2(env = process.env) {
|
|
|
5808
7535
|
stopTimeoutMs: Number(env.RUNNER_STOP_TIMEOUT_MS ?? 9e4)
|
|
5809
7536
|
};
|
|
5810
7537
|
}
|
|
5811
|
-
var
|
|
7538
|
+
var init_config3 = __esm({
|
|
5812
7539
|
"../runner/src/config.ts"() {
|
|
5813
7540
|
"use strict";
|
|
5814
7541
|
}
|
|
@@ -6125,7 +7852,7 @@ var init_init = __esm({
|
|
|
6125
7852
|
"../runner/src/init.ts"() {
|
|
6126
7853
|
"use strict";
|
|
6127
7854
|
init_auth_check();
|
|
6128
|
-
|
|
7855
|
+
init_config3();
|
|
6129
7856
|
init_db();
|
|
6130
7857
|
init_heartbeat();
|
|
6131
7858
|
init_providers();
|
|
@@ -6261,6 +7988,7 @@ init_commands2();
|
|
|
6261
7988
|
init_context();
|
|
6262
7989
|
init_json();
|
|
6263
7990
|
init_theme();
|
|
7991
|
+
init_argv_parsers();
|
|
6264
7992
|
init_format();
|
|
6265
7993
|
|
|
6266
7994
|
// src/live/types.ts
|
|
@@ -6440,7 +8168,7 @@ function registerLiveCommands(program) {
|
|
|
6440
8168
|
render: async () => agentsView(await loadBoard(ctx))
|
|
6441
8169
|
});
|
|
6442
8170
|
});
|
|
6443
|
-
program.command("feed").description("live activity stream").option("-n, --limit <n>", "rows to show", "
|
|
8171
|
+
program.command("feed").description("live activity stream").option("-n, --limit <n>", "rows to show", positiveInteger("limit"), 40).action(async function() {
|
|
6444
8172
|
const ctx = await requireWorkspace(slugOf4(this));
|
|
6445
8173
|
const limit = Number(this.opts().limit) || 40;
|
|
6446
8174
|
if (isJsonMode()) {
|
|
@@ -6479,6 +8207,7 @@ init_group();
|
|
|
6479
8207
|
init_editor();
|
|
6480
8208
|
init_epics();
|
|
6481
8209
|
init_tickets2();
|
|
8210
|
+
init_argv_parsers();
|
|
6482
8211
|
import { readFile as readFile6 } from "fs/promises";
|
|
6483
8212
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6484
8213
|
function slugOf5(command) {
|
|
@@ -6497,7 +8226,7 @@ function requestId(command) {
|
|
|
6497
8226
|
return String(command.opts().requestId ?? randomUUID4());
|
|
6498
8227
|
}
|
|
6499
8228
|
function registerWriteCommands(program) {
|
|
6500
|
-
program.command("new").argument("[title...]", "ticket title").description("file a ticket").option("-b, --body <text>", "body markdown").option("-A, --acceptance <text>", "acceptance criteria").option("--area <area>", "area tag, e.g. web/auth").option("-a, --agent <name>", "pin a builder by display name").option("-p, --priority <n>", "priority, higher runs first", "
|
|
8229
|
+
program.command("new").argument("[title...]", "ticket title").description("file a ticket").option("-b, --body <text>", "body markdown").option("-A, --acceptance <text>", "acceptance criteria").option("--area <area>", "area tag, e.g. web/auth").option("-a, --agent <name>", "pin a builder by display name").option("-p, --priority <n>", "priority, higher runs first", integer("priority"), 0).option("--host <id>", "run on a specific host").option("-e, --epic <id>", "attach to an epic").option("--block <keys>", "comma-separated keys this depends on").option("-t, --template <id>", "start from a workspace template").option("--edit", "open $EDITOR for the body and acceptance").action(async function(titleWords) {
|
|
6501
8230
|
const opts = this.opts();
|
|
6502
8231
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6503
8232
|
let title = titleWords.join(" ").trim();
|
|
@@ -6541,7 +8270,7 @@ function registerWriteCommands(program) {
|
|
|
6541
8270
|
});
|
|
6542
8271
|
ok(`${c.bold(ticket.key)} ${ticket.title} ${c.dim(`(${ticket.status})`)}`, { ticket });
|
|
6543
8272
|
});
|
|
6544
|
-
program.command("set").argument("<key>", "ticket key").description("change a ticket").option("--title <text>", "new title").option("-b, --body <text>", "new body").option("-A, --acceptance <text>", "new acceptance criteria").option("--area <area>", "new area").option("-a, --agent <name>", "assign a builder, or 'none' to unassign").option("-p, --priority <n>", "new priority").option("--host <id>", "run on a specific host, or 'none'").option("-s, --status <status>", "move to a status").option("-e, --epic <id>", "attach to an epic, or 'none'").option("--edit", "open $EDITOR for the body and acceptance").option("--request-id <uuid>", "stable operation id for safe retry").action(async function(key2) {
|
|
8273
|
+
program.command("set").argument("<key>", "ticket key").description("change a ticket").option("--title <text>", "new title").option("-b, --body <text>", "new body").option("-A, --acceptance <text>", "new acceptance criteria").option("--area <area>", "new area").option("-a, --agent <name>", "assign a builder, or 'none' to unassign").option("-p, --priority <n>", "new priority", integer("priority")).option("--host <id>", "run on a specific host, or 'none'").option("-s, --status <status>", "move to a status", oneOf("status", ticketStatuses)).option("-e, --epic <id>", "attach to an epic, or 'none'").option("--edit", "open $EDITOR for the body and acceptance").option("--request-id <uuid>", "stable operation id for safe retry", uuid).action(async function(key2) {
|
|
6545
8274
|
const opts = this.opts();
|
|
6546
8275
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6547
8276
|
const ticket = await findTicket(ctx, key2);
|
|
@@ -6571,21 +8300,21 @@ function registerWriteCommands(program) {
|
|
|
6571
8300
|
const updated = await updateTicket(ctx, ticket, patch, operationId);
|
|
6572
8301
|
ok(`${c.bold(updated.key)} updated ${c.dim(`(${updated.status})`)} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6573
8302
|
});
|
|
6574
|
-
program.command("block").argument("<key>", "ticket key").argument("<blockers...>", "keys it should wait for").description("make a ticket wait for others").option("--request-id <uuid>", "stable operation id for safe retry").action(async function(key2, blockers) {
|
|
8303
|
+
program.command("block").argument("<key>", "ticket key").argument("<blockers...>", "keys it should wait for").description("make a ticket wait for others").option("--request-id <uuid>", "stable operation id for safe retry", uuid).action(async function(key2, blockers) {
|
|
6575
8304
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6576
8305
|
const ticket = await findTicket(ctx, key2);
|
|
6577
8306
|
const operationId = requestId(this);
|
|
6578
8307
|
const updated = await setBlockedBy(ctx, ticket, blockers, "add", operationId);
|
|
6579
8308
|
ok(`${c.bold(updated.key)} is ${updated.status} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6580
8309
|
});
|
|
6581
|
-
program.command("unblock").argument("<key>", "ticket key").argument("[blockers...]", "keys to drop; omit to clear them all").description("drop dependencies").option("--request-id <uuid>", "stable operation id for safe retry").action(async function(key2, blockers) {
|
|
8310
|
+
program.command("unblock").argument("<key>", "ticket key").argument("[blockers...]", "keys to drop; omit to clear them all").description("drop dependencies").option("--request-id <uuid>", "stable operation id for safe retry", uuid).action(async function(key2, blockers) {
|
|
6582
8311
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6583
8312
|
const ticket = await findTicket(ctx, key2);
|
|
6584
8313
|
const operationId = requestId(this);
|
|
6585
8314
|
const updated = blockers.length ? await setBlockedBy(ctx, ticket, blockers, "remove", operationId) : await setBlockedBy(ctx, ticket, [], "set", operationId);
|
|
6586
8315
|
ok(`${c.bold(updated.key)} is ${updated.status} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6587
8316
|
});
|
|
6588
|
-
program.command("take").argument("<key>", "ticket key").description("take a ticket over so the board stops dispatching it").option("--request-id <uuid>", "stable operation id for safe retry").action(async function(key2) {
|
|
8317
|
+
program.command("take").argument("<key>", "ticket key").description("take a ticket over so the board stops dispatching it").option("--request-id <uuid>", "stable operation id for safe retry", uuid).action(async function(key2) {
|
|
6589
8318
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6590
8319
|
const operationId = requestId(this);
|
|
6591
8320
|
const updated = await takeOver(ctx, await findTicket(ctx, key2), operationId);
|
|
@@ -6594,13 +8323,13 @@ function registerWriteCommands(program) {
|
|
|
6594
8323
|
request_id: operationId
|
|
6595
8324
|
});
|
|
6596
8325
|
});
|
|
6597
|
-
program.command("handback").argument("<key>", "ticket key").description("give a ticket back to the board").option("--request-id <uuid>", "stable operation id for safe retry").action(async function(key2) {
|
|
8326
|
+
program.command("handback").argument("<key>", "ticket key").description("give a ticket back to the board").option("--request-id <uuid>", "stable operation id for safe retry", uuid).action(async function(key2) {
|
|
6598
8327
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6599
8328
|
const operationId = requestId(this);
|
|
6600
8329
|
const updated = await handBack(ctx, await findTicket(ctx, key2), operationId);
|
|
6601
8330
|
ok(`${c.bold(updated.key)} handed back ${c.dim(`(${updated.status})`)} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6602
8331
|
});
|
|
6603
|
-
program.command("cancel").argument("<key>", "ticket key").description("cancel a ticket").option("--request-id <uuid>", "stable operation id for safe retry").action(async function(key2) {
|
|
8332
|
+
program.command("cancel").argument("<key>", "ticket key").description("cancel a ticket").option("--request-id <uuid>", "stable operation id for safe retry", uuid).action(async function(key2) {
|
|
6604
8333
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6605
8334
|
const ticket = await findTicket(ctx, key2);
|
|
6606
8335
|
const operationId = requestId(this);
|
|
@@ -6634,7 +8363,7 @@ function registerWriteCommands(program) {
|
|
|
6634
8363
|
{ epic: created }
|
|
6635
8364
|
);
|
|
6636
8365
|
});
|
|
6637
|
-
epic.command("set").argument("<id>", "epic id or prefix").description("change an epic").option("--title <text>", "new title").option("--spec <path>", "replace the spec from a file").option("-s, --status <status>", "draft, decomposing, active, or done").action(async function(id) {
|
|
8366
|
+
epic.command("set").argument("<id>", "epic id or prefix").description("change an epic").option("--title <text>", "new title").option("--spec <path>", "replace the spec from a file").option("-s, --status <status>", "draft, decomposing, active, or done", oneOf("status", epicStatuses)).action(async function(id) {
|
|
6638
8367
|
const opts = this.opts();
|
|
6639
8368
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6640
8369
|
const found = resolveEpic(await loadBoard(ctx), id);
|
|
@@ -6697,7 +8426,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6697
8426
|
import { z as z3 } from "zod";
|
|
6698
8427
|
|
|
6699
8428
|
// src/version.ts
|
|
6700
|
-
var VERSION = "0.1
|
|
8429
|
+
var VERSION = "0.2.1";
|
|
6701
8430
|
|
|
6702
8431
|
// src/architect/mcp.ts
|
|
6703
8432
|
init_tools();
|
|
@@ -6772,6 +8501,7 @@ init_brain();
|
|
|
6772
8501
|
init_config();
|
|
6773
8502
|
init_connection();
|
|
6774
8503
|
init_tools();
|
|
8504
|
+
init_argv_parsers();
|
|
6775
8505
|
function slugOf6(command) {
|
|
6776
8506
|
return command.optsWithGlobals().workspace;
|
|
6777
8507
|
}
|
|
@@ -6870,7 +8600,7 @@ function registerArchitectCommands(program) {
|
|
|
6870
8600
|
}
|
|
6871
8601
|
await serveMcp({ readOnly, workspace: slugOf6(this) });
|
|
6872
8602
|
});
|
|
6873
|
-
program.command("brain").description("the model you talk to, and the subscription behind it").option("--model <model>", "which model to use").option("--effort <level>", "low, medium, or high").action(async function() {
|
|
8603
|
+
program.command("brain").description("the model you talk to, and the subscription behind it").option("--model <model>", "which model to use").option("--effort <level>", "low, medium, or high", oneOf("effort", ["low", "medium", "high"])).action(async function() {
|
|
6874
8604
|
const opts = this.opts();
|
|
6875
8605
|
if (opts.model || opts.effort) {
|
|
6876
8606
|
const next = await setBrain({
|