@higherdev/cli 0.1.2 → 0.1.3
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/README.md +6 -4
- package/dist/index.js +231 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,15 +56,17 @@ hd ask "why is HD-12 stuck?"
|
|
|
56
56
|
hd msg HD-12 --to builder "also rename the old column" --interrupt
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`hd plan`
|
|
60
|
-
subscription. It
|
|
59
|
+
`hd plan` talks to **your own model**, here on your machine, on your ChatGPT
|
|
60
|
+
subscription. It is not a worker in a workspace and not a row in the agent
|
|
61
|
+
registry: nothing on the runner dispatches to it, and it reaches the whole
|
|
62
|
+
platform. `hd brain` shows which model it is and connects the subscription the
|
|
63
|
+
first time. It reads the repo, decides what should be built, files an epic,
|
|
61
64
|
and can tune the platform itself: models, effort, routing notes, prompt addenda,
|
|
62
65
|
conventions, budgets. Creating an epic fires `epic.created`, which the
|
|
63
66
|
orchestrator already triggers on, so the handoff needs no glue.
|
|
64
67
|
|
|
65
68
|
```bash
|
|
66
|
-
hd
|
|
67
|
-
hd doctor # what a run would cost, before it runs
|
|
69
|
+
hd brain # which model, and connect it the first time
|
|
68
70
|
hd plan # a conversation: type, it answers, keep going
|
|
69
71
|
```
|
|
70
72
|
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,10 @@ var __export = (target, all) => {
|
|
|
11
11
|
|
|
12
12
|
// ../../packages/db/src/enums.ts
|
|
13
13
|
function asEffort(value) {
|
|
14
|
-
|
|
14
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
15
|
+
return efforts.includes(raw) ? raw : DEFAULT_EFFORT;
|
|
15
16
|
}
|
|
16
|
-
var ticketStatuses, epicStatuses, runStatuses, runKinds, runEventTypes, agentRoles, providers, efforts, messageRoles, messageToRoles, deliveries, createdBy;
|
|
17
|
+
var ticketStatuses, epicStatuses, runStatuses, runKinds, runEventTypes, agentRoles, providers, efforts, messageRoles, messageToRoles, deliveries, createdBy, DEFAULT_EFFORT;
|
|
17
18
|
var init_enums = __esm({
|
|
18
19
|
"../../packages/db/src/enums.ts"() {
|
|
19
20
|
"use strict";
|
|
@@ -43,13 +44,14 @@ var init_enums = __esm({
|
|
|
43
44
|
"error",
|
|
44
45
|
"status"
|
|
45
46
|
];
|
|
46
|
-
agentRoles = ["orchestrator", "reviewer", "builder"
|
|
47
|
+
agentRoles = ["orchestrator", "reviewer", "builder"];
|
|
47
48
|
providers = ["claude", "codex", "gemini", "grok"];
|
|
48
49
|
efforts = ["low", "medium", "high"];
|
|
49
50
|
messageRoles = ["human", "operator", "orchestrator", "reviewer", "builder", "architect"];
|
|
50
51
|
messageToRoles = [...messageRoles, "all"];
|
|
51
52
|
deliveries = ["queue", "interrupt"];
|
|
52
53
|
createdBy = ["human", "orchestrator", "api"];
|
|
54
|
+
DEFAULT_EFFORT = "high";
|
|
53
55
|
}
|
|
54
56
|
});
|
|
55
57
|
|
|
@@ -183,6 +185,8 @@ function auditSummary(actor, action, payload = {}) {
|
|
|
183
185
|
return key2 ? `${actor} took over ${key2}` : `${actor} took over a ticket`;
|
|
184
186
|
case "hand_back":
|
|
185
187
|
return key2 ? `${actor} handed back ${key2}` : `${actor} handed back a ticket`;
|
|
188
|
+
case "merge_override":
|
|
189
|
+
return key2 ? `${actor} authorized an optional-check merge for ${key2}` : `${actor} authorized an optional-check merge`;
|
|
186
190
|
case "create":
|
|
187
191
|
return key2 ? `${actor} created ${key2}` : `${actor} created ${subject(payload)}`;
|
|
188
192
|
case "update":
|
|
@@ -456,6 +460,36 @@ var init_cost = __esm({
|
|
|
456
460
|
}
|
|
457
461
|
});
|
|
458
462
|
|
|
463
|
+
// ../../packages/db/src/commands/lifecycle.ts
|
|
464
|
+
var lifecycleCommandRejectionCodes, lifecycleCommandResultCodes, TERMINAL_TICKET_STATUSES, OPTIONAL_CHECK_OVERRIDE_STATUSES, ticketStatusSet, terminalStatusSet, optionalCheckOverrideStatusSet;
|
|
465
|
+
var init_lifecycle = __esm({
|
|
466
|
+
"../../packages/db/src/commands/lifecycle.ts"() {
|
|
467
|
+
"use strict";
|
|
468
|
+
init_enums();
|
|
469
|
+
lifecycleCommandRejectionCodes = [
|
|
470
|
+
"illegal_transition",
|
|
471
|
+
"terminal_target",
|
|
472
|
+
"stale_state",
|
|
473
|
+
"malformed_intent",
|
|
474
|
+
"duplicate_command_identity"
|
|
475
|
+
];
|
|
476
|
+
lifecycleCommandResultCodes = ["accepted", ...lifecycleCommandRejectionCodes];
|
|
477
|
+
TERMINAL_TICKET_STATUSES = ["merged", "cancelled"];
|
|
478
|
+
OPTIONAL_CHECK_OVERRIDE_STATUSES = ["approved", "needs_decision"];
|
|
479
|
+
ticketStatusSet = new Set(ticketStatuses);
|
|
480
|
+
terminalStatusSet = new Set(TERMINAL_TICKET_STATUSES);
|
|
481
|
+
optionalCheckOverrideStatusSet = new Set(OPTIONAL_CHECK_OVERRIDE_STATUSES);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// ../../packages/db/src/commands/index.ts
|
|
486
|
+
var init_commands = __esm({
|
|
487
|
+
"../../packages/db/src/commands/index.ts"() {
|
|
488
|
+
"use strict";
|
|
489
|
+
init_lifecycle();
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
459
493
|
// ../../packages/db/src/defaults.ts
|
|
460
494
|
function slugify(name) {
|
|
461
495
|
const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -467,9 +501,19 @@ function hostAllowsRepo(allowedRepoOwners, repo) {
|
|
|
467
501
|
if (!owner) return false;
|
|
468
502
|
return allowedRepoOwners.some((item) => item.toLowerCase() === owner);
|
|
469
503
|
}
|
|
504
|
+
var DEFAULT_AGENTS;
|
|
470
505
|
var init_defaults = __esm({
|
|
471
506
|
"../../packages/db/src/defaults.ts"() {
|
|
472
507
|
"use strict";
|
|
508
|
+
init_enums();
|
|
509
|
+
DEFAULT_AGENTS = [
|
|
510
|
+
{ role: "orchestrator", provider: "claude", model: "claude-fable-5", display_name: "Orchestrator", effort: DEFAULT_EFFORT },
|
|
511
|
+
{ role: "reviewer", provider: "claude", model: "claude-opus-5", display_name: "Reviewer", effort: DEFAULT_EFFORT },
|
|
512
|
+
{ role: "builder", provider: "claude", model: "claude-sonnet-5", display_name: "Claude", effort: DEFAULT_EFFORT },
|
|
513
|
+
{ role: "builder", provider: "codex", model: "gpt-5.6-sol", display_name: "Codex", effort: DEFAULT_EFFORT },
|
|
514
|
+
{ role: "builder", provider: "gemini", model: "gemini-3.7-flash-high", display_name: "Gemini", effort: DEFAULT_EFFORT },
|
|
515
|
+
{ role: "builder", provider: "grok", model: "grok-4.6", display_name: "Grok", effort: DEFAULT_EFFORT }
|
|
516
|
+
];
|
|
473
517
|
}
|
|
474
518
|
});
|
|
475
519
|
|
|
@@ -691,7 +735,7 @@ var init_schemas = __esm({
|
|
|
691
735
|
role: z.enum(agentRoles),
|
|
692
736
|
provider: z.enum(providers),
|
|
693
737
|
model: z.string().min(1),
|
|
694
|
-
effort: z.enum(efforts).default(
|
|
738
|
+
effort: z.enum(efforts).default(DEFAULT_EFFORT),
|
|
695
739
|
display_name: z.string().min(1),
|
|
696
740
|
enabled: z.boolean(),
|
|
697
741
|
routing_notes: z.string().default(""),
|
|
@@ -746,6 +790,7 @@ var init_schemas = __esm({
|
|
|
746
790
|
next_review_at: isoDateSchema.nullable(),
|
|
747
791
|
created_by: z.enum(createdBy),
|
|
748
792
|
last_error: z.string().nullable(),
|
|
793
|
+
merge_override_optional_checks: z.boolean().default(false),
|
|
749
794
|
created_at: isoDateSchema,
|
|
750
795
|
updated_at: isoDateSchema
|
|
751
796
|
});
|
|
@@ -835,7 +880,8 @@ var init_schemas = __esm({
|
|
|
835
880
|
"dismiss",
|
|
836
881
|
"reroute",
|
|
837
882
|
"takeover",
|
|
838
|
-
"hand_back"
|
|
883
|
+
"hand_back",
|
|
884
|
+
"merge_override"
|
|
839
885
|
]);
|
|
840
886
|
auditEventSchema = z.object({
|
|
841
887
|
id: uuidSchema,
|
|
@@ -1036,6 +1082,32 @@ var init_templates = __esm({
|
|
|
1036
1082
|
}
|
|
1037
1083
|
});
|
|
1038
1084
|
|
|
1085
|
+
// ../../packages/db/src/tickets.ts
|
|
1086
|
+
function uncertainCreateError(error) {
|
|
1087
|
+
return Boolean(error && (!error.code || /^PGRST00[0-3]$/.test(error.code)));
|
|
1088
|
+
}
|
|
1089
|
+
async function createTicketRecord(db, workspaceId, fields, ticketId) {
|
|
1090
|
+
const requestId = ticketId ?? globalThis.crypto.randomUUID();
|
|
1091
|
+
const args = {
|
|
1092
|
+
p_ticket_id: requestId,
|
|
1093
|
+
p_workspace_id: workspaceId,
|
|
1094
|
+
p_fields: fields
|
|
1095
|
+
};
|
|
1096
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
1097
|
+
try {
|
|
1098
|
+
const result = await db.rpc("create_ticket", args);
|
|
1099
|
+
if (!result.error || attempt === 1 || !uncertainCreateError(result.error)) return result;
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
if (attempt === 1) throw error;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
var init_tickets = __esm({
|
|
1106
|
+
"../../packages/db/src/tickets.ts"() {
|
|
1107
|
+
"use strict";
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1039
1111
|
// ../../packages/db/src/ticket-writes.ts
|
|
1040
1112
|
async function workspaceBySlug(db, slug) {
|
|
1041
1113
|
const { data, error } = await db.from("workspaces").select("*").eq("slug", slug).maybeSingle();
|
|
@@ -1134,7 +1206,13 @@ function buildTimeline(input) {
|
|
|
1134
1206
|
id: `run:${run5.id}`,
|
|
1135
1207
|
at,
|
|
1136
1208
|
kind,
|
|
1137
|
-
title: [
|
|
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(" "),
|
|
1138
1216
|
detail: run5.summary ? clip(run5.summary) : void 0,
|
|
1139
1217
|
durationMs: runDurationMs(run5, nowIso),
|
|
1140
1218
|
endedAt: run5.ended_at
|
|
@@ -1388,6 +1466,7 @@ var init_src = __esm({
|
|
|
1388
1466
|
init_blocked();
|
|
1389
1467
|
init_budgets();
|
|
1390
1468
|
init_cost();
|
|
1469
|
+
init_commands();
|
|
1391
1470
|
init_defaults();
|
|
1392
1471
|
init_enums();
|
|
1393
1472
|
init_epic_progress();
|
|
@@ -1399,6 +1478,7 @@ var init_src = __esm({
|
|
|
1399
1478
|
init_status();
|
|
1400
1479
|
init_stuck();
|
|
1401
1480
|
init_templates();
|
|
1481
|
+
init_tickets();
|
|
1402
1482
|
init_ticket_writes();
|
|
1403
1483
|
init_timeline();
|
|
1404
1484
|
init_transcript();
|
|
@@ -4037,10 +4117,8 @@ async function createTicket(ctx, input) {
|
|
|
4037
4117
|
assigned: Boolean(assigned.agent),
|
|
4038
4118
|
blockerStatuses: blockers.statuses
|
|
4039
4119
|
});
|
|
4040
|
-
const { data, error } = await ctx.db.
|
|
4041
|
-
workspace_id: ctx.workspace.id,
|
|
4120
|
+
const { data, error } = await createTicketRecord(ctx.db, ctx.workspace.id, {
|
|
4042
4121
|
epic_id: input.epic ?? null,
|
|
4043
|
-
key: "",
|
|
4044
4122
|
title,
|
|
4045
4123
|
body_md: input.body ?? "",
|
|
4046
4124
|
acceptance_md: acceptance,
|
|
@@ -4053,7 +4131,7 @@ async function createTicket(ctx, input) {
|
|
|
4053
4131
|
provider_pinned: Boolean(assigned.agent),
|
|
4054
4132
|
blocked_by: blockers.ids,
|
|
4055
4133
|
created_by: "human"
|
|
4056
|
-
})
|
|
4134
|
+
});
|
|
4057
4135
|
if (error) fail(error.message);
|
|
4058
4136
|
await ctx.audit(ctx.workspace.id, "create", { ticket_key: data.key, subject: "a ticket" });
|
|
4059
4137
|
return data;
|
|
@@ -4141,7 +4219,12 @@ async function handBack(ctx, ticket) {
|
|
|
4141
4219
|
return data;
|
|
4142
4220
|
}
|
|
4143
4221
|
async function killRun(ctx, runId, ticketKey) {
|
|
4144
|
-
const { data, error } = await ctx.db.
|
|
4222
|
+
const { data, error } = await ctx.db.rpc("request_run_cancellation", {
|
|
4223
|
+
p_run_id: runId,
|
|
4224
|
+
p_workspace_id: ctx.workspace.id,
|
|
4225
|
+
p_requested_by: ctx.email,
|
|
4226
|
+
p_reason: `Cancellation requested by ${ctx.email}.`
|
|
4227
|
+
});
|
|
4145
4228
|
if (error) fail(error.message);
|
|
4146
4229
|
if (!data) fail(`No run ${runId} in ${ctx.workspace.slug}.`);
|
|
4147
4230
|
await ctx.audit(ctx.workspace.id, "kill", { ticket_key: ticketKey, run_id: runId });
|
|
@@ -4273,14 +4356,14 @@ function registerWriteCommands(program) {
|
|
|
4273
4356
|
const live = runs2.find((run6) => run6.status === "running" || run6.status === "queued");
|
|
4274
4357
|
if (!live) fail(`${ticket.key} has no live run.`);
|
|
4275
4358
|
const killed2 = await killRun(ctx, live.id, ticket.key);
|
|
4276
|
-
ok(`
|
|
4359
|
+
ok(`Cancellation requested for ${c.dim(killed2.id.slice(0, 8))} on ${c.bold(ticket.key)}.`, { run: killed2 });
|
|
4277
4360
|
return;
|
|
4278
4361
|
}
|
|
4279
4362
|
const runs = await loadRuns(ctx, { limit: 200 });
|
|
4280
4363
|
const run5 = runs.find((row) => row.id.startsWith(target));
|
|
4281
4364
|
if (!run5) fail(`No run matching ${target}.`);
|
|
4282
4365
|
const killed = await killRun(ctx, run5.id);
|
|
4283
|
-
ok(`
|
|
4366
|
+
ok(`Cancellation requested for ${c.dim(killed.id.slice(0, 8))}.`, { run: killed });
|
|
4284
4367
|
});
|
|
4285
4368
|
const epic = group(program, "epic", "epics and their dependency waves");
|
|
4286
4369
|
epic.command("new").argument("<title>", "epic title").argument("[spec]", "path to a markdown spec").description("create an epic; the orchestrator decomposes it").action(async function(title, spec) {
|
|
@@ -4591,7 +4674,7 @@ function registerAgentCommands(program) {
|
|
|
4591
4674
|
row.role,
|
|
4592
4675
|
row.provider,
|
|
4593
4676
|
c.dim(row.model),
|
|
4594
|
-
row.effort,
|
|
4677
|
+
asEffort(row.effort),
|
|
4595
4678
|
row.enabled ? c.green("yes") : c.dim("no"),
|
|
4596
4679
|
truncate(row.routing_notes ?? "", 44)
|
|
4597
4680
|
])
|
|
@@ -4605,7 +4688,7 @@ function registerAgentCommands(program) {
|
|
|
4605
4688
|
found,
|
|
4606
4689
|
() => [
|
|
4607
4690
|
`${c.bold(found.display_name)} ${c.dim(found.role)}`,
|
|
4608
|
-
`${c.dim("model")} ${found.provider}/${found.model} ${c.dim(`effort ${found.effort}`)}`,
|
|
4691
|
+
`${c.dim("model")} ${found.provider}/${found.model} ${c.dim(`effort ${asEffort(found.effort)}`)}`,
|
|
4609
4692
|
`${c.dim("enabled")} ${found.enabled ? "yes" : "no"}`,
|
|
4610
4693
|
`${c.dim("caps")} ${found.runs_per_hour ?? "unlimited"} runs/hour, ${found.daily_spend_usd ?? "unlimited"} usd/day`,
|
|
4611
4694
|
found.routing_notes ? `
|
|
@@ -4660,7 +4743,7 @@ ${found.prompt_addendum}` : ""
|
|
|
4660
4743
|
await ctx.audit(ctx.workspace.id, "configure", { subject: `agent ${data.display_name}` });
|
|
4661
4744
|
ok(`${c.bold(data.display_name)} updated.`, { agent: data });
|
|
4662
4745
|
});
|
|
4663
|
-
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",
|
|
4746
|
+
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) {
|
|
4664
4747
|
const opts = this.opts();
|
|
4665
4748
|
const ctx = await requireWorkspace(slugOf5(this));
|
|
4666
4749
|
if (!providers.includes(opts.provider)) {
|
|
@@ -4745,8 +4828,13 @@ function registerWorkspaceCommands(program) {
|
|
|
4745
4828
|
default_host: opts.host
|
|
4746
4829
|
}).select("*").single();
|
|
4747
4830
|
if (error) fail(error.message);
|
|
4831
|
+
const { error: agentsError } = await ctx.db.from("agents").insert(DEFAULT_AGENTS.map((agent) => ({ ...agent, workspace_id: data.id })));
|
|
4832
|
+
if (agentsError) fail(`Workspace created, but seeding its agents failed: ${agentsError.message}`);
|
|
4748
4833
|
await ctx.audit(data.id, "create", { subject: `workspace ${data.slug}` });
|
|
4749
|
-
ok(
|
|
4834
|
+
ok(
|
|
4835
|
+
`${c.bold(data.slug)} created with ${DEFAULT_AGENTS.length} agents. Run \`hd use ${data.slug}\`.`,
|
|
4836
|
+
{ workspace: data }
|
|
4837
|
+
);
|
|
4750
4838
|
});
|
|
4751
4839
|
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() {
|
|
4752
4840
|
const opts = this.opts();
|
|
@@ -4878,7 +4966,7 @@ function registerAttachCommands(program) {
|
|
|
4878
4966
|
}
|
|
4879
4967
|
|
|
4880
4968
|
// src/architect/commands.ts
|
|
4881
|
-
import { createInterface as
|
|
4969
|
+
import { createInterface as createInterface3 } from "readline/promises";
|
|
4882
4970
|
init_json();
|
|
4883
4971
|
init_theme();
|
|
4884
4972
|
|
|
@@ -4888,7 +4976,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4888
4976
|
import { z as z3 } from "zod";
|
|
4889
4977
|
|
|
4890
4978
|
// src/version.ts
|
|
4891
|
-
var VERSION = "0.1.
|
|
4979
|
+
var VERSION = "0.1.3";
|
|
4892
4980
|
|
|
4893
4981
|
// src/architect/tools.ts
|
|
4894
4982
|
init_src();
|
|
@@ -5401,13 +5489,93 @@ async function serveMcp(opts) {
|
|
|
5401
5489
|
init_json();
|
|
5402
5490
|
init_theme();
|
|
5403
5491
|
init_chatgpt();
|
|
5404
|
-
init_connection();
|
|
5405
5492
|
import { execFile as execFile2 } from "child_process";
|
|
5406
5493
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
5407
5494
|
import { homedir as homedir3 } from "os";
|
|
5408
5495
|
import path6 from "path";
|
|
5409
5496
|
import { promisify as promisify2 } from "util";
|
|
5410
5497
|
|
|
5498
|
+
// src/architect/brain.ts
|
|
5499
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
5500
|
+
init_json();
|
|
5501
|
+
init_theme();
|
|
5502
|
+
init_connection();
|
|
5503
|
+
var BRAINS = [
|
|
5504
|
+
{
|
|
5505
|
+
id: "chatgpt",
|
|
5506
|
+
label: "ChatGPT subscription",
|
|
5507
|
+
billing: "included in your ChatGPT plan, nothing per token",
|
|
5508
|
+
// gpt-5.6, without a suffix, is refused by this endpoint for ChatGPT accounts.
|
|
5509
|
+
defaultModel: "gpt-5.6-sol",
|
|
5510
|
+
models: ["gpt-5.6-sol", "gpt-5.5"]
|
|
5511
|
+
}
|
|
5512
|
+
];
|
|
5513
|
+
var DEFAULT_BRAIN = {
|
|
5514
|
+
id: "chatgpt",
|
|
5515
|
+
model: "gpt-5.6-sol",
|
|
5516
|
+
effort: "high"
|
|
5517
|
+
};
|
|
5518
|
+
function brainById(id) {
|
|
5519
|
+
return BRAINS.find((brain) => brain.id === id) ?? null;
|
|
5520
|
+
}
|
|
5521
|
+
function describeBrain(config) {
|
|
5522
|
+
const brain = brainById(config.id);
|
|
5523
|
+
return `${brain?.label ?? config.id} (${config.model}, effort ${config.effort})`;
|
|
5524
|
+
}
|
|
5525
|
+
async function confirm(question) {
|
|
5526
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
5527
|
+
try {
|
|
5528
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
5529
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
5530
|
+
} finally {
|
|
5531
|
+
rl.close();
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
async function requireBrain(opts = {}) {
|
|
5535
|
+
const config = await loadConfig();
|
|
5536
|
+
const chosen = config.brain ?? DEFAULT_BRAIN;
|
|
5537
|
+
const interactive = opts.interactive ?? (process.stdin.isTTY && process.stdout.isTTY);
|
|
5538
|
+
const connection = await codexConnection();
|
|
5539
|
+
if (connection.state === "connected") {
|
|
5540
|
+
if (!config.brain) await saveConfig({ brain: chosen });
|
|
5541
|
+
return chosen;
|
|
5542
|
+
}
|
|
5543
|
+
if (!interactive) {
|
|
5544
|
+
fail(`${connectionSummary(connection)}
|
|
5545
|
+
${howToFix(connection) ?? ""}`.trim());
|
|
5546
|
+
}
|
|
5547
|
+
if (connection.state === "not_installed") {
|
|
5548
|
+
fail(`${connectionSummary(connection)}
|
|
5549
|
+
${howToFix(connection) ?? ""}`.trim());
|
|
5550
|
+
}
|
|
5551
|
+
const brain = brainById(chosen.id);
|
|
5552
|
+
out("");
|
|
5553
|
+
out(`${c.bold("HigherDEV needs your ChatGPT subscription to answer.")}`);
|
|
5554
|
+
out(c.dim(`${brain?.billing ?? ""}. The sign-in happens in your browser; hd never sees the token.`));
|
|
5555
|
+
if (!await confirm(c.dim("Press enter to sign in, or n to cancel: "))) {
|
|
5556
|
+
fail("Not connected. Run `hd brain` when you want to.");
|
|
5557
|
+
}
|
|
5558
|
+
const connected = await connectCodex();
|
|
5559
|
+
if (connected.state !== "connected") {
|
|
5560
|
+
fail(`${connectionSummary(connected)}
|
|
5561
|
+
${howToFix(connected) ?? ""}`.trim());
|
|
5562
|
+
}
|
|
5563
|
+
out(c.green(connectionSummary(connected)));
|
|
5564
|
+
await saveConfig({ brain: chosen });
|
|
5565
|
+
return chosen;
|
|
5566
|
+
}
|
|
5567
|
+
async function setBrain(patch) {
|
|
5568
|
+
const config = await loadConfig();
|
|
5569
|
+
const next = { ...config.brain ?? DEFAULT_BRAIN, ...patch };
|
|
5570
|
+
const brain = brainById(next.id);
|
|
5571
|
+
if (!brain) fail(`Unknown brain ${next.id}. One of: ${BRAINS.map((row) => row.id).join(", ")}.`);
|
|
5572
|
+
if (patch.model && !brain.models.includes(patch.model)) {
|
|
5573
|
+
out(c.yellow(`${patch.model} is not one of the models known to work here (${brain.models.join(", ")}).`));
|
|
5574
|
+
}
|
|
5575
|
+
await saveConfig({ brain: next });
|
|
5576
|
+
return next;
|
|
5577
|
+
}
|
|
5578
|
+
|
|
5411
5579
|
// src/architect/repo.ts
|
|
5412
5580
|
import { readFile as readFile6, readdir, stat } from "fs/promises";
|
|
5413
5581
|
import path5 from "path";
|
|
@@ -5564,18 +5732,6 @@ function buildRepoTools(root) {
|
|
|
5564
5732
|
|
|
5565
5733
|
// src/architect/plan.ts
|
|
5566
5734
|
var run2 = promisify2(execFile2);
|
|
5567
|
-
var ARCHITECT_ROLE = "architect";
|
|
5568
|
-
async function architectAgent(ctx) {
|
|
5569
|
-
const { data, error } = await ctx.db.from("agents").select("*").eq("workspace_id", ctx.workspace.id).eq("role", ARCHITECT_ROLE).maybeSingle();
|
|
5570
|
-
if (error) fail(error.message);
|
|
5571
|
-
if (!data) {
|
|
5572
|
-
fail(
|
|
5573
|
-
"This workspace has no architect agent. Apply the migrations, or add one with:\n hd agent new Architect --provider codex --model gpt-5.6-sol"
|
|
5574
|
-
);
|
|
5575
|
-
}
|
|
5576
|
-
if (!data.enabled) fail(`${data.display_name} is disabled. Enable it with \`hd agent set ${data.display_name} --enable\`.`);
|
|
5577
|
-
return data;
|
|
5578
|
-
}
|
|
5579
5735
|
async function repoCheckout(ctx) {
|
|
5580
5736
|
const here = process.cwd();
|
|
5581
5737
|
try {
|
|
@@ -5632,19 +5788,12 @@ function architectInstructions(opts) {
|
|
|
5632
5788
|
"conventions, budgets, pausing. The orchestrator has no action for any of it.",
|
|
5633
5789
|
"",
|
|
5634
5790
|
opts.readOnly ? "This session is read-only. Propose in your reply; the tools that change things are not loaded." : "Say what you filed and what the owner should look at.",
|
|
5635
|
-
"Answer conversationally and briefly. Lead with the answer. Never use an em dash."
|
|
5636
|
-
opts.addendum ? `
|
|
5637
|
-
${opts.addendum}` : ""
|
|
5791
|
+
"Answer conversationally and briefly. Lead with the answer. Never use an em dash."
|
|
5638
5792
|
].filter(Boolean).join("\n");
|
|
5639
5793
|
}
|
|
5640
5794
|
async function openArchitect(opts) {
|
|
5641
5795
|
const { ctx } = opts;
|
|
5642
|
-
const
|
|
5643
|
-
const connection = await codexConnection();
|
|
5644
|
-
if (connection.state !== "connected" && !opts.allowApiBilling) {
|
|
5645
|
-
fail(`${connectionSummary(connection)}
|
|
5646
|
-
${howToFix(connection) ?? ""}`.trim());
|
|
5647
|
-
}
|
|
5796
|
+
const brain = await requireBrain();
|
|
5648
5797
|
const repo = await repoCheckout(ctx);
|
|
5649
5798
|
const platformTools = toolsFor(opts.readOnly ? "read" : "all").map((tool) => ({
|
|
5650
5799
|
name: tool.name,
|
|
@@ -5653,10 +5802,9 @@ ${howToFix(connection) ?? ""}`.trim());
|
|
|
5653
5802
|
run: (args) => tool.run(ctx, args)
|
|
5654
5803
|
}));
|
|
5655
5804
|
return {
|
|
5656
|
-
|
|
5805
|
+
brain,
|
|
5657
5806
|
instructions: architectInstructions({
|
|
5658
5807
|
ctx,
|
|
5659
|
-
addendum: agent.prompt_addendum,
|
|
5660
5808
|
readOnly: opts.readOnly,
|
|
5661
5809
|
hasRepo: Boolean(repo)
|
|
5662
5810
|
}),
|
|
@@ -5671,10 +5819,12 @@ async function architectTurn(opts) {
|
|
|
5671
5819
|
const { data: runRow, error: runError } = await ctx.db.from("runs").insert({
|
|
5672
5820
|
workspace_id: ctx.workspace.id,
|
|
5673
5821
|
ticket_id: null,
|
|
5674
|
-
|
|
5822
|
+
// No agent: the brain is the operator's own model, not a worker in this
|
|
5823
|
+
// workspace. The run exists so what it spends is still visible.
|
|
5824
|
+
agent_id: null,
|
|
5675
5825
|
kind: "architect",
|
|
5676
|
-
provider:
|
|
5677
|
-
model: session.
|
|
5826
|
+
provider: "codex",
|
|
5827
|
+
model: session.brain.model,
|
|
5678
5828
|
status: "running",
|
|
5679
5829
|
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5680
5830
|
session_id: session.sessionId,
|
|
@@ -5693,7 +5843,7 @@ async function architectTurn(opts) {
|
|
|
5693
5843
|
instructions: session.instructions,
|
|
5694
5844
|
history: session.history,
|
|
5695
5845
|
tools: session.tools,
|
|
5696
|
-
model: session.
|
|
5846
|
+
model: session.brain.model,
|
|
5697
5847
|
sessionId: session.sessionId,
|
|
5698
5848
|
signal: opts.signal,
|
|
5699
5849
|
onEvent: (event) => {
|
|
@@ -5715,7 +5865,7 @@ async function architectTurn(opts) {
|
|
|
5715
5865
|
turns: result.steps,
|
|
5716
5866
|
summary: result.text.slice(0, 500) || "Architect turn"
|
|
5717
5867
|
}).eq("id", runRow.id);
|
|
5718
|
-
await ctx.audit(ctx.workspace.id, "architect", { subject: session.
|
|
5868
|
+
await ctx.audit(ctx.workspace.id, "architect", { subject: describeBrain(session.brain) });
|
|
5719
5869
|
return { text: result.text, runId: runRow.id, toolCalls: result.toolCalls };
|
|
5720
5870
|
} catch (error) {
|
|
5721
5871
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -5738,7 +5888,7 @@ function slugOf6(command) {
|
|
|
5738
5888
|
return command.optsWithGlobals().workspace;
|
|
5739
5889
|
}
|
|
5740
5890
|
async function askLine(prompt2) {
|
|
5741
|
-
const rl =
|
|
5891
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
5742
5892
|
try {
|
|
5743
5893
|
return await new Promise((resolve) => {
|
|
5744
5894
|
rl.once("close", () => resolve(""));
|
|
@@ -5759,16 +5909,12 @@ ${howToFix(connection) ?? ""}`.trim()
|
|
|
5759
5909
|
);
|
|
5760
5910
|
}
|
|
5761
5911
|
function registerArchitectCommands(program) {
|
|
5762
|
-
program.command("plan").argument("[request...]", "what to plan, assess, or change; omit for a conversation").description("the architect: assess the repo, shape the work, tune the platform").option("--read-only", "let it look and propose, but change nothing").option("--once", "run a single turn and exit, even without a request").
|
|
5912
|
+
program.command("plan").argument("[request...]", "what to plan, assess, or change; omit for a conversation").description("the architect: assess the repo, shape the work, tune the platform").option("--read-only", "let it look and propose, but change nothing").option("--once", "run a single turn and exit, even without a request").action(async function(request) {
|
|
5763
5913
|
const opts = this.opts();
|
|
5764
5914
|
const ctx = await requireWorkspace(slugOf6(this));
|
|
5765
5915
|
const first = request.join(" ").trim();
|
|
5766
5916
|
const conversational = !first && !opts.once && process.stdin.isTTY && !isJsonMode();
|
|
5767
|
-
const session = await openArchitect({
|
|
5768
|
-
ctx,
|
|
5769
|
-
readOnly: Boolean(opts.readOnly),
|
|
5770
|
-
allowApiBilling: Boolean(opts.allowApiBilling)
|
|
5771
|
-
});
|
|
5917
|
+
const session = await openArchitect({ ctx, readOnly: Boolean(opts.readOnly) });
|
|
5772
5918
|
let wroteText = false;
|
|
5773
5919
|
const onEvent = (event) => {
|
|
5774
5920
|
if (isJsonMode()) return;
|
|
@@ -5811,7 +5957,7 @@ function registerArchitectCommands(program) {
|
|
|
5811
5957
|
}
|
|
5812
5958
|
out(
|
|
5813
5959
|
c.dim(
|
|
5814
|
-
|
|
5960
|
+
`${describeBrain(session.brain)}${session.repo ? ", repo readable" : ""}. Blank line or /exit leaves.`
|
|
5815
5961
|
)
|
|
5816
5962
|
);
|
|
5817
5963
|
for (; ; ) {
|
|
@@ -5836,6 +5982,33 @@ function registerArchitectCommands(program) {
|
|
|
5836
5982
|
}
|
|
5837
5983
|
await serveMcp({ readOnly, workspace: slugOf6(this) });
|
|
5838
5984
|
});
|
|
5985
|
+
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() {
|
|
5986
|
+
const opts = this.opts();
|
|
5987
|
+
if (opts.model || opts.effort) {
|
|
5988
|
+
const next = await setBrain({
|
|
5989
|
+
...opts.model ? { model: String(opts.model) } : {},
|
|
5990
|
+
...opts.effort ? { effort: String(opts.effort) } : {}
|
|
5991
|
+
});
|
|
5992
|
+
ok(describeBrain(next), { brain: next });
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
const config = await loadConfig();
|
|
5996
|
+
const chosen = config.brain ?? DEFAULT_BRAIN;
|
|
5997
|
+
const connection = await codexConnection();
|
|
5998
|
+
emit(
|
|
5999
|
+
{ brain: chosen, connected: connection.state === "connected", codex: connection },
|
|
6000
|
+
() => [
|
|
6001
|
+
`${c.bold(describeBrain(chosen))}`,
|
|
6002
|
+
...BRAINS.map(
|
|
6003
|
+
(brain) => ` ${brain.id === chosen.id ? c.blue("*") : " "} ${brain.label.padEnd(24)} ${c.dim(brain.billing)}`
|
|
6004
|
+
),
|
|
6005
|
+
"",
|
|
6006
|
+
connection.state === "connected" ? c.green(connectionSummary(connection)) : `${c.yellow(connectionSummary(connection))}
|
|
6007
|
+
${howToFix(connection) ?? ""}`,
|
|
6008
|
+
c.dim("Models: " + (brainById(chosen.id)?.models.join(", ") ?? ""))
|
|
6009
|
+
].join("\n")
|
|
6010
|
+
);
|
|
6011
|
+
});
|
|
5839
6012
|
program.command("connect").description("connect Codex to your ChatGPT subscription, so the architect costs nothing per token").option("--check", "report the state without starting a login").action(async function() {
|
|
5840
6013
|
let connection = await codexConnection();
|
|
5841
6014
|
if (connection.state === "connected" || this.opts().check) {
|
|
@@ -5952,7 +6125,7 @@ ${runner.HD_HELP}`);
|
|
|
5952
6125
|
|
|
5953
6126
|
// src/host/upgrade.ts
|
|
5954
6127
|
import { execFile as execFile5 } from "child_process";
|
|
5955
|
-
import { createInterface as
|
|
6128
|
+
import { createInterface as createInterface4 } from "readline/promises";
|
|
5956
6129
|
import { promisify as promisify5 } from "util";
|
|
5957
6130
|
init_json();
|
|
5958
6131
|
init_theme();
|
|
@@ -6001,7 +6174,7 @@ async function offerUpdate(now = Date.now()) {
|
|
|
6001
6174
|
await saveUpdateState({ checked_at: now });
|
|
6002
6175
|
if (!latest || !isNewer(latest, VERSION)) return;
|
|
6003
6176
|
if (state.skipped === latest) return;
|
|
6004
|
-
const rl =
|
|
6177
|
+
const rl = createInterface4({ input: process.stdin, output: process.stderr });
|
|
6005
6178
|
let answer = "";
|
|
6006
6179
|
try {
|
|
6007
6180
|
answer = (await rl.question(
|