@higherdev/cli 0.2.0 → 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 +2664 -801
- 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";
|
|
@@ -482,11 +470,125 @@ var init_lifecycle = __esm({
|
|
|
482
470
|
}
|
|
483
471
|
});
|
|
484
472
|
|
|
473
|
+
// ../../packages/db/src/commands/execute.ts
|
|
474
|
+
function ownerTicketCommand(opts) {
|
|
475
|
+
return {
|
|
476
|
+
commandId: opts.operationId,
|
|
477
|
+
idempotencyKey: `owner-ticket:${opts.workspaceId}:${opts.ticketId}:${opts.operationId}`,
|
|
478
|
+
actor: { type: "human", id: opts.actorEmail.trim().toLowerCase() },
|
|
479
|
+
workspaceId: opts.workspaceId,
|
|
480
|
+
target: { type: "ticket", id: opts.ticketId },
|
|
481
|
+
expected: { status: opts.expectedStatus, version: opts.expectedVersion },
|
|
482
|
+
intent: opts.intent
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function isLifecycleResult(value) {
|
|
486
|
+
return Boolean(
|
|
487
|
+
value && typeof value === "object" && !Array.isArray(value) && (value.outcome === "accepted" || value.outcome === "rejected")
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
async function applyOwnerTicketCommand(db, command) {
|
|
491
|
+
const { data, error } = await db.rpc("apply_owner_ticket_command", {
|
|
492
|
+
p_command: command
|
|
493
|
+
});
|
|
494
|
+
if (error) throw error;
|
|
495
|
+
if (!isLifecycleResult(data)) throw new Error("Owner ticket command returned an invalid receipt.");
|
|
496
|
+
return data;
|
|
497
|
+
}
|
|
498
|
+
var init_execute = __esm({
|
|
499
|
+
"../../packages/db/src/commands/execute.ts"() {
|
|
500
|
+
"use strict";
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
485
504
|
// ../../packages/db/src/commands/index.ts
|
|
486
505
|
var init_commands = __esm({
|
|
487
506
|
"../../packages/db/src/commands/index.ts"() {
|
|
488
507
|
"use strict";
|
|
489
508
|
init_lifecycle();
|
|
509
|
+
init_execute();
|
|
510
|
+
}
|
|
511
|
+
});
|
|
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();
|
|
490
592
|
}
|
|
491
593
|
});
|
|
492
594
|
|
|
@@ -517,6 +619,14 @@ var init_defaults = __esm({
|
|
|
517
619
|
}
|
|
518
620
|
});
|
|
519
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
|
+
|
|
520
630
|
// ../../packages/db/src/epic-progress.ts
|
|
521
631
|
function round4(value) {
|
|
522
632
|
return Math.round(value * 1e4) / 1e4;
|
|
@@ -690,6 +800,10 @@ var init_schemas = __esm({
|
|
|
690
800
|
timeout_minutes: z.number().int().positive(),
|
|
691
801
|
max_attempts: z.number().int().positive(),
|
|
692
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(),
|
|
693
807
|
webhook_url: z.union([z.url(), z.literal("")]).optional(),
|
|
694
808
|
smoke_url: z.union([z.url(), z.literal("")]).optional(),
|
|
695
809
|
quiet_hours: z.object({
|
|
@@ -723,6 +837,7 @@ var init_schemas = __esm({
|
|
|
723
837
|
api_key_hash: z.string().nullable(),
|
|
724
838
|
paused: z.boolean(),
|
|
725
839
|
webhook_secret: z.string().nullable().optional(),
|
|
840
|
+
webhook_secret_pending: z.string().nullable().optional(),
|
|
726
841
|
webhook_last_status: z.string().nullable().optional(),
|
|
727
842
|
webhook_last_at: isoDateSchema.nullable().optional(),
|
|
728
843
|
webhook_attempts: z.number().int().nonnegative().optional(),
|
|
@@ -894,80 +1009,6 @@ var init_schemas = __esm({
|
|
|
894
1009
|
}
|
|
895
1010
|
});
|
|
896
1011
|
|
|
897
|
-
// ../../packages/db/src/status.ts
|
|
898
|
-
function statusLabel(status) {
|
|
899
|
-
return status.replaceAll("_", " ");
|
|
900
|
-
}
|
|
901
|
-
function statusTone(status) {
|
|
902
|
-
switch (status) {
|
|
903
|
-
case "queued":
|
|
904
|
-
case "running":
|
|
905
|
-
case "in_review":
|
|
906
|
-
return "blue";
|
|
907
|
-
case "approved":
|
|
908
|
-
case "merged":
|
|
909
|
-
case "ready":
|
|
910
|
-
return "success";
|
|
911
|
-
case "blocked":
|
|
912
|
-
case "needs_decision":
|
|
913
|
-
case "changes_requested":
|
|
914
|
-
return "warning";
|
|
915
|
-
case "failed":
|
|
916
|
-
case "cancelled":
|
|
917
|
-
return "danger";
|
|
918
|
-
default:
|
|
919
|
-
return "muted";
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
function resolveTicketStatus(requested, blockerStatuses2) {
|
|
923
|
-
if (requested === "cancelled") return "cancelled";
|
|
924
|
-
const open = blockerStatuses2.filter((status) => status !== "merged" && status !== "cancelled");
|
|
925
|
-
if (open.length > 0) return "blocked";
|
|
926
|
-
if (requested === "blocked") return "backlog";
|
|
927
|
-
return requested;
|
|
928
|
-
}
|
|
929
|
-
function initialHumanStatus(opts) {
|
|
930
|
-
const groomed = Boolean(opts.acceptanceMd.trim() && opts.area && opts.assigned);
|
|
931
|
-
return resolveTicketStatus(groomed ? "ready" : "backlog", opts.blockerStatuses);
|
|
932
|
-
}
|
|
933
|
-
function statusAfterAnswer(opts) {
|
|
934
|
-
return opts.assigned && Boolean(opts.acceptanceMd.trim()) ? "queued" : "ready";
|
|
935
|
-
}
|
|
936
|
-
function handBackStatus(opts) {
|
|
937
|
-
if (opts.acceptanceMd.trim() && opts.assigned) {
|
|
938
|
-
return resolveTicketStatus(statusAfterAnswer(opts), opts.blockerStatuses);
|
|
939
|
-
}
|
|
940
|
-
return initialHumanStatus(opts);
|
|
941
|
-
}
|
|
942
|
-
function cancelledSuffix(cancelled) {
|
|
943
|
-
return cancelled > 0 ? ` +${cancelled} cancelled` : "";
|
|
944
|
-
}
|
|
945
|
-
function epicProgressCaption(merged, total, cancelled = 0) {
|
|
946
|
-
if (total === 0 && cancelled === 0) return "No tickets";
|
|
947
|
-
return `${merged}/${total} merged${cancelledSuffix(cancelled)}`;
|
|
948
|
-
}
|
|
949
|
-
var BOARD_COLUMNS;
|
|
950
|
-
var init_status = __esm({
|
|
951
|
-
"../../packages/db/src/status.ts"() {
|
|
952
|
-
"use strict";
|
|
953
|
-
init_enums();
|
|
954
|
-
BOARD_COLUMNS = [
|
|
955
|
-
"backlog",
|
|
956
|
-
"ready",
|
|
957
|
-
"blocked",
|
|
958
|
-
"queued",
|
|
959
|
-
"running",
|
|
960
|
-
"failed",
|
|
961
|
-
"needs_decision",
|
|
962
|
-
"in_review",
|
|
963
|
-
"changes_requested",
|
|
964
|
-
"approved",
|
|
965
|
-
"merged",
|
|
966
|
-
"cancelled"
|
|
967
|
-
];
|
|
968
|
-
}
|
|
969
|
-
});
|
|
970
|
-
|
|
971
1012
|
// ../../packages/db/src/stuck.ts
|
|
972
1013
|
function stuckReason(opts) {
|
|
973
1014
|
const { ticket } = opts;
|
|
@@ -1087,9 +1128,9 @@ function uncertainCreateError(error) {
|
|
|
1087
1128
|
return Boolean(error && (!error.code || /^PGRST00[0-3]$/.test(error.code)));
|
|
1088
1129
|
}
|
|
1089
1130
|
async function createTicketRecord(db, workspaceId, fields, ticketId) {
|
|
1090
|
-
const
|
|
1131
|
+
const requestId2 = ticketId ?? globalThis.crypto.randomUUID();
|
|
1091
1132
|
const args = {
|
|
1092
|
-
p_ticket_id:
|
|
1133
|
+
p_ticket_id: requestId2,
|
|
1093
1134
|
p_workspace_id: workspaceId,
|
|
1094
1135
|
p_fields: fields
|
|
1095
1136
|
};
|
|
@@ -1140,173 +1181,54 @@ var init_ticket_writes = __esm({
|
|
|
1140
1181
|
}
|
|
1141
1182
|
});
|
|
1142
1183
|
|
|
1143
|
-
// ../../packages/db/src/
|
|
1144
|
-
function
|
|
1145
|
-
|
|
1146
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
1147
|
-
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1148
|
-
const seconds = totalSeconds % 60;
|
|
1149
|
-
if (hours > 0) return minutes ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
1150
|
-
if (minutes > 0) return seconds ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
1151
|
-
return `${seconds}s`;
|
|
1184
|
+
// ../../packages/db/src/transcript.ts
|
|
1185
|
+
function asRecord(value) {
|
|
1186
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1152
1187
|
}
|
|
1153
|
-
function
|
|
1154
|
-
|
|
1155
|
-
const end = Date.parse(to);
|
|
1156
|
-
if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
|
|
1157
|
-
return end - start;
|
|
1188
|
+
function str(value) {
|
|
1189
|
+
return typeof value === "string" ? value : value == null ? "" : String(value);
|
|
1158
1190
|
}
|
|
1159
|
-
function
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1191
|
+
function assistantText(payload) {
|
|
1192
|
+
if (typeof payload.text === "string") return payload.text;
|
|
1193
|
+
const item = asRecord(payload.item);
|
|
1194
|
+
if (typeof item.text === "string") return item.text;
|
|
1195
|
+
if (Array.isArray(item.content)) {
|
|
1196
|
+
const joined = item.content.map((entry) => str(asRecord(entry).text)).filter(Boolean).join("\n");
|
|
1197
|
+
if (joined) return joined;
|
|
1198
|
+
}
|
|
1199
|
+
const message = asRecord(payload.message);
|
|
1200
|
+
const content = message.content;
|
|
1201
|
+
if (Array.isArray(content)) {
|
|
1202
|
+
return content.map((item2) => {
|
|
1203
|
+
const row = asRecord(item2);
|
|
1204
|
+
return str(row.text);
|
|
1205
|
+
}).filter(Boolean).join("\n");
|
|
1206
|
+
}
|
|
1207
|
+
if (typeof content === "string") return content;
|
|
1208
|
+
return "";
|
|
1163
1209
|
}
|
|
1164
|
-
function
|
|
1165
|
-
|
|
1166
|
-
const date = new Date(iso);
|
|
1167
|
-
if (Number.isNaN(date.getTime())) return "";
|
|
1168
|
-
return date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
1210
|
+
function toolName(payload) {
|
|
1211
|
+
return str(payload.name || payload.tool || payload.tool_name || asRecord(payload.input).command || "tool");
|
|
1169
1212
|
}
|
|
1170
|
-
function
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1213
|
+
function toolDetail(payload) {
|
|
1214
|
+
const input = asRecord(payload.input ?? payload.args ?? payload);
|
|
1215
|
+
const file = str(input.path || input.file_path || input.file || input.target);
|
|
1216
|
+
const command = str(input.command || input.cmd);
|
|
1217
|
+
const additions = Number(input.additions ?? input.added);
|
|
1218
|
+
const deletions = Number(input.deletions ?? input.removed);
|
|
1219
|
+
const stat2 = Number.isFinite(additions) || Number.isFinite(deletions) ? ` +${Number.isFinite(additions) ? additions : 0}/-${Number.isFinite(deletions) ? deletions : 0}` : "";
|
|
1220
|
+
if (file) return `${file}${stat2}`;
|
|
1221
|
+
if (command) return command.length > 80 ? `${command.slice(0, 77)}...` : command;
|
|
1222
|
+
return "";
|
|
1176
1223
|
}
|
|
1177
|
-
function
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const items = [];
|
|
1186
|
-
const statuses = [...input.statusEvents].sort((a, b) => a.at.localeCompare(b.at) || a.id.localeCompare(b.id));
|
|
1187
|
-
for (let i = 0; i < statuses.length; i++) {
|
|
1188
|
-
const event = statuses[i];
|
|
1189
|
-
const next = statuses[i + 1];
|
|
1190
|
-
const from = event.from_status ? statusLabel(event.from_status) : "new";
|
|
1191
|
-
const to = statusLabel(event.to_status);
|
|
1192
|
-
items.push({
|
|
1193
|
-
id: `status:${event.id}`,
|
|
1194
|
-
at: event.at,
|
|
1195
|
-
kind: "status",
|
|
1196
|
-
title: `${from} to ${to}`,
|
|
1197
|
-
durationMs: msBetween(event.at, next?.at ?? nowIso),
|
|
1198
|
-
endedAt: next?.at ?? null
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
for (const run5 of input.runs) {
|
|
1202
|
-
const at = run5.started_at ?? run5.created_at;
|
|
1203
|
-
const span = runSpan(run5);
|
|
1204
|
-
const kind = run5.kind === "review" ? "verdict" : "run";
|
|
1205
|
-
items.push({
|
|
1206
|
-
id: `run:${run5.id}`,
|
|
1207
|
-
at,
|
|
1208
|
-
kind,
|
|
1209
|
-
title: [
|
|
1210
|
-
run5.kind,
|
|
1211
|
-
run5.provider,
|
|
1212
|
-
run5.status,
|
|
1213
|
-
run5.reviewed_sha ? `@ ${run5.reviewed_sha.slice(0, 12)}` : "",
|
|
1214
|
-
span
|
|
1215
|
-
].filter(Boolean).join(" "),
|
|
1216
|
-
detail: run5.summary ? clip(run5.summary) : void 0,
|
|
1217
|
-
durationMs: runDurationMs(run5, nowIso),
|
|
1218
|
-
endedAt: run5.ended_at
|
|
1219
|
-
});
|
|
1220
|
-
}
|
|
1221
|
-
for (const message of input.messages) {
|
|
1222
|
-
const kind = message.from_role === "reviewer" ? "verdict" : "message";
|
|
1223
|
-
items.push({
|
|
1224
|
-
id: `message:${message.id}`,
|
|
1225
|
-
at: message.created_at,
|
|
1226
|
-
kind,
|
|
1227
|
-
title: `${message.from_name} to ${message.to_role}`,
|
|
1228
|
-
detail: clip(message.body_md),
|
|
1229
|
-
durationMs: null
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
for (const decision of input.decisions) {
|
|
1233
|
-
const answered = Boolean(decision.answered_at);
|
|
1234
|
-
items.push({
|
|
1235
|
-
id: `decision:${decision.id}`,
|
|
1236
|
-
at: decision.created_at,
|
|
1237
|
-
kind: "decision",
|
|
1238
|
-
title: answered ? `answered (${decision.asked_by_role})` : `asked by ${decision.asked_by_role}`,
|
|
1239
|
-
detail: clip(answered ? decision.answer_md || decision.question_md : decision.question_md),
|
|
1240
|
-
durationMs: msBetween(decision.created_at, decision.answered_at ?? nowIso),
|
|
1241
|
-
endedAt: decision.answered_at
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
items.sort((a, b) => a.at.localeCompare(b.at) || KIND_RANK[a.kind] - KIND_RANK[b.kind] || a.id.localeCompare(b.id));
|
|
1245
|
-
return items;
|
|
1246
|
-
}
|
|
1247
|
-
var KIND_RANK;
|
|
1248
|
-
var init_timeline = __esm({
|
|
1249
|
-
"../../packages/db/src/timeline.ts"() {
|
|
1250
|
-
"use strict";
|
|
1251
|
-
init_status();
|
|
1252
|
-
KIND_RANK = {
|
|
1253
|
-
status: 0,
|
|
1254
|
-
run: 1,
|
|
1255
|
-
verdict: 2,
|
|
1256
|
-
message: 3,
|
|
1257
|
-
decision: 4
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
// ../../packages/db/src/transcript.ts
|
|
1263
|
-
function asRecord(value) {
|
|
1264
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1265
|
-
}
|
|
1266
|
-
function str(value) {
|
|
1267
|
-
return typeof value === "string" ? value : value == null ? "" : String(value);
|
|
1268
|
-
}
|
|
1269
|
-
function assistantText(payload) {
|
|
1270
|
-
if (typeof payload.text === "string") return payload.text;
|
|
1271
|
-
const item = asRecord(payload.item);
|
|
1272
|
-
if (typeof item.text === "string") return item.text;
|
|
1273
|
-
if (Array.isArray(item.content)) {
|
|
1274
|
-
const joined = item.content.map((entry) => str(asRecord(entry).text)).filter(Boolean).join("\n");
|
|
1275
|
-
if (joined) return joined;
|
|
1276
|
-
}
|
|
1277
|
-
const message = asRecord(payload.message);
|
|
1278
|
-
const content = message.content;
|
|
1279
|
-
if (Array.isArray(content)) {
|
|
1280
|
-
return content.map((item2) => {
|
|
1281
|
-
const row = asRecord(item2);
|
|
1282
|
-
return str(row.text);
|
|
1283
|
-
}).filter(Boolean).join("\n");
|
|
1284
|
-
}
|
|
1285
|
-
if (typeof content === "string") return content;
|
|
1286
|
-
return "";
|
|
1287
|
-
}
|
|
1288
|
-
function toolName(payload) {
|
|
1289
|
-
return str(payload.name || payload.tool || payload.tool_name || asRecord(payload.input).command || "tool");
|
|
1290
|
-
}
|
|
1291
|
-
function toolDetail(payload) {
|
|
1292
|
-
const input = asRecord(payload.input ?? payload.args ?? payload);
|
|
1293
|
-
const file = str(input.path || input.file_path || input.file || input.target);
|
|
1294
|
-
const command = str(input.command || input.cmd);
|
|
1295
|
-
const additions = Number(input.additions ?? input.added);
|
|
1296
|
-
const deletions = Number(input.deletions ?? input.removed);
|
|
1297
|
-
const stat2 = Number.isFinite(additions) || Number.isFinite(deletions) ? ` +${Number.isFinite(additions) ? additions : 0}/-${Number.isFinite(deletions) ? deletions : 0}` : "";
|
|
1298
|
-
if (file) return `${file}${stat2}`;
|
|
1299
|
-
if (command) return command.length > 80 ? `${command.slice(0, 77)}...` : command;
|
|
1300
|
-
return "";
|
|
1301
|
-
}
|
|
1302
|
-
function resultTail(payload) {
|
|
1303
|
-
const content = str(payload.content ?? payload.output ?? payload.stdout ?? payload.result);
|
|
1304
|
-
const code = payload.exit_code ?? payload.exitCode ?? asRecord(payload.tool_result).exit_code;
|
|
1305
|
-
const tail = content.trim().split("\n").slice(-8).join("\n");
|
|
1306
|
-
const prefix = code == null ? "" : `exit ${code}`;
|
|
1307
|
-
if (prefix && tail) return `${prefix}
|
|
1308
|
-
${tail}`;
|
|
1309
|
-
return prefix || tail;
|
|
1224
|
+
function resultTail(payload) {
|
|
1225
|
+
const content = str(payload.content ?? payload.output ?? payload.stdout ?? payload.result);
|
|
1226
|
+
const code = payload.exit_code ?? payload.exitCode ?? asRecord(payload.tool_result).exit_code;
|
|
1227
|
+
const tail = content.trim().split("\n").slice(-8).join("\n");
|
|
1228
|
+
const prefix = code == null ? "" : `exit ${code}`;
|
|
1229
|
+
if (prefix && tail) return `${prefix}
|
|
1230
|
+
${tail}`;
|
|
1231
|
+
return prefix || tail;
|
|
1310
1232
|
}
|
|
1311
1233
|
function codexItemLine(event, item) {
|
|
1312
1234
|
const kind = str(item.type);
|
|
@@ -1322,12 +1244,12 @@ function codexItemLine(event, item) {
|
|
|
1322
1244
|
return {
|
|
1323
1245
|
id: event.id,
|
|
1324
1246
|
kind: failed ? "error" : "tool",
|
|
1325
|
-
title: `${name}${
|
|
1326
|
-
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
|
|
1327
1249
|
};
|
|
1328
1250
|
}
|
|
1329
1251
|
if (kind === "command_execution") {
|
|
1330
|
-
const command =
|
|
1252
|
+
const command = clip(str(item.command ?? item.cmd), 120);
|
|
1331
1253
|
const code = item.exit_code ?? item.exitCode;
|
|
1332
1254
|
return {
|
|
1333
1255
|
id: event.id,
|
|
@@ -1342,7 +1264,7 @@ function codexItemLine(event, item) {
|
|
|
1342
1264
|
if (kind === "reasoning" || kind === "todo_list") return SKIP;
|
|
1343
1265
|
return { id: event.id, kind: "status", title: kind.replaceAll("_", " ") };
|
|
1344
1266
|
}
|
|
1345
|
-
function
|
|
1267
|
+
function clip(text, max) {
|
|
1346
1268
|
const flat = String(text ?? "").trim();
|
|
1347
1269
|
return flat.length <= max ? flat : `${flat.slice(0, Math.max(0, max - 3))}...`;
|
|
1348
1270
|
}
|
|
@@ -1415,6 +1337,185 @@ var init_transcript = __esm({
|
|
|
1415
1337
|
}
|
|
1416
1338
|
});
|
|
1417
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
|
+
|
|
1418
1519
|
// ../../packages/db/src/waves.ts
|
|
1419
1520
|
function ticketWaves(tickets) {
|
|
1420
1521
|
const ids = new Set(tickets.map((ticket) => ticket.id));
|
|
@@ -1467,7 +1568,9 @@ var init_src = __esm({
|
|
|
1467
1568
|
init_budgets();
|
|
1468
1569
|
init_cost();
|
|
1469
1570
|
init_commands();
|
|
1571
|
+
init_cockpit();
|
|
1470
1572
|
init_defaults();
|
|
1573
|
+
init_effective_config();
|
|
1471
1574
|
init_enums();
|
|
1472
1575
|
init_epic_progress();
|
|
1473
1576
|
init_filter();
|
|
@@ -1931,6 +2034,17 @@ var init_connection = __esm({
|
|
|
1931
2034
|
}
|
|
1932
2035
|
});
|
|
1933
2036
|
|
|
2037
|
+
// src/tui/capability.ts
|
|
2038
|
+
function canLaunchApp(streams = { stdin: process.stdin, stdout: process.stdout }) {
|
|
2039
|
+
return !isJsonMode() && streams.stdin.isTTY === true && streams.stdout.isTTY === true;
|
|
2040
|
+
}
|
|
2041
|
+
var init_capability = __esm({
|
|
2042
|
+
"src/tui/capability.ts"() {
|
|
2043
|
+
"use strict";
|
|
2044
|
+
init_json();
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
|
|
1934
2048
|
// src/architect/brain.ts
|
|
1935
2049
|
import { createInterface } from "readline/promises";
|
|
1936
2050
|
function brainById(id) {
|
|
@@ -1952,36 +2066,40 @@ async function confirm(question) {
|
|
|
1952
2066
|
async function requireBrain(opts = {}) {
|
|
1953
2067
|
const config = await loadConfig();
|
|
1954
2068
|
const chosen = config.brain ?? DEFAULT_BRAIN;
|
|
1955
|
-
const interactive = opts.interactive ?? (process.stdin.isTTY && process.stdout.isTTY);
|
|
2069
|
+
const interactive = opts.interactive ?? (opts.fatal !== false && process.stdin.isTTY && process.stdout.isTTY);
|
|
1956
2070
|
const connection = await codexConnection();
|
|
1957
2071
|
if (connection.state === "connected") {
|
|
1958
2072
|
if (!config.brain) await saveConfig({ brain: chosen });
|
|
1959
2073
|
return chosen;
|
|
1960
2074
|
}
|
|
1961
2075
|
if (!interactive) {
|
|
1962
|
-
|
|
1963
|
-
${howToFix(connection) ?? ""}`.trim());
|
|
2076
|
+
brainFailure(`${connectionSummary(connection)}
|
|
2077
|
+
${howToFix(connection) ?? ""}`.trim(), opts.fatal);
|
|
1964
2078
|
}
|
|
1965
2079
|
if (connection.state === "not_installed") {
|
|
1966
|
-
|
|
1967
|
-
${howToFix(connection) ?? ""}`.trim());
|
|
2080
|
+
brainFailure(`${connectionSummary(connection)}
|
|
2081
|
+
${howToFix(connection) ?? ""}`.trim(), opts.fatal);
|
|
1968
2082
|
}
|
|
1969
2083
|
const brain = brainById(chosen.id);
|
|
1970
2084
|
out("");
|
|
1971
2085
|
out(`${c.bold("HigherDEV needs your ChatGPT subscription to answer.")}`);
|
|
1972
2086
|
out(c.dim(`${brain?.billing ?? ""}. The sign-in happens in your browser; hd never sees the token.`));
|
|
1973
2087
|
if (!await confirm(c.dim("Press enter to sign in, or n to cancel: "))) {
|
|
1974
|
-
|
|
2088
|
+
brainFailure("Not connected. Run `hd brain` when you want to.", opts.fatal);
|
|
1975
2089
|
}
|
|
1976
2090
|
const connected = await connectCodex();
|
|
1977
2091
|
if (connected.state !== "connected") {
|
|
1978
|
-
|
|
1979
|
-
${howToFix(connected) ?? ""}`.trim());
|
|
2092
|
+
brainFailure(`${connectionSummary(connected)}
|
|
2093
|
+
${howToFix(connected) ?? ""}`.trim(), opts.fatal);
|
|
1980
2094
|
}
|
|
1981
2095
|
out(c.green(connectionSummary(connected)));
|
|
1982
2096
|
await saveConfig({ brain: chosen });
|
|
1983
2097
|
return chosen;
|
|
1984
2098
|
}
|
|
2099
|
+
function brainFailure(message, fatal = true) {
|
|
2100
|
+
if (fatal) fail(message);
|
|
2101
|
+
throw new Error(message);
|
|
2102
|
+
}
|
|
1985
2103
|
async function setBrain(patch) {
|
|
1986
2104
|
const config = await loadConfig();
|
|
1987
2105
|
const next = { ...config.brain ?? DEFAULT_BRAIN, ...patch };
|
|
@@ -2109,6 +2227,12 @@ async function loadRunEvents(ctx, runId, afterSeq = -1) {
|
|
|
2109
2227
|
if (error) fail(error.message);
|
|
2110
2228
|
return data ?? [];
|
|
2111
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
|
+
}
|
|
2112
2236
|
async function loadMessages(ctx, opts = {}) {
|
|
2113
2237
|
let query = ctx.db.from("messages").select("*").eq("workspace_id", ctx.workspace.id).order("created_at", { ascending: true }).limit(opts.limit ?? 200);
|
|
2114
2238
|
if (opts.ticketId) query = query.eq("ticket_id", opts.ticketId);
|
|
@@ -2469,9 +2593,9 @@ async function runAgent(opts) {
|
|
|
2469
2593
|
opts.onEvent?.({ kind: "tool", name: call.name, args });
|
|
2470
2594
|
const tool = byName.get(call.name);
|
|
2471
2595
|
let output;
|
|
2472
|
-
let
|
|
2596
|
+
let ok3 = true;
|
|
2473
2597
|
if (!tool) {
|
|
2474
|
-
|
|
2598
|
+
ok3 = false;
|
|
2475
2599
|
output = `Error: no tool named ${call.name}.`;
|
|
2476
2600
|
} else {
|
|
2477
2601
|
try {
|
|
@@ -2479,11 +2603,11 @@ async function runAgent(opts) {
|
|
|
2479
2603
|
output = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
2480
2604
|
if (!output) output = "(no result)";
|
|
2481
2605
|
} catch (error) {
|
|
2482
|
-
|
|
2606
|
+
ok3 = false;
|
|
2483
2607
|
output = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
2484
2608
|
}
|
|
2485
2609
|
}
|
|
2486
|
-
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) });
|
|
2487
2611
|
input.push({
|
|
2488
2612
|
type: "function_call",
|
|
2489
2613
|
name: call.name,
|
|
@@ -2686,7 +2810,96 @@ var init_group = __esm({
|
|
|
2686
2810
|
}
|
|
2687
2811
|
});
|
|
2688
2812
|
|
|
2689
|
-
// src/
|
|
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
|
+
|
|
2902
|
+
// src/out/format.ts
|
|
2690
2903
|
function truncate(text, max) {
|
|
2691
2904
|
const flat = String(text ?? "").replace(/\s+/g, " ").trim();
|
|
2692
2905
|
if (flat.length <= max) return flat;
|
|
@@ -3152,7 +3365,7 @@ function registerReadCommands(program) {
|
|
|
3152
3365
|
${statusView(board, pausedAll)}`
|
|
3153
3366
|
);
|
|
3154
3367
|
});
|
|
3155
|
-
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() {
|
|
3156
3369
|
const opts = this.opts();
|
|
3157
3370
|
const ctx = await requireWorkspace(slugOf(this));
|
|
3158
3371
|
const board = await loadBoard(ctx);
|
|
@@ -3201,7 +3414,7 @@ ${statusView(board, pausedAll)}`
|
|
|
3201
3414
|
const board = await loadBoard(ctx);
|
|
3202
3415
|
emit(board.hosts, () => hostsView(board.hosts));
|
|
3203
3416
|
});
|
|
3204
|
-
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() {
|
|
3205
3418
|
const ctx = await requireWorkspace(slugOf(this));
|
|
3206
3419
|
const days = Number(this.opts().days) || 14;
|
|
3207
3420
|
const [daily, tickets, epics] = await Promise.all([
|
|
@@ -3255,6 +3468,7 @@ var init_commands2 = __esm({
|
|
|
3255
3468
|
init_theme();
|
|
3256
3469
|
init_queries();
|
|
3257
3470
|
init_group();
|
|
3471
|
+
init_argv_parsers();
|
|
3258
3472
|
init_views();
|
|
3259
3473
|
}
|
|
3260
3474
|
});
|
|
@@ -3295,31 +3509,46 @@ async function postMessage(ctx, input) {
|
|
|
3295
3509
|
await ctx.audit(ctx.workspace.id, "message", { to, ticket_key: input.ticketKey });
|
|
3296
3510
|
return data;
|
|
3297
3511
|
}
|
|
3298
|
-
|
|
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 = {}) {
|
|
3299
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);
|
|
3300
|
-
if (loadError)
|
|
3522
|
+
if (loadError) return { ok: false, error: loadError.message };
|
|
3301
3523
|
const matches = (rows ?? []).filter((row) => row.id.startsWith(decisionId));
|
|
3302
|
-
if (matches.length === 0)
|
|
3303
|
-
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
|
+
}
|
|
3304
3528
|
const decision = matches[0];
|
|
3305
3529
|
if (opts.dismiss) {
|
|
3306
3530
|
const { error: error2 } = await ctx.db.from("decisions").update({ dismissed_at: (/* @__PURE__ */ new Date()).toISOString(), answered_by: ctx.email }).eq("id", decision.id);
|
|
3307
|
-
if (error2)
|
|
3531
|
+
if (error2) return { ok: false, error: error2.message };
|
|
3308
3532
|
await ctx.audit(ctx.workspace.id, "dismiss", { decision_id: decision.id });
|
|
3309
|
-
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." };
|
|
3310
3538
|
}
|
|
3311
|
-
const options = Array.isArray(decision.options) ? decision.options : [];
|
|
3312
|
-
const index = Number(answer);
|
|
3313
|
-
const resolved = options.length && Number.isInteger(index) && index >= 1 && index <= options.length ? options[index - 1] : answer;
|
|
3314
|
-
if (!resolved.trim()) fail("An answer needs text, or the number of an option.");
|
|
3315
3539
|
const { error } = await ctx.db.from("decisions").update({
|
|
3316
3540
|
answer_md: resolved,
|
|
3317
3541
|
answered_by: ctx.email,
|
|
3318
3542
|
answered_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3319
3543
|
}).eq("id", decision.id);
|
|
3320
|
-
if (error)
|
|
3544
|
+
if (error) return { ok: false, error: error.message };
|
|
3321
3545
|
await ctx.audit(ctx.workspace.id, "answer", { decision_id: decision.id });
|
|
3322
|
-
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 };
|
|
3323
3552
|
}
|
|
3324
3553
|
async function awaitReply(ctx, since, timeoutMs) {
|
|
3325
3554
|
return new Promise((resolve) => {
|
|
@@ -3348,7 +3577,7 @@ async function awaitReply(ctx, since, timeoutMs) {
|
|
|
3348
3577
|
});
|
|
3349
3578
|
}
|
|
3350
3579
|
function registerMessagingCommands(program) {
|
|
3351
|
-
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) {
|
|
3352
3581
|
const opts = this.opts();
|
|
3353
3582
|
const ctx = await requireWorkspace(slugOf2(this));
|
|
3354
3583
|
const looksLikeKey = key2 && /^HD-\d+$/i.test(key2);
|
|
@@ -3361,7 +3590,7 @@ function registerMessagingCommands(program) {
|
|
|
3361
3590
|
});
|
|
3362
3591
|
ok(`Sent to ${c.bold(opts.to)}${looksLikeKey ? ` on ${key2}` : ""}.`, { message });
|
|
3363
3592
|
});
|
|
3364
|
-
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) {
|
|
3365
3594
|
const opts = this.opts();
|
|
3366
3595
|
const ctx = await requireWorkspace(slugOf2(this));
|
|
3367
3596
|
const since = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3417,6 +3646,7 @@ var init_commands3 = __esm({
|
|
|
3417
3646
|
init_queries();
|
|
3418
3647
|
init_views();
|
|
3419
3648
|
init_subscribe();
|
|
3649
|
+
init_argv_parsers();
|
|
3420
3650
|
}
|
|
3421
3651
|
});
|
|
3422
3652
|
|
|
@@ -3481,14 +3711,31 @@ async function createTicket(ctx, input) {
|
|
|
3481
3711
|
await ctx.audit(ctx.workspace.id, "create", { ticket_key: data.key, subject: "a ticket" });
|
|
3482
3712
|
return data;
|
|
3483
3713
|
}
|
|
3484
|
-
async function
|
|
3714
|
+
async function applyOwnerCommand(ctx, ticket, operationId, intent) {
|
|
3715
|
+
const result = await applyOwnerTicketCommand(ctx.db, ownerTicketCommand({
|
|
3716
|
+
operationId,
|
|
3717
|
+
actorEmail: ctx.email,
|
|
3718
|
+
workspaceId: ctx.workspace.id,
|
|
3719
|
+
ticketId: ticket.id,
|
|
3720
|
+
expectedStatus: ticket.status,
|
|
3721
|
+
expectedVersion: ticket.lifecycle_version,
|
|
3722
|
+
intent
|
|
3723
|
+
}));
|
|
3724
|
+
if (result.outcome === "rejected") {
|
|
3725
|
+
fail(`Ticket command rejected: ${result.code}. Retry with a new --request-id after reloading the ticket.`);
|
|
3726
|
+
}
|
|
3727
|
+
const { data, error } = await ctx.db.from("tickets").select("*").eq("id", ticket.id).single();
|
|
3728
|
+
if (error) fail(error.message);
|
|
3729
|
+
return data;
|
|
3730
|
+
}
|
|
3731
|
+
async function updateTicket(ctx, ticket, patch, operationId) {
|
|
3485
3732
|
const fields = {};
|
|
3486
3733
|
if (patch.title !== void 0) fields.title = patch.title.trim();
|
|
3487
|
-
if (patch.body !== void 0) fields.
|
|
3488
|
-
if (patch.acceptance !== void 0) fields.
|
|
3734
|
+
if (patch.body !== void 0) fields.bodyMd = patch.body;
|
|
3735
|
+
if (patch.acceptance !== void 0) fields.acceptanceMd = patch.acceptance;
|
|
3489
3736
|
if (patch.area !== void 0) fields.area = patch.area;
|
|
3490
3737
|
if (patch.priority !== void 0) fields.priority = patch.priority;
|
|
3491
|
-
if (patch.epic !== void 0) fields.
|
|
3738
|
+
if (patch.epic !== void 0) fields.epicId = patch.epic;
|
|
3492
3739
|
if (patch.host !== void 0) {
|
|
3493
3740
|
const hostError = await assertHost(ctx.db, patch.host, ctx.workspace.repo);
|
|
3494
3741
|
if (hostError) fail(hostError);
|
|
@@ -3498,9 +3745,7 @@ async function updateTicket(ctx, ticket, patch) {
|
|
|
3498
3745
|
const agentId = await agentIdFor(ctx, patch.agent);
|
|
3499
3746
|
const assigned = await resolveAssignedBuilder(ctx.db, ctx.workspace.id, agentId, null);
|
|
3500
3747
|
if (!assigned.ok) fail(assigned.error);
|
|
3501
|
-
fields.
|
|
3502
|
-
fields.provider = assigned.agent?.provider ?? null;
|
|
3503
|
-
fields.provider_pinned = Boolean(assigned.agent);
|
|
3748
|
+
fields.agentId = assigned.agent?.id ?? null;
|
|
3504
3749
|
}
|
|
3505
3750
|
if (patch.status !== void 0) {
|
|
3506
3751
|
const wanted = patch.status.trim();
|
|
@@ -3509,19 +3754,19 @@ async function updateTicket(ctx, ticket, patch) {
|
|
|
3509
3754
|
}
|
|
3510
3755
|
const blockers = await blockerStatuses(ctx.db, ctx.workspace.id, ticket.blocked_by ?? [], ticket.id);
|
|
3511
3756
|
if (blockers.error) fail(blockers.error);
|
|
3512
|
-
fields.status = resolveTicketStatus(wanted, blockers.statuses);
|
|
3513
3757
|
}
|
|
3514
|
-
if (Object.keys(fields).length === 0) fail("Nothing to change.");
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3758
|
+
if (Object.keys(fields).length === 0 && patch.status === void 0) fail("Nothing to change.");
|
|
3759
|
+
if (Object.keys(fields).length === 0) fields.title = ticket.title;
|
|
3760
|
+
const requested = patch.status === void 0 ? ticket.status : patch.status.trim();
|
|
3761
|
+
return applyOwnerCommand(ctx, ticket, operationId, {
|
|
3762
|
+
type: "edit_ticket",
|
|
3763
|
+
fields,
|
|
3764
|
+
blockedBy: ticket.blocked_by ?? [],
|
|
3765
|
+
toStatus: requested,
|
|
3766
|
+
reason: `CLI owner edited ${ticket.key}`
|
|
3521
3767
|
});
|
|
3522
|
-
return data;
|
|
3523
3768
|
}
|
|
3524
|
-
async function setBlockedBy(ctx, ticket, keys, mode) {
|
|
3769
|
+
async function setBlockedBy(ctx, ticket, keys, mode, operationId) {
|
|
3525
3770
|
const ids = await keysToIds(ctx, keys);
|
|
3526
3771
|
const current = ticket.blocked_by ?? [];
|
|
3527
3772
|
const next = mode === "set" ? ids : mode === "add" ? [.../* @__PURE__ */ new Set([...current, ...ids])] : current.filter((id) => !ids.includes(id));
|
|
@@ -3532,23 +3777,22 @@ async function setBlockedBy(ctx, ticket, keys, mode) {
|
|
|
3532
3777
|
}
|
|
3533
3778
|
const blockers = await blockerStatuses(ctx.db, ctx.workspace.id, next, ticket.id);
|
|
3534
3779
|
if (blockers.error) fail(blockers.error);
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
subject: "dependencies"
|
|
3780
|
+
return applyOwnerCommand(ctx, ticket, operationId, {
|
|
3781
|
+
type: "set_dependencies",
|
|
3782
|
+
blockedBy: blockers.ids,
|
|
3783
|
+
toStatus: ticket.status,
|
|
3784
|
+
reason: `CLI owner changed dependencies for ${ticket.key}`
|
|
3541
3785
|
});
|
|
3542
|
-
return data;
|
|
3543
3786
|
}
|
|
3544
|
-
async function takeOver(ctx, ticket) {
|
|
3787
|
+
async function takeOver(ctx, ticket, operationId) {
|
|
3545
3788
|
if (ticket.human_owner) fail(`${ticket.key} is already owned by ${ticket.human_owner}.`);
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3789
|
+
return applyOwnerCommand(ctx, ticket, operationId, {
|
|
3790
|
+
type: "take_over",
|
|
3791
|
+
owner: ctx.email,
|
|
3792
|
+
reason: `CLI owner took over ${ticket.key}`
|
|
3793
|
+
});
|
|
3550
3794
|
}
|
|
3551
|
-
async function handBack(ctx, ticket) {
|
|
3795
|
+
async function handBack(ctx, ticket, operationId) {
|
|
3552
3796
|
if (!ticket.human_owner) fail(`${ticket.key} is not human-owned.`);
|
|
3553
3797
|
const blockers = await blockerStatuses(ctx.db, ctx.workspace.id, ticket.blocked_by ?? [], ticket.id);
|
|
3554
3798
|
if (blockers.error) fail(blockers.error);
|
|
@@ -3558,10 +3802,11 @@ async function handBack(ctx, ticket) {
|
|
|
3558
3802
|
assigned: Boolean(ticket.agent_id),
|
|
3559
3803
|
blockerStatuses: blockers.statuses
|
|
3560
3804
|
});
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3805
|
+
return applyOwnerCommand(ctx, ticket, operationId, {
|
|
3806
|
+
type: "hand_back",
|
|
3807
|
+
toStatus: status,
|
|
3808
|
+
reason: `CLI owner handed back ${ticket.key}`
|
|
3809
|
+
});
|
|
3565
3810
|
}
|
|
3566
3811
|
async function killRun(ctx, runId, ticketKey) {
|
|
3567
3812
|
const { data, error } = await ctx.db.rpc("request_run_cancellation", {
|
|
@@ -3656,6 +3901,160 @@ var init_epics = __esm({
|
|
|
3656
3901
|
}
|
|
3657
3902
|
});
|
|
3658
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
|
+
|
|
3659
4058
|
// src/write/editor.ts
|
|
3660
4059
|
import { spawn as spawn2 } from "child_process";
|
|
3661
4060
|
import { mkdtemp, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -3693,6 +4092,10 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
3693
4092
|
function slugOf3(command) {
|
|
3694
4093
|
return command.optsWithGlobals().workspace;
|
|
3695
4094
|
}
|
|
4095
|
+
function must(result) {
|
|
4096
|
+
if (!result.ok) fail(result.error);
|
|
4097
|
+
return result.value;
|
|
4098
|
+
}
|
|
3696
4099
|
async function setWorkspacePaused(ctx, paused) {
|
|
3697
4100
|
const { error } = await ctx.db.from("workspaces").update({ paused }).eq("id", ctx.workspace.id);
|
|
3698
4101
|
if (error) fail(error.message);
|
|
@@ -3717,7 +4120,7 @@ async function setProviderPaused(ctx, provider, paused) {
|
|
|
3717
4120
|
await ctx.audit(ctx.workspace.id, paused ? "pause" : "resume", { subject: provider });
|
|
3718
4121
|
}
|
|
3719
4122
|
function registerControlCommands(program) {
|
|
3720
|
-
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() {
|
|
3721
4124
|
const opts = this.opts();
|
|
3722
4125
|
if (opts.all) {
|
|
3723
4126
|
const ctx2 = await requireCtx();
|
|
@@ -3734,7 +4137,7 @@ function registerControlCommands(program) {
|
|
|
3734
4137
|
await setWorkspacePaused(ctx, true);
|
|
3735
4138
|
ok(c.yellow(`${ctx.workspace.slug} is paused.`));
|
|
3736
4139
|
});
|
|
3737
|
-
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() {
|
|
3738
4141
|
const opts = this.opts();
|
|
3739
4142
|
if (opts.all) {
|
|
3740
4143
|
const ctx2 = await requireCtx();
|
|
@@ -3787,7 +4190,7 @@ function registerAgentCommands(program) {
|
|
|
3787
4190
|
});
|
|
3788
4191
|
agent.command("show").argument("<name>", "agent display name").description("one agent in full").action(async function(name) {
|
|
3789
4192
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3790
|
-
const found = await findAgent(ctx, name);
|
|
4193
|
+
const found = must(await findAgent(ctx, name));
|
|
3791
4194
|
emit(
|
|
3792
4195
|
found,
|
|
3793
4196
|
() => [
|
|
@@ -3804,80 +4207,43 @@ ${found.prompt_addendum}` : ""
|
|
|
3804
4207
|
].filter(Boolean).join("\n")
|
|
3805
4208
|
);
|
|
3806
4209
|
});
|
|
3807
|
-
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) {
|
|
3808
4211
|
const opts = this.opts();
|
|
3809
4212
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3810
|
-
|
|
3811
|
-
const fields = {};
|
|
3812
|
-
if (opts.name) fields.display_name = String(opts.name).trim();
|
|
3813
|
-
if (opts.model) fields.model = String(opts.model).trim();
|
|
3814
|
-
if (opts.effort) fields.effort = asEffort(opts.effort);
|
|
3815
|
-
if (opts.provider) {
|
|
3816
|
-
if (!providers.includes(opts.provider)) {
|
|
3817
|
-
fail(`Unknown provider ${opts.provider}. One of: ${providers.join(", ")}.`);
|
|
3818
|
-
}
|
|
3819
|
-
fields.provider = opts.provider;
|
|
3820
|
-
}
|
|
3821
|
-
if (opts.notes !== void 0) fields.routing_notes = opts.notes;
|
|
3822
|
-
if (opts.prompt !== void 0) fields.prompt_addendum = opts.prompt;
|
|
4213
|
+
let prompt2 = opts.prompt;
|
|
3823
4214
|
if (opts.editPrompt) {
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
if (opts.enable) fields.enabled = true;
|
|
3827
|
-
if (opts.disable) fields.enabled = false;
|
|
3828
|
-
if (opts.runsPerHour !== void 0) {
|
|
3829
|
-
const cap = parseOptionalCap(opts.runsPerHour === "none" ? "" : opts.runsPerHour, {
|
|
3830
|
-
integer: true
|
|
3831
|
-
});
|
|
3832
|
-
if (!cap.ok) fail(cap.error);
|
|
3833
|
-
fields.runs_per_hour = cap.value;
|
|
4215
|
+
const found = must(await findAgent(ctx, name));
|
|
4216
|
+
prompt2 = (await editInEditor(found.prompt_addendum ?? "", "hd-prompt.md")).trim();
|
|
3834
4217
|
}
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
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
|
+
);
|
|
3848
4231
|
ok(`${c.bold(data.display_name)} updated.`, { agent: data });
|
|
3849
4232
|
});
|
|
3850
|
-
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) {
|
|
3851
4234
|
const opts = this.opts();
|
|
3852
4235
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
display_name: name.trim(),
|
|
3862
|
-
effort: asEffort(opts.effort),
|
|
3863
|
-
routing_notes: opts.notes ?? ""
|
|
3864
|
-
}).select("*").single();
|
|
3865
|
-
if (error) fail(error.message);
|
|
3866
|
-
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
|
+
);
|
|
3867
4244
|
ok(`${c.bold(data.display_name)} added.`, { agent: data });
|
|
3868
4245
|
});
|
|
3869
4246
|
}
|
|
3870
|
-
async function findAgent(ctx, name) {
|
|
3871
|
-
const { data, error } = await ctx.db.from("agents").select("*").eq("workspace_id", ctx.workspace.id);
|
|
3872
|
-
if (error) fail(error.message);
|
|
3873
|
-
const wanted = name.trim().toLowerCase();
|
|
3874
|
-
const matches = (data ?? []).filter(
|
|
3875
|
-
(row) => row.id === name || row.display_name.toLowerCase() === wanted || row.role.toLowerCase() === wanted
|
|
3876
|
-
);
|
|
3877
|
-
if (matches.length === 0) fail(`No agent named ${name}. Run \`hd agent ls\`.`);
|
|
3878
|
-
if (matches.length > 1) fail(`${name} matches ${matches.length} agents. Use a display name.`);
|
|
3879
|
-
return matches[0];
|
|
3880
|
-
}
|
|
3881
4247
|
function registerWorkspaceCommands(program) {
|
|
3882
4248
|
const workspace = program.command("workspace").description("workspaces and their settings");
|
|
3883
4249
|
workspace.command("ls", { isDefault: true }).description("every workspace").action(async function() {
|
|
@@ -3923,45 +4289,25 @@ function registerWorkspaceCommands(program) {
|
|
|
3923
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) {
|
|
3924
4290
|
const opts = this.opts();
|
|
3925
4291
|
const ctx = await requireCtx();
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
slug: slugify(name),
|
|
3930
|
-
repo,
|
|
3931
|
-
default_branch: opts.branch,
|
|
3932
|
-
default_host: opts.host
|
|
3933
|
-
}).select("*").single();
|
|
3934
|
-
if (error) fail(error.message);
|
|
3935
|
-
const { error: agentsError } = await ctx.db.from("agents").insert(DEFAULT_AGENTS.map((agent) => ({ ...agent, workspace_id: data.id })));
|
|
3936
|
-
if (agentsError) fail(`Workspace created, but seeding its agents failed: ${agentsError.message}`);
|
|
3937
|
-
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
|
+
);
|
|
3938
4295
|
ok(
|
|
3939
|
-
`${c.bold(
|
|
3940
|
-
{ workspace:
|
|
4296
|
+
`${c.bold(made.workspace.slug)} created with ${made.agents} agents. Run \`hd use ${made.workspace.slug}\`.`,
|
|
4297
|
+
{ workspace: made.workspace }
|
|
3941
4298
|
);
|
|
3942
4299
|
});
|
|
3943
|
-
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() {
|
|
3944
4301
|
const opts = this.opts();
|
|
3945
4302
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
3946
|
-
const
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
if (!providers.includes(provider)) fail(`Unknown provider ${provider}.`);
|
|
3955
|
-
const n = Number(value);
|
|
3956
|
-
if (!Number.isInteger(n) || n < 0) fail(`Cap for ${provider} must be a whole number.`);
|
|
3957
|
-
caps[provider] = n;
|
|
3958
|
-
}
|
|
3959
|
-
fields.provider_caps = caps;
|
|
3960
|
-
}
|
|
3961
|
-
if (Object.keys(fields).length === 0) fail("Nothing to change.");
|
|
3962
|
-
const { data, error } = await ctx.db.from("workspaces").update(fields).eq("id", ctx.workspace.id).select("*").single();
|
|
3963
|
-
if (error) fail(error.message);
|
|
3964
|
-
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
|
+
);
|
|
3965
4311
|
ok(`${c.bold(data.slug)} updated.`, { workspace: data });
|
|
3966
4312
|
});
|
|
3967
4313
|
workspace.command("templates").description("ticket templates for this workspace").action(async function() {
|
|
@@ -4051,7 +4397,7 @@ function registerAttachCommands(program) {
|
|
|
4051
4397
|
paths: uploaded
|
|
4052
4398
|
});
|
|
4053
4399
|
});
|
|
4054
|
-
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) {
|
|
4055
4401
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
4056
4402
|
const { data: ticket } = await ctx.db.from("tickets").select("id, key").eq("workspace_id", ctx.workspace.id).eq("key", key2.trim().toUpperCase()).maybeSingle();
|
|
4057
4403
|
if (!ticket) fail(`No ticket ${key2} in ${ctx.workspace.slug}.`);
|
|
@@ -4074,17 +4420,20 @@ var init_commands4 = __esm({
|
|
|
4074
4420
|
"use strict";
|
|
4075
4421
|
init_src();
|
|
4076
4422
|
init_context();
|
|
4423
|
+
init_config2();
|
|
4077
4424
|
init_json();
|
|
4078
4425
|
init_theme();
|
|
4079
4426
|
init_format();
|
|
4080
4427
|
init_queries();
|
|
4081
4428
|
init_views();
|
|
4082
4429
|
init_editor();
|
|
4430
|
+
init_argv_parsers();
|
|
4083
4431
|
DEFAULT_CAPS = { claude: 2, codex: 3, gemini: 1, grok: 2 };
|
|
4084
4432
|
}
|
|
4085
4433
|
});
|
|
4086
4434
|
|
|
4087
4435
|
// src/architect/tools.ts
|
|
4436
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4088
4437
|
import { z as z2 } from "zod";
|
|
4089
4438
|
async function ticketOf(ctx, wanted) {
|
|
4090
4439
|
const board = await loadBoard(ctx);
|
|
@@ -4302,10 +4651,12 @@ var init_tools = __esm({
|
|
|
4302
4651
|
agent: z2.string().nullable().optional(),
|
|
4303
4652
|
priority: z2.number().optional(),
|
|
4304
4653
|
status: z2.string().optional(),
|
|
4305
|
-
epic_id: z2.string().nullable().optional()
|
|
4654
|
+
epic_id: z2.string().nullable().optional(),
|
|
4655
|
+
request_id: z2.string().optional()
|
|
4306
4656
|
}),
|
|
4307
4657
|
async run(ctx, args) {
|
|
4308
4658
|
const ticket = await ticketOf(ctx, String(args.key));
|
|
4659
|
+
const operationId = String(args.request_id ?? randomUUID3());
|
|
4309
4660
|
const updated = await updateTicket(ctx, ticket, {
|
|
4310
4661
|
title: args.title,
|
|
4311
4662
|
body: args.body_md,
|
|
@@ -4315,8 +4666,8 @@ var init_tools = __esm({
|
|
|
4315
4666
|
priority: args.priority,
|
|
4316
4667
|
status: args.status,
|
|
4317
4668
|
epic: args.epic_id
|
|
4318
|
-
});
|
|
4319
|
-
return { key: updated.key, status: updated.status };
|
|
4669
|
+
}, operationId);
|
|
4670
|
+
return { key: updated.key, status: updated.status, request_id: operationId };
|
|
4320
4671
|
}
|
|
4321
4672
|
},
|
|
4322
4673
|
{
|
|
@@ -4326,17 +4677,20 @@ var init_tools = __esm({
|
|
|
4326
4677
|
schema: z2.object({
|
|
4327
4678
|
key,
|
|
4328
4679
|
blocked_by: z2.array(z2.string()).describe("Ticket keys."),
|
|
4329
|
-
mode: z2.enum(["set", "add", "remove"]).optional()
|
|
4680
|
+
mode: z2.enum(["set", "add", "remove"]).optional(),
|
|
4681
|
+
request_id: z2.string().optional()
|
|
4330
4682
|
}),
|
|
4331
4683
|
async run(ctx, args) {
|
|
4332
4684
|
const ticket = await ticketOf(ctx, String(args.key));
|
|
4685
|
+
const operationId = String(args.request_id ?? randomUUID3());
|
|
4333
4686
|
const updated = await setBlockedBy(
|
|
4334
4687
|
ctx,
|
|
4335
4688
|
ticket,
|
|
4336
4689
|
args.blocked_by ?? [],
|
|
4337
|
-
args.mode ?? "set"
|
|
4690
|
+
args.mode ?? "set",
|
|
4691
|
+
operationId
|
|
4338
4692
|
);
|
|
4339
|
-
return { key: updated.key, status: updated.status, blocked_by: updated.blocked_by };
|
|
4693
|
+
return { key: updated.key, status: updated.status, blocked_by: updated.blocked_by, request_id: operationId };
|
|
4340
4694
|
}
|
|
4341
4695
|
},
|
|
4342
4696
|
{
|
|
@@ -4421,20 +4775,22 @@ var init_tools = __esm({
|
|
|
4421
4775
|
name: "take_over_ticket",
|
|
4422
4776
|
access: "write",
|
|
4423
4777
|
description: "Mark a ticket human-owned so the board stops dispatching it.",
|
|
4424
|
-
schema: z2.object({ key }),
|
|
4778
|
+
schema: z2.object({ key, request_id: z2.string().optional() }),
|
|
4425
4779
|
async run(ctx, args) {
|
|
4426
|
-
const
|
|
4427
|
-
|
|
4780
|
+
const operationId = String(args.request_id ?? randomUUID3());
|
|
4781
|
+
const updated = await takeOver(ctx, await ticketOf(ctx, String(args.key)), operationId);
|
|
4782
|
+
return { key: updated.key, human_owner: updated.human_owner, request_id: operationId };
|
|
4428
4783
|
}
|
|
4429
4784
|
},
|
|
4430
4785
|
{
|
|
4431
4786
|
name: "hand_back_ticket",
|
|
4432
4787
|
access: "write",
|
|
4433
4788
|
description: "Give a human-owned ticket back to the board.",
|
|
4434
|
-
schema: z2.object({ key }),
|
|
4789
|
+
schema: z2.object({ key, request_id: z2.string().optional() }),
|
|
4435
4790
|
async run(ctx, args) {
|
|
4436
|
-
const
|
|
4437
|
-
|
|
4791
|
+
const operationId = String(args.request_id ?? randomUUID3());
|
|
4792
|
+
const updated = await handBack(ctx, await ticketOf(ctx, String(args.key)), operationId);
|
|
4793
|
+
return { key: updated.key, status: updated.status, request_id: operationId };
|
|
4438
4794
|
}
|
|
4439
4795
|
},
|
|
4440
4796
|
{
|
|
@@ -4604,7 +4960,8 @@ function architectInstructions(opts) {
|
|
|
4604
4960
|
}
|
|
4605
4961
|
async function openArchitect(opts) {
|
|
4606
4962
|
const { ctx } = opts;
|
|
4607
|
-
const
|
|
4963
|
+
const fatalAuth = opts.fatalAuth ?? true;
|
|
4964
|
+
const brain = await requireBrain({ fatal: fatalAuth });
|
|
4608
4965
|
const repo = await repoCheckout(ctx);
|
|
4609
4966
|
const platformTools = toolsFor(opts.readOnly ? "read" : "all").map((tool) => ({
|
|
4610
4967
|
name: tool.name,
|
|
@@ -4622,7 +4979,8 @@ async function openArchitect(opts) {
|
|
|
4622
4979
|
tools: [...platformTools, ...buildRepoTools(repo)],
|
|
4623
4980
|
sessionId: newSessionId(),
|
|
4624
4981
|
history: [],
|
|
4625
|
-
repo
|
|
4982
|
+
repo,
|
|
4983
|
+
fatalAuth
|
|
4626
4984
|
};
|
|
4627
4985
|
}
|
|
4628
4986
|
async function architectTurn(opts) {
|
|
@@ -4644,7 +5002,7 @@ async function architectTurn(opts) {
|
|
|
4644
5002
|
if (runError) fail(runError.message);
|
|
4645
5003
|
let seq = 0;
|
|
4646
5004
|
const events = [];
|
|
4647
|
-
const
|
|
5005
|
+
const record2 = (type, payload) => events.push({ run_id: runRow.id, seq: seq++, type, payload });
|
|
4648
5006
|
const flush = async () => {
|
|
4649
5007
|
if (events.length) await ctx.db.from("run_events").insert(events);
|
|
4650
5008
|
};
|
|
@@ -4658,14 +5016,14 @@ async function architectTurn(opts) {
|
|
|
4658
5016
|
sessionId: session.sessionId,
|
|
4659
5017
|
signal: opts.signal,
|
|
4660
5018
|
onEvent: (event) => {
|
|
4661
|
-
if (event.kind === "tool")
|
|
5019
|
+
if (event.kind === "tool") record2("tool_use", { name: event.name, input: event.args });
|
|
4662
5020
|
else if (event.kind === "tool_result") {
|
|
4663
|
-
|
|
5021
|
+
record2("tool_result", { name: event.name, ok: event.ok, content: event.detail });
|
|
4664
5022
|
}
|
|
4665
5023
|
opts.onEvent(event);
|
|
4666
5024
|
}
|
|
4667
5025
|
});
|
|
4668
|
-
if (result.text)
|
|
5026
|
+
if (result.text) record2("text", { text: result.text });
|
|
4669
5027
|
session.history.push({ role: "user", content: opts.request });
|
|
4670
5028
|
session.history.push({ role: "assistant", content: result.text });
|
|
4671
5029
|
await flush();
|
|
@@ -4680,7 +5038,7 @@ async function architectTurn(opts) {
|
|
|
4680
5038
|
return { text: result.text, runId: runRow.id, toolCalls: result.toolCalls };
|
|
4681
5039
|
} catch (error) {
|
|
4682
5040
|
const message = error instanceof Error ? error.message : String(error);
|
|
4683
|
-
|
|
5041
|
+
record2("error", { error: message });
|
|
4684
5042
|
await flush();
|
|
4685
5043
|
await ctx.db.from("runs").update({
|
|
4686
5044
|
status: "failed",
|
|
@@ -4688,7 +5046,7 @@ async function architectTurn(opts) {
|
|
|
4688
5046
|
exit_code: 1,
|
|
4689
5047
|
summary: message.slice(0, 500)
|
|
4690
5048
|
}).eq("id", runRow.id);
|
|
4691
|
-
if (error instanceof ChatGptAuthError) fail(message);
|
|
5049
|
+
if (error instanceof ChatGptAuthError && session.fatalAuth) fail(message);
|
|
4692
5050
|
throw error;
|
|
4693
5051
|
}
|
|
4694
5052
|
}
|
|
@@ -4759,297 +5117,1335 @@ var init_theme2 = __esm({
|
|
|
4759
5117
|
});
|
|
4760
5118
|
|
|
4761
5119
|
// src/tui/Banner.tsx
|
|
4762
|
-
import { useEffect, useState } from "react";
|
|
4763
|
-
import { Box, Text } from "ink";
|
|
5120
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5121
|
+
import { Box, Text, useStdout } from "ink";
|
|
4764
5122
|
import { jsx } from "react/jsx-runtime";
|
|
4765
|
-
function
|
|
4766
|
-
|
|
4767
|
-
|
|
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) => {
|
|
4768
5132
|
const glyph = GLYPHS[letter];
|
|
4769
|
-
if (!glyph) continue;
|
|
4770
5133
|
const width = Math.max(...glyph.map((row) => row.length));
|
|
4771
|
-
|
|
4772
|
-
|
|
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];
|
|
4773
5157
|
}
|
|
5158
|
+
rows.push(row);
|
|
4774
5159
|
}
|
|
4775
|
-
return rows
|
|
5160
|
+
return rows;
|
|
4776
5161
|
}
|
|
4777
|
-
function
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
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) });
|
|
4783
5187
|
}
|
|
4784
|
-
|
|
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;
|
|
4785
5216
|
onDone?.();
|
|
4786
|
-
return;
|
|
4787
5217
|
}
|
|
4788
|
-
|
|
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);
|
|
4789
5222
|
return () => clearTimeout(timer);
|
|
4790
|
-
}, [
|
|
4791
|
-
const
|
|
4792
|
-
const
|
|
4793
|
-
|
|
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)) });
|
|
4794
5228
|
}
|
|
4795
|
-
var GLYPHS,
|
|
5229
|
+
var WORD, GLYPHS, PIXEL_ROWS, BANNER_WIDTH, BANNER_HEIGHT, HALF, LEVELS, SETTLED, STORM_MS;
|
|
4796
5230
|
var init_Banner = __esm({
|
|
4797
5231
|
"src/tui/Banner.tsx"() {
|
|
4798
5232
|
"use strict";
|
|
4799
5233
|
init_theme2();
|
|
5234
|
+
WORD = "HigherDEV";
|
|
4800
5235
|
GLYPHS = {
|
|
4801
|
-
H: [
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
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
|
+
" ": ["..", "..", "..", "..", "..", "..", "..", "..", "..", "..", "..", ".."]
|
|
4811
5350
|
};
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
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 }
|
|
4822
5369
|
];
|
|
4823
|
-
|
|
5370
|
+
SETTLED = { color: UI.text, dimColor: false, bold: false };
|
|
5371
|
+
STORM_MS = 24e4;
|
|
4824
5372
|
}
|
|
4825
5373
|
});
|
|
4826
5374
|
|
|
4827
|
-
// 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
|
|
4828
5416
|
import "react";
|
|
4829
5417
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
4830
5418
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
4831
|
-
function
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
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(
|
|
4835
5424
|
Box2,
|
|
4836
5425
|
{
|
|
4837
|
-
borderStyle:
|
|
4838
|
-
borderColor:
|
|
4839
|
-
...style.backgroundColor ? { backgroundColor: style.backgroundColor } : {},
|
|
5426
|
+
borderStyle: "single",
|
|
5427
|
+
borderColor: UI.text,
|
|
4840
5428
|
flexDirection: "column",
|
|
4841
|
-
paddingX:
|
|
5429
|
+
paddingX: 2,
|
|
5430
|
+
width,
|
|
4842
5431
|
children: [
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
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 })
|
|
4846
5441
|
]
|
|
4847
5442
|
}
|
|
4848
|
-
)
|
|
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");
|
|
4849
6244
|
}
|
|
4850
|
-
var
|
|
4851
|
-
|
|
6245
|
+
var DOT3;
|
|
6246
|
+
var init_Panels = __esm({
|
|
6247
|
+
"src/tui/Panels.tsx"() {
|
|
4852
6248
|
"use strict";
|
|
6249
|
+
init_src();
|
|
6250
|
+
init_format();
|
|
6251
|
+
init_theme();
|
|
4853
6252
|
init_theme2();
|
|
6253
|
+
init_bounded();
|
|
6254
|
+
DOT3 = "\u25CF";
|
|
4854
6255
|
}
|
|
4855
6256
|
});
|
|
4856
6257
|
|
|
4857
|
-
// src/tui/
|
|
6258
|
+
// src/tui/Decision.tsx
|
|
4858
6259
|
import "react";
|
|
4859
|
-
import { Box as
|
|
4860
|
-
import { jsx as
|
|
4861
|
-
function
|
|
4862
|
-
|
|
4863
|
-
|
|
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,
|
|
4864
6288
|
{
|
|
4865
6289
|
borderStyle: "single",
|
|
4866
|
-
borderColor: UI.
|
|
6290
|
+
borderColor: UI.warn,
|
|
4867
6291
|
flexDirection: "column",
|
|
4868
|
-
paddingX:
|
|
4869
|
-
paddingY: 1,
|
|
6292
|
+
paddingX: 1,
|
|
4870
6293
|
width,
|
|
6294
|
+
marginBottom: 1,
|
|
4871
6295
|
children: [
|
|
4872
|
-
/* @__PURE__ */
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
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
|
+
")"
|
|
4878
6309
|
] }) }),
|
|
4879
|
-
/* @__PURE__ */
|
|
4880
|
-
] },
|
|
4881
|
-
/* @__PURE__ */
|
|
4882
|
-
/* @__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" })
|
|
4883
6313
|
]
|
|
4884
6314
|
}
|
|
4885
6315
|
);
|
|
4886
6316
|
}
|
|
4887
|
-
var
|
|
4888
|
-
var
|
|
4889
|
-
"src/tui/
|
|
6317
|
+
var DECISION_CHROME;
|
|
6318
|
+
var init_Decision = __esm({
|
|
6319
|
+
"src/tui/Decision.tsx"() {
|
|
4890
6320
|
"use strict";
|
|
6321
|
+
init_format();
|
|
6322
|
+
init_Panels();
|
|
6323
|
+
init_commands3();
|
|
4891
6324
|
init_theme2();
|
|
4892
|
-
|
|
4893
|
-
{ name: "/architect", help: "talk to your own model, which can do anything in the platform" },
|
|
4894
|
-
{ name: "/orchestrator", help: "talk to the agent that gets work in flight finished" },
|
|
4895
|
-
{ name: "/browse", help: "stop talking, look around" },
|
|
4896
|
-
{ name: "/workspace", args: "[slug]", help: "switch workspace, or list the ones you can reach" },
|
|
4897
|
-
{ name: "/board", help: "the kanban board" },
|
|
4898
|
-
{ name: "/agents", help: "every agent and what it is doing" },
|
|
4899
|
-
{ name: "/feed", help: "what just happened" },
|
|
4900
|
-
{ name: "/inbox", help: "decisions and messages waiting on you" },
|
|
4901
|
-
{ name: "/ticket", args: "HD-12", help: "open one ticket" },
|
|
4902
|
-
{ name: "/refresh", help: "reload the board now" },
|
|
4903
|
-
{ name: "/help", help: "this list" },
|
|
4904
|
-
{ name: "/exit", help: "leave" }
|
|
4905
|
-
];
|
|
6325
|
+
DECISION_CHROME = 5;
|
|
4906
6326
|
}
|
|
4907
6327
|
});
|
|
4908
6328
|
|
|
4909
|
-
// src/tui/
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
return
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
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;
|
|
4941
6364
|
}
|
|
4942
|
-
function
|
|
4943
|
-
|
|
4944
|
-
|
|
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
|
|
4945
6372
|
);
|
|
4946
|
-
|
|
4947
|
-
return /* @__PURE__ */ jsx4(Panel, { title: "Board", children: /* @__PURE__ */ jsx4(Text4, { color: UI.dim, children: "No tickets yet." }) });
|
|
4948
|
-
}
|
|
4949
|
-
return /* @__PURE__ */ jsx4(Panel, { title: "Board", children: active.map((status) => {
|
|
4950
|
-
const rows = board.tickets.filter((ticket) => ticket.status === status);
|
|
4951
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
4952
|
-
/* @__PURE__ */ jsxs3(Text4, { color: inkColor(statusTone(status)), children: [
|
|
4953
|
-
statusLabel(status),
|
|
4954
|
-
" ",
|
|
4955
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.dim, children: [
|
|
4956
|
-
"(",
|
|
4957
|
-
rows.length,
|
|
4958
|
-
")"
|
|
4959
|
-
] })
|
|
4960
|
-
] }),
|
|
4961
|
-
rows.map((ticket) => /* @__PURE__ */ jsx4(TicketLine, { ticket, selected: ticket.id === cursor }, ticket.id))
|
|
4962
|
-
] }, status);
|
|
4963
|
-
}) });
|
|
4964
|
-
}
|
|
4965
|
-
function TicketLine({ ticket, selected }) {
|
|
4966
|
-
return /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
4967
|
-
/* @__PURE__ */ jsx4(Text4, { color: selected ? UI.accent : UI.dim, children: selected ? " > " : " " }),
|
|
4968
|
-
/* @__PURE__ */ jsx4(Box4, { width: 8, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, bold: true, children: ticket.key }) }),
|
|
4969
|
-
/* @__PURE__ */ jsx4(Box4, { width: 44, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, children: truncate(ticket.title, 43) }) }),
|
|
4970
|
-
/* @__PURE__ */ jsx4(Box4, { width: 14, children: /* @__PURE__ */ jsx4(Text4, { color: UI.dim, children: ticket.agent_name ?? "" }) }),
|
|
4971
|
-
ticket.stuck ? /* @__PURE__ */ jsx4(Text4, { color: UI.warn, children: truncate(ticket.stuck, 30) }) : null
|
|
4972
|
-
] });
|
|
6373
|
+
return next.length > limit ? next.slice(next.length - limit) : next;
|
|
4973
6374
|
}
|
|
4974
|
-
function
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
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));
|
|
4985
6388
|
}
|
|
4986
|
-
function
|
|
4987
|
-
|
|
4988
|
-
|
|
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
|
+
});
|
|
4989
6400
|
}
|
|
4990
|
-
return
|
|
4991
|
-
const ticket = board.tickets.find((row) => row.id === decision.ticket_id);
|
|
4992
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
4993
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.warn, children: [
|
|
4994
|
-
decision.id.slice(0, 8),
|
|
4995
|
-
ticket ? ` ${ticket.key}` : ""
|
|
4996
|
-
] }),
|
|
4997
|
-
/* @__PURE__ */ jsx4(Text4, { color: UI.text, children: truncate(decision.question_md, 100) })
|
|
4998
|
-
] }, decision.id);
|
|
4999
|
-
}) });
|
|
5000
|
-
}
|
|
5001
|
-
function EpicsPanel({ board }) {
|
|
5002
|
-
const rows = epicProgress(board).filter((row) => row.epic.status !== "done");
|
|
5003
|
-
if (rows.length === 0) return null;
|
|
5004
|
-
return /* @__PURE__ */ jsx4(Panel, { title: "Epics", children: rows.map((row) => /* @__PURE__ */ jsxs3(Box4, { children: [
|
|
5005
|
-
/* @__PURE__ */ jsx4(Box4, { width: 40, children: /* @__PURE__ */ jsx4(Text4, { color: UI.text, children: truncate(row.epic.title, 39) }) }),
|
|
5006
|
-
/* @__PURE__ */ jsx4(Text4, { color: UI.dim, children: epicProgressCaption(row.merged, row.total, row.cancelled) })
|
|
5007
|
-
] }, row.epic.id)) });
|
|
5008
|
-
}
|
|
5009
|
-
function TicketPanel({ ticket }) {
|
|
5010
|
-
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginBottom: 1, children: [
|
|
5011
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.text, bold: true, children: [
|
|
5012
|
-
ticket.key,
|
|
5013
|
-
" ",
|
|
5014
|
-
ticket.title
|
|
5015
|
-
] }),
|
|
5016
|
-
/* @__PURE__ */ jsxs3(Box4, { children: [
|
|
5017
|
-
/* @__PURE__ */ jsx4(Text4, { color: inkColor(statusTone(ticket.status)), children: statusLabel(ticket.status) }),
|
|
5018
|
-
/* @__PURE__ */ jsxs3(Text4, { color: UI.dim, children: [
|
|
5019
|
-
ticket.area ? ` ${ticket.area}` : "",
|
|
5020
|
-
ticket.agent_name ? ` ${ticket.agent_name}` : " unassigned",
|
|
5021
|
-
ticket.attempts ? ` attempt ${ticket.attempts}` : ""
|
|
5022
|
-
] })
|
|
5023
|
-
] }),
|
|
5024
|
-
ticket.stuck ? /* @__PURE__ */ jsxs3(Text4, { color: UI.warn, children: [
|
|
5025
|
-
"why: ",
|
|
5026
|
-
ticket.stuck
|
|
5027
|
-
] }) : null,
|
|
5028
|
-
ticket.blocker_keys.length ? /* @__PURE__ */ jsxs3(Text4, { color: UI.dim, children: [
|
|
5029
|
-
"blocked by ",
|
|
5030
|
-
ticket.blocker_keys.join(", ")
|
|
5031
|
-
] }) : null,
|
|
5032
|
-
ticket.pr_url ? /* @__PURE__ */ jsx4(Text4, { color: UI.accent, children: ticket.pr_url }) : null,
|
|
5033
|
-
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
|
|
5034
|
-
] });
|
|
6401
|
+
return map;
|
|
5035
6402
|
}
|
|
5036
|
-
var
|
|
5037
|
-
var
|
|
5038
|
-
"src/tui/
|
|
6403
|
+
var STREAM_LIMIT;
|
|
6404
|
+
var init_stream = __esm({
|
|
6405
|
+
"src/tui/stream.ts"() {
|
|
5039
6406
|
"use strict";
|
|
5040
6407
|
init_src();
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
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";
|
|
5044
6438
|
init_theme2();
|
|
5045
|
-
DOT2 = "\u25CF";
|
|
5046
6439
|
}
|
|
5047
6440
|
});
|
|
5048
6441
|
|
|
5049
6442
|
// src/tui/TextInput.tsx
|
|
5050
|
-
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
5051
|
-
import { Text as
|
|
5052
|
-
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
|
+
}
|
|
5053
6449
|
function TextInput({
|
|
5054
6450
|
value,
|
|
5055
6451
|
onChange,
|
|
@@ -5063,12 +6459,19 @@ function TextInput({
|
|
|
5063
6459
|
color
|
|
5064
6460
|
}) {
|
|
5065
6461
|
const [cursor, setCursor] = useState2(value.length);
|
|
6462
|
+
const ours = useRef2(value);
|
|
5066
6463
|
useEffect2(() => {
|
|
5067
|
-
|
|
6464
|
+
if (value === ours.current) return;
|
|
6465
|
+
ours.current = value;
|
|
6466
|
+
setCursor(value.length);
|
|
5068
6467
|
}, [value]);
|
|
6468
|
+
const change = (next) => {
|
|
6469
|
+
ours.current = next;
|
|
6470
|
+
onChange(next);
|
|
6471
|
+
};
|
|
5069
6472
|
const insert = (text) => {
|
|
5070
6473
|
const next = value.slice(0, cursor) + text + value.slice(cursor);
|
|
5071
|
-
|
|
6474
|
+
change(next);
|
|
5072
6475
|
setCursor(cursor + text.length);
|
|
5073
6476
|
};
|
|
5074
6477
|
usePaste((text) => {
|
|
@@ -5080,18 +6483,31 @@ function TextInput({
|
|
|
5080
6483
|
if (key2.escape) return onCancel?.();
|
|
5081
6484
|
if (key2.upArrow) return onUp?.();
|
|
5082
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
|
+
}
|
|
5083
6499
|
if (key2.leftArrow) return setCursor((c2) => Math.max(0, c2 - 1));
|
|
5084
6500
|
if (key2.rightArrow) return setCursor((c2) => Math.min(value.length, c2 + 1));
|
|
5085
6501
|
if (key2.home) return setCursor(0);
|
|
5086
6502
|
if (key2.end) return setCursor(value.length);
|
|
5087
6503
|
if (key2.backspace) {
|
|
5088
6504
|
if (cursor === 0) return;
|
|
5089
|
-
|
|
6505
|
+
change(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
5090
6506
|
return setCursor(cursor - 1);
|
|
5091
6507
|
}
|
|
5092
6508
|
if (key2.delete) {
|
|
5093
6509
|
if (cursor >= value.length) return;
|
|
5094
|
-
return
|
|
6510
|
+
return change(value.slice(0, cursor) + value.slice(cursor + 1));
|
|
5095
6511
|
}
|
|
5096
6512
|
if (key2.ctrl) {
|
|
5097
6513
|
switch (input) {
|
|
@@ -5100,14 +6516,14 @@ function TextInput({
|
|
|
5100
6516
|
case "e":
|
|
5101
6517
|
return setCursor(value.length);
|
|
5102
6518
|
case "u":
|
|
5103
|
-
|
|
6519
|
+
change(value.slice(cursor));
|
|
5104
6520
|
return setCursor(0);
|
|
5105
6521
|
case "k":
|
|
5106
|
-
return
|
|
6522
|
+
return change(value.slice(0, cursor));
|
|
5107
6523
|
case "w": {
|
|
5108
6524
|
const head = value.slice(0, cursor);
|
|
5109
6525
|
const trimmed = head.replace(/\s*\S*$/, "");
|
|
5110
|
-
|
|
6526
|
+
change(trimmed + value.slice(cursor));
|
|
5111
6527
|
return setCursor(trimmed.length);
|
|
5112
6528
|
}
|
|
5113
6529
|
default:
|
|
@@ -5115,7 +6531,7 @@ function TextInput({
|
|
|
5115
6531
|
}
|
|
5116
6532
|
}
|
|
5117
6533
|
if (key2.meta || !input) return;
|
|
5118
|
-
const printable = input
|
|
6534
|
+
const printable = printableOf(input);
|
|
5119
6535
|
if (printable) insert(printable);
|
|
5120
6536
|
},
|
|
5121
6537
|
{ isActive }
|
|
@@ -5124,15 +6540,15 @@ function TextInput({
|
|
|
5124
6540
|
const before = value.slice(0, cursor);
|
|
5125
6541
|
const at = value[cursor] ?? " ";
|
|
5126
6542
|
const after = value.slice(cursor + 1);
|
|
5127
|
-
return /* @__PURE__ */
|
|
6543
|
+
return /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
5128
6544
|
prompt2,
|
|
5129
|
-
showPlaceholder ? /* @__PURE__ */
|
|
5130
|
-
isActive ? /* @__PURE__ */
|
|
5131
|
-
/* @__PURE__ */
|
|
5132
|
-
] }) : /* @__PURE__ */
|
|
5133
|
-
/* @__PURE__ */
|
|
5134
|
-
isActive ? /* @__PURE__ */
|
|
5135
|
-
/* @__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 })
|
|
5136
6552
|
] })
|
|
5137
6553
|
] });
|
|
5138
6554
|
}
|
|
@@ -5161,13 +6577,42 @@ function parseLine(raw) {
|
|
|
5161
6577
|
case "agents":
|
|
5162
6578
|
case "feed":
|
|
5163
6579
|
case "inbox":
|
|
5164
|
-
case "
|
|
6580
|
+
case "settings":
|
|
5165
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
|
+
}
|
|
5166
6594
|
case "workspace":
|
|
5167
|
-
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
|
+
}
|
|
5168
6604
|
return { kind: "workspace", slug: argument || null };
|
|
6605
|
+
}
|
|
5169
6606
|
case "ticket":
|
|
5170
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
|
+
}
|
|
5171
6616
|
case "refresh":
|
|
5172
6617
|
return { kind: "refresh" };
|
|
5173
6618
|
case "exit":
|
|
@@ -5183,15 +6628,40 @@ var init_parse = __esm({
|
|
|
5183
6628
|
}
|
|
5184
6629
|
});
|
|
5185
6630
|
|
|
6631
|
+
// src/tui/workspace-load.ts
|
|
6632
|
+
var WorkspaceLoads;
|
|
6633
|
+
var init_workspace_load = __esm({
|
|
6634
|
+
"src/tui/workspace-load.ts"() {
|
|
6635
|
+
"use strict";
|
|
6636
|
+
WorkspaceLoads = class {
|
|
6637
|
+
#workspaceId;
|
|
6638
|
+
#generation = 0;
|
|
6639
|
+
constructor(workspaceId) {
|
|
6640
|
+
this.#workspaceId = workspaceId;
|
|
6641
|
+
}
|
|
6642
|
+
start(workspaceId) {
|
|
6643
|
+
return { workspaceId, generation: this.#generation };
|
|
6644
|
+
}
|
|
6645
|
+
switchTo(workspaceId) {
|
|
6646
|
+
this.#workspaceId = workspaceId;
|
|
6647
|
+
this.#generation += 1;
|
|
6648
|
+
}
|
|
6649
|
+
isCurrent(token) {
|
|
6650
|
+
return token.workspaceId === this.#workspaceId && token.generation === this.#generation;
|
|
6651
|
+
}
|
|
6652
|
+
};
|
|
6653
|
+
}
|
|
6654
|
+
});
|
|
6655
|
+
|
|
5186
6656
|
// src/tui/App.tsx
|
|
5187
6657
|
var App_exports = {};
|
|
5188
6658
|
__export(App_exports, {
|
|
5189
6659
|
App: () => App,
|
|
5190
6660
|
COMMANDS: () => COMMANDS
|
|
5191
6661
|
});
|
|
5192
|
-
import { useCallback, useEffect as useEffect3, useMemo, useRef, useState as useState3 } from "react";
|
|
5193
|
-
import { Box as
|
|
5194
|
-
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";
|
|
5195
6665
|
function App({
|
|
5196
6666
|
ctx,
|
|
5197
6667
|
workspace,
|
|
@@ -5199,8 +6669,9 @@ function App({
|
|
|
5199
6669
|
brainLabel
|
|
5200
6670
|
}) {
|
|
5201
6671
|
const { exit } = useApp();
|
|
5202
|
-
const { stdout } =
|
|
6672
|
+
const { stdout } = useStdout2();
|
|
5203
6673
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
6674
|
+
const rows = stdout?.rows && stdout.rows > 0 ? stdout.rows : 24;
|
|
5204
6675
|
const width = Math.max(48, Math.min(columns - 1, 120));
|
|
5205
6676
|
const [current, setCurrent] = useState3(workspace);
|
|
5206
6677
|
const [available, setAvailable] = useState3(workspaces);
|
|
@@ -5216,41 +6687,170 @@ function App({
|
|
|
5216
6687
|
const [notice, setNotice] = useState3(null);
|
|
5217
6688
|
const [ticketKey, setTicketKey] = useState3(null);
|
|
5218
6689
|
const [ready, setReady] = useState3(false);
|
|
5219
|
-
const
|
|
5220
|
-
const
|
|
5221
|
-
const
|
|
5222
|
-
const
|
|
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);
|
|
5223
6703
|
const say = useCallback((speaker, body, steps) => {
|
|
5224
|
-
setMessages((prior) => [...prior, { id: nextId(), speaker, body, steps }]);
|
|
6704
|
+
setMessages((prior) => [...prior, { id: nextId(), speaker, body, steps, done: true }]);
|
|
5225
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
|
+
);
|
|
5226
6771
|
const refresh = useCallback(async () => {
|
|
6772
|
+
const loadToken = workspaceLoads.current.start(workspaceCtx.workspace.id);
|
|
5227
6773
|
try {
|
|
5228
6774
|
const [next, activity, paused] = await Promise.all([
|
|
5229
6775
|
loadBoard(workspaceCtx),
|
|
5230
6776
|
loadActivity(workspaceCtx, 20),
|
|
5231
6777
|
loadGlobalPause(workspaceCtx)
|
|
5232
6778
|
]);
|
|
6779
|
+
if (!workspaceLoads.current.isCurrent(loadToken)) return;
|
|
5233
6780
|
setBoard(next);
|
|
5234
6781
|
setFeed(activity);
|
|
5235
6782
|
setPausedAll2(paused);
|
|
5236
6783
|
} catch (error) {
|
|
6784
|
+
if (!workspaceLoads.current.isCurrent(loadToken)) return;
|
|
5237
6785
|
setNotice(error instanceof Error ? error.message : String(error));
|
|
5238
6786
|
}
|
|
5239
6787
|
}, [workspaceCtx]);
|
|
6788
|
+
refreshRef.current = refresh;
|
|
5240
6789
|
useEffect3(() => {
|
|
5241
6790
|
void refresh();
|
|
5242
6791
|
const repaint = debounce(() => void refresh(), 250);
|
|
5243
6792
|
const sub = subscribe({
|
|
5244
6793
|
db: ctx.db,
|
|
5245
|
-
tables: [
|
|
5246
|
-
|
|
6794
|
+
tables: [
|
|
6795
|
+
"tickets",
|
|
6796
|
+
"runs",
|
|
6797
|
+
"run_events",
|
|
6798
|
+
"messages",
|
|
6799
|
+
"decisions",
|
|
6800
|
+
"agents",
|
|
6801
|
+
"hosts",
|
|
6802
|
+
"provider_pauses",
|
|
6803
|
+
"runtime"
|
|
6804
|
+
],
|
|
6805
|
+
filter: {
|
|
6806
|
+
tickets: `workspace_id=eq.${current.id}`,
|
|
6807
|
+
runs: `workspace_id=eq.${current.id}`,
|
|
6808
|
+
messages: `workspace_id=eq.${current.id}`,
|
|
6809
|
+
decisions: `workspace_id=eq.${current.id}`,
|
|
6810
|
+
agents: `workspace_id=eq.${current.id}`
|
|
6811
|
+
},
|
|
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
|
+
},
|
|
5247
6822
|
onState: setLive
|
|
5248
6823
|
});
|
|
5249
6824
|
return () => {
|
|
5250
6825
|
repaint.cancel();
|
|
5251
6826
|
void sub.close();
|
|
5252
6827
|
};
|
|
5253
|
-
}, [ctx, refresh]);
|
|
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]);
|
|
5254
6854
|
useEffect3(() => {
|
|
5255
6855
|
session.current = null;
|
|
5256
6856
|
}, [current.id]);
|
|
@@ -5261,6 +6861,14 @@ function App({
|
|
|
5261
6861
|
setNotice(`No workspace ${slug}. You can reach: ${available.map((r) => r.slug).join(", ")}.`);
|
|
5262
6862
|
return;
|
|
5263
6863
|
}
|
|
6864
|
+
workspaceLoads.current.switchTo(found.id);
|
|
6865
|
+
session.current = null;
|
|
6866
|
+
setBoard(null);
|
|
6867
|
+
setFeed([]);
|
|
6868
|
+
setPausedAll2(false);
|
|
6869
|
+
setStream([]);
|
|
6870
|
+
setCursor(null);
|
|
6871
|
+
setNotice(null);
|
|
5264
6872
|
setCurrent(found);
|
|
5265
6873
|
setView("home");
|
|
5266
6874
|
setTicketKey(null);
|
|
@@ -5277,7 +6885,7 @@ function App({
|
|
|
5277
6885
|
setMessages((prior) => [...prior, { id, speaker: "architect", body: "", steps, pending: true }]);
|
|
5278
6886
|
try {
|
|
5279
6887
|
if (!session.current) {
|
|
5280
|
-
session.current = await openArchitect({ ctx: workspaceCtx, readOnly: false });
|
|
6888
|
+
session.current = await openArchitect({ ctx: workspaceCtx, readOnly: false, fatalAuth: false });
|
|
5281
6889
|
}
|
|
5282
6890
|
let streamed = "";
|
|
5283
6891
|
const result = await architectTurn({
|
|
@@ -5309,6 +6917,7 @@ function App({
|
|
|
5309
6917
|
(prior) => prior.map((m) => m.id === id ? { ...m, body: message, pending: false } : m)
|
|
5310
6918
|
);
|
|
5311
6919
|
} finally {
|
|
6920
|
+
setMessages((prior) => prior.map((m) => m.id === id ? { ...m, done: true } : m));
|
|
5312
6921
|
setBusy(false);
|
|
5313
6922
|
}
|
|
5314
6923
|
},
|
|
@@ -5337,6 +6946,7 @@ function App({
|
|
|
5337
6946
|
const message = error instanceof Error ? error.message : String(error);
|
|
5338
6947
|
setMessages((prior) => prior.map((m) => m.id === id ? { ...m, body: message, pending: false } : m));
|
|
5339
6948
|
} finally {
|
|
6949
|
+
setMessages((prior) => prior.map((m) => m.id === id ? { ...m, done: true } : m));
|
|
5340
6950
|
setBusy(false);
|
|
5341
6951
|
}
|
|
5342
6952
|
},
|
|
@@ -5345,7 +6955,41 @@ function App({
|
|
|
5345
6955
|
const run5 = useCallback(
|
|
5346
6956
|
async (raw) => {
|
|
5347
6957
|
const text = raw.trim();
|
|
5348
|
-
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
|
+
}
|
|
5349
6993
|
history.current.push(text);
|
|
5350
6994
|
historyAt.current = -1;
|
|
5351
6995
|
setDraft("");
|
|
@@ -5361,14 +7005,105 @@ function App({
|
|
|
5361
7005
|
switch (action.kind) {
|
|
5362
7006
|
case "mode":
|
|
5363
7007
|
setMode(action.mode);
|
|
5364
|
-
if (action.mode !== "browse")
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
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
|
+
});
|
|
5369
7029
|
return;
|
|
5370
7030
|
case "view":
|
|
5371
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
|
+
]);
|
|
5372
7107
|
return;
|
|
5373
7108
|
case "workspace":
|
|
5374
7109
|
if (!action.slug) {
|
|
@@ -5393,85 +7128,183 @@ function App({
|
|
|
5393
7128
|
return;
|
|
5394
7129
|
}
|
|
5395
7130
|
},
|
|
5396
|
-
[
|
|
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
|
+
]
|
|
5397
7151
|
);
|
|
5398
7152
|
useInput2((input, key2) => {
|
|
5399
7153
|
if (key2.ctrl && input === "c") exit();
|
|
5400
7154
|
});
|
|
5401
7155
|
const ticket = ticketKey ? board?.tickets.find((row) => row.key === ticketKey) : null;
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
/* @__PURE__ */
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
"No ticket ",
|
|
5436
|
-
ticketKey,
|
|
5437
|
-
" here."
|
|
5438
|
-
] }) : null,
|
|
5439
|
-
view === "home" && board ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
5440
|
-
/* @__PURE__ */ jsx6(AgentsPanel, { board }),
|
|
5441
|
-
/* @__PURE__ */ jsx6(EpicsPanel, { board })
|
|
5442
|
-
] }) : null,
|
|
5443
|
-
messages.map((message) => /* @__PURE__ */ jsx6(Bubble, { message, width }, message.id)),
|
|
5444
|
-
notice ? /* @__PURE__ */ jsx6(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: UI.warn, children: notice }) }) : null,
|
|
5445
|
-
/* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsx6(
|
|
5446
|
-
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,
|
|
5447
7189
|
{
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
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,
|
|
5455
7232
|
" "
|
|
5456
7233
|
] }),
|
|
5457
|
-
color: UI.
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
},
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
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] ?? "");
|
|
5470
7304
|
}
|
|
5471
|
-
setDraft(history.current[historyAt.current] ?? "");
|
|
5472
7305
|
}
|
|
5473
|
-
}
|
|
5474
|
-
|
|
7306
|
+
) })
|
|
7307
|
+
] })
|
|
5475
7308
|
] });
|
|
5476
7309
|
}
|
|
5477
7310
|
function promptFor(mode) {
|
|
@@ -5499,12 +7332,23 @@ var init_App = __esm({
|
|
|
5499
7332
|
init_plan();
|
|
5500
7333
|
init_commands3();
|
|
5501
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();
|
|
5502
7345
|
init_Bubble();
|
|
5503
7346
|
init_Help();
|
|
5504
7347
|
init_Panels();
|
|
5505
7348
|
init_TextInput();
|
|
5506
7349
|
init_parse();
|
|
5507
7350
|
init_theme2();
|
|
7351
|
+
init_workspace_load();
|
|
5508
7352
|
messageSeq = 0;
|
|
5509
7353
|
nextId = () => `m${messageSeq++}`;
|
|
5510
7354
|
}
|
|
@@ -5516,20 +7360,21 @@ __export(launch_exports, {
|
|
|
5516
7360
|
launchApp: () => launchApp
|
|
5517
7361
|
});
|
|
5518
7362
|
async function launchApp(slug) {
|
|
7363
|
+
if (!canLaunchApp()) return;
|
|
5519
7364
|
const ctx = await requireWorkspace(slug);
|
|
5520
|
-
const [{ render },
|
|
7365
|
+
const [{ render }, React12, { App: App2 }] = await Promise.all([
|
|
5521
7366
|
import("ink"),
|
|
5522
7367
|
import("react"),
|
|
5523
7368
|
Promise.resolve().then(() => (init_App(), App_exports))
|
|
5524
7369
|
]);
|
|
5525
7370
|
const { data: workspaces } = await ctx.db.from("workspaces").select("*").order("name");
|
|
5526
|
-
const
|
|
7371
|
+
const config = await loadConfig();
|
|
5527
7372
|
const instance = render(
|
|
5528
|
-
|
|
7373
|
+
React12.createElement(App2, {
|
|
5529
7374
|
ctx,
|
|
5530
7375
|
workspace: ctx.workspace,
|
|
5531
7376
|
workspaces: workspaces ?? [],
|
|
5532
|
-
brainLabel: describeBrain(brain)
|
|
7377
|
+
brainLabel: describeBrain(config.brain ?? DEFAULT_BRAIN)
|
|
5533
7378
|
}),
|
|
5534
7379
|
{ exitOnCtrlC: false }
|
|
5535
7380
|
);
|
|
@@ -5542,7 +7387,9 @@ var init_launch = __esm({
|
|
|
5542
7387
|
"src/tui/launch.ts"() {
|
|
5543
7388
|
"use strict";
|
|
5544
7389
|
init_context();
|
|
7390
|
+
init_config();
|
|
5545
7391
|
init_brain();
|
|
7392
|
+
init_capability();
|
|
5546
7393
|
}
|
|
5547
7394
|
});
|
|
5548
7395
|
|
|
@@ -5688,7 +7535,7 @@ function loadConfig2(env = process.env) {
|
|
|
5688
7535
|
stopTimeoutMs: Number(env.RUNNER_STOP_TIMEOUT_MS ?? 9e4)
|
|
5689
7536
|
};
|
|
5690
7537
|
}
|
|
5691
|
-
var
|
|
7538
|
+
var init_config3 = __esm({
|
|
5692
7539
|
"../runner/src/config.ts"() {
|
|
5693
7540
|
"use strict";
|
|
5694
7541
|
}
|
|
@@ -6005,7 +7852,7 @@ var init_init = __esm({
|
|
|
6005
7852
|
"../runner/src/init.ts"() {
|
|
6006
7853
|
"use strict";
|
|
6007
7854
|
init_auth_check();
|
|
6008
|
-
|
|
7855
|
+
init_config3();
|
|
6009
7856
|
init_db();
|
|
6010
7857
|
init_heartbeat();
|
|
6011
7858
|
init_providers();
|
|
@@ -6055,6 +7902,7 @@ init_theme();
|
|
|
6055
7902
|
init_json();
|
|
6056
7903
|
init_supabase();
|
|
6057
7904
|
init_connection();
|
|
7905
|
+
init_capability();
|
|
6058
7906
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6059
7907
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
6060
7908
|
var CODE_RE = /^\d{6}$/;
|
|
@@ -6094,7 +7942,7 @@ async function login(opts) {
|
|
|
6094
7942
|
if (codex.state !== "connected") {
|
|
6095
7943
|
out(`${c.dim("codex")} ${c.yellow(connectionSummary(codex))} ${c.dim(howToFix(codex) ?? "")}`);
|
|
6096
7944
|
}
|
|
6097
|
-
if (opts.launch !== false &&
|
|
7945
|
+
if (opts.launch !== false && canLaunchApp()) {
|
|
6098
7946
|
const { launchApp: launchApp2 } = await Promise.resolve().then(() => (init_launch(), launch_exports));
|
|
6099
7947
|
await launchApp2();
|
|
6100
7948
|
}
|
|
@@ -6140,6 +7988,7 @@ init_commands2();
|
|
|
6140
7988
|
init_context();
|
|
6141
7989
|
init_json();
|
|
6142
7990
|
init_theme();
|
|
7991
|
+
init_argv_parsers();
|
|
6143
7992
|
init_format();
|
|
6144
7993
|
|
|
6145
7994
|
// src/live/types.ts
|
|
@@ -6319,7 +8168,7 @@ function registerLiveCommands(program) {
|
|
|
6319
8168
|
render: async () => agentsView(await loadBoard(ctx))
|
|
6320
8169
|
});
|
|
6321
8170
|
});
|
|
6322
|
-
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() {
|
|
6323
8172
|
const ctx = await requireWorkspace(slugOf4(this));
|
|
6324
8173
|
const limit = Number(this.opts().limit) || 40;
|
|
6325
8174
|
if (isJsonMode()) {
|
|
@@ -6358,7 +8207,9 @@ init_group();
|
|
|
6358
8207
|
init_editor();
|
|
6359
8208
|
init_epics();
|
|
6360
8209
|
init_tickets2();
|
|
8210
|
+
init_argv_parsers();
|
|
6361
8211
|
import { readFile as readFile6 } from "fs/promises";
|
|
8212
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
6362
8213
|
function slugOf5(command) {
|
|
6363
8214
|
return command.optsWithGlobals().workspace;
|
|
6364
8215
|
}
|
|
@@ -6371,8 +8222,11 @@ async function findTicket(ctx, key2) {
|
|
|
6371
8222
|
function splitKeys(value) {
|
|
6372
8223
|
return (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
6373
8224
|
}
|
|
8225
|
+
function requestId(command) {
|
|
8226
|
+
return String(command.opts().requestId ?? randomUUID4());
|
|
8227
|
+
}
|
|
6374
8228
|
function registerWriteCommands(program) {
|
|
6375
|
-
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) {
|
|
6376
8230
|
const opts = this.opts();
|
|
6377
8231
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6378
8232
|
let title = titleWords.join(" ").trim();
|
|
@@ -6416,7 +8270,7 @@ function registerWriteCommands(program) {
|
|
|
6416
8270
|
});
|
|
6417
8271
|
ok(`${c.bold(ticket.key)} ${ticket.title} ${c.dim(`(${ticket.status})`)}`, { ticket });
|
|
6418
8272
|
});
|
|
6419
|
-
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").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) {
|
|
6420
8274
|
const opts = this.opts();
|
|
6421
8275
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6422
8276
|
const ticket = await findTicket(ctx, key2);
|
|
@@ -6442,38 +8296,45 @@ function registerWriteCommands(program) {
|
|
|
6442
8296
|
patch.body = parsed.body;
|
|
6443
8297
|
patch.acceptance = parsed.acceptance;
|
|
6444
8298
|
}
|
|
6445
|
-
const
|
|
6446
|
-
|
|
8299
|
+
const operationId = requestId(this);
|
|
8300
|
+
const updated = await updateTicket(ctx, ticket, patch, operationId);
|
|
8301
|
+
ok(`${c.bold(updated.key)} updated ${c.dim(`(${updated.status})`)} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6447
8302
|
});
|
|
6448
|
-
program.command("block").argument("<key>", "ticket key").argument("<blockers...>", "keys it should wait for").description("make a ticket wait for others").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) {
|
|
6449
8304
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6450
8305
|
const ticket = await findTicket(ctx, key2);
|
|
6451
|
-
const
|
|
6452
|
-
|
|
8306
|
+
const operationId = requestId(this);
|
|
8307
|
+
const updated = await setBlockedBy(ctx, ticket, blockers, "add", operationId);
|
|
8308
|
+
ok(`${c.bold(updated.key)} is ${updated.status} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6453
8309
|
});
|
|
6454
|
-
program.command("unblock").argument("<key>", "ticket key").argument("[blockers...]", "keys to drop; omit to clear them all").description("drop dependencies").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) {
|
|
6455
8311
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6456
8312
|
const ticket = await findTicket(ctx, key2);
|
|
6457
|
-
const
|
|
6458
|
-
|
|
8313
|
+
const operationId = requestId(this);
|
|
8314
|
+
const updated = blockers.length ? await setBlockedBy(ctx, ticket, blockers, "remove", operationId) : await setBlockedBy(ctx, ticket, [], "set", operationId);
|
|
8315
|
+
ok(`${c.bold(updated.key)} is ${updated.status} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6459
8316
|
});
|
|
6460
|
-
program.command("take").argument("<key>", "ticket key").description("take a ticket over so the board stops dispatching it").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) {
|
|
6461
8318
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6462
|
-
const
|
|
8319
|
+
const operationId = requestId(this);
|
|
8320
|
+
const updated = await takeOver(ctx, await findTicket(ctx, key2), operationId);
|
|
6463
8321
|
ok(`${c.bold(updated.key)} is yours. Hand it back with \`hd handback ${updated.key}\`.`, {
|
|
6464
|
-
ticket: updated
|
|
8322
|
+
ticket: updated,
|
|
8323
|
+
request_id: operationId
|
|
6465
8324
|
});
|
|
6466
8325
|
});
|
|
6467
|
-
program.command("handback").argument("<key>", "ticket key").description("give a ticket back to the board").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) {
|
|
6468
8327
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6469
|
-
const
|
|
6470
|
-
|
|
8328
|
+
const operationId = requestId(this);
|
|
8329
|
+
const updated = await handBack(ctx, await findTicket(ctx, key2), operationId);
|
|
8330
|
+
ok(`${c.bold(updated.key)} handed back ${c.dim(`(${updated.status})`)} ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6471
8331
|
});
|
|
6472
|
-
program.command("cancel").argument("<key>", "ticket key").description("cancel a ticket").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) {
|
|
6473
8333
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6474
8334
|
const ticket = await findTicket(ctx, key2);
|
|
6475
|
-
const
|
|
6476
|
-
|
|
8335
|
+
const operationId = requestId(this);
|
|
8336
|
+
const updated = await updateTicket(ctx, ticket, { status: "cancelled" }, operationId);
|
|
8337
|
+
ok(`${c.bold(updated.key)} cancelled ${c.dim(`request ${operationId}`)}`, { ticket: updated, request_id: operationId });
|
|
6477
8338
|
});
|
|
6478
8339
|
program.command("kill").argument("<target>", "run id prefix, or a ticket key to kill its live run").description("stop a run").action(async function(target) {
|
|
6479
8340
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
@@ -6502,7 +8363,7 @@ function registerWriteCommands(program) {
|
|
|
6502
8363
|
{ epic: created }
|
|
6503
8364
|
);
|
|
6504
8365
|
});
|
|
6505
|
-
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) {
|
|
6506
8367
|
const opts = this.opts();
|
|
6507
8368
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
6508
8369
|
const found = resolveEpic(await loadBoard(ctx), id);
|
|
@@ -6565,7 +8426,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6565
8426
|
import { z as z3 } from "zod";
|
|
6566
8427
|
|
|
6567
8428
|
// src/version.ts
|
|
6568
|
-
var VERSION = "0.2.
|
|
8429
|
+
var VERSION = "0.2.1";
|
|
6569
8430
|
|
|
6570
8431
|
// src/architect/mcp.ts
|
|
6571
8432
|
init_tools();
|
|
@@ -6640,6 +8501,7 @@ init_brain();
|
|
|
6640
8501
|
init_config();
|
|
6641
8502
|
init_connection();
|
|
6642
8503
|
init_tools();
|
|
8504
|
+
init_argv_parsers();
|
|
6643
8505
|
function slugOf6(command) {
|
|
6644
8506
|
return command.optsWithGlobals().workspace;
|
|
6645
8507
|
}
|
|
@@ -6738,7 +8600,7 @@ function registerArchitectCommands(program) {
|
|
|
6738
8600
|
}
|
|
6739
8601
|
await serveMcp({ readOnly, workspace: slugOf6(this) });
|
|
6740
8602
|
});
|
|
6741
|
-
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() {
|
|
6742
8604
|
const opts = this.opts();
|
|
6743
8605
|
if (opts.model || opts.effort) {
|
|
6744
8606
|
const next = await setBrain({
|
|
@@ -6821,11 +8683,12 @@ init_queries();
|
|
|
6821
8683
|
init_commands2();
|
|
6822
8684
|
init_views();
|
|
6823
8685
|
init_theme();
|
|
8686
|
+
init_capability();
|
|
6824
8687
|
function registerDashboard(program) {
|
|
6825
8688
|
program.action(async function() {
|
|
6826
8689
|
const slug = this.optsWithGlobals().workspace;
|
|
6827
8690
|
const ctx = await requireWorkspace(slug);
|
|
6828
|
-
if (
|
|
8691
|
+
if (!canLaunchApp()) {
|
|
6829
8692
|
const [board, pausedAll] = await Promise.all([loadBoard(ctx), loadGlobalPause(ctx)]);
|
|
6830
8693
|
emit(
|
|
6831
8694
|
{ workspace: ctx.workspace.slug, ...statusPayload(board, pausedAll) },
|