zelari-code 0.7.5 → 0.7.9
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 +27 -1
- package/dist/cli/app.js +50 -18
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/components/Sidebar.js +86 -39
- package/dist/cli/components/Sidebar.js.map +1 -1
- package/dist/cli/components/SplashScreen.js +204 -0
- package/dist/cli/components/SplashScreen.js.map +1 -0
- package/dist/cli/components/StatusBar.js +22 -18
- package/dist/cli/components/StatusBar.js.map +1 -1
- package/dist/cli/councilConfig.js +38 -0
- package/dist/cli/councilConfig.js.map +1 -0
- package/dist/cli/councilDispatcher.js +38 -32
- package/dist/cli/councilDispatcher.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +80 -61
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useExecutionTimer.js +25 -0
- package/dist/cli/hooks/useExecutionTimer.js.map +1 -0
- package/dist/cli/hooks/useGitChanges.js +142 -0
- package/dist/cli/hooks/useGitChanges.js.map +1 -0
- package/dist/cli/hooks/useSlashDispatch.js +9 -2
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/main.bundled.js +2560 -1231
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +9 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/mcpClient.js +14 -6
- package/dist/cli/mcp/mcpClient.js.map +1 -1
- package/dist/cli/updater.js +8 -5
- package/dist/cli/updater.js.map +1 -1
- package/dist/cli/utils/cmdline.js +26 -0
- package/dist/cli/utils/cmdline.js.map +1 -0
- package/dist/cli/utils/paths.js +20 -0
- package/dist/cli/utils/paths.js.map +1 -0
- package/dist/cli/wizard/index.js +1 -1
- package/dist/cli/wizard/index.js.map +1 -1
- package/dist/cli/workspace/completeDesign.js +128 -0
- package/dist/cli/workspace/completeDesign.js.map +1 -0
- package/dist/cli/workspace/planDetect.js +17 -0
- package/dist/cli/workspace/planDetect.js.map +1 -0
- package/dist/cli/workspace/postCouncilHook.js +157 -20
- package/dist/cli/workspace/postCouncilHook.js.map +1 -1
- package/dist/cli/workspace/stubs.js +406 -225
- package/dist/cli/workspace/stubs.js.map +1 -1
- package/package.json +71 -71
package/dist/cli/main.bundled.js
CHANGED
|
@@ -141,12 +141,13 @@ var init_skills = __esm({
|
|
|
141
141
|
color: "#10b981",
|
|
142
142
|
enabledByDefault: true,
|
|
143
143
|
builtin: true,
|
|
144
|
-
requiredTools: ["createTask", "createPhase", "createMilestone", "updateTask"],
|
|
144
|
+
requiredTools: ["createPlan", "createTask", "createPhase", "createMilestone", "updateTask"],
|
|
145
145
|
systemPromptFragment: `You plan projects hierarchically.
|
|
146
146
|
- Decompose work into phases, then tasks within phases.
|
|
147
147
|
- For each task include: title, description, priority (low|medium|high|critical), and optional tags/subtasks.
|
|
148
148
|
- Define milestones to mark key deliverables.
|
|
149
|
-
- When relevant, reference file paths and acceptance criteria
|
|
149
|
+
- When relevant, reference file paths and acceptance criteria.
|
|
150
|
+
- Prefer ONE createPlan call (phases with nested tasks + milestone) over many itemized createPhase/createTask calls.`
|
|
150
151
|
},
|
|
151
152
|
{
|
|
152
153
|
id: "idea-synthesizer",
|
|
@@ -15864,7 +15865,7 @@ function resolveBashWindows() {
|
|
|
15864
15865
|
return p3;
|
|
15865
15866
|
}
|
|
15866
15867
|
try {
|
|
15867
|
-
const result = spawnSync("where", ["bash"], {
|
|
15868
|
+
const result = spawnSync("where", ["bash"], { encoding: "utf-8", windowsHide: true });
|
|
15868
15869
|
if (result.status === 0 && result.stdout) {
|
|
15869
15870
|
const first = result.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
|
|
15870
15871
|
if (first && existsSyncSafe(first))
|
|
@@ -17043,6 +17044,61 @@ var init_toolSchemas = __esm({
|
|
|
17043
17044
|
"use strict";
|
|
17044
17045
|
init_tools();
|
|
17045
17046
|
PARAM_SCHEMAS = {
|
|
17047
|
+
createPlan: {
|
|
17048
|
+
type: "object",
|
|
17049
|
+
properties: {
|
|
17050
|
+
phases: {
|
|
17051
|
+
type: "array",
|
|
17052
|
+
description: "Ordered list of plan phases, each with its tasks nested (aim for 3 tasks per phase)",
|
|
17053
|
+
items: {
|
|
17054
|
+
type: "object",
|
|
17055
|
+
properties: {
|
|
17056
|
+
name: { type: "string", description: "Phase name" },
|
|
17057
|
+
description: { type: "string", description: "What this phase delivers and its exit criterion" },
|
|
17058
|
+
order: { type: "number", description: "Position of the phase in the plan (1-based)" },
|
|
17059
|
+
color: { type: "string", description: 'Hex color, e.g. "#3b82f6"' },
|
|
17060
|
+
tasks: {
|
|
17061
|
+
type: "array",
|
|
17062
|
+
description: "Concrete tasks for this phase",
|
|
17063
|
+
items: {
|
|
17064
|
+
type: "object",
|
|
17065
|
+
properties: {
|
|
17066
|
+
title: { type: "string", description: "Concise verb-led task title" },
|
|
17067
|
+
description: { type: "string", description: "2-3 sentences of context" },
|
|
17068
|
+
fileRefs: {
|
|
17069
|
+
type: "array",
|
|
17070
|
+
items: { type: "string" },
|
|
17071
|
+
description: 'File paths with line ranges, e.g. "src/App.tsx:L10-L40"'
|
|
17072
|
+
},
|
|
17073
|
+
acceptance: {
|
|
17074
|
+
type: "array",
|
|
17075
|
+
items: { type: "string" },
|
|
17076
|
+
description: "Concrete, testable acceptance criteria"
|
|
17077
|
+
},
|
|
17078
|
+
qaScenario: { type: "string", description: "Step-by-step manual QA scenario" },
|
|
17079
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] }
|
|
17080
|
+
},
|
|
17081
|
+
required: ["title"]
|
|
17082
|
+
}
|
|
17083
|
+
}
|
|
17084
|
+
},
|
|
17085
|
+
required: ["name"]
|
|
17086
|
+
}
|
|
17087
|
+
},
|
|
17088
|
+
milestone: {
|
|
17089
|
+
type: "object",
|
|
17090
|
+
description: "The milestone this plan ships (e.g. the design-complete milestone)",
|
|
17091
|
+
properties: {
|
|
17092
|
+
title: { type: "string", description: "Milestone title" },
|
|
17093
|
+
description: { type: "string", description: "Milestone description" },
|
|
17094
|
+
targetVersion: { type: "string", description: 'Target version, e.g. "v0.1.0"' },
|
|
17095
|
+
dueDate: { type: "string", description: "Due date (ISO string)" }
|
|
17096
|
+
},
|
|
17097
|
+
required: ["title"]
|
|
17098
|
+
}
|
|
17099
|
+
},
|
|
17100
|
+
required: ["phases"]
|
|
17101
|
+
},
|
|
17046
17102
|
createTask: {
|
|
17047
17103
|
type: "object",
|
|
17048
17104
|
properties: {
|
|
@@ -17051,6 +17107,17 @@ var init_toolSchemas = __esm({
|
|
|
17051
17107
|
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
17052
17108
|
phaseId: { type: "string", description: "ID of the phase this task belongs to" },
|
|
17053
17109
|
parentId: { type: "string", description: "ID of the parent task, if nested" },
|
|
17110
|
+
fileRefs: {
|
|
17111
|
+
type: "array",
|
|
17112
|
+
items: { type: "string" },
|
|
17113
|
+
description: 'File paths with line ranges where the work lands, e.g. "src/App.tsx:L10-L40"'
|
|
17114
|
+
},
|
|
17115
|
+
acceptance: {
|
|
17116
|
+
type: "array",
|
|
17117
|
+
items: { type: "string" },
|
|
17118
|
+
description: "Concrete, testable acceptance criteria"
|
|
17119
|
+
},
|
|
17120
|
+
qaScenario: { type: "string", description: "Step-by-step manual QA scenario" },
|
|
17054
17121
|
tags: { type: "array", items: { type: "string" } },
|
|
17055
17122
|
subtasks: { type: "array", items: { type: "string" }, description: "Subtask titles" }
|
|
17056
17123
|
},
|
|
@@ -17180,6 +17247,7 @@ var init_toolSchemas = __esm({
|
|
|
17180
17247
|
properties: {
|
|
17181
17248
|
title: { type: "string", description: "Milestone title" },
|
|
17182
17249
|
description: { type: "string", description: "Milestone description" },
|
|
17250
|
+
targetVersion: { type: "string", description: 'Target version this milestone ships, e.g. "v0.1.0"' },
|
|
17183
17251
|
dueDate: { type: "string", description: "Due date (ISO string)" }
|
|
17184
17252
|
},
|
|
17185
17253
|
required: ["title"]
|
|
@@ -17543,7 +17611,7 @@ async function refreshGrokToken(options) {
|
|
|
17543
17611
|
return parseTokenResponseBody(obj, accessToken);
|
|
17544
17612
|
}
|
|
17545
17613
|
async function openBrowser(url2) {
|
|
17546
|
-
const { spawn:
|
|
17614
|
+
const { spawn: spawn5 } = await import("node:child_process");
|
|
17547
17615
|
const cmd = (() => {
|
|
17548
17616
|
switch (process.platform) {
|
|
17549
17617
|
case "darwin":
|
|
@@ -17556,7 +17624,7 @@ async function openBrowser(url2) {
|
|
|
17556
17624
|
})();
|
|
17557
17625
|
return new Promise((resolve, reject) => {
|
|
17558
17626
|
try {
|
|
17559
|
-
const child =
|
|
17627
|
+
const child = spawn5(cmd.bin, cmd.args, {
|
|
17560
17628
|
stdio: "ignore",
|
|
17561
17629
|
detached: true
|
|
17562
17630
|
});
|
|
@@ -18605,12 +18673,12 @@ import {
|
|
|
18605
18673
|
realpathSync
|
|
18606
18674
|
} from "node:fs";
|
|
18607
18675
|
import { join, basename } from "node:path";
|
|
18608
|
-
import { homedir } from "node:os";
|
|
18676
|
+
import { homedir as homedir2 } from "node:os";
|
|
18609
18677
|
import { createHash } from "node:crypto";
|
|
18610
18678
|
function resolveWorkspaceRoot(projectRoot = process.cwd()) {
|
|
18611
18679
|
const candidates = [
|
|
18612
18680
|
join(projectRoot, ".zelari"),
|
|
18613
|
-
join(
|
|
18681
|
+
join(homedir2(), ".zelari-code", "workspace", hashProject(projectRoot))
|
|
18614
18682
|
];
|
|
18615
18683
|
for (const candidate of candidates) {
|
|
18616
18684
|
if (isWritableDir(projectRoot) || candidate !== candidates[0]) {
|
|
@@ -19202,9 +19270,17 @@ __export(stubs_exports, {
|
|
|
19202
19270
|
createWorkspaceContext: () => createWorkspaceContext,
|
|
19203
19271
|
createWorkspaceStubs: () => createWorkspaceStubs,
|
|
19204
19272
|
deriveProjectName: () => projectName,
|
|
19273
|
+
readPlanSummary: () => readPlanSummary,
|
|
19205
19274
|
resolveWorkspaceRoot: () => resolveWorkspaceRoot
|
|
19206
19275
|
});
|
|
19207
|
-
import {
|
|
19276
|
+
import {
|
|
19277
|
+
existsSync as existsSync9,
|
|
19278
|
+
readdirSync as readdirSync3,
|
|
19279
|
+
writeFileSync as writeFileSync6,
|
|
19280
|
+
readFileSync as readFileSync6,
|
|
19281
|
+
mkdirSync as mkdirSync7,
|
|
19282
|
+
renameSync as renameSync3
|
|
19283
|
+
} from "node:fs";
|
|
19208
19284
|
import { join as join4, basename as basename2, dirname as dirname2, relative as relative2 } from "node:path";
|
|
19209
19285
|
function createWorkspaceContext(projectRoot = process.cwd()) {
|
|
19210
19286
|
const rootDir = resolveWorkspaceRoot(projectRoot);
|
|
@@ -19221,7 +19297,9 @@ function readPlan(ctx) {
|
|
|
19221
19297
|
const jsonPath = planJsonPath(ctx);
|
|
19222
19298
|
if (existsSync9(jsonPath)) {
|
|
19223
19299
|
try {
|
|
19224
|
-
const parsed = JSON.parse(
|
|
19300
|
+
const parsed = JSON.parse(
|
|
19301
|
+
readFileSync6(jsonPath, "utf8")
|
|
19302
|
+
);
|
|
19225
19303
|
return {
|
|
19226
19304
|
phases: Array.isArray(parsed.phases) ? parsed.phases : [],
|
|
19227
19305
|
tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
|
|
@@ -19265,8 +19343,12 @@ function renderPlanBody(summary) {
|
|
|
19265
19343
|
if (summary.phases.length === 0) {
|
|
19266
19344
|
lines.push(`_(none yet)_`);
|
|
19267
19345
|
} else {
|
|
19268
|
-
for (const phase of [...summary.phases].sort(
|
|
19269
|
-
|
|
19346
|
+
for (const phase of [...summary.phases].sort(
|
|
19347
|
+
(a, b) => (a.order ?? 0) - (b.order ?? 0)
|
|
19348
|
+
)) {
|
|
19349
|
+
lines.push(
|
|
19350
|
+
`### ${phase.order ?? "?"}. ${phase.id} \`${phase.color ?? ""}\``
|
|
19351
|
+
);
|
|
19270
19352
|
lines.push("");
|
|
19271
19353
|
}
|
|
19272
19354
|
}
|
|
@@ -19316,8 +19398,108 @@ function nextAdrId(ctx) {
|
|
|
19316
19398
|
function slugify2(s) {
|
|
19317
19399
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
19318
19400
|
}
|
|
19401
|
+
function addPhaseRecord(summary, input) {
|
|
19402
|
+
const id = slugify2(input.name) || `phase-${input.order}`;
|
|
19403
|
+
if (summary.phases.some((p3) => p3.id === id)) {
|
|
19404
|
+
return { id, created: false };
|
|
19405
|
+
}
|
|
19406
|
+
summary.phases.push({
|
|
19407
|
+
kind: "phase",
|
|
19408
|
+
id,
|
|
19409
|
+
name: input.name,
|
|
19410
|
+
description: input.description,
|
|
19411
|
+
order: input.order,
|
|
19412
|
+
color: input.color
|
|
19413
|
+
});
|
|
19414
|
+
return { id, created: true };
|
|
19415
|
+
}
|
|
19416
|
+
function addTaskRecord(ctx, summary, phaseId, t, options) {
|
|
19417
|
+
const slug = slugify2(t.title);
|
|
19418
|
+
if (options.dedupe) {
|
|
19419
|
+
const existing = summary.tasks.find(
|
|
19420
|
+
(k) => k.phaseId === phaseId && k.id.replace(/-\d+$/, "") === `${phaseId}-${slug}`
|
|
19421
|
+
);
|
|
19422
|
+
if (existing) return { id: existing.id, created: false };
|
|
19423
|
+
}
|
|
19424
|
+
const id = `${phaseId}-${slug}-${summary.tasks.filter((k) => k.phaseId === phaseId).length + 1}`;
|
|
19425
|
+
summary.tasks.push({
|
|
19426
|
+
kind: "task",
|
|
19427
|
+
id,
|
|
19428
|
+
// v0.7.3: keep the human-readable title in plan.json so
|
|
19429
|
+
// buildPlanSummary can render it in the system prompt.
|
|
19430
|
+
name: t.title,
|
|
19431
|
+
phaseId,
|
|
19432
|
+
status: "pending",
|
|
19433
|
+
priority: t.priority
|
|
19434
|
+
});
|
|
19435
|
+
const taskPath = join4(ctx.rootDir, "plan-tasks", `${id}.md`);
|
|
19436
|
+
const meta3 = {
|
|
19437
|
+
kind: "task",
|
|
19438
|
+
id,
|
|
19439
|
+
phaseId,
|
|
19440
|
+
status: "pending",
|
|
19441
|
+
priority: t.priority,
|
|
19442
|
+
tags: t.fileRefs
|
|
19443
|
+
};
|
|
19444
|
+
const body = [
|
|
19445
|
+
`# ${t.title}`,
|
|
19446
|
+
"",
|
|
19447
|
+
t.description,
|
|
19448
|
+
"",
|
|
19449
|
+
`## File references`,
|
|
19450
|
+
...t.fileRefs.map((f) => `- \`${f}\``),
|
|
19451
|
+
"",
|
|
19452
|
+
`## Acceptance criteria`,
|
|
19453
|
+
...t.acceptance.map((a) => `- ${a}`),
|
|
19454
|
+
"",
|
|
19455
|
+
`## QA scenario`,
|
|
19456
|
+
"",
|
|
19457
|
+
t.qaScenario || "_(not specified)_",
|
|
19458
|
+
""
|
|
19459
|
+
].join("\n");
|
|
19460
|
+
ctx.storage.write(taskPath, meta3, body);
|
|
19461
|
+
return { id, created: true };
|
|
19462
|
+
}
|
|
19463
|
+
function addMilestoneRecord(ctx, summary, input) {
|
|
19464
|
+
const id = `m-${slugify2(input.title)}`;
|
|
19465
|
+
if (summary.milestones.some((m) => m.id === id)) {
|
|
19466
|
+
return { id, created: false };
|
|
19467
|
+
}
|
|
19468
|
+
const version2 = input.targetVersion ?? input.dueDate ?? "TBD";
|
|
19469
|
+
summary.milestones.push({
|
|
19470
|
+
kind: "milestone",
|
|
19471
|
+
id,
|
|
19472
|
+
name: input.title,
|
|
19473
|
+
description: input.description,
|
|
19474
|
+
dueDate: input.dueDate,
|
|
19475
|
+
targetVersion: version2
|
|
19476
|
+
});
|
|
19477
|
+
const path22 = join4(ctx.rootDir, "milestones", `${id}.md`);
|
|
19478
|
+
const meta3 = {
|
|
19479
|
+
kind: "milestone",
|
|
19480
|
+
id,
|
|
19481
|
+
name: input.title,
|
|
19482
|
+
description: input.description,
|
|
19483
|
+
dueDate: input.dueDate,
|
|
19484
|
+
targetVersion: version2
|
|
19485
|
+
};
|
|
19486
|
+
const body = [
|
|
19487
|
+
`# Milestone: ${input.title}`,
|
|
19488
|
+
"",
|
|
19489
|
+
input.description,
|
|
19490
|
+
"",
|
|
19491
|
+
`Target version: ${version2}`,
|
|
19492
|
+
""
|
|
19493
|
+
].join("\n");
|
|
19494
|
+
ctx.storage.write(path22, meta3, body);
|
|
19495
|
+
return { id, created: true };
|
|
19496
|
+
}
|
|
19497
|
+
function readPlanSummary(ctx) {
|
|
19498
|
+
return readPlan(ctx);
|
|
19499
|
+
}
|
|
19319
19500
|
function createWorkspaceStubs(ctx) {
|
|
19320
19501
|
return [
|
|
19502
|
+
createPlanStub(ctx),
|
|
19321
19503
|
createPhaseStub(ctx),
|
|
19322
19504
|
createTaskStub(ctx),
|
|
19323
19505
|
updateTaskStub(ctx),
|
|
@@ -19329,6 +19511,67 @@ function createWorkspaceStubs(ctx) {
|
|
|
19329
19511
|
getDocumentBacklinksStub(ctx)
|
|
19330
19512
|
];
|
|
19331
19513
|
}
|
|
19514
|
+
function createPlanStub(ctx) {
|
|
19515
|
+
return {
|
|
19516
|
+
name: "createPlan",
|
|
19517
|
+
description: "Create the FULL project plan in one call: all phases, the tasks nested under each phase, and the target milestone. Preferred over itemized createPhase/createTask/createMilestone calls \u2014 one createPlan call persists everything atomically.",
|
|
19518
|
+
category: "project",
|
|
19519
|
+
parameters: [],
|
|
19520
|
+
execute: async (args) => {
|
|
19521
|
+
return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
|
|
19522
|
+
const rawPhases = Array.isArray(args["phases"]) ? args["phases"] : [];
|
|
19523
|
+
if (rawPhases.length === 0) {
|
|
19524
|
+
return 'createPlan requires a non-empty "phases" array (each phase: { name, description, order, color, tasks: [{ title, description, fileRefs, acceptance, qaScenario, priority }] }).';
|
|
19525
|
+
}
|
|
19526
|
+
const summary = readPlan(ctx);
|
|
19527
|
+
let phasesCreated = 0;
|
|
19528
|
+
let tasksCreated = 0;
|
|
19529
|
+
const phaseIds = [];
|
|
19530
|
+
rawPhases.forEach((rawPhase, idx) => {
|
|
19531
|
+
const { id: phaseId, created } = addPhaseRecord(summary, {
|
|
19532
|
+
name: rawPhase["name"] ?? `Phase ${idx + 1}`,
|
|
19533
|
+
description: rawPhase["description"] ?? "",
|
|
19534
|
+
order: rawPhase["order"] ?? idx + 1,
|
|
19535
|
+
color: rawPhase["color"] ?? "#3b82f6"
|
|
19536
|
+
});
|
|
19537
|
+
if (created) phasesCreated += 1;
|
|
19538
|
+
phaseIds.push(phaseId);
|
|
19539
|
+
const rawTasks = Array.isArray(rawPhase["tasks"]) ? rawPhase["tasks"] : [];
|
|
19540
|
+
for (const rawTask of rawTasks) {
|
|
19541
|
+
const { created: taskCreated } = addTaskRecord(
|
|
19542
|
+
ctx,
|
|
19543
|
+
summary,
|
|
19544
|
+
phaseId,
|
|
19545
|
+
{
|
|
19546
|
+
title: rawTask["title"] ?? "New Task",
|
|
19547
|
+
description: rawTask["description"] ?? "",
|
|
19548
|
+
fileRefs: rawTask["fileRefs"] ?? rawTask["files"] ?? [],
|
|
19549
|
+
acceptance: rawTask["acceptance"] ?? [],
|
|
19550
|
+
qaScenario: rawTask["qaScenario"] ?? rawTask["qa"] ?? "",
|
|
19551
|
+
priority: rawTask["priority"] ?? "medium"
|
|
19552
|
+
},
|
|
19553
|
+
{ dedupe: true }
|
|
19554
|
+
);
|
|
19555
|
+
if (taskCreated) tasksCreated += 1;
|
|
19556
|
+
}
|
|
19557
|
+
});
|
|
19558
|
+
let milestonesCreated = 0;
|
|
19559
|
+
const rawMilestone = args["milestone"];
|
|
19560
|
+
if (rawMilestone && typeof rawMilestone === "object") {
|
|
19561
|
+
const { created } = addMilestoneRecord(ctx, summary, {
|
|
19562
|
+
title: rawMilestone["title"] ?? "v0.1.0 design-complete",
|
|
19563
|
+
description: rawMilestone["description"] ?? "",
|
|
19564
|
+
dueDate: rawMilestone["dueDate"],
|
|
19565
|
+
targetVersion: rawMilestone["targetVersion"]
|
|
19566
|
+
});
|
|
19567
|
+
if (created) milestonesCreated += 1;
|
|
19568
|
+
}
|
|
19569
|
+
writePlan(ctx, summary);
|
|
19570
|
+
return `Plan created: ${phasesCreated} phases, ${tasksCreated} tasks, ${milestonesCreated} milestone(s). Phase ids: ${phaseIds.join(", ")}.`;
|
|
19571
|
+
});
|
|
19572
|
+
}
|
|
19573
|
+
};
|
|
19574
|
+
}
|
|
19332
19575
|
function createPhaseStub(ctx) {
|
|
19333
19576
|
return {
|
|
19334
19577
|
name: "createPhase",
|
|
@@ -19338,22 +19581,17 @@ function createPhaseStub(ctx) {
|
|
|
19338
19581
|
execute: async (args) => {
|
|
19339
19582
|
return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
|
|
19340
19583
|
const name = args["name"] ?? "New Phase";
|
|
19341
|
-
const description = args["description"] ?? "";
|
|
19342
19584
|
const order = args["order"] ?? 0;
|
|
19343
|
-
const color = args["color"] ?? "#3b82f6";
|
|
19344
|
-
const id = slugify2(name) || `phase-${order}`;
|
|
19345
19585
|
const summary = readPlan(ctx);
|
|
19346
|
-
|
|
19347
|
-
return `Phase "${id}" already exists.`;
|
|
19348
|
-
}
|
|
19349
|
-
summary.phases.push({
|
|
19350
|
-
kind: "phase",
|
|
19351
|
-
id,
|
|
19586
|
+
const { id, created } = addPhaseRecord(summary, {
|
|
19352
19587
|
name,
|
|
19353
|
-
description,
|
|
19588
|
+
description: args["description"] ?? "",
|
|
19354
19589
|
order,
|
|
19355
|
-
color
|
|
19590
|
+
color: args["color"] ?? "#3b82f6"
|
|
19356
19591
|
});
|
|
19592
|
+
if (!created) {
|
|
19593
|
+
return `Phase "${id}" already exists.`;
|
|
19594
|
+
}
|
|
19357
19595
|
writePlan(ctx, summary);
|
|
19358
19596
|
return `Phase "${name}" created (id: ${id}, order: ${order}).`;
|
|
19359
19597
|
});
|
|
@@ -19370,54 +19608,26 @@ function createTaskStub(ctx) {
|
|
|
19370
19608
|
return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
|
|
19371
19609
|
const phaseId = args["phaseId"] ?? args["phase"];
|
|
19372
19610
|
const title = args["title"] ?? "New Task";
|
|
19373
|
-
const description = args["description"] ?? "";
|
|
19374
|
-
const fileRefs = args["fileRefs"] ?? args["files"] ?? [];
|
|
19375
|
-
const acceptance = args["acceptance"] ?? [];
|
|
19376
|
-
const qaScenario = args["qaScenario"] ?? args["qa"] ?? "";
|
|
19377
|
-
const priority = args["priority"] ?? "medium";
|
|
19378
19611
|
if (!phaseId) return "Task requires a phaseId.";
|
|
19379
19612
|
const summary = readPlan(ctx);
|
|
19380
19613
|
if (!summary.phases.some((p3) => p3.id === phaseId)) {
|
|
19381
19614
|
return `Phase "${phaseId}" not found. Create it first via /createPhase.`;
|
|
19382
19615
|
}
|
|
19383
|
-
const id =
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
id,
|
|
19387
|
-
// v0.7.3: keep the human-readable title in plan.json so
|
|
19388
|
-
// buildPlanSummary can render it in the system prompt.
|
|
19389
|
-
name: title,
|
|
19616
|
+
const { id } = addTaskRecord(
|
|
19617
|
+
ctx,
|
|
19618
|
+
summary,
|
|
19390
19619
|
phaseId,
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
|
|
19620
|
+
{
|
|
19621
|
+
title,
|
|
19622
|
+
description: args["description"] ?? "",
|
|
19623
|
+
fileRefs: args["fileRefs"] ?? args["files"] ?? [],
|
|
19624
|
+
acceptance: args["acceptance"] ?? [],
|
|
19625
|
+
qaScenario: args["qaScenario"] ?? args["qa"] ?? "",
|
|
19626
|
+
priority: args["priority"] ?? "medium"
|
|
19627
|
+
},
|
|
19628
|
+
{ dedupe: false }
|
|
19629
|
+
);
|
|
19394
19630
|
writePlan(ctx, summary);
|
|
19395
|
-
const taskPath = join4(ctx.rootDir, "plan-tasks", `${id}.md`);
|
|
19396
|
-
const meta3 = {
|
|
19397
|
-
kind: "task",
|
|
19398
|
-
id,
|
|
19399
|
-
phaseId,
|
|
19400
|
-
status: "pending",
|
|
19401
|
-
priority,
|
|
19402
|
-
tags: fileRefs
|
|
19403
|
-
};
|
|
19404
|
-
const body = [
|
|
19405
|
-
`# ${title}`,
|
|
19406
|
-
"",
|
|
19407
|
-
description,
|
|
19408
|
-
"",
|
|
19409
|
-
`## File references`,
|
|
19410
|
-
...fileRefs.map((f) => `- \`${f}\``),
|
|
19411
|
-
"",
|
|
19412
|
-
`## Acceptance criteria`,
|
|
19413
|
-
...acceptance.map((a) => `- ${a}`),
|
|
19414
|
-
"",
|
|
19415
|
-
`## QA scenario`,
|
|
19416
|
-
"",
|
|
19417
|
-
qaScenario || "_(not specified)_",
|
|
19418
|
-
""
|
|
19419
|
-
].join("\n");
|
|
19420
|
-
ctx.storage.write(taskPath, meta3, body);
|
|
19421
19631
|
return `Task "${title}" created (id: ${id}) under phase "${phaseId}".`;
|
|
19422
19632
|
});
|
|
19423
19633
|
}
|
|
@@ -19433,30 +19643,32 @@ function updateTaskStub(ctx) {
|
|
|
19433
19643
|
return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
|
|
19434
19644
|
const taskId = args["taskId"];
|
|
19435
19645
|
const rawStatus = args["status"];
|
|
19436
|
-
if (!taskId || !rawStatus)
|
|
19646
|
+
if (!taskId || !rawStatus)
|
|
19647
|
+
return "updateTask requires taskId and status.";
|
|
19437
19648
|
const valid = ["pending", "in_progress", "done", "blocked"];
|
|
19438
19649
|
const aliases = {
|
|
19439
|
-
|
|
19650
|
+
todo: "pending",
|
|
19440
19651
|
"to-do": "pending",
|
|
19441
|
-
|
|
19442
|
-
|
|
19652
|
+
open: "pending",
|
|
19653
|
+
backlog: "pending",
|
|
19443
19654
|
"in-progress": "in_progress",
|
|
19444
19655
|
"in progress": "in_progress",
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19656
|
+
doing: "in_progress",
|
|
19657
|
+
started: "in_progress",
|
|
19658
|
+
active: "in_progress",
|
|
19659
|
+
complete: "done",
|
|
19660
|
+
completed: "done",
|
|
19661
|
+
finished: "done",
|
|
19662
|
+
closed: "done",
|
|
19663
|
+
resolved: "done",
|
|
19664
|
+
stuck: "blocked",
|
|
19665
|
+
waiting: "blocked",
|
|
19455
19666
|
"on-hold": "blocked",
|
|
19456
19667
|
"on hold": "blocked"
|
|
19457
19668
|
};
|
|
19458
19669
|
const status = valid.includes(rawStatus) ? rawStatus : aliases[rawStatus.toLowerCase()];
|
|
19459
|
-
if (!status)
|
|
19670
|
+
if (!status)
|
|
19671
|
+
return `Invalid status: ${rawStatus}. Must be one of: ${valid.join(", ")}.`;
|
|
19460
19672
|
const summary = readPlan(ctx);
|
|
19461
19673
|
const task = summary.tasks.find((t) => t.id === taskId);
|
|
19462
19674
|
if (!task) return `Task "${taskId}" not found.`;
|
|
@@ -19523,39 +19735,16 @@ function createMilestoneStub(ctx) {
|
|
|
19523
19735
|
execute: async (args) => {
|
|
19524
19736
|
return workspaceMutex.run(`${ctx.rootDir}:plan`, () => {
|
|
19525
19737
|
const title = args["title"] ?? "New Milestone";
|
|
19526
|
-
const description = args["description"] ?? "";
|
|
19527
|
-
const dueDate = args["dueDate"];
|
|
19528
|
-
const id = `m-${slugify2(title)}`;
|
|
19529
19738
|
const summary = readPlan(ctx);
|
|
19530
|
-
|
|
19531
|
-
|
|
19532
|
-
|
|
19533
|
-
|
|
19534
|
-
|
|
19535
|
-
name: title,
|
|
19536
|
-
description,
|
|
19537
|
-
dueDate,
|
|
19538
|
-
targetVersion: version2
|
|
19739
|
+
const { id, created } = addMilestoneRecord(ctx, summary, {
|
|
19740
|
+
title,
|
|
19741
|
+
description: args["description"] ?? "",
|
|
19742
|
+
dueDate: args["dueDate"],
|
|
19743
|
+
targetVersion: args["targetVersion"]
|
|
19539
19744
|
});
|
|
19745
|
+
if (!created) return `Milestone "${id}" already exists.`;
|
|
19540
19746
|
writePlan(ctx, summary);
|
|
19541
|
-
const
|
|
19542
|
-
const meta3 = {
|
|
19543
|
-
kind: "milestone",
|
|
19544
|
-
id,
|
|
19545
|
-
name: title,
|
|
19546
|
-
description,
|
|
19547
|
-
dueDate,
|
|
19548
|
-
targetVersion: version2
|
|
19549
|
-
};
|
|
19550
|
-
const body = [
|
|
19551
|
-
`# Milestone: ${title}`,
|
|
19552
|
-
"",
|
|
19553
|
-
description,
|
|
19554
|
-
"",
|
|
19555
|
-
`Target version: ${version2}`,
|
|
19556
|
-
""
|
|
19557
|
-
].join("\n");
|
|
19558
|
-
ctx.storage.write(path22, meta3, body);
|
|
19747
|
+
const version2 = summary.milestones.find((m) => m.id === id)?.targetVersion ?? "TBD";
|
|
19559
19748
|
return `Milestone "${title}" created (id: ${id}, version: ${version2}).`;
|
|
19560
19749
|
});
|
|
19561
19750
|
}
|
|
@@ -19572,16 +19761,28 @@ function createDocumentStub(ctx) {
|
|
|
19572
19761
|
const title = args["title"] ?? "New Document";
|
|
19573
19762
|
const content = args["content"] ?? "";
|
|
19574
19763
|
const tags = args["tags"] ?? [];
|
|
19575
|
-
const
|
|
19576
|
-
const
|
|
19764
|
+
const normalizedTitle = title.replace(/\.(md|markdown)\s*$/i, "");
|
|
19765
|
+
const slug = slugify2(normalizedTitle) || `doc-${Date.now()}`;
|
|
19766
|
+
if (slug === "risks") {
|
|
19767
|
+
const risksPath = workspaceFile(ctx.rootDir, "risks");
|
|
19768
|
+
const riskMeta = {
|
|
19769
|
+
kind: "risk",
|
|
19770
|
+
id: "risks",
|
|
19771
|
+
severity: "medium",
|
|
19772
|
+
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
19773
|
+
};
|
|
19774
|
+
ctx.storage.write(risksPath, riskMeta, content);
|
|
19775
|
+
return `Document "${title}" created at risks.md (workspace root).`;
|
|
19776
|
+
}
|
|
19777
|
+
const path22 = workspaceArtifact(ctx.rootDir, "docs", slug);
|
|
19577
19778
|
const meta3 = {
|
|
19578
19779
|
kind: "doc",
|
|
19579
|
-
id,
|
|
19780
|
+
id: slug,
|
|
19580
19781
|
date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
19581
19782
|
tags
|
|
19582
19783
|
};
|
|
19583
19784
|
ctx.storage.write(path22, meta3, content);
|
|
19584
|
-
return `Document "${title}" created at docs/${
|
|
19785
|
+
return `Document "${title}" created at docs/${slug}.md.`;
|
|
19585
19786
|
});
|
|
19586
19787
|
}
|
|
19587
19788
|
};
|
|
@@ -19602,7 +19803,9 @@ function searchDocumentsStub(ctx) {
|
|
|
19602
19803
|
const limit = args["limit"] ?? 5;
|
|
19603
19804
|
if (!rawQuery) return "searchDocuments requires a query.";
|
|
19604
19805
|
const phrases = rawQuery.toLowerCase().split(/\s+or\s+/i).map((p3) => p3.trim()).filter((p3) => p3.length > 0);
|
|
19605
|
-
const words = rawQuery.toLowerCase().split(/[^a-z0-9_-]+/).filter(
|
|
19806
|
+
const words = rawQuery.toLowerCase().split(/[^a-z0-9_-]+/).filter(
|
|
19807
|
+
(w) => w.length >= 3 && w !== "or" && w !== "and" && w !== "the"
|
|
19808
|
+
);
|
|
19606
19809
|
const files = [
|
|
19607
19810
|
...ctx.storage.listMarkdown(join4(ctx.rootDir, "decisions")),
|
|
19608
19811
|
...ctx.storage.listMarkdown(join4(ctx.rootDir, "docs")),
|
|
@@ -19641,10 +19844,14 @@ function searchDocumentsStub(ctx) {
|
|
|
19641
19844
|
const start = Math.max(0, idx - 40);
|
|
19642
19845
|
const end = Math.min(raw.length, idx + matchLen + 40);
|
|
19643
19846
|
const snippet = "\u2026" + raw.slice(start, end).replace(/\n/g, " ").trim() + "\u2026";
|
|
19644
|
-
results.push({
|
|
19847
|
+
results.push({
|
|
19848
|
+
path: relative2(ctx.rootDir, file2) || basename2(file2),
|
|
19849
|
+
snippet
|
|
19850
|
+
});
|
|
19645
19851
|
if (results.length >= limit) break;
|
|
19646
19852
|
}
|
|
19647
|
-
if (results.length === 0)
|
|
19853
|
+
if (results.length === 0)
|
|
19854
|
+
return `${nudge}No matches for "${rawQuery}" in workspace.`;
|
|
19648
19855
|
return nudge + results.map((r) => `[${r.path}]
|
|
19649
19856
|
${r.snippet}`).join("\n\n");
|
|
19650
19857
|
}
|
|
@@ -19658,8 +19865,8 @@ function linkDocumentsStub(ctx) {
|
|
|
19658
19865
|
parameters: [],
|
|
19659
19866
|
execute: async (args) => {
|
|
19660
19867
|
return workspaceMutex.run(`${ctx.rootDir}:link`, () => {
|
|
19661
|
-
const fromId = args["fromId"];
|
|
19662
|
-
const toId = args["toId"];
|
|
19868
|
+
const fromId = args["fromId"] ?? args["sourceId"];
|
|
19869
|
+
const toId = args["toId"] ?? args["targetId"] ?? args["targetPathOrTitle"];
|
|
19663
19870
|
if (!fromId || !toId) return "linkDocuments requires fromId and toId.";
|
|
19664
19871
|
const allFiles = [
|
|
19665
19872
|
...ctx.storage.listMarkdown(join4(ctx.rootDir, "decisions")),
|
|
@@ -19692,7 +19899,7 @@ function getDocumentBacklinksStub(ctx) {
|
|
|
19692
19899
|
category: "analysis",
|
|
19693
19900
|
parameters: [],
|
|
19694
19901
|
execute: async (args) => {
|
|
19695
|
-
const targetId = args["targetId"];
|
|
19902
|
+
const targetId = args["targetId"] ?? args["id"];
|
|
19696
19903
|
if (!targetId) return "getDocumentBacklinks requires targetId.";
|
|
19697
19904
|
const allFiles = [
|
|
19698
19905
|
...ctx.storage.listMarkdown(join4(ctx.rootDir, "decisions")),
|
|
@@ -19702,7 +19909,9 @@ function getDocumentBacklinksStub(ctx) {
|
|
|
19702
19909
|
const backlinks = [];
|
|
19703
19910
|
for (const file2 of allFiles) {
|
|
19704
19911
|
try {
|
|
19705
|
-
const doc = ctx.storage.read(
|
|
19912
|
+
const doc = ctx.storage.read(
|
|
19913
|
+
file2
|
|
19914
|
+
);
|
|
19706
19915
|
if (doc.meta.id === targetId) continue;
|
|
19707
19916
|
if ((doc.meta.related ?? []).includes(targetId)) {
|
|
19708
19917
|
backlinks.push(doc.meta.id);
|
|
@@ -19776,12 +19985,28 @@ var init_toolRegistry = __esm({
|
|
|
19776
19985
|
}
|
|
19777
19986
|
});
|
|
19778
19987
|
|
|
19988
|
+
// src/cli/utils/cmdline.ts
|
|
19989
|
+
function quoteCmdArg(arg) {
|
|
19990
|
+
if (arg === "") return '""';
|
|
19991
|
+
if (!/[\s"^&|<>()%!]/.test(arg)) return arg;
|
|
19992
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
19993
|
+
}
|
|
19994
|
+
function buildCmdLine(command, args) {
|
|
19995
|
+
return [command, ...args].map(quoteCmdArg).join(" ");
|
|
19996
|
+
}
|
|
19997
|
+
var init_cmdline = __esm({
|
|
19998
|
+
"src/cli/utils/cmdline.ts"() {
|
|
19999
|
+
"use strict";
|
|
20000
|
+
}
|
|
20001
|
+
});
|
|
20002
|
+
|
|
19779
20003
|
// src/cli/mcp/mcpClient.ts
|
|
19780
20004
|
import { spawn as spawn2 } from "node:child_process";
|
|
19781
20005
|
var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
|
|
19782
20006
|
var init_mcpClient = __esm({
|
|
19783
20007
|
"src/cli/mcp/mcpClient.ts"() {
|
|
19784
20008
|
"use strict";
|
|
20009
|
+
init_cmdline();
|
|
19785
20010
|
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
19786
20011
|
INIT_TIMEOUT_MS = 15e3;
|
|
19787
20012
|
MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
@@ -19798,14 +20023,15 @@ var init_mcpClient = __esm({
|
|
|
19798
20023
|
/** Spawn the server process and run the MCP initialize handshake. */
|
|
19799
20024
|
async start() {
|
|
19800
20025
|
if (this.child) return;
|
|
19801
|
-
const
|
|
20026
|
+
const spawnOpts = {
|
|
19802
20027
|
stdio: ["pipe", "pipe", "pipe"],
|
|
19803
20028
|
env: { ...process.env, ...this.config.env ?? {} },
|
|
19804
|
-
// On Windows `npx`/`uvx` resolve to .cmd shims which plain spawn
|
|
19805
|
-
// cannot execute; shell:true lets cmd.exe resolve them.
|
|
19806
|
-
shell: process.platform === "win32",
|
|
19807
20029
|
windowsHide: true
|
|
19808
|
-
}
|
|
20030
|
+
};
|
|
20031
|
+
const child = process.platform === "win32" ? spawn2(buildCmdLine(this.config.command, this.config.args ?? []), {
|
|
20032
|
+
...spawnOpts,
|
|
20033
|
+
shell: true
|
|
20034
|
+
}) : spawn2(this.config.command, this.config.args ?? [], spawnOpts);
|
|
19809
20035
|
this.child = child;
|
|
19810
20036
|
child.stdout.setEncoding("utf8");
|
|
19811
20037
|
child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
@@ -19820,7 +20046,7 @@ var init_mcpClient = __esm({
|
|
|
19820
20046
|
{
|
|
19821
20047
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
19822
20048
|
capabilities: {},
|
|
19823
|
-
clientInfo: { name: "zelari-code", version: "0.7.
|
|
20049
|
+
clientInfo: { name: "zelari-code", version: "0.7.9" }
|
|
19824
20050
|
},
|
|
19825
20051
|
INIT_TIMEOUT_MS
|
|
19826
20052
|
);
|
|
@@ -19922,11 +20148,11 @@ __export(mcpManager_exports, {
|
|
|
19922
20148
|
});
|
|
19923
20149
|
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
|
|
19924
20150
|
import { join as join5 } from "node:path";
|
|
19925
|
-
import { homedir as
|
|
20151
|
+
import { homedir as homedir3 } from "node:os";
|
|
19926
20152
|
function readMcpConfig(projectRoot = process.cwd()) {
|
|
19927
20153
|
const merged = {};
|
|
19928
20154
|
const paths = [
|
|
19929
|
-
join5(
|
|
20155
|
+
join5(homedir3(), ".zelari-code", "mcp.json"),
|
|
19930
20156
|
join5(projectRoot, ".zelari", "mcp.json")
|
|
19931
20157
|
// later = higher precedence
|
|
19932
20158
|
];
|
|
@@ -20135,7 +20361,40 @@ Example format:
|
|
|
20135
20361
|
- Make dependencies explicit (task B depends on task A).
|
|
20136
20362
|
- If the stack/platform is unknown, ask ONCE (see clarification protocol) rather than guessing.
|
|
20137
20363
|
|
|
20138
|
-
Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROTOCOL8}
|
|
20364
|
+
Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROTOCOL8}
|
|
20365
|
+
|
|
20366
|
+
## Design-phase mandatory plan artifact
|
|
20367
|
+
In design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), you MUST persist the plan through workspace tools. The plan exists ONLY if it was persisted via a tool call \u2014 prose does not count, and the post-run check will flag your run if the plan is missing.
|
|
20368
|
+
|
|
20369
|
+
PREFERRED \u2014 emit ONE single \`createPlan\` call containing the whole plan: 4 phases, 12 concrete tasks (3 per phase), and 1 milestone, all in the same call:
|
|
20370
|
+
|
|
20371
|
+
\`\`\`
|
|
20372
|
+
createPlan({
|
|
20373
|
+
phases: [
|
|
20374
|
+
{
|
|
20375
|
+
name: "Foundation & Technical Blueprint",
|
|
20376
|
+
description: "Lock the stack and the non-negotiable quality budget. Exit: baseline builds green.",
|
|
20377
|
+
order: 1,
|
|
20378
|
+
color: "#3b82f6",
|
|
20379
|
+
tasks: [
|
|
20380
|
+
{ title: "Lock stack baseline", description: "2-3 sentences of context", fileRefs: ["src/main.tsx:L1-L40"], acceptance: ["App boots with strict TS", "CI build green"], qaScenario: "Run npm run build and confirm zero errors", priority: "high" },
|
|
20381
|
+
{ title: "Define NFR budget", description: "...", fileRefs: ["docs/nfr.md"], acceptance: ["LCP/WCAG/Lighthouse targets documented"], qaScenario: "...", priority: "high" },
|
|
20382
|
+
{ title: "Document design-phase exit criteria", description: "...", fileRefs: ["docs/exit-criteria.md"], acceptance: ["Each phase has a testable exit row"], qaScenario: "...", priority: "medium" }
|
|
20383
|
+
]
|
|
20384
|
+
},
|
|
20385
|
+
{ name: "...", order: 2, tasks: [ /* 3 tasks */ ] },
|
|
20386
|
+
{ name: "...", order: 3, tasks: [ /* 3 tasks */ ] },
|
|
20387
|
+
{ name: "...", order: 4, tasks: [ /* 3 tasks */ ] }
|
|
20388
|
+
],
|
|
20389
|
+
milestone: { title: "v0.1.0 design-complete", description: "All design artifacts exist and the green-light checklist passes.", targetVersion: "v0.1.0" }
|
|
20390
|
+
})
|
|
20391
|
+
\`\`\`
|
|
20392
|
+
|
|
20393
|
+
Every task MUST include fileRefs, acceptance, and qaScenario \u2014 the QA scenario proves the task is verifiable.
|
|
20394
|
+
|
|
20395
|
+
FALLBACK \u2014 only if you cannot batch, use the itemized tools: \`createPhase\` once per phase; each createPhase response returns the new phase id \u2014 use that exact id as \`phaseId\` in the 3 \`createTask\` calls for that phase; finish with one \`createMilestone\` ({ title, description, targetVersion }). If you forget an id, call \`searchDocuments\` (limit 1) to look it up, then continue.
|
|
20396
|
+
|
|
20397
|
+
Do NOT output tasks as prose. The system will refuse any task that does not pass a valid phaseId.`,
|
|
20139
20398
|
// v0.7.2: read/search the codebase to ground the plan in reality.
|
|
20140
20399
|
tools: ["list_files", "read_file", "grep_content"],
|
|
20141
20400
|
skills: ["project-planner", "vault-manager"]
|
|
@@ -20166,10 +20425,21 @@ Keep plans hierarchical and practical. Stay under 250 words.${CLARIFICATION_PROT
|
|
|
20166
20425
|
## Themes
|
|
20167
20426
|
## Top picks (with feasibility/novelty + de-risk note)
|
|
20168
20427
|
|
|
20169
|
-
Stay under 200 words.${CLARIFICATION_PROTOCOL8}
|
|
20428
|
+
Stay under 200 words.${CLARIFICATION_PROTOCOL8}
|
|
20429
|
+
|
|
20430
|
+
## Design-phase artifact (mandatory when running council in design-phase mode)
|
|
20431
|
+
If the council is in design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), you MUST also persist your ideation output as workspace documents via the \`createDocument\` tool. Emit AT LEAST 3 separate \`createDocument\` calls, each tagged with one of these categories:
|
|
20432
|
+
|
|
20433
|
+
- \`customer-journey-map\` \u2014 2-3 personas (give them names, demographics, goals), journey table (stage \u2192 action \u2192 touchpoint \u2192 emotion), pain points per stage. This is a customer journey deliverable.
|
|
20434
|
+
- \`information-architecture\` \u2014 site map tree (root \u2192 section \u2192 page), navigation model (primary nav, breadcrumbs, footer), URL patterns. This is an information architecture deliverable.
|
|
20435
|
+
- \`design-tokens\` \u2014 color palette (semantic + hex), typography scale, spacing scale, motion principles. This is a design tokens deliverable.
|
|
20436
|
+
|
|
20437
|
+
You may optionally add a 4th design system doc if the project warrants it.
|
|
20438
|
+
|
|
20439
|
+
Pass the tool call as: \`createDocument({ title: "<category>", content: "<markdown body>" })\`. Do NOT summarize these into prose \u2014 they are the deliverable.`,
|
|
20170
20440
|
// v0.7.2: explore the codebase to ground ideas in what exists.
|
|
20171
20441
|
tools: ["list_files", "read_file"],
|
|
20172
|
-
skills: ["
|
|
20442
|
+
skills: ["document-writer", "mind-mapper"]
|
|
20173
20443
|
},
|
|
20174
20444
|
{
|
|
20175
20445
|
id: "pluton",
|
|
@@ -20194,10 +20464,17 @@ Stay under 200 words.${CLARIFICATION_PROTOCOL8}`,
|
|
|
20194
20464
|
- Keep the graph comprehensible: prune redundant or duplicate nodes.
|
|
20195
20465
|
|
|
20196
20466
|
## Output format
|
|
20197
|
-
Describe the proposed structure (root \u2192 branches \u2192 leaves) in text
|
|
20467
|
+
Describe the proposed structure (root \u2192 branches \u2192 leaves) in text. Stay under 200 words.${CLARIFICATION_PROTOCOL8}
|
|
20468
|
+
|
|
20469
|
+
## Design-phase artifact (mandatory when running council in design-phase mode)
|
|
20470
|
+
Persist the knowledge map as ONE \`createDocument\` call:
|
|
20471
|
+
|
|
20472
|
+
\`createDocument({ title: "knowledge-map", content: "<markdown: root concept, branches, leaf nodes, and cross-links>" })\`
|
|
20473
|
+
|
|
20474
|
+
Do NOT rely on buildMindMap \u2014 it is not available in the CLI workspace.`,
|
|
20198
20475
|
// v0.7.2: map the actual code/module structure instead of an abstract mind-map.
|
|
20199
20476
|
tools: ["list_files", "read_file", "grep_content"],
|
|
20200
|
-
skills: ["
|
|
20477
|
+
skills: ["document-writer", "research-analyst"]
|
|
20201
20478
|
},
|
|
20202
20479
|
{
|
|
20203
20480
|
id: "minos",
|
|
@@ -20226,9 +20503,21 @@ Describe the proposed structure (root \u2192 branches \u2192 leaves) in text and
|
|
|
20226
20503
|
## Contradictions
|
|
20227
20504
|
## Top improvements
|
|
20228
20505
|
|
|
20229
|
-
Stay under 200 words
|
|
20506
|
+
Stay under 200 words.${CLARIFICATION_PROTOCOL8}
|
|
20507
|
+
|
|
20508
|
+
## Design-phase risks artifact (mandatory when running council in design-phase mode)
|
|
20509
|
+
When the council is in design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), you ALSO write a \`risks\` document via the \`createDocument\` tool. This is your one allowed artifact emission. Pass the tool call as:
|
|
20510
|
+
|
|
20511
|
+
\`\`\`
|
|
20512
|
+
createDocument({
|
|
20513
|
+
title: "risks",
|
|
20514
|
+
content: \`# Risks\\n\\n## 1. <Risk title>\\n- Impact: <high|medium|low>\\n- Likelihood: <high|medium|low>\\n- Mitigation: <one-line mitigation>\\n\\n## 2. ...\`
|
|
20515
|
+
})
|
|
20516
|
+
\`\`\`
|
|
20517
|
+
|
|
20518
|
+
Include AT LEAST 5 risks, each scored on Impact and Likelihood, with a one-line mitigation. Cover: technical (e.g. stack risk), product (e.g. scope creep), accessibility, performance, security. The artifact is persisted at \`.zelari/risks.md\` (workspace root), NOT under docs/. Do NOT emit other workspace artifacts \u2014 your role is to evaluate, not to build.`,
|
|
20230
20519
|
tools: [],
|
|
20231
|
-
skills: ["research-analyst"]
|
|
20520
|
+
skills: ["document-writer", "research-analyst"]
|
|
20232
20521
|
},
|
|
20233
20522
|
{
|
|
20234
20523
|
id: "lucifer",
|
|
@@ -20251,7 +20540,19 @@ Stay under 200 words. (You do not create workspace artifacts, so you do not emit
|
|
|
20251
20540
|
- Apply Minosse's highest-value improvements; drop descoped items.
|
|
20252
20541
|
- After making changes, verify they work (compile, run tests, etc.) when feasible.
|
|
20253
20542
|
|
|
20254
|
-
Use the tools available to you (read_file, write_file, edit_file, bash, list_files) directly as tool calls \u2014 the harness handles execution.${CLARIFICATION_PROTOCOL8}
|
|
20543
|
+
Use the tools available to you (read_file, write_file, edit_file, bash, list_files) directly as tool calls \u2014 the harness handles execution.${CLARIFICATION_PROTOCOL8}
|
|
20544
|
+
|
|
20545
|
+
## Design-phase synthesis artifact (mandatory when running council in design-phase mode)
|
|
20546
|
+
When the council is in design-phase mode (TASK mentions design/architecture/spec, no existing codebase to edit), your final deliverable is a \`synthesis\` document via the \`createDocument\` tool (NOT \`write_file\` and NOT \`list_files\`):
|
|
20547
|
+
|
|
20548
|
+
\`\`\`
|
|
20549
|
+
createDocument({
|
|
20550
|
+
title: "synthesis",
|
|
20551
|
+
content: \`# Council Synthesis\\n\\n## Executive summary\\n<2-3 paragraphs>\\n\\n## Stack and key decisions\\n- <ADR-by-ADR summary>\\n\\n## Phases\\n<1-2 line summary per phase>\\n\\n## Top risks\\n<top 3 risks from Minosse>\\n\\n## Green-light checklist\\n- [ ] All ADRs accepted\\n- [ ] Risks have mitigations\\n- [ ] Tasks have acceptance criteria\`
|
|
20552
|
+
})
|
|
20553
|
+
\`\`\`
|
|
20554
|
+
|
|
20555
|
+
DO NOT call \`list_files\` \u2014 it is NOT a workspace tool. Use \`searchDocuments\` if you need to look something up (limit 2-3 searches, then act on the results). Your deliverable is the synthesis document; emit it via \`createDocument\`, not via prose.`,
|
|
20255
20556
|
// v0.7.2: implementation tools — the synthesizer writes/edits files and runs commands.
|
|
20256
20557
|
tools: ["read_file", "write_file", "edit_file", "bash", "list_files"],
|
|
20257
20558
|
skills: ["vault-manager", "project-planner", "idea-synthesizer"]
|
|
@@ -20609,6 +20910,57 @@ var init_systemPromptBuilder = __esm({
|
|
|
20609
20910
|
}
|
|
20610
20911
|
});
|
|
20611
20912
|
|
|
20913
|
+
// packages/core/dist/council/modeBanners.js
|
|
20914
|
+
function councilModeBanner(runMode) {
|
|
20915
|
+
return runMode === "design-phase" ? DESIGN_PHASE_MODE_BANNER : IMPLEMENTATION_MODE_BANNER;
|
|
20916
|
+
}
|
|
20917
|
+
var DESIGN_PHASE_MODE_BANNER, IMPLEMENTATION_MODE_BANNER;
|
|
20918
|
+
var init_modeBanners = __esm({
|
|
20919
|
+
"packages/core/dist/council/modeBanners.js"() {
|
|
20920
|
+
"use strict";
|
|
20921
|
+
DESIGN_PHASE_MODE_BANNER = `COUNCIL RUN MODE: design-phase.
|
|
20922
|
+
Workspace tool emissions described in your role prompt are MANDATORY in this run \u2014 prose alone does not count as a deliverable. Persist artifacts via the workspace tools listed in your AVAILABLE TOOLS section.`;
|
|
20923
|
+
IMPLEMENTATION_MODE_BANNER = `COUNCIL RUN MODE: implementation.
|
|
20924
|
+
Prefer write_file, edit_file, and bash for code changes. Design-phase mandatory workspace blocks in your role prompt are INACTIVE in this run \u2014 only call workspace tools when they add durable project value.`;
|
|
20925
|
+
}
|
|
20926
|
+
});
|
|
20927
|
+
|
|
20928
|
+
// packages/core/dist/council/runMode.js
|
|
20929
|
+
function resolveCouncilRunMode(input) {
|
|
20930
|
+
if (input.forceMode) {
|
|
20931
|
+
return input.forceMode;
|
|
20932
|
+
}
|
|
20933
|
+
const envMode = input.env?.ZELARI_COUNCIL_MODE?.toLowerCase();
|
|
20934
|
+
if (envMode === "design" || envMode === "design-phase") {
|
|
20935
|
+
return "design-phase";
|
|
20936
|
+
}
|
|
20937
|
+
if (envMode === "implementation" || envMode === "impl") {
|
|
20938
|
+
return "implementation";
|
|
20939
|
+
}
|
|
20940
|
+
const msg = input.userMessage;
|
|
20941
|
+
const hasDesign = DESIGN_KEYWORDS.test(msg);
|
|
20942
|
+
const hasImpl = IMPLEMENTATION_KEYWORDS.test(msg);
|
|
20943
|
+
if (hasDesign && !hasImpl) {
|
|
20944
|
+
return "design-phase";
|
|
20945
|
+
}
|
|
20946
|
+
if (PLAN_CONTINUE.test(msg) && input.hasExistingPlan) {
|
|
20947
|
+
return "design-phase";
|
|
20948
|
+
}
|
|
20949
|
+
return "implementation";
|
|
20950
|
+
}
|
|
20951
|
+
function councilTierFromSize(councilSize) {
|
|
20952
|
+
return councilSize >= 6 ? "full" : "lite";
|
|
20953
|
+
}
|
|
20954
|
+
var DESIGN_KEYWORDS, IMPLEMENTATION_KEYWORDS, PLAN_CONTINUE;
|
|
20955
|
+
var init_runMode = __esm({
|
|
20956
|
+
"packages/core/dist/council/runMode.js"() {
|
|
20957
|
+
"use strict";
|
|
20958
|
+
DESIGN_KEYWORDS = /\b(design|architect|architecture|spec|blueprint|mockup|wireframe|greenfield|from scratch)\b/i;
|
|
20959
|
+
IMPLEMENTATION_KEYWORDS = /\b(fix|refactor|bug|implement|patch|migrate|add tests|debug|repair|hotfix)\b/i;
|
|
20960
|
+
PLAN_CONTINUE = /\b(continue|extend|update|refine)\b[\s\S]{0,40}\b(plan|phase|milestone)\b/i;
|
|
20961
|
+
}
|
|
20962
|
+
});
|
|
20963
|
+
|
|
20612
20964
|
// packages/core/dist/agents/councilApi.js
|
|
20613
20965
|
function parseClarificationRequest(text) {
|
|
20614
20966
|
const start = text.indexOf(QUESTION_MARKER);
|
|
@@ -20641,7 +20993,7 @@ function parseThinking(text) {
|
|
|
20641
20993
|
function cleanAgentContent(text) {
|
|
20642
20994
|
return text.replace(/<think>[\s\S]*?<\/think>/g, "").replace(/<minimax:tool_call>[\s\S]*?<\/minimax:tool_call>/g, "").replace(/---QUESTION---[\s\S]*?---END---/g, "").trim();
|
|
20643
20995
|
}
|
|
20644
|
-
function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools) {
|
|
20996
|
+
function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = "implementation") {
|
|
20645
20997
|
const allToolNames = computeAgentTools(agent, aiConfig);
|
|
20646
20998
|
const toolNames = executableTools ? allToolNames.filter((n) => executableTools.has(n)) : allToolNames;
|
|
20647
20999
|
const enhancedSystemPrompt = buildSystemPrompt(agent, {
|
|
@@ -20653,6 +21005,7 @@ function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, pr
|
|
|
20653
21005
|
});
|
|
20654
21006
|
const messages = [
|
|
20655
21007
|
{ role: "system", content: enhancedSystemPrompt },
|
|
21008
|
+
{ role: "system", content: councilModeBanner(runMode) },
|
|
20656
21009
|
{ role: "system", content: "IMPORTANT: Before making any tool calls or expensive operations, check if the information already exists in the shared context from previous agents. Avoid redundant work." }
|
|
20657
21010
|
];
|
|
20658
21011
|
if (ragContext) {
|
|
@@ -20683,6 +21036,13 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20683
21036
|
...config2.existingOutputs ?? []
|
|
20684
21037
|
];
|
|
20685
21038
|
const sessionId = config2.sessionId ?? crypto.randomUUID();
|
|
21039
|
+
const runMode = config2.runMode ?? "implementation";
|
|
21040
|
+
const isDesignPhase = runMode === "design-phase";
|
|
21041
|
+
yield createBrainEvent("council_mode", sessionId, {
|
|
21042
|
+
tier: councilTierFromSize(config2.councilSize),
|
|
21043
|
+
councilSize: config2.councilSize,
|
|
21044
|
+
runMode
|
|
21045
|
+
});
|
|
20686
21046
|
yield {
|
|
20687
21047
|
type: "agent_start",
|
|
20688
21048
|
id: crypto.randomUUID(),
|
|
@@ -20707,8 +21067,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20707
21067
|
};
|
|
20708
21068
|
callbacks.onMemberCost?.(cost);
|
|
20709
21069
|
};
|
|
20710
|
-
const
|
|
20711
|
-
const
|
|
21070
|
+
const executorToolNames = config2.tools ? config2.tools.list() : [];
|
|
21071
|
+
const executableNames = config2.tools ? new Set(executorToolNames) : null;
|
|
21072
|
+
const filterExecutable = (names) => {
|
|
21073
|
+
if (!executableNames)
|
|
21074
|
+
return names;
|
|
21075
|
+
const merged = Array.from(/* @__PURE__ */ new Set([...names, ...executorToolNames]));
|
|
21076
|
+
return merged.filter((n) => executableNames.has(n));
|
|
21077
|
+
};
|
|
20712
21078
|
const allSpecialists = agents.filter((a) => a.id !== "lucifer" && a.id !== "minos");
|
|
20713
21079
|
const specialists = config2.feedbackStore ? config2.feedbackStore.ranked(allSpecialists) : allSpecialists;
|
|
20714
21080
|
const oracle = agents.find((a) => a.id === "minos");
|
|
@@ -20730,7 +21096,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20730
21096
|
model: effectiveModel,
|
|
20731
21097
|
provider: effectiveProvider,
|
|
20732
21098
|
sessionId,
|
|
20733
|
-
messages: buildAgentMessages(agent, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames),
|
|
21099
|
+
messages: buildAgentMessages(agent, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames, runMode),
|
|
20734
21100
|
tools: agentTools,
|
|
20735
21101
|
eventBus: config2.eventBus,
|
|
20736
21102
|
toolRegistry: config2.tools,
|
|
@@ -20751,12 +21117,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20751
21117
|
let toolCalls = 0;
|
|
20752
21118
|
let usage = null;
|
|
20753
21119
|
let errored = false;
|
|
21120
|
+
const emittedToolNames = [];
|
|
20754
21121
|
const memberStart = Date.now();
|
|
20755
21122
|
try {
|
|
20756
21123
|
for await (const event of harness.run()) {
|
|
20757
21124
|
yield event;
|
|
20758
21125
|
if (event.type === "tool_execution_start") {
|
|
20759
21126
|
toolCalls += 1;
|
|
21127
|
+
emittedToolNames.push(event.toolName);
|
|
20760
21128
|
}
|
|
20761
21129
|
if (event.type === "message_end" && event.usage) {
|
|
20762
21130
|
usage = event.usage;
|
|
@@ -20774,6 +21142,27 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20774
21142
|
fullText = `Error: ${err instanceof Error ? err.message : "Unknown"}`;
|
|
20775
21143
|
errored = true;
|
|
20776
21144
|
}
|
|
21145
|
+
if (isDesignPhase && !errored && !NON_RETRY_AGENTS.has(agent.id)) {
|
|
21146
|
+
const specialistCheck = enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
|
|
21147
|
+
yield* applyRetryIfMissing({
|
|
21148
|
+
agent,
|
|
21149
|
+
check: specialistCheck,
|
|
21150
|
+
requirements: DESIGN_PHASE_REQUIREMENTS[agent.id],
|
|
21151
|
+
emittedToolNames,
|
|
21152
|
+
executableNames,
|
|
21153
|
+
sessionId,
|
|
21154
|
+
userMessage,
|
|
21155
|
+
agentOutputs,
|
|
21156
|
+
config: config2,
|
|
21157
|
+
effectiveProvider,
|
|
21158
|
+
effectiveModel,
|
|
21159
|
+
onToolCall: () => {
|
|
21160
|
+
toolCalls += 1;
|
|
21161
|
+
}
|
|
21162
|
+
});
|
|
21163
|
+
} else if (isDesignPhase) {
|
|
21164
|
+
enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
|
|
21165
|
+
}
|
|
20777
21166
|
const memberDuration = Date.now() - memberStart;
|
|
20778
21167
|
emitMemberCost({
|
|
20779
21168
|
memberId: agent.id,
|
|
@@ -20819,7 +21208,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20819
21208
|
});
|
|
20820
21209
|
}
|
|
20821
21210
|
}
|
|
20822
|
-
if (
|
|
21211
|
+
if (oracle && !completedIds.has(oracle.id)) {
|
|
20823
21212
|
callbacks.onAgentStart?.(oracle);
|
|
20824
21213
|
const override = config2.agentModels?.[oracle.id];
|
|
20825
21214
|
const effectiveProvider = override?.providerId ?? config2.provider ?? "minimax";
|
|
@@ -20833,8 +21222,19 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20833
21222
|
model: effectiveModel,
|
|
20834
21223
|
provider: effectiveProvider,
|
|
20835
21224
|
sessionId,
|
|
20836
|
-
messages: buildAgentMessages(oracle, `Review these proposals for: "${userMessage}"`, "", "", anonymized, config2.aiConfig, executableNames),
|
|
20837
|
-
tools:
|
|
21225
|
+
messages: buildAgentMessages(oracle, `Review these proposals for: "${userMessage}"`, "", "", anonymized, config2.aiConfig, executableNames, runMode),
|
|
21226
|
+
tools: (() => {
|
|
21227
|
+
const oracleToolNames = filterExecutable(Array.from(/* @__PURE__ */ new Set([
|
|
21228
|
+
"createDocument",
|
|
21229
|
+
"searchDocuments",
|
|
21230
|
+
...computeAgentTools(oracle, config2.aiConfig)
|
|
21231
|
+
])));
|
|
21232
|
+
return oracleToolNames.length > 0 ? getProviderTools(oracleToolNames).map((tool) => ({
|
|
21233
|
+
name: tool.function.name,
|
|
21234
|
+
description: tool.function.description,
|
|
21235
|
+
parameters: tool.function.parameters
|
|
21236
|
+
})) : [];
|
|
21237
|
+
})(),
|
|
20838
21238
|
eventBus: config2.eventBus,
|
|
20839
21239
|
toolRegistry: config2.tools,
|
|
20840
21240
|
// Task G.2 — same per-turn limit applies to oracle.
|
|
@@ -20852,12 +21252,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20852
21252
|
let toolCalls = 0;
|
|
20853
21253
|
let usage = null;
|
|
20854
21254
|
let errored = false;
|
|
21255
|
+
const emittedToolNames = [];
|
|
20855
21256
|
const memberStart = Date.now();
|
|
20856
21257
|
try {
|
|
20857
21258
|
for await (const event of harness.run()) {
|
|
20858
21259
|
yield event;
|
|
20859
21260
|
if (event.type === "tool_execution_start") {
|
|
20860
21261
|
toolCalls += 1;
|
|
21262
|
+
emittedToolNames.push(event.toolName);
|
|
20861
21263
|
}
|
|
20862
21264
|
if (event.type === "message_end" && event.usage) {
|
|
20863
21265
|
usage = event.usage;
|
|
@@ -20875,6 +21277,27 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20875
21277
|
fullText = `Review error: ${err instanceof Error ? err.message : "Unknown"}`;
|
|
20876
21278
|
errored = true;
|
|
20877
21279
|
}
|
|
21280
|
+
if (isDesignPhase && !errored) {
|
|
21281
|
+
const oracleCheck = enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
|
|
21282
|
+
yield* applyRetryIfMissing({
|
|
21283
|
+
agent: oracle,
|
|
21284
|
+
check: oracleCheck,
|
|
21285
|
+
requirements: DESIGN_PHASE_REQUIREMENTS[oracle.id],
|
|
21286
|
+
emittedToolNames,
|
|
21287
|
+
executableNames,
|
|
21288
|
+
sessionId,
|
|
21289
|
+
userMessage: `Review these proposals for: "${userMessage}"`,
|
|
21290
|
+
agentOutputs,
|
|
21291
|
+
config: config2,
|
|
21292
|
+
effectiveProvider,
|
|
21293
|
+
effectiveModel,
|
|
21294
|
+
onToolCall: () => {
|
|
21295
|
+
toolCalls += 1;
|
|
21296
|
+
}
|
|
21297
|
+
});
|
|
21298
|
+
} else if (isDesignPhase) {
|
|
21299
|
+
enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
|
|
21300
|
+
}
|
|
20878
21301
|
const memberDuration = Date.now() - memberStart;
|
|
20879
21302
|
emitMemberCost({
|
|
20880
21303
|
memberId: oracle.id,
|
|
@@ -20929,7 +21352,7 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20929
21352
|
model: effectiveModel,
|
|
20930
21353
|
provider: effectiveProvider,
|
|
20931
21354
|
sessionId,
|
|
20932
|
-
messages: buildAgentMessages(chairman, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames),
|
|
21355
|
+
messages: buildAgentMessages(chairman, userMessage, config2.ragContext, config2.workspaceContext, agentOutputs, config2.aiConfig, executableNames, runMode),
|
|
20933
21356
|
tools: chairmanTools,
|
|
20934
21357
|
eventBus: config2.eventBus,
|
|
20935
21358
|
toolRegistry: config2.tools,
|
|
@@ -20948,12 +21371,14 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20948
21371
|
let usage = null;
|
|
20949
21372
|
let errored = false;
|
|
20950
21373
|
let lastErrorMessage = "";
|
|
21374
|
+
const emittedToolNames = [];
|
|
20951
21375
|
const memberStart = Date.now();
|
|
20952
21376
|
try {
|
|
20953
21377
|
for await (const event of chairmanHarness.run()) {
|
|
20954
21378
|
yield event;
|
|
20955
21379
|
if (event.type === "tool_execution_start") {
|
|
20956
21380
|
toolCalls += 1;
|
|
21381
|
+
emittedToolNames.push(event.toolName);
|
|
20957
21382
|
}
|
|
20958
21383
|
if (event.type === "message_end" && event.usage) {
|
|
20959
21384
|
usage = event.usage;
|
|
@@ -20975,6 +21400,27 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
20975
21400
|
errored = true;
|
|
20976
21401
|
lastErrorMessage = err instanceof Error ? err.message : String(err);
|
|
20977
21402
|
}
|
|
21403
|
+
if (isDesignPhase && !errored) {
|
|
21404
|
+
const chairmanCheck = enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
|
|
21405
|
+
yield* applyRetryIfMissing({
|
|
21406
|
+
agent: chairman,
|
|
21407
|
+
check: chairmanCheck,
|
|
21408
|
+
requirements: DESIGN_PHASE_REQUIREMENTS[chairman.id],
|
|
21409
|
+
emittedToolNames,
|
|
21410
|
+
executableNames,
|
|
21411
|
+
sessionId,
|
|
21412
|
+
userMessage,
|
|
21413
|
+
agentOutputs,
|
|
21414
|
+
config: config2,
|
|
21415
|
+
effectiveProvider,
|
|
21416
|
+
effectiveModel,
|
|
21417
|
+
onToolCall: () => {
|
|
21418
|
+
toolCalls += 1;
|
|
21419
|
+
}
|
|
21420
|
+
});
|
|
21421
|
+
} else if (isDesignPhase) {
|
|
21422
|
+
enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
|
|
21423
|
+
}
|
|
20978
21424
|
const memberDuration = Date.now() - memberStart;
|
|
20979
21425
|
const finalSynthesis = errored && fullText.length === 0 ? `[Chairman synthesis failed: ${lastErrorMessage || "unknown error"}]` : fullText;
|
|
20980
21426
|
callbacks.onSynthesisDone?.(finalSynthesis, void 0, void 0);
|
|
@@ -21008,7 +21454,143 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
|
|
|
21008
21454
|
durationMs: 0
|
|
21009
21455
|
};
|
|
21010
21456
|
}
|
|
21011
|
-
|
|
21457
|
+
function checkMemberToolEmissions(_memberId, emittedToolNames, requirements) {
|
|
21458
|
+
if (requirements.length === 0) {
|
|
21459
|
+
return { ok: true, missing: [] };
|
|
21460
|
+
}
|
|
21461
|
+
const counts = /* @__PURE__ */ new Map();
|
|
21462
|
+
for (const name of emittedToolNames) {
|
|
21463
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
21464
|
+
}
|
|
21465
|
+
const missing = [];
|
|
21466
|
+
for (const req of requirements) {
|
|
21467
|
+
const got = counts.get(req.name) ?? 0;
|
|
21468
|
+
if (got < req.min) {
|
|
21469
|
+
missing.push(`${req.name} (got ${got}, need >= ${req.min})`);
|
|
21470
|
+
}
|
|
21471
|
+
}
|
|
21472
|
+
return { ok: missing.length === 0, missing };
|
|
21473
|
+
}
|
|
21474
|
+
function checkMemberToolEmissionSets(memberId, emittedToolNames, sets) {
|
|
21475
|
+
if (sets.length === 0) {
|
|
21476
|
+
return { ok: true, missing: [] };
|
|
21477
|
+
}
|
|
21478
|
+
const results = sets.map((set2) => checkMemberToolEmissions(memberId, emittedToolNames, set2));
|
|
21479
|
+
if (results.some((r) => r.ok)) {
|
|
21480
|
+
return { ok: true, missing: [] };
|
|
21481
|
+
}
|
|
21482
|
+
return results[0];
|
|
21483
|
+
}
|
|
21484
|
+
function enforceDesignPhaseToolEmissions(memberId, emittedToolNames) {
|
|
21485
|
+
const sets = DESIGN_PHASE_REQUIREMENT_SETS[memberId];
|
|
21486
|
+
if (!sets || sets.length === 0) {
|
|
21487
|
+
return { ok: true, missing: [] };
|
|
21488
|
+
}
|
|
21489
|
+
const result = checkMemberToolEmissionSets(memberId, emittedToolNames, sets);
|
|
21490
|
+
if (!result.ok) {
|
|
21491
|
+
console.warn(`[council] member "${memberId}" did not emit required tools: ${result.missing.join(", ")}. The downstream .zelari/ deliverable may be incomplete. (A forced retry turn scoped to the missing tools follows; the deterministic complete-design fallback covers any remaining gap.)`);
|
|
21492
|
+
}
|
|
21493
|
+
return result;
|
|
21494
|
+
}
|
|
21495
|
+
function shouldRetryMember(missingToolNames, attemptsSoFar) {
|
|
21496
|
+
if (missingToolNames.length === 0)
|
|
21497
|
+
return false;
|
|
21498
|
+
if (attemptsSoFar >= MAX_RETRY_PER_MEMBER)
|
|
21499
|
+
return false;
|
|
21500
|
+
return true;
|
|
21501
|
+
}
|
|
21502
|
+
function buildRetryPrompt(missingToolNames) {
|
|
21503
|
+
const names = missingToolNames.join(", ");
|
|
21504
|
+
return `You did not emit the required workspace tools: ${names}. Call ${names} NOW with concrete arguments. No prose. No search.`;
|
|
21505
|
+
}
|
|
21506
|
+
async function* runRetryTurnForMember(args) {
|
|
21507
|
+
const executableMissing = args.executableTools ? args.missingToolNames.filter((n) => args.executableTools.has(n)) : args.missingToolNames;
|
|
21508
|
+
if (executableMissing.length === 0) {
|
|
21509
|
+
return [];
|
|
21510
|
+
}
|
|
21511
|
+
const retryToolNames = executableMissing;
|
|
21512
|
+
const retryToolSpecs = getProviderTools(retryToolNames).map((t) => ({
|
|
21513
|
+
name: t.function.name,
|
|
21514
|
+
description: t.function.description,
|
|
21515
|
+
parameters: t.function.parameters
|
|
21516
|
+
}));
|
|
21517
|
+
const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools, args.runMode ?? "implementation");
|
|
21518
|
+
const retryMessages = [
|
|
21519
|
+
...baseMessages,
|
|
21520
|
+
{ role: "user", content: buildRetryPrompt(executableMissing) }
|
|
21521
|
+
];
|
|
21522
|
+
const maxToolCalls = args.minPerTool !== void 0 ? Object.entries(args.minPerTool).filter(([name]) => executableMissing.includes(name)).reduce((sum, [, min]) => sum + min, 0) : retryToolNames.length;
|
|
21523
|
+
const retryHarness = new AgentHarness({
|
|
21524
|
+
model: args.effectiveModel,
|
|
21525
|
+
provider: args.effectiveProvider,
|
|
21526
|
+
sessionId: args.sessionId,
|
|
21527
|
+
messages: retryMessages,
|
|
21528
|
+
tools: retryToolSpecs,
|
|
21529
|
+
eventBus: args.eventBus,
|
|
21530
|
+
toolRegistry: args.toolRegistry,
|
|
21531
|
+
// Budget the retry so the model can satisfy every requirement in
|
|
21532
|
+
// ONE tool_calls turn. For createTask min:12 this needs 12 calls.
|
|
21533
|
+
maxToolCallsPerTurn: maxToolCalls,
|
|
21534
|
+
memberId: args.agent.id,
|
|
21535
|
+
memberName: args.agent.name,
|
|
21536
|
+
providerStream: (params) => args.providerStream(params)
|
|
21537
|
+
});
|
|
21538
|
+
const retryEmitted = [];
|
|
21539
|
+
for await (const event of retryHarness.run()) {
|
|
21540
|
+
if (event.type === "tool_execution_start") {
|
|
21541
|
+
retryEmitted.push(event.toolName);
|
|
21542
|
+
}
|
|
21543
|
+
yield event;
|
|
21544
|
+
}
|
|
21545
|
+
return retryEmitted;
|
|
21546
|
+
}
|
|
21547
|
+
async function* applyRetryIfMissing(args) {
|
|
21548
|
+
if (args.check.ok)
|
|
21549
|
+
return;
|
|
21550
|
+
const missingToolNames = args.check.missing.map((m) => m.split(" ")[0]);
|
|
21551
|
+
if (!shouldRetryMember(missingToolNames, 0))
|
|
21552
|
+
return;
|
|
21553
|
+
console.warn(`[council] ${args.agent.id} retrying missing tools: ${missingToolNames.join(", ")}`);
|
|
21554
|
+
const minPerTool = {};
|
|
21555
|
+
if (args.requirements) {
|
|
21556
|
+
for (const req of args.requirements) {
|
|
21557
|
+
if (missingToolNames.includes(req.name)) {
|
|
21558
|
+
minPerTool[req.name] = req.min;
|
|
21559
|
+
}
|
|
21560
|
+
}
|
|
21561
|
+
}
|
|
21562
|
+
try {
|
|
21563
|
+
const retryGenerator = runRetryTurnForMember({
|
|
21564
|
+
agent: args.agent,
|
|
21565
|
+
missingToolNames,
|
|
21566
|
+
minPerTool,
|
|
21567
|
+
executableTools: args.executableNames,
|
|
21568
|
+
userMessage: args.userMessage,
|
|
21569
|
+
ragContext: args.config.ragContext,
|
|
21570
|
+
workspaceContext: args.config.workspaceContext,
|
|
21571
|
+
priorOutputs: args.agentOutputs,
|
|
21572
|
+
aiConfig: args.config.aiConfig,
|
|
21573
|
+
sessionId: args.sessionId,
|
|
21574
|
+
effectiveModel: args.effectiveModel,
|
|
21575
|
+
effectiveProvider: args.effectiveProvider,
|
|
21576
|
+
eventBus: args.config.eventBus,
|
|
21577
|
+
toolRegistry: args.config.tools,
|
|
21578
|
+
providerStream: args.config.providerStream,
|
|
21579
|
+
runMode: args.config.runMode
|
|
21580
|
+
});
|
|
21581
|
+
for await (const event of retryGenerator) {
|
|
21582
|
+
if (event.type === "tool_execution_start") {
|
|
21583
|
+
args.onToolCall();
|
|
21584
|
+
args.emittedToolNames.push(event.toolName);
|
|
21585
|
+
}
|
|
21586
|
+
yield event;
|
|
21587
|
+
}
|
|
21588
|
+
} catch (retryErr) {
|
|
21589
|
+
console.error(`[council] ${args.agent.id} retry failed:`, retryErr);
|
|
21590
|
+
}
|
|
21591
|
+
enforceDesignPhaseToolEmissions(args.agent.id, args.emittedToolNames);
|
|
21592
|
+
}
|
|
21593
|
+
var NON_RETRY_AGENTS, QUESTION_MARKER, QUESTION_END_MARKER, DESIGN_PHASE_REQUIREMENT_SETS, DESIGN_PHASE_REQUIREMENTS, MAX_RETRY_PER_MEMBER;
|
|
21012
21594
|
var init_councilApi = __esm({
|
|
21013
21595
|
"packages/core/dist/agents/councilApi.js"() {
|
|
21014
21596
|
"use strict";
|
|
@@ -21018,8 +21600,35 @@ var init_councilApi = __esm({
|
|
|
21018
21600
|
init_tools();
|
|
21019
21601
|
init_events();
|
|
21020
21602
|
init_AgentHarness();
|
|
21603
|
+
init_modeBanners();
|
|
21604
|
+
init_runMode();
|
|
21605
|
+
NON_RETRY_AGENTS = /* @__PURE__ */ new Set([]);
|
|
21021
21606
|
QUESTION_MARKER = "---QUESTION---";
|
|
21022
21607
|
QUESTION_END_MARKER = "---END---";
|
|
21608
|
+
DESIGN_PHASE_REQUIREMENT_SETS = {
|
|
21609
|
+
nettun: [
|
|
21610
|
+
[{ name: "createPlan", min: 1 }],
|
|
21611
|
+
[
|
|
21612
|
+
{ name: "createPhase", min: 3 },
|
|
21613
|
+
{ name: "createTask", min: 6 },
|
|
21614
|
+
{ name: "createMilestone", min: 1 }
|
|
21615
|
+
]
|
|
21616
|
+
],
|
|
21617
|
+
geryon: [
|
|
21618
|
+
[{ name: "createDocument", min: 3 }]
|
|
21619
|
+
],
|
|
21620
|
+
pluton: [
|
|
21621
|
+
[{ name: "createDocument", min: 1 }]
|
|
21622
|
+
],
|
|
21623
|
+
minos: [
|
|
21624
|
+
[{ name: "createDocument", min: 1 }]
|
|
21625
|
+
],
|
|
21626
|
+
lucifer: [
|
|
21627
|
+
[{ name: "createDocument", min: 1 }]
|
|
21628
|
+
]
|
|
21629
|
+
};
|
|
21630
|
+
DESIGN_PHASE_REQUIREMENTS = Object.fromEntries(Object.entries(DESIGN_PHASE_REQUIREMENT_SETS).map(([id, sets]) => [id, sets[0]]));
|
|
21631
|
+
MAX_RETRY_PER_MEMBER = 1;
|
|
21023
21632
|
}
|
|
21024
21633
|
});
|
|
21025
21634
|
|
|
@@ -21186,16 +21795,29 @@ var council_exports = {};
|
|
|
21186
21795
|
__export(council_exports, {
|
|
21187
21796
|
AGENT_ROLES: () => AGENT_ROLES,
|
|
21188
21797
|
COLLABORATION_DIRECTIVE: () => COLLABORATION_DIRECTIVE,
|
|
21798
|
+
DESIGN_PHASE_MODE_BANNER: () => DESIGN_PHASE_MODE_BANNER,
|
|
21799
|
+
DESIGN_PHASE_REQUIREMENTS: () => DESIGN_PHASE_REQUIREMENTS,
|
|
21800
|
+
DESIGN_PHASE_REQUIREMENT_SETS: () => DESIGN_PHASE_REQUIREMENT_SETS,
|
|
21801
|
+
IMPLEMENTATION_MODE_BANNER: () => IMPLEMENTATION_MODE_BANNER,
|
|
21802
|
+
MAX_RETRY_PER_MEMBER: () => MAX_RETRY_PER_MEMBER,
|
|
21803
|
+
NON_RETRY_AGENTS: () => NON_RETRY_AGENTS,
|
|
21189
21804
|
OUTPUT_QUALITY_DIRECTIVE: () => OUTPUT_QUALITY_DIRECTIVE,
|
|
21190
21805
|
PROMPT_MODULES: () => PROMPT_MODULES,
|
|
21191
21806
|
STRUCTURED_REASONING_DIRECTIVE: () => STRUCTURED_REASONING_DIRECTIVE,
|
|
21192
21807
|
TOOL_USE_PROTOCOL_DIRECTIVE: () => TOOL_USE_PROTOCOL_DIRECTIVE,
|
|
21193
21808
|
UnknownMemberError: () => UnknownMemberError,
|
|
21809
|
+
applyRetryIfMissing: () => applyRetryIfMissing,
|
|
21810
|
+
buildRetryPrompt: () => buildRetryPrompt,
|
|
21194
21811
|
buildSkillDefinition: () => buildSkillDefinition,
|
|
21195
21812
|
buildSystemPrompt: () => buildSystemPrompt,
|
|
21813
|
+
checkMemberToolEmissionSets: () => checkMemberToolEmissionSets,
|
|
21814
|
+
checkMemberToolEmissions: () => checkMemberToolEmissions,
|
|
21196
21815
|
cleanAgentContent: () => cleanAgentContent,
|
|
21197
21816
|
computeAgentSkills: () => computeAgentSkills,
|
|
21198
21817
|
computeAgentTools: () => computeAgentTools,
|
|
21818
|
+
councilModeBanner: () => councilModeBanner,
|
|
21819
|
+
councilTierFromSize: () => councilTierFromSize,
|
|
21820
|
+
enforceDesignPhaseToolEmissions: () => enforceDesignPhaseToolEmissions,
|
|
21199
21821
|
getAgent: () => getAgent,
|
|
21200
21822
|
getBasePromptModules: () => getBasePromptModules,
|
|
21201
21823
|
getCouncilAgents: () => getCouncilAgents,
|
|
@@ -21206,7 +21828,10 @@ __export(council_exports, {
|
|
|
21206
21828
|
parseThinking: () => parseThinking,
|
|
21207
21829
|
promoteMember: () => promoteMember,
|
|
21208
21830
|
renderSkillMarkdown: () => renderSkillMarkdown,
|
|
21831
|
+
resolveCouncilRunMode: () => resolveCouncilRunMode,
|
|
21209
21832
|
runCouncilPure: () => runCouncilPure,
|
|
21833
|
+
runRetryTurnForMember: () => runRetryTurnForMember,
|
|
21834
|
+
shouldRetryMember: () => shouldRetryMember,
|
|
21210
21835
|
slugify: () => slugify3,
|
|
21211
21836
|
stripClarificationProtocol: () => stripClarificationProtocol,
|
|
21212
21837
|
swapMembers: () => swapMembers
|
|
@@ -21215,6 +21840,8 @@ var init_council = __esm({
|
|
|
21215
21840
|
"packages/core/dist/council/index.js"() {
|
|
21216
21841
|
"use strict";
|
|
21217
21842
|
init_councilApi();
|
|
21843
|
+
init_runMode();
|
|
21844
|
+
init_modeBanners();
|
|
21218
21845
|
init_roles();
|
|
21219
21846
|
init_promoteMember();
|
|
21220
21847
|
init_councilDirectives();
|
|
@@ -21223,6 +21850,64 @@ var init_council = __esm({
|
|
|
21223
21850
|
}
|
|
21224
21851
|
});
|
|
21225
21852
|
|
|
21853
|
+
// src/cli/councilConfig.ts
|
|
21854
|
+
function resolveCouncilTier(opts) {
|
|
21855
|
+
const env = opts?.env ?? process.env;
|
|
21856
|
+
if (opts?.explicitSize !== void 0) {
|
|
21857
|
+
const size = clampCouncilSize(opts.explicitSize);
|
|
21858
|
+
return { tier: size >= COUNCIL_TIER_SIZES.full ? "full" : "lite", councilSize: size };
|
|
21859
|
+
}
|
|
21860
|
+
const envSize = env["ZELARI_COUNCIL_SIZE"];
|
|
21861
|
+
if (envSize) {
|
|
21862
|
+
const parsed = Number.parseInt(envSize, 10);
|
|
21863
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
21864
|
+
const size = clampCouncilSize(parsed);
|
|
21865
|
+
return { tier: size >= COUNCIL_TIER_SIZES.full ? "full" : "lite", councilSize: size };
|
|
21866
|
+
}
|
|
21867
|
+
}
|
|
21868
|
+
const tierEnv = env["ZELARI_COUNCIL_TIER"]?.toLowerCase();
|
|
21869
|
+
if (tierEnv === "lite") {
|
|
21870
|
+
return { tier: "lite", councilSize: COUNCIL_TIER_SIZES.lite };
|
|
21871
|
+
}
|
|
21872
|
+
if (tierEnv === "full") {
|
|
21873
|
+
return { tier: "full", councilSize: COUNCIL_TIER_SIZES.full };
|
|
21874
|
+
}
|
|
21875
|
+
return { tier: "full", councilSize: COUNCIL_TIER_SIZES.full };
|
|
21876
|
+
}
|
|
21877
|
+
function clampCouncilSize(n) {
|
|
21878
|
+
return Math.min(COUNCIL_TIER_SIZES.full, Math.max(1, Math.floor(n)));
|
|
21879
|
+
}
|
|
21880
|
+
var COUNCIL_TIER_SIZES;
|
|
21881
|
+
var init_councilConfig = __esm({
|
|
21882
|
+
"src/cli/councilConfig.ts"() {
|
|
21883
|
+
"use strict";
|
|
21884
|
+
COUNCIL_TIER_SIZES = {
|
|
21885
|
+
lite: 3,
|
|
21886
|
+
full: 6
|
|
21887
|
+
};
|
|
21888
|
+
}
|
|
21889
|
+
});
|
|
21890
|
+
|
|
21891
|
+
// src/cli/workspace/planDetect.ts
|
|
21892
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
21893
|
+
import { join as join6 } from "node:path";
|
|
21894
|
+
function hasWorkspacePlan(projectRoot = process.cwd()) {
|
|
21895
|
+
const planPath = join6(resolveWorkspaceRoot(projectRoot), "plan.json");
|
|
21896
|
+
if (!existsSync11(planPath)) return false;
|
|
21897
|
+
try {
|
|
21898
|
+
const parsed = JSON.parse(readFileSync8(planPath, "utf8"));
|
|
21899
|
+
return Array.isArray(parsed.phases) && parsed.phases.length > 0;
|
|
21900
|
+
} catch {
|
|
21901
|
+
return false;
|
|
21902
|
+
}
|
|
21903
|
+
}
|
|
21904
|
+
var init_planDetect = __esm({
|
|
21905
|
+
"src/cli/workspace/planDetect.ts"() {
|
|
21906
|
+
"use strict";
|
|
21907
|
+
init_paths();
|
|
21908
|
+
}
|
|
21909
|
+
});
|
|
21910
|
+
|
|
21226
21911
|
// src/cli/councilDispatcher.ts
|
|
21227
21912
|
var councilDispatcher_exports = {};
|
|
21228
21913
|
__export(councilDispatcher_exports, {
|
|
@@ -21236,26 +21921,44 @@ async function* dispatchCouncil(userMessage, options) {
|
|
|
21236
21921
|
);
|
|
21237
21922
|
}
|
|
21238
21923
|
if (!userMessage || userMessage.trim().length === 0) {
|
|
21239
|
-
throw new CouncilDispatchError(
|
|
21924
|
+
throw new CouncilDispatchError(
|
|
21925
|
+
"Empty userMessage \u2014 /council requires a non-empty input."
|
|
21926
|
+
);
|
|
21240
21927
|
}
|
|
21928
|
+
const projectRoot = options.workspaceRoot ?? process.cwd();
|
|
21929
|
+
const { councilSize } = resolveCouncilTier({
|
|
21930
|
+
explicitSize: options.councilSize
|
|
21931
|
+
});
|
|
21932
|
+
const runMode = options.runMode ?? resolveCouncilRunMode({
|
|
21933
|
+
userMessage,
|
|
21934
|
+
hasExistingPlan: hasWorkspacePlan(projectRoot),
|
|
21935
|
+
env: process.env
|
|
21936
|
+
});
|
|
21241
21937
|
const config2 = {
|
|
21242
21938
|
apiKey: options.apiKey,
|
|
21243
21939
|
provider: options.provider ?? "openai-compatible",
|
|
21244
21940
|
model: options.model,
|
|
21245
|
-
councilSize
|
|
21941
|
+
councilSize,
|
|
21246
21942
|
debateMode: options.debateMode ?? false,
|
|
21943
|
+
runMode,
|
|
21247
21944
|
ragContext: options.ragContext ?? "",
|
|
21248
21945
|
workspaceContext: options.workspaceContext ?? "",
|
|
21249
21946
|
providerStream: options.providerStream,
|
|
21250
21947
|
eventBus: options.eventBus,
|
|
21251
21948
|
sessionId: options.sessionId,
|
|
21252
|
-
|
|
21253
|
-
// AnathemaBrain Electron ctx). Pass `tools: undefined` via options
|
|
21254
|
-
// to skip workspace wiring (e.g. in tests).
|
|
21255
|
-
tools: options.tools ?? (options.disableWorkspaceTools ? void 0 : createWorkspaceToolRegistry(createWorkspaceContext())),
|
|
21949
|
+
tools: options.tools ?? (options.disableWorkspaceTools ? void 0 : createWorkspaceToolRegistry(createWorkspaceContext(projectRoot))),
|
|
21256
21950
|
maxToolCallsPerTurn: options.maxToolCallsPerTurn,
|
|
21257
21951
|
feedbackStore: options.feedbackStore
|
|
21258
21952
|
};
|
|
21953
|
+
if (!options.disableWorkspaceTools) {
|
|
21954
|
+
const { setWorkspaceStubs: setWorkspaceStubs2 } = await Promise.resolve().then(() => (init_skills2(), skills_exports));
|
|
21955
|
+
const {
|
|
21956
|
+
createWorkspaceContext: buildCtx,
|
|
21957
|
+
createWorkspaceStubs: buildStubs
|
|
21958
|
+
} = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
|
|
21959
|
+
const wsCtx = buildCtx(projectRoot);
|
|
21960
|
+
setWorkspaceStubs2(buildStubs(wsCtx));
|
|
21961
|
+
}
|
|
21259
21962
|
yield* runCouncilPure(userMessage, config2, {});
|
|
21260
21963
|
}
|
|
21261
21964
|
var CouncilDispatchError;
|
|
@@ -21263,6 +21966,8 @@ var init_councilDispatcher = __esm({
|
|
|
21263
21966
|
"src/cli/councilDispatcher.ts"() {
|
|
21264
21967
|
"use strict";
|
|
21265
21968
|
init_council();
|
|
21969
|
+
init_councilConfig();
|
|
21970
|
+
init_planDetect();
|
|
21266
21971
|
init_stubs();
|
|
21267
21972
|
init_toolRegistry();
|
|
21268
21973
|
CouncilDispatchError = class extends Error {
|
|
@@ -21282,13 +21987,13 @@ __export(agentsMd_exports, {
|
|
|
21282
21987
|
serializeAgentsMd: () => serializeAgentsMd,
|
|
21283
21988
|
updateAgentsMd: () => updateAgentsMd
|
|
21284
21989
|
});
|
|
21285
|
-
import { existsSync as
|
|
21990
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
21286
21991
|
import { createHash as createHash2 } from "node:crypto";
|
|
21287
|
-
import { join as
|
|
21992
|
+
import { join as join7 } from "node:path";
|
|
21288
21993
|
import { readFile } from "node:fs/promises";
|
|
21289
21994
|
async function readPackageJson2(projectRoot) {
|
|
21290
|
-
const path22 =
|
|
21291
|
-
if (!
|
|
21995
|
+
const path22 = join7(projectRoot, "package.json");
|
|
21996
|
+
if (!existsSync12(path22)) return null;
|
|
21292
21997
|
try {
|
|
21293
21998
|
return JSON.parse(await readFile(path22, "utf8"));
|
|
21294
21999
|
} catch {
|
|
@@ -21312,8 +22017,8 @@ async function genTechStack(ctx) {
|
|
|
21312
22017
|
].join("\n");
|
|
21313
22018
|
}
|
|
21314
22019
|
async function genDecisions(ctx) {
|
|
21315
|
-
const decisionsDir =
|
|
21316
|
-
if (!
|
|
22020
|
+
const decisionsDir = join7(ctx.rootDir, "decisions");
|
|
22021
|
+
if (!existsSync12(decisionsDir)) return "_No ADRs yet._";
|
|
21317
22022
|
const files = ctx.storage.listMarkdown(decisionsDir).sort();
|
|
21318
22023
|
const accepted = [];
|
|
21319
22024
|
const proposed = [];
|
|
@@ -21338,9 +22043,9 @@ async function genDecisions(ctx) {
|
|
|
21338
22043
|
}
|
|
21339
22044
|
async function genConventions(ctx) {
|
|
21340
22045
|
const lines = [];
|
|
21341
|
-
const claudeMd =
|
|
21342
|
-
if (
|
|
21343
|
-
const content =
|
|
22046
|
+
const claudeMd = join7(ctx.projectRoot, "CLAUDE.MD");
|
|
22047
|
+
if (existsSync12(claudeMd)) {
|
|
22048
|
+
const content = readFileSync9(claudeMd, "utf8");
|
|
21344
22049
|
const match = content.match(/## Architecture rules[\s\S]+?(?=\n## |\n*$)/);
|
|
21345
22050
|
if (match) {
|
|
21346
22051
|
lines.push('<!-- Extracted from CLAUDE.MD "Architecture rules" -->');
|
|
@@ -21372,9 +22077,9 @@ async function genBuild(ctx) {
|
|
|
21372
22077
|
].join("\n");
|
|
21373
22078
|
}
|
|
21374
22079
|
async function genOpenQuestions(ctx) {
|
|
21375
|
-
const path22 =
|
|
21376
|
-
if (!
|
|
21377
|
-
const content =
|
|
22080
|
+
const path22 = join7(ctx.rootDir, "risks.md");
|
|
22081
|
+
if (!existsSync12(path22)) return "_No open questions._";
|
|
22082
|
+
const content = readFileSync9(path22, "utf8");
|
|
21378
22083
|
const lines = content.split("\n");
|
|
21379
22084
|
const questions = [];
|
|
21380
22085
|
let currentTitle = "";
|
|
@@ -21448,9 +22153,9 @@ function titleCase(id) {
|
|
|
21448
22153
|
return id.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
21449
22154
|
}
|
|
21450
22155
|
async function updateAgentsMd(ctx, projectRoot) {
|
|
21451
|
-
const agentsPath =
|
|
21452
|
-
if (
|
|
21453
|
-
const content =
|
|
22156
|
+
const agentsPath = join7(projectRoot, "AGENTS.MD");
|
|
22157
|
+
if (existsSync12(agentsPath)) {
|
|
22158
|
+
const content = readFileSync9(agentsPath, "utf8");
|
|
21454
22159
|
const hasAnyMarker = AUTO_SECTIONS.some((id) => content.includes(MARKER_OPEN(id)));
|
|
21455
22160
|
if (!hasAnyMarker) {
|
|
21456
22161
|
return {
|
|
@@ -21465,8 +22170,8 @@ async function updateAgentsMd(ctx, projectRoot) {
|
|
|
21465
22170
|
newSections.set(id, await GENERATORS[id](ctx));
|
|
21466
22171
|
}
|
|
21467
22172
|
let manualContent = "";
|
|
21468
|
-
if (
|
|
21469
|
-
const { manualBlocks } = parseAgentsMd(
|
|
22173
|
+
if (existsSync12(agentsPath)) {
|
|
22174
|
+
const { manualBlocks } = parseAgentsMd(readFileSync9(agentsPath, "utf8"));
|
|
21470
22175
|
manualContent = manualBlocks.after;
|
|
21471
22176
|
} else {
|
|
21472
22177
|
const projectName2 = projectName(projectRoot);
|
|
@@ -21482,7 +22187,7 @@ async function updateAgentsMd(ctx, projectRoot) {
|
|
|
21482
22187
|
""
|
|
21483
22188
|
].join("\n");
|
|
21484
22189
|
}
|
|
21485
|
-
const oldContent =
|
|
22190
|
+
const oldContent = existsSync12(agentsPath) ? readFileSync9(agentsPath, "utf8") : "";
|
|
21486
22191
|
const { sections: oldSections } = parseAgentsMd(oldContent);
|
|
21487
22192
|
const changedSections = [];
|
|
21488
22193
|
for (const id of AUTO_SECTIONS) {
|
|
@@ -21524,36 +22229,229 @@ var init_agentsMd = __esm({
|
|
|
21524
22229
|
}
|
|
21525
22230
|
});
|
|
21526
22231
|
|
|
22232
|
+
// src/cli/workspace/completeDesign.ts
|
|
22233
|
+
function curatedTasksForPhase(phase) {
|
|
22234
|
+
const label = (phase.name ?? phase.id).trim();
|
|
22235
|
+
const scope = phase.description?.trim() ? ` Scope: ${phase.description.trim()}` : "";
|
|
22236
|
+
return [
|
|
22237
|
+
{
|
|
22238
|
+
title: `Specify ${label} deliverables`,
|
|
22239
|
+
description: `Turn the council documents for "${label}" into a concrete specification: enumerate deliverables, interfaces, constraints, and open decisions, grounding each item in the ADRs and design docs already present in .zelari/.${scope}`,
|
|
22240
|
+
fileRefs: [".zelari/docs/", ".zelari/decisions/"],
|
|
22241
|
+
acceptance: [
|
|
22242
|
+
`A written spec for "${label}" exists and references at least one ADR or design doc`,
|
|
22243
|
+
"Every deliverable in the spec has an owner artifact (file, doc, or component) named"
|
|
22244
|
+
],
|
|
22245
|
+
qaScenario: `Open the spec for "${label}" and confirm each deliverable maps to a concrete artifact and no open decision is left unresolved.`,
|
|
22246
|
+
priority: "high"
|
|
22247
|
+
},
|
|
22248
|
+
{
|
|
22249
|
+
title: `Implement ${label} deliverables`,
|
|
22250
|
+
description: `Produce the artifacts the "${label}" phase promises, following the spec task of this phase and the decisions recorded in .zelari/decisions/.${scope}`,
|
|
22251
|
+
fileRefs: ["src/"],
|
|
22252
|
+
acceptance: [
|
|
22253
|
+
`Every deliverable listed in the "${label}" spec exists and builds/renders without errors`,
|
|
22254
|
+
"No implementation contradicts an accepted ADR"
|
|
22255
|
+
],
|
|
22256
|
+
qaScenario: `Walk the "${label}" spec top to bottom and check each deliverable off against the produced artifact.`,
|
|
22257
|
+
priority: "high"
|
|
22258
|
+
},
|
|
22259
|
+
{
|
|
22260
|
+
title: `Verify ${label} exit criteria`,
|
|
22261
|
+
description: `Run the QA pass for "${label}": execute the QA scenarios of the phase tasks, check the phase row in the synthesis green-light checklist, and record any gaps as follow-up tasks.${scope}`,
|
|
22262
|
+
fileRefs: [".zelari/docs/synthesis.md"],
|
|
22263
|
+
acceptance: [
|
|
22264
|
+
`The "${label}" row of the synthesis green-light checklist passes`,
|
|
22265
|
+
"All QA scenarios of this phase have been executed with recorded outcomes"
|
|
22266
|
+
],
|
|
22267
|
+
qaScenario: `Open .zelari/docs/synthesis.md, locate the "${label}" checklist row, and confirm every criterion is checked with evidence.`,
|
|
22268
|
+
priority: "medium"
|
|
22269
|
+
}
|
|
22270
|
+
];
|
|
22271
|
+
}
|
|
22272
|
+
async function runBuiltinCompleteDesign(ctx) {
|
|
22273
|
+
const summary = readPlanSummary(ctx);
|
|
22274
|
+
if (summary.phases.length === 0) {
|
|
22275
|
+
return { ran: false, tasksAdded: 0, milestonesAdded: 0, reason: "plan has no phases" };
|
|
22276
|
+
}
|
|
22277
|
+
const stubs = createWorkspaceStubs(ctx);
|
|
22278
|
+
const createTask = stubs.find((s) => s.name === "createTask");
|
|
22279
|
+
const createMilestone = stubs.find((s) => s.name === "createMilestone");
|
|
22280
|
+
const stubCtx = ctx;
|
|
22281
|
+
let tasksAdded = 0;
|
|
22282
|
+
for (const phase of summary.phases) {
|
|
22283
|
+
const existingTasks = summary.tasks.filter((t) => t.phaseId === phase.id);
|
|
22284
|
+
if (existingTasks.length >= MIN_TASKS_PER_PHASE) continue;
|
|
22285
|
+
const existingTitles = new Set(existingTasks.map((t) => t.name ?? ""));
|
|
22286
|
+
const templates = curatedTasksForPhase(phase).filter((t) => !existingTitles.has(t.title)).slice(0, MIN_TASKS_PER_PHASE - existingTasks.length);
|
|
22287
|
+
for (const t of templates) {
|
|
22288
|
+
await createTask.execute(
|
|
22289
|
+
{
|
|
22290
|
+
phaseId: phase.id,
|
|
22291
|
+
title: t.title,
|
|
22292
|
+
description: t.description,
|
|
22293
|
+
fileRefs: t.fileRefs,
|
|
22294
|
+
acceptance: t.acceptance,
|
|
22295
|
+
qaScenario: t.qaScenario,
|
|
22296
|
+
priority: t.priority
|
|
22297
|
+
},
|
|
22298
|
+
stubCtx
|
|
22299
|
+
);
|
|
22300
|
+
tasksAdded += 1;
|
|
22301
|
+
}
|
|
22302
|
+
}
|
|
22303
|
+
let milestonesAdded = 0;
|
|
22304
|
+
if (summary.milestones.length === 0) {
|
|
22305
|
+
await createMilestone.execute(
|
|
22306
|
+
{
|
|
22307
|
+
title: "v0.1.0 design-complete",
|
|
22308
|
+
description: "All design-phase artifacts (phases, tasks, ADRs, design docs, risks, synthesis) exist and the green-light checklist passes.",
|
|
22309
|
+
targetVersion: "v0.1.0"
|
|
22310
|
+
},
|
|
22311
|
+
stubCtx
|
|
22312
|
+
);
|
|
22313
|
+
milestonesAdded = 1;
|
|
22314
|
+
}
|
|
22315
|
+
return { ran: true, tasksAdded, milestonesAdded };
|
|
22316
|
+
}
|
|
22317
|
+
var MIN_TASKS_PER_PHASE;
|
|
22318
|
+
var init_completeDesign = __esm({
|
|
22319
|
+
"src/cli/workspace/completeDesign.ts"() {
|
|
22320
|
+
"use strict";
|
|
22321
|
+
init_stubs();
|
|
22322
|
+
MIN_TASKS_PER_PHASE = 3;
|
|
22323
|
+
}
|
|
22324
|
+
});
|
|
22325
|
+
|
|
21527
22326
|
// src/cli/workspace/postCouncilHook.ts
|
|
21528
22327
|
var postCouncilHook_exports = {};
|
|
21529
22328
|
__export(postCouncilHook_exports, {
|
|
22329
|
+
runCompleteDesignPostProcessor: () => runCompleteDesignPostProcessor,
|
|
21530
22330
|
runPostCouncilHook: () => runPostCouncilHook
|
|
21531
22331
|
});
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
const result = await updateAgentsMd(ctx, ctx.projectRoot);
|
|
22332
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
22333
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "node:fs";
|
|
22334
|
+
import { join as join8 } from "node:path";
|
|
22335
|
+
async function runCompleteDesignPostProcessor(ctx, options) {
|
|
22336
|
+
if (options?.runMode === "implementation") {
|
|
21538
22337
|
return {
|
|
21539
|
-
ran:
|
|
21540
|
-
|
|
21541
|
-
sections: result.sections,
|
|
21542
|
-
...result.reason ? { reason: result.reason } : {}
|
|
22338
|
+
ran: false,
|
|
22339
|
+
reason: "implementation mode (complete-design skipped)"
|
|
21543
22340
|
};
|
|
21544
|
-
}
|
|
22341
|
+
}
|
|
22342
|
+
if (process.env["ZELARI_COMPLETE_DESIGN"] === "0") {
|
|
22343
|
+
return { ran: false, reason: "ZELARI_COMPLETE_DESIGN=0 (disabled)" };
|
|
22344
|
+
}
|
|
22345
|
+
const planJsonPath2 = join8(ctx.rootDir, "plan.json");
|
|
22346
|
+
const scriptPath = join8(ctx.projectRoot, "complete-design.mjs");
|
|
22347
|
+
if (!existsSync13(planJsonPath2)) {
|
|
21545
22348
|
return {
|
|
22349
|
+
ran: false,
|
|
22350
|
+
reason: ".zelari/plan.json missing (not design-phase)"
|
|
22351
|
+
};
|
|
22352
|
+
}
|
|
22353
|
+
let phaseCount = 0;
|
|
22354
|
+
try {
|
|
22355
|
+
const parsed = JSON.parse(readFileSync10(planJsonPath2, "utf8"));
|
|
22356
|
+
phaseCount = Array.isArray(parsed.phases) ? parsed.phases.length : 0;
|
|
22357
|
+
} catch {
|
|
22358
|
+
return { ran: false, reason: ".zelari/plan.json corrupt" };
|
|
22359
|
+
}
|
|
22360
|
+
if (phaseCount === 0) {
|
|
22361
|
+
return { ran: false, reason: ".zelari/plan.json has no phases" };
|
|
22362
|
+
}
|
|
22363
|
+
if (!existsSync13(scriptPath)) {
|
|
22364
|
+
try {
|
|
22365
|
+
const builtin = await runBuiltinCompleteDesign(ctx);
|
|
22366
|
+
return {
|
|
22367
|
+
ran: builtin.ran,
|
|
22368
|
+
...builtin.ran ? { exitCode: 0 } : {},
|
|
22369
|
+
output: `builtin complete-design: +${builtin.tasksAdded} tasks, +${builtin.milestonesAdded} milestones`,
|
|
22370
|
+
...builtin.reason ? { reason: builtin.reason } : {}
|
|
22371
|
+
};
|
|
22372
|
+
} catch (err) {
|
|
22373
|
+
return {
|
|
22374
|
+
ran: true,
|
|
22375
|
+
exitCode: -1,
|
|
22376
|
+
reason: `builtin complete-design error: ${err instanceof Error ? err.message : String(err)}`
|
|
22377
|
+
};
|
|
22378
|
+
}
|
|
22379
|
+
}
|
|
22380
|
+
return await new Promise((resolveRun) => {
|
|
22381
|
+
const child = spawn3(process.execPath, [scriptPath], {
|
|
22382
|
+
cwd: ctx.projectRoot,
|
|
22383
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
22384
|
+
env: process.env
|
|
22385
|
+
});
|
|
22386
|
+
let stdout = "";
|
|
22387
|
+
let stderr = "";
|
|
22388
|
+
child.stdout?.on("data", (chunk) => {
|
|
22389
|
+
stdout += chunk.toString("utf8");
|
|
22390
|
+
});
|
|
22391
|
+
child.stderr?.on("data", (chunk) => {
|
|
22392
|
+
stderr += chunk.toString("utf8");
|
|
22393
|
+
});
|
|
22394
|
+
child.on("error", (err) => {
|
|
22395
|
+
resolveRun({
|
|
22396
|
+
ran: true,
|
|
22397
|
+
exitCode: -1,
|
|
22398
|
+
output: stderr,
|
|
22399
|
+
reason: `spawn error: ${err.message}`
|
|
22400
|
+
});
|
|
22401
|
+
});
|
|
22402
|
+
child.on("close", (code) => {
|
|
22403
|
+
const exitCode = code ?? -1;
|
|
22404
|
+
const ok = exitCode === 0;
|
|
22405
|
+
resolveRun({
|
|
22406
|
+
ran: true,
|
|
22407
|
+
exitCode,
|
|
22408
|
+
output: stdout + stderr,
|
|
22409
|
+
...ok ? {} : { reason: `complete-design exited with code ${exitCode}` }
|
|
22410
|
+
});
|
|
22411
|
+
});
|
|
22412
|
+
});
|
|
22413
|
+
}
|
|
22414
|
+
async function runPostCouncilHook(ctx, options) {
|
|
22415
|
+
let agentsMdResult;
|
|
22416
|
+
if (process.env["ZELARI_AGENTS_MD"] === "0") {
|
|
22417
|
+
agentsMdResult = {
|
|
21546
22418
|
ran: false,
|
|
21547
22419
|
changed: false,
|
|
21548
22420
|
sections: [],
|
|
21549
|
-
reason:
|
|
22421
|
+
reason: "ZELARI_AGENTS_MD=0 (disabled)"
|
|
21550
22422
|
};
|
|
22423
|
+
} else {
|
|
22424
|
+
try {
|
|
22425
|
+
const result = await updateAgentsMd(ctx, ctx.projectRoot);
|
|
22426
|
+
agentsMdResult = {
|
|
22427
|
+
ran: true,
|
|
22428
|
+
changed: result.changed,
|
|
22429
|
+
sections: result.sections,
|
|
22430
|
+
...result.reason ? { reason: result.reason } : {}
|
|
22431
|
+
};
|
|
22432
|
+
} catch (err) {
|
|
22433
|
+
agentsMdResult = {
|
|
22434
|
+
ran: false,
|
|
22435
|
+
changed: false,
|
|
22436
|
+
sections: [],
|
|
22437
|
+
reason: `error: ${err instanceof Error ? err.message : String(err)}`
|
|
22438
|
+
};
|
|
22439
|
+
}
|
|
21551
22440
|
}
|
|
22441
|
+
const completeDesign = await runCompleteDesignPostProcessor(ctx, options);
|
|
22442
|
+
return {
|
|
22443
|
+
ran: agentsMdResult.ran || completeDesign.ran,
|
|
22444
|
+
changed: agentsMdResult.changed,
|
|
22445
|
+
sections: agentsMdResult.sections,
|
|
22446
|
+
...agentsMdResult.reason ? { reason: agentsMdResult.reason } : {},
|
|
22447
|
+
completeDesign
|
|
22448
|
+
};
|
|
21552
22449
|
}
|
|
21553
22450
|
var init_postCouncilHook = __esm({
|
|
21554
22451
|
"src/cli/workspace/postCouncilHook.ts"() {
|
|
21555
22452
|
"use strict";
|
|
21556
22453
|
init_agentsMd();
|
|
22454
|
+
init_completeDesign();
|
|
21557
22455
|
}
|
|
21558
22456
|
});
|
|
21559
22457
|
|
|
@@ -21564,8 +22462,8 @@ __export(councilFeedback_exports, {
|
|
|
21564
22462
|
});
|
|
21565
22463
|
import {
|
|
21566
22464
|
promises as fs11,
|
|
21567
|
-
existsSync as
|
|
21568
|
-
readFileSync as
|
|
22465
|
+
existsSync as existsSync14,
|
|
22466
|
+
readFileSync as readFileSync11,
|
|
21569
22467
|
writeFileSync as writeFileSync8,
|
|
21570
22468
|
mkdirSync as mkdirSync8
|
|
21571
22469
|
} from "node:fs";
|
|
@@ -21673,9 +22571,9 @@ var init_councilFeedback = __esm({
|
|
|
21673
22571
|
}
|
|
21674
22572
|
// --- persistence ---------------------------------------------------------
|
|
21675
22573
|
load() {
|
|
21676
|
-
if (!
|
|
22574
|
+
if (!existsSync14(this.file)) return;
|
|
21677
22575
|
try {
|
|
21678
|
-
const raw =
|
|
22576
|
+
const raw = readFileSync11(this.file, "utf-8");
|
|
21679
22577
|
const parsed = JSON.parse(raw);
|
|
21680
22578
|
if (parsed && Array.isArray(parsed.entries)) {
|
|
21681
22579
|
this.entries = parsed.entries.filter(
|
|
@@ -21721,7 +22619,7 @@ __export(updater_exports, {
|
|
|
21721
22619
|
performUpdate: () => performUpdate
|
|
21722
22620
|
});
|
|
21723
22621
|
import { createRequire } from "node:module";
|
|
21724
|
-
import { spawn as
|
|
22622
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
21725
22623
|
import path16 from "node:path";
|
|
21726
22624
|
import { fileURLToPath } from "node:url";
|
|
21727
22625
|
function getCurrentVersion() {
|
|
@@ -21786,15 +22684,13 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
|
|
|
21786
22684
|
updateAvailable: cmp < 0
|
|
21787
22685
|
};
|
|
21788
22686
|
}
|
|
21789
|
-
async function performUpdate(packageName = "zelari-code", executor =
|
|
22687
|
+
async function performUpdate(packageName = "zelari-code", executor = spawn4) {
|
|
21790
22688
|
return new Promise((resolve) => {
|
|
21791
22689
|
const args = ["install", "-g", `${packageName}@latest`];
|
|
21792
22690
|
let stdout = "";
|
|
21793
22691
|
let stderr = "";
|
|
21794
|
-
const
|
|
21795
|
-
|
|
21796
|
-
shell: process.platform === "win32"
|
|
21797
|
-
});
|
|
22692
|
+
const stdio = ["ignore", "pipe", "pipe"];
|
|
22693
|
+
const child = process.platform === "win32" ? executor(buildCmdLine("npm", args), { stdio, shell: true }) : executor("npm", args, { stdio });
|
|
21798
22694
|
child.stdout?.on("data", (chunk) => {
|
|
21799
22695
|
stdout += chunk.toString();
|
|
21800
22696
|
});
|
|
@@ -21824,6 +22720,7 @@ var require2, __dirname2, REGISTRY_URL;
|
|
|
21824
22720
|
var init_updater = __esm({
|
|
21825
22721
|
"src/cli/updater.ts"() {
|
|
21826
22722
|
"use strict";
|
|
22723
|
+
init_cmdline();
|
|
21827
22724
|
require2 = createRequire(import.meta.url);
|
|
21828
22725
|
__dirname2 = path16.dirname(fileURLToPath(import.meta.url));
|
|
21829
22726
|
REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
|
|
@@ -21831,12 +22728,12 @@ var init_updater = __esm({
|
|
|
21831
22728
|
});
|
|
21832
22729
|
|
|
21833
22730
|
// src/cli/main.ts
|
|
21834
|
-
import
|
|
22731
|
+
import React11 from "react";
|
|
21835
22732
|
import { render } from "ink";
|
|
21836
22733
|
|
|
21837
22734
|
// src/cli/app.tsx
|
|
21838
|
-
import
|
|
21839
|
-
import { Box as
|
|
22735
|
+
import React7, { useState as useState6, useMemo, useCallback as useCallback5 } from "react";
|
|
22736
|
+
import { Box as Box7, Static, useInput, useStdin } from "ink";
|
|
21840
22737
|
|
|
21841
22738
|
// src/cli/components/InputBar.tsx
|
|
21842
22739
|
import React from "react";
|
|
@@ -22106,68 +23003,22 @@ function StreamingTail(m) {
|
|
|
22106
23003
|
import React5 from "react";
|
|
22107
23004
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
22108
23005
|
|
|
22109
|
-
// src/cli/
|
|
22110
|
-
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22123
|
-
|
|
22124
|
-
|
|
22125
|
-
"MiniMax-M2-her": { input: 0.3, output: 1.2 },
|
|
22126
|
-
// OpenAI (for openai-compatible fallback)
|
|
22127
|
-
"gpt-4o": { input: 2.5, output: 10 },
|
|
22128
|
-
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
22129
|
-
"gpt-4-turbo": { input: 10, output: 30 },
|
|
22130
|
-
"o1-preview": { input: 15, output: 60 },
|
|
22131
|
-
"o1-mini": { input: 3, output: 12 }
|
|
22132
|
-
};
|
|
22133
|
-
var DEFAULT_RATE = { input: 1, output: 3 };
|
|
22134
|
-
function envOverride(model) {
|
|
22135
|
-
const key = `ANATHEMA_PRICE_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
22136
|
-
const raw = process.env[key] ?? process.env.ANATHEMA_PRICE_DEFAULT;
|
|
22137
|
-
return parseRateOverride(raw);
|
|
22138
|
-
}
|
|
22139
|
-
function parseRateOverride(raw) {
|
|
22140
|
-
if (!raw) return null;
|
|
22141
|
-
const [inStr, outStr] = raw.split("/");
|
|
22142
|
-
const input = Number.parseFloat(inStr);
|
|
22143
|
-
const output = outStr !== void 0 ? Number.parseFloat(outStr) : input;
|
|
22144
|
-
if (!Number.isFinite(input) || !Number.isFinite(output)) return null;
|
|
22145
|
-
return { input, output };
|
|
22146
|
-
}
|
|
22147
|
-
function getModelRate(model) {
|
|
22148
|
-
if (!model) return DEFAULT_RATE;
|
|
22149
|
-
return envOverride(model) ?? PRICES_PER_MILLION[model] ?? DEFAULT_RATE;
|
|
22150
|
-
}
|
|
22151
|
-
function calculateCost(model, promptTokens, completionTokens) {
|
|
22152
|
-
if (!Number.isFinite(promptTokens) || promptTokens < 0) return 0;
|
|
22153
|
-
if (!Number.isFinite(completionTokens) || completionTokens < 0) return 0;
|
|
22154
|
-
const rate = getModelRate(model);
|
|
22155
|
-
const inputCost = promptTokens / 1e6 * rate.input;
|
|
22156
|
-
const outputCost = completionTokens / 1e6 * rate.output;
|
|
22157
|
-
return Number((inputCost + outputCost).toFixed(6));
|
|
22158
|
-
}
|
|
22159
|
-
function formatCost(usd) {
|
|
22160
|
-
if (!Number.isFinite(usd) || usd < 0) return "$0.0000";
|
|
22161
|
-
if (usd === 0) return "$0.0000";
|
|
22162
|
-
if (usd < 1e-4) return "<$0.0001";
|
|
22163
|
-
return `$${usd.toFixed(4)}`;
|
|
22164
|
-
}
|
|
22165
|
-
function formatTokens(tokens) {
|
|
22166
|
-
if (!Number.isFinite(tokens) || tokens < 0) return "0";
|
|
22167
|
-
if (tokens < 1e3) return `${tokens}`;
|
|
22168
|
-
if (tokens < 1e6) return `${(tokens / 1e3).toFixed(1)}k`;
|
|
22169
|
-
if (tokens < 1e9) return `${(tokens / 1e6).toFixed(2)}M`;
|
|
22170
|
-
return `${(tokens / 1e9).toFixed(2)}B`;
|
|
23006
|
+
// src/cli/utils/duration.ts
|
|
23007
|
+
function formatDuration(ms) {
|
|
23008
|
+
const abs = Math.abs(ms);
|
|
23009
|
+
const sign = ms < 0 ? " ago" : "";
|
|
23010
|
+
if (abs < 1e3) return `${abs}ms${sign}`;
|
|
23011
|
+
const s = Math.floor(abs / 1e3);
|
|
23012
|
+
if (s < 60) return `${s}s${sign}`;
|
|
23013
|
+
const m = Math.floor(s / 60);
|
|
23014
|
+
const rs = s % 60;
|
|
23015
|
+
if (m < 60) return rs === 0 ? `${m}m${sign}` : `${m}m ${rs}s${sign}`;
|
|
23016
|
+
const h = Math.floor(m / 60);
|
|
23017
|
+
const rm = m % 60;
|
|
23018
|
+
if (h < 24) return rm === 0 ? `${h}h${sign}` : `${h}h ${rm}m${sign}`;
|
|
23019
|
+
const d = Math.floor(h / 24);
|
|
23020
|
+
const rh = h % 24;
|
|
23021
|
+
return rh === 0 ? `${d}d${sign}` : `${d}d ${rh}h${sign}`;
|
|
22171
23022
|
}
|
|
22172
23023
|
|
|
22173
23024
|
// src/cli/components/StatusBar.tsx
|
|
@@ -22176,622 +23027,369 @@ function StatusBar({
|
|
|
22176
23027
|
provider,
|
|
22177
23028
|
sessionId,
|
|
22178
23029
|
sessionActive,
|
|
22179
|
-
totalTokens,
|
|
22180
|
-
totalCostUsd,
|
|
22181
23030
|
queueCount = 0,
|
|
22182
|
-
busy = false
|
|
23031
|
+
busy = false,
|
|
23032
|
+
mode = "agent",
|
|
23033
|
+
cwd,
|
|
23034
|
+
elapsedMs = null,
|
|
23035
|
+
lastMs = null
|
|
22183
23036
|
}) {
|
|
22184
|
-
|
|
22185
|
-
|
|
22186
|
-
|
|
22187
|
-
|
|
23037
|
+
return /* @__PURE__ */ React5.createElement(Box5, { paddingX: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: sessionActive ? "green" : "gray" }, sessionActive ? "\u25CF" : "\u25CB"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " "), /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: mode === "council" ? "magenta" : "cyan" }, mode === "council" ? "\u26EC council" : "\u23F5 agent"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " (shift+tab)"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: "cyan" }, provider), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, null, model), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "session ", sessionId), cwd ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { color: "blue" }, cwd)) : null, busy && elapsedMs !== null ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, "\u23F1 ", formatDuration(elapsedMs))) : lastMs !== null ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "last ", formatDuration(lastMs))) : null, queueCount > 0 ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, "queue ", queueCount)) : null);
|
|
23038
|
+
}
|
|
23039
|
+
|
|
23040
|
+
// src/cli/components/Sidebar.tsx
|
|
23041
|
+
import React6 from "react";
|
|
23042
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
23043
|
+
var EMBLEM_BRAILLE = `\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28F4\u28FF\u28F7\u28C6\u2840
|
|
23044
|
+
\u2800\u2800\u2800\u2800\u2800\u2800\u2800\u2880\u28F6\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6\u2840
|
|
23045
|
+
\u2800\u2800\u2800\u2800\u2800\u2800\u28F0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E6
|
|
23046
|
+
\u2800\u2800\u2800\u2800\u28A0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28F7\u2844
|
|
23047
|
+
\u2800\u2800\u2800\u2800\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2819\u283B\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2806
|
|
23048
|
+
\u2800\u2800\u2800\u2800\u28C8\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2800\u28F7\u28EC\u285B\u28BF\u28FF\u28DF\u28C1
|
|
23049
|
+
\u2800\u2880\u28F4\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2800\u287F\u28BF\u287F\u2883\u28C8\u28FB\u28FF\u28FF\u28F6
|
|
23050
|
+
\u2800\u28FC\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28E0\u28F4\u28C6\u283B\u280E\u28BF\u28FF\u28FF\u28FF\u28FF\u28E7
|
|
23051
|
+
\u28F0\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u28FF\u2847`;
|
|
23052
|
+
var SIDEBAR_WIDTH = 28;
|
|
23053
|
+
var SIDEBAR_MIN_COLUMNS = 96;
|
|
23054
|
+
var EMBLEM_MIN_ROWS = 26;
|
|
23055
|
+
var MAX_FILES_SHORT = 6;
|
|
23056
|
+
var MAX_FILES_TALL = 10;
|
|
23057
|
+
function shouldShowSidebar(columns, rows) {
|
|
23058
|
+
return columns >= SIDEBAR_MIN_COLUMNS && rows >= 16;
|
|
23059
|
+
}
|
|
23060
|
+
function truncatePath(p3, max) {
|
|
23061
|
+
if (p3.length <= max) return p3;
|
|
23062
|
+
return `\u2026${p3.slice(-(max - 1))}`;
|
|
23063
|
+
}
|
|
23064
|
+
function Sidebar({ version: version2, changes, rows }) {
|
|
23065
|
+
const showEmblem = rows >= EMBLEM_MIN_ROWS;
|
|
23066
|
+
const maxFiles = rows >= EMBLEM_MIN_ROWS + 8 ? MAX_FILES_TALL : MAX_FILES_SHORT;
|
|
23067
|
+
const visible = changes.files.slice(0, maxFiles);
|
|
23068
|
+
const hidden = changes.files.length - visible.length;
|
|
23069
|
+
const innerWidth = SIDEBAR_WIDTH - 4;
|
|
23070
|
+
return /* @__PURE__ */ React6.createElement(
|
|
23071
|
+
Box6,
|
|
23072
|
+
{
|
|
23073
|
+
flexDirection: "column",
|
|
23074
|
+
width: SIDEBAR_WIDTH,
|
|
23075
|
+
borderStyle: "single",
|
|
23076
|
+
borderColor: "gray",
|
|
23077
|
+
paddingX: 1,
|
|
23078
|
+
flexShrink: 0
|
|
23079
|
+
},
|
|
23080
|
+
showEmblem && /* @__PURE__ */ React6.createElement(Box6, { justifyContent: "center" }, /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, EMBLEM_BRAILLE)),
|
|
23081
|
+
/* @__PURE__ */ React6.createElement(Box6, { justifyContent: "center" }, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "white" }, "ZELARI CODE")),
|
|
23082
|
+
/* @__PURE__ */ React6.createElement(Box6, { justifyContent: "center" }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "v", version2, changes.branch ? ` \xB7 ${truncatePath(changes.branch, 12)}` : "")),
|
|
23083
|
+
/* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\u2500".repeat(innerWidth)),
|
|
23084
|
+
!changes.isRepo ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true, italic: true }, "not a git repo") : changes.files.length === 0 ? /* @__PURE__ */ React6.createElement(Text6, { dimColor: true, italic: true }, "no changes") : /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "changes (", changes.files.length, ")"), visible.map((f) => /* @__PURE__ */ React6.createElement(FileRow, { key: f.path, file: f, pathWidth: innerWidth - 10 })), hidden > 0 && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " +", hidden, " more\u2026"))
|
|
23085
|
+
);
|
|
22188
23086
|
}
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
|
|
22192
|
-
const trimmed = text.trim();
|
|
22193
|
-
if (!trimmed.startsWith("/")) return null;
|
|
22194
|
-
const parts = trimmed.slice(1).split(/\s+/);
|
|
22195
|
-
const command = parts[0];
|
|
22196
|
-
const args = parts.slice(1);
|
|
22197
|
-
return { command, args };
|
|
23087
|
+
function FileRow({ file: file2, pathWidth }) {
|
|
23088
|
+
const name = truncatePath(file2.path, Math.max(6, pathWidth));
|
|
23089
|
+
return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { wrap: "truncate" }, /* @__PURE__ */ React6.createElement(Text6, { color: file2.untracked ? "yellow" : "white" }, name), file2.untracked ? /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " new") : /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, " +", file2.added ?? "\xB7"), /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, " -", file2.removed ?? "\xB7"))));
|
|
22198
23090
|
}
|
|
22199
|
-
function expandSkillTemplate(skill, options = {}) {
|
|
22200
|
-
const userInput = options.input?.trim() ?? "";
|
|
22201
|
-
const prompt = userInput ? `${skill.systemPromptFragment}
|
|
22202
23091
|
|
|
22203
|
-
|
|
22204
|
-
|
|
23092
|
+
// src/cli/app.tsx
|
|
23093
|
+
init_skills2();
|
|
22205
23094
|
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
|
|
22211
|
-
|
|
22212
|
-
|
|
22213
|
-
|
|
22214
|
-
|
|
22215
|
-
|
|
22216
|
-
|
|
22217
|
-
|
|
22218
|
-
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
23095
|
+
// src/cli/hooks/useGitChanges.ts
|
|
23096
|
+
import { useEffect, useRef, useState } from "react";
|
|
23097
|
+
import { execFile } from "node:child_process";
|
|
23098
|
+
var EMPTY_GIT_CHANGES = { isRepo: false, branch: null, files: [] };
|
|
23099
|
+
function parseNumstat(out) {
|
|
23100
|
+
const files = [];
|
|
23101
|
+
for (const line of out.split("\n")) {
|
|
23102
|
+
if (!line.trim()) continue;
|
|
23103
|
+
const parts = line.split(" ");
|
|
23104
|
+
if (parts.length < 3) continue;
|
|
23105
|
+
const [a, r] = parts;
|
|
23106
|
+
const rawPath = parts.slice(2).join(" ");
|
|
23107
|
+
files.push({
|
|
23108
|
+
path: normalizeRenamePath(rawPath),
|
|
23109
|
+
added: a === "-" ? null : Number.parseInt(a, 10),
|
|
23110
|
+
removed: r === "-" ? null : Number.parseInt(r, 10),
|
|
23111
|
+
untracked: false
|
|
23112
|
+
});
|
|
22223
23113
|
}
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
23114
|
+
return files;
|
|
23115
|
+
}
|
|
23116
|
+
function normalizeRenamePath(p3) {
|
|
23117
|
+
const braced = p3.match(/^(.*)\{.* => (.*)\}(.*)$/);
|
|
23118
|
+
if (braced) return `${braced[1]}${braced[2]}${braced[3]}`.replace(/\/\//g, "/");
|
|
23119
|
+
const arrow = p3.match(/^.* => (.*)$/);
|
|
23120
|
+
if (arrow) return arrow[1];
|
|
23121
|
+
return p3;
|
|
23122
|
+
}
|
|
23123
|
+
function parseUntracked(out) {
|
|
23124
|
+
const paths = [];
|
|
23125
|
+
for (const line of out.split("\n")) {
|
|
23126
|
+
if (!line.startsWith("?? ")) continue;
|
|
23127
|
+
let p3 = line.slice(3);
|
|
23128
|
+
if (p3.startsWith('"') && p3.endsWith('"')) p3 = p3.slice(1, -1);
|
|
23129
|
+
paths.push(p3);
|
|
23130
|
+
}
|
|
23131
|
+
return paths;
|
|
23132
|
+
}
|
|
23133
|
+
function mergeChanges(unstaged, staged, untrackedPaths) {
|
|
23134
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
23135
|
+
for (const f of [...unstaged, ...staged]) {
|
|
23136
|
+
const prev2 = byPath.get(f.path);
|
|
23137
|
+
if (!prev2) {
|
|
23138
|
+
byPath.set(f.path, { ...f });
|
|
23139
|
+
} else {
|
|
23140
|
+
prev2.added = prev2.added === null || f.added === null ? null : prev2.added + f.added;
|
|
23141
|
+
prev2.removed = prev2.removed === null || f.removed === null ? null : prev2.removed + f.removed;
|
|
22231
23142
|
}
|
|
22232
23143
|
}
|
|
22233
|
-
|
|
23144
|
+
for (const p3 of untrackedPaths) {
|
|
23145
|
+
if (!byPath.has(p3)) {
|
|
23146
|
+
byPath.set(p3, { path: p3, added: null, removed: null, untracked: true });
|
|
23147
|
+
}
|
|
23148
|
+
}
|
|
23149
|
+
const churn = (f) => f.untracked ? -1 : (f.added ?? 0) + (f.removed ?? 0);
|
|
23150
|
+
return [...byPath.values()].sort((a, b) => churn(b) - churn(a));
|
|
22234
23151
|
}
|
|
22235
|
-
function
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
23152
|
+
function runGit(args, cwd) {
|
|
23153
|
+
return new Promise((resolve, reject) => {
|
|
23154
|
+
execFile(
|
|
23155
|
+
"git",
|
|
23156
|
+
args,
|
|
23157
|
+
{ cwd, timeout: 5e3, windowsHide: true, maxBuffer: 4 * 1024 * 1024 },
|
|
23158
|
+
(err, stdout) => err ? reject(err) : resolve(stdout)
|
|
23159
|
+
);
|
|
23160
|
+
});
|
|
23161
|
+
}
|
|
23162
|
+
async function snapshotGitChanges(cwd) {
|
|
23163
|
+
let branch = null;
|
|
23164
|
+
try {
|
|
23165
|
+
branch = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd)).trim() || null;
|
|
23166
|
+
} catch {
|
|
22239
23167
|
}
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
/diff [--staged] \u2014 show uncommitted changes (or staged with --staged)
|
|
22272
|
-
/undo [--yes] \u2014 revert working-tree changes (destructive! requires --yes)
|
|
22273
|
-
/help \u2014 show this help
|
|
22274
|
-
/exit \u2014 exit the CLI
|
|
22275
|
-
|
|
22276
|
-
${formatSkillList(availableSkills)}`
|
|
22277
|
-
};
|
|
22278
|
-
case "exit":
|
|
22279
|
-
return { handled: true, kind: "exit", message: "Goodbye." };
|
|
22280
|
-
case "clear":
|
|
22281
|
-
return { handled: true, kind: "clear", message: "Transcript cleared." };
|
|
22282
|
-
case "login": {
|
|
22283
|
-
const provider = args[0];
|
|
22284
|
-
if (!provider) {
|
|
22285
|
-
return { handled: true, kind: "login", message: "Usage: /login <provider> [key] (or /login <provider> for OAuth if supported)" };
|
|
22286
|
-
}
|
|
22287
|
-
const key = args.slice(1).join(" ").trim();
|
|
22288
|
-
if (!key) {
|
|
22289
|
-
if (provider === "grok") {
|
|
22290
|
-
return { handled: true, kind: "login_oauth", provider };
|
|
22291
|
-
}
|
|
22292
|
-
return {
|
|
22293
|
-
handled: true,
|
|
22294
|
-
kind: "login",
|
|
22295
|
-
provider,
|
|
22296
|
-
message: `[login] no key supplied \u2014 set ${provider.toUpperCase()}_API_KEY env or pass the key: /login ${provider} <key>`
|
|
22297
|
-
};
|
|
22298
|
-
}
|
|
22299
|
-
return { handled: true, kind: "login", provider, loginKey: key };
|
|
22300
|
-
}
|
|
22301
|
-
case "model": {
|
|
22302
|
-
const model = args[0];
|
|
22303
|
-
if (!model) {
|
|
22304
|
-
return {
|
|
22305
|
-
handled: true,
|
|
22306
|
-
kind: "model_show",
|
|
22307
|
-
message: "Usage: /model <name> to change, /model to show current"
|
|
22308
|
-
};
|
|
22309
|
-
}
|
|
22310
|
-
if (model === "refresh") {
|
|
22311
|
-
return { handled: true, kind: "model_refresh" };
|
|
22312
|
-
}
|
|
22313
|
-
return { handled: true, kind: "model_set", model };
|
|
22314
|
-
}
|
|
22315
|
-
case "models": {
|
|
22316
|
-
const sub = args[0];
|
|
22317
|
-
if (sub === "refresh") {
|
|
22318
|
-
return { handled: true, kind: "models_refresh" };
|
|
22319
|
-
}
|
|
22320
|
-
return { handled: true, kind: "models_list" };
|
|
22321
|
-
}
|
|
22322
|
-
case "provider": {
|
|
22323
|
-
const subcommand = args[0];
|
|
22324
|
-
if (!subcommand) {
|
|
22325
|
-
return {
|
|
22326
|
-
handled: true,
|
|
22327
|
-
kind: "provider_list",
|
|
22328
|
-
message: "Usage: /provider <name> \u2014 switch active provider\n /provider custom <baseUrl> \u2014 set custom base URL (Ollama, LM Studio, vLLM, ...)\n /provider custom clear \u2014 clear the custom override\n /provider <name> refresh \u2014 force token refresh (v3-F)\n /provider <name> status \u2014 show key source, expiry, refresh impl (v3-F)\nAvailable: openai-compatible, minimax, glm, grok, custom"
|
|
22329
|
-
};
|
|
22330
|
-
}
|
|
22331
|
-
if (subcommand === "custom") {
|
|
22332
|
-
const target = args[1];
|
|
22333
|
-
if (!target || target === "show") {
|
|
22334
|
-
return {
|
|
22335
|
-
handled: true,
|
|
22336
|
-
kind: "provider_custom",
|
|
22337
|
-
message: "Usage: /provider custom <baseUrl> \u2014 set custom base URL for the active provider\n /provider custom clear \u2014 clear the custom override for the active provider"
|
|
22338
|
-
};
|
|
22339
|
-
}
|
|
22340
|
-
if (target === "clear") {
|
|
22341
|
-
return {
|
|
22342
|
-
handled: true,
|
|
22343
|
-
kind: "provider_custom",
|
|
22344
|
-
customClear: true
|
|
22345
|
-
};
|
|
23168
|
+
let status;
|
|
23169
|
+
try {
|
|
23170
|
+
status = await runGit(["status", "--porcelain=v1"], cwd);
|
|
23171
|
+
} catch {
|
|
23172
|
+
return EMPTY_GIT_CHANGES;
|
|
23173
|
+
}
|
|
23174
|
+
const [unstaged, staged] = await Promise.all([
|
|
23175
|
+
runGit(["diff", "--numstat"], cwd).catch(() => ""),
|
|
23176
|
+
runGit(["diff", "--numstat", "--cached"], cwd).catch(() => "")
|
|
23177
|
+
]);
|
|
23178
|
+
return {
|
|
23179
|
+
isRepo: true,
|
|
23180
|
+
branch,
|
|
23181
|
+
files: mergeChanges(parseNumstat(unstaged), parseNumstat(staged), parseUntracked(status))
|
|
23182
|
+
};
|
|
23183
|
+
}
|
|
23184
|
+
function useGitChanges(opts = {}) {
|
|
23185
|
+
const { pollMs = 4e3, cwd = process.cwd() } = opts;
|
|
23186
|
+
const [changes, setChanges] = useState(EMPTY_GIT_CHANGES);
|
|
23187
|
+
const lastJson = useRef("");
|
|
23188
|
+
useEffect(() => {
|
|
23189
|
+
let cancelled = false;
|
|
23190
|
+
let timer = null;
|
|
23191
|
+
const tick = async () => {
|
|
23192
|
+
try {
|
|
23193
|
+
const snap = await snapshotGitChanges(cwd);
|
|
23194
|
+
if (cancelled) return;
|
|
23195
|
+
const json2 = JSON.stringify(snap);
|
|
23196
|
+
if (json2 !== lastJson.current) {
|
|
23197
|
+
lastJson.current = json2;
|
|
23198
|
+
setChanges(snap);
|
|
22346
23199
|
}
|
|
22347
|
-
|
|
22348
|
-
return {
|
|
22349
|
-
handled: true,
|
|
22350
|
-
kind: "provider_custom",
|
|
22351
|
-
customEndpoint: url2
|
|
22352
|
-
};
|
|
22353
|
-
}
|
|
22354
|
-
const providerId = subcommand;
|
|
22355
|
-
const sub = args[1];
|
|
22356
|
-
if (sub === "refresh") {
|
|
22357
|
-
return { handled: true, kind: "provider_refresh", provider: providerId };
|
|
23200
|
+
} catch {
|
|
22358
23201
|
}
|
|
22359
|
-
if (
|
|
22360
|
-
|
|
23202
|
+
if (!cancelled) timer = setTimeout(tick, pollMs);
|
|
23203
|
+
};
|
|
23204
|
+
void tick();
|
|
23205
|
+
return () => {
|
|
23206
|
+
cancelled = true;
|
|
23207
|
+
if (timer !== null) clearTimeout(timer);
|
|
23208
|
+
};
|
|
23209
|
+
}, [pollMs, cwd]);
|
|
23210
|
+
return changes;
|
|
23211
|
+
}
|
|
23212
|
+
|
|
23213
|
+
// src/cli/hooks/useExecutionTimer.ts
|
|
23214
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
23215
|
+
function useExecutionTimer(busy, tickMs = 1e3) {
|
|
23216
|
+
const [elapsedMs, setElapsedMs] = useState2(null);
|
|
23217
|
+
const [lastMs, setLastMs] = useState2(null);
|
|
23218
|
+
const startRef = useRef2(null);
|
|
23219
|
+
useEffect2(() => {
|
|
23220
|
+
if (busy) {
|
|
23221
|
+
startRef.current = Date.now();
|
|
23222
|
+
setElapsedMs(0);
|
|
23223
|
+
const t = setInterval(() => {
|
|
23224
|
+
if (startRef.current !== null) setElapsedMs(Date.now() - startRef.current);
|
|
23225
|
+
}, tickMs);
|
|
23226
|
+
return () => clearInterval(t);
|
|
23227
|
+
}
|
|
23228
|
+
if (startRef.current !== null) {
|
|
23229
|
+
setLastMs(Date.now() - startRef.current);
|
|
23230
|
+
startRef.current = null;
|
|
23231
|
+
}
|
|
23232
|
+
setElapsedMs(null);
|
|
23233
|
+
return void 0;
|
|
23234
|
+
}, [busy, tickMs]);
|
|
23235
|
+
return { elapsedMs, lastMs };
|
|
23236
|
+
}
|
|
23237
|
+
|
|
23238
|
+
// src/cli/utils/paths.ts
|
|
23239
|
+
import { homedir } from "node:os";
|
|
23240
|
+
function shortenCwd(p3, maxLen = 40, home = homedir()) {
|
|
23241
|
+
let out = p3;
|
|
23242
|
+
if (home && (out === home || out.startsWith(`${home}\\`) || out.startsWith(`${home}/`))) {
|
|
23243
|
+
out = `~${out.slice(home.length)}`;
|
|
23244
|
+
}
|
|
23245
|
+
if (out.length <= maxLen) return out;
|
|
23246
|
+
return `\u2026${out.slice(-(maxLen - 1))}`;
|
|
23247
|
+
}
|
|
23248
|
+
|
|
23249
|
+
// packages/core/dist/agents/skills/builtin/debugging.js
|
|
23250
|
+
init_skills();
|
|
23251
|
+
var CLARIFICATION_PROTOCOL = `
|
|
23252
|
+
|
|
23253
|
+
WHEN TO ASK THE USER (clarification):
|
|
23254
|
+
If a single missing fact would materially change your output (target platform, scope, a binary design choice with significant trade-offs, a constraint you cannot safely assume), pause and ask the user by appending EXACTLY this block at the end of your message:
|
|
23255
|
+
|
|
23256
|
+
---QUESTION---
|
|
23257
|
+
{ "question": "One focused question", "choices": ["Option A", "Option B", "Option C"], "context": "Why this matters" }
|
|
23258
|
+
---END---
|
|
23259
|
+
|
|
23260
|
+
Rules for clarifications:
|
|
23261
|
+
- Ask AT MOST ONE question per turn, and only when genuinely blocked.
|
|
23262
|
+
- Prefer a small set of concrete "choices" (2-4). The user can still type a wave-in response.
|
|
23263
|
+
- Do NOT ask for information that could be reasonably assumed or already in shared context.
|
|
23264
|
+
- If you can proceed with a sound documented assumption, DO SO instead of asking.`;
|
|
23265
|
+
var reproduceBug = {
|
|
23266
|
+
id: "reproduce-bug",
|
|
23267
|
+
version: "1.0.0",
|
|
23268
|
+
name: "Reproduce Bug",
|
|
23269
|
+
description: "Convert a bug report into a MINIMAL failing test. The test must fail without any code changes, then pass after the fix.",
|
|
23270
|
+
category: "debug",
|
|
23271
|
+
requiredRoles: ["minos", "pluton"],
|
|
23272
|
+
requiredTools: ["read_file", "write_file"],
|
|
23273
|
+
estimatedCost: "medium",
|
|
23274
|
+
enabledByDefault: true,
|
|
23275
|
+
builtin: true,
|
|
23276
|
+
triggers: [
|
|
23277
|
+
"Bug report without a minimal reproduction",
|
|
23278
|
+
"Bug that only happens in production (not in tests)",
|
|
23279
|
+
'Bug that happens "sometimes" (intermittent)',
|
|
23280
|
+
"Bug reported via screenshot or video, not repro steps",
|
|
23281
|
+
"A complex multi-agent state race condition that is hard to verify without a test"
|
|
23282
|
+
],
|
|
23283
|
+
antiPatterns: [
|
|
23284
|
+
"Bug is in production AND has minimal repro steps already \u2014 skip to fix",
|
|
23285
|
+
"Bug is in test code itself \u2014 fix the test, not the production code",
|
|
23286
|
+
"Bug requires special hardware/network state \u2014 use a mock instead"
|
|
23287
|
+
],
|
|
23288
|
+
requires: [],
|
|
23289
|
+
relatedSkills: ["debug-with-rag", "root-cause-five-whys"],
|
|
23290
|
+
tags: ["debug", "reproduction", "test", "minimal-example"],
|
|
23291
|
+
examples: [
|
|
23292
|
+
{
|
|
23293
|
+
input: 'Bug report: "Council sometimes shows duplicate messages at the start of a session"',
|
|
23294
|
+
output: {
|
|
23295
|
+
minimalRepro: `// tests/regression/duplicate-messages.test.ts
|
|
23296
|
+
import { runCouncilPure } from '../agents/councilApi.js';
|
|
23297
|
+
|
|
23298
|
+
describe('council duplicate message bug', () => {
|
|
23299
|
+
it('does not emit duplicate user message when session resumes', async () => {
|
|
23300
|
+
const mockProvider = async function* () {
|
|
23301
|
+
yield { kind: 'text', delta: 'first' };
|
|
23302
|
+
yield { kind: 'finish', reason: 'stop' };
|
|
23303
|
+
};
|
|
23304
|
+
const harness = new AgentHarness({
|
|
23305
|
+
model: 'test', provider: 'test',
|
|
23306
|
+
messages: [
|
|
23307
|
+
{ role: 'user', content: 'hi' },
|
|
23308
|
+
{ role: 'user', content: 'hi' }, // duplicate from session resume
|
|
23309
|
+
],
|
|
23310
|
+
tools: [],
|
|
23311
|
+
providerStream: mockProvider,
|
|
23312
|
+
});
|
|
23313
|
+
const events = [];
|
|
23314
|
+
for await (const e of harness.run()) events.push(e);
|
|
23315
|
+
const userMessages = events.filter(e => e.type === 'message_delta').length;
|
|
23316
|
+
expect(userMessages).toBeGreaterThan(0); // sanity
|
|
23317
|
+
// The actual bug: duplicate is rendered twice
|
|
23318
|
+
// After fix, expect exactly 1 user message rendered
|
|
23319
|
+
});
|
|
23320
|
+
});`,
|
|
23321
|
+
reproSteps: [
|
|
23322
|
+
"1. Set up session with duplicate user message in transcript",
|
|
23323
|
+
"2. Run runCouncilPure",
|
|
23324
|
+
"3. Observe duplicate message in output"
|
|
23325
|
+
],
|
|
23326
|
+
whyMinimal: "Removed all agents except the first specialist (was: sisyphus+prometheus+hephaestus+oracle+chairman). Removed RAG context, workspace context. Kept only the duplication pattern."
|
|
22361
23327
|
}
|
|
22362
|
-
return { handled: true, kind: "provider_set", provider: subcommand };
|
|
22363
23328
|
}
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
|
|
22384
|
-
|
|
22385
|
-
|
|
22386
|
-
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
|
|
22390
|
-
|
|
22391
|
-
|
|
22392
|
-
|
|
22393
|
-
|
|
22394
|
-
|
|
22395
|
-
|
|
22396
|
-
|
|
22397
|
-
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22401
|
-
|
|
22402
|
-
|
|
22403
|
-
|
|
22404
|
-
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
};
|
|
22429
|
-
case "council": {
|
|
22430
|
-
const input = args.join(" ").trim();
|
|
22431
|
-
if (!input) {
|
|
22432
|
-
return {
|
|
22433
|
-
handled: true,
|
|
22434
|
-
kind: "council",
|
|
22435
|
-
message: "Usage: /council <input> \u2014 invokes the multi-agent council on the input"
|
|
22436
|
-
};
|
|
22437
|
-
}
|
|
22438
|
-
return { handled: true, kind: "council", councilInput: input };
|
|
22439
|
-
}
|
|
22440
|
-
case "council-feedback": {
|
|
22441
|
-
const memberId = args[0];
|
|
22442
|
-
const scoreStr = args[1];
|
|
22443
|
-
if (!memberId || !scoreStr) {
|
|
22444
|
-
return {
|
|
22445
|
-
handled: true,
|
|
22446
|
-
kind: "council_feedback",
|
|
22447
|
-
message: "Usage: /council-feedback <memberId> <1-5> [note...] \u2014 rate a council member (e.g. /council-feedback sisyphus 4 great framing)"
|
|
22448
|
-
};
|
|
22449
|
-
}
|
|
22450
|
-
const score = Number.parseInt(scoreStr, 10);
|
|
22451
|
-
if (!Number.isInteger(score) || score < 1 || score > 5) {
|
|
22452
|
-
return {
|
|
22453
|
-
handled: true,
|
|
22454
|
-
kind: "council_feedback",
|
|
22455
|
-
message: `Invalid score "${scoreStr}" \u2014 must be an integer in [1, 5].`
|
|
22456
|
-
};
|
|
22457
|
-
}
|
|
22458
|
-
const note = args.slice(2).join(" ").trim() || void 0;
|
|
22459
|
-
return {
|
|
22460
|
-
handled: true,
|
|
22461
|
-
kind: "council_feedback",
|
|
22462
|
-
feedbackMemberId: memberId,
|
|
22463
|
-
feedbackScore: score,
|
|
22464
|
-
...note ? { feedbackNote: note } : {}
|
|
22465
|
-
};
|
|
22466
|
-
}
|
|
22467
|
-
case "skill": {
|
|
22468
|
-
const skillId = args[0];
|
|
22469
|
-
if (!skillId) {
|
|
22470
|
-
return {
|
|
22471
|
-
handled: true,
|
|
22472
|
-
kind: "skill",
|
|
22473
|
-
skillError: "Usage: /skill <name> [input]",
|
|
22474
|
-
message: formatSkillList(availableSkills)
|
|
22475
|
-
};
|
|
22476
|
-
}
|
|
22477
|
-
const skill = availableSkills.find((s) => s.id === skillId);
|
|
22478
|
-
if (!skill) {
|
|
22479
|
-
return {
|
|
22480
|
-
handled: true,
|
|
22481
|
-
kind: "skill",
|
|
22482
|
-
skillError: `Unknown skill: "${skillId}". Use /skill <TAB> for autocomplete.`,
|
|
22483
|
-
message: formatSkillList(availableSkills)
|
|
22484
|
-
};
|
|
22485
|
-
}
|
|
22486
|
-
const input = args.slice(1).join(" ");
|
|
22487
|
-
const expandedSkill = expandSkillTemplate(skill, { input });
|
|
22488
|
-
return { handled: true, kind: "skill", expandedSkill };
|
|
22489
|
-
}
|
|
22490
|
-
case "skill_stats": {
|
|
22491
|
-
const skillId = args[0];
|
|
22492
|
-
return {
|
|
22493
|
-
handled: true,
|
|
22494
|
-
kind: "skill_stats",
|
|
22495
|
-
skillStatsSkillId: skillId,
|
|
22496
|
-
message: skillId ? `Computing stats for skill "${skillId}"\u2026` : "Computing stats for all skills\u2026"
|
|
22497
|
-
};
|
|
22498
|
-
}
|
|
22499
|
-
case "skill-compare": {
|
|
22500
|
-
const id1 = args[0];
|
|
22501
|
-
const id2 = args[1];
|
|
22502
|
-
if (!id1 || !id2) {
|
|
22503
|
-
return {
|
|
22504
|
-
handled: true,
|
|
22505
|
-
kind: "skill-compare",
|
|
22506
|
-
message: "\u26A0 /skill-compare requires exactly 2 skill IDs (e.g. `/skill-compare debug refactor`)."
|
|
22507
|
-
};
|
|
22508
|
-
}
|
|
22509
|
-
return {
|
|
22510
|
-
handled: true,
|
|
22511
|
-
kind: "skill-compare",
|
|
22512
|
-
compareIds: [id1, id2],
|
|
22513
|
-
message: `Comparing skills "${id1}" vs "${id2}"\u2026`
|
|
22514
|
-
};
|
|
22515
|
-
}
|
|
22516
|
-
case "steer": {
|
|
22517
|
-
let interrupt = false;
|
|
22518
|
-
const filtered = [];
|
|
22519
|
-
for (const a of args) {
|
|
22520
|
-
if (a === "--interrupt" || a === "-i") {
|
|
22521
|
-
interrupt = true;
|
|
22522
|
-
} else {
|
|
22523
|
-
filtered.push(a);
|
|
22524
|
-
}
|
|
22525
|
-
}
|
|
22526
|
-
const text2 = filtered.join(" ").trim();
|
|
22527
|
-
if (!text2) {
|
|
22528
|
-
return {
|
|
22529
|
-
handled: true,
|
|
22530
|
-
kind: interrupt ? "steer_interrupt" : "steer",
|
|
22531
|
-
message: "Usage: /steer [--interrupt|-i] <text> \u2014 enqueue (and optionally cancel) a follow-up prompt"
|
|
22532
|
-
};
|
|
22533
|
-
}
|
|
22534
|
-
return {
|
|
22535
|
-
handled: true,
|
|
22536
|
-
kind: interrupt ? "steer_interrupt" : "steer",
|
|
22537
|
-
steerText: text2
|
|
22538
|
-
};
|
|
22539
|
-
}
|
|
22540
|
-
case "diff": {
|
|
22541
|
-
const staged = args.includes("--staged") || args.includes("--cached");
|
|
22542
|
-
return { handled: true, kind: "diff", diffStaged: staged ? true : void 0 };
|
|
22543
|
-
}
|
|
22544
|
-
case "promote-member": {
|
|
22545
|
-
const memberId = args[0];
|
|
22546
|
-
if (!memberId) {
|
|
22547
|
-
return {
|
|
22548
|
-
handled: true,
|
|
22549
|
-
kind: "promote_member_error",
|
|
22550
|
-
promoteMemberError: "Usage: /promote-member <memberId> \u2014 e.g. /promote-member geryon"
|
|
22551
|
-
};
|
|
22552
|
-
}
|
|
22553
|
-
return { handled: true, kind: "promote_member", promoteMemberId: memberId };
|
|
22554
|
-
}
|
|
22555
|
-
case "update": {
|
|
22556
|
-
let force = false;
|
|
22557
|
-
for (const a of args) {
|
|
22558
|
-
if (a === "--yes" || a === "-y") {
|
|
22559
|
-
force = true;
|
|
22560
|
-
}
|
|
22561
|
-
}
|
|
22562
|
-
if (args.length > 0 && !force) {
|
|
22563
|
-
return {
|
|
22564
|
-
handled: true,
|
|
22565
|
-
kind: "update_usage",
|
|
22566
|
-
message: "Usage: /update \u2014 check for the latest zelari-code version\n /update --yes (or -y) \u2014 install the latest version (will ask you to restart manually)"
|
|
22567
|
-
};
|
|
22568
|
-
}
|
|
22569
|
-
return {
|
|
22570
|
-
handled: true,
|
|
22571
|
-
kind: force ? "update_perform" : "update_check",
|
|
22572
|
-
updateForce: force
|
|
22573
|
-
};
|
|
22574
|
-
}
|
|
22575
|
-
case "undo": {
|
|
22576
|
-
const confirmed = args.includes("--yes") || args.includes("-y");
|
|
22577
|
-
if (confirmed) {
|
|
22578
|
-
return { handled: true, kind: "undo_confirm", undoConfirmed: true };
|
|
22579
|
-
}
|
|
22580
|
-
return {
|
|
22581
|
-
handled: true,
|
|
22582
|
-
kind: "undo",
|
|
22583
|
-
message: "\u26A0 /undo is DESTRUCTIVE \u2014 it reverts all unstaged modifications and unstages everything.\nUse `/undo --yes` (or `/undo -y`) to confirm."
|
|
22584
|
-
};
|
|
22585
|
-
}
|
|
22586
|
-
case "workspace": {
|
|
22587
|
-
const sub = args[0];
|
|
22588
|
-
if (!sub || sub === "--help" || sub === "help") {
|
|
22589
|
-
return {
|
|
22590
|
-
handled: true,
|
|
22591
|
-
kind: "workspace",
|
|
22592
|
-
message: "Workspace commands (`.zelari/` + `AGENTS.MD`):\n /workspace \u2014 list artifacts (plan, decisions, risks, reviews, docs)\n /workspace show plan \u2014 render plan.md\n /workspace show decisions \u2014 list ADRs (Architecture Decision Records)\n /workspace show risks \u2014 render risks.md\n /workspace show agents \u2014 render AGENTS.MD\n /workspace show docs \u2014 list docs drafts\n /workspace sync \u2014 re-run AGENTS.MD auto-curation now\n /workspace reset \u2014 delete .zelari/ (destructive \u2014 confirmation required)\n\nSee `docs/plans/2026-07-01-council-workspace-cli-stubs.md` for schema."
|
|
22593
|
-
};
|
|
22594
|
-
}
|
|
22595
|
-
if (sub === "show") {
|
|
22596
|
-
const what = args[1];
|
|
22597
|
-
if (!what) {
|
|
22598
|
-
return {
|
|
22599
|
-
handled: true,
|
|
22600
|
-
kind: "workspace_show",
|
|
22601
|
-
workspaceWhat: "",
|
|
22602
|
-
message: "Usage: /workspace show <plan|decisions|risks|agents|docs>"
|
|
22603
|
-
};
|
|
22604
|
-
}
|
|
22605
|
-
if (!["plan", "decisions", "risks", "agents", "docs"].includes(what)) {
|
|
22606
|
-
return {
|
|
22607
|
-
handled: true,
|
|
22608
|
-
kind: "workspace_show",
|
|
22609
|
-
workspaceWhat: "",
|
|
22610
|
-
message: `Unknown artifact "${what}". Available: plan | decisions | risks | agents | docs`
|
|
22611
|
-
};
|
|
22612
|
-
}
|
|
22613
|
-
return {
|
|
22614
|
-
handled: true,
|
|
22615
|
-
kind: "workspace_show",
|
|
22616
|
-
workspaceWhat: what
|
|
22617
|
-
};
|
|
22618
|
-
}
|
|
22619
|
-
if (sub === "sync") {
|
|
22620
|
-
return { handled: true, kind: "workspace_sync" };
|
|
22621
|
-
}
|
|
22622
|
-
if (sub === "reset") {
|
|
22623
|
-
const force = args.includes("--yes") || args.includes("-y");
|
|
22624
|
-
if (force) {
|
|
22625
|
-
return { handled: true, kind: "workspace_reset", workspaceForce: true };
|
|
22626
|
-
}
|
|
22627
|
-
return {
|
|
22628
|
-
handled: true,
|
|
22629
|
-
kind: "workspace_reset",
|
|
22630
|
-
message: "\u26A0 /workspace reset is DESTRUCTIVE \u2014 deletes the entire `.zelari/` directory.\nUse `/workspace reset --yes` to confirm."
|
|
22631
|
-
};
|
|
22632
|
-
}
|
|
22633
|
-
return {
|
|
22634
|
-
handled: true,
|
|
22635
|
-
kind: "workspace",
|
|
22636
|
-
message: `Unknown /workspace subcommand: "${sub}". Type /workspace for usage.`
|
|
22637
|
-
};
|
|
22638
|
-
}
|
|
22639
|
-
default:
|
|
22640
|
-
return {
|
|
22641
|
-
handled: false,
|
|
22642
|
-
kind: "unknown",
|
|
22643
|
-
message: `Unknown command: /${command}. Type /help for available commands.`
|
|
22644
|
-
};
|
|
22645
|
-
}
|
|
22646
|
-
}
|
|
22647
|
-
|
|
22648
|
-
// src/cli/app.tsx
|
|
22649
|
-
init_skills2();
|
|
22650
|
-
|
|
22651
|
-
// packages/core/dist/agents/skills/builtin/debugging.js
|
|
22652
|
-
init_skills();
|
|
22653
|
-
var CLARIFICATION_PROTOCOL = `
|
|
22654
|
-
|
|
22655
|
-
WHEN TO ASK THE USER (clarification):
|
|
22656
|
-
If a single missing fact would materially change your output (target platform, scope, a binary design choice with significant trade-offs, a constraint you cannot safely assume), pause and ask the user by appending EXACTLY this block at the end of your message:
|
|
22657
|
-
|
|
22658
|
-
---QUESTION---
|
|
22659
|
-
{ "question": "One focused question", "choices": ["Option A", "Option B", "Option C"], "context": "Why this matters" }
|
|
22660
|
-
---END---
|
|
22661
|
-
|
|
22662
|
-
Rules for clarifications:
|
|
22663
|
-
- Ask AT MOST ONE question per turn, and only when genuinely blocked.
|
|
22664
|
-
- Prefer a small set of concrete "choices" (2-4). The user can still type a wave-in response.
|
|
22665
|
-
- Do NOT ask for information that could be reasonably assumed or already in shared context.
|
|
22666
|
-
- If you can proceed with a sound documented assumption, DO SO instead of asking.`;
|
|
22667
|
-
var reproduceBug = {
|
|
22668
|
-
id: "reproduce-bug",
|
|
22669
|
-
version: "1.0.0",
|
|
22670
|
-
name: "Reproduce Bug",
|
|
22671
|
-
description: "Convert a bug report into a MINIMAL failing test. The test must fail without any code changes, then pass after the fix.",
|
|
22672
|
-
category: "debug",
|
|
22673
|
-
requiredRoles: ["minos", "pluton"],
|
|
22674
|
-
requiredTools: ["read_file", "write_file"],
|
|
22675
|
-
estimatedCost: "medium",
|
|
22676
|
-
enabledByDefault: true,
|
|
22677
|
-
builtin: true,
|
|
22678
|
-
triggers: [
|
|
22679
|
-
"Bug report without a minimal reproduction",
|
|
22680
|
-
"Bug that only happens in production (not in tests)",
|
|
22681
|
-
'Bug that happens "sometimes" (intermittent)',
|
|
22682
|
-
"Bug reported via screenshot or video, not repro steps",
|
|
22683
|
-
"A complex multi-agent state race condition that is hard to verify without a test"
|
|
22684
|
-
],
|
|
22685
|
-
antiPatterns: [
|
|
22686
|
-
"Bug is in production AND has minimal repro steps already \u2014 skip to fix",
|
|
22687
|
-
"Bug is in test code itself \u2014 fix the test, not the production code",
|
|
22688
|
-
"Bug requires special hardware/network state \u2014 use a mock instead"
|
|
22689
|
-
],
|
|
22690
|
-
requires: [],
|
|
22691
|
-
relatedSkills: ["debug-with-rag", "root-cause-five-whys"],
|
|
22692
|
-
tags: ["debug", "reproduction", "test", "minimal-example"],
|
|
22693
|
-
examples: [
|
|
22694
|
-
{
|
|
22695
|
-
input: 'Bug report: "Council sometimes shows duplicate messages at the start of a session"',
|
|
22696
|
-
output: {
|
|
22697
|
-
minimalRepro: `// tests/regression/duplicate-messages.test.ts
|
|
22698
|
-
import { runCouncilPure } from '../agents/councilApi.js';
|
|
22699
|
-
|
|
22700
|
-
describe('council duplicate message bug', () => {
|
|
22701
|
-
it('does not emit duplicate user message when session resumes', async () => {
|
|
22702
|
-
const mockProvider = async function* () {
|
|
22703
|
-
yield { kind: 'text', delta: 'first' };
|
|
22704
|
-
yield { kind: 'finish', reason: 'stop' };
|
|
22705
|
-
};
|
|
22706
|
-
const harness = new AgentHarness({
|
|
22707
|
-
model: 'test', provider: 'test',
|
|
22708
|
-
messages: [
|
|
22709
|
-
{ role: 'user', content: 'hi' },
|
|
22710
|
-
{ role: 'user', content: 'hi' }, // duplicate from session resume
|
|
22711
|
-
],
|
|
22712
|
-
tools: [],
|
|
22713
|
-
providerStream: mockProvider,
|
|
22714
|
-
});
|
|
22715
|
-
const events = [];
|
|
22716
|
-
for await (const e of harness.run()) events.push(e);
|
|
22717
|
-
const userMessages = events.filter(e => e.type === 'message_delta').length;
|
|
22718
|
-
expect(userMessages).toBeGreaterThan(0); // sanity
|
|
22719
|
-
// The actual bug: duplicate is rendered twice
|
|
22720
|
-
// After fix, expect exactly 1 user message rendered
|
|
22721
|
-
});
|
|
22722
|
-
});`,
|
|
22723
|
-
reproSteps: [
|
|
22724
|
-
"1. Set up session with duplicate user message in transcript",
|
|
22725
|
-
"2. Run runCouncilPure",
|
|
22726
|
-
"3. Observe duplicate message in output"
|
|
22727
|
-
],
|
|
22728
|
-
whyMinimal: "Removed all agents except the first specialist (was: sisyphus+prometheus+hephaestus+oracle+chairman). Removed RAG context, workspace context. Kept only the duplication pattern."
|
|
22729
|
-
}
|
|
22730
|
-
}
|
|
22731
|
-
],
|
|
22732
|
-
outputSchema: "{ minimalRepro: string; reproSteps: string[]; whyMinimal: string }",
|
|
22733
|
-
systemPromptFragment: `You are converting a bug report into a minimal failing test.
|
|
22734
|
-
|
|
22735
|
-
## Methodology
|
|
22736
|
-
1. **Read the bug report** carefully: what's the exact symptom? When does it happen?
|
|
22737
|
-
2. **Strip to essentials**: remove every agent, tool, env var, user state that isn't strictly required.
|
|
22738
|
-
3. **Identify the trigger**: what ONE input causes the bug?
|
|
22739
|
-
4. **Write the failing test**: it must FAIL on the current (buggy) code.
|
|
22740
|
-
5. **Verify it fails**: the test must fail without any code changes.
|
|
22741
|
-
6. **After the fix lands**, the test must PASS.
|
|
22742
|
-
|
|
22743
|
-
## Output format (JSON-typed)
|
|
22744
|
-
- minimalRepro: string (the test code, runnable as-is)
|
|
22745
|
-
- reproSteps: string[] (3-7 numbered steps to reproduce manually)
|
|
22746
|
-
- whyMinimal: string (what you stripped + why)
|
|
22747
|
-
|
|
22748
|
-
## Minimal-repro principles
|
|
22749
|
-
- **One assertion per test** \u2014 easier to debug when the test fails
|
|
22750
|
-
- **Deterministic** \u2014 no flaky timing, no random data
|
|
22751
|
-
- **No external dependencies** \u2014 use mocks for network/DB
|
|
22752
|
-
- **Fast** \u2014 under 100ms if possible
|
|
22753
|
-
- **Independent** \u2014 doesn't depend on other tests' state
|
|
22754
|
-
|
|
22755
|
-
Stay under 300 words.${CLARIFICATION_PROTOCOL}`
|
|
22756
|
-
};
|
|
22757
|
-
var debugWithRag = {
|
|
22758
|
-
id: "debug-with-rag",
|
|
22759
|
-
version: "1.0.0",
|
|
22760
|
-
name: "Debug With RAG",
|
|
22761
|
-
description: "Search the knowledge base for similar past bugs, read the stack trace, propose the most likely root cause and a concrete fix.",
|
|
22762
|
-
category: "debug",
|
|
22763
|
-
requiredRoles: ["minos", "pluton"],
|
|
22764
|
-
requiredTools: ["searchRAG", "grep_content", "read_file"],
|
|
22765
|
-
estimatedCost: "medium",
|
|
22766
|
-
enabledByDefault: true,
|
|
22767
|
-
builtin: true,
|
|
22768
|
-
triggers: [
|
|
22769
|
-
"A bug report with a stack trace or error message",
|
|
22770
|
-
"A regression: feature worked before, now broken",
|
|
22771
|
-
"A flaky test that fails intermittently",
|
|
22772
|
-
"Performance regression: response time > 5s where it used to be < 500ms",
|
|
22773
|
-
"A memory leak causing out-of-memory crashes"
|
|
22774
|
-
],
|
|
22775
|
-
antiPatterns: [
|
|
22776
|
-
"Bug is obvious from the error message (e.g. TypeError: cannot read property of undefined \u2014 fix directly)",
|
|
22777
|
-
"No stack trace or repro steps \u2014 first ask the user for a minimal repro",
|
|
22778
|
-
"A test is failing because the test itself is wrong \u2014 debug the test, not the code"
|
|
22779
|
-
],
|
|
22780
|
-
requires: ["reproduce-bug"],
|
|
22781
|
-
relatedSkills: ["root-cause-five-whys", "reproduce-bug"],
|
|
22782
|
-
tags: ["debug", "rag", "stack-trace", "root-cause", "regression"],
|
|
22783
|
-
examples: [
|
|
22784
|
-
{
|
|
22785
|
-
input: 'TypeError: Cannot read properties of undefined (reading "map") at Council.tsx:847',
|
|
22786
|
-
output: {
|
|
22787
|
-
ragSearch: 'searchRAG(query="Council.tsx undefined map error") returned 2 prior incidents: similar bug fixed in commit a3f9d by adding optional chaining; another similar issue in Council.tsx:612.',
|
|
22788
|
-
stackTraceAnalysis: "Line 847 is inside `renderMessages()`. The most likely cause: `messages` is undefined when `runCouncilPure()` returns early on the first iteration before any agent has produced output.",
|
|
22789
|
-
rootCause: "race condition: messages array is set after the first agent completes, but renderMessages() is called during the loading state when messages is still undefined.",
|
|
22790
|
-
proposedFix: `function renderMessages() {
|
|
22791
|
-
const messages = session.messages ?? [];
|
|
22792
|
-
return messages.map(m => <Message key={m.id} {...m} />);
|
|
22793
|
-
}`,
|
|
22794
|
-
verification: "Add a unit test that calls renderMessages() with messages=undefined and expects [] (not crash). Manual: trigger the bug scenario, confirm no crash."
|
|
23329
|
+
],
|
|
23330
|
+
outputSchema: "{ minimalRepro: string; reproSteps: string[]; whyMinimal: string }",
|
|
23331
|
+
systemPromptFragment: `You are converting a bug report into a minimal failing test.
|
|
23332
|
+
|
|
23333
|
+
## Methodology
|
|
23334
|
+
1. **Read the bug report** carefully: what's the exact symptom? When does it happen?
|
|
23335
|
+
2. **Strip to essentials**: remove every agent, tool, env var, user state that isn't strictly required.
|
|
23336
|
+
3. **Identify the trigger**: what ONE input causes the bug?
|
|
23337
|
+
4. **Write the failing test**: it must FAIL on the current (buggy) code.
|
|
23338
|
+
5. **Verify it fails**: the test must fail without any code changes.
|
|
23339
|
+
6. **After the fix lands**, the test must PASS.
|
|
23340
|
+
|
|
23341
|
+
## Output format (JSON-typed)
|
|
23342
|
+
- minimalRepro: string (the test code, runnable as-is)
|
|
23343
|
+
- reproSteps: string[] (3-7 numbered steps to reproduce manually)
|
|
23344
|
+
- whyMinimal: string (what you stripped + why)
|
|
23345
|
+
|
|
23346
|
+
## Minimal-repro principles
|
|
23347
|
+
- **One assertion per test** \u2014 easier to debug when the test fails
|
|
23348
|
+
- **Deterministic** \u2014 no flaky timing, no random data
|
|
23349
|
+
- **No external dependencies** \u2014 use mocks for network/DB
|
|
23350
|
+
- **Fast** \u2014 under 100ms if possible
|
|
23351
|
+
- **Independent** \u2014 doesn't depend on other tests' state
|
|
23352
|
+
|
|
23353
|
+
Stay under 300 words.${CLARIFICATION_PROTOCOL}`
|
|
23354
|
+
};
|
|
23355
|
+
var debugWithRag = {
|
|
23356
|
+
id: "debug-with-rag",
|
|
23357
|
+
version: "1.0.0",
|
|
23358
|
+
name: "Debug With RAG",
|
|
23359
|
+
description: "Search the knowledge base for similar past bugs, read the stack trace, propose the most likely root cause and a concrete fix.",
|
|
23360
|
+
category: "debug",
|
|
23361
|
+
requiredRoles: ["minos", "pluton"],
|
|
23362
|
+
requiredTools: ["searchRAG", "grep_content", "read_file"],
|
|
23363
|
+
estimatedCost: "medium",
|
|
23364
|
+
enabledByDefault: true,
|
|
23365
|
+
builtin: true,
|
|
23366
|
+
triggers: [
|
|
23367
|
+
"A bug report with a stack trace or error message",
|
|
23368
|
+
"A regression: feature worked before, now broken",
|
|
23369
|
+
"A flaky test that fails intermittently",
|
|
23370
|
+
"Performance regression: response time > 5s where it used to be < 500ms",
|
|
23371
|
+
"A memory leak causing out-of-memory crashes"
|
|
23372
|
+
],
|
|
23373
|
+
antiPatterns: [
|
|
23374
|
+
"Bug is obvious from the error message (e.g. TypeError: cannot read property of undefined \u2014 fix directly)",
|
|
23375
|
+
"No stack trace or repro steps \u2014 first ask the user for a minimal repro",
|
|
23376
|
+
"A test is failing because the test itself is wrong \u2014 debug the test, not the code"
|
|
23377
|
+
],
|
|
23378
|
+
requires: ["reproduce-bug"],
|
|
23379
|
+
relatedSkills: ["root-cause-five-whys", "reproduce-bug"],
|
|
23380
|
+
tags: ["debug", "rag", "stack-trace", "root-cause", "regression"],
|
|
23381
|
+
examples: [
|
|
23382
|
+
{
|
|
23383
|
+
input: 'TypeError: Cannot read properties of undefined (reading "map") at Council.tsx:847',
|
|
23384
|
+
output: {
|
|
23385
|
+
ragSearch: 'searchRAG(query="Council.tsx undefined map error") returned 2 prior incidents: similar bug fixed in commit a3f9d by adding optional chaining; another similar issue in Council.tsx:612.',
|
|
23386
|
+
stackTraceAnalysis: "Line 847 is inside `renderMessages()`. The most likely cause: `messages` is undefined when `runCouncilPure()` returns early on the first iteration before any agent has produced output.",
|
|
23387
|
+
rootCause: "race condition: messages array is set after the first agent completes, but renderMessages() is called during the loading state when messages is still undefined.",
|
|
23388
|
+
proposedFix: `function renderMessages() {
|
|
23389
|
+
const messages = session.messages ?? [];
|
|
23390
|
+
return messages.map(m => <Message key={m.id} {...m} />);
|
|
23391
|
+
}`,
|
|
23392
|
+
verification: "Add a unit test that calls renderMessages() with messages=undefined and expects [] (not crash). Manual: trigger the bug scenario, confirm no crash."
|
|
22795
23393
|
}
|
|
22796
23394
|
}
|
|
22797
23395
|
],
|
|
@@ -24698,7 +25296,7 @@ function defaultBaseDir() {
|
|
|
24698
25296
|
}
|
|
24699
25297
|
|
|
24700
25298
|
// src/cli/hooks/useSession.ts
|
|
24701
|
-
import { useState, useRef, useCallback, useEffect } from "react";
|
|
25299
|
+
import { useState as useState3, useRef as useRef3, useCallback, useEffect as useEffect3 } from "react";
|
|
24702
25300
|
|
|
24703
25301
|
// src/cli/sessionManager.ts
|
|
24704
25302
|
import { promises as fs8, existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync, statSync } from "node:fs";
|
|
@@ -24978,20 +25576,20 @@ function completeTool(setFinalized, setLive, toolCallId, isError, durationMs, re
|
|
|
24978
25576
|
|
|
24979
25577
|
// src/cli/hooks/useSession.ts
|
|
24980
25578
|
function useSession() {
|
|
24981
|
-
const [sessionId, setSessionId] =
|
|
24982
|
-
const [messages, setMessages] =
|
|
24983
|
-
const [live, setLive] =
|
|
24984
|
-
const [sessionActive, setSessionActive] =
|
|
24985
|
-
const writerRef =
|
|
24986
|
-
const liveRef =
|
|
24987
|
-
|
|
25579
|
+
const [sessionId, setSessionId] = useState3("");
|
|
25580
|
+
const [messages, setMessages] = useState3([]);
|
|
25581
|
+
const [live, setLive] = useState3(EMPTY_LIVE);
|
|
25582
|
+
const [sessionActive, setSessionActive] = useState3(false);
|
|
25583
|
+
const writerRef = useRef3(null);
|
|
25584
|
+
const liveRef = useRef3(EMPTY_LIVE);
|
|
25585
|
+
useEffect3(() => {
|
|
24988
25586
|
liveRef.current = live;
|
|
24989
25587
|
}, [live]);
|
|
24990
25588
|
const resetTranscript = useCallback(() => {
|
|
24991
25589
|
setMessages([]);
|
|
24992
25590
|
setLive(EMPTY_LIVE);
|
|
24993
25591
|
}, []);
|
|
24994
|
-
|
|
25592
|
+
useEffect3(() => {
|
|
24995
25593
|
let cancelled = false;
|
|
24996
25594
|
(async () => {
|
|
24997
25595
|
await ensureSessionDir();
|
|
@@ -25085,7 +25683,7 @@ ${lines.join("\n")}`;
|
|
|
25085
25683
|
}
|
|
25086
25684
|
|
|
25087
25685
|
// src/cli/hooks/useChatTurn.ts
|
|
25088
|
-
import { useState as
|
|
25686
|
+
import { useState as useState4, useRef as useRef4, useCallback as useCallback2 } from "react";
|
|
25089
25687
|
|
|
25090
25688
|
// src/cli/metrics.ts
|
|
25091
25689
|
import { promises as fs9, existsSync as existsSync5, statSync as statSync2, renameSync, appendFileSync, mkdirSync as mkdirSync4 } from "node:fs";
|
|
@@ -25866,6 +26464,57 @@ function finalizeStreamingAssistant(setMessages) {
|
|
|
25866
26464
|
});
|
|
25867
26465
|
}
|
|
25868
26466
|
|
|
26467
|
+
// src/cli/modelPricing.ts
|
|
26468
|
+
var PRICES_PER_MILLION = {
|
|
26469
|
+
// xAI Grok
|
|
26470
|
+
"grok-4": { input: 3, output: 15 },
|
|
26471
|
+
"grok-4-fast": { input: 0.2, output: 0.5 },
|
|
26472
|
+
"grok-3": { input: 3, output: 15 },
|
|
26473
|
+
"grok-3-mini": { input: 0.3, output: 0.5 },
|
|
26474
|
+
"grok-2-vision": { input: 2, output: 10 },
|
|
26475
|
+
// GLM / Z.AI
|
|
26476
|
+
"glm-4.6": { input: 0.6, output: 2.2 },
|
|
26477
|
+
"glm-4.5": { input: 0.5, output: 2 },
|
|
26478
|
+
"glm-4.5-air": { input: 0.1, output: 0.6 },
|
|
26479
|
+
"glm-z1": { input: 0.5, output: 2 },
|
|
26480
|
+
// MiniMax
|
|
26481
|
+
"MiniMax-M2.5": { input: 0.2, output: 1.1 },
|
|
26482
|
+
"MiniMax-M2": { input: 0.2, output: 1.1 },
|
|
26483
|
+
"MiniMax-M2-her": { input: 0.3, output: 1.2 },
|
|
26484
|
+
// OpenAI (for openai-compatible fallback)
|
|
26485
|
+
"gpt-4o": { input: 2.5, output: 10 },
|
|
26486
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
26487
|
+
"gpt-4-turbo": { input: 10, output: 30 },
|
|
26488
|
+
"o1-preview": { input: 15, output: 60 },
|
|
26489
|
+
"o1-mini": { input: 3, output: 12 }
|
|
26490
|
+
};
|
|
26491
|
+
var DEFAULT_RATE = { input: 1, output: 3 };
|
|
26492
|
+
function envOverride(model) {
|
|
26493
|
+
const key = `ANATHEMA_PRICE_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
26494
|
+
const raw = process.env[key] ?? process.env.ANATHEMA_PRICE_DEFAULT;
|
|
26495
|
+
return parseRateOverride(raw);
|
|
26496
|
+
}
|
|
26497
|
+
function parseRateOverride(raw) {
|
|
26498
|
+
if (!raw) return null;
|
|
26499
|
+
const [inStr, outStr] = raw.split("/");
|
|
26500
|
+
const input = Number.parseFloat(inStr);
|
|
26501
|
+
const output = outStr !== void 0 ? Number.parseFloat(outStr) : input;
|
|
26502
|
+
if (!Number.isFinite(input) || !Number.isFinite(output)) return null;
|
|
26503
|
+
return { input, output };
|
|
26504
|
+
}
|
|
26505
|
+
function getModelRate(model) {
|
|
26506
|
+
if (!model) return DEFAULT_RATE;
|
|
26507
|
+
return envOverride(model) ?? PRICES_PER_MILLION[model] ?? DEFAULT_RATE;
|
|
26508
|
+
}
|
|
26509
|
+
function calculateCost(model, promptTokens, completionTokens) {
|
|
26510
|
+
if (!Number.isFinite(promptTokens) || promptTokens < 0) return 0;
|
|
26511
|
+
if (!Number.isFinite(completionTokens) || completionTokens < 0) return 0;
|
|
26512
|
+
const rate = getModelRate(model);
|
|
26513
|
+
const inputCost = promptTokens / 1e6 * rate.input;
|
|
26514
|
+
const outputCost = completionTokens / 1e6 * rate.output;
|
|
26515
|
+
return Number((inputCost + outputCost).toFixed(6));
|
|
26516
|
+
}
|
|
26517
|
+
|
|
25869
26518
|
// src/cli/hooks/chatStats.ts
|
|
25870
26519
|
function computeSessionStatsDelta(realUsage, userText, assistantContent, model, prev2) {
|
|
25871
26520
|
const promptTokens = realUsage ? realUsage.promptTokens : Math.ceil(userText.length / 4);
|
|
@@ -25891,8 +26540,8 @@ function useChatTurn(params) {
|
|
|
25891
26540
|
setLive,
|
|
25892
26541
|
liveRef
|
|
25893
26542
|
} = params;
|
|
25894
|
-
const harnessRef =
|
|
25895
|
-
const [queueCount, setQueueCount] =
|
|
26543
|
+
const harnessRef = useRef4(null);
|
|
26544
|
+
const [queueCount, setQueueCount] = useState4(0);
|
|
25896
26545
|
const useLiveModel = !!(setLive && liveRef);
|
|
25897
26546
|
const dispatchPrompt = useCallback2(
|
|
25898
26547
|
async (userText, opts) => {
|
|
@@ -25939,7 +26588,11 @@ function useChatTurn(params) {
|
|
|
25939
26588
|
let workspaceSummary = null;
|
|
25940
26589
|
let zelariReadHint = "";
|
|
25941
26590
|
try {
|
|
25942
|
-
const {
|
|
26591
|
+
const {
|
|
26592
|
+
buildPlanSummary: buildPlanSummary2,
|
|
26593
|
+
buildWorkspaceSummary: buildWorkspaceSummary2,
|
|
26594
|
+
buildZelariReadHint: buildZelariReadHint2
|
|
26595
|
+
} = await Promise.resolve().then(() => (init_workspaceSummary(), workspaceSummary_exports));
|
|
25943
26596
|
planSummary = buildPlanSummary2(cwd);
|
|
25944
26597
|
workspaceSummary = buildWorkspaceSummary2(cwd);
|
|
25945
26598
|
zelariReadHint = buildZelariReadHint2(cwd);
|
|
@@ -25963,7 +26616,9 @@ function useChatTurn(params) {
|
|
|
25963
26616
|
if (wantedWorkspaceTools.size > 0) {
|
|
25964
26617
|
const { createWorkspaceContext: createWorkspaceContext2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
|
|
25965
26618
|
const { createWorkspaceToolRegistry: createWorkspaceToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
|
|
25966
|
-
const wsRegistry = createWorkspaceToolRegistry2(
|
|
26619
|
+
const wsRegistry = createWorkspaceToolRegistry2(
|
|
26620
|
+
createWorkspaceContext2(cwd)
|
|
26621
|
+
);
|
|
25967
26622
|
for (const name of wantedWorkspaceTools) {
|
|
25968
26623
|
const td = wsRegistry.get(name);
|
|
25969
26624
|
if (td) toolRegistry.register(td);
|
|
@@ -26097,10 +26752,15 @@ function useChatTurn(params) {
|
|
|
26097
26752
|
...event.memberName ? { memberName: event.memberName } : {}
|
|
26098
26753
|
});
|
|
26099
26754
|
} else {
|
|
26100
|
-
appendOrExtendStreamingAssistant(
|
|
26101
|
-
|
|
26102
|
-
|
|
26103
|
-
|
|
26755
|
+
appendOrExtendStreamingAssistant(
|
|
26756
|
+
commitStreaming,
|
|
26757
|
+
streamContent,
|
|
26758
|
+
Date.now(),
|
|
26759
|
+
{
|
|
26760
|
+
...event.memberId ? { memberId: event.memberId } : {},
|
|
26761
|
+
...event.memberName ? { memberName: event.memberName } : {}
|
|
26762
|
+
}
|
|
26763
|
+
);
|
|
26104
26764
|
}
|
|
26105
26765
|
} else if (event.type === "error") {
|
|
26106
26766
|
flushStreaming();
|
|
@@ -26110,17 +26770,42 @@ function useChatTurn(params) {
|
|
|
26110
26770
|
flushStreaming();
|
|
26111
26771
|
if (useLiveModel) {
|
|
26112
26772
|
finalizeStreaming(setMessages, setLive);
|
|
26113
|
-
startTool(
|
|
26773
|
+
startTool(
|
|
26774
|
+
setLive,
|
|
26775
|
+
event.toolName,
|
|
26776
|
+
event.toolCallId,
|
|
26777
|
+
event.args,
|
|
26778
|
+
event.ts
|
|
26779
|
+
);
|
|
26114
26780
|
} else {
|
|
26115
26781
|
finalizeStreamingAssistant(setMessages);
|
|
26116
|
-
appendToolStart(
|
|
26782
|
+
appendToolStart(
|
|
26783
|
+
setMessages,
|
|
26784
|
+
event.toolName,
|
|
26785
|
+
event.toolCallId,
|
|
26786
|
+
event.args,
|
|
26787
|
+
event.ts
|
|
26788
|
+
);
|
|
26117
26789
|
}
|
|
26118
26790
|
streamContent = "";
|
|
26119
26791
|
} else if (event.type === "tool_execution_end") {
|
|
26120
26792
|
if (useLiveModel) {
|
|
26121
|
-
completeTool(
|
|
26793
|
+
completeTool(
|
|
26794
|
+
setMessages,
|
|
26795
|
+
setLive,
|
|
26796
|
+
event.toolCallId,
|
|
26797
|
+
event.isError,
|
|
26798
|
+
event.durationMs,
|
|
26799
|
+
event.result
|
|
26800
|
+
);
|
|
26122
26801
|
} else {
|
|
26123
|
-
updateToolMessageEnd(
|
|
26802
|
+
updateToolMessageEnd(
|
|
26803
|
+
setMessages,
|
|
26804
|
+
event.toolCallId,
|
|
26805
|
+
event.isError,
|
|
26806
|
+
event.durationMs,
|
|
26807
|
+
event.result
|
|
26808
|
+
);
|
|
26124
26809
|
}
|
|
26125
26810
|
}
|
|
26126
26811
|
}
|
|
@@ -26170,278 +26855,746 @@ function useChatTurn(params) {
|
|
|
26170
26855
|
sessionId,
|
|
26171
26856
|
writerRef,
|
|
26172
26857
|
setMessages,
|
|
26173
|
-
commitStreaming,
|
|
26174
|
-
flushStreaming,
|
|
26175
|
-
setBusy,
|
|
26176
|
-
setQueueCount,
|
|
26177
|
-
setLive,
|
|
26178
|
-
liveRef
|
|
26179
|
-
});
|
|
26180
|
-
},
|
|
26181
|
-
[
|
|
26182
|
-
sessionId,
|
|
26183
|
-
writerRef,
|
|
26184
|
-
setMessages,
|
|
26185
|
-
commitStreaming,
|
|
26186
|
-
flushStreaming,
|
|
26187
|
-
setBusy,
|
|
26188
|
-
setQueueCount,
|
|
26189
|
-
setLive,
|
|
26190
|
-
liveRef
|
|
26191
|
-
]
|
|
26192
|
-
);
|
|
26858
|
+
commitStreaming,
|
|
26859
|
+
flushStreaming,
|
|
26860
|
+
setBusy,
|
|
26861
|
+
setQueueCount,
|
|
26862
|
+
setLive,
|
|
26863
|
+
liveRef
|
|
26864
|
+
});
|
|
26865
|
+
},
|
|
26866
|
+
[
|
|
26867
|
+
sessionId,
|
|
26868
|
+
writerRef,
|
|
26869
|
+
setMessages,
|
|
26870
|
+
commitStreaming,
|
|
26871
|
+
flushStreaming,
|
|
26872
|
+
setBusy,
|
|
26873
|
+
setQueueCount,
|
|
26874
|
+
setLive,
|
|
26875
|
+
liveRef
|
|
26876
|
+
]
|
|
26877
|
+
);
|
|
26878
|
+
return {
|
|
26879
|
+
dispatchPrompt,
|
|
26880
|
+
dispatchCouncilPrompt,
|
|
26881
|
+
harnessRef,
|
|
26882
|
+
queueCount,
|
|
26883
|
+
setQueueCount
|
|
26884
|
+
};
|
|
26885
|
+
}
|
|
26886
|
+
async function dispatchCouncilPromptImpl(text, deps) {
|
|
26887
|
+
const {
|
|
26888
|
+
sessionId,
|
|
26889
|
+
writerRef,
|
|
26890
|
+
setMessages,
|
|
26891
|
+
commitStreaming,
|
|
26892
|
+
flushStreaming,
|
|
26893
|
+
setBusy,
|
|
26894
|
+
setLive,
|
|
26895
|
+
liveRef
|
|
26896
|
+
} = deps;
|
|
26897
|
+
const useLiveModel = !!(setLive && liveRef);
|
|
26898
|
+
const envConfig = await providerFromEnv();
|
|
26899
|
+
if (!envConfig) {
|
|
26900
|
+
const active = resolveActiveProvider();
|
|
26901
|
+
const spec = PROVIDERS.find((p3) => p3.id === active);
|
|
26902
|
+
appendSystem(
|
|
26903
|
+
setMessages,
|
|
26904
|
+
`No API key for the active provider "${active}". Set ${spec?.envVar ?? "the provider API key env var"} or run /login ${active} before invoking /council.`
|
|
26905
|
+
);
|
|
26906
|
+
return;
|
|
26907
|
+
}
|
|
26908
|
+
setBusy(true);
|
|
26909
|
+
const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
|
|
26910
|
+
const { createWorkspaceContext: createWorkspaceContext2, createWorkspaceStubs: createWorkspaceStubs2 } = await Promise.resolve().then(() => (init_stubs(), stubs_exports));
|
|
26911
|
+
const { createWorkspaceToolRegistry: createWorkspaceToolRegistry2 } = await Promise.resolve().then(() => (init_toolRegistry(), toolRegistry_exports));
|
|
26912
|
+
const { setWorkspaceStubs: setWorkspaceStubs2 } = await Promise.resolve().then(() => (init_skills2(), skills_exports));
|
|
26913
|
+
const { runPostCouncilHook: runPostCouncilHook2 } = await Promise.resolve().then(() => (init_postCouncilHook(), postCouncilHook_exports));
|
|
26914
|
+
const { buildWorkspaceSummary: buildWorkspaceSummary2, buildPlanSummary: buildPlanSummary2 } = await Promise.resolve().then(() => (init_workspaceSummary(), workspaceSummary_exports));
|
|
26915
|
+
const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
|
|
26916
|
+
const { registry: councilToolRegistry } = createBuiltinToolRegistry();
|
|
26917
|
+
const workspaceCtx = createWorkspaceContext2();
|
|
26918
|
+
const workspaceReg = createWorkspaceToolRegistry2(workspaceCtx);
|
|
26919
|
+
for (const name of workspaceReg.list()) {
|
|
26920
|
+
const td = workspaceReg.get(name);
|
|
26921
|
+
if (td) councilToolRegistry.register(td);
|
|
26922
|
+
}
|
|
26923
|
+
try {
|
|
26924
|
+
const { registerMcpTools: registerMcpTools2 } = await Promise.resolve().then(() => (init_mcpManager(), mcpManager_exports));
|
|
26925
|
+
const mcp = await registerMcpTools2(councilToolRegistry);
|
|
26926
|
+
for (const w of mcp.warnings) appendSystem(setMessages, w);
|
|
26927
|
+
} catch {
|
|
26928
|
+
}
|
|
26929
|
+
setWorkspaceStubs2(createWorkspaceStubs2(workspaceCtx));
|
|
26930
|
+
const councilFeedbackStore = new FeedbackStore2();
|
|
26931
|
+
let streamContent = "";
|
|
26932
|
+
let streamMemberId = null;
|
|
26933
|
+
const councilMaxToolCalls = (() => {
|
|
26934
|
+
const raw = process.env.ZELARI_MAX_TOOL_CALLS;
|
|
26935
|
+
const n = raw ? Number.parseInt(raw, 10) : 15;
|
|
26936
|
+
return Number.isFinite(n) && n > 0 ? n : 15;
|
|
26937
|
+
})();
|
|
26938
|
+
let membersCompleted = 0;
|
|
26939
|
+
let chairmanProducedOutput = false;
|
|
26940
|
+
let consecutiveProviderErrors = 0;
|
|
26941
|
+
let lastErrorMessage = "";
|
|
26942
|
+
let councilAborted = false;
|
|
26943
|
+
let councilRunMode = "implementation";
|
|
26944
|
+
const PROVIDER_ERROR_ABORT_THRESHOLD = 2;
|
|
26945
|
+
try {
|
|
26946
|
+
for await (const event of dispatchCouncil2(text, {
|
|
26947
|
+
apiKey: envConfig.apiKey,
|
|
26948
|
+
model: envConfig.model,
|
|
26949
|
+
provider: "openai-compatible",
|
|
26950
|
+
providerStream: openaiCompatibleProvider(envConfig),
|
|
26951
|
+
sessionId,
|
|
26952
|
+
tools: councilToolRegistry,
|
|
26953
|
+
feedbackStore: councilFeedbackStore,
|
|
26954
|
+
// v0.7.2 (B2): give the council the same project awareness the
|
|
26955
|
+
// single-prompt path has — cwd, tech stack, file layout, build scripts.
|
|
26956
|
+
// Without this, members had no idea which project they were operating
|
|
26957
|
+
// on and projected their identity onto the task.
|
|
26958
|
+
// v0.7.3: append the existing plan (if any) so a follow-up /council
|
|
26959
|
+
// continues it instead of re-planning from scratch.
|
|
26960
|
+
workspaceContext: [
|
|
26961
|
+
buildWorkspaceSummary2(process.cwd()),
|
|
26962
|
+
buildPlanSummary2(process.cwd())
|
|
26963
|
+
].filter(Boolean).join("\n\n"),
|
|
26964
|
+
maxToolCallsPerTurn: councilMaxToolCalls
|
|
26965
|
+
})) {
|
|
26966
|
+
if (councilAborted) {
|
|
26967
|
+
if (writerRef.current) await writerRef.current.append(event);
|
|
26968
|
+
continue;
|
|
26969
|
+
}
|
|
26970
|
+
if (writerRef.current) {
|
|
26971
|
+
await writerRef.current.append(event);
|
|
26972
|
+
}
|
|
26973
|
+
if (event.type === "council_mode") {
|
|
26974
|
+
councilRunMode = event.runMode;
|
|
26975
|
+
appendSystem(
|
|
26976
|
+
setMessages,
|
|
26977
|
+
`[council] ${event.tier} \xB7 ${event.runMode} \xB7 ${event.councilSize} members`,
|
|
26978
|
+
event.ts
|
|
26979
|
+
);
|
|
26980
|
+
} else if (event.type === "message_delta") {
|
|
26981
|
+
if (useLiveModel) {
|
|
26982
|
+
const memberId = event.memberId ?? null;
|
|
26983
|
+
if (memberId !== streamMemberId) {
|
|
26984
|
+
flushStreaming();
|
|
26985
|
+
finalizeStreaming(setMessages, setLive);
|
|
26986
|
+
streamContent = "";
|
|
26987
|
+
streamMemberId = memberId;
|
|
26988
|
+
}
|
|
26989
|
+
streamContent += event.delta;
|
|
26990
|
+
setStreaming(commitStreaming, streamContent, event.ts, {
|
|
26991
|
+
...event.memberId ? { memberId: event.memberId } : {},
|
|
26992
|
+
...event.memberName ? { memberName: event.memberName } : {}
|
|
26993
|
+
});
|
|
26994
|
+
} else {
|
|
26995
|
+
commitStreaming((prev2) => {
|
|
26996
|
+
const last = prev2[prev2.length - 1];
|
|
26997
|
+
if (last && last.role === "assistant" && last.id.startsWith("streaming-") && (last.memberId ?? null) === (event.memberId ?? null)) {
|
|
26998
|
+
return [
|
|
26999
|
+
...prev2.slice(0, -1),
|
|
27000
|
+
{ ...last, content: last.content + event.delta }
|
|
27001
|
+
];
|
|
27002
|
+
}
|
|
27003
|
+
return [
|
|
27004
|
+
...prev2,
|
|
27005
|
+
{
|
|
27006
|
+
id: `streaming-${crypto.randomUUID()}`,
|
|
27007
|
+
role: "assistant",
|
|
27008
|
+
content: event.delta,
|
|
27009
|
+
ts: event.ts,
|
|
27010
|
+
...event.memberId ? { memberId: event.memberId } : {},
|
|
27011
|
+
...event.memberName ? { memberName: event.memberName } : {}
|
|
27012
|
+
}
|
|
27013
|
+
];
|
|
27014
|
+
});
|
|
27015
|
+
}
|
|
27016
|
+
} else if (event.type === "message_end") {
|
|
27017
|
+
flushStreaming();
|
|
27018
|
+
if (useLiveModel) finalizeStreaming(setMessages, setLive);
|
|
27019
|
+
else finalizeStreamingAssistant(setMessages);
|
|
27020
|
+
streamContent = "";
|
|
27021
|
+
streamMemberId = null;
|
|
27022
|
+
membersCompleted++;
|
|
27023
|
+
if (event.memberId === "lucifer" || event.memberName === "Lucifero") {
|
|
27024
|
+
chairmanProducedOutput = true;
|
|
27025
|
+
}
|
|
27026
|
+
} else if (event.type === "tool_execution_start") {
|
|
27027
|
+
flushStreaming();
|
|
27028
|
+
if (useLiveModel) {
|
|
27029
|
+
finalizeStreaming(setMessages, setLive);
|
|
27030
|
+
startTool(
|
|
27031
|
+
setLive,
|
|
27032
|
+
event.toolName,
|
|
27033
|
+
event.toolCallId,
|
|
27034
|
+
event.args,
|
|
27035
|
+
event.ts
|
|
27036
|
+
);
|
|
27037
|
+
} else {
|
|
27038
|
+
finalizeStreamingAssistant(setMessages);
|
|
27039
|
+
appendToolStart(
|
|
27040
|
+
setMessages,
|
|
27041
|
+
event.toolName,
|
|
27042
|
+
event.toolCallId,
|
|
27043
|
+
event.args,
|
|
27044
|
+
event.ts
|
|
27045
|
+
);
|
|
27046
|
+
}
|
|
27047
|
+
streamContent = "";
|
|
27048
|
+
} else if (event.type === "tool_execution_end") {
|
|
27049
|
+
if (useLiveModel) {
|
|
27050
|
+
completeTool(
|
|
27051
|
+
setMessages,
|
|
27052
|
+
setLive,
|
|
27053
|
+
event.toolCallId,
|
|
27054
|
+
event.isError,
|
|
27055
|
+
event.durationMs,
|
|
27056
|
+
event.result
|
|
27057
|
+
);
|
|
27058
|
+
} else {
|
|
27059
|
+
updateToolMessageEnd(
|
|
27060
|
+
setMessages,
|
|
27061
|
+
event.toolCallId,
|
|
27062
|
+
event.isError,
|
|
27063
|
+
event.durationMs,
|
|
27064
|
+
event.result
|
|
27065
|
+
);
|
|
27066
|
+
}
|
|
27067
|
+
} else if (event.type === "error") {
|
|
27068
|
+
flushStreaming();
|
|
27069
|
+
const memberTag = event.memberName ? ` \xB7 ${event.memberName}` : "";
|
|
27070
|
+
appendSystem(
|
|
27071
|
+
setMessages,
|
|
27072
|
+
`[error${memberTag}] ${event.message}`,
|
|
27073
|
+
event.ts
|
|
27074
|
+
);
|
|
27075
|
+
if (event.message === lastErrorMessage) {
|
|
27076
|
+
consecutiveProviderErrors++;
|
|
27077
|
+
} else {
|
|
27078
|
+
consecutiveProviderErrors = 1;
|
|
27079
|
+
lastErrorMessage = event.message;
|
|
27080
|
+
}
|
|
27081
|
+
if (consecutiveProviderErrors >= PROVIDER_ERROR_ABORT_THRESHOLD) {
|
|
27082
|
+
councilAborted = true;
|
|
27083
|
+
appendSystem(
|
|
27084
|
+
setMessages,
|
|
27085
|
+
`[council aborted: repeated provider error \u2014 ${consecutiveProviderErrors}\xD7 "${event.message.slice(0, 80)}"]`,
|
|
27086
|
+
Date.now()
|
|
27087
|
+
);
|
|
27088
|
+
}
|
|
27089
|
+
}
|
|
27090
|
+
}
|
|
27091
|
+
} catch (err) {
|
|
27092
|
+
flushStreaming();
|
|
27093
|
+
appendSystem(
|
|
27094
|
+
setMessages,
|
|
27095
|
+
`[council error] ${err instanceof Error ? err.message : String(err)}`,
|
|
27096
|
+
Date.now()
|
|
27097
|
+
);
|
|
27098
|
+
} finally {
|
|
27099
|
+
flushStreaming();
|
|
27100
|
+
if (useLiveModel) finalizeStreaming(setMessages, setLive);
|
|
27101
|
+
else finalizeStreamingAssistant(setMessages);
|
|
27102
|
+
const hookShouldRun = membersCompleted > 0 || chairmanProducedOutput;
|
|
27103
|
+
if (hookShouldRun) {
|
|
27104
|
+
try {
|
|
27105
|
+
const hook = await runPostCouncilHook2(workspaceCtx, {
|
|
27106
|
+
runMode: councilRunMode
|
|
27107
|
+
});
|
|
27108
|
+
if (hook.ran && hook.changed) {
|
|
27109
|
+
appendSystem(
|
|
27110
|
+
setMessages,
|
|
27111
|
+
`[agents.md] updated: ${hook.sections.length} section(s) changed (${hook.sections.join(", ")})`,
|
|
27112
|
+
Date.now()
|
|
27113
|
+
);
|
|
27114
|
+
} else if (hook.ran && hook.reason) {
|
|
27115
|
+
if (!hook.reason.includes("disabled")) {
|
|
27116
|
+
appendSystem(setMessages, `[agents.md] ${hook.reason}`, Date.now());
|
|
27117
|
+
}
|
|
27118
|
+
}
|
|
27119
|
+
} catch {
|
|
27120
|
+
}
|
|
27121
|
+
} else if (!councilAborted) {
|
|
27122
|
+
appendSystem(
|
|
27123
|
+
setMessages,
|
|
27124
|
+
"[agents.md] skipped \u2014 council produced no output",
|
|
27125
|
+
Date.now()
|
|
27126
|
+
);
|
|
27127
|
+
}
|
|
27128
|
+
setBusy(false);
|
|
27129
|
+
}
|
|
27130
|
+
}
|
|
27131
|
+
|
|
27132
|
+
// src/cli/hooks/useSlashDispatch.ts
|
|
27133
|
+
import { useCallback as useCallback3 } from "react";
|
|
27134
|
+
|
|
27135
|
+
// src/cli/slashCommands.ts
|
|
27136
|
+
function parseSlashCommand(text) {
|
|
27137
|
+
const trimmed = text.trim();
|
|
27138
|
+
if (!trimmed.startsWith("/")) return null;
|
|
27139
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
27140
|
+
const command = parts[0];
|
|
27141
|
+
const args = parts.slice(1);
|
|
27142
|
+
return { command, args };
|
|
27143
|
+
}
|
|
27144
|
+
function expandSkillTemplate(skill, options = {}) {
|
|
27145
|
+
const userInput = options.input?.trim() ?? "";
|
|
27146
|
+
const prompt = userInput ? `${skill.systemPromptFragment}
|
|
27147
|
+
|
|
27148
|
+
## User input
|
|
27149
|
+
${userInput}` : `${skill.systemPromptFragment}
|
|
27150
|
+
|
|
27151
|
+
## User input
|
|
27152
|
+
(Please provide the task description or input for this skill.)`;
|
|
26193
27153
|
return {
|
|
26194
|
-
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
|
|
26198
|
-
|
|
27154
|
+
skillId: skill.id,
|
|
27155
|
+
prompt,
|
|
27156
|
+
requiredRoles: skill.requiredRoles,
|
|
27157
|
+
requiredTools: skill.requiredTools,
|
|
27158
|
+
estimatedCost: skill.estimatedCost
|
|
26199
27159
|
};
|
|
26200
27160
|
}
|
|
26201
|
-
|
|
26202
|
-
|
|
26203
|
-
|
|
26204
|
-
|
|
26205
|
-
|
|
26206
|
-
|
|
26207
|
-
|
|
26208
|
-
setBusy,
|
|
26209
|
-
setLive,
|
|
26210
|
-
liveRef
|
|
26211
|
-
} = deps;
|
|
26212
|
-
const useLiveModel = !!(setLive && liveRef);
|
|
26213
|
-
const envConfig = await providerFromEnv();
|
|
26214
|
-
if (!envConfig) {
|
|
26215
|
-
const active = resolveActiveProvider();
|
|
26216
|
-
const spec = PROVIDERS.find((p3) => p3.id === active);
|
|
26217
|
-
appendSystem(
|
|
26218
|
-
setMessages,
|
|
26219
|
-
`No API key for the active provider "${active}". Set ${spec?.envVar ?? "the provider API key env var"} or run /login ${active} before invoking /council.`
|
|
26220
|
-
);
|
|
26221
|
-
return;
|
|
27161
|
+
function formatSkillList(skills) {
|
|
27162
|
+
if (skills.length === 0) return "(no skills available)";
|
|
27163
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
27164
|
+
for (const skill of skills) {
|
|
27165
|
+
const list = byCategory.get(skill.category) ?? [];
|
|
27166
|
+
list.push(skill);
|
|
27167
|
+
byCategory.set(skill.category, list);
|
|
26222
27168
|
}
|
|
26223
|
-
|
|
26224
|
-
const
|
|
26225
|
-
|
|
26226
|
-
|
|
26227
|
-
|
|
26228
|
-
|
|
26229
|
-
|
|
26230
|
-
|
|
26231
|
-
const { registry: councilToolRegistry } = createBuiltinToolRegistry();
|
|
26232
|
-
const workspaceCtx = createWorkspaceContext2();
|
|
26233
|
-
const workspaceReg = createWorkspaceToolRegistry2(workspaceCtx);
|
|
26234
|
-
for (const name of workspaceReg.list()) {
|
|
26235
|
-
const td = workspaceReg.get(name);
|
|
26236
|
-
if (td) councilToolRegistry.register(td);
|
|
27169
|
+
const lines = ["Available skills:"];
|
|
27170
|
+
for (const [category, list] of [...byCategory.entries()].sort()) {
|
|
27171
|
+
lines.push(`
|
|
27172
|
+
[${category}]`);
|
|
27173
|
+
for (const skill of list) {
|
|
27174
|
+
const costLabel = skill.estimatedCost;
|
|
27175
|
+
lines.push(` /skill ${skill.id} \u2014 ${skill.name} (${costLabel} cost)`);
|
|
27176
|
+
}
|
|
26237
27177
|
}
|
|
26238
|
-
|
|
26239
|
-
|
|
26240
|
-
|
|
26241
|
-
|
|
26242
|
-
|
|
27178
|
+
return lines.join("\n");
|
|
27179
|
+
}
|
|
27180
|
+
function handleSlashCommand(text, availableSkills) {
|
|
27181
|
+
const parsed = parseSlashCommand(text);
|
|
27182
|
+
if (!parsed) {
|
|
27183
|
+
return { handled: false, kind: "unknown" };
|
|
26243
27184
|
}
|
|
26244
|
-
|
|
26245
|
-
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
|
|
26250
|
-
|
|
26251
|
-
|
|
26252
|
-
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
|
|
26256
|
-
|
|
26257
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
26260
|
-
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
|
|
26267
|
-
|
|
26268
|
-
|
|
26269
|
-
|
|
26270
|
-
|
|
26271
|
-
|
|
26272
|
-
|
|
26273
|
-
|
|
26274
|
-
|
|
26275
|
-
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
|
|
26279
|
-
|
|
26280
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
27185
|
+
const { command, args } = parsed;
|
|
27186
|
+
switch (command) {
|
|
27187
|
+
case "help":
|
|
27188
|
+
return {
|
|
27189
|
+
handled: true,
|
|
27190
|
+
kind: "help",
|
|
27191
|
+
message: `Available commands:
|
|
27192
|
+
/login <provider> \u2014 authenticate with provider (grok, minimax, glm, custom)
|
|
27193
|
+
/model <name> \u2014 switch the active model
|
|
27194
|
+
/model refresh \u2014 re-discover models for the active provider (v3-U)
|
|
27195
|
+
/models \u2014 list discovered models for the active provider (v3-U)
|
|
27196
|
+
/models refresh \u2014 re-discover models for the active provider (v3-U)
|
|
27197
|
+
/provider <name> \u2014 switch the active provider
|
|
27198
|
+
/provider custom <baseUrl> \u2014 point the active provider at a self-hosted endpoint (Ollama, LM Studio, vLLM, ...)
|
|
27199
|
+
/provider custom clear \u2014 clear the custom endpoint override
|
|
27200
|
+
/skill <name> [input] \u2014 invoke a skill (autocomplete with /skill <TAB>)
|
|
27201
|
+
/skill-stats [name] \u2014 show invocation stats (success rate, avg duration, total tokens)
|
|
27202
|
+
/council <input> \u2014 invoke the multi-agent council on input
|
|
27203
|
+
/council-feedback <memberId> <1-5> [note] \u2014 rate a council member for future ranking (Task I.2)
|
|
27204
|
+
/promote-member <memberId> \u2014 promote a council member to a standalone skill (v3-K)
|
|
27205
|
+
/update [--yes|-y] \u2014 check for zelari-code updates; --yes performs the update (v3-N)
|
|
27206
|
+
/steer <text> \u2014 enqueue a follow-up prompt on the active run (Task 18.2)
|
|
27207
|
+
/steer --interrupt <text> \u2014 cancel current run + enqueue <text> for next dispatch (Task C.3.2)
|
|
27208
|
+
/compact \u2014 compact the session transcript
|
|
27209
|
+
/clear \u2014 clear the visible transcript (session is preserved)
|
|
27210
|
+
/sessions \u2014 list past sessions
|
|
27211
|
+
/resume <id> \u2014 load a past session
|
|
27212
|
+
/branch <name> \u2014 snapshot the current session into a new branch
|
|
27213
|
+
/branches \u2014 list branches
|
|
27214
|
+
/checkout <name> \u2014 switch the active branch
|
|
27215
|
+
/new \u2014 start a fresh session
|
|
27216
|
+
/diff [--staged] \u2014 show uncommitted changes (or staged with --staged)
|
|
27217
|
+
/undo [--yes] \u2014 revert working-tree changes (destructive! requires --yes)
|
|
27218
|
+
/help \u2014 show this help
|
|
27219
|
+
/exit \u2014 exit the CLI
|
|
27220
|
+
|
|
27221
|
+
${formatSkillList(availableSkills)}`
|
|
27222
|
+
};
|
|
27223
|
+
case "exit":
|
|
27224
|
+
return { handled: true, kind: "exit", message: "Goodbye." };
|
|
27225
|
+
case "clear":
|
|
27226
|
+
return { handled: true, kind: "clear", message: "Transcript cleared." };
|
|
27227
|
+
case "login": {
|
|
27228
|
+
const provider = args[0];
|
|
27229
|
+
if (!provider) {
|
|
27230
|
+
return { handled: true, kind: "login", message: "Usage: /login <provider> [key] (or /login <provider> for OAuth if supported)" };
|
|
26283
27231
|
}
|
|
26284
|
-
|
|
26285
|
-
|
|
27232
|
+
const key = args.slice(1).join(" ").trim();
|
|
27233
|
+
if (!key) {
|
|
27234
|
+
if (provider === "grok") {
|
|
27235
|
+
return { handled: true, kind: "login_oauth", provider };
|
|
27236
|
+
}
|
|
27237
|
+
return {
|
|
27238
|
+
handled: true,
|
|
27239
|
+
kind: "login",
|
|
27240
|
+
provider,
|
|
27241
|
+
message: `[login] no key supplied \u2014 set ${provider.toUpperCase()}_API_KEY env or pass the key: /login ${provider} <key>`
|
|
27242
|
+
};
|
|
27243
|
+
}
|
|
27244
|
+
return { handled: true, kind: "login", provider, loginKey: key };
|
|
27245
|
+
}
|
|
27246
|
+
case "model": {
|
|
27247
|
+
const model = args[0];
|
|
27248
|
+
if (!model) {
|
|
27249
|
+
return {
|
|
27250
|
+
handled: true,
|
|
27251
|
+
kind: "model_show",
|
|
27252
|
+
message: "Usage: /model <name> to change, /model to show current"
|
|
27253
|
+
};
|
|
27254
|
+
}
|
|
27255
|
+
if (model === "refresh") {
|
|
27256
|
+
return { handled: true, kind: "model_refresh" };
|
|
27257
|
+
}
|
|
27258
|
+
return { handled: true, kind: "model_set", model };
|
|
27259
|
+
}
|
|
27260
|
+
case "models": {
|
|
27261
|
+
const sub = args[0];
|
|
27262
|
+
if (sub === "refresh") {
|
|
27263
|
+
return { handled: true, kind: "models_refresh" };
|
|
27264
|
+
}
|
|
27265
|
+
return { handled: true, kind: "models_list" };
|
|
27266
|
+
}
|
|
27267
|
+
case "provider": {
|
|
27268
|
+
const subcommand = args[0];
|
|
27269
|
+
if (!subcommand) {
|
|
27270
|
+
return {
|
|
27271
|
+
handled: true,
|
|
27272
|
+
kind: "provider_list",
|
|
27273
|
+
message: "Usage: /provider <name> \u2014 switch active provider\n /provider custom <baseUrl> \u2014 set custom base URL (Ollama, LM Studio, vLLM, ...)\n /provider custom clear \u2014 clear the custom override\n /provider <name> refresh \u2014 force token refresh (v3-F)\n /provider <name> status \u2014 show key source, expiry, refresh impl (v3-F)\nAvailable: openai-compatible, minimax, glm, grok, custom"
|
|
27274
|
+
};
|
|
26286
27275
|
}
|
|
26287
|
-
if (
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
}
|
|
26296
|
-
streamContent += event.delta;
|
|
26297
|
-
setStreaming(commitStreaming, streamContent, event.ts, {
|
|
26298
|
-
...event.memberId ? { memberId: event.memberId } : {},
|
|
26299
|
-
...event.memberName ? { memberName: event.memberName } : {}
|
|
26300
|
-
});
|
|
26301
|
-
} else {
|
|
26302
|
-
commitStreaming((prev2) => {
|
|
26303
|
-
const last = prev2[prev2.length - 1];
|
|
26304
|
-
if (last && last.role === "assistant" && last.id.startsWith("streaming-") && (last.memberId ?? null) === (event.memberId ?? null)) {
|
|
26305
|
-
return [
|
|
26306
|
-
...prev2.slice(0, -1),
|
|
26307
|
-
{ ...last, content: last.content + event.delta }
|
|
26308
|
-
];
|
|
26309
|
-
}
|
|
26310
|
-
return [
|
|
26311
|
-
...prev2,
|
|
26312
|
-
{
|
|
26313
|
-
id: `streaming-${crypto.randomUUID()}`,
|
|
26314
|
-
role: "assistant",
|
|
26315
|
-
content: event.delta,
|
|
26316
|
-
ts: event.ts,
|
|
26317
|
-
...event.memberId ? { memberId: event.memberId } : {},
|
|
26318
|
-
...event.memberName ? { memberName: event.memberName } : {}
|
|
26319
|
-
}
|
|
26320
|
-
];
|
|
26321
|
-
});
|
|
26322
|
-
}
|
|
26323
|
-
} else if (event.type === "message_end") {
|
|
26324
|
-
flushStreaming();
|
|
26325
|
-
if (useLiveModel) finalizeStreaming(setMessages, setLive);
|
|
26326
|
-
else finalizeStreamingAssistant(setMessages);
|
|
26327
|
-
streamContent = "";
|
|
26328
|
-
streamMemberId = null;
|
|
26329
|
-
membersCompleted++;
|
|
26330
|
-
if (event.memberId === "lucifer" || event.memberName === "Lucifero") {
|
|
26331
|
-
chairmanProducedOutput = true;
|
|
27276
|
+
if (subcommand === "custom") {
|
|
27277
|
+
const target = args[1];
|
|
27278
|
+
if (!target || target === "show") {
|
|
27279
|
+
return {
|
|
27280
|
+
handled: true,
|
|
27281
|
+
kind: "provider_custom",
|
|
27282
|
+
message: "Usage: /provider custom <baseUrl> \u2014 set custom base URL for the active provider\n /provider custom clear \u2014 clear the custom override for the active provider"
|
|
27283
|
+
};
|
|
26332
27284
|
}
|
|
26333
|
-
|
|
26334
|
-
|
|
26335
|
-
|
|
26336
|
-
|
|
26337
|
-
|
|
26338
|
-
|
|
26339
|
-
event.toolName,
|
|
26340
|
-
event.toolCallId,
|
|
26341
|
-
event.args,
|
|
26342
|
-
event.ts
|
|
26343
|
-
);
|
|
26344
|
-
} else {
|
|
26345
|
-
finalizeStreamingAssistant(setMessages);
|
|
26346
|
-
appendToolStart(
|
|
26347
|
-
setMessages,
|
|
26348
|
-
event.toolName,
|
|
26349
|
-
event.toolCallId,
|
|
26350
|
-
event.args,
|
|
26351
|
-
event.ts
|
|
26352
|
-
);
|
|
27285
|
+
if (target === "clear") {
|
|
27286
|
+
return {
|
|
27287
|
+
handled: true,
|
|
27288
|
+
kind: "provider_custom",
|
|
27289
|
+
customClear: true
|
|
27290
|
+
};
|
|
26353
27291
|
}
|
|
26354
|
-
|
|
26355
|
-
|
|
26356
|
-
|
|
26357
|
-
|
|
26358
|
-
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
26362
|
-
|
|
26363
|
-
|
|
26364
|
-
|
|
26365
|
-
|
|
26366
|
-
|
|
26367
|
-
|
|
26368
|
-
|
|
26369
|
-
|
|
26370
|
-
|
|
26371
|
-
|
|
26372
|
-
|
|
27292
|
+
const url2 = args.slice(1).join(" ").trim();
|
|
27293
|
+
return {
|
|
27294
|
+
handled: true,
|
|
27295
|
+
kind: "provider_custom",
|
|
27296
|
+
customEndpoint: url2
|
|
27297
|
+
};
|
|
27298
|
+
}
|
|
27299
|
+
const providerId = subcommand;
|
|
27300
|
+
const sub = args[1];
|
|
27301
|
+
if (sub === "refresh") {
|
|
27302
|
+
return { handled: true, kind: "provider_refresh", provider: providerId };
|
|
27303
|
+
}
|
|
27304
|
+
if (sub === "status") {
|
|
27305
|
+
return { handled: true, kind: "provider_status", provider: providerId };
|
|
27306
|
+
}
|
|
27307
|
+
return { handled: true, kind: "provider_set", provider: subcommand };
|
|
27308
|
+
}
|
|
27309
|
+
case "branch": {
|
|
27310
|
+
const name = args[0];
|
|
27311
|
+
if (!name) {
|
|
27312
|
+
return {
|
|
27313
|
+
handled: true,
|
|
27314
|
+
kind: "branch_create",
|
|
27315
|
+
message: "Usage: /branch <name> \u2014 snapshots the current session into a new branch"
|
|
27316
|
+
};
|
|
27317
|
+
}
|
|
27318
|
+
return { handled: true, kind: "branch_create", branchName: name };
|
|
27319
|
+
}
|
|
27320
|
+
case "branches":
|
|
27321
|
+
return { handled: true, kind: "branch_list" };
|
|
27322
|
+
case "checkout": {
|
|
27323
|
+
const name = args[0];
|
|
27324
|
+
if (!name) {
|
|
27325
|
+
return {
|
|
27326
|
+
handled: true,
|
|
27327
|
+
kind: "branch_checkout",
|
|
27328
|
+
message: "Usage: /checkout <name> \u2014 switch the active branch"
|
|
27329
|
+
};
|
|
27330
|
+
}
|
|
27331
|
+
return { handled: true, kind: "branch_checkout", branchName: name };
|
|
27332
|
+
}
|
|
27333
|
+
case "compact": {
|
|
27334
|
+
let compactThreshold;
|
|
27335
|
+
let compactKeepRecent;
|
|
27336
|
+
for (let i = 0; i < args.length; i++) {
|
|
27337
|
+
const a = args[i];
|
|
27338
|
+
if (a === "--threshold" || a === "-t") {
|
|
27339
|
+
const v = Number.parseInt(args[++i] ?? "", 10);
|
|
27340
|
+
if (Number.isFinite(v) && v > 0) compactThreshold = v;
|
|
27341
|
+
} else if (a === "--keep" || a === "-k") {
|
|
27342
|
+
const v = Number.parseInt(args[++i] ?? "", 10);
|
|
27343
|
+
if (Number.isFinite(v) && v > 0) compactKeepRecent = v;
|
|
26373
27344
|
}
|
|
26374
|
-
}
|
|
26375
|
-
|
|
26376
|
-
|
|
26377
|
-
|
|
26378
|
-
|
|
26379
|
-
|
|
26380
|
-
|
|
26381
|
-
|
|
26382
|
-
|
|
26383
|
-
|
|
27345
|
+
}
|
|
27346
|
+
return {
|
|
27347
|
+
handled: true,
|
|
27348
|
+
kind: "compact",
|
|
27349
|
+
message: "Compacting session...",
|
|
27350
|
+
compactThreshold,
|
|
27351
|
+
compactKeepRecent
|
|
27352
|
+
};
|
|
27353
|
+
}
|
|
27354
|
+
case "sessions":
|
|
27355
|
+
return { handled: true, kind: "session" };
|
|
27356
|
+
case "resume": {
|
|
27357
|
+
const targetId = args[0];
|
|
27358
|
+
if (!targetId) {
|
|
27359
|
+
return { handled: true, kind: "resume", message: "Usage: /resume <id>" };
|
|
27360
|
+
}
|
|
27361
|
+
return {
|
|
27362
|
+
handled: true,
|
|
27363
|
+
kind: "resume",
|
|
27364
|
+
targetSessionId: targetId,
|
|
27365
|
+
message: `[resume] session ${targetId} ready \u2014 restart zelari-code to load`
|
|
27366
|
+
};
|
|
27367
|
+
}
|
|
27368
|
+
case "new":
|
|
27369
|
+
return {
|
|
27370
|
+
handled: true,
|
|
27371
|
+
kind: "new",
|
|
27372
|
+
message: "[new] session reset \u2014 next prompt starts fresh"
|
|
27373
|
+
};
|
|
27374
|
+
case "council": {
|
|
27375
|
+
const input = args.join(" ").trim();
|
|
27376
|
+
if (!input) {
|
|
27377
|
+
return {
|
|
27378
|
+
handled: true,
|
|
27379
|
+
kind: "council",
|
|
27380
|
+
message: "Usage: /council <input> \u2014 invokes the multi-agent council on the input"
|
|
27381
|
+
};
|
|
27382
|
+
}
|
|
27383
|
+
return { handled: true, kind: "council", councilInput: input };
|
|
27384
|
+
}
|
|
27385
|
+
case "council-feedback": {
|
|
27386
|
+
const memberId = args[0];
|
|
27387
|
+
const scoreStr = args[1];
|
|
27388
|
+
if (!memberId || !scoreStr) {
|
|
27389
|
+
return {
|
|
27390
|
+
handled: true,
|
|
27391
|
+
kind: "council_feedback",
|
|
27392
|
+
message: "Usage: /council-feedback <memberId> <1-5> [note...] \u2014 rate a council member (e.g. /council-feedback sisyphus 4 great framing)"
|
|
27393
|
+
};
|
|
27394
|
+
}
|
|
27395
|
+
const score = Number.parseInt(scoreStr, 10);
|
|
27396
|
+
if (!Number.isInteger(score) || score < 1 || score > 5) {
|
|
27397
|
+
return {
|
|
27398
|
+
handled: true,
|
|
27399
|
+
kind: "council_feedback",
|
|
27400
|
+
message: `Invalid score "${scoreStr}" \u2014 must be an integer in [1, 5].`
|
|
27401
|
+
};
|
|
27402
|
+
}
|
|
27403
|
+
const note = args.slice(2).join(" ").trim() || void 0;
|
|
27404
|
+
return {
|
|
27405
|
+
handled: true,
|
|
27406
|
+
kind: "council_feedback",
|
|
27407
|
+
feedbackMemberId: memberId,
|
|
27408
|
+
feedbackScore: score,
|
|
27409
|
+
...note ? { feedbackNote: note } : {}
|
|
27410
|
+
};
|
|
27411
|
+
}
|
|
27412
|
+
case "skill": {
|
|
27413
|
+
const skillId = args[0];
|
|
27414
|
+
if (!skillId) {
|
|
27415
|
+
return {
|
|
27416
|
+
handled: true,
|
|
27417
|
+
kind: "skill",
|
|
27418
|
+
skillError: "Usage: /skill <name> [input]",
|
|
27419
|
+
message: formatSkillList(availableSkills)
|
|
27420
|
+
};
|
|
27421
|
+
}
|
|
27422
|
+
const skill = availableSkills.find((s) => s.id === skillId);
|
|
27423
|
+
if (!skill) {
|
|
27424
|
+
return {
|
|
27425
|
+
handled: true,
|
|
27426
|
+
kind: "skill",
|
|
27427
|
+
skillError: `Unknown skill: "${skillId}". Use /skill <TAB> for autocomplete.`,
|
|
27428
|
+
message: formatSkillList(availableSkills)
|
|
27429
|
+
};
|
|
27430
|
+
}
|
|
27431
|
+
const input = args.slice(1).join(" ");
|
|
27432
|
+
const expandedSkill = expandSkillTemplate(skill, { input });
|
|
27433
|
+
return { handled: true, kind: "skill", expandedSkill };
|
|
27434
|
+
}
|
|
27435
|
+
case "skill_stats": {
|
|
27436
|
+
const skillId = args[0];
|
|
27437
|
+
return {
|
|
27438
|
+
handled: true,
|
|
27439
|
+
kind: "skill_stats",
|
|
27440
|
+
skillStatsSkillId: skillId,
|
|
27441
|
+
message: skillId ? `Computing stats for skill "${skillId}"\u2026` : "Computing stats for all skills\u2026"
|
|
27442
|
+
};
|
|
27443
|
+
}
|
|
27444
|
+
case "skill-compare": {
|
|
27445
|
+
const id1 = args[0];
|
|
27446
|
+
const id2 = args[1];
|
|
27447
|
+
if (!id1 || !id2) {
|
|
27448
|
+
return {
|
|
27449
|
+
handled: true,
|
|
27450
|
+
kind: "skill-compare",
|
|
27451
|
+
message: "\u26A0 /skill-compare requires exactly 2 skill IDs (e.g. `/skill-compare debug refactor`)."
|
|
27452
|
+
};
|
|
27453
|
+
}
|
|
27454
|
+
return {
|
|
27455
|
+
handled: true,
|
|
27456
|
+
kind: "skill-compare",
|
|
27457
|
+
compareIds: [id1, id2],
|
|
27458
|
+
message: `Comparing skills "${id1}" vs "${id2}"\u2026`
|
|
27459
|
+
};
|
|
27460
|
+
}
|
|
27461
|
+
case "steer": {
|
|
27462
|
+
let interrupt = false;
|
|
27463
|
+
const filtered = [];
|
|
27464
|
+
for (const a of args) {
|
|
27465
|
+
if (a === "--interrupt" || a === "-i") {
|
|
27466
|
+
interrupt = true;
|
|
26384
27467
|
} else {
|
|
26385
|
-
|
|
26386
|
-
lastErrorMessage = event.message;
|
|
27468
|
+
filtered.push(a);
|
|
26387
27469
|
}
|
|
26388
|
-
|
|
26389
|
-
|
|
26390
|
-
|
|
26391
|
-
|
|
26392
|
-
|
|
26393
|
-
|
|
26394
|
-
)
|
|
27470
|
+
}
|
|
27471
|
+
const text2 = filtered.join(" ").trim();
|
|
27472
|
+
if (!text2) {
|
|
27473
|
+
return {
|
|
27474
|
+
handled: true,
|
|
27475
|
+
kind: interrupt ? "steer_interrupt" : "steer",
|
|
27476
|
+
message: "Usage: /steer [--interrupt|-i] <text> \u2014 enqueue (and optionally cancel) a follow-up prompt"
|
|
27477
|
+
};
|
|
27478
|
+
}
|
|
27479
|
+
return {
|
|
27480
|
+
handled: true,
|
|
27481
|
+
kind: interrupt ? "steer_interrupt" : "steer",
|
|
27482
|
+
steerText: text2
|
|
27483
|
+
};
|
|
27484
|
+
}
|
|
27485
|
+
case "diff": {
|
|
27486
|
+
const staged = args.includes("--staged") || args.includes("--cached");
|
|
27487
|
+
return { handled: true, kind: "diff", diffStaged: staged ? true : void 0 };
|
|
27488
|
+
}
|
|
27489
|
+
case "promote-member": {
|
|
27490
|
+
const memberId = args[0];
|
|
27491
|
+
if (!memberId) {
|
|
27492
|
+
return {
|
|
27493
|
+
handled: true,
|
|
27494
|
+
kind: "promote_member_error",
|
|
27495
|
+
promoteMemberError: "Usage: /promote-member <memberId> \u2014 e.g. /promote-member geryon"
|
|
27496
|
+
};
|
|
27497
|
+
}
|
|
27498
|
+
return { handled: true, kind: "promote_member", promoteMemberId: memberId };
|
|
27499
|
+
}
|
|
27500
|
+
case "update": {
|
|
27501
|
+
let force = false;
|
|
27502
|
+
for (const a of args) {
|
|
27503
|
+
if (a === "--yes" || a === "-y") {
|
|
27504
|
+
force = true;
|
|
26395
27505
|
}
|
|
26396
27506
|
}
|
|
27507
|
+
if (args.length > 0 && !force) {
|
|
27508
|
+
return {
|
|
27509
|
+
handled: true,
|
|
27510
|
+
kind: "update_usage",
|
|
27511
|
+
message: "Usage: /update \u2014 check for the latest zelari-code version\n /update --yes (or -y) \u2014 install the latest version (will ask you to restart manually)"
|
|
27512
|
+
};
|
|
27513
|
+
}
|
|
27514
|
+
return {
|
|
27515
|
+
handled: true,
|
|
27516
|
+
kind: force ? "update_perform" : "update_check",
|
|
27517
|
+
updateForce: force
|
|
27518
|
+
};
|
|
26397
27519
|
}
|
|
26398
|
-
|
|
26399
|
-
|
|
26400
|
-
|
|
26401
|
-
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
26411
|
-
|
|
26412
|
-
|
|
26413
|
-
|
|
26414
|
-
|
|
26415
|
-
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
|
|
26419
|
-
|
|
26420
|
-
|
|
26421
|
-
|
|
26422
|
-
|
|
27520
|
+
case "undo": {
|
|
27521
|
+
const confirmed = args.includes("--yes") || args.includes("-y");
|
|
27522
|
+
if (confirmed) {
|
|
27523
|
+
return { handled: true, kind: "undo_confirm", undoConfirmed: true };
|
|
27524
|
+
}
|
|
27525
|
+
return {
|
|
27526
|
+
handled: true,
|
|
27527
|
+
kind: "undo",
|
|
27528
|
+
message: "\u26A0 /undo is DESTRUCTIVE \u2014 it reverts all unstaged modifications and unstages everything.\nUse `/undo --yes` (or `/undo -y`) to confirm."
|
|
27529
|
+
};
|
|
27530
|
+
}
|
|
27531
|
+
case "workspace": {
|
|
27532
|
+
const sub = args[0];
|
|
27533
|
+
if (!sub || sub === "--help" || sub === "help") {
|
|
27534
|
+
return {
|
|
27535
|
+
handled: true,
|
|
27536
|
+
kind: "workspace",
|
|
27537
|
+
message: "Workspace commands (`.zelari/` + `AGENTS.MD`):\n /workspace \u2014 list artifacts (plan, decisions, risks, reviews, docs)\n /workspace show plan \u2014 render plan.md\n /workspace show decisions \u2014 list ADRs (Architecture Decision Records)\n /workspace show risks \u2014 render risks.md\n /workspace show agents \u2014 render AGENTS.MD\n /workspace show docs \u2014 list docs drafts\n /workspace sync \u2014 re-run AGENTS.MD auto-curation now\n /workspace reset \u2014 delete .zelari/ (destructive \u2014 confirmation required)\n\nSee `docs/plans/2026-07-01-council-workspace-cli-stubs.md` for schema."
|
|
27538
|
+
};
|
|
27539
|
+
}
|
|
27540
|
+
if (sub === "show") {
|
|
27541
|
+
const what = args[1];
|
|
27542
|
+
if (!what) {
|
|
27543
|
+
return {
|
|
27544
|
+
handled: true,
|
|
27545
|
+
kind: "workspace_show",
|
|
27546
|
+
workspaceWhat: "",
|
|
27547
|
+
message: "Usage: /workspace show <plan|decisions|risks|agents|docs>"
|
|
27548
|
+
};
|
|
26423
27549
|
}
|
|
26424
|
-
|
|
27550
|
+
if (!["plan", "decisions", "risks", "agents", "docs"].includes(what)) {
|
|
27551
|
+
return {
|
|
27552
|
+
handled: true,
|
|
27553
|
+
kind: "workspace_show",
|
|
27554
|
+
workspaceWhat: "",
|
|
27555
|
+
message: `Unknown artifact "${what}". Available: plan | decisions | risks | agents | docs`
|
|
27556
|
+
};
|
|
27557
|
+
}
|
|
27558
|
+
return {
|
|
27559
|
+
handled: true,
|
|
27560
|
+
kind: "workspace_show",
|
|
27561
|
+
workspaceWhat: what
|
|
27562
|
+
};
|
|
26425
27563
|
}
|
|
26426
|
-
|
|
26427
|
-
|
|
26428
|
-
|
|
26429
|
-
|
|
26430
|
-
|
|
26431
|
-
|
|
27564
|
+
if (sub === "sync") {
|
|
27565
|
+
return { handled: true, kind: "workspace_sync" };
|
|
27566
|
+
}
|
|
27567
|
+
if (sub === "reset") {
|
|
27568
|
+
const force = args.includes("--yes") || args.includes("-y");
|
|
27569
|
+
if (force) {
|
|
27570
|
+
return { handled: true, kind: "workspace_reset", workspaceForce: true };
|
|
27571
|
+
}
|
|
27572
|
+
return {
|
|
27573
|
+
handled: true,
|
|
27574
|
+
kind: "workspace_reset",
|
|
27575
|
+
message: "\u26A0 /workspace reset is DESTRUCTIVE \u2014 deletes the entire `.zelari/` directory.\nUse `/workspace reset --yes` to confirm."
|
|
27576
|
+
};
|
|
27577
|
+
}
|
|
27578
|
+
return {
|
|
27579
|
+
handled: true,
|
|
27580
|
+
kind: "workspace",
|
|
27581
|
+
message: `Unknown /workspace subcommand: "${sub}". Type /workspace for usage.`
|
|
27582
|
+
};
|
|
26432
27583
|
}
|
|
26433
|
-
|
|
27584
|
+
default:
|
|
27585
|
+
return {
|
|
27586
|
+
handled: false,
|
|
27587
|
+
kind: "unknown",
|
|
27588
|
+
message: `Unknown command: /${command}. Type /help for available commands.`
|
|
27589
|
+
};
|
|
26434
27590
|
}
|
|
26435
27591
|
}
|
|
26436
27592
|
|
|
26437
|
-
// src/cli/hooks/useSlashDispatch.ts
|
|
26438
|
-
import { useCallback as useCallback3 } from "react";
|
|
26439
|
-
|
|
26440
27593
|
// src/cli/gitOps.ts
|
|
26441
|
-
import { execFile } from "node:child_process";
|
|
27594
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
26442
27595
|
import { promisify } from "node:util";
|
|
26443
27596
|
import path15 from "node:path";
|
|
26444
|
-
var execFileAsync = promisify(
|
|
27597
|
+
var execFileAsync = promisify(execFile2);
|
|
26445
27598
|
async function git(cwd, args) {
|
|
26446
27599
|
try {
|
|
26447
27600
|
const { stdout } = await execFileAsync("git", ["-C", cwd, ...args], {
|
|
@@ -26651,7 +27804,7 @@ async function handlePromoteMember(ctx, memberId) {
|
|
|
26651
27804
|
}
|
|
26652
27805
|
|
|
26653
27806
|
// src/cli/branchManager.ts
|
|
26654
|
-
import { promises as fs13, existsSync as
|
|
27807
|
+
import { promises as fs13, existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, statSync as statSync4, rmSync } from "node:fs";
|
|
26655
27808
|
import path18 from "node:path";
|
|
26656
27809
|
import os9 from "node:os";
|
|
26657
27810
|
var META_FILENAME = "meta.json";
|
|
@@ -26673,11 +27826,11 @@ function sessionsPathFor(name, baseDir) {
|
|
|
26673
27826
|
}
|
|
26674
27827
|
function readBranchMeta(name, baseDir) {
|
|
26675
27828
|
const metaPath = metaPathFor(name, baseDir);
|
|
26676
|
-
if (!
|
|
27829
|
+
if (!existsSync15(metaPath)) {
|
|
26677
27830
|
throw new BranchNotFoundError(`Branch "${name}" not found`);
|
|
26678
27831
|
}
|
|
26679
27832
|
try {
|
|
26680
|
-
const raw =
|
|
27833
|
+
const raw = readFileSync12(metaPath, "utf-8");
|
|
26681
27834
|
const parsed = JSON.parse(raw);
|
|
26682
27835
|
if (!parsed || typeof parsed !== "object" || typeof parsed.name !== "string" || typeof parsed.createdAt !== "number" || typeof parsed.fromSessionId !== "string") {
|
|
26683
27836
|
throw new BranchCorruptError(`Branch "${name}" meta.json is malformed`);
|
|
@@ -26733,7 +27886,7 @@ var SessionNotFoundError = class extends Error {
|
|
|
26733
27886
|
};
|
|
26734
27887
|
function branchExists(name, baseDir = getBranchesBaseDir()) {
|
|
26735
27888
|
const bp = branchPathFor(name, baseDir);
|
|
26736
|
-
return
|
|
27889
|
+
return existsSync15(bp) && existsSync15(metaPathFor(name, baseDir));
|
|
26737
27890
|
}
|
|
26738
27891
|
async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(), sessionsBaseDir = getSessionsBaseDir()) {
|
|
26739
27892
|
if (!name || name.trim().length === 0) {
|
|
@@ -26746,7 +27899,7 @@ async function createBranch(name, fromSessionId, baseDir = getBranchesBaseDir(),
|
|
|
26746
27899
|
throw new BranchAlreadyExistsError(name);
|
|
26747
27900
|
}
|
|
26748
27901
|
const sourcePath = path18.join(sessionsBaseDir, `${fromSessionId}.jsonl`);
|
|
26749
|
-
if (!
|
|
27902
|
+
if (!existsSync15(sourcePath)) {
|
|
26750
27903
|
throw new SessionNotFoundError(`Source session "${fromSessionId}" not found at ${sourcePath}`);
|
|
26751
27904
|
}
|
|
26752
27905
|
const branchPath = branchPathFor(name, baseDir);
|
|
@@ -26779,7 +27932,7 @@ async function listBranches(baseDir = getBranchesBaseDir()) {
|
|
|
26779
27932
|
const results = [];
|
|
26780
27933
|
for (const entry of entries) {
|
|
26781
27934
|
const metaPath = metaPathFor(entry, baseDir);
|
|
26782
|
-
if (!
|
|
27935
|
+
if (!existsSync15(metaPath)) continue;
|
|
26783
27936
|
try {
|
|
26784
27937
|
const meta3 = readBranchMeta(entry, baseDir);
|
|
26785
27938
|
const sessionCount = await countSessions(entry, baseDir);
|
|
@@ -27017,8 +28170,8 @@ async function validateApiKey(providerId, apiKey, options = {}) {
|
|
|
27017
28170
|
init_providerConfig();
|
|
27018
28171
|
|
|
27019
28172
|
// src/cli/modelDiscovery.ts
|
|
27020
|
-
import { promises as fs15, existsSync as
|
|
27021
|
-
import { homedir as
|
|
28173
|
+
import { promises as fs15, existsSync as existsSync16, readFileSync as readFileSync13 } from "node:fs";
|
|
28174
|
+
import { homedir as homedir4 } from "node:os";
|
|
27022
28175
|
import path20 from "node:path";
|
|
27023
28176
|
var PROVIDER_BASE_URLS = {
|
|
27024
28177
|
"grok": "https://api.x.ai/v1",
|
|
@@ -27027,15 +28180,15 @@ var PROVIDER_BASE_URLS = {
|
|
|
27027
28180
|
"openai-compatible": "https://api.openai.com/v1"
|
|
27028
28181
|
};
|
|
27029
28182
|
function defaultModelsFilePath() {
|
|
27030
|
-
return process.env.ANATHEMA_MODELS_FILE ?? path20.join(
|
|
28183
|
+
return process.env.ANATHEMA_MODELS_FILE ?? path20.join(homedir4(), ".tmp", "zelari-code", "models.json");
|
|
27031
28184
|
}
|
|
27032
28185
|
function getModelsFilePath() {
|
|
27033
28186
|
return defaultModelsFilePath();
|
|
27034
28187
|
}
|
|
27035
28188
|
function loadModelsRegistry(file2 = getModelsFilePath()) {
|
|
27036
|
-
if (!
|
|
28189
|
+
if (!existsSync16(file2)) return {};
|
|
27037
28190
|
try {
|
|
27038
|
-
const raw =
|
|
28191
|
+
const raw = readFileSync13(file2, "utf-8");
|
|
27039
28192
|
const parsed = JSON.parse(raw);
|
|
27040
28193
|
if (!parsed || typeof parsed !== "object") return {};
|
|
27041
28194
|
return parsed;
|
|
@@ -27159,24 +28312,6 @@ var ModelDiscoveryError = class extends Error {
|
|
|
27159
28312
|
}
|
|
27160
28313
|
};
|
|
27161
28314
|
|
|
27162
|
-
// src/cli/utils/duration.ts
|
|
27163
|
-
function formatDuration(ms) {
|
|
27164
|
-
const abs = Math.abs(ms);
|
|
27165
|
-
const sign = ms < 0 ? " ago" : "";
|
|
27166
|
-
if (abs < 1e3) return `${abs}ms${sign}`;
|
|
27167
|
-
const s = Math.floor(abs / 1e3);
|
|
27168
|
-
if (s < 60) return `${s}s${sign}`;
|
|
27169
|
-
const m = Math.floor(s / 60);
|
|
27170
|
-
const rs = s % 60;
|
|
27171
|
-
if (m < 60) return rs === 0 ? `${m}m${sign}` : `${m}m ${rs}s${sign}`;
|
|
27172
|
-
const h = Math.floor(m / 60);
|
|
27173
|
-
const rm = m % 60;
|
|
27174
|
-
if (h < 24) return rm === 0 ? `${h}h${sign}` : `${h}h ${rm}m${sign}`;
|
|
27175
|
-
const d = Math.floor(h / 24);
|
|
27176
|
-
const rh = h % 24;
|
|
27177
|
-
return rh === 0 ? `${d}d${sign}` : `${d}d ${rh}h${sign}`;
|
|
27178
|
-
}
|
|
27179
|
-
|
|
27180
28315
|
// src/cli/slashHandlers/provider.ts
|
|
27181
28316
|
var UNKNOWN_PROVIDER_MSG = (id) => `[provider] unknown: ${id}. Available: openai-compatible, minimax, glm, grok, custom`;
|
|
27182
28317
|
function handleProviderList(ctx) {
|
|
@@ -27395,7 +28530,7 @@ import path21 from "node:path";
|
|
|
27395
28530
|
import os10 from "node:os";
|
|
27396
28531
|
|
|
27397
28532
|
// src/cli/skillHistory.ts
|
|
27398
|
-
import { promises as fs16, existsSync as
|
|
28533
|
+
import { promises as fs16, existsSync as existsSync17, statSync as statSync5, renameSync as renameSync4, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "node:fs";
|
|
27399
28534
|
var SKILL_HISTORY_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
27400
28535
|
async function readSkillHistory(file2) {
|
|
27401
28536
|
let raw = "";
|
|
@@ -27582,7 +28717,8 @@ function useSlashDispatch(params) {
|
|
|
27582
28717
|
harnessRef,
|
|
27583
28718
|
setQueueCount,
|
|
27584
28719
|
dispatchPrompt,
|
|
27585
|
-
dispatchCouncilPrompt
|
|
28720
|
+
dispatchCouncilPrompt,
|
|
28721
|
+
mode = "agent"
|
|
27586
28722
|
} = params;
|
|
27587
28723
|
return useCallback3(async (value) => {
|
|
27588
28724
|
if (!value.trim()) return;
|
|
@@ -27604,6 +28740,11 @@ function useSlashDispatch(params) {
|
|
|
27604
28740
|
if (!result.handled) {
|
|
27605
28741
|
appendUser(setMessages, value);
|
|
27606
28742
|
setSessionActive(true);
|
|
28743
|
+
if (mode === "council") {
|
|
28744
|
+
setInput("");
|
|
28745
|
+
await dispatchCouncilPrompt(value);
|
|
28746
|
+
return;
|
|
28747
|
+
}
|
|
27607
28748
|
await dispatchPrompt(value);
|
|
27608
28749
|
setInput("");
|
|
27609
28750
|
return;
|
|
@@ -27845,15 +28986,16 @@ function useSlashDispatch(params) {
|
|
|
27845
28986
|
setQueueCount,
|
|
27846
28987
|
dispatchPrompt,
|
|
27847
28988
|
dispatchCouncilPrompt,
|
|
28989
|
+
mode,
|
|
27848
28990
|
params
|
|
27849
28991
|
]);
|
|
27850
28992
|
}
|
|
27851
28993
|
|
|
27852
28994
|
// src/cli/hooks/useBatchedMessages.ts
|
|
27853
|
-
import { useCallback as useCallback4, useEffect as
|
|
28995
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef5 } from "react";
|
|
27854
28996
|
function useBatchedMessages(state2, setState, cadenceMs = 16) {
|
|
27855
|
-
const pendingRef =
|
|
27856
|
-
const timerRef =
|
|
28997
|
+
const pendingRef = useRef5(null);
|
|
28998
|
+
const timerRef = useRef5(null);
|
|
27857
28999
|
const flush = useCallback4(() => {
|
|
27858
29000
|
if (timerRef.current !== null) {
|
|
27859
29001
|
clearTimeout(timerRef.current);
|
|
@@ -27891,7 +29033,7 @@ function useBatchedMessages(state2, setState, cadenceMs = 16) {
|
|
|
27891
29033
|
},
|
|
27892
29034
|
[cadenceMs, setState]
|
|
27893
29035
|
);
|
|
27894
|
-
|
|
29036
|
+
useEffect4(() => {
|
|
27895
29037
|
return () => {
|
|
27896
29038
|
if (timerRef.current !== null) {
|
|
27897
29039
|
clearTimeout(timerRef.current);
|
|
@@ -27903,16 +29045,16 @@ function useBatchedMessages(state2, setState, cadenceMs = 16) {
|
|
|
27903
29045
|
}
|
|
27904
29046
|
|
|
27905
29047
|
// src/cli/hooks/useTerminalSize.ts
|
|
27906
|
-
import { useEffect as
|
|
29048
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
27907
29049
|
import { useStdout as useStdout2 } from "ink";
|
|
27908
29050
|
function useTerminalSize(options = {}) {
|
|
27909
29051
|
const { defaults = { columns: 80, rows: 24 }, coalesceMs = 16 } = options;
|
|
27910
29052
|
const { stdout } = useStdout2();
|
|
27911
|
-
const [size, setSize] =
|
|
29053
|
+
const [size, setSize] = useState5({
|
|
27912
29054
|
columns: stdout?.columns ?? defaults.columns,
|
|
27913
29055
|
rows: stdout?.rows ?? defaults.rows
|
|
27914
29056
|
});
|
|
27915
|
-
|
|
29057
|
+
useEffect5(() => {
|
|
27916
29058
|
if (!stdout) return;
|
|
27917
29059
|
setSize({
|
|
27918
29060
|
columns: stdout.columns ?? defaults.columns,
|
|
@@ -27954,15 +29096,28 @@ var providerDefaults = {
|
|
|
27954
29096
|
"glm": "glm-4.5"
|
|
27955
29097
|
};
|
|
27956
29098
|
function App() {
|
|
27957
|
-
const [input, setInput] =
|
|
27958
|
-
const [busy, setBusy] =
|
|
27959
|
-
const [providerConfig, setProviderConfig] =
|
|
27960
|
-
const [sessionStats, setSessionStats] =
|
|
27961
|
-
const [clearEpoch, setClearEpoch] =
|
|
29099
|
+
const [input, setInput] = useState6("");
|
|
29100
|
+
const [busy, setBusy] = useState6(false);
|
|
29101
|
+
const [providerConfig, setProviderConfig] = useState6(() => getProviderConfig());
|
|
29102
|
+
const [sessionStats, setSessionStats] = useState6({ totalTokens: 0, totalCostUsd: 0 });
|
|
29103
|
+
const [clearEpoch, setClearEpoch] = useState6(0);
|
|
29104
|
+
const [mode, setMode] = useState6("agent");
|
|
27962
29105
|
const activeProviderSpec = getActiveProvider();
|
|
27963
29106
|
const activeModel = providerConfig.modelByProvider[activeProviderSpec.id];
|
|
27964
29107
|
const session = useSession();
|
|
27965
29108
|
const size = useTerminalSize();
|
|
29109
|
+
const gitChanges = useGitChanges();
|
|
29110
|
+
const cwd = useMemo(() => shortenCwd(process.cwd(), 32), []);
|
|
29111
|
+
const timer = useExecutionTimer(busy);
|
|
29112
|
+
const { isRawModeSupported } = useStdin();
|
|
29113
|
+
useInput(
|
|
29114
|
+
(_input, key) => {
|
|
29115
|
+
if (key.tab && key.shift) {
|
|
29116
|
+
setMode((m) => m === "agent" ? "council" : "agent");
|
|
29117
|
+
}
|
|
29118
|
+
},
|
|
29119
|
+
{ isActive: isRawModeSupported === true }
|
|
29120
|
+
);
|
|
27966
29121
|
const { commit: commitLive, flush: flushLive } = useBatchedMessages(
|
|
27967
29122
|
session.live,
|
|
27968
29123
|
session.setLive
|
|
@@ -28008,55 +29163,227 @@ function App() {
|
|
|
28008
29163
|
setQueueCount: chatTurn.setQueueCount,
|
|
28009
29164
|
dispatchPrompt: chatTurn.dispatchPrompt,
|
|
28010
29165
|
dispatchCouncilPrompt: chatTurn.dispatchCouncilPrompt,
|
|
29166
|
+
mode,
|
|
28011
29167
|
onNewSession,
|
|
28012
29168
|
onExit,
|
|
28013
29169
|
onClear
|
|
28014
29170
|
});
|
|
28015
|
-
const skills = useMemo(() => listCodingSkills(), []);
|
|
28016
29171
|
const banner = useMemo(() => {
|
|
28017
|
-
const skillList = formatSkillList(skills);
|
|
28018
29172
|
return {
|
|
28019
29173
|
id: "banner-once",
|
|
28020
29174
|
role: "system",
|
|
28021
29175
|
ts: 0,
|
|
28022
|
-
content: `zelari-code
|
|
28023
|
-
${
|
|
28024
|
-
\
|
|
28025
|
-
${skillList}`
|
|
29176
|
+
content: `zelari-code v${VERSION} \u2014 ${activeProviderSpec.id}/${activeModel}
|
|
29177
|
+
cwd: ${cwd}
|
|
29178
|
+
/help for commands \xB7 /skill <name> \xB7 shift+tab toggles agent/council`
|
|
28026
29179
|
};
|
|
28027
|
-
}, [
|
|
29180
|
+
}, [activeProviderSpec.id, activeModel, cwd]);
|
|
28028
29181
|
const staticKey = `${session.sessionId || "pre-bootstrap"}-${clearEpoch}`;
|
|
28029
|
-
const staticItems = [banner, ...session.messages];
|
|
28030
|
-
|
|
29182
|
+
const staticItems = session.sessionId ? [banner, ...session.messages] : [];
|
|
29183
|
+
const showSidebar = shouldShowSidebar(size.columns, size.rows);
|
|
29184
|
+
return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Static, { key: staticKey, items: staticItems }, (item) => renderMessage(item)), /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "row" }, /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", flexGrow: 1, paddingX: 1 }, /* @__PURE__ */ React7.createElement(LiveRegion, { live: session.live, busy }), /* @__PURE__ */ React7.createElement(
|
|
29185
|
+
InputBar,
|
|
29186
|
+
{
|
|
29187
|
+
value: input,
|
|
29188
|
+
onChange: setInput,
|
|
29189
|
+
onSubmit: handleSubmit,
|
|
29190
|
+
disabled: busy
|
|
29191
|
+
}
|
|
29192
|
+
), /* @__PURE__ */ React7.createElement(
|
|
28031
29193
|
StatusBar,
|
|
28032
29194
|
{
|
|
28033
29195
|
model: activeModel,
|
|
28034
29196
|
provider: activeProviderSpec.id,
|
|
28035
29197
|
sessionId: session.sessionId ? session.sessionId.slice(0, 8) : "...",
|
|
28036
29198
|
sessionActive: session.sessionActive,
|
|
28037
|
-
totalTokens: sessionStats.totalTokens,
|
|
28038
|
-
totalCostUsd: sessionStats.totalCostUsd,
|
|
28039
29199
|
queueCount: chatTurn.queueCount,
|
|
28040
|
-
busy
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
29200
|
+
busy,
|
|
29201
|
+
mode,
|
|
29202
|
+
cwd,
|
|
29203
|
+
elapsedMs: timer.elapsedMs,
|
|
29204
|
+
lastMs: timer.lastMs
|
|
29205
|
+
}
|
|
29206
|
+
)), showSidebar && /* @__PURE__ */ React7.createElement(Sidebar, { version: VERSION, changes: gitChanges, rows: size.rows })));
|
|
29207
|
+
}
|
|
29208
|
+
|
|
29209
|
+
// src/cli/components/SplashScreen.tsx
|
|
29210
|
+
import React8, { useEffect as useEffect6, useState as useState7 } from "react";
|
|
29211
|
+
import { Box as Box8, Text as Text8, useInput as useInput2, useStdin as useStdin2 } from "ink";
|
|
29212
|
+
var SPLASH_DURATION_MS = 2e3;
|
|
29213
|
+
var EMBLEM_LARGE = ` =%@*-
|
|
29214
|
+
=%@@@@@#:
|
|
29215
|
+
*%%%%%%@@%-
|
|
29216
|
+
:#%%%%%%%%%@@*
|
|
29217
|
+
:*%%%%%%%%%%%%%@%*.
|
|
29218
|
+
=%%%%%%%%%%%%%%%%@@%.
|
|
29219
|
+
=%%%%%%%%%%%%%%%%%%%@%-
|
|
29220
|
+
*%%%%%%%%%%%%%%%%%%%%%@@=
|
|
29221
|
+
.#%%%%%%%%@@@@@@@@%%%%%%@@@+
|
|
29222
|
+
:#%%%%%%@@@@@@@@@@@@@@@%%%%@@+
|
|
29223
|
+
-%%%%%@@@@@@@@@@@@@@@@@@@@@%%@@#.
|
|
29224
|
+
-%%%%%@@@@@@@@@@@@@@@@@@@@@@@%%@@%:
|
|
29225
|
+
=%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@%.
|
|
29226
|
+
+%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@%:
|
|
29227
|
+
=%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-
|
|
29228
|
+
-%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%:
|
|
29229
|
+
:%%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%
|
|
29230
|
+
#%%%%%@@@@@@@@@@@@@@@%--#%@@@@@@@@@@@@@%%@@@=
|
|
29231
|
+
#@@%%%%@@@@@@@@@@@@@@%: -*@@@@@@@@@@@%%@@@@=
|
|
29232
|
+
#@@%%%%@@@@@@@@@@@@@%: .. -#@@@@@@@%%%%@@@=
|
|
29233
|
+
+@@@%%%@@@@@@@@@@@@%: :#= =%@@@@%%%@@@%-
|
|
29234
|
+
=@@@@%%%@@@@@@@@@@%: -%%*: +%@@%%@@@#
|
|
29235
|
+
+%@@@@%%@@@@@@@@@%: -%@@%*=.:+#@@@@#:
|
|
29236
|
+
.+*@@@@@@@@@%%%@@@@@@%: -%@@@@@#- +@@@@@@*=
|
|
29237
|
+
=*%%%%@@@@@@@@@@@%%@@@@%: -#@@@@@@@*: :*@@@@@@%+=
|
|
29238
|
+
-*%%%%%%%%%%@@@@@@@@@@@@@@@: -%@@@@@@@@#- -#@@@@@@@@*
|
|
29239
|
+
#%%%%%%%%%%%%%@@@@@@@@@@@@@: -#@@@@@%+-::. *@@@@@@@@=
|
|
29240
|
+
=%%%%%%%%%%%%%%%@@@@@@@@@@@@: :*+--=%#. :+++++*@@@@@@@@%
|
|
29241
|
+
#@%%%%%%%%%%%%%@@@@@@@@@@@@%: .. - -## *@@@@@@@@@@@@@@=
|
|
29242
|
+
:@@%%%%%%%%%%%%@@@@@@@@@@@@@%: .**= *%= :%@@@@@@@@@@@@@%
|
|
29243
|
+
#@@@%%%%%%%%%%%@@@@@@@@@@@@@@- =#@@%- :**: =%@@@@@@@@@@@@@+
|
|
29244
|
+
=@@@@@%%%@@%%%%@@@@@@@@@@@@@@@@@@@@@@%- :: .-#@@@@@@@@@@@@@%.
|
|
29245
|
+
:@@@@@@%%@@@%%@@@@@@@@@@@@@@@@@@@@@@@@@%==*%%@@@@@@@@@@@@@@@@#
|
|
29246
|
+
*@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:
|
|
29247
|
+
:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+
|
|
29248
|
+
=##########*######**##**#*********##**#*#######################*`;
|
|
29249
|
+
var EMBLEM_SMALL = ` =%%*:
|
|
29250
|
+
:#%%@@@+
|
|
29251
|
+
=%%%%%%%@#.
|
|
29252
|
+
.*%%%%%%%%%@%-
|
|
29253
|
+
=%%%%%%%%%%%%%@*.
|
|
29254
|
+
+%%%%%%@@@@@%%%%@%:
|
|
29255
|
+
*%%%%@@@@@@@@@@@%%@%:
|
|
29256
|
+
*%%%@@@@@@@@@@@@@@@%@%-
|
|
29257
|
+
:#%%@@@@@@@@@@@@@@@@@@@@@+
|
|
29258
|
+
:%%%@@@@@@@@@@@@@@@@@@@@@@@+
|
|
29259
|
+
#%%@@@@@@@@@@@@@@@@@@@@@@@@@-
|
|
29260
|
+
+%%@@@@@@@@@@@@#@@@@@@@@@@@@@%:
|
|
29261
|
+
%%%%@@@@@@@@@@%::*@@@@@@@@@%@@=
|
|
29262
|
+
-%@%%@@@@@@@@@%:.::*@@@@@%%@@*.
|
|
29263
|
+
:%@@%%@@@@@@@%.:%#::*@@%@@@-
|
|
29264
|
+
:#@@@%@@@@@@%.:%@%+.-#@@@=
|
|
29265
|
+
:+#@@@@@@@%@@@@%.:%@@@%=.+@@@%*-
|
|
29266
|
+
.*#%%%%%%@@@@@@@@@@.:%@@@@@*:.+%@@@@#-
|
|
29267
|
+
+%%%%%%%%%%@@@@@@@@::##*%* :----%@@@@%.
|
|
29268
|
+
.@%%%%%%%%%@@@@@@@@@:.-..-#=:%@@@@@@@@@+
|
|
29269
|
+
*@@%%%%%%%%@@@@@@@@@: =#+ +#.-@@@@@@@@@@.
|
|
29270
|
+
-@@@%%%%%%%@@@@@@@@@@#%@@@- -.:*@@@@@@@@@*
|
|
29271
|
+
%@@@@%@@%@@@@@@@@@@@@@@@@@%**%@@@@@@@@@@@@:
|
|
29272
|
+
=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*`;
|
|
29273
|
+
var EMBLEM_TINY = ` -%%+
|
|
29274
|
+
.*%%%@#:
|
|
29275
|
+
=%%%%%%%@*
|
|
29276
|
+
+%%%%%%%%%%#.
|
|
29277
|
+
+%%%@@@@@@@%@#.
|
|
29278
|
+
.*%@@@@@@@@@@@@@%-
|
|
29279
|
+
.#%@@@@@@@@@@@@@@@@-
|
|
29280
|
+
*%@@@@@@@@@@@@@@@@@%.
|
|
29281
|
+
:@%%@@@@@@%:+%@@@@@%@=
|
|
29282
|
+
=@%%@@@@@%.=-+%@%%@*
|
|
29283
|
+
.=@@%@@@@%.*@*-+@@*.
|
|
29284
|
+
-*%@@@@@@@@%.*@@@+:#@@#=.
|
|
29285
|
+
*%%%%%%@@@@@@.*#%#=-:+@@@%
|
|
29286
|
+
:@%%%%%%@@@@@@.:=.*:*@@@@@@=
|
|
29287
|
+
*@%%@%%@@@@@@@*%@*:-:%@@@@@%.
|
|
29288
|
+
:@@@%@@@@@@@@@@@@@@#%@@@@@@@@=
|
|
29289
|
+
*%%%%%%%%%%%%%%%%%%%%%%%%%%%%#`;
|
|
29290
|
+
var EMBLEM_MICRO = ` -#%=
|
|
29291
|
+
+%%%%#.
|
|
29292
|
+
:#%%%%%%%-
|
|
29293
|
+
:%%@@@@@@@@=
|
|
29294
|
+
-%@@@@@@@@@@@+
|
|
29295
|
+
:%@@@@@@%@@@@@@=
|
|
29296
|
+
-%%@@@@@.+%@@%@+
|
|
29297
|
+
-@@@@@@:%++%@=
|
|
29298
|
+
=*%@@@@@@:@@%=*@#+.
|
|
29299
|
+
=%%%%%@@@@:+++-*%@@*
|
|
29300
|
+
.%%%%%@@@@@+%+=:%@@@@:
|
|
29301
|
+
*@@@@@@@@@@@@@%@@@@@@*`;
|
|
29302
|
+
var FOOTER_ROWS_FULL = 5;
|
|
29303
|
+
var FOOTER_ROWS_COMPACT = 2;
|
|
29304
|
+
function measure(art) {
|
|
29305
|
+
const lines = art.split("\n");
|
|
29306
|
+
return {
|
|
29307
|
+
art,
|
|
29308
|
+
width: Math.max(...lines.map((l) => l.length)),
|
|
29309
|
+
height: lines.length
|
|
29310
|
+
};
|
|
29311
|
+
}
|
|
29312
|
+
var VARIANTS = [
|
|
29313
|
+
measure(EMBLEM_LARGE),
|
|
29314
|
+
measure(EMBLEM_SMALL),
|
|
29315
|
+
measure(EMBLEM_TINY),
|
|
29316
|
+
measure(EMBLEM_MICRO)
|
|
29317
|
+
];
|
|
29318
|
+
function pickSplashArt(columns, rows) {
|
|
29319
|
+
for (const v of VARIANTS) {
|
|
29320
|
+
if (v.width > columns - 2) continue;
|
|
29321
|
+
if (v.height + FOOTER_ROWS_FULL <= rows) return { ...v, compact: false };
|
|
29322
|
+
if (v.height + FOOTER_ROWS_COMPACT <= rows) return { ...v, compact: true };
|
|
29323
|
+
}
|
|
29324
|
+
return null;
|
|
29325
|
+
}
|
|
29326
|
+
function shouldShowSplash(opts) {
|
|
29327
|
+
if (!opts.isTTY) return false;
|
|
29328
|
+
if (opts.env["ZELARI_NO_SPLASH"] === "1") return false;
|
|
29329
|
+
return pickSplashArt(opts.columns, opts.rows) !== null;
|
|
29330
|
+
}
|
|
29331
|
+
function Splash({ onDone, version: version2 }) {
|
|
29332
|
+
const { isRawModeSupported } = useStdin2();
|
|
29333
|
+
const columns = process.stdout.columns ?? 80;
|
|
29334
|
+
const rows = process.stdout.rows ?? 24;
|
|
29335
|
+
const picked = pickSplashArt(columns, rows);
|
|
29336
|
+
useEffect6(() => {
|
|
29337
|
+
const t = setTimeout(onDone, SPLASH_DURATION_MS);
|
|
29338
|
+
return () => clearTimeout(t);
|
|
29339
|
+
}, [onDone]);
|
|
29340
|
+
useInput2(
|
|
29341
|
+
() => {
|
|
29342
|
+
onDone();
|
|
29343
|
+
},
|
|
29344
|
+
{ isActive: isRawModeSupported === true }
|
|
29345
|
+
);
|
|
29346
|
+
if (!picked) return null;
|
|
29347
|
+
return /* @__PURE__ */ React8.createElement(
|
|
29348
|
+
Box8,
|
|
28044
29349
|
{
|
|
28045
|
-
|
|
28046
|
-
|
|
28047
|
-
|
|
28048
|
-
|
|
28049
|
-
|
|
28050
|
-
|
|
29350
|
+
flexDirection: "column",
|
|
29351
|
+
alignItems: "center",
|
|
29352
|
+
justifyContent: "center",
|
|
29353
|
+
width: columns,
|
|
29354
|
+
height: rows - 1
|
|
29355
|
+
},
|
|
29356
|
+
/* @__PURE__ */ React8.createElement(Text8, { color: "cyan" }, picked.art),
|
|
29357
|
+
/* @__PURE__ */ React8.createElement(Box8, { marginTop: 1 }, /* @__PURE__ */ React8.createElement(Text8, { bold: true, color: "white" }, "Z E L A R I C O D E")),
|
|
29358
|
+
!picked.compact && /* @__PURE__ */ React8.createElement(Text8, { dimColor: true }, `${version2 ? `v${version2} \u2014 ` : ""}N-THEM Studio`),
|
|
29359
|
+
!picked.compact && /* @__PURE__ */ React8.createElement(Text8, { dimColor: true, italic: true }, "press any key to skip")
|
|
29360
|
+
);
|
|
29361
|
+
}
|
|
29362
|
+
function SplashGate({
|
|
29363
|
+
children,
|
|
29364
|
+
version: version2
|
|
29365
|
+
}) {
|
|
29366
|
+
const [show, setShow] = useState7(
|
|
29367
|
+
() => shouldShowSplash({
|
|
29368
|
+
isTTY: process.stdout.isTTY === true,
|
|
29369
|
+
env: process.env,
|
|
29370
|
+
columns: process.stdout.columns ?? 80,
|
|
29371
|
+
rows: process.stdout.rows ?? 24
|
|
29372
|
+
})
|
|
29373
|
+
);
|
|
29374
|
+
if (show) {
|
|
29375
|
+
return /* @__PURE__ */ React8.createElement(Splash, { onDone: () => setShow(false), version: version2 });
|
|
29376
|
+
}
|
|
29377
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, children);
|
|
28051
29378
|
}
|
|
28052
29379
|
|
|
28053
29380
|
// src/cli/main.ts
|
|
28054
29381
|
init_providerConfig();
|
|
28055
29382
|
|
|
28056
29383
|
// src/cli/wizard/firstRun.ts
|
|
28057
|
-
import { existsSync as
|
|
29384
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
28058
29385
|
function shouldRunWizard(input) {
|
|
28059
|
-
const exists = input.exists ??
|
|
29386
|
+
const exists = input.exists ?? existsSync18;
|
|
28060
29387
|
if (input.hasResetConfigFlag) {
|
|
28061
29388
|
return { shouldRun: true, reason: "--reset-config flag forced wizard" };
|
|
28062
29389
|
}
|
|
@@ -28091,15 +29418,15 @@ function parseWizardFlags(argv) {
|
|
|
28091
29418
|
}
|
|
28092
29419
|
|
|
28093
29420
|
// src/cli/wizard/runWizard.tsx
|
|
28094
|
-
import
|
|
28095
|
-
import { Box as
|
|
28096
|
-
import { useInput } from "ink";
|
|
29421
|
+
import React10, { useEffect as useEffect7, useState as useState8 } from "react";
|
|
29422
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
29423
|
+
import { useInput as useInput3 } from "ink";
|
|
28097
29424
|
init_keyStore();
|
|
28098
29425
|
init_providerConfig();
|
|
28099
29426
|
|
|
28100
29427
|
// src/cli/wizard/index.tsx
|
|
28101
|
-
import
|
|
28102
|
-
import { Box as
|
|
29428
|
+
import React9 from "react";
|
|
29429
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
28103
29430
|
|
|
28104
29431
|
// src/cli/wizard/useWizardState.ts
|
|
28105
29432
|
init_providerConfig();
|
|
@@ -28214,18 +29541,17 @@ function createWizardState(opts) {
|
|
|
28214
29541
|
var API_KEY_OPTIONS = ["env", "keystore", "skip"];
|
|
28215
29542
|
|
|
28216
29543
|
// src/cli/wizard/index.tsx
|
|
28217
|
-
var VERSION = "0.7.2";
|
|
28218
29544
|
function Frame({ children }) {
|
|
28219
|
-
return /* @__PURE__ */
|
|
29545
|
+
return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1 }, children);
|
|
28220
29546
|
}
|
|
28221
29547
|
function Step(props) {
|
|
28222
|
-
return /* @__PURE__ */
|
|
29548
|
+
return /* @__PURE__ */ React9.createElement(Box9, null, /* @__PURE__ */ React9.createElement(Text9, { color: props.active ? "cyan" : "gray", inverse: props.active }, " ", props.index, "/", props.total, " ", props.name, " "));
|
|
28223
29549
|
}
|
|
28224
29550
|
function renderProviderList(providers, cursor) {
|
|
28225
|
-
return /* @__PURE__ */
|
|
29551
|
+
return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, providers.map((p3, i) => {
|
|
28226
29552
|
const arrow = i === cursor ? "\u279C " : " ";
|
|
28227
29553
|
const color = i === cursor ? "cyan" : void 0;
|
|
28228
|
-
return /* @__PURE__ */
|
|
29554
|
+
return /* @__PURE__ */ React9.createElement(Text9, { key: p3.id, color }, arrow, p3.displayName, " ", /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "(", p3.id, ", uses env ", p3.envVar, ")"));
|
|
28229
29555
|
}));
|
|
28230
29556
|
}
|
|
28231
29557
|
function renderApiKeyOptions(cursor) {
|
|
@@ -28234,37 +29560,37 @@ function renderApiKeyOptions(cursor) {
|
|
|
28234
29560
|
keystore: "Save to local keyStore (encrypted)",
|
|
28235
29561
|
skip: "Skip for now (chat will fail until key is set)"
|
|
28236
29562
|
};
|
|
28237
|
-
return /* @__PURE__ */
|
|
29563
|
+
return /* @__PURE__ */ React9.createElement(Box9, { flexDirection: "column" }, API_KEY_OPTIONS.map((choice, i) => {
|
|
28238
29564
|
const arrow = i === cursor ? "\u279C " : " ";
|
|
28239
29565
|
const color = i === cursor ? "cyan" : void 0;
|
|
28240
|
-
return /* @__PURE__ */
|
|
29566
|
+
return /* @__PURE__ */ React9.createElement(Text9, { key: choice, color }, arrow, labels[choice]);
|
|
28241
29567
|
}));
|
|
28242
29568
|
}
|
|
28243
29569
|
function Wizard({ state: state2, providers }) {
|
|
28244
29570
|
const s = state2.state;
|
|
28245
29571
|
if (s.committed) {
|
|
28246
|
-
return /* @__PURE__ */
|
|
29572
|
+
return /* @__PURE__ */ React9.createElement(Frame, null, /* @__PURE__ */ React9.createElement(Text9, { color: "green" }, "\u2713 Setup complete!"), /* @__PURE__ */ React9.createElement(Text9, null, "Provider: ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.providerId), " | ", "Model: ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.model), " | ", "API key: ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.apiKeyChoice ?? "n/a")), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "Launching zelari-code\u2026 (press any key)")));
|
|
28247
29573
|
}
|
|
28248
|
-
return /* @__PURE__ */
|
|
29574
|
+
return /* @__PURE__ */ React9.createElement(Frame, null, /* @__PURE__ */ React9.createElement(Text9, { color: "cyan", bold: true }, "zelari-code v", VERSION, " \u2014 first-time setup"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1, marginBottom: 1, flexDirection: "row" }, /* @__PURE__ */ React9.createElement(Step, { index: 1, total: 5, name: "welcome", active: s.step === "welcome" }), /* @__PURE__ */ React9.createElement(Step, { index: 2, total: 5, name: "provider", active: s.step === "provider" }), /* @__PURE__ */ React9.createElement(Step, { index: 3, total: 5, name: "model", active: s.step === "model" }), /* @__PURE__ */ React9.createElement(Step, { index: 4, total: 5, name: "apikey", active: s.step === "apikey" }), /* @__PURE__ */ React9.createElement(Step, { index: 5, total: 5, name: "confirm", active: s.step === "confirm" })), s.step === "welcome" && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Text9, null, "Welcome! Let's get you coding in under two minutes."), /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "We'll pick a provider, default model, and how to handle your API key."), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, null, "Press "), /* @__PURE__ */ React9.createElement(Text9, { color: "cyan", inverse: true }, " Enter "), /* @__PURE__ */ React9.createElement(Text9, null, " to continue, or "), /* @__PURE__ */ React9.createElement(Text9, { color: "red", inverse: true }, " Q "), /* @__PURE__ */ React9.createElement(Text9, null, " to quit (re-run with "), /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "--no-wizard"), /* @__PURE__ */ React9.createElement(Text9, null, " to skip later)."))), s.step === "provider" && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Text9, null, "Choose your LLM provider:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, renderProviderList(providers, s.providerCursor)), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "model" && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Text9, null, "Model for ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.providerId), ":"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Text9, null, s.model ?? "(empty)")), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, null, "Press ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan", inverse: true }, " Enter "), "to accept, or type a new name. ", /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "(default kept on empty input)")))), s.step === "apikey" && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Text9, null, "How should we handle the API key?"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, renderApiKeyOptions(s.apiKeyCursor)), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, { color: "gray" }, "\u2191/\u2193 to move, Enter to confirm"))), s.step === "confirm" && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Text9, null, "Confirm your setup:"), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React9.createElement(Text9, null, "Provider: ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.providerId)), /* @__PURE__ */ React9.createElement(Text9, null, "Model: ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.model)), /* @__PURE__ */ React9.createElement(Text9, null, "API key: ", /* @__PURE__ */ React9.createElement(Text9, { color: "cyan" }, s.apiKeyChoice ?? "(unset)"))), /* @__PURE__ */ React9.createElement(Box9, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text9, null, "Press ", /* @__PURE__ */ React9.createElement(Text9, { color: "green", inverse: true }, " Enter "), "to save and launch, or ", /* @__PURE__ */ React9.createElement(Text9, { color: "yellow", inverse: true }, " B "), "to go back."))));
|
|
28249
29575
|
}
|
|
28250
29576
|
|
|
28251
29577
|
// src/cli/wizard/runWizard.tsx
|
|
28252
29578
|
function RunWizard(_props) {
|
|
28253
|
-
const [wiz] =
|
|
29579
|
+
const [wiz] = useState8(
|
|
28254
29580
|
() => createWizardState({
|
|
28255
29581
|
providers: PROVIDERS,
|
|
28256
29582
|
defaultModelFor: (id) => getModelForProvider(id)
|
|
28257
29583
|
})
|
|
28258
29584
|
);
|
|
28259
|
-
const [, force] =
|
|
28260
|
-
|
|
29585
|
+
const [, force] = useState8(0);
|
|
29586
|
+
useEffect7(() => {
|
|
28261
29587
|
if (typeof wiz.subscribe === "function") {
|
|
28262
29588
|
const sub = wiz.subscribe(() => force((n) => n + 1));
|
|
28263
29589
|
return () => sub();
|
|
28264
29590
|
}
|
|
28265
29591
|
return void 0;
|
|
28266
29592
|
}, [wiz]);
|
|
28267
|
-
|
|
29593
|
+
useInput3((input, key) => {
|
|
28268
29594
|
if (wiz.state.committed) return;
|
|
28269
29595
|
const s = wiz.state;
|
|
28270
29596
|
if (key.escape) {
|
|
@@ -28321,20 +29647,20 @@ function RunWizard(_props) {
|
|
|
28321
29647
|
}
|
|
28322
29648
|
});
|
|
28323
29649
|
if (wiz.state.committed) {
|
|
28324
|
-
return /* @__PURE__ */
|
|
29650
|
+
return /* @__PURE__ */ React10.createElement(PostCommitBridge, null);
|
|
28325
29651
|
}
|
|
28326
|
-
return /* @__PURE__ */
|
|
29652
|
+
return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React10.createElement(Wizard, { state: wiz, providers: PROVIDERS }));
|
|
28327
29653
|
}
|
|
28328
29654
|
function PostCommitBridge() {
|
|
28329
|
-
const [showApp, setShowApp] =
|
|
28330
|
-
|
|
29655
|
+
const [showApp, setShowApp] = useState8(false);
|
|
29656
|
+
useEffect7(() => {
|
|
28331
29657
|
const t = setTimeout(() => setShowApp(true), 1200);
|
|
28332
29658
|
return () => clearTimeout(t);
|
|
28333
29659
|
}, []);
|
|
28334
29660
|
if (showApp) {
|
|
28335
|
-
return /* @__PURE__ */
|
|
29661
|
+
return /* @__PURE__ */ React10.createElement(App, null);
|
|
28336
29662
|
}
|
|
28337
|
-
return /* @__PURE__ */
|
|
29663
|
+
return /* @__PURE__ */ React10.createElement(Box10, { flexDirection: "column", paddingX: 2, paddingY: 1 }, /* @__PURE__ */ React10.createElement(Box10, { borderStyle: "round", borderColor: "green", paddingX: 2, paddingY: 1, flexDirection: "column" }, /* @__PURE__ */ React10.createElement(Text10, { color: "green", bold: true }, "\u2713 Setup complete! Launching zelari-code\u2026"), /* @__PURE__ */ React10.createElement(Text10, { color: "gray" }, "Press Ctrl+C any time to exit.")));
|
|
28338
29664
|
}
|
|
28339
29665
|
|
|
28340
29666
|
// src/cli/headless.ts
|
|
@@ -28546,9 +29872,9 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
28546
29872
|
|
|
28547
29873
|
// src/cli/skillsMd.ts
|
|
28548
29874
|
init_skills2();
|
|
28549
|
-
import { existsSync as
|
|
28550
|
-
import { join as
|
|
28551
|
-
import { homedir as
|
|
29875
|
+
import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync14 } from "node:fs";
|
|
29876
|
+
import { join as join9 } from "node:path";
|
|
29877
|
+
import { homedir as homedir5 } from "node:os";
|
|
28552
29878
|
var CODING_CATEGORIES = /* @__PURE__ */ new Set([
|
|
28553
29879
|
"plan",
|
|
28554
29880
|
"refactor",
|
|
@@ -28563,10 +29889,10 @@ var CODING_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
28563
29889
|
]);
|
|
28564
29890
|
function skillMdSearchDirs(projectRoot = process.cwd()) {
|
|
28565
29891
|
return [
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
28569
|
-
|
|
29892
|
+
join9(projectRoot, ".zelari", "skills"),
|
|
29893
|
+
join9(projectRoot, ".claude", "skills"),
|
|
29894
|
+
join9(projectRoot, ".opencode", "skills"),
|
|
29895
|
+
join9(homedir5(), ".zelari-code", "skills")
|
|
28570
29896
|
];
|
|
28571
29897
|
}
|
|
28572
29898
|
function parseSkillMd(content, sourcePath) {
|
|
@@ -28624,7 +29950,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
|
|
|
28624
29950
|
const summary = { loaded: [], skipped: [] };
|
|
28625
29951
|
const seen = new Set(options.existingIds ?? []);
|
|
28626
29952
|
for (const dir of skillMdSearchDirs(projectRoot)) {
|
|
28627
|
-
if (!
|
|
29953
|
+
if (!existsSync19(dir)) continue;
|
|
28628
29954
|
let entries;
|
|
28629
29955
|
try {
|
|
28630
29956
|
entries = readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -28632,10 +29958,10 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
|
|
|
28632
29958
|
continue;
|
|
28633
29959
|
}
|
|
28634
29960
|
for (const entry of entries) {
|
|
28635
|
-
const skillPath =
|
|
28636
|
-
if (!
|
|
29961
|
+
const skillPath = join9(dir, entry, "SKILL.md");
|
|
29962
|
+
if (!existsSync19(skillPath)) continue;
|
|
28637
29963
|
try {
|
|
28638
|
-
const parsed = parseSkillMd(
|
|
29964
|
+
const parsed = parseSkillMd(readFileSync14(skillPath, "utf8"), skillPath);
|
|
28639
29965
|
if (!parsed) {
|
|
28640
29966
|
summary.skipped.push({ path: skillPath, reason: "missing/invalid frontmatter (name, description) or empty body" });
|
|
28641
29967
|
continue;
|
|
@@ -28657,7 +29983,7 @@ function loadSkillMdSkills(projectRoot = process.cwd(), options = {}) {
|
|
|
28657
29983
|
|
|
28658
29984
|
// src/cli/main.ts
|
|
28659
29985
|
init_skills2();
|
|
28660
|
-
var
|
|
29986
|
+
var VERSION = "0.7.9";
|
|
28661
29987
|
async function backgroundUpdateCheck() {
|
|
28662
29988
|
if (process.env.ANATHEMA_DEV === "1") return;
|
|
28663
29989
|
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
@@ -28687,7 +30013,7 @@ async function shutdown() {
|
|
|
28687
30013
|
function pickRootComponent() {
|
|
28688
30014
|
const argv = process.argv.slice(2);
|
|
28689
30015
|
if (argv.includes("--version") || argv.includes("-v")) {
|
|
28690
|
-
console.log(`zelari-code v${
|
|
30016
|
+
console.log(`zelari-code v${VERSION}`);
|
|
28691
30017
|
process.exit(0);
|
|
28692
30018
|
}
|
|
28693
30019
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
@@ -28713,9 +30039,12 @@ function pickRootComponent() {
|
|
|
28713
30039
|
});
|
|
28714
30040
|
if (decision.shouldRun) {
|
|
28715
30041
|
console.error(`[zelari-code] starting wizard: ${decision.reason}`);
|
|
28716
|
-
return { kind: "wizard", element:
|
|
30042
|
+
return { kind: "wizard", element: React11.createElement(RunWizard) };
|
|
28717
30043
|
}
|
|
28718
|
-
return {
|
|
30044
|
+
return {
|
|
30045
|
+
kind: "app",
|
|
30046
|
+
element: React11.createElement(SplashGate, { version: VERSION }, React11.createElement(App))
|
|
30047
|
+
};
|
|
28719
30048
|
}
|
|
28720
30049
|
function loadUserSkills() {
|
|
28721
30050
|
try {
|
|
@@ -28758,6 +30087,6 @@ function main() {
|
|
|
28758
30087
|
}
|
|
28759
30088
|
main();
|
|
28760
30089
|
export {
|
|
28761
|
-
|
|
30090
|
+
VERSION
|
|
28762
30091
|
};
|
|
28763
30092
|
//# sourceMappingURL=main.bundled.js.map
|