@higherdev/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +477 -156
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -619,6 +619,13 @@ var init_defaults = __esm({
|
|
|
619
619
|
}
|
|
620
620
|
});
|
|
621
621
|
|
|
622
|
+
// ../../packages/db/src/desirability.ts
|
|
623
|
+
var init_desirability = __esm({
|
|
624
|
+
"../../packages/db/src/desirability.ts"() {
|
|
625
|
+
"use strict";
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
622
629
|
// ../../packages/db/src/effective-config.ts
|
|
623
630
|
var init_effective_config = __esm({
|
|
624
631
|
"../../packages/db/src/effective-config.ts"() {
|
|
@@ -1009,53 +1016,6 @@ var init_schemas = __esm({
|
|
|
1009
1016
|
}
|
|
1010
1017
|
});
|
|
1011
1018
|
|
|
1012
|
-
// ../../packages/db/src/stuck.ts
|
|
1013
|
-
function stuckReason(opts) {
|
|
1014
|
-
const { ticket } = opts;
|
|
1015
|
-
if (QUIET.has(ticket.status)) return null;
|
|
1016
|
-
if (ticket.human_owner) return null;
|
|
1017
|
-
if (ticket.status === "needs_decision") return "needs decision";
|
|
1018
|
-
if (ticket.status === "blocked") {
|
|
1019
|
-
const keys = (opts.blockerKeys ?? []).filter(Boolean);
|
|
1020
|
-
return keys.length > 0 ? `blocked by ${keys.join(", ")}` : "blocked";
|
|
1021
|
-
}
|
|
1022
|
-
if (opts.agent && !opts.agent.enabled) {
|
|
1023
|
-
return `${opts.agent.display_name} is disabled`;
|
|
1024
|
-
}
|
|
1025
|
-
if (opts.hostOnline === false) return "host offline";
|
|
1026
|
-
if (ticket.next_review_at) {
|
|
1027
|
-
const until = Date.parse(ticket.next_review_at);
|
|
1028
|
-
const now = opts.now ?? Date.now();
|
|
1029
|
-
if (!Number.isNaN(until) && until > now) {
|
|
1030
|
-
const clock2 = new Date(until).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
1031
|
-
return `review backoff until ${clock2}`;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
if (ticket.status === "queued" || ticket.status === "ready") {
|
|
1035
|
-
if (!ticket.agent_id && !ticket.provider) return "no agent assigned";
|
|
1036
|
-
const row = opts.providerStatus;
|
|
1037
|
-
if (row && !row.available) {
|
|
1038
|
-
const who = opts.agent?.display_name ?? ticket.provider ?? "agent";
|
|
1039
|
-
return `${who}: ${availabilityLabel(row)}`;
|
|
1040
|
-
}
|
|
1041
|
-
if (ticket.status === "queued") {
|
|
1042
|
-
const who = opts.agent?.display_name ?? ticket.provider;
|
|
1043
|
-
return who ? `waiting for a slot (${who})` : "waiting for a slot";
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
if (ticket.status === "failed" && ticket.last_error) return ticket.last_error;
|
|
1047
|
-
if (ticket.last_error) return ticket.last_error;
|
|
1048
|
-
return null;
|
|
1049
|
-
}
|
|
1050
|
-
var QUIET;
|
|
1051
|
-
var init_stuck = __esm({
|
|
1052
|
-
"../../packages/db/src/stuck.ts"() {
|
|
1053
|
-
"use strict";
|
|
1054
|
-
init_availability();
|
|
1055
|
-
QUIET = /* @__PURE__ */ new Set(["running", "merged", "cancelled"]);
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
|
|
1059
1019
|
// ../../packages/db/src/templates.ts
|
|
1060
1020
|
function parseTicketTemplatesStrict(value) {
|
|
1061
1021
|
let raw = value;
|
|
@@ -1067,25 +1027,25 @@ function parseTicketTemplatesStrict(value) {
|
|
|
1067
1027
|
}
|
|
1068
1028
|
}
|
|
1069
1029
|
if (!Array.isArray(raw)) return { ok: false, error: "Templates must be a list." };
|
|
1070
|
-
const
|
|
1030
|
+
const templates2 = [];
|
|
1071
1031
|
const ids = /* @__PURE__ */ new Set();
|
|
1072
1032
|
for (const row of raw) {
|
|
1073
1033
|
const parsed = ticketTemplateSchema.safeParse(row);
|
|
1074
1034
|
if (!parsed.success) return { ok: false, error: "Each template needs an id, name, body, and acceptance." };
|
|
1075
1035
|
if (ids.has(parsed.data.id)) return { ok: false, error: "Template ids must be unique." };
|
|
1076
1036
|
ids.add(parsed.data.id);
|
|
1077
|
-
|
|
1037
|
+
templates2.push(parsed.data);
|
|
1078
1038
|
}
|
|
1079
|
-
return { ok: true, templates };
|
|
1039
|
+
return { ok: true, templates: templates2 };
|
|
1080
1040
|
}
|
|
1081
1041
|
function parseTicketTemplates(value) {
|
|
1082
1042
|
const parsed = parseTicketTemplatesStrict(value);
|
|
1083
1043
|
if (parsed.ok) return parsed.templates;
|
|
1084
1044
|
return DEFAULT_TICKET_TEMPLATES.map((row) => ({ ...row }));
|
|
1085
1045
|
}
|
|
1086
|
-
function applyTicketTemplate(
|
|
1046
|
+
function applyTicketTemplate(templates2, id) {
|
|
1087
1047
|
if (!id) return null;
|
|
1088
|
-
const template =
|
|
1048
|
+
const template = templates2.find((row) => row.id === id);
|
|
1089
1049
|
if (!template) return null;
|
|
1090
1050
|
return { body_md: template.body_md, acceptance_md: template.acceptance_md };
|
|
1091
1051
|
}
|
|
@@ -1123,6 +1083,136 @@ var init_templates = __esm({
|
|
|
1123
1083
|
}
|
|
1124
1084
|
});
|
|
1125
1085
|
|
|
1086
|
+
// ../../packages/db/src/recipes.ts
|
|
1087
|
+
import { z as z2 } from "zod";
|
|
1088
|
+
var itemSchema, workflowRecipeSchema, templates, WORKFLOW_RECIPES;
|
|
1089
|
+
var init_recipes = __esm({
|
|
1090
|
+
"../../packages/db/src/recipes.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
init_templates();
|
|
1093
|
+
itemSchema = z2.object({
|
|
1094
|
+
id: z2.string().regex(/^[a-z][a-z0-9-]{0,39}$/),
|
|
1095
|
+
title: z2.string().min(1).max(200),
|
|
1096
|
+
body_md: z2.string().max(2e4),
|
|
1097
|
+
acceptance_md: z2.string().max(1e4),
|
|
1098
|
+
area: z2.string().max(100).nullable().default(null),
|
|
1099
|
+
priority: z2.number().int().min(-100).max(100).default(0),
|
|
1100
|
+
blocked_by: z2.array(z2.string()).max(20).default([])
|
|
1101
|
+
});
|
|
1102
|
+
workflowRecipeSchema = z2.object({
|
|
1103
|
+
id: z2.string().regex(/^[a-z][a-z0-9-]{0,39}$/),
|
|
1104
|
+
name: z2.string().min(1).max(100),
|
|
1105
|
+
description: z2.string().min(1).max(300),
|
|
1106
|
+
conventions_md: z2.string().max(2e4),
|
|
1107
|
+
ticket_templates: z2.array(z2.object({ id: z2.string(), name: z2.string(), body_md: z2.string(), acceptance_md: z2.string() })),
|
|
1108
|
+
epic: z2.object({ title: z2.string().min(1).max(200), spec_md: z2.string().max(5e4), tickets: z2.array(itemSchema).max(50) }).nullable()
|
|
1109
|
+
}).superRefine((recipe, context) => {
|
|
1110
|
+
const ids = new Set(recipe.epic?.tickets.map((ticket) => ticket.id) ?? []);
|
|
1111
|
+
if (ids.size !== (recipe.epic?.tickets.length ?? 0)) context.addIssue({ code: "custom", message: "Recipe ticket ids must be unique." });
|
|
1112
|
+
for (const ticket of recipe.epic?.tickets ?? []) {
|
|
1113
|
+
if (new Set(ticket.blocked_by).size !== ticket.blocked_by.length) context.addIssue({ code: "custom", message: `Ticket ${ticket.id} repeats a blocker.` });
|
|
1114
|
+
for (const blocker of ticket.blocked_by) if (!ids.has(blocker) || blocker === ticket.id) context.addIssue({ code: "custom", message: `Invalid blocker ${blocker}.` });
|
|
1115
|
+
}
|
|
1116
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1117
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1118
|
+
const byId = new Map((recipe.epic?.tickets ?? []).map((ticket) => [ticket.id, ticket]));
|
|
1119
|
+
const visit = (id) => {
|
|
1120
|
+
if (visiting.has(id)) return false;
|
|
1121
|
+
if (visited.has(id)) return true;
|
|
1122
|
+
visiting.add(id);
|
|
1123
|
+
for (const blocker of byId.get(id)?.blocked_by ?? []) if (!visit(blocker)) return false;
|
|
1124
|
+
visiting.delete(id);
|
|
1125
|
+
visited.add(id);
|
|
1126
|
+
return true;
|
|
1127
|
+
};
|
|
1128
|
+
for (const id of ids) if (!visit(id)) {
|
|
1129
|
+
context.addIssue({ code: "custom", message: "Recipe dependencies must be acyclic." });
|
|
1130
|
+
break;
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
templates = () => DEFAULT_TICKET_TEMPLATES.map((row) => ({ ...row }));
|
|
1134
|
+
WORKFLOW_RECIPES = [
|
|
1135
|
+
{ id: "blank", name: "Blank workspace", description: "Agents and ticket templates only.", conventions_md: "", ticket_templates: templates(), epic: null },
|
|
1136
|
+
{
|
|
1137
|
+
id: "feature-delivery",
|
|
1138
|
+
name: "Feature delivery",
|
|
1139
|
+
description: "Discover, implement, verify, and release a product feature.",
|
|
1140
|
+
conventions_md: "Keep changes reviewable, preserve owner intent, and record verification evidence before release.",
|
|
1141
|
+
ticket_templates: templates(),
|
|
1142
|
+
epic: { title: "First feature delivery", spec_md: "Replace this starter specification with the product outcome and constraints.", tickets: [
|
|
1143
|
+
{ id: "scope", title: "Confirm outcome and acceptance contract", body_md: "Document the user outcome, constraints, and non-goals.", acceptance_md: "- Acceptance criteria are testable\n- Owner decisions are explicit", area: "product", priority: 30, blocked_by: [] },
|
|
1144
|
+
{ id: "build", title: "Implement the scoped feature", body_md: "Build the smallest complete vertical slice that satisfies the accepted contract.", acceptance_md: "- The accepted behavior works\n- Failure paths are covered", area: "product", priority: 20, blocked_by: ["scope"] },
|
|
1145
|
+
{ id: "release", title: "Verify and release the feature", body_md: "Run complete gates, review release evidence, and execute the approved release plan.", acceptance_md: "- Required gates pass\n- Rollback and live verification are recorded", area: "release", priority: 10, blocked_by: ["build"] }
|
|
1146
|
+
] }
|
|
1147
|
+
}
|
|
1148
|
+
];
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
// ../../packages/db/src/slo.ts
|
|
1153
|
+
var SLO_THRESHOLDS;
|
|
1154
|
+
var init_slo = __esm({
|
|
1155
|
+
"../../packages/db/src/slo.ts"() {
|
|
1156
|
+
"use strict";
|
|
1157
|
+
init_budgets();
|
|
1158
|
+
SLO_THRESHOLDS = {
|
|
1159
|
+
queueLatencySeconds: 60,
|
|
1160
|
+
stuckAgeSeconds: 15 * 60,
|
|
1161
|
+
successRatePercent: 90,
|
|
1162
|
+
retryRatePercent: 10,
|
|
1163
|
+
expiredLeases: 0,
|
|
1164
|
+
webhookDeliveryPercent: 99
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// ../../packages/db/src/stuck.ts
|
|
1170
|
+
function stuckReason(opts) {
|
|
1171
|
+
const { ticket } = opts;
|
|
1172
|
+
if (QUIET.has(ticket.status)) return null;
|
|
1173
|
+
if (ticket.human_owner) return null;
|
|
1174
|
+
if (ticket.status === "needs_decision") return "needs decision";
|
|
1175
|
+
if (ticket.status === "blocked") {
|
|
1176
|
+
const keys = (opts.blockerKeys ?? []).filter(Boolean);
|
|
1177
|
+
return keys.length > 0 ? `blocked by ${keys.join(", ")}` : "blocked";
|
|
1178
|
+
}
|
|
1179
|
+
if (opts.agent && !opts.agent.enabled) {
|
|
1180
|
+
return `${opts.agent.display_name} is disabled`;
|
|
1181
|
+
}
|
|
1182
|
+
if (opts.hostOnline === false) return "host offline";
|
|
1183
|
+
if (ticket.next_review_at) {
|
|
1184
|
+
const until = Date.parse(ticket.next_review_at);
|
|
1185
|
+
const now = opts.now ?? Date.now();
|
|
1186
|
+
if (!Number.isNaN(until) && until > now) {
|
|
1187
|
+
const clock2 = new Date(until).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
1188
|
+
return `review backoff until ${clock2}`;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (ticket.status === "queued" || ticket.status === "ready") {
|
|
1192
|
+
if (!ticket.agent_id && !ticket.provider) return "no agent assigned";
|
|
1193
|
+
const row = opts.providerStatus;
|
|
1194
|
+
if (row && !row.available) {
|
|
1195
|
+
const who = opts.agent?.display_name ?? ticket.provider ?? "agent";
|
|
1196
|
+
return `${who}: ${availabilityLabel(row)}`;
|
|
1197
|
+
}
|
|
1198
|
+
if (ticket.status === "queued") {
|
|
1199
|
+
const who = opts.agent?.display_name ?? ticket.provider;
|
|
1200
|
+
return who ? `waiting for a slot (${who})` : "waiting for a slot";
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (ticket.status === "failed" && ticket.last_error) return ticket.last_error;
|
|
1204
|
+
if (ticket.last_error) return ticket.last_error;
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
var QUIET;
|
|
1208
|
+
var init_stuck = __esm({
|
|
1209
|
+
"../../packages/db/src/stuck.ts"() {
|
|
1210
|
+
"use strict";
|
|
1211
|
+
init_availability();
|
|
1212
|
+
QUIET = /* @__PURE__ */ new Set(["running", "merged", "cancelled"]);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1126
1216
|
// ../../packages/db/src/tickets.ts
|
|
1127
1217
|
function uncertainCreateError(error) {
|
|
1128
1218
|
return Boolean(error && (!error.code || /^PGRST00[0-3]$/.test(error.code)));
|
|
@@ -1570,6 +1660,7 @@ var init_src = __esm({
|
|
|
1570
1660
|
init_commands();
|
|
1571
1661
|
init_cockpit();
|
|
1572
1662
|
init_defaults();
|
|
1663
|
+
init_desirability();
|
|
1573
1664
|
init_effective_config();
|
|
1574
1665
|
init_enums();
|
|
1575
1666
|
init_epic_progress();
|
|
@@ -1577,7 +1668,9 @@ var init_src = __esm({
|
|
|
1577
1668
|
init_host_health();
|
|
1578
1669
|
init_models();
|
|
1579
1670
|
init_quiet_hours();
|
|
1671
|
+
init_recipes();
|
|
1580
1672
|
init_schemas();
|
|
1673
|
+
init_slo();
|
|
1581
1674
|
init_status();
|
|
1582
1675
|
init_stuck();
|
|
1583
1676
|
init_templates();
|
|
@@ -3977,7 +4070,12 @@ async function createBuilder(ctx, name, input) {
|
|
|
3977
4070
|
model,
|
|
3978
4071
|
display_name: display,
|
|
3979
4072
|
effort: asEffort(input.effort ?? DEFAULT_EFFORT),
|
|
3980
|
-
routing_notes: input.notes ?? ""
|
|
4073
|
+
routing_notes: input.notes ?? "",
|
|
4074
|
+
prompt_addendum: input.prompt ?? "",
|
|
4075
|
+
// Absent means unlimited, which is the column default. Null is a cap
|
|
4076
|
+
// deliberately removed, and both arrive here as null.
|
|
4077
|
+
...input.runsPerHour === void 0 ? {} : { runs_per_hour: input.runsPerHour },
|
|
4078
|
+
...input.dailySpend === void 0 ? {} : { daily_spend_usd: input.dailySpend }
|
|
3981
4079
|
}).select("*").single();
|
|
3982
4080
|
if (error) return no(error.message);
|
|
3983
4081
|
await ctx.audit(ctx.workspace.id, "create", { subject: `agent ${data.display_name}` });
|
|
@@ -4312,12 +4410,12 @@ function registerWorkspaceCommands(program) {
|
|
|
4312
4410
|
});
|
|
4313
4411
|
workspace.command("templates").description("ticket templates for this workspace").action(async function() {
|
|
4314
4412
|
const ctx = await requireWorkspace(slugOf3(this));
|
|
4315
|
-
const
|
|
4413
|
+
const templates2 = parseTicketTemplates(ctx.workspace.ticket_templates);
|
|
4316
4414
|
emit(
|
|
4317
|
-
|
|
4415
|
+
templates2,
|
|
4318
4416
|
() => table(
|
|
4319
4417
|
[{ header: "ID" }, { header: "NAME" }],
|
|
4320
|
-
|
|
4418
|
+
templates2.map((row) => [c.bold(row.id), row.name])
|
|
4321
4419
|
)
|
|
4322
4420
|
);
|
|
4323
4421
|
});
|
|
@@ -4434,7 +4532,7 @@ var init_commands4 = __esm({
|
|
|
4434
4532
|
|
|
4435
4533
|
// src/architect/tools.ts
|
|
4436
4534
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4437
|
-
import { z as
|
|
4535
|
+
import { z as z3 } from "zod";
|
|
4438
4536
|
async function ticketOf(ctx, wanted) {
|
|
4439
4537
|
const board = await loadBoard(ctx);
|
|
4440
4538
|
const ticket = board.tickets.find((row) => row.key === wanted.trim().toUpperCase());
|
|
@@ -4452,7 +4550,7 @@ function toolsFor(access2) {
|
|
|
4452
4550
|
return access2 === "read" ? TOOLS.filter((tool) => tool.access === "read") : TOOLS;
|
|
4453
4551
|
}
|
|
4454
4552
|
function toolJsonSchema(tool) {
|
|
4455
|
-
const schema =
|
|
4553
|
+
const schema = z3.toJSONSchema(tool.schema);
|
|
4456
4554
|
delete schema.$schema;
|
|
4457
4555
|
if (!schema.type) schema.type = "object";
|
|
4458
4556
|
if (!schema.properties) schema.properties = {};
|
|
@@ -4474,13 +4572,13 @@ var init_tools = __esm({
|
|
|
4474
4572
|
init_epics();
|
|
4475
4573
|
init_commands4();
|
|
4476
4574
|
init_json();
|
|
4477
|
-
key =
|
|
4575
|
+
key = z3.string().describe("Ticket key, for example HD-12.");
|
|
4478
4576
|
TOOLS = [
|
|
4479
4577
|
{
|
|
4480
4578
|
name: "status",
|
|
4481
4579
|
access: "read",
|
|
4482
4580
|
description: "The whole board at a glance: ticket counts by state, what is running, what is stuck and why, open decisions, provider availability, and epic progress. Start here.",
|
|
4483
|
-
schema:
|
|
4581
|
+
schema: z3.object({}),
|
|
4484
4582
|
async run(ctx) {
|
|
4485
4583
|
const [board, pausedAll] = await Promise.all([loadBoard(ctx), loadGlobalPause(ctx)]);
|
|
4486
4584
|
return {
|
|
@@ -4500,10 +4598,10 @@ var init_tools = __esm({
|
|
|
4500
4598
|
name: "list_tickets",
|
|
4501
4599
|
access: "read",
|
|
4502
4600
|
description: "Every ticket, with its status, agent, area, dependencies, and stuck reason.",
|
|
4503
|
-
schema:
|
|
4504
|
-
status:
|
|
4505
|
-
area:
|
|
4506
|
-
epic_id:
|
|
4601
|
+
schema: z3.object({
|
|
4602
|
+
status: z3.string().optional().describe("Filter to one status."),
|
|
4603
|
+
area: z3.string().optional(),
|
|
4604
|
+
epic_id: z3.string().optional()
|
|
4507
4605
|
}),
|
|
4508
4606
|
async run(ctx, args) {
|
|
4509
4607
|
const board = await loadBoard(ctx);
|
|
@@ -4526,7 +4624,7 @@ var init_tools = __esm({
|
|
|
4526
4624
|
name: "get_ticket",
|
|
4527
4625
|
access: "read",
|
|
4528
4626
|
description: "One ticket in full: body, acceptance criteria, runs, messages, and history.",
|
|
4529
|
-
schema:
|
|
4627
|
+
schema: z3.object({ key }),
|
|
4530
4628
|
async run(ctx, args) {
|
|
4531
4629
|
const ticket = await ticketOf(ctx, String(args.key));
|
|
4532
4630
|
const [runs, messages, statusEvents] = await Promise.all([
|
|
@@ -4541,7 +4639,7 @@ var init_tools = __esm({
|
|
|
4541
4639
|
name: "list_agents",
|
|
4542
4640
|
access: "read",
|
|
4543
4641
|
description: "The agent registry: role, provider, model, effort, routing notes, prompt addendum, caps, and whether each is enabled.",
|
|
4544
|
-
schema:
|
|
4642
|
+
schema: z3.object({}),
|
|
4545
4643
|
async run(ctx) {
|
|
4546
4644
|
const board = await loadBoard(ctx);
|
|
4547
4645
|
return board.agents;
|
|
@@ -4551,7 +4649,7 @@ var init_tools = __esm({
|
|
|
4551
4649
|
name: "list_epics",
|
|
4552
4650
|
access: "read",
|
|
4553
4651
|
description: "Epics with their specs and progress.",
|
|
4554
|
-
schema:
|
|
4652
|
+
schema: z3.object({ include_spec: z3.boolean().optional() }),
|
|
4555
4653
|
async run(ctx, args) {
|
|
4556
4654
|
const board = await loadBoard(ctx);
|
|
4557
4655
|
return board.epics.map((epic) => {
|
|
@@ -4571,7 +4669,7 @@ var init_tools = __esm({
|
|
|
4571
4669
|
name: "list_hosts",
|
|
4572
4670
|
access: "read",
|
|
4573
4671
|
description: "Registered runner hosts: online state, providers installed, queue depth, auth status.",
|
|
4574
|
-
schema:
|
|
4672
|
+
schema: z3.object({}),
|
|
4575
4673
|
async run(ctx) {
|
|
4576
4674
|
const board = await loadBoard(ctx);
|
|
4577
4675
|
return board.hosts;
|
|
@@ -4581,7 +4679,7 @@ var init_tools = __esm({
|
|
|
4581
4679
|
name: "get_cost",
|
|
4582
4680
|
access: "read",
|
|
4583
4681
|
description: "Spend by day and provider, so a plan can be weighed against the budget.",
|
|
4584
|
-
schema:
|
|
4682
|
+
schema: z3.object({}),
|
|
4585
4683
|
async run(ctx) {
|
|
4586
4684
|
const daily = await loadDailyCosts(ctx);
|
|
4587
4685
|
const total = sumCost(daily.map((row) => ({ cost_usd: row.cost_usd })));
|
|
@@ -4592,7 +4690,7 @@ var init_tools = __esm({
|
|
|
4592
4690
|
name: "get_conventions",
|
|
4593
4691
|
access: "read",
|
|
4594
4692
|
description: "The workspace conventions document injected into every builder and reviewer prompt.",
|
|
4595
|
-
schema:
|
|
4693
|
+
schema: z3.object({}),
|
|
4596
4694
|
async run(ctx) {
|
|
4597
4695
|
return { conventions_md: ctx.workspace.conventions_md ?? "" };
|
|
4598
4696
|
}
|
|
@@ -4601,7 +4699,7 @@ var init_tools = __esm({
|
|
|
4601
4699
|
name: "get_run_transcript",
|
|
4602
4700
|
access: "read",
|
|
4603
4701
|
description: "A readable transcript of one run: what the agent said, the tools it called, errors.",
|
|
4604
|
-
schema:
|
|
4702
|
+
schema: z3.object({ run_id: z3.string().describe("Run id or a unique prefix.") }),
|
|
4605
4703
|
async run(ctx, args) {
|
|
4606
4704
|
const runs = await loadRuns(ctx, { limit: 200 });
|
|
4607
4705
|
const run5 = runs.find((row) => row.id.startsWith(String(args.run_id)));
|
|
@@ -4614,15 +4712,15 @@ var init_tools = __esm({
|
|
|
4614
4712
|
name: "create_ticket",
|
|
4615
4713
|
access: "write",
|
|
4616
4714
|
description: "File a ticket. One ticket is one PR, one area, under about 400 changed lines. Always give acceptance criteria and a test expectation. The orchestrator picks it up and dispatches it.",
|
|
4617
|
-
schema:
|
|
4618
|
-
title:
|
|
4619
|
-
body_md:
|
|
4620
|
-
acceptance_md:
|
|
4621
|
-
area:
|
|
4622
|
-
agent:
|
|
4623
|
-
priority:
|
|
4624
|
-
epic_id:
|
|
4625
|
-
blocked_by:
|
|
4715
|
+
schema: z3.object({
|
|
4716
|
+
title: z3.string(),
|
|
4717
|
+
body_md: z3.string().describe("What to build and why."),
|
|
4718
|
+
acceptance_md: z3.string().describe("How anyone can tell it is done."),
|
|
4719
|
+
area: z3.string().optional().describe("Surface tag, e.g. web/auth. Two live tickets must not share one."),
|
|
4720
|
+
agent: z3.string().optional().describe("Builder display name, to pin it. Leave empty to let the orchestrator choose."),
|
|
4721
|
+
priority: z3.number().optional(),
|
|
4722
|
+
epic_id: z3.string().optional(),
|
|
4723
|
+
blocked_by: z3.array(z3.string()).optional().describe("Ticket keys this must wait for.")
|
|
4626
4724
|
}),
|
|
4627
4725
|
async run(ctx, args) {
|
|
4628
4726
|
const ticket = await createTicket(ctx, {
|
|
@@ -4642,17 +4740,17 @@ var init_tools = __esm({
|
|
|
4642
4740
|
name: "update_ticket",
|
|
4643
4741
|
access: "write",
|
|
4644
4742
|
description: "Change a ticket's title, body, acceptance, area, agent, priority, status, or epic.",
|
|
4645
|
-
schema:
|
|
4743
|
+
schema: z3.object({
|
|
4646
4744
|
key,
|
|
4647
|
-
title:
|
|
4648
|
-
body_md:
|
|
4649
|
-
acceptance_md:
|
|
4650
|
-
area:
|
|
4651
|
-
agent:
|
|
4652
|
-
priority:
|
|
4653
|
-
status:
|
|
4654
|
-
epic_id:
|
|
4655
|
-
request_id:
|
|
4745
|
+
title: z3.string().optional(),
|
|
4746
|
+
body_md: z3.string().optional(),
|
|
4747
|
+
acceptance_md: z3.string().optional(),
|
|
4748
|
+
area: z3.string().nullable().optional(),
|
|
4749
|
+
agent: z3.string().nullable().optional(),
|
|
4750
|
+
priority: z3.number().optional(),
|
|
4751
|
+
status: z3.string().optional(),
|
|
4752
|
+
epic_id: z3.string().nullable().optional(),
|
|
4753
|
+
request_id: z3.string().optional()
|
|
4656
4754
|
}),
|
|
4657
4755
|
async run(ctx, args) {
|
|
4658
4756
|
const ticket = await ticketOf(ctx, String(args.key));
|
|
@@ -4674,11 +4772,11 @@ var init_tools = __esm({
|
|
|
4674
4772
|
name: "set_blocked_by",
|
|
4675
4773
|
access: "write",
|
|
4676
4774
|
description: "Set, add, or remove the tickets one ticket waits for. Cycles are refused.",
|
|
4677
|
-
schema:
|
|
4775
|
+
schema: z3.object({
|
|
4678
4776
|
key,
|
|
4679
|
-
blocked_by:
|
|
4680
|
-
mode:
|
|
4681
|
-
request_id:
|
|
4777
|
+
blocked_by: z3.array(z3.string()).describe("Ticket keys."),
|
|
4778
|
+
mode: z3.enum(["set", "add", "remove"]).optional(),
|
|
4779
|
+
request_id: z3.string().optional()
|
|
4682
4780
|
}),
|
|
4683
4781
|
async run(ctx, args) {
|
|
4684
4782
|
const ticket = await ticketOf(ctx, String(args.key));
|
|
@@ -4697,7 +4795,7 @@ var init_tools = __esm({
|
|
|
4697
4795
|
name: "create_epic",
|
|
4698
4796
|
access: "write",
|
|
4699
4797
|
description: "Create an epic from a spec. This is the handoff: the orchestrator is triggered by epic.created and decomposes the spec into tickets itself. Prefer one epic with a good spec over filing many tickets by hand.",
|
|
4700
|
-
schema:
|
|
4798
|
+
schema: z3.object({ title: z3.string(), spec_md: z3.string() }),
|
|
4701
4799
|
async run(ctx, args) {
|
|
4702
4800
|
const epic = await createEpic(ctx, String(args.title), String(args.spec_md ?? ""));
|
|
4703
4801
|
return { id: epic.id, title: epic.title, status: epic.status };
|
|
@@ -4707,11 +4805,11 @@ var init_tools = __esm({
|
|
|
4707
4805
|
name: "update_epic",
|
|
4708
4806
|
access: "write",
|
|
4709
4807
|
description: "Change an epic's title, spec, or status.",
|
|
4710
|
-
schema:
|
|
4711
|
-
epic_id:
|
|
4712
|
-
title:
|
|
4713
|
-
spec_md:
|
|
4714
|
-
status:
|
|
4808
|
+
schema: z3.object({
|
|
4809
|
+
epic_id: z3.string(),
|
|
4810
|
+
title: z3.string().optional(),
|
|
4811
|
+
spec_md: z3.string().optional(),
|
|
4812
|
+
status: z3.string().optional()
|
|
4715
4813
|
}),
|
|
4716
4814
|
async run(ctx, args) {
|
|
4717
4815
|
const epic = await epicOf(ctx, String(args.epic_id));
|
|
@@ -4727,7 +4825,7 @@ var init_tools = __esm({
|
|
|
4727
4825
|
name: "replan_epic",
|
|
4728
4826
|
access: "write",
|
|
4729
4827
|
description: "Ask the orchestrator to re-decompose an epic's unmerged remainder after its spec changed.",
|
|
4730
|
-
schema:
|
|
4828
|
+
schema: z3.object({ epic_id: z3.string() }),
|
|
4731
4829
|
async run(ctx, args) {
|
|
4732
4830
|
const epic = await epicOf(ctx, String(args.epic_id));
|
|
4733
4831
|
await replanEpic(ctx, epic.id);
|
|
@@ -4738,11 +4836,11 @@ var init_tools = __esm({
|
|
|
4738
4836
|
name: "post_message",
|
|
4739
4837
|
access: "write",
|
|
4740
4838
|
description: "Post on the board's message bus. Use to_role 'orchestrator' to hand over work or ask about progress, 'builder' with a ticket to steer a live session, 'human' to leave a note for the owner.",
|
|
4741
|
-
schema:
|
|
4742
|
-
to_role:
|
|
4743
|
-
body_md:
|
|
4744
|
-
ticket_key:
|
|
4745
|
-
delivery:
|
|
4839
|
+
schema: z3.object({
|
|
4840
|
+
to_role: z3.enum(["orchestrator", "reviewer", "builder", "operator", "human", "all"]),
|
|
4841
|
+
body_md: z3.string(),
|
|
4842
|
+
ticket_key: z3.string().optional(),
|
|
4843
|
+
delivery: z3.enum(["queue", "interrupt"]).optional()
|
|
4746
4844
|
}),
|
|
4747
4845
|
async run(ctx, args) {
|
|
4748
4846
|
const message = await postMessage(ctx, {
|
|
@@ -4759,10 +4857,10 @@ var init_tools = __esm({
|
|
|
4759
4857
|
name: "answer_decision",
|
|
4760
4858
|
access: "write",
|
|
4761
4859
|
description: "Answer an open decision, or dismiss it.",
|
|
4762
|
-
schema:
|
|
4763
|
-
decision_id:
|
|
4764
|
-
answer_md:
|
|
4765
|
-
dismiss:
|
|
4860
|
+
schema: z3.object({
|
|
4861
|
+
decision_id: z3.string(),
|
|
4862
|
+
answer_md: z3.string().optional(),
|
|
4863
|
+
dismiss: z3.boolean().optional()
|
|
4766
4864
|
}),
|
|
4767
4865
|
async run(ctx, args) {
|
|
4768
4866
|
const result = await answerDecision(ctx, String(args.decision_id), String(args.answer_md ?? ""), {
|
|
@@ -4775,7 +4873,7 @@ var init_tools = __esm({
|
|
|
4775
4873
|
name: "take_over_ticket",
|
|
4776
4874
|
access: "write",
|
|
4777
4875
|
description: "Mark a ticket human-owned so the board stops dispatching it.",
|
|
4778
|
-
schema:
|
|
4876
|
+
schema: z3.object({ key, request_id: z3.string().optional() }),
|
|
4779
4877
|
async run(ctx, args) {
|
|
4780
4878
|
const operationId = String(args.request_id ?? randomUUID3());
|
|
4781
4879
|
const updated = await takeOver(ctx, await ticketOf(ctx, String(args.key)), operationId);
|
|
@@ -4786,7 +4884,7 @@ var init_tools = __esm({
|
|
|
4786
4884
|
name: "hand_back_ticket",
|
|
4787
4885
|
access: "write",
|
|
4788
4886
|
description: "Give a human-owned ticket back to the board.",
|
|
4789
|
-
schema:
|
|
4887
|
+
schema: z3.object({ key, request_id: z3.string().optional() }),
|
|
4790
4888
|
async run(ctx, args) {
|
|
4791
4889
|
const operationId = String(args.request_id ?? randomUUID3());
|
|
4792
4890
|
const updated = await handBack(ctx, await ticketOf(ctx, String(args.key)), operationId);
|
|
@@ -4797,7 +4895,7 @@ var init_tools = __esm({
|
|
|
4797
4895
|
name: "kill_run",
|
|
4798
4896
|
access: "write",
|
|
4799
4897
|
description: "Stop a live run.",
|
|
4800
|
-
schema:
|
|
4898
|
+
schema: z3.object({ run_id: z3.string() }),
|
|
4801
4899
|
async run(ctx, args) {
|
|
4802
4900
|
const runs = await loadRuns(ctx, { limit: 200 });
|
|
4803
4901
|
const run5 = runs.find((row) => row.id.startsWith(String(args.run_id)));
|
|
@@ -4810,16 +4908,16 @@ var init_tools = __esm({
|
|
|
4810
4908
|
name: "update_agent",
|
|
4811
4909
|
access: "write",
|
|
4812
4910
|
description: "Change an agent: model, effort, provider, routing notes the orchestrator reads when assigning, the prompt addendum prepended to its every run, whether it is enabled, and its budget caps. The orchestrator cannot do this; you can.",
|
|
4813
|
-
schema:
|
|
4814
|
-
name:
|
|
4815
|
-
model:
|
|
4816
|
-
effort:
|
|
4817
|
-
provider:
|
|
4818
|
-
routing_notes:
|
|
4819
|
-
prompt_addendum:
|
|
4820
|
-
enabled:
|
|
4821
|
-
runs_per_hour:
|
|
4822
|
-
daily_spend_usd:
|
|
4911
|
+
schema: z3.object({
|
|
4912
|
+
name: z3.string().describe("Agent display name."),
|
|
4913
|
+
model: z3.string().optional(),
|
|
4914
|
+
effort: z3.enum(["low", "medium", "high"]).optional(),
|
|
4915
|
+
provider: z3.enum(["claude", "codex", "gemini", "grok"]).optional(),
|
|
4916
|
+
routing_notes: z3.string().optional().describe("Free text, e.g. 'UI and isolated components only'."),
|
|
4917
|
+
prompt_addendum: z3.string().optional().describe("Prepended to this agent's prompt on every run."),
|
|
4918
|
+
enabled: z3.boolean().optional(),
|
|
4919
|
+
runs_per_hour: z3.number().nullable().optional(),
|
|
4920
|
+
daily_spend_usd: z3.number().nullable().optional()
|
|
4823
4921
|
}),
|
|
4824
4922
|
async run(ctx, args) {
|
|
4825
4923
|
const { data, error } = await ctx.db.from("agents").select("*").eq("workspace_id", ctx.workspace.id);
|
|
@@ -4853,7 +4951,7 @@ var init_tools = __esm({
|
|
|
4853
4951
|
name: "set_conventions",
|
|
4854
4952
|
access: "write",
|
|
4855
4953
|
description: "Replace the workspace conventions document. It is injected into every builder and reviewer prompt, so this is where stack rules, commands, style, and do-not-touch areas belong.",
|
|
4856
|
-
schema:
|
|
4954
|
+
schema: z3.object({ conventions_md: z3.string() }),
|
|
4857
4955
|
async run(ctx, args) {
|
|
4858
4956
|
const { error } = await ctx.db.from("workspaces").update({ conventions_md: String(args.conventions_md) }).eq("id", ctx.workspace.id);
|
|
4859
4957
|
if (error) fail(error.message);
|
|
@@ -4865,10 +4963,10 @@ var init_tools = __esm({
|
|
|
4865
4963
|
name: "set_paused",
|
|
4866
4964
|
access: "write",
|
|
4867
4965
|
description: "Pause or resume this workspace, everything, or one provider.",
|
|
4868
|
-
schema:
|
|
4869
|
-
paused:
|
|
4870
|
-
scope:
|
|
4871
|
-
provider:
|
|
4966
|
+
schema: z3.object({
|
|
4967
|
+
paused: z3.boolean(),
|
|
4968
|
+
scope: z3.enum(["workspace", "all", "provider"]).optional(),
|
|
4969
|
+
provider: z3.string().optional()
|
|
4872
4970
|
}),
|
|
4873
4971
|
async run(ctx, args) {
|
|
4874
4972
|
const scope = args.scope ?? "workspace";
|
|
@@ -4883,7 +4981,7 @@ var init_tools = __esm({
|
|
|
4883
4981
|
name: "nudge_orchestrator",
|
|
4884
4982
|
access: "write",
|
|
4885
4983
|
description: "Wake the orchestrator with a note. Use after filing work so it grooms and dispatches without waiting for the heartbeat.",
|
|
4886
|
-
schema:
|
|
4984
|
+
schema: z3.object({ body_md: z3.string() }),
|
|
4887
4985
|
async run(ctx, args) {
|
|
4888
4986
|
await nudgeOrchestrator(ctx, String(args.body_md));
|
|
4889
4987
|
return { ok: true };
|
|
@@ -5179,13 +5277,20 @@ function stormSchedule(opts = {}) {
|
|
|
5179
5277
|
elapsed2 += frame.ms;
|
|
5180
5278
|
};
|
|
5181
5279
|
while (elapsed2 < durationMs) {
|
|
5182
|
-
|
|
5183
|
-
|
|
5280
|
+
const strikes = between(2, 5);
|
|
5281
|
+
for (let strike = 0; strike < strikes; strike += 1) {
|
|
5282
|
+
const last2 = strike === strikes - 1;
|
|
5283
|
+
push({ level: 3, ms: last2 ? between(110, 170) : between(35, 70) });
|
|
5284
|
+
if (last2) {
|
|
5285
|
+
push({ level: 2, ms: between(120, 180) });
|
|
5286
|
+
push({ level: 1, ms: between(180, 260) });
|
|
5287
|
+
break;
|
|
5288
|
+
}
|
|
5184
5289
|
push({ level: 1, ms: between(25, 55) });
|
|
5185
5290
|
if (random() < 0.35) push({ level: 2, ms: between(30, 60) });
|
|
5186
5291
|
push({ level: 0, ms: between(45, 160) });
|
|
5187
5292
|
}
|
|
5188
|
-
push({ level: 0, ms: between(
|
|
5293
|
+
push({ level: 0, ms: between(600, 3e3), quiet: true });
|
|
5189
5294
|
}
|
|
5190
5295
|
const last = frames[frames.length - 1];
|
|
5191
5296
|
if (last && elapsed2 > durationMs) last.ms = Math.max(1, last.ms - (elapsed2 - durationMs));
|
|
@@ -5450,15 +5555,16 @@ var init_Help = __esm({
|
|
|
5450
5555
|
init_height();
|
|
5451
5556
|
HELP_FOOTER = "Anything not starting with / goes to whoever you are talking to. Ctrl-C leaves.";
|
|
5452
5557
|
COMMANDS = [
|
|
5558
|
+
{ name: "/home", help: "the board and the agents side by side, with what they are doing" },
|
|
5453
5559
|
{ name: "/architect", help: "talk to your own model, which can do anything in the platform" },
|
|
5454
5560
|
{ name: "/orchestrator", help: "talk to the agent that gets work in flight finished" },
|
|
5455
5561
|
{ name: "/browse", help: "put a cursor on the board; up and down move it, enter opens" },
|
|
5456
5562
|
{ name: "/workspace", args: "[slug]", help: "switch workspace, or list the ones you can reach" },
|
|
5457
5563
|
{ name: "/workspace new", args: "name owner/repo", help: "create a workspace, seeded with agents" },
|
|
5458
5564
|
{ name: "/settings", help: "change workspace and agent settings, with the arrow keys" },
|
|
5459
|
-
{ name: "/agent new",
|
|
5565
|
+
{ name: "/agent new", help: "add a builder, filling in a form" },
|
|
5460
5566
|
{ name: "/board", help: "the kanban board" },
|
|
5461
|
-
{ name: "/agents", help: "every agent and
|
|
5567
|
+
{ name: "/agents", help: "every agent in full, and the live run stream" },
|
|
5462
5568
|
{ name: "/feed", help: "what just happened" },
|
|
5463
5569
|
{ name: "/inbox", help: "decisions and messages waiting on you" },
|
|
5464
5570
|
{ name: "/decide", args: "2 | text", help: "answer the decision on screen, or --skip it" },
|
|
@@ -5519,6 +5625,11 @@ function planLayout(input) {
|
|
|
5519
5625
|
const cockpit = Math.max(2, room - stream);
|
|
5520
5626
|
return finish({ cockpit, stream, panels: input.home ? cockpit + stream + gap : room });
|
|
5521
5627
|
}
|
|
5628
|
+
function splitPanels(budget) {
|
|
5629
|
+
const bottom = Math.min(14, Math.round((budget - PANEL_GAP) * 0.4));
|
|
5630
|
+
const top = budget - PANEL_GAP - bottom;
|
|
5631
|
+
return bottom >= 3 && top >= 3 ? { top, bottom } : { top: Math.max(0, budget), bottom: 0 };
|
|
5632
|
+
}
|
|
5522
5633
|
var init_layout = __esm({
|
|
5523
5634
|
"src/tui/layout.ts"() {
|
|
5524
5635
|
"use strict";
|
|
@@ -5789,6 +5900,7 @@ function SettingsPanel({
|
|
|
5789
5900
|
entries,
|
|
5790
5901
|
width,
|
|
5791
5902
|
rows,
|
|
5903
|
+
title = "Settings",
|
|
5792
5904
|
cursor,
|
|
5793
5905
|
editing
|
|
5794
5906
|
}) {
|
|
@@ -5797,7 +5909,7 @@ function SettingsPanel({
|
|
|
5797
5909
|
const { start, end } = scrollWindow(entries.length, inner, focus);
|
|
5798
5910
|
const label = Math.max(10, Math.min(18, Math.round(width / 4)));
|
|
5799
5911
|
return /* @__PURE__ */ jsx6(BoundedPanel, { width, rows, children: [
|
|
5800
|
-
/* @__PURE__ */ jsx6(Heading, { text:
|
|
5912
|
+
/* @__PURE__ */ jsx6(Heading, { text: title, note: hidden(entries.length, start, end) }, "h"),
|
|
5801
5913
|
...entries.slice(start, end).map((row) => {
|
|
5802
5914
|
if (row.kind === "heading") {
|
|
5803
5915
|
return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
|
|
@@ -5807,14 +5919,27 @@ function SettingsPanel({
|
|
|
5807
5919
|
}
|
|
5808
5920
|
const selected = row.key === cursor;
|
|
5809
5921
|
const typing = editing?.key === row.key;
|
|
5922
|
+
if (row.kind === "action") {
|
|
5923
|
+
return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
|
|
5924
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.accent, children: selected ? " \u203A " : " " }),
|
|
5925
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.cream, bold: true, inverse: selected, children: pad(truncate(row.label, label - 1), label) }),
|
|
5926
|
+
/* @__PURE__ */ jsx6(Text5, { color: UI.dim, children: truncate(row.value, Math.max(8, width - label - 6)) })
|
|
5927
|
+
] }, row.key);
|
|
5928
|
+
}
|
|
5810
5929
|
return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
|
|
5811
5930
|
/* @__PURE__ */ jsx6(Text5, { color: UI.accent, children: selected ? " \u203A " : " " }),
|
|
5812
5931
|
/* @__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 || "-",
|
|
5932
|
+
/* @__PURE__ */ jsx6(Text5, { color: typing ? UI.cream : UI.text, inverse: selected && !typing, children: truncate(typing ? `${editing.draft}\u258F` : row.value || "-", valueWidth(width, label, row, selected)) }),
|
|
5933
|
+
selected && row.hint && !typing ? /* @__PURE__ */ jsx6(Text5, { color: UI.dim, children: ` ${row.hint}` }) : null
|
|
5814
5934
|
] }, row.key);
|
|
5815
5935
|
})
|
|
5816
5936
|
] });
|
|
5817
5937
|
}
|
|
5938
|
+
function valueWidth(width, label, row, selected) {
|
|
5939
|
+
const room = Math.max(8, width - label - 6);
|
|
5940
|
+
if (!selected || !row.hint) return room;
|
|
5941
|
+
return Math.max(8, room - row.hint.length - 2);
|
|
5942
|
+
}
|
|
5818
5943
|
function hidden(count, start, end) {
|
|
5819
5944
|
const above = start;
|
|
5820
5945
|
const below = count - end;
|
|
@@ -5832,6 +5957,17 @@ var init_Settings = __esm({
|
|
|
5832
5957
|
});
|
|
5833
5958
|
|
|
5834
5959
|
// src/tui/settings-model.ts
|
|
5960
|
+
function modelExamples(provider) {
|
|
5961
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5962
|
+
for (const agent of DEFAULT_AGENTS) {
|
|
5963
|
+
if (agent.provider === provider) seen.add(agent.model);
|
|
5964
|
+
}
|
|
5965
|
+
return [...seen];
|
|
5966
|
+
}
|
|
5967
|
+
function modelHint(provider) {
|
|
5968
|
+
const examples = modelExamples(provider);
|
|
5969
|
+
return examples.length ? `like ${examples.join(", ")}` : "the provider's model id";
|
|
5970
|
+
}
|
|
5835
5971
|
function capLabel(value) {
|
|
5836
5972
|
return value == null ? UNLIMITED : String(value);
|
|
5837
5973
|
}
|
|
@@ -5885,7 +6021,8 @@ function settingsRows(workspace, agents) {
|
|
|
5885
6021
|
kind: "text",
|
|
5886
6022
|
label: "model",
|
|
5887
6023
|
value: agent.model,
|
|
5888
|
-
agent: agent.display_name
|
|
6024
|
+
agent: agent.display_name,
|
|
6025
|
+
hint: modelHint(agent.provider)
|
|
5889
6026
|
});
|
|
5890
6027
|
rows.push({
|
|
5891
6028
|
key: `a:${agent.id}:effort`,
|
|
@@ -5916,7 +6053,16 @@ function settingsRows(workspace, agents) {
|
|
|
5916
6053
|
kind: "text",
|
|
5917
6054
|
label: "routing notes",
|
|
5918
6055
|
value: agent.routing_notes,
|
|
5919
|
-
agent: agent.display_name
|
|
6056
|
+
agent: agent.display_name,
|
|
6057
|
+
hint: "what the orchestrator reads when it picks who does the work"
|
|
6058
|
+
});
|
|
6059
|
+
rows.push({
|
|
6060
|
+
key: `a:${agent.id}:prompt_addendum`,
|
|
6061
|
+
kind: "text",
|
|
6062
|
+
label: "prompt",
|
|
6063
|
+
value: agent.prompt_addendum,
|
|
6064
|
+
agent: agent.display_name,
|
|
6065
|
+
hint: "prepended to every run this agent does"
|
|
5920
6066
|
});
|
|
5921
6067
|
}
|
|
5922
6068
|
return rows;
|
|
@@ -5978,6 +6124,8 @@ function editFor(row, raw) {
|
|
|
5978
6124
|
}
|
|
5979
6125
|
case "routing_notes":
|
|
5980
6126
|
return ok2({ target: "agent", name, input: { notes: raw } });
|
|
6127
|
+
case "prompt_addendum":
|
|
6128
|
+
return ok2({ target: "agent", name, input: { prompt: raw } });
|
|
5981
6129
|
default:
|
|
5982
6130
|
return no(`Nothing to change on ${row.label}.`);
|
|
5983
6131
|
}
|
|
@@ -5985,6 +6133,95 @@ function editFor(row, raw) {
|
|
|
5985
6133
|
function unlimitedAsNone(value) {
|
|
5986
6134
|
return value.toLowerCase() === UNLIMITED || value === "" ? "none" : value;
|
|
5987
6135
|
}
|
|
6136
|
+
function emptyDraft() {
|
|
6137
|
+
return {
|
|
6138
|
+
name: "",
|
|
6139
|
+
provider: providers[0],
|
|
6140
|
+
model: "",
|
|
6141
|
+
effort: DEFAULT_EFFORT,
|
|
6142
|
+
runsPerHour: UNLIMITED,
|
|
6143
|
+
dailySpend: UNLIMITED,
|
|
6144
|
+
notes: "",
|
|
6145
|
+
prompt: ""
|
|
6146
|
+
};
|
|
6147
|
+
}
|
|
6148
|
+
function draftRows(draft) {
|
|
6149
|
+
return [
|
|
6150
|
+
{ key: "h:new", kind: "heading", label: "New builder", value: "enter fills a row, esc cancels" },
|
|
6151
|
+
{ key: "n:name", kind: "text", label: "name", value: draft.name, hint: "what you will call it" },
|
|
6152
|
+
{
|
|
6153
|
+
key: "n:provider",
|
|
6154
|
+
kind: "choice",
|
|
6155
|
+
label: "provider",
|
|
6156
|
+
value: draft.provider,
|
|
6157
|
+
choices: providers
|
|
6158
|
+
},
|
|
6159
|
+
{ key: "n:model", kind: "text", label: "model", value: draft.model, hint: modelHint(draft.provider) },
|
|
6160
|
+
{ key: "n:effort", kind: "choice", label: "effort", value: draft.effort, choices: efforts },
|
|
6161
|
+
{
|
|
6162
|
+
key: "n:runsPerHour",
|
|
6163
|
+
kind: "number",
|
|
6164
|
+
label: "runs per hour",
|
|
6165
|
+
value: draft.runsPerHour,
|
|
6166
|
+
hint: `a number, or ${UNLIMITED}`
|
|
6167
|
+
},
|
|
6168
|
+
{
|
|
6169
|
+
key: "n:dailySpend",
|
|
6170
|
+
kind: "number",
|
|
6171
|
+
label: "usd per day",
|
|
6172
|
+
value: draft.dailySpend,
|
|
6173
|
+
hint: `a number, or ${UNLIMITED}`
|
|
6174
|
+
},
|
|
6175
|
+
{
|
|
6176
|
+
key: "n:notes",
|
|
6177
|
+
kind: "text",
|
|
6178
|
+
label: "routing notes",
|
|
6179
|
+
value: draft.notes,
|
|
6180
|
+
hint: "what the orchestrator reads when it picks who does the work"
|
|
6181
|
+
},
|
|
6182
|
+
{
|
|
6183
|
+
key: "n:prompt",
|
|
6184
|
+
kind: "text",
|
|
6185
|
+
label: "prompt",
|
|
6186
|
+
value: draft.prompt,
|
|
6187
|
+
hint: "prepended to every run this agent does"
|
|
6188
|
+
},
|
|
6189
|
+
{ key: "n:save", kind: "action", label: "save", value: saveLabel(draft) }
|
|
6190
|
+
];
|
|
6191
|
+
}
|
|
6192
|
+
function saveLabel(draft) {
|
|
6193
|
+
const ready = draftReady(draft);
|
|
6194
|
+
return ready.ok ? `create ${draft.name}` : ready.error;
|
|
6195
|
+
}
|
|
6196
|
+
function applyToDraft(draft, key2, value) {
|
|
6197
|
+
const field = key2.split(":")[1];
|
|
6198
|
+
if (!(field in draft)) return draft;
|
|
6199
|
+
const blankIsUnlimited = field === "runsPerHour" || field === "dailySpend";
|
|
6200
|
+
const next = blankIsUnlimited && value.trim() === "" ? UNLIMITED : value;
|
|
6201
|
+
return { ...draft, [field]: field === "prompt" || field === "notes" ? value : next.trim() };
|
|
6202
|
+
}
|
|
6203
|
+
function draftReady(draft) {
|
|
6204
|
+
if (!draft.name.trim()) return no("A name, first.");
|
|
6205
|
+
if (!draft.model.trim()) return no(`A model, ${modelHint(draft.provider)}.`);
|
|
6206
|
+
const provider = parseField(oneOf("provider", providers), draft.provider);
|
|
6207
|
+
if (!provider.ok) return no(provider.error);
|
|
6208
|
+
const effort = parseField(oneOf("effort", efforts), draft.effort);
|
|
6209
|
+
if (!effort.ok) return no(effort.error);
|
|
6210
|
+
const runs = parseField(optionalPositiveInteger("runs per hour"), unlimitedAsNone(draft.runsPerHour));
|
|
6211
|
+
if (!runs.ok) return no(runs.error);
|
|
6212
|
+
const spend = parseField(optionalPositiveNumber("usd per day"), unlimitedAsNone(draft.dailySpend));
|
|
6213
|
+
if (!spend.ok) return no(spend.error);
|
|
6214
|
+
return ok2({
|
|
6215
|
+
name: draft.name.trim(),
|
|
6216
|
+
provider: provider.value,
|
|
6217
|
+
model: draft.model.trim(),
|
|
6218
|
+
effort: effort.value,
|
|
6219
|
+
notes: draft.notes,
|
|
6220
|
+
prompt: draft.prompt,
|
|
6221
|
+
runsPerHour: runs.value,
|
|
6222
|
+
dailySpend: spend.value
|
|
6223
|
+
});
|
|
6224
|
+
}
|
|
5988
6225
|
var UNLIMITED;
|
|
5989
6226
|
var init_settings_model = __esm({
|
|
5990
6227
|
"src/tui/settings-model.ts"() {
|
|
@@ -6573,6 +6810,9 @@ function parseLine(raw) {
|
|
|
6573
6810
|
return { kind: "mode", mode: "orchestrator" };
|
|
6574
6811
|
case "browse":
|
|
6575
6812
|
return { kind: "mode", mode: "browse" };
|
|
6813
|
+
case "home":
|
|
6814
|
+
case "cockpit":
|
|
6815
|
+
return { kind: "view", view: "home" };
|
|
6576
6816
|
case "board":
|
|
6577
6817
|
case "agents":
|
|
6578
6818
|
case "feed":
|
|
@@ -6584,10 +6824,14 @@ function parseLine(raw) {
|
|
|
6584
6824
|
case "agent": {
|
|
6585
6825
|
const [verb, name, provider, model] = argument.split(/\s+/).filter(Boolean);
|
|
6586
6826
|
if (verb?.toLowerCase() !== "new") {
|
|
6587
|
-
return { kind: "unknown", command: "agent, try /agent new
|
|
6827
|
+
return { kind: "unknown", command: "agent, try /agent new" };
|
|
6588
6828
|
}
|
|
6589
|
-
if (!name
|
|
6590
|
-
|
|
6829
|
+
if (!name) return { kind: "agent-form" };
|
|
6830
|
+
if (!provider || !model) {
|
|
6831
|
+
return {
|
|
6832
|
+
kind: "unknown",
|
|
6833
|
+
command: "agent new needs a provider and a model too, or /agent new on its own for the form"
|
|
6834
|
+
};
|
|
6591
6835
|
}
|
|
6592
6836
|
return { kind: "agent-new", name, provider, model };
|
|
6593
6837
|
}
|
|
@@ -6710,9 +6954,10 @@ function App({
|
|
|
6710
6954
|
);
|
|
6711
6955
|
const liveRunIds = [...labels.current.keys()].sort().join(",");
|
|
6712
6956
|
const order = useMemo2(() => board ? boardTicketIds(board) : [], [board]);
|
|
6957
|
+
const [agentDraft, setAgentDraft] = useState3(emptyDraft);
|
|
6713
6958
|
const settings = useMemo2(
|
|
6714
|
-
() => board ? settingsRows(current, board.agents) : [],
|
|
6715
|
-
[current, board]
|
|
6959
|
+
() => view === "new-agent" ? draftRows(agentDraft) : board ? settingsRows(current, board.agents) : [],
|
|
6960
|
+
[view, agentDraft, current, board]
|
|
6716
6961
|
);
|
|
6717
6962
|
const settingsOrder = useMemo2(() => editableKeys(settings), [settings]);
|
|
6718
6963
|
const [field, setField] = useState3(null);
|
|
@@ -6721,7 +6966,7 @@ function App({
|
|
|
6721
6966
|
const editingRef = useRef3(null);
|
|
6722
6967
|
editingRef.current = editing;
|
|
6723
6968
|
const browsing = mode === "browse" && draft.length === 0 && cursor !== null;
|
|
6724
|
-
const configuring = view === "settings" && !editing;
|
|
6969
|
+
const configuring = (view === "settings" || view === "new-agent") && !editing;
|
|
6725
6970
|
const moveField = useCallback(
|
|
6726
6971
|
(delta) => {
|
|
6727
6972
|
const next = nextCursor(settingsOrder, fieldRef.current, delta);
|
|
@@ -6768,6 +7013,36 @@ function App({
|
|
|
6768
7013
|
},
|
|
6769
7014
|
[settings, workspaceCtx]
|
|
6770
7015
|
);
|
|
7016
|
+
const saveDraft = useCallback(async () => {
|
|
7017
|
+
const ready2 = draftReady(agentDraft);
|
|
7018
|
+
if (!ready2.ok) {
|
|
7019
|
+
setNotice(ready2.error);
|
|
7020
|
+
return;
|
|
7021
|
+
}
|
|
7022
|
+
setBusy(true);
|
|
7023
|
+
try {
|
|
7024
|
+
const made = await createBuilder(workspaceCtx, ready2.value.name, {
|
|
7025
|
+
provider: ready2.value.provider,
|
|
7026
|
+
model: ready2.value.model,
|
|
7027
|
+
effort: ready2.value.effort,
|
|
7028
|
+
notes: ready2.value.notes,
|
|
7029
|
+
prompt: ready2.value.prompt,
|
|
7030
|
+
runsPerHour: ready2.value.runsPerHour,
|
|
7031
|
+
dailySpend: ready2.value.dailySpend
|
|
7032
|
+
});
|
|
7033
|
+
if (!made.ok) {
|
|
7034
|
+
setNotice(made.error);
|
|
7035
|
+
return;
|
|
7036
|
+
}
|
|
7037
|
+
setNotice(null);
|
|
7038
|
+
setAgentDraft(emptyDraft());
|
|
7039
|
+
setView("settings");
|
|
7040
|
+
say("system", `Added ${made.value.display_name}.`);
|
|
7041
|
+
await refreshRef.current?.();
|
|
7042
|
+
} finally {
|
|
7043
|
+
setBusy(false);
|
|
7044
|
+
}
|
|
7045
|
+
}, [agentDraft, workspaceCtx, say]);
|
|
6771
7046
|
const refresh = useCallback(async () => {
|
|
6772
7047
|
const loadToken = workspaceLoads.current.start(workspaceCtx.workspace.id);
|
|
6773
7048
|
try {
|
|
@@ -6955,6 +7230,32 @@ function App({
|
|
|
6955
7230
|
const run5 = useCallback(
|
|
6956
7231
|
async (raw) => {
|
|
6957
7232
|
const text = raw.trim();
|
|
7233
|
+
if (view === "new-agent") {
|
|
7234
|
+
const open = editingRef.current;
|
|
7235
|
+
if (open) {
|
|
7236
|
+
setEditing(null);
|
|
7237
|
+
setDraft("");
|
|
7238
|
+
setAgentDraft((prior) => applyToDraft(prior, open.key, raw));
|
|
7239
|
+
setNotice(null);
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
if (!text) {
|
|
7243
|
+
const key2 = fieldRef.current;
|
|
7244
|
+
const row = settings.find((entry) => entry.key === key2);
|
|
7245
|
+
if (!row || !key2) return;
|
|
7246
|
+
if (row.kind === "action") return saveDraft();
|
|
7247
|
+
const flipped = nextValue(row);
|
|
7248
|
+
if (flipped !== null) {
|
|
7249
|
+
setAgentDraft((prior) => applyToDraft(prior, key2, flipped));
|
|
7250
|
+
return;
|
|
7251
|
+
}
|
|
7252
|
+
const seed = seedFor(row);
|
|
7253
|
+
setEditing({ key: key2, draft: seed });
|
|
7254
|
+
setDraft(seed);
|
|
7255
|
+
setNotice(row.hint ?? null);
|
|
7256
|
+
return;
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
6958
7259
|
if (view === "settings") {
|
|
6959
7260
|
const open = editingRef.current;
|
|
6960
7261
|
if (open) {
|
|
@@ -7036,6 +7337,16 @@ function App({
|
|
|
7036
7337
|
setEditing(null);
|
|
7037
7338
|
}
|
|
7038
7339
|
return;
|
|
7340
|
+
case "agent-form": {
|
|
7341
|
+
setAgentDraft(emptyDraft());
|
|
7342
|
+
setView("new-agent");
|
|
7343
|
+
const first = editableKeys(draftRows(emptyDraft()))[0] ?? null;
|
|
7344
|
+
fieldRef.current = first;
|
|
7345
|
+
setField(first);
|
|
7346
|
+
setEditing(null);
|
|
7347
|
+
setNotice(null);
|
|
7348
|
+
return;
|
|
7349
|
+
}
|
|
7039
7350
|
case "agent-new": {
|
|
7040
7351
|
setBusy(true);
|
|
7041
7352
|
try {
|
|
@@ -7137,6 +7448,7 @@ function App({
|
|
|
7137
7448
|
view,
|
|
7138
7449
|
settings,
|
|
7139
7450
|
applyEdit,
|
|
7451
|
+
saveDraft,
|
|
7140
7452
|
ctx,
|
|
7141
7453
|
workspaceCtx,
|
|
7142
7454
|
settingsOrder,
|
|
@@ -7177,6 +7489,7 @@ function App({
|
|
|
7177
7489
|
const streamRows = plan.stream;
|
|
7178
7490
|
const fits = plan.fits;
|
|
7179
7491
|
const running = board?.runs.filter((run6) => run6.status === "running").length ?? 0;
|
|
7492
|
+
const agentsView2 = splitPanels(budget);
|
|
7180
7493
|
const selected = cursor ? board?.tickets.find((row) => row.id === cursor) : null;
|
|
7181
7494
|
return /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
7182
7495
|
/* @__PURE__ */ jsx11(Static, { items: scrollback, children: (item) => {
|
|
@@ -7198,14 +7511,26 @@ function App({
|
|
|
7198
7511
|
) : null,
|
|
7199
7512
|
/* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", width, children: [
|
|
7200
7513
|
view === "board" && board && budget > 0 ? /* @__PURE__ */ jsx11(BoardPanel, { board, width, rows: budget, cursor }) : null,
|
|
7201
|
-
view === "agents" && board && budget > 0 ?
|
|
7514
|
+
view === "agents" && board && budget > 0 ? (
|
|
7515
|
+
// The registry and what it is doing, together. "What is that agent
|
|
7516
|
+
// up to" is the question this view is opened to answer, and the run
|
|
7517
|
+
// stream is the only thing that answers it.
|
|
7518
|
+
/* @__PURE__ */ jsxs10(Box8, { flexDirection: "column", children: [
|
|
7519
|
+
/* @__PURE__ */ jsx11(AgentsPanel, { board, width, rows: agentsView2.top }),
|
|
7520
|
+
agentsView2.bottom > 0 ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
7521
|
+
/* @__PURE__ */ jsx11(Box8, { height: 1 }),
|
|
7522
|
+
/* @__PURE__ */ jsx11(StreamPanel, { lines: stream, width, rows: agentsView2.bottom, live: running > 0 })
|
|
7523
|
+
] }) : null
|
|
7524
|
+
] })
|
|
7525
|
+
) : null,
|
|
7202
7526
|
view === "feed" && budget > 0 ? /* @__PURE__ */ jsx11(FeedPanel, { entries: feed, width, rows: budget }) : null,
|
|
7203
|
-
view === "settings" && budget > 0 ? /* @__PURE__ */ jsx11(
|
|
7527
|
+
(view === "settings" || view === "new-agent") && budget > 0 ? /* @__PURE__ */ jsx11(
|
|
7204
7528
|
SettingsPanel,
|
|
7205
7529
|
{
|
|
7206
7530
|
entries: settings,
|
|
7207
7531
|
width,
|
|
7208
7532
|
rows: budget,
|
|
7533
|
+
title: view === "new-agent" ? "New agent" : "Settings",
|
|
7209
7534
|
cursor: field,
|
|
7210
7535
|
editing
|
|
7211
7536
|
}
|
|
@@ -7271,11 +7596,7 @@ function App({
|
|
|
7271
7596
|
setNotice(null);
|
|
7272
7597
|
return;
|
|
7273
7598
|
}
|
|
7274
|
-
if (view
|
|
7275
|
-
setView("home");
|
|
7276
|
-
return;
|
|
7277
|
-
}
|
|
7278
|
-
if (view === "ticket") {
|
|
7599
|
+
if (view !== "home") {
|
|
7279
7600
|
setView("home");
|
|
7280
7601
|
setTicketKey(null);
|
|
7281
7602
|
return;
|
|
@@ -8233,8 +8554,8 @@ function registerWriteCommands(program) {
|
|
|
8233
8554
|
let body = opts.body ?? "";
|
|
8234
8555
|
let acceptance = opts.acceptance ?? "";
|
|
8235
8556
|
if (opts.template) {
|
|
8236
|
-
const
|
|
8237
|
-
const applied = applyTicketTemplate(
|
|
8557
|
+
const templates2 = parseTicketTemplates(ctx.workspace.ticket_templates);
|
|
8558
|
+
const applied = applyTicketTemplate(templates2, opts.template);
|
|
8238
8559
|
if (!applied) fail(`No template ${opts.template}. Run \`hd workspace templates\`.`);
|
|
8239
8560
|
body = body || applied.body_md;
|
|
8240
8561
|
acceptance = acceptance || applied.acceptance_md;
|
|
@@ -8423,10 +8744,10 @@ import { createInterface as createInterface3 } from "readline/promises";
|
|
|
8423
8744
|
// src/architect/mcp.ts
|
|
8424
8745
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8425
8746
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8426
|
-
import { z as
|
|
8747
|
+
import { z as z4 } from "zod";
|
|
8427
8748
|
|
|
8428
8749
|
// src/version.ts
|
|
8429
|
-
var VERSION = "0.
|
|
8750
|
+
var VERSION = "0.3.0";
|
|
8430
8751
|
|
|
8431
8752
|
// src/architect/mcp.ts
|
|
8432
8753
|
init_tools();
|
|
@@ -8451,7 +8772,7 @@ async function serveMcp(opts) {
|
|
|
8451
8772
|
return work;
|
|
8452
8773
|
};
|
|
8453
8774
|
for (const tool of toolsFor(opts.readOnly ? "read" : "all")) {
|
|
8454
|
-
const shape = tool.schema instanceof
|
|
8775
|
+
const shape = tool.schema instanceof z4.ZodObject ? tool.schema.shape : {};
|
|
8455
8776
|
server.registerTool(
|
|
8456
8777
|
tool.name,
|
|
8457
8778
|
{
|