@jaggerxtrm/specialists 3.2.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -257
- package/bin/install.js +152 -329
- package/dist/index.js +1286 -428
- package/package.json +2 -2
- package/specialists/codebase-explorer.specialist.yaml +1 -1
- package/specialists/planner.specialist.yaml +87 -0
- package/specialists/specialist-author.specialist.yaml +56 -0
- package/specialists/sync-docs.specialist.yaml +53 -0
- package/specialists/xt-merge.specialist.yaml +78 -0
- package/hooks/beads-close-memory-prompt.mjs +0 -47
- package/hooks/beads-commit-gate.mjs +0 -58
- package/hooks/beads-edit-gate.mjs +0 -53
- package/hooks/beads-stop-gate.mjs +0 -52
- package/hooks/specialists-main-guard.mjs +0 -90
package/dist/index.js
CHANGED
|
@@ -8531,6 +8531,7 @@ var require_limitLength = __commonJS((exports) => {
|
|
|
8531
8531
|
var require_pattern = __commonJS((exports) => {
|
|
8532
8532
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8533
8533
|
var code_1 = require_code2();
|
|
8534
|
+
var util_1 = require_util();
|
|
8534
8535
|
var codegen_1 = require_codegen();
|
|
8535
8536
|
var error2 = {
|
|
8536
8537
|
message: ({ schemaCode }) => (0, codegen_1.str)`must match pattern "${schemaCode}"`,
|
|
@@ -8543,10 +8544,18 @@ var require_pattern = __commonJS((exports) => {
|
|
|
8543
8544
|
$data: true,
|
|
8544
8545
|
error: error2,
|
|
8545
8546
|
code(cxt) {
|
|
8546
|
-
const { data, $data, schema, schemaCode, it } = cxt;
|
|
8547
|
+
const { gen, data, $data, schema, schemaCode, it } = cxt;
|
|
8547
8548
|
const u = it.opts.unicodeRegExp ? "u" : "";
|
|
8548
|
-
|
|
8549
|
-
|
|
8549
|
+
if ($data) {
|
|
8550
|
+
const { regExp } = it.opts.code;
|
|
8551
|
+
const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._)`new RegExp` : (0, util_1.useFunc)(gen, regExp);
|
|
8552
|
+
const valid = gen.let("valid");
|
|
8553
|
+
gen.try(() => gen.assign(valid, (0, codegen_1._)`${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false));
|
|
8554
|
+
cxt.fail$data((0, codegen_1._)`!${valid}`);
|
|
8555
|
+
} else {
|
|
8556
|
+
const regExp = (0, code_1.usePattern)(cxt, schema);
|
|
8557
|
+
cxt.fail$data((0, codegen_1._)`!${regExp}.test(${data})`);
|
|
8558
|
+
}
|
|
8550
8559
|
}
|
|
8551
8560
|
};
|
|
8552
8561
|
exports.default = def;
|
|
@@ -17524,12 +17533,13 @@ class SpecialistLoader {
|
|
|
17524
17533
|
this.userDir = options.userDir ?? join(homedir(), ".agents", "specialists");
|
|
17525
17534
|
}
|
|
17526
17535
|
getScanDirs() {
|
|
17527
|
-
|
|
17536
|
+
const dirs = [
|
|
17528
17537
|
{ path: join(this.projectDir, "specialists"), scope: "project" },
|
|
17529
17538
|
{ path: join(this.projectDir, ".claude", "specialists"), scope: "project" },
|
|
17530
17539
|
{ path: join(this.projectDir, ".agent-forge", "specialists"), scope: "project" },
|
|
17531
17540
|
{ path: this.userDir, scope: "user" }
|
|
17532
|
-
]
|
|
17541
|
+
];
|
|
17542
|
+
return dirs.filter((d) => existsSync(d.path));
|
|
17533
17543
|
}
|
|
17534
17544
|
async list(category) {
|
|
17535
17545
|
const results = [];
|
|
@@ -17640,6 +17650,9 @@ var init_backendMap = __esm(() => {
|
|
|
17640
17650
|
|
|
17641
17651
|
// src/pi/session.ts
|
|
17642
17652
|
import { spawn } from "node:child_process";
|
|
17653
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
17654
|
+
import { homedir as homedir2 } from "node:os";
|
|
17655
|
+
import { join as join2 } from "node:path";
|
|
17643
17656
|
function mapPermissionToTools(level) {
|
|
17644
17657
|
switch (level?.toUpperCase()) {
|
|
17645
17658
|
case "READ_ONLY":
|
|
@@ -17659,6 +17672,7 @@ class PiAgentSession {
|
|
|
17659
17672
|
options;
|
|
17660
17673
|
proc;
|
|
17661
17674
|
_lastOutput = "";
|
|
17675
|
+
_donePromise;
|
|
17662
17676
|
_doneResolve;
|
|
17663
17677
|
_doneReject;
|
|
17664
17678
|
_agentEndReceived = false;
|
|
@@ -17686,6 +17700,7 @@ class PiAgentSession {
|
|
|
17686
17700
|
const args = [
|
|
17687
17701
|
"--mode",
|
|
17688
17702
|
"rpc",
|
|
17703
|
+
"--no-extensions",
|
|
17689
17704
|
...providerArgs,
|
|
17690
17705
|
"--no-session",
|
|
17691
17706
|
...extraArgs
|
|
@@ -17693,11 +17708,22 @@ class PiAgentSession {
|
|
|
17693
17708
|
const toolsFlag = mapPermissionToTools(this.options.permissionLevel);
|
|
17694
17709
|
if (toolsFlag)
|
|
17695
17710
|
args.push("--tools", toolsFlag);
|
|
17711
|
+
const piExtDir = join2(homedir2(), ".pi", "agent", "extensions");
|
|
17712
|
+
const permLevel = (this.options.permissionLevel ?? "").toUpperCase();
|
|
17713
|
+
if (permLevel !== "READ_ONLY") {
|
|
17714
|
+
const qgPath = join2(piExtDir, "quality-gates");
|
|
17715
|
+
if (existsSync2(qgPath))
|
|
17716
|
+
args.push("-e", qgPath);
|
|
17717
|
+
}
|
|
17718
|
+
const ssPath = join2(piExtDir, "service-skills");
|
|
17719
|
+
if (existsSync2(ssPath))
|
|
17720
|
+
args.push("-e", ssPath);
|
|
17696
17721
|
if (this.options.systemPrompt) {
|
|
17697
17722
|
args.push("--append-system-prompt", this.options.systemPrompt);
|
|
17698
17723
|
}
|
|
17699
17724
|
this.proc = spawn("pi", args, {
|
|
17700
|
-
stdio: ["pipe", "pipe", "inherit"]
|
|
17725
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
17726
|
+
cwd: this.options.cwd
|
|
17701
17727
|
});
|
|
17702
17728
|
const donePromise = new Promise((resolve, reject) => {
|
|
17703
17729
|
this._doneResolve = resolve;
|
|
@@ -17759,34 +17785,11 @@ class PiAgentSession {
|
|
|
17759
17785
|
this._lastOutput = last.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
17760
17786
|
}
|
|
17761
17787
|
this._agentEndReceived = true;
|
|
17762
|
-
this.options.onEvent?.("
|
|
17788
|
+
this.options.onEvent?.("agent_end");
|
|
17763
17789
|
this._doneResolve?.();
|
|
17764
17790
|
return;
|
|
17765
17791
|
}
|
|
17766
|
-
if (type === "thinking_start") {
|
|
17767
|
-
this.options.onEvent?.("thinking");
|
|
17768
|
-
return;
|
|
17769
|
-
}
|
|
17770
|
-
if (type === "thinking_delta") {
|
|
17771
|
-
if (event.delta)
|
|
17772
|
-
this.options.onThinking?.(event.delta);
|
|
17773
|
-
this.options.onEvent?.("thinking");
|
|
17774
|
-
return;
|
|
17775
|
-
}
|
|
17776
|
-
if (type === "thinking_end") {
|
|
17777
|
-
return;
|
|
17778
|
-
}
|
|
17779
|
-
if (type === "toolcall_start") {
|
|
17780
|
-
this.options.onToolStart?.(event.name ?? event.toolName ?? "tool");
|
|
17781
|
-
this.options.onEvent?.("toolcall");
|
|
17782
|
-
return;
|
|
17783
|
-
}
|
|
17784
|
-
if (type === "toolcall_end") {
|
|
17785
|
-
this.options.onEvent?.("toolcall");
|
|
17786
|
-
return;
|
|
17787
|
-
}
|
|
17788
17792
|
if (type === "tool_execution_start") {
|
|
17789
|
-
this.options.onToolStart?.(event.name ?? event.toolName ?? "tool");
|
|
17790
17793
|
this.options.onEvent?.("tool_execution");
|
|
17791
17794
|
return;
|
|
17792
17795
|
}
|
|
@@ -17795,7 +17798,7 @@ class PiAgentSession {
|
|
|
17795
17798
|
return;
|
|
17796
17799
|
}
|
|
17797
17800
|
if (type === "tool_execution_end") {
|
|
17798
|
-
this.options.onToolEnd?.(event.
|
|
17801
|
+
this.options.onToolEnd?.(event.toolName ?? event.name ?? "tool");
|
|
17799
17802
|
this.options.onEvent?.("tool_execution_end");
|
|
17800
17803
|
return;
|
|
17801
17804
|
}
|
|
@@ -17803,9 +17806,30 @@ class PiAgentSession {
|
|
|
17803
17806
|
const ae = event.assistantMessageEvent;
|
|
17804
17807
|
if (!ae)
|
|
17805
17808
|
return;
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
+
switch (ae.type) {
|
|
17810
|
+
case "text_delta":
|
|
17811
|
+
if (ae.delta)
|
|
17812
|
+
this.options.onToken?.(ae.delta);
|
|
17813
|
+
this.options.onEvent?.("text");
|
|
17814
|
+
break;
|
|
17815
|
+
case "thinking_start":
|
|
17816
|
+
this.options.onEvent?.("thinking");
|
|
17817
|
+
break;
|
|
17818
|
+
case "thinking_delta":
|
|
17819
|
+
if (ae.delta)
|
|
17820
|
+
this.options.onThinking?.(ae.delta);
|
|
17821
|
+
this.options.onEvent?.("thinking");
|
|
17822
|
+
break;
|
|
17823
|
+
case "toolcall_start":
|
|
17824
|
+
this.options.onToolStart?.(ae.name ?? ae.toolName ?? "tool");
|
|
17825
|
+
this.options.onEvent?.("toolcall");
|
|
17826
|
+
break;
|
|
17827
|
+
case "toolcall_end":
|
|
17828
|
+
this.options.onEvent?.("toolcall");
|
|
17829
|
+
break;
|
|
17830
|
+
case "done":
|
|
17831
|
+
this.options.onEvent?.("message_done");
|
|
17832
|
+
break;
|
|
17809
17833
|
}
|
|
17810
17834
|
}
|
|
17811
17835
|
}
|
|
@@ -17831,7 +17855,7 @@ class PiAgentSession {
|
|
|
17831
17855
|
this.proc?.stdin?.write(msg);
|
|
17832
17856
|
}
|
|
17833
17857
|
async waitForDone(timeout) {
|
|
17834
|
-
const donePromise = this._donePromise;
|
|
17858
|
+
const donePromise = this._donePromise ?? Promise.resolve();
|
|
17835
17859
|
if (!timeout)
|
|
17836
17860
|
return donePromise;
|
|
17837
17861
|
return Promise.race([
|
|
@@ -17868,7 +17892,7 @@ class PiAgentSession {
|
|
|
17868
17892
|
if (this._killed)
|
|
17869
17893
|
return;
|
|
17870
17894
|
this.proc?.stdin?.end();
|
|
17871
|
-
await this._donePromise
|
|
17895
|
+
await this._donePromise?.catch(() => {});
|
|
17872
17896
|
}
|
|
17873
17897
|
kill() {
|
|
17874
17898
|
if (this._killed)
|
|
@@ -17892,6 +17916,29 @@ var init_session = __esm(() => {
|
|
|
17892
17916
|
|
|
17893
17917
|
// src/specialist/beads.ts
|
|
17894
17918
|
import { spawnSync } from "node:child_process";
|
|
17919
|
+
function buildBeadContext(bead, completedBlockers = []) {
|
|
17920
|
+
const lines = [`# Task: ${bead.title}`];
|
|
17921
|
+
if (bead.description?.trim()) {
|
|
17922
|
+
lines.push(bead.description.trim());
|
|
17923
|
+
}
|
|
17924
|
+
if (bead.notes?.trim()) {
|
|
17925
|
+
lines.push("", "## Notes", bead.notes.trim());
|
|
17926
|
+
}
|
|
17927
|
+
if (completedBlockers.length > 0) {
|
|
17928
|
+
lines.push("", "## Context from completed dependencies:");
|
|
17929
|
+
for (const blocker of completedBlockers) {
|
|
17930
|
+
lines.push("", `### ${blocker.title} (${blocker.id})`);
|
|
17931
|
+
if (blocker.description?.trim()) {
|
|
17932
|
+
lines.push(blocker.description.trim());
|
|
17933
|
+
}
|
|
17934
|
+
if (blocker.notes?.trim()) {
|
|
17935
|
+
lines.push("", blocker.notes.trim());
|
|
17936
|
+
}
|
|
17937
|
+
}
|
|
17938
|
+
}
|
|
17939
|
+
return lines.join(`
|
|
17940
|
+
`).trim();
|
|
17941
|
+
}
|
|
17895
17942
|
|
|
17896
17943
|
class BeadsClient {
|
|
17897
17944
|
available;
|
|
@@ -17917,12 +17964,66 @@ class BeadsClient {
|
|
|
17917
17964
|
const id = result.stdout?.trim();
|
|
17918
17965
|
return id || null;
|
|
17919
17966
|
}
|
|
17967
|
+
readBead(id) {
|
|
17968
|
+
if (!this.available || !id)
|
|
17969
|
+
return null;
|
|
17970
|
+
const result = spawnSync("bd", ["show", id, "--json"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
|
|
17971
|
+
if (result.error || result.status !== 0 || !result.stdout?.trim())
|
|
17972
|
+
return null;
|
|
17973
|
+
try {
|
|
17974
|
+
const parsed = JSON.parse(result.stdout);
|
|
17975
|
+
const bead = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
17976
|
+
if (!bead || typeof bead !== "object" || typeof bead.id !== "string" || typeof bead.title !== "string")
|
|
17977
|
+
return null;
|
|
17978
|
+
return bead;
|
|
17979
|
+
} catch (err) {
|
|
17980
|
+
console.warn(`[specialists] readBead: JSON parse failed for id=${id}: ${err}`);
|
|
17981
|
+
return null;
|
|
17982
|
+
}
|
|
17983
|
+
}
|
|
17984
|
+
getCompletedBlockers(id, depth = 1) {
|
|
17985
|
+
if (!this.available || !id || depth < 1)
|
|
17986
|
+
return [];
|
|
17987
|
+
const result = spawnSync("bd", ["dep", "list", id, "--json"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
|
|
17988
|
+
if (result.error || result.status !== 0 || !result.stdout?.trim())
|
|
17989
|
+
return [];
|
|
17990
|
+
let deps;
|
|
17991
|
+
try {
|
|
17992
|
+
deps = JSON.parse(result.stdout);
|
|
17993
|
+
if (!Array.isArray(deps))
|
|
17994
|
+
return [];
|
|
17995
|
+
} catch {
|
|
17996
|
+
return [];
|
|
17997
|
+
}
|
|
17998
|
+
const blockers = deps.filter((d) => d.dependency_type === "blocks" && d.status === "closed");
|
|
17999
|
+
const records = [];
|
|
18000
|
+
for (const dep of blockers) {
|
|
18001
|
+
const record3 = this.readBead(dep.id);
|
|
18002
|
+
if (record3) {
|
|
18003
|
+
records.push(record3);
|
|
18004
|
+
if (depth > 1) {
|
|
18005
|
+
records.push(...this.getCompletedBlockers(dep.id, depth - 1));
|
|
18006
|
+
}
|
|
18007
|
+
}
|
|
18008
|
+
}
|
|
18009
|
+
return records;
|
|
18010
|
+
}
|
|
18011
|
+
addDependency(trackingBeadId, inputBeadId) {
|
|
18012
|
+
if (!this.available || !trackingBeadId || !inputBeadId)
|
|
18013
|
+
return;
|
|
18014
|
+
spawnSync("bd", ["dep", "add", trackingBeadId, inputBeadId], { stdio: "ignore" });
|
|
18015
|
+
}
|
|
17920
18016
|
closeBead(id, status, durationMs, model) {
|
|
17921
18017
|
if (!this.available || !id)
|
|
17922
18018
|
return;
|
|
17923
18019
|
const reason = `${status}, ${Math.round(durationMs)}ms, ${model}`;
|
|
17924
18020
|
spawnSync("bd", ["close", id, "-r", reason], { stdio: "ignore" });
|
|
17925
18021
|
}
|
|
18022
|
+
updateBeadNotes(id, notes) {
|
|
18023
|
+
if (!this.available || !id || !notes)
|
|
18024
|
+
return;
|
|
18025
|
+
spawnSync("bd", ["update", id, "--notes", notes], { stdio: "ignore" });
|
|
18026
|
+
}
|
|
17926
18027
|
auditBead(id, toolName, model, exitCode) {
|
|
17927
18028
|
if (!this.available || !id)
|
|
17928
18029
|
return;
|
|
@@ -18006,7 +18107,14 @@ class SpecialistRunner {
|
|
|
18006
18107
|
const preScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "pre") ?? [];
|
|
18007
18108
|
const preResults = preScripts.map((s) => runScript(s.path)).filter((_, i) => preScripts[i].inject_output);
|
|
18008
18109
|
const preScriptOutput = formatScriptOutput(preResults);
|
|
18009
|
-
const
|
|
18110
|
+
const beadVariables = options.inputBeadId ? { bead_context: options.prompt, bead_id: options.inputBeadId } : {};
|
|
18111
|
+
const variables = {
|
|
18112
|
+
prompt: options.prompt,
|
|
18113
|
+
cwd: process.cwd(),
|
|
18114
|
+
pre_script_output: preScriptOutput,
|
|
18115
|
+
...options.variables ?? {},
|
|
18116
|
+
...beadVariables
|
|
18117
|
+
};
|
|
18010
18118
|
const renderedTask = renderTemplate(prompt.task_template, variables);
|
|
18011
18119
|
const promptHash = createHash("sha256").update(renderedTask).digest("hex").slice(0, 16);
|
|
18012
18120
|
await hooks.emit("post_render", invocationId, metadata.name, metadata.version, {
|
|
@@ -18059,10 +18167,15 @@ You have access via Bash:
|
|
|
18059
18167
|
});
|
|
18060
18168
|
const beadsIntegration = spec.specialist.beads_integration ?? "auto";
|
|
18061
18169
|
let beadId;
|
|
18062
|
-
|
|
18170
|
+
let ownsBead = false;
|
|
18171
|
+
if (options.inputBeadId) {
|
|
18172
|
+
beadId = options.inputBeadId;
|
|
18173
|
+
} else if (beadsClient && shouldCreateBead(beadsIntegration, execution.permission_required)) {
|
|
18063
18174
|
beadId = beadsClient.createBead(metadata.name) ?? undefined;
|
|
18064
|
-
if (beadId)
|
|
18175
|
+
if (beadId) {
|
|
18176
|
+
ownsBead = true;
|
|
18065
18177
|
onBeadCreated?.(beadId);
|
|
18178
|
+
}
|
|
18066
18179
|
}
|
|
18067
18180
|
let output;
|
|
18068
18181
|
let sessionBackend = model;
|
|
@@ -18072,6 +18185,7 @@ You have access via Bash:
|
|
|
18072
18185
|
model,
|
|
18073
18186
|
systemPrompt: agentsMd || undefined,
|
|
18074
18187
|
permissionLevel,
|
|
18188
|
+
cwd: process.cwd(),
|
|
18075
18189
|
onToken: (delta) => onProgress?.(delta),
|
|
18076
18190
|
onThinking: (delta) => onProgress?.(`\uD83D\uDCAD ${delta}`),
|
|
18077
18191
|
onToolStart: (tool) => onProgress?.(`
|
|
@@ -18100,7 +18214,8 @@ You have access via Bash:
|
|
|
18100
18214
|
}
|
|
18101
18215
|
const beadStatus = isCancelled ? "CANCELLED" : "ERROR";
|
|
18102
18216
|
if (beadId) {
|
|
18103
|
-
|
|
18217
|
+
if (ownsBead)
|
|
18218
|
+
beadsClient?.closeBead(beadId, beadStatus, Date.now() - start, model);
|
|
18104
18219
|
beadsClient?.auditBead(beadId, metadata.name, model, 1);
|
|
18105
18220
|
}
|
|
18106
18221
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
@@ -18123,7 +18238,8 @@ You have access via Bash:
|
|
|
18123
18238
|
output_valid: true
|
|
18124
18239
|
});
|
|
18125
18240
|
if (beadId) {
|
|
18126
|
-
|
|
18241
|
+
if (ownsBead)
|
|
18242
|
+
beadsClient?.closeBead(beadId, "COMPLETE", durationMs, model);
|
|
18127
18243
|
beadsClient?.auditBead(beadId, metadata.name, model, 0);
|
|
18128
18244
|
}
|
|
18129
18245
|
return {
|
|
@@ -18132,6 +18248,7 @@ You have access via Bash:
|
|
|
18132
18248
|
model,
|
|
18133
18249
|
durationMs,
|
|
18134
18250
|
specialistVersion: metadata.version,
|
|
18251
|
+
promptHash,
|
|
18135
18252
|
beadId
|
|
18136
18253
|
};
|
|
18137
18254
|
}
|
|
@@ -18234,9 +18351,9 @@ __export(exports_install, {
|
|
|
18234
18351
|
});
|
|
18235
18352
|
import { execFileSync } from "node:child_process";
|
|
18236
18353
|
import { fileURLToPath } from "node:url";
|
|
18237
|
-
import { dirname as dirname2, join as
|
|
18354
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
18238
18355
|
async function run() {
|
|
18239
|
-
const installerPath =
|
|
18356
|
+
const installerPath = join5(dirname2(fileURLToPath(import.meta.url)), "..", "bin", "install.js");
|
|
18240
18357
|
execFileSync(process.execPath, [installerPath], { stdio: "inherit" });
|
|
18241
18358
|
}
|
|
18242
18359
|
var init_install = () => {};
|
|
@@ -18391,7 +18508,7 @@ async function run4() {
|
|
|
18391
18508
|
}
|
|
18392
18509
|
const allModels = parsePiModels();
|
|
18393
18510
|
if (!allModels) {
|
|
18394
|
-
console.error("pi not found or failed —
|
|
18511
|
+
console.error("pi not found or failed — install and configure pi first");
|
|
18395
18512
|
process.exit(1);
|
|
18396
18513
|
}
|
|
18397
18514
|
let models = allModels;
|
|
@@ -18447,36 +18564,62 @@ var exports_init = {};
|
|
|
18447
18564
|
__export(exports_init, {
|
|
18448
18565
|
run: () => run5
|
|
18449
18566
|
});
|
|
18450
|
-
import { existsSync as
|
|
18451
|
-
import { join as
|
|
18567
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18568
|
+
import { join as join6 } from "node:path";
|
|
18452
18569
|
function ok(msg) {
|
|
18453
18570
|
console.log(` ${green2("✓")} ${msg}`);
|
|
18454
18571
|
}
|
|
18455
18572
|
function skip(msg) {
|
|
18456
18573
|
console.log(` ${yellow3("○")} ${msg}`);
|
|
18457
18574
|
}
|
|
18575
|
+
function loadJson(path, fallback) {
|
|
18576
|
+
if (!existsSync4(path))
|
|
18577
|
+
return structuredClone(fallback);
|
|
18578
|
+
try {
|
|
18579
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
18580
|
+
} catch {
|
|
18581
|
+
return structuredClone(fallback);
|
|
18582
|
+
}
|
|
18583
|
+
}
|
|
18584
|
+
function saveJson(path, value) {
|
|
18585
|
+
writeFileSync(path, JSON.stringify(value, null, 2) + `
|
|
18586
|
+
`, "utf-8");
|
|
18587
|
+
}
|
|
18588
|
+
function ensureProjectMcp(cwd) {
|
|
18589
|
+
const mcpPath = join6(cwd, MCP_FILE);
|
|
18590
|
+
const mcp = loadJson(mcpPath, { mcpServers: {} });
|
|
18591
|
+
mcp.mcpServers ??= {};
|
|
18592
|
+
const existing = mcp.mcpServers[MCP_SERVER_NAME];
|
|
18593
|
+
if (existing && existing.command === MCP_SERVER_CONFIG.command && Array.isArray(existing.args) && existing.args.length === MCP_SERVER_CONFIG.args.length) {
|
|
18594
|
+
skip(".mcp.json already registers specialists");
|
|
18595
|
+
return;
|
|
18596
|
+
}
|
|
18597
|
+
mcp.mcpServers[MCP_SERVER_NAME] = MCP_SERVER_CONFIG;
|
|
18598
|
+
saveJson(mcpPath, mcp);
|
|
18599
|
+
ok("registered specialists in project .mcp.json");
|
|
18600
|
+
}
|
|
18458
18601
|
async function run5() {
|
|
18459
18602
|
const cwd = process.cwd();
|
|
18460
18603
|
console.log(`
|
|
18461
18604
|
${bold3("specialists init")}
|
|
18462
18605
|
`);
|
|
18463
|
-
const specialistsDir =
|
|
18464
|
-
if (
|
|
18606
|
+
const specialistsDir = join6(cwd, "specialists");
|
|
18607
|
+
if (existsSync4(specialistsDir)) {
|
|
18465
18608
|
skip("specialists/ already exists");
|
|
18466
18609
|
} else {
|
|
18467
18610
|
mkdirSync(specialistsDir, { recursive: true });
|
|
18468
18611
|
ok("created specialists/");
|
|
18469
18612
|
}
|
|
18470
|
-
const runtimeDir =
|
|
18471
|
-
if (
|
|
18613
|
+
const runtimeDir = join6(cwd, ".specialists");
|
|
18614
|
+
if (existsSync4(runtimeDir)) {
|
|
18472
18615
|
skip(".specialists/ already exists");
|
|
18473
18616
|
} else {
|
|
18474
|
-
mkdirSync(
|
|
18475
|
-
mkdirSync(
|
|
18617
|
+
mkdirSync(join6(runtimeDir, "jobs"), { recursive: true });
|
|
18618
|
+
mkdirSync(join6(runtimeDir, "ready"), { recursive: true });
|
|
18476
18619
|
ok("created .specialists/ (jobs/, ready/)");
|
|
18477
18620
|
}
|
|
18478
|
-
const gitignorePath =
|
|
18479
|
-
if (
|
|
18621
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
18622
|
+
if (existsSync4(gitignorePath)) {
|
|
18480
18623
|
const existing = readFileSync(gitignorePath, "utf-8");
|
|
18481
18624
|
if (existing.includes(GITIGNORE_ENTRY)) {
|
|
18482
18625
|
skip(".gitignore already has .specialists/ entry");
|
|
@@ -18493,8 +18636,8 @@ ${bold3("specialists init")}
|
|
|
18493
18636
|
`, "utf-8");
|
|
18494
18637
|
ok("created .gitignore with .specialists/ entry");
|
|
18495
18638
|
}
|
|
18496
|
-
const agentsPath =
|
|
18497
|
-
if (
|
|
18639
|
+
const agentsPath = join6(cwd, "AGENTS.md");
|
|
18640
|
+
if (existsSync4(agentsPath)) {
|
|
18498
18641
|
const existing = readFileSync(agentsPath, "utf-8");
|
|
18499
18642
|
if (existing.includes(AGENTS_MARKER)) {
|
|
18500
18643
|
skip("AGENTS.md already has Specialists section");
|
|
@@ -18508,16 +18651,17 @@ ${bold3("specialists init")}
|
|
|
18508
18651
|
writeFileSync(agentsPath, AGENTS_BLOCK, "utf-8");
|
|
18509
18652
|
ok("created AGENTS.md with Specialists section");
|
|
18510
18653
|
}
|
|
18654
|
+
ensureProjectMcp(cwd);
|
|
18511
18655
|
console.log(`
|
|
18512
18656
|
${bold3("Done!")}
|
|
18513
18657
|
`);
|
|
18514
18658
|
console.log(` ${dim3("Next steps:")}`);
|
|
18515
18659
|
console.log(` 1. Add your specialists to ${yellow3("specialists/")}`);
|
|
18516
18660
|
console.log(` 2. Run ${yellow3("specialists list")} to verify they are discovered`);
|
|
18517
|
-
console.log(` 3. Restart Claude Code to pick up AGENTS.md changes
|
|
18661
|
+
console.log(` 3. Restart Claude Code to pick up AGENTS.md / .mcp.json changes
|
|
18518
18662
|
`);
|
|
18519
18663
|
}
|
|
18520
|
-
var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, green2 = (s) => `\x1B[32m${s}\x1B[0m`, yellow3 = (s) => `\x1B[33m${s}\x1B[0m`, dim3 = (s) => `\x1B[2m${s}\x1B[0m`, AGENTS_BLOCK, AGENTS_MARKER = "## Specialists", GITIGNORE_ENTRY = ".specialists/";
|
|
18664
|
+
var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, green2 = (s) => `\x1B[32m${s}\x1B[0m`, yellow3 = (s) => `\x1B[33m${s}\x1B[0m`, dim3 = (s) => `\x1B[2m${s}\x1B[0m`, AGENTS_BLOCK, AGENTS_MARKER = "## Specialists", GITIGNORE_ENTRY = ".specialists/", MCP_FILE = ".mcp.json", MCP_SERVER_NAME = "specialists", MCP_SERVER_CONFIG;
|
|
18521
18665
|
var init_init = __esm(() => {
|
|
18522
18666
|
AGENTS_BLOCK = `
|
|
18523
18667
|
## Specialists
|
|
@@ -18527,6 +18671,7 @@ see available specialists. Use \`use_specialist\` or \`start_specialist\` to
|
|
|
18527
18671
|
delegate heavy tasks (code review, bug hunting, deep reasoning) to the right
|
|
18528
18672
|
specialist without user intervention.
|
|
18529
18673
|
`.trimStart();
|
|
18674
|
+
MCP_SERVER_CONFIG = { command: "specialists", args: [] };
|
|
18530
18675
|
});
|
|
18531
18676
|
|
|
18532
18677
|
// src/cli/edit.ts
|
|
@@ -18659,10 +18804,119 @@ var init_edit = __esm(() => {
|
|
|
18659
18804
|
VALID_PERMISSIONS = ["READ_ONLY", "LOW", "MEDIUM", "HIGH"];
|
|
18660
18805
|
});
|
|
18661
18806
|
|
|
18807
|
+
// src/specialist/timeline-events.ts
|
|
18808
|
+
function mapCallbackEventToTimelineEvent(callbackEvent, context) {
|
|
18809
|
+
const t = Date.now();
|
|
18810
|
+
switch (callbackEvent) {
|
|
18811
|
+
case "thinking":
|
|
18812
|
+
return { t, type: TIMELINE_EVENT_TYPES.THINKING };
|
|
18813
|
+
case "toolcall":
|
|
18814
|
+
return {
|
|
18815
|
+
t,
|
|
18816
|
+
type: TIMELINE_EVENT_TYPES.TOOL,
|
|
18817
|
+
tool: context.tool ?? "unknown",
|
|
18818
|
+
phase: "start",
|
|
18819
|
+
tool_call_id: context.toolCallId
|
|
18820
|
+
};
|
|
18821
|
+
case "tool_execution_end":
|
|
18822
|
+
return {
|
|
18823
|
+
t,
|
|
18824
|
+
type: TIMELINE_EVENT_TYPES.TOOL,
|
|
18825
|
+
tool: context.tool ?? "unknown",
|
|
18826
|
+
phase: "end",
|
|
18827
|
+
tool_call_id: context.toolCallId,
|
|
18828
|
+
is_error: context.isError
|
|
18829
|
+
};
|
|
18830
|
+
case "text":
|
|
18831
|
+
return { t, type: TIMELINE_EVENT_TYPES.TEXT };
|
|
18832
|
+
case "agent_end":
|
|
18833
|
+
case "message_done":
|
|
18834
|
+
case "done":
|
|
18835
|
+
return null;
|
|
18836
|
+
default:
|
|
18837
|
+
return null;
|
|
18838
|
+
}
|
|
18839
|
+
}
|
|
18840
|
+
function createRunStartEvent(specialist, beadId) {
|
|
18841
|
+
return {
|
|
18842
|
+
t: Date.now(),
|
|
18843
|
+
type: TIMELINE_EVENT_TYPES.RUN_START,
|
|
18844
|
+
specialist,
|
|
18845
|
+
bead_id: beadId
|
|
18846
|
+
};
|
|
18847
|
+
}
|
|
18848
|
+
function createMetaEvent(model, backend) {
|
|
18849
|
+
return {
|
|
18850
|
+
t: Date.now(),
|
|
18851
|
+
type: TIMELINE_EVENT_TYPES.META,
|
|
18852
|
+
model,
|
|
18853
|
+
backend
|
|
18854
|
+
};
|
|
18855
|
+
}
|
|
18856
|
+
function createRunCompleteEvent(status, elapsed_s, options) {
|
|
18857
|
+
return {
|
|
18858
|
+
t: Date.now(),
|
|
18859
|
+
type: TIMELINE_EVENT_TYPES.RUN_COMPLETE,
|
|
18860
|
+
status,
|
|
18861
|
+
elapsed_s,
|
|
18862
|
+
...options
|
|
18863
|
+
};
|
|
18864
|
+
}
|
|
18865
|
+
function parseTimelineEvent(line) {
|
|
18866
|
+
try {
|
|
18867
|
+
const parsed = JSON.parse(line);
|
|
18868
|
+
if (!parsed || typeof parsed !== "object")
|
|
18869
|
+
return null;
|
|
18870
|
+
if (typeof parsed.t !== "number")
|
|
18871
|
+
return null;
|
|
18872
|
+
if (typeof parsed.type !== "string")
|
|
18873
|
+
return null;
|
|
18874
|
+
if (parsed.type === TIMELINE_EVENT_TYPES.DONE) {
|
|
18875
|
+
return {
|
|
18876
|
+
t: parsed.t,
|
|
18877
|
+
type: TIMELINE_EVENT_TYPES.DONE,
|
|
18878
|
+
elapsed_s: typeof parsed.elapsed_s === "number" ? parsed.elapsed_s : undefined
|
|
18879
|
+
};
|
|
18880
|
+
}
|
|
18881
|
+
if (parsed.type === TIMELINE_EVENT_TYPES.AGENT_END) {
|
|
18882
|
+
return {
|
|
18883
|
+
t: parsed.t,
|
|
18884
|
+
type: TIMELINE_EVENT_TYPES.AGENT_END,
|
|
18885
|
+
elapsed_s: typeof parsed.elapsed_s === "number" ? parsed.elapsed_s : undefined
|
|
18886
|
+
};
|
|
18887
|
+
}
|
|
18888
|
+
const knownTypes = Object.values(TIMELINE_EVENT_TYPES).filter((type) => type !== TIMELINE_EVENT_TYPES.DONE && type !== TIMELINE_EVENT_TYPES.AGENT_END);
|
|
18889
|
+
if (!knownTypes.includes(parsed.type))
|
|
18890
|
+
return null;
|
|
18891
|
+
return parsed;
|
|
18892
|
+
} catch {
|
|
18893
|
+
return null;
|
|
18894
|
+
}
|
|
18895
|
+
}
|
|
18896
|
+
function isRunCompleteEvent(event) {
|
|
18897
|
+
return event.type === TIMELINE_EVENT_TYPES.RUN_COMPLETE;
|
|
18898
|
+
}
|
|
18899
|
+
function compareTimelineEvents(a, b) {
|
|
18900
|
+
return a.t - b.t;
|
|
18901
|
+
}
|
|
18902
|
+
var TIMELINE_EVENT_TYPES;
|
|
18903
|
+
var init_timeline_events = __esm(() => {
|
|
18904
|
+
TIMELINE_EVENT_TYPES = {
|
|
18905
|
+
RUN_START: "run_start",
|
|
18906
|
+
META: "meta",
|
|
18907
|
+
THINKING: "thinking",
|
|
18908
|
+
TOOL: "tool",
|
|
18909
|
+
TEXT: "text",
|
|
18910
|
+
RUN_COMPLETE: "run_complete",
|
|
18911
|
+
DONE: "done",
|
|
18912
|
+
AGENT_END: "agent_end"
|
|
18913
|
+
};
|
|
18914
|
+
});
|
|
18915
|
+
|
|
18662
18916
|
// src/specialist/supervisor.ts
|
|
18663
18917
|
import {
|
|
18664
18918
|
closeSync,
|
|
18665
|
-
existsSync as
|
|
18919
|
+
existsSync as existsSync5,
|
|
18666
18920
|
mkdirSync as mkdirSync2,
|
|
18667
18921
|
openSync,
|
|
18668
18922
|
readdirSync,
|
|
@@ -18673,7 +18927,32 @@ import {
|
|
|
18673
18927
|
writeFileSync as writeFileSync3,
|
|
18674
18928
|
writeSync
|
|
18675
18929
|
} from "node:fs";
|
|
18676
|
-
import { join as
|
|
18930
|
+
import { join as join7 } from "node:path";
|
|
18931
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
18932
|
+
function getCurrentGitSha() {
|
|
18933
|
+
const result = spawnSync4("git", ["rev-parse", "HEAD"], {
|
|
18934
|
+
encoding: "utf-8",
|
|
18935
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
18936
|
+
});
|
|
18937
|
+
if (result.status !== 0)
|
|
18938
|
+
return;
|
|
18939
|
+
const sha = result.stdout?.trim();
|
|
18940
|
+
return sha || undefined;
|
|
18941
|
+
}
|
|
18942
|
+
function formatBeadNotes(result) {
|
|
18943
|
+
const metadata = [
|
|
18944
|
+
`prompt_hash=${result.promptHash}`,
|
|
18945
|
+
`git_sha=${getCurrentGitSha() ?? "unknown"}`,
|
|
18946
|
+
`elapsed_ms=${Math.round(result.durationMs)}`,
|
|
18947
|
+
`model=${result.model}`,
|
|
18948
|
+
`backend=${result.backend}`
|
|
18949
|
+
].join(`
|
|
18950
|
+
`);
|
|
18951
|
+
return `${result.output}
|
|
18952
|
+
|
|
18953
|
+
---
|
|
18954
|
+
${metadata}`;
|
|
18955
|
+
}
|
|
18677
18956
|
|
|
18678
18957
|
class Supervisor {
|
|
18679
18958
|
opts;
|
|
@@ -18681,23 +18960,23 @@ class Supervisor {
|
|
|
18681
18960
|
this.opts = opts;
|
|
18682
18961
|
}
|
|
18683
18962
|
jobDir(id) {
|
|
18684
|
-
return
|
|
18963
|
+
return join7(this.opts.jobsDir, id);
|
|
18685
18964
|
}
|
|
18686
18965
|
statusPath(id) {
|
|
18687
|
-
return
|
|
18966
|
+
return join7(this.jobDir(id), "status.json");
|
|
18688
18967
|
}
|
|
18689
18968
|
resultPath(id) {
|
|
18690
|
-
return
|
|
18969
|
+
return join7(this.jobDir(id), "result.txt");
|
|
18691
18970
|
}
|
|
18692
18971
|
eventsPath(id) {
|
|
18693
|
-
return
|
|
18972
|
+
return join7(this.jobDir(id), "events.jsonl");
|
|
18694
18973
|
}
|
|
18695
18974
|
readyDir() {
|
|
18696
|
-
return
|
|
18975
|
+
return join7(this.opts.jobsDir, "..", "ready");
|
|
18697
18976
|
}
|
|
18698
18977
|
readStatus(id) {
|
|
18699
18978
|
const path = this.statusPath(id);
|
|
18700
|
-
if (!
|
|
18979
|
+
if (!existsSync5(path))
|
|
18701
18980
|
return null;
|
|
18702
18981
|
try {
|
|
18703
18982
|
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
@@ -18706,12 +18985,12 @@ class Supervisor {
|
|
|
18706
18985
|
}
|
|
18707
18986
|
}
|
|
18708
18987
|
listJobs() {
|
|
18709
|
-
if (!
|
|
18988
|
+
if (!existsSync5(this.opts.jobsDir))
|
|
18710
18989
|
return [];
|
|
18711
18990
|
const jobs = [];
|
|
18712
18991
|
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18713
|
-
const path =
|
|
18714
|
-
if (!
|
|
18992
|
+
const path = join7(this.opts.jobsDir, entry, "status.json");
|
|
18993
|
+
if (!existsSync5(path))
|
|
18715
18994
|
continue;
|
|
18716
18995
|
try {
|
|
18717
18996
|
jobs.push(JSON.parse(readFileSync3(path, "utf-8")));
|
|
@@ -18732,11 +19011,11 @@ class Supervisor {
|
|
|
18732
19011
|
this.writeStatusFile(id, { ...current, ...updates });
|
|
18733
19012
|
}
|
|
18734
19013
|
gc() {
|
|
18735
|
-
if (!
|
|
19014
|
+
if (!existsSync5(this.opts.jobsDir))
|
|
18736
19015
|
return;
|
|
18737
19016
|
const cutoff = Date.now() - JOB_TTL_DAYS * 86400000;
|
|
18738
19017
|
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18739
|
-
const dir =
|
|
19018
|
+
const dir = join7(this.opts.jobsDir, entry);
|
|
18740
19019
|
try {
|
|
18741
19020
|
const stat2 = statSync(dir);
|
|
18742
19021
|
if (!stat2.isDirectory())
|
|
@@ -18747,11 +19026,11 @@ class Supervisor {
|
|
|
18747
19026
|
}
|
|
18748
19027
|
}
|
|
18749
19028
|
crashRecovery() {
|
|
18750
|
-
if (!
|
|
19029
|
+
if (!existsSync5(this.opts.jobsDir))
|
|
18751
19030
|
return;
|
|
18752
19031
|
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18753
|
-
const statusPath =
|
|
18754
|
-
if (!
|
|
19032
|
+
const statusPath = join7(this.opts.jobsDir, entry, "status.json");
|
|
19033
|
+
if (!existsSync5(statusPath))
|
|
18755
19034
|
continue;
|
|
18756
19035
|
try {
|
|
18757
19036
|
const s = JSON.parse(readFileSync3(statusPath, "utf-8"));
|
|
@@ -18788,14 +19067,19 @@ class Supervisor {
|
|
|
18788
19067
|
};
|
|
18789
19068
|
this.writeStatusFile(id, initialStatus);
|
|
18790
19069
|
const eventsFd = openSync(this.eventsPath(id), "a");
|
|
18791
|
-
const
|
|
19070
|
+
const appendTimelineEvent = (event) => {
|
|
18792
19071
|
try {
|
|
18793
|
-
writeSync(eventsFd, JSON.stringify(
|
|
19072
|
+
writeSync(eventsFd, JSON.stringify(event) + `
|
|
18794
19073
|
`);
|
|
18795
19074
|
} catch {}
|
|
18796
19075
|
};
|
|
19076
|
+
appendTimelineEvent(createRunStartEvent(runOptions.name));
|
|
18797
19077
|
let textLogged = false;
|
|
18798
19078
|
let currentTool = "";
|
|
19079
|
+
let currentToolCallId = "";
|
|
19080
|
+
let killFn;
|
|
19081
|
+
const sigtermHandler = () => killFn?.();
|
|
19082
|
+
process.once("SIGTERM", sigtermHandler);
|
|
18799
19083
|
try {
|
|
18800
19084
|
const result = await runner.run(runOptions, (delta) => {
|
|
18801
19085
|
const toolMatch = delta.match(/⚙ (.+?)…/);
|
|
@@ -18811,21 +19095,29 @@ class Supervisor {
|
|
|
18811
19095
|
last_event_at_ms: now,
|
|
18812
19096
|
elapsed_s: Math.round((now - startedAtMs) / 1000)
|
|
18813
19097
|
});
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
19098
|
+
const timelineEvent = mapCallbackEventToTimelineEvent(eventType, {
|
|
19099
|
+
tool: currentTool,
|
|
19100
|
+
toolCallId: currentToolCallId || undefined
|
|
19101
|
+
});
|
|
19102
|
+
if (timelineEvent) {
|
|
19103
|
+
appendTimelineEvent(timelineEvent);
|
|
18817
19104
|
} else if (eventType === "text" && !textLogged) {
|
|
18818
19105
|
textLogged = true;
|
|
18819
|
-
|
|
19106
|
+
appendTimelineEvent({ t: Date.now(), type: TIMELINE_EVENT_TYPES.TEXT });
|
|
18820
19107
|
}
|
|
18821
19108
|
}, (meta) => {
|
|
18822
19109
|
this.updateStatus(id, { model: meta.model, backend: meta.backend });
|
|
18823
|
-
|
|
18824
|
-
}, (
|
|
19110
|
+
appendTimelineEvent(createMetaEvent(meta.model, meta.backend));
|
|
19111
|
+
}, (fn) => {
|
|
19112
|
+
killFn = fn;
|
|
19113
|
+
}, (beadId) => {
|
|
18825
19114
|
this.updateStatus(id, { bead_id: beadId });
|
|
18826
19115
|
});
|
|
18827
19116
|
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
18828
19117
|
writeFileSync3(this.resultPath(id), result.output, "utf-8");
|
|
19118
|
+
if (result.beadId) {
|
|
19119
|
+
this.opts.beadsClient?.updateBeadNotes(result.beadId, formatBeadNotes(result));
|
|
19120
|
+
}
|
|
18829
19121
|
this.updateStatus(id, {
|
|
18830
19122
|
status: "done",
|
|
18831
19123
|
elapsed_s: elapsed,
|
|
@@ -18834,27 +19126,35 @@ class Supervisor {
|
|
|
18834
19126
|
backend: result.backend,
|
|
18835
19127
|
bead_id: result.beadId
|
|
18836
19128
|
});
|
|
18837
|
-
|
|
18838
|
-
|
|
19129
|
+
appendTimelineEvent(createRunCompleteEvent("COMPLETE", elapsed, {
|
|
19130
|
+
model: result.model,
|
|
19131
|
+
backend: result.backend,
|
|
19132
|
+
bead_id: result.beadId
|
|
19133
|
+
}));
|
|
19134
|
+
writeFileSync3(join7(this.readyDir(), id), "", "utf-8");
|
|
18839
19135
|
return id;
|
|
18840
19136
|
} catch (err) {
|
|
18841
19137
|
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
19138
|
+
const errorMsg = err?.message ?? String(err);
|
|
18842
19139
|
this.updateStatus(id, {
|
|
18843
19140
|
status: "error",
|
|
18844
19141
|
elapsed_s: elapsed,
|
|
18845
|
-
error:
|
|
19142
|
+
error: errorMsg
|
|
18846
19143
|
});
|
|
18847
|
-
|
|
19144
|
+
appendTimelineEvent(createRunCompleteEvent("ERROR", elapsed, {
|
|
19145
|
+
error: errorMsg
|
|
19146
|
+
}));
|
|
18848
19147
|
throw err;
|
|
18849
19148
|
} finally {
|
|
19149
|
+
process.removeListener("SIGTERM", sigtermHandler);
|
|
18850
19150
|
closeSync(eventsFd);
|
|
18851
19151
|
}
|
|
18852
19152
|
}
|
|
18853
19153
|
}
|
|
18854
|
-
var JOB_TTL_DAYS
|
|
19154
|
+
var JOB_TTL_DAYS;
|
|
18855
19155
|
var init_supervisor = __esm(() => {
|
|
19156
|
+
init_timeline_events();
|
|
18856
19157
|
JOB_TTL_DAYS = Number(process.env.SPECIALISTS_JOB_TTL_DAYS ?? 7);
|
|
18857
|
-
LOGGED_EVENTS = new Set(["thinking", "toolcall", "tool_execution_end", "done"]);
|
|
18858
19158
|
});
|
|
18859
19159
|
|
|
18860
19160
|
// src/cli/run.ts
|
|
@@ -18862,27 +19162,37 @@ var exports_run = {};
|
|
|
18862
19162
|
__export(exports_run, {
|
|
18863
19163
|
run: () => run7
|
|
18864
19164
|
});
|
|
18865
|
-
import { join as
|
|
19165
|
+
import { join as join8 } from "node:path";
|
|
18866
19166
|
async function parseArgs4(argv) {
|
|
18867
19167
|
const name = argv[0];
|
|
18868
19168
|
if (!name || name.startsWith("--")) {
|
|
18869
|
-
console.error('Usage: specialists run <name> [--prompt "..."] [--model <model>] [--no-beads] [--background]');
|
|
19169
|
+
console.error('Usage: specialists run <name> [--prompt "..."] [--bead <id>] [--context-depth <n>] [--model <model>] [--no-beads] [--background]');
|
|
18870
19170
|
process.exit(1);
|
|
18871
19171
|
}
|
|
18872
19172
|
let prompt = "";
|
|
19173
|
+
let beadId;
|
|
18873
19174
|
let model;
|
|
18874
19175
|
let noBeads = false;
|
|
18875
19176
|
let background = false;
|
|
19177
|
+
let contextDepth = 1;
|
|
18876
19178
|
for (let i = 1;i < argv.length; i++) {
|
|
18877
19179
|
const token = argv[i];
|
|
18878
19180
|
if (token === "--prompt" && argv[i + 1]) {
|
|
18879
19181
|
prompt = argv[++i];
|
|
18880
19182
|
continue;
|
|
18881
19183
|
}
|
|
19184
|
+
if (token === "--bead" && argv[i + 1]) {
|
|
19185
|
+
beadId = argv[++i];
|
|
19186
|
+
continue;
|
|
19187
|
+
}
|
|
18882
19188
|
if (token === "--model" && argv[i + 1]) {
|
|
18883
19189
|
model = argv[++i];
|
|
18884
19190
|
continue;
|
|
18885
19191
|
}
|
|
19192
|
+
if (token === "--context-depth" && argv[i + 1]) {
|
|
19193
|
+
contextDepth = parseInt(argv[++i], 10) || 0;
|
|
19194
|
+
continue;
|
|
19195
|
+
}
|
|
18886
19196
|
if (token === "--no-beads") {
|
|
18887
19197
|
noBeads = true;
|
|
18888
19198
|
continue;
|
|
@@ -18892,10 +19202,11 @@ async function parseArgs4(argv) {
|
|
|
18892
19202
|
continue;
|
|
18893
19203
|
}
|
|
18894
19204
|
}
|
|
18895
|
-
if (
|
|
18896
|
-
|
|
18897
|
-
|
|
18898
|
-
|
|
19205
|
+
if (prompt && beadId) {
|
|
19206
|
+
console.error("Error: use either --prompt or --bead, not both.");
|
|
19207
|
+
process.exit(1);
|
|
19208
|
+
}
|
|
19209
|
+
if (!prompt && !beadId && !process.stdin.isTTY) {
|
|
18899
19210
|
prompt = await new Promise((resolve) => {
|
|
18900
19211
|
let buf = "";
|
|
18901
19212
|
process.stdin.setEncoding("utf-8");
|
|
@@ -18905,26 +19216,58 @@ async function parseArgs4(argv) {
|
|
|
18905
19216
|
process.stdin.on("end", () => resolve(buf.trim()));
|
|
18906
19217
|
});
|
|
18907
19218
|
}
|
|
18908
|
-
|
|
19219
|
+
if (!prompt && !beadId) {
|
|
19220
|
+
console.error("Error: provide --prompt, pipe stdin, or use --bead <id>.");
|
|
19221
|
+
process.exit(1);
|
|
19222
|
+
}
|
|
19223
|
+
return { name, prompt, beadId, model, noBeads, background, contextDepth };
|
|
18909
19224
|
}
|
|
18910
19225
|
async function run7() {
|
|
18911
19226
|
const args = await parseArgs4(process.argv.slice(3));
|
|
18912
19227
|
const loader = new SpecialistLoader;
|
|
18913
19228
|
const circuitBreaker = new CircuitBreaker;
|
|
18914
|
-
const hooks = new HookEmitter({ tracePath:
|
|
18915
|
-
const beadsClient = args.noBeads ?
|
|
19229
|
+
const hooks = new HookEmitter({ tracePath: join8(process.cwd(), ".specialists", "trace.jsonl") });
|
|
19230
|
+
const beadsClient = args.noBeads ? undefined : new BeadsClient;
|
|
19231
|
+
const beadReader = beadsClient ?? new BeadsClient;
|
|
19232
|
+
let prompt = args.prompt;
|
|
19233
|
+
let variables;
|
|
19234
|
+
if (args.beadId) {
|
|
19235
|
+
const bead = beadReader.readBead(args.beadId);
|
|
19236
|
+
if (!bead) {
|
|
19237
|
+
throw new Error(`Unable to read bead '${args.beadId}' via bd show --json`);
|
|
19238
|
+
}
|
|
19239
|
+
const blockers = args.contextDepth > 0 ? beadReader.getCompletedBlockers(args.beadId, args.contextDepth) : [];
|
|
19240
|
+
if (blockers.length > 0) {
|
|
19241
|
+
process.stderr.write(dim5(`
|
|
19242
|
+
[context: ${blockers.length} completed dep${blockers.length > 1 ? "s" : ""} injected]
|
|
19243
|
+
`));
|
|
19244
|
+
}
|
|
19245
|
+
const beadContext = buildBeadContext(bead, blockers);
|
|
19246
|
+
prompt = beadContext;
|
|
19247
|
+
variables = {
|
|
19248
|
+
bead_context: beadContext,
|
|
19249
|
+
bead_id: args.beadId
|
|
19250
|
+
};
|
|
19251
|
+
}
|
|
18916
19252
|
const runner = new SpecialistRunner({
|
|
18917
19253
|
loader,
|
|
18918
19254
|
hooks,
|
|
18919
19255
|
circuitBreaker,
|
|
18920
|
-
beadsClient
|
|
19256
|
+
beadsClient
|
|
18921
19257
|
});
|
|
18922
19258
|
if (args.background) {
|
|
18923
|
-
const jobsDir =
|
|
19259
|
+
const jobsDir = join8(process.cwd(), ".specialists", "jobs");
|
|
18924
19260
|
const supervisor = new Supervisor({
|
|
18925
19261
|
runner,
|
|
18926
|
-
runOptions: {
|
|
18927
|
-
|
|
19262
|
+
runOptions: {
|
|
19263
|
+
name: args.name,
|
|
19264
|
+
prompt,
|
|
19265
|
+
variables,
|
|
19266
|
+
backendOverride: args.model,
|
|
19267
|
+
inputBeadId: args.beadId
|
|
19268
|
+
},
|
|
19269
|
+
jobsDir,
|
|
19270
|
+
beadsClient
|
|
18928
19271
|
});
|
|
18929
19272
|
try {
|
|
18930
19273
|
const jobId = await supervisor.run();
|
|
@@ -18941,11 +19284,13 @@ async function run7() {
|
|
|
18941
19284
|
${bold5(`Running ${cyan3(args.name)}`)}
|
|
18942
19285
|
|
|
18943
19286
|
`);
|
|
18944
|
-
let
|
|
19287
|
+
let trackingBeadId;
|
|
18945
19288
|
const result = await runner.run({
|
|
18946
19289
|
name: args.name,
|
|
18947
|
-
prompt
|
|
18948
|
-
|
|
19290
|
+
prompt,
|
|
19291
|
+
variables,
|
|
19292
|
+
backendOverride: args.model,
|
|
19293
|
+
inputBeadId: args.beadId
|
|
18949
19294
|
}, (delta) => process.stdout.write(delta), undefined, (meta) => process.stderr.write(dim5(`
|
|
18950
19295
|
[${meta.backend} / ${meta.model}]
|
|
18951
19296
|
|
|
@@ -18958,10 +19303,10 @@ Interrupted.
|
|
|
18958
19303
|
killFn();
|
|
18959
19304
|
process.exit(130);
|
|
18960
19305
|
});
|
|
18961
|
-
}, (
|
|
18962
|
-
|
|
19306
|
+
}, (beadId) => {
|
|
19307
|
+
trackingBeadId = beadId;
|
|
18963
19308
|
process.stderr.write(dim5(`
|
|
18964
|
-
[bead: ${
|
|
19309
|
+
[bead: ${beadId}]
|
|
18965
19310
|
`));
|
|
18966
19311
|
});
|
|
18967
19312
|
if (result.output && !result.output.endsWith(`
|
|
@@ -18969,8 +19314,9 @@ Interrupted.
|
|
|
18969
19314
|
process.stdout.write(`
|
|
18970
19315
|
`);
|
|
18971
19316
|
const secs = (result.durationMs / 1000).toFixed(1);
|
|
19317
|
+
const effectiveBeadId = args.beadId ?? trackingBeadId;
|
|
18972
19318
|
const footer = [
|
|
18973
|
-
|
|
19319
|
+
effectiveBeadId ? `bead ${effectiveBeadId}` : "",
|
|
18974
19320
|
`${secs}s`,
|
|
18975
19321
|
dim5(result.model)
|
|
18976
19322
|
].filter(Boolean).join(" ");
|
|
@@ -18988,14 +19334,103 @@ var init_run = __esm(() => {
|
|
|
18988
19334
|
init_supervisor();
|
|
18989
19335
|
});
|
|
18990
19336
|
|
|
19337
|
+
// src/cli/format-helpers.ts
|
|
19338
|
+
function formatTime(t) {
|
|
19339
|
+
return new Date(t).toISOString().slice(11, 19);
|
|
19340
|
+
}
|
|
19341
|
+
function formatElapsed(seconds) {
|
|
19342
|
+
if (seconds < 60)
|
|
19343
|
+
return `${seconds}s`;
|
|
19344
|
+
const m = Math.floor(seconds / 60);
|
|
19345
|
+
const s = seconds % 60;
|
|
19346
|
+
return s > 0 ? `${m}m ${s}s` : `${m}m`;
|
|
19347
|
+
}
|
|
19348
|
+
function getEventLabel(type) {
|
|
19349
|
+
return EVENT_LABELS[type] ?? type.slice(0, 5).toUpperCase();
|
|
19350
|
+
}
|
|
19351
|
+
|
|
19352
|
+
class JobColorMap {
|
|
19353
|
+
colors = new Map;
|
|
19354
|
+
nextIdx = 0;
|
|
19355
|
+
getColor(jobId) {
|
|
19356
|
+
let color = this.colors.get(jobId);
|
|
19357
|
+
if (!color) {
|
|
19358
|
+
color = JOB_COLORS[this.nextIdx % JOB_COLORS.length];
|
|
19359
|
+
this.colors.set(jobId, color);
|
|
19360
|
+
this.nextIdx++;
|
|
19361
|
+
}
|
|
19362
|
+
return color;
|
|
19363
|
+
}
|
|
19364
|
+
get(jobId) {
|
|
19365
|
+
return this.getColor(jobId);
|
|
19366
|
+
}
|
|
19367
|
+
has(jobId) {
|
|
19368
|
+
return this.colors.has(jobId);
|
|
19369
|
+
}
|
|
19370
|
+
get size() {
|
|
19371
|
+
return this.colors.size;
|
|
19372
|
+
}
|
|
19373
|
+
}
|
|
19374
|
+
function formatEventLine(event, options) {
|
|
19375
|
+
const ts = dim6(formatTime(event.t));
|
|
19376
|
+
const label = options.colorize(bold6(getEventLabel(event.type).padEnd(5)));
|
|
19377
|
+
const prefix = `${options.colorize(`[${options.jobId}]`)} ${options.specialist}${options.beadId ? ` ${dim6(`[${options.beadId}]`)}` : ""}`;
|
|
19378
|
+
const detailParts = [];
|
|
19379
|
+
if (event.type === "meta") {
|
|
19380
|
+
detailParts.push(`model=${event.model}`);
|
|
19381
|
+
detailParts.push(`backend=${event.backend}`);
|
|
19382
|
+
} else if (event.type === "tool") {
|
|
19383
|
+
detailParts.push(`tool=${event.tool}`);
|
|
19384
|
+
detailParts.push(`phase=${event.phase}`);
|
|
19385
|
+
if (event.phase === "end") {
|
|
19386
|
+
detailParts.push(`ok=${event.is_error ? "false" : "true"}`);
|
|
19387
|
+
}
|
|
19388
|
+
} else if (event.type === "run_complete") {
|
|
19389
|
+
detailParts.push(`status=${event.status}`);
|
|
19390
|
+
detailParts.push(`elapsed=${formatElapsed(event.elapsed_s)}`);
|
|
19391
|
+
if (event.error) {
|
|
19392
|
+
detailParts.push(`error=${event.error}`);
|
|
19393
|
+
}
|
|
19394
|
+
} else if (event.type === "done" || event.type === "agent_end") {
|
|
19395
|
+
detailParts.push("status=COMPLETE");
|
|
19396
|
+
detailParts.push(`elapsed=${formatElapsed(event.elapsed_s ?? 0)}`);
|
|
19397
|
+
} else if (event.type === "run_start") {
|
|
19398
|
+
detailParts.push(`specialist=${event.specialist}`);
|
|
19399
|
+
if (event.bead_id) {
|
|
19400
|
+
detailParts.push(`bead=${event.bead_id}`);
|
|
19401
|
+
}
|
|
19402
|
+
} else if (event.type === "text") {
|
|
19403
|
+
detailParts.push("kind=assistant");
|
|
19404
|
+
} else if (event.type === "thinking") {
|
|
19405
|
+
detailParts.push("kind=model");
|
|
19406
|
+
}
|
|
19407
|
+
const detail = detailParts.length > 0 ? dim6(detailParts.join(" ")) : "";
|
|
19408
|
+
return `${ts} ${prefix} ${label}${detail ? ` ${detail}` : ""}`.trimEnd();
|
|
19409
|
+
}
|
|
19410
|
+
var dim6 = (s) => `\x1B[2m${s}\x1B[0m`, bold6 = (s) => `\x1B[1m${s}\x1B[0m`, cyan4 = (s) => `\x1B[36m${s}\x1B[0m`, yellow5 = (s) => `\x1B[33m${s}\x1B[0m`, red = (s) => `\x1B[31m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, magenta = (s) => `\x1B[35m${s}\x1B[0m`, JOB_COLORS, EVENT_LABELS;
|
|
19411
|
+
var init_format_helpers = __esm(() => {
|
|
19412
|
+
JOB_COLORS = [cyan4, yellow5, magenta, green5, blue, red];
|
|
19413
|
+
EVENT_LABELS = {
|
|
19414
|
+
run_start: "START",
|
|
19415
|
+
meta: "META",
|
|
19416
|
+
thinking: "THINK",
|
|
19417
|
+
tool: "TOOL",
|
|
19418
|
+
text: "TEXT",
|
|
19419
|
+
run_complete: "DONE",
|
|
19420
|
+
done: "DONE",
|
|
19421
|
+
agent_end: "DONE",
|
|
19422
|
+
error: "ERR"
|
|
19423
|
+
};
|
|
19424
|
+
});
|
|
19425
|
+
|
|
18991
19426
|
// src/cli/status.ts
|
|
18992
19427
|
var exports_status = {};
|
|
18993
19428
|
__export(exports_status, {
|
|
18994
19429
|
run: () => run8
|
|
18995
19430
|
});
|
|
18996
|
-
import { spawnSync as
|
|
18997
|
-
import { existsSync as
|
|
18998
|
-
import { join as
|
|
19431
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
19432
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
19433
|
+
import { join as join9 } from "node:path";
|
|
18999
19434
|
function ok2(msg) {
|
|
19000
19435
|
console.log(` ${green5("✓")} ${msg}`);
|
|
19001
19436
|
}
|
|
@@ -19003,7 +19438,7 @@ function warn(msg) {
|
|
|
19003
19438
|
console.log(` ${yellow5("○")} ${msg}`);
|
|
19004
19439
|
}
|
|
19005
19440
|
function fail(msg) {
|
|
19006
|
-
console.log(` ${
|
|
19441
|
+
console.log(` ${red2("✗")} ${msg}`);
|
|
19007
19442
|
}
|
|
19008
19443
|
function info(msg) {
|
|
19009
19444
|
console.log(` ${dim6(msg)}`);
|
|
@@ -19014,7 +19449,7 @@ function section(label) {
|
|
|
19014
19449
|
${bold6(`── ${label} ${line}`)}`);
|
|
19015
19450
|
}
|
|
19016
19451
|
function cmd(bin, args) {
|
|
19017
|
-
const r =
|
|
19452
|
+
const r = spawnSync5(bin, args, {
|
|
19018
19453
|
encoding: "utf8",
|
|
19019
19454
|
stdio: "pipe",
|
|
19020
19455
|
timeout: 5000
|
|
@@ -19022,9 +19457,9 @@ function cmd(bin, args) {
|
|
|
19022
19457
|
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
19023
19458
|
}
|
|
19024
19459
|
function isInstalled(bin) {
|
|
19025
|
-
return
|
|
19460
|
+
return spawnSync5("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
19026
19461
|
}
|
|
19027
|
-
function
|
|
19462
|
+
function formatElapsed2(s) {
|
|
19028
19463
|
if (s.elapsed_s === undefined)
|
|
19029
19464
|
return "...";
|
|
19030
19465
|
const m = Math.floor(s.elapsed_s / 60);
|
|
@@ -19034,11 +19469,11 @@ function formatElapsed(s) {
|
|
|
19034
19469
|
function statusColor(status) {
|
|
19035
19470
|
switch (status) {
|
|
19036
19471
|
case "running":
|
|
19037
|
-
return
|
|
19472
|
+
return cyan5(status);
|
|
19038
19473
|
case "done":
|
|
19039
19474
|
return green5(status);
|
|
19040
19475
|
case "error":
|
|
19041
|
-
return
|
|
19476
|
+
return red2(status);
|
|
19042
19477
|
case "starting":
|
|
19043
19478
|
return yellow5(status);
|
|
19044
19479
|
default:
|
|
@@ -19057,11 +19492,11 @@ async function run8() {
|
|
|
19057
19492
|
`).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
|
|
19058
19493
|
const bdInstalled = isInstalled("bd");
|
|
19059
19494
|
const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
|
|
19060
|
-
const beadsPresent =
|
|
19495
|
+
const beadsPresent = existsSync6(join9(process.cwd(), ".beads"));
|
|
19061
19496
|
const specialistsBin = cmd("which", ["specialists"]);
|
|
19062
|
-
const jobsDir =
|
|
19497
|
+
const jobsDir = join9(process.cwd(), ".specialists", "jobs");
|
|
19063
19498
|
let jobs = [];
|
|
19064
|
-
if (
|
|
19499
|
+
if (existsSync6(jobsDir)) {
|
|
19065
19500
|
const supervisor = new Supervisor({
|
|
19066
19501
|
runner: null,
|
|
19067
19502
|
runOptions: null,
|
|
@@ -19127,7 +19562,7 @@ ${bold6("specialists status")}
|
|
|
19127
19562
|
for (const s of allSpecialists) {
|
|
19128
19563
|
const staleness = stalenessMap[s.name];
|
|
19129
19564
|
if (staleness === "AGED") {
|
|
19130
|
-
warn(`${s.name} ${
|
|
19565
|
+
warn(`${s.name} ${red2("AGED")} ${dim6(s.scope)}`);
|
|
19131
19566
|
} else if (staleness === "STALE") {
|
|
19132
19567
|
warn(`${s.name} ${yellow5("STALE")} ${dim6(s.scope)}`);
|
|
19133
19568
|
}
|
|
@@ -19135,7 +19570,7 @@ ${bold6("specialists status")}
|
|
|
19135
19570
|
}
|
|
19136
19571
|
section("pi (coding agent runtime)");
|
|
19137
19572
|
if (!piInstalled) {
|
|
19138
|
-
fail(`pi not installed —
|
|
19573
|
+
fail(`pi not installed — install ${yellow5("pi")} first`);
|
|
19139
19574
|
} else {
|
|
19140
19575
|
const vStr = piVersion?.ok ? `v${piVersion.stdout}` : "unknown version";
|
|
19141
19576
|
const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim6(`(${[...piProviders].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
|
|
@@ -19143,7 +19578,7 @@ ${bold6("specialists status")}
|
|
|
19143
19578
|
}
|
|
19144
19579
|
section("beads (issue tracker)");
|
|
19145
19580
|
if (!bdInstalled) {
|
|
19146
|
-
fail(`bd not installed —
|
|
19581
|
+
fail(`bd not installed — install ${yellow5("bd")} first`);
|
|
19147
19582
|
} else {
|
|
19148
19583
|
ok2(`bd installed${bdVersion?.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
|
|
19149
19584
|
if (beadsPresent) {
|
|
@@ -19163,17 +19598,18 @@ ${bold6("specialists status")}
|
|
|
19163
19598
|
if (jobs.length > 0) {
|
|
19164
19599
|
section("Active Jobs");
|
|
19165
19600
|
for (const job of jobs) {
|
|
19166
|
-
const elapsed =
|
|
19167
|
-
const detail = job.status === "error" ?
|
|
19601
|
+
const elapsed = formatElapsed2(job);
|
|
19602
|
+
const detail = job.status === "error" ? red2(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
|
|
19168
19603
|
console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
|
|
19169
19604
|
}
|
|
19170
19605
|
}
|
|
19171
19606
|
console.log();
|
|
19172
19607
|
}
|
|
19173
|
-
var
|
|
19608
|
+
var red2 = (s) => `\x1B[31m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
19174
19609
|
var init_status = __esm(() => {
|
|
19175
19610
|
init_loader();
|
|
19176
19611
|
init_supervisor();
|
|
19612
|
+
init_format_helpers();
|
|
19177
19613
|
});
|
|
19178
19614
|
|
|
19179
19615
|
// src/cli/result.ts
|
|
@@ -19181,15 +19617,15 @@ var exports_result = {};
|
|
|
19181
19617
|
__export(exports_result, {
|
|
19182
19618
|
run: () => run9
|
|
19183
19619
|
});
|
|
19184
|
-
import { existsSync as
|
|
19185
|
-
import { join as
|
|
19620
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
|
|
19621
|
+
import { join as join10 } from "node:path";
|
|
19186
19622
|
async function run9() {
|
|
19187
19623
|
const jobId = process.argv[3];
|
|
19188
19624
|
if (!jobId) {
|
|
19189
19625
|
console.error("Usage: specialists result <job-id>");
|
|
19190
19626
|
process.exit(1);
|
|
19191
19627
|
}
|
|
19192
|
-
const jobsDir =
|
|
19628
|
+
const jobsDir = join10(process.cwd(), ".specialists", "jobs");
|
|
19193
19629
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19194
19630
|
const status = supervisor.readStatus(jobId);
|
|
19195
19631
|
if (!status) {
|
|
@@ -19202,107 +19638,340 @@ async function run9() {
|
|
|
19202
19638
|
process.exit(1);
|
|
19203
19639
|
}
|
|
19204
19640
|
if (status.status === "error") {
|
|
19205
|
-
process.stderr.write(`${
|
|
19641
|
+
process.stderr.write(`${red3(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
|
|
19206
19642
|
`);
|
|
19207
19643
|
process.exit(1);
|
|
19208
19644
|
}
|
|
19209
|
-
const resultPath =
|
|
19210
|
-
if (!
|
|
19645
|
+
const resultPath = join10(jobsDir, jobId, "result.txt");
|
|
19646
|
+
if (!existsSync7(resultPath)) {
|
|
19211
19647
|
console.error(`Result file not found for job ${jobId}`);
|
|
19212
19648
|
process.exit(1);
|
|
19213
19649
|
}
|
|
19214
19650
|
process.stdout.write(readFileSync4(resultPath, "utf-8"));
|
|
19215
19651
|
}
|
|
19216
|
-
var dim7 = (s) => `\x1B[2m${s}\x1B[0m`,
|
|
19652
|
+
var dim7 = (s) => `\x1B[2m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
19217
19653
|
var init_result = __esm(() => {
|
|
19218
19654
|
init_supervisor();
|
|
19219
19655
|
});
|
|
19220
19656
|
|
|
19657
|
+
// src/specialist/timeline-query.ts
|
|
19658
|
+
import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
19659
|
+
import { join as join11 } from "node:path";
|
|
19660
|
+
function readJobEvents(jobDir) {
|
|
19661
|
+
const eventsPath = join11(jobDir, "events.jsonl");
|
|
19662
|
+
if (!existsSync8(eventsPath))
|
|
19663
|
+
return [];
|
|
19664
|
+
const content = readFileSync5(eventsPath, "utf-8");
|
|
19665
|
+
const lines = content.split(`
|
|
19666
|
+
`).filter(Boolean);
|
|
19667
|
+
const events = [];
|
|
19668
|
+
for (const line of lines) {
|
|
19669
|
+
const event = parseTimelineEvent(line);
|
|
19670
|
+
if (event)
|
|
19671
|
+
events.push(event);
|
|
19672
|
+
}
|
|
19673
|
+
events.sort(compareTimelineEvents);
|
|
19674
|
+
return events;
|
|
19675
|
+
}
|
|
19676
|
+
function readAllJobEvents(jobsDir) {
|
|
19677
|
+
if (!existsSync8(jobsDir))
|
|
19678
|
+
return [];
|
|
19679
|
+
const batches = [];
|
|
19680
|
+
const entries = readdirSync2(jobsDir);
|
|
19681
|
+
for (const entry of entries) {
|
|
19682
|
+
const jobDir = join11(jobsDir, entry);
|
|
19683
|
+
try {
|
|
19684
|
+
const stat2 = __require("node:fs").statSync(jobDir);
|
|
19685
|
+
if (!stat2.isDirectory())
|
|
19686
|
+
continue;
|
|
19687
|
+
} catch {
|
|
19688
|
+
continue;
|
|
19689
|
+
}
|
|
19690
|
+
const jobId = entry;
|
|
19691
|
+
const statusPath = join11(jobDir, "status.json");
|
|
19692
|
+
let specialist = "unknown";
|
|
19693
|
+
let beadId;
|
|
19694
|
+
if (existsSync8(statusPath)) {
|
|
19695
|
+
try {
|
|
19696
|
+
const status = JSON.parse(readFileSync5(statusPath, "utf-8"));
|
|
19697
|
+
specialist = status.specialist ?? "unknown";
|
|
19698
|
+
beadId = status.bead_id;
|
|
19699
|
+
} catch {}
|
|
19700
|
+
}
|
|
19701
|
+
const events = readJobEvents(jobDir);
|
|
19702
|
+
if (events.length > 0) {
|
|
19703
|
+
batches.push({ jobId, specialist, beadId, events });
|
|
19704
|
+
}
|
|
19705
|
+
}
|
|
19706
|
+
return batches;
|
|
19707
|
+
}
|
|
19708
|
+
function mergeTimelineEvents(batches) {
|
|
19709
|
+
const merged = [];
|
|
19710
|
+
for (const batch of batches) {
|
|
19711
|
+
for (const event of batch.events) {
|
|
19712
|
+
merged.push({
|
|
19713
|
+
jobId: batch.jobId,
|
|
19714
|
+
specialist: batch.specialist,
|
|
19715
|
+
beadId: batch.beadId,
|
|
19716
|
+
event
|
|
19717
|
+
});
|
|
19718
|
+
}
|
|
19719
|
+
}
|
|
19720
|
+
merged.sort((a, b) => compareTimelineEvents(a.event, b.event));
|
|
19721
|
+
return merged;
|
|
19722
|
+
}
|
|
19723
|
+
function filterTimelineEvents(merged, filter) {
|
|
19724
|
+
let result = merged;
|
|
19725
|
+
if (filter.since !== undefined) {
|
|
19726
|
+
result = result.filter(({ event }) => event.t >= filter.since);
|
|
19727
|
+
}
|
|
19728
|
+
if (filter.jobId !== undefined) {
|
|
19729
|
+
result = result.filter(({ jobId }) => jobId === filter.jobId);
|
|
19730
|
+
}
|
|
19731
|
+
if (filter.specialist !== undefined) {
|
|
19732
|
+
result = result.filter(({ specialist }) => specialist === filter.specialist);
|
|
19733
|
+
}
|
|
19734
|
+
if (filter.limit !== undefined && filter.limit > 0) {
|
|
19735
|
+
result = result.slice(0, filter.limit);
|
|
19736
|
+
}
|
|
19737
|
+
return result;
|
|
19738
|
+
}
|
|
19739
|
+
function queryTimeline(jobsDir, filter = {}) {
|
|
19740
|
+
let batches = readAllJobEvents(jobsDir);
|
|
19741
|
+
if (filter.jobId !== undefined) {
|
|
19742
|
+
batches = batches.filter((b) => b.jobId === filter.jobId);
|
|
19743
|
+
}
|
|
19744
|
+
if (filter.specialist !== undefined) {
|
|
19745
|
+
batches = batches.filter((b) => b.specialist === filter.specialist);
|
|
19746
|
+
}
|
|
19747
|
+
const merged = mergeTimelineEvents(batches);
|
|
19748
|
+
return filterTimelineEvents(merged, filter);
|
|
19749
|
+
}
|
|
19750
|
+
var init_timeline_query = __esm(() => {
|
|
19751
|
+
init_timeline_events();
|
|
19752
|
+
});
|
|
19753
|
+
|
|
19221
19754
|
// src/cli/feed.ts
|
|
19222
19755
|
var exports_feed = {};
|
|
19223
19756
|
__export(exports_feed, {
|
|
19224
19757
|
run: () => run10
|
|
19225
19758
|
});
|
|
19226
|
-
import { existsSync as
|
|
19227
|
-
import { join as
|
|
19228
|
-
function
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19759
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
19760
|
+
import { join as join12 } from "node:path";
|
|
19761
|
+
function getHumanEventKey(event) {
|
|
19762
|
+
switch (event.type) {
|
|
19763
|
+
case "meta":
|
|
19764
|
+
return `meta:${event.backend}:${event.model}`;
|
|
19765
|
+
case "tool":
|
|
19766
|
+
return `tool:${event.tool}:${event.phase}:${event.is_error ? "error" : "ok"}`;
|
|
19767
|
+
case "text":
|
|
19768
|
+
return "text";
|
|
19769
|
+
case "thinking":
|
|
19770
|
+
return "thinking";
|
|
19771
|
+
case "run_start":
|
|
19772
|
+
return `run_start:${event.specialist}:${event.bead_id ?? ""}`;
|
|
19773
|
+
case "run_complete":
|
|
19774
|
+
return `run_complete:${event.status}:${event.error ?? ""}`;
|
|
19775
|
+
case "done":
|
|
19776
|
+
case "agent_end":
|
|
19777
|
+
return `complete:${event.type}`;
|
|
19778
|
+
default:
|
|
19779
|
+
return event.type;
|
|
19237
19780
|
}
|
|
19238
19781
|
}
|
|
19239
|
-
function
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19782
|
+
function shouldSkipHumanEvent(event, jobId, lastPrintedEventKey, seenMetaKey) {
|
|
19783
|
+
if (event.type === "meta") {
|
|
19784
|
+
const metaKey = `${event.backend}:${event.model}`;
|
|
19785
|
+
if (seenMetaKey.get(jobId) === metaKey)
|
|
19786
|
+
return true;
|
|
19787
|
+
seenMetaKey.set(jobId, metaKey);
|
|
19788
|
+
}
|
|
19789
|
+
const key = getHumanEventKey(event);
|
|
19790
|
+
if (lastPrintedEventKey.get(jobId) === key)
|
|
19791
|
+
return true;
|
|
19792
|
+
lastPrintedEventKey.set(jobId, key);
|
|
19793
|
+
return false;
|
|
19794
|
+
}
|
|
19795
|
+
function parseSince(value) {
|
|
19796
|
+
if (value.includes("T") || value.includes("-")) {
|
|
19797
|
+
return new Date(value).getTime();
|
|
19798
|
+
}
|
|
19799
|
+
const match = value.match(/^(\d+)([smhd])$/);
|
|
19800
|
+
if (match) {
|
|
19801
|
+
const num = parseInt(match[1], 10);
|
|
19802
|
+
const unit = match[2];
|
|
19803
|
+
const multipliers = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
19804
|
+
return Date.now() - num * multipliers[unit];
|
|
19244
19805
|
}
|
|
19245
|
-
return
|
|
19806
|
+
return;
|
|
19246
19807
|
}
|
|
19247
|
-
|
|
19248
|
-
const argv = process.argv.slice(3);
|
|
19808
|
+
function parseArgs5(argv) {
|
|
19249
19809
|
let jobId;
|
|
19810
|
+
let specialist;
|
|
19811
|
+
let since;
|
|
19812
|
+
let limit = 100;
|
|
19250
19813
|
let follow = false;
|
|
19814
|
+
let forever = false;
|
|
19815
|
+
let json = false;
|
|
19251
19816
|
for (let i = 0;i < argv.length; i++) {
|
|
19252
19817
|
if (argv[i] === "--job" && argv[i + 1]) {
|
|
19253
19818
|
jobId = argv[++i];
|
|
19254
19819
|
continue;
|
|
19255
19820
|
}
|
|
19821
|
+
if (argv[i] === "--specialist" && argv[i + 1]) {
|
|
19822
|
+
specialist = argv[++i];
|
|
19823
|
+
continue;
|
|
19824
|
+
}
|
|
19825
|
+
if (argv[i] === "--since" && argv[i + 1]) {
|
|
19826
|
+
since = parseSince(argv[++i]);
|
|
19827
|
+
continue;
|
|
19828
|
+
}
|
|
19829
|
+
if (argv[i] === "--limit" && argv[i + 1]) {
|
|
19830
|
+
limit = parseInt(argv[++i], 10);
|
|
19831
|
+
continue;
|
|
19832
|
+
}
|
|
19256
19833
|
if (argv[i] === "--follow" || argv[i] === "-f") {
|
|
19257
19834
|
follow = true;
|
|
19258
19835
|
continue;
|
|
19259
19836
|
}
|
|
19837
|
+
if (argv[i] === "--forever") {
|
|
19838
|
+
forever = true;
|
|
19839
|
+
continue;
|
|
19840
|
+
}
|
|
19841
|
+
if (argv[i] === "--json") {
|
|
19842
|
+
json = true;
|
|
19843
|
+
continue;
|
|
19844
|
+
}
|
|
19260
19845
|
if (!jobId && !argv[i].startsWith("--"))
|
|
19261
19846
|
jobId = argv[i];
|
|
19262
19847
|
}
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19848
|
+
return { jobId, specialist, since, limit, follow, forever, json };
|
|
19849
|
+
}
|
|
19850
|
+
function printSnapshot(merged, options) {
|
|
19851
|
+
if (merged.length === 0) {
|
|
19852
|
+
if (!options.json)
|
|
19853
|
+
console.log(dim6("No events found."));
|
|
19854
|
+
return;
|
|
19266
19855
|
}
|
|
19267
|
-
const
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
if (!supervisor.readStatus(jobId)) {
|
|
19272
|
-
console.error(`No job found: ${jobId}`);
|
|
19273
|
-
process.exit(1);
|
|
19856
|
+
const colorMap = new JobColorMap;
|
|
19857
|
+
if (options.json) {
|
|
19858
|
+
for (const { jobId, specialist, beadId, event } of merged) {
|
|
19859
|
+
console.log(JSON.stringify({ jobId, specialist, beadId, ...event }));
|
|
19274
19860
|
}
|
|
19275
|
-
console.log(dim8("No events yet."));
|
|
19276
19861
|
return;
|
|
19277
19862
|
}
|
|
19278
|
-
const
|
|
19279
|
-
|
|
19280
|
-
|
|
19863
|
+
const lastPrintedEventKey = new Map;
|
|
19864
|
+
const seenMetaKey = new Map;
|
|
19865
|
+
for (const { jobId, specialist, beadId, event } of merged) {
|
|
19866
|
+
if (shouldSkipHumanEvent(event, jobId, lastPrintedEventKey, seenMetaKey))
|
|
19867
|
+
continue;
|
|
19868
|
+
const colorize = colorMap.get(jobId);
|
|
19869
|
+
console.log(formatEventLine(event, { jobId, specialist, beadId, colorize }));
|
|
19870
|
+
}
|
|
19871
|
+
}
|
|
19872
|
+
function isCompletionEvent(event) {
|
|
19873
|
+
return isRunCompleteEvent(event) || event.type === "done" || event.type === "agent_end";
|
|
19874
|
+
}
|
|
19875
|
+
async function followMerged(jobsDir, options) {
|
|
19876
|
+
const colorMap = new JobColorMap;
|
|
19877
|
+
const lastSeenT = new Map;
|
|
19878
|
+
const completedJobs = new Set;
|
|
19879
|
+
const filteredBatches = () => readAllJobEvents(jobsDir).filter((batch) => !options.jobId || batch.jobId === options.jobId).filter((batch) => !options.specialist || batch.specialist === options.specialist);
|
|
19880
|
+
const initial = queryTimeline(jobsDir, {
|
|
19881
|
+
jobId: options.jobId,
|
|
19882
|
+
specialist: options.specialist,
|
|
19883
|
+
since: options.since,
|
|
19884
|
+
limit: options.limit
|
|
19885
|
+
});
|
|
19886
|
+
printSnapshot(initial, { ...options, json: options.json });
|
|
19887
|
+
for (const batch of filteredBatches()) {
|
|
19888
|
+
if (batch.events.length > 0) {
|
|
19889
|
+
const maxT = Math.max(...batch.events.map((event) => event.t));
|
|
19890
|
+
lastSeenT.set(batch.jobId, maxT);
|
|
19891
|
+
}
|
|
19892
|
+
if (batch.events.some(isCompletionEvent)) {
|
|
19893
|
+
completedJobs.add(batch.jobId);
|
|
19894
|
+
}
|
|
19895
|
+
}
|
|
19896
|
+
const initialBatchCount = filteredBatches().length;
|
|
19897
|
+
if (!options.forever && initialBatchCount > 0 && completedJobs.size === initialBatchCount) {
|
|
19898
|
+
if (!options.json) {
|
|
19899
|
+
process.stderr.write(dim6(`All jobs complete.
|
|
19900
|
+
`));
|
|
19901
|
+
}
|
|
19281
19902
|
return;
|
|
19282
|
-
|
|
19903
|
+
}
|
|
19904
|
+
if (!options.json) {
|
|
19905
|
+
process.stderr.write(dim6(`Following... (Ctrl+C to stop)
|
|
19283
19906
|
`));
|
|
19907
|
+
}
|
|
19908
|
+
const lastPrintedEventKey = new Map;
|
|
19909
|
+
const seenMetaKey = new Map;
|
|
19284
19910
|
await new Promise((resolve) => {
|
|
19285
|
-
|
|
19286
|
-
|
|
19287
|
-
|
|
19288
|
-
|
|
19289
|
-
const
|
|
19290
|
-
const
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19911
|
+
const interval = setInterval(() => {
|
|
19912
|
+
const batches = filteredBatches();
|
|
19913
|
+
const newEvents = [];
|
|
19914
|
+
for (const batch of batches) {
|
|
19915
|
+
const lastT = lastSeenT.get(batch.jobId) ?? 0;
|
|
19916
|
+
for (const event of batch.events) {
|
|
19917
|
+
if (event.t > lastT) {
|
|
19918
|
+
newEvents.push({
|
|
19919
|
+
jobId: batch.jobId,
|
|
19920
|
+
specialist: batch.specialist,
|
|
19921
|
+
beadId: batch.beadId,
|
|
19922
|
+
event
|
|
19923
|
+
});
|
|
19924
|
+
}
|
|
19298
19925
|
}
|
|
19299
|
-
|
|
19300
|
-
|
|
19926
|
+
if (batch.events.length > 0) {
|
|
19927
|
+
const maxT = Math.max(...batch.events.map((e) => e.t));
|
|
19928
|
+
lastSeenT.set(batch.jobId, maxT);
|
|
19929
|
+
}
|
|
19930
|
+
if (batch.events.some(isCompletionEvent)) {
|
|
19931
|
+
completedJobs.add(batch.jobId);
|
|
19932
|
+
}
|
|
19933
|
+
}
|
|
19934
|
+
newEvents.sort((a, b) => a.event.t - b.event.t);
|
|
19935
|
+
for (const { jobId, specialist, beadId, event } of newEvents) {
|
|
19936
|
+
if (options.json) {
|
|
19937
|
+
console.log(JSON.stringify({ jobId, specialist, beadId, ...event }));
|
|
19938
|
+
} else {
|
|
19939
|
+
if (shouldSkipHumanEvent(event, jobId, lastPrintedEventKey, seenMetaKey))
|
|
19940
|
+
continue;
|
|
19941
|
+
const colorize = colorMap.get(jobId);
|
|
19942
|
+
console.log(formatEventLine(event, { jobId, specialist, beadId, colorize }));
|
|
19943
|
+
}
|
|
19944
|
+
}
|
|
19945
|
+
if (!options.forever && batches.length > 0 && completedJobs.size === batches.length) {
|
|
19946
|
+
clearInterval(interval);
|
|
19947
|
+
resolve();
|
|
19948
|
+
}
|
|
19949
|
+
}, 500);
|
|
19950
|
+
});
|
|
19951
|
+
}
|
|
19952
|
+
async function run10() {
|
|
19953
|
+
const options = parseArgs5(process.argv.slice(3));
|
|
19954
|
+
const jobsDir = join12(process.cwd(), ".specialists", "jobs");
|
|
19955
|
+
if (!existsSync9(jobsDir)) {
|
|
19956
|
+
console.log(dim6("No jobs directory found."));
|
|
19957
|
+
return;
|
|
19958
|
+
}
|
|
19959
|
+
if (options.follow) {
|
|
19960
|
+
await followMerged(jobsDir, options);
|
|
19961
|
+
return;
|
|
19962
|
+
}
|
|
19963
|
+
const merged = queryTimeline(jobsDir, {
|
|
19964
|
+
jobId: options.jobId,
|
|
19965
|
+
specialist: options.specialist,
|
|
19966
|
+
since: options.since,
|
|
19967
|
+
limit: options.limit
|
|
19301
19968
|
});
|
|
19969
|
+
printSnapshot(merged, options);
|
|
19302
19970
|
}
|
|
19303
|
-
var dim8 = (s) => `\x1B[2m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
19304
19971
|
var init_feed = __esm(() => {
|
|
19305
|
-
|
|
19972
|
+
init_timeline_events();
|
|
19973
|
+
init_timeline_query();
|
|
19974
|
+
init_format_helpers();
|
|
19306
19975
|
});
|
|
19307
19976
|
|
|
19308
19977
|
// src/cli/stop.ts
|
|
@@ -19310,14 +19979,14 @@ var exports_stop = {};
|
|
|
19310
19979
|
__export(exports_stop, {
|
|
19311
19980
|
run: () => run11
|
|
19312
19981
|
});
|
|
19313
|
-
import { join as
|
|
19982
|
+
import { join as join13 } from "node:path";
|
|
19314
19983
|
async function run11() {
|
|
19315
19984
|
const jobId = process.argv[3];
|
|
19316
19985
|
if (!jobId) {
|
|
19317
19986
|
console.error("Usage: specialists stop <job-id>");
|
|
19318
19987
|
process.exit(1);
|
|
19319
19988
|
}
|
|
19320
|
-
const jobsDir =
|
|
19989
|
+
const jobsDir = join13(process.cwd(), ".specialists", "jobs");
|
|
19321
19990
|
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19322
19991
|
const status = supervisor.readStatus(jobId);
|
|
19323
19992
|
if (!status) {
|
|
@@ -19325,31 +19994,31 @@ async function run11() {
|
|
|
19325
19994
|
process.exit(1);
|
|
19326
19995
|
}
|
|
19327
19996
|
if (status.status === "done" || status.status === "error") {
|
|
19328
|
-
process.stderr.write(`${
|
|
19997
|
+
process.stderr.write(`${dim8(`Job ${jobId} is already ${status.status}.`)}
|
|
19329
19998
|
`);
|
|
19330
19999
|
return;
|
|
19331
20000
|
}
|
|
19332
20001
|
if (!status.pid) {
|
|
19333
|
-
process.stderr.write(`${
|
|
20002
|
+
process.stderr.write(`${red5(`No PID recorded for job ${jobId}.`)}
|
|
19334
20003
|
`);
|
|
19335
20004
|
process.exit(1);
|
|
19336
20005
|
}
|
|
19337
20006
|
try {
|
|
19338
20007
|
process.kill(status.pid, "SIGTERM");
|
|
19339
|
-
process.stdout.write(`${
|
|
20008
|
+
process.stdout.write(`${green7("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
|
|
19340
20009
|
`);
|
|
19341
20010
|
} catch (err) {
|
|
19342
20011
|
if (err.code === "ESRCH") {
|
|
19343
|
-
process.stderr.write(`${
|
|
20012
|
+
process.stderr.write(`${red5(`Process ${status.pid} not found.`)} Job may have already completed.
|
|
19344
20013
|
`);
|
|
19345
20014
|
} else {
|
|
19346
|
-
process.stderr.write(`${
|
|
20015
|
+
process.stderr.write(`${red5("Error:")} ${err.message}
|
|
19347
20016
|
`);
|
|
19348
20017
|
process.exit(1);
|
|
19349
20018
|
}
|
|
19350
20019
|
}
|
|
19351
20020
|
}
|
|
19352
|
-
var
|
|
20021
|
+
var green7 = (s) => `\x1B[32m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`, dim8 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
19353
20022
|
var init_stop = __esm(() => {
|
|
19354
20023
|
init_supervisor();
|
|
19355
20024
|
});
|
|
@@ -19362,27 +20031,27 @@ __export(exports_quickstart, {
|
|
|
19362
20031
|
function section2(title) {
|
|
19363
20032
|
const bar = "─".repeat(60);
|
|
19364
20033
|
return `
|
|
19365
|
-
${bold7(
|
|
19366
|
-
${
|
|
20034
|
+
${bold7(cyan7(title))}
|
|
20035
|
+
${dim9(bar)}`;
|
|
19367
20036
|
}
|
|
19368
20037
|
function cmd2(s) {
|
|
19369
|
-
return
|
|
20038
|
+
return yellow6(s);
|
|
19370
20039
|
}
|
|
19371
20040
|
function flag(s) {
|
|
19372
|
-
return
|
|
20041
|
+
return green8(s);
|
|
19373
20042
|
}
|
|
19374
20043
|
async function run12() {
|
|
19375
20044
|
const lines = [
|
|
19376
20045
|
"",
|
|
19377
20046
|
bold7("specialists · Quick Start Guide"),
|
|
19378
|
-
|
|
20047
|
+
dim9("One MCP server. Multiple AI backends. Intelligent orchestration."),
|
|
19379
20048
|
""
|
|
19380
20049
|
];
|
|
19381
20050
|
lines.push(section2("1. Installation"));
|
|
19382
20051
|
lines.push("");
|
|
19383
20052
|
lines.push(` ${cmd2("npm install -g @jaggerxtrm/specialists")} # install globally`);
|
|
19384
|
-
lines.push(` ${cmd2("specialists install")} #
|
|
19385
|
-
lines.push(` ${
|
|
20053
|
+
lines.push(` ${cmd2("specialists install")} # project setup:`);
|
|
20054
|
+
lines.push(` ${dim9(" # checks pi · bd · xt, then wires MCP + hooks")}`);
|
|
19386
20055
|
lines.push("");
|
|
19387
20056
|
lines.push(` Verify everything is healthy:`);
|
|
19388
20057
|
lines.push(` ${cmd2("specialists status")} # shows pi, beads, MCP, active jobs`);
|
|
@@ -19393,9 +20062,9 @@ async function run12() {
|
|
|
19393
20062
|
lines.push(` ${cmd2("specialists init")} # creates specialists/, .specialists/, AGENTS.md`);
|
|
19394
20063
|
lines.push("");
|
|
19395
20064
|
lines.push(` What this creates:`);
|
|
19396
|
-
lines.push(` ${
|
|
19397
|
-
lines.push(` ${
|
|
19398
|
-
lines.push(` ${
|
|
20065
|
+
lines.push(` ${dim9("specialists/")} — put your .specialist.yaml files here`);
|
|
20066
|
+
lines.push(` ${dim9(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
|
|
20067
|
+
lines.push(` ${dim9("AGENTS.md")} — context block injected into Claude sessions`);
|
|
19399
20068
|
lines.push("");
|
|
19400
20069
|
lines.push(section2("3. Discover Specialists"));
|
|
19401
20070
|
lines.push("");
|
|
@@ -19406,24 +20075,24 @@ async function run12() {
|
|
|
19406
20075
|
lines.push(` ${cmd2("specialists list")} ${flag("--json")} # machine-readable JSON`);
|
|
19407
20076
|
lines.push("");
|
|
19408
20077
|
lines.push(` Scopes (searched in order):`);
|
|
19409
|
-
lines.push(` ${
|
|
19410
|
-
lines.push(` ${
|
|
19411
|
-
lines.push(` ${
|
|
20078
|
+
lines.push(` ${blue2("project")} ./specialists/*.specialist.yaml`);
|
|
20079
|
+
lines.push(` ${blue2("user")} ~/.specialists/*.specialist.yaml`);
|
|
20080
|
+
lines.push(` ${blue2("system")} bundled specialists (shipped with the package)`);
|
|
19412
20081
|
lines.push("");
|
|
19413
20082
|
lines.push(section2("4. Running a Specialist"));
|
|
19414
20083
|
lines.push("");
|
|
19415
20084
|
lines.push(` ${bold7("Foreground")} (streams output to stdout):`);
|
|
19416
|
-
lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${
|
|
20085
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"Review src/api.ts for security issues"')}`);
|
|
19417
20086
|
lines.push("");
|
|
19418
20087
|
lines.push(` ${bold7("Background")} (returns a job ID immediately):`);
|
|
19419
|
-
lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${
|
|
19420
|
-
lines.push(` ${
|
|
20088
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"..."')} ${flag("--background")}`);
|
|
20089
|
+
lines.push(` ${dim9(" # → Job started: job_a1b2c3d4")}`);
|
|
19421
20090
|
lines.push("");
|
|
19422
20091
|
lines.push(` Override model for one run:`);
|
|
19423
|
-
lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${
|
|
20092
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim9("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim9('"..."')}`);
|
|
19424
20093
|
lines.push("");
|
|
19425
20094
|
lines.push(` Run without beads issue tracking:`);
|
|
19426
|
-
lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${
|
|
20095
|
+
lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim9('"..."')}`);
|
|
19427
20096
|
lines.push("");
|
|
19428
20097
|
lines.push(` Pipe a prompt from stdin:`);
|
|
19429
20098
|
lines.push(` ${cmd2("cat my-brief.md | specialists run code-review")}`);
|
|
@@ -19440,22 +20109,22 @@ async function run12() {
|
|
|
19440
20109
|
lines.push(` ${bold7("Cancel a job")}:`);
|
|
19441
20110
|
lines.push(` ${cmd2("specialists stop job_a1b2c3d4")} # sends SIGTERM to the agent process`);
|
|
19442
20111
|
lines.push("");
|
|
19443
|
-
lines.push(` ${bold7("Job files")} in ${
|
|
19444
|
-
lines.push(` ${
|
|
19445
|
-
lines.push(` ${
|
|
19446
|
-
lines.push(` ${
|
|
20112
|
+
lines.push(` ${bold7("Job files")} in ${dim9(".specialists/jobs/<job-id>/")}:`);
|
|
20113
|
+
lines.push(` ${dim9("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
|
|
20114
|
+
lines.push(` ${dim9("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
|
|
20115
|
+
lines.push(` ${dim9("result.txt")} — final output (written when status=done)`);
|
|
19447
20116
|
lines.push("");
|
|
19448
20117
|
lines.push(section2("6. Editing Specialists"));
|
|
19449
20118
|
lines.push("");
|
|
19450
20119
|
lines.push(` Change a field without opening the YAML manually:`);
|
|
19451
|
-
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${
|
|
19452
|
-
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${
|
|
19453
|
-
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${
|
|
19454
|
-
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${
|
|
19455
|
-
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${
|
|
20120
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("anthropic/claude-sonnet-4-6")}`);
|
|
20121
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim9('"Updated description"')}`);
|
|
20122
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim9("120000")}`);
|
|
20123
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim9("HIGH")}`);
|
|
20124
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim9("analysis,security,review")}`);
|
|
19456
20125
|
lines.push("");
|
|
19457
20126
|
lines.push(` Preview without writing:`);
|
|
19458
|
-
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${
|
|
20127
|
+
lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("...")} ${flag("--dry-run")}`);
|
|
19459
20128
|
lines.push("");
|
|
19460
20129
|
lines.push(section2("7. .specialist.yaml Schema"));
|
|
19461
20130
|
lines.push("");
|
|
@@ -19502,21 +20171,21 @@ async function run12() {
|
|
|
19502
20171
|
" priority: 2 # 0=critical … 4=backlog"
|
|
19503
20172
|
];
|
|
19504
20173
|
for (const l of schemaLines) {
|
|
19505
|
-
lines.push(` ${
|
|
20174
|
+
lines.push(` ${dim9(l)}`);
|
|
19506
20175
|
}
|
|
19507
20176
|
lines.push("");
|
|
19508
20177
|
lines.push(section2("8. Hook System"));
|
|
19509
20178
|
lines.push("");
|
|
19510
|
-
lines.push(` Specialists emits lifecycle events to ${
|
|
20179
|
+
lines.push(` Specialists emits lifecycle events to ${dim9(".specialists/trace.jsonl")}:`);
|
|
19511
20180
|
lines.push("");
|
|
19512
20181
|
lines.push(` ${bold7("Hook point")} ${bold7("When fired")}`);
|
|
19513
|
-
lines.push(` ${
|
|
19514
|
-
lines.push(` ${
|
|
19515
|
-
lines.push(` ${
|
|
19516
|
-
lines.push(` ${
|
|
20182
|
+
lines.push(` ${yellow6("specialist:start")} before the agent session begins`);
|
|
20183
|
+
lines.push(` ${yellow6("specialist:token")} on each streamed token (delta)`);
|
|
20184
|
+
lines.push(` ${yellow6("specialist:done")} after successful completion`);
|
|
20185
|
+
lines.push(` ${yellow6("specialist:error")} on failure or timeout`);
|
|
19517
20186
|
lines.push("");
|
|
19518
20187
|
lines.push(` Each event line in trace.jsonl:`);
|
|
19519
|
-
lines.push(` ${
|
|
20188
|
+
lines.push(` ${dim9('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
|
|
19520
20189
|
lines.push("");
|
|
19521
20190
|
lines.push(` Tail the trace file to observe all activity:`);
|
|
19522
20191
|
lines.push(` ${cmd2("tail -f .specialists/trace.jsonl | jq .")}`);
|
|
@@ -19547,38 +20216,37 @@ async function run12() {
|
|
|
19547
20216
|
lines.push(` ${bold7("Override model for a single run:")}`);
|
|
19548
20217
|
lines.push(` ${cmd2('specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."')}`);
|
|
19549
20218
|
lines.push("");
|
|
19550
|
-
lines.push(
|
|
19551
|
-
lines.push(` ${
|
|
19552
|
-
lines.push(` ${
|
|
20219
|
+
lines.push(dim9("─".repeat(62)));
|
|
20220
|
+
lines.push(` ${dim9("specialists help")} command list ${dim9("specialists <cmd> --help")} per-command flags`);
|
|
20221
|
+
lines.push(` ${dim9("specialists status")} health check ${dim9("specialists models")} available models`);
|
|
19553
20222
|
lines.push("");
|
|
19554
20223
|
console.log(lines.join(`
|
|
19555
20224
|
`));
|
|
19556
20225
|
}
|
|
19557
|
-
var bold7 = (s) => `\x1B[1m${s}\x1B[0m`,
|
|
20226
|
+
var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim9 = (s) => `\x1B[2m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, cyan7 = (s) => `\x1B[36m${s}\x1B[0m`, blue2 = (s) => `\x1B[34m${s}\x1B[0m`, green8 = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
19558
20227
|
|
|
19559
20228
|
// src/cli/doctor.ts
|
|
19560
20229
|
var exports_doctor = {};
|
|
19561
20230
|
__export(exports_doctor, {
|
|
19562
20231
|
run: () => run13
|
|
19563
20232
|
});
|
|
19564
|
-
import { spawnSync as
|
|
19565
|
-
import { existsSync as
|
|
19566
|
-
import {
|
|
19567
|
-
import { join as join12 } from "node:path";
|
|
20233
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
20234
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
20235
|
+
import { join as join14 } from "node:path";
|
|
19568
20236
|
function ok3(msg) {
|
|
19569
|
-
console.log(` ${
|
|
20237
|
+
console.log(` ${green9("✓")} ${msg}`);
|
|
19570
20238
|
}
|
|
19571
20239
|
function warn2(msg) {
|
|
19572
|
-
console.log(` ${
|
|
20240
|
+
console.log(` ${yellow7("○")} ${msg}`);
|
|
19573
20241
|
}
|
|
19574
20242
|
function fail2(msg) {
|
|
19575
|
-
console.log(` ${
|
|
20243
|
+
console.log(` ${red6("✗")} ${msg}`);
|
|
19576
20244
|
}
|
|
19577
20245
|
function fix(msg) {
|
|
19578
|
-
console.log(` ${
|
|
20246
|
+
console.log(` ${dim10("→ fix:")} ${yellow7(msg)}`);
|
|
19579
20247
|
}
|
|
19580
20248
|
function hint(msg) {
|
|
19581
|
-
console.log(` ${
|
|
20249
|
+
console.log(` ${dim10(msg)}`);
|
|
19582
20250
|
}
|
|
19583
20251
|
function section3(label) {
|
|
19584
20252
|
const line = "─".repeat(Math.max(0, 38 - label.length));
|
|
@@ -19586,17 +20254,26 @@ function section3(label) {
|
|
|
19586
20254
|
${bold8(`── ${label} ${line}`)}`);
|
|
19587
20255
|
}
|
|
19588
20256
|
function sp(bin, args) {
|
|
19589
|
-
const r =
|
|
20257
|
+
const r = spawnSync6(bin, args, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
|
|
19590
20258
|
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
19591
20259
|
}
|
|
19592
20260
|
function isInstalled2(bin) {
|
|
19593
|
-
return
|
|
20261
|
+
return spawnSync6("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
20262
|
+
}
|
|
20263
|
+
function loadJson2(path) {
|
|
20264
|
+
if (!existsSync10(path))
|
|
20265
|
+
return null;
|
|
20266
|
+
try {
|
|
20267
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
20268
|
+
} catch {
|
|
20269
|
+
return null;
|
|
20270
|
+
}
|
|
19594
20271
|
}
|
|
19595
20272
|
function checkPi() {
|
|
19596
20273
|
section3("pi (coding agent runtime)");
|
|
19597
20274
|
if (!isInstalled2("pi")) {
|
|
19598
20275
|
fail2("pi not installed");
|
|
19599
|
-
fix("
|
|
20276
|
+
fix("install pi first");
|
|
19600
20277
|
return false;
|
|
19601
20278
|
}
|
|
19602
20279
|
const version2 = sp("pi", ["--version"]);
|
|
@@ -19609,75 +20286,95 @@ function checkPi() {
|
|
|
19609
20286
|
fix("pi config (add at least one API key)");
|
|
19610
20287
|
return false;
|
|
19611
20288
|
}
|
|
19612
|
-
ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${
|
|
20289
|
+
ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim10(`(${[...providers].join(", ")})`)}`);
|
|
20290
|
+
return true;
|
|
20291
|
+
}
|
|
20292
|
+
function checkBd() {
|
|
20293
|
+
section3("beads (issue tracker)");
|
|
20294
|
+
if (!isInstalled2("bd")) {
|
|
20295
|
+
fail2("bd not installed");
|
|
20296
|
+
fix("install beads (bd) first");
|
|
20297
|
+
return false;
|
|
20298
|
+
}
|
|
20299
|
+
ok3(`bd installed ${dim10(sp("bd", ["--version"]).stdout || "")}`);
|
|
20300
|
+
if (existsSync10(join14(CWD, ".beads")))
|
|
20301
|
+
ok3(".beads/ present in project");
|
|
20302
|
+
else
|
|
20303
|
+
warn2(".beads/ not found in project");
|
|
20304
|
+
return true;
|
|
20305
|
+
}
|
|
20306
|
+
function checkXt() {
|
|
20307
|
+
section3("xtrm-tools");
|
|
20308
|
+
if (!isInstalled2("xt")) {
|
|
20309
|
+
fail2("xt not installed");
|
|
20310
|
+
fix("install xtrm-tools first");
|
|
20311
|
+
return false;
|
|
20312
|
+
}
|
|
20313
|
+
ok3(`xt installed ${dim10(sp("xt", ["--version"]).stdout || "")}`);
|
|
19613
20314
|
return true;
|
|
19614
20315
|
}
|
|
19615
20316
|
function checkHooks() {
|
|
19616
|
-
section3("Claude Code hooks (
|
|
20317
|
+
section3("Claude Code hooks (2 expected)");
|
|
19617
20318
|
let allPresent = true;
|
|
19618
20319
|
for (const name of HOOK_NAMES) {
|
|
19619
|
-
const dest =
|
|
19620
|
-
if (!
|
|
19621
|
-
fail2(`${name} ${
|
|
19622
|
-
fix("specialists install
|
|
20320
|
+
const dest = join14(HOOKS_DIR, name);
|
|
20321
|
+
if (!existsSync10(dest)) {
|
|
20322
|
+
fail2(`${name} ${red6("missing")}`);
|
|
20323
|
+
fix("specialists install");
|
|
19623
20324
|
allPresent = false;
|
|
19624
20325
|
} else {
|
|
19625
20326
|
ok3(name);
|
|
19626
20327
|
}
|
|
19627
20328
|
}
|
|
19628
|
-
|
|
19629
|
-
|
|
19630
|
-
|
|
19631
|
-
|
|
19632
|
-
|
|
19633
|
-
|
|
19634
|
-
|
|
19635
|
-
|
|
19636
|
-
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
19644
|
-
allPresent = false;
|
|
19645
|
-
} else {
|
|
19646
|
-
hint(`Hooks wired in ${SETTINGS_FILE}`);
|
|
19647
|
-
}
|
|
19648
|
-
} catch {
|
|
19649
|
-
warn2(`Could not parse ${SETTINGS_FILE}`);
|
|
20329
|
+
const settings = loadJson2(SETTINGS_FILE);
|
|
20330
|
+
if (!settings) {
|
|
20331
|
+
warn2(`Could not read ${SETTINGS_FILE}`);
|
|
20332
|
+
fix("specialists install");
|
|
20333
|
+
return false;
|
|
20334
|
+
}
|
|
20335
|
+
const hooks = settings.hooks ?? {};
|
|
20336
|
+
const wiredCommands = new Set([
|
|
20337
|
+
...hooks.UserPromptSubmit ?? [],
|
|
20338
|
+
...hooks.SessionStart ?? []
|
|
20339
|
+
].flatMap((entry) => (entry.hooks ?? []).map((h) => h.command ?? "")));
|
|
20340
|
+
for (const name of HOOK_NAMES) {
|
|
20341
|
+
const expected = join14(HOOKS_DIR, name);
|
|
20342
|
+
if (!wiredCommands.has(expected)) {
|
|
20343
|
+
warn2(`${name} not wired in settings.json`);
|
|
20344
|
+
fix("specialists install");
|
|
19650
20345
|
allPresent = false;
|
|
19651
20346
|
}
|
|
19652
20347
|
}
|
|
20348
|
+
if (allPresent)
|
|
20349
|
+
hint(`Hooks wired in ${SETTINGS_FILE}`);
|
|
19653
20350
|
return allPresent;
|
|
19654
20351
|
}
|
|
19655
20352
|
function checkMCP() {
|
|
19656
20353
|
section3("MCP registration");
|
|
19657
|
-
const
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
20354
|
+
const mcp = loadJson2(MCP_FILE2);
|
|
20355
|
+
const spec = mcp?.mcpServers?.specialists;
|
|
20356
|
+
if (!spec || spec.command !== "specialists") {
|
|
20357
|
+
fail2(`MCP server 'specialists' not registered in .mcp.json`);
|
|
20358
|
+
fix("specialists install");
|
|
19661
20359
|
return false;
|
|
19662
20360
|
}
|
|
19663
|
-
ok3(`MCP server '${
|
|
20361
|
+
ok3(`MCP server 'specialists' registered in ${MCP_FILE2}`);
|
|
19664
20362
|
return true;
|
|
19665
20363
|
}
|
|
19666
20364
|
function checkRuntimeDirs() {
|
|
19667
20365
|
section3(".specialists/ runtime directories");
|
|
19668
|
-
const
|
|
19669
|
-
const
|
|
19670
|
-
const
|
|
19671
|
-
const readyDir = join12(rootDir, "ready");
|
|
20366
|
+
const rootDir = join14(CWD, ".specialists");
|
|
20367
|
+
const jobsDir = join14(rootDir, "jobs");
|
|
20368
|
+
const readyDir = join14(rootDir, "ready");
|
|
19672
20369
|
let allOk = true;
|
|
19673
|
-
if (!
|
|
20370
|
+
if (!existsSync10(rootDir)) {
|
|
19674
20371
|
warn2(".specialists/ not found in current project");
|
|
19675
20372
|
fix("specialists init");
|
|
19676
20373
|
allOk = false;
|
|
19677
20374
|
} else {
|
|
19678
20375
|
ok3(".specialists/ present");
|
|
19679
20376
|
for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
|
|
19680
|
-
if (!
|
|
20377
|
+
if (!existsSync10(subDir)) {
|
|
19681
20378
|
warn2(`.specialists/${label}/ missing — auto-creating`);
|
|
19682
20379
|
mkdirSync3(subDir, { recursive: true });
|
|
19683
20380
|
ok3(`.specialists/${label}/ created`);
|
|
@@ -19690,14 +20387,14 @@ function checkRuntimeDirs() {
|
|
|
19690
20387
|
}
|
|
19691
20388
|
function checkZombieJobs() {
|
|
19692
20389
|
section3("Background jobs");
|
|
19693
|
-
const jobsDir =
|
|
19694
|
-
if (!
|
|
20390
|
+
const jobsDir = join14(CWD, ".specialists", "jobs");
|
|
20391
|
+
if (!existsSync10(jobsDir)) {
|
|
19695
20392
|
hint("No .specialists/jobs/ — skipping");
|
|
19696
20393
|
return true;
|
|
19697
20394
|
}
|
|
19698
20395
|
let entries;
|
|
19699
20396
|
try {
|
|
19700
|
-
entries =
|
|
20397
|
+
entries = readdirSync3(jobsDir);
|
|
19701
20398
|
} catch {
|
|
19702
20399
|
entries = [];
|
|
19703
20400
|
}
|
|
@@ -19709,8 +20406,8 @@ function checkZombieJobs() {
|
|
|
19709
20406
|
let total = 0;
|
|
19710
20407
|
let running = 0;
|
|
19711
20408
|
for (const jobId of entries) {
|
|
19712
|
-
const statusPath =
|
|
19713
|
-
if (!
|
|
20409
|
+
const statusPath = join14(jobsDir, jobId, "status.json");
|
|
20410
|
+
if (!existsSync10(statusPath))
|
|
19714
20411
|
continue;
|
|
19715
20412
|
try {
|
|
19716
20413
|
const status = JSON.parse(readFileSync6(statusPath, "utf8"));
|
|
@@ -19723,11 +20420,11 @@ function checkZombieJobs() {
|
|
|
19723
20420
|
process.kill(pid, 0);
|
|
19724
20421
|
alive = true;
|
|
19725
20422
|
} catch {}
|
|
19726
|
-
if (alive)
|
|
20423
|
+
if (alive)
|
|
19727
20424
|
running++;
|
|
19728
|
-
|
|
20425
|
+
else {
|
|
19729
20426
|
zombies++;
|
|
19730
|
-
warn2(`${jobId} ${
|
|
20427
|
+
warn2(`${jobId} ${yellow7("ZOMBIE")} ${dim10(`pid ${pid} not found, status=${status.status}`)}`);
|
|
19731
20428
|
fix(`Edit .specialists/jobs/${jobId}/status.json → set "status": "error"`);
|
|
19732
20429
|
}
|
|
19733
20430
|
}
|
|
@@ -19745,32 +20442,30 @@ async function run13() {
|
|
|
19745
20442
|
${bold8("specialists doctor")}
|
|
19746
20443
|
`);
|
|
19747
20444
|
const piOk = checkPi();
|
|
20445
|
+
const bdOk = checkBd();
|
|
20446
|
+
const xtOk = checkXt();
|
|
19748
20447
|
const hooksOk = checkHooks();
|
|
19749
20448
|
const mcpOk = checkMCP();
|
|
19750
20449
|
const dirsOk = checkRuntimeDirs();
|
|
19751
20450
|
const jobsOk = checkZombieJobs();
|
|
19752
|
-
const allOk = piOk && hooksOk && mcpOk && dirsOk && jobsOk;
|
|
20451
|
+
const allOk = piOk && bdOk && xtOk && hooksOk && mcpOk && dirsOk && jobsOk;
|
|
19753
20452
|
console.log("");
|
|
19754
20453
|
if (allOk) {
|
|
19755
|
-
console.log(` ${
|
|
20454
|
+
console.log(` ${green9("✓")} ${bold8("All checks passed")} — specialists is healthy`);
|
|
19756
20455
|
} else {
|
|
19757
|
-
console.log(` ${
|
|
19758
|
-
console.log(` ${
|
|
20456
|
+
console.log(` ${yellow7("○")} ${bold8("Some checks failed")} — follow the fix hints above`);
|
|
20457
|
+
console.log(` ${dim10("specialists install fixes hook + MCP registration; pi, bd, and xt must be installed separately.")}`);
|
|
19759
20458
|
}
|
|
19760
20459
|
console.log("");
|
|
19761
20460
|
}
|
|
19762
|
-
var bold8 = (s) => `\x1B[1m${s}\x1B[0m`,
|
|
20461
|
+
var bold8 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, green9 = (s) => `\x1B[32m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, red6 = (s) => `\x1B[31m${s}\x1B[0m`, CWD, CLAUDE_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_FILE2, HOOK_NAMES;
|
|
19763
20462
|
var init_doctor = __esm(() => {
|
|
19764
|
-
|
|
19765
|
-
CLAUDE_DIR =
|
|
19766
|
-
HOOKS_DIR =
|
|
19767
|
-
SETTINGS_FILE =
|
|
20463
|
+
CWD = process.cwd();
|
|
20464
|
+
CLAUDE_DIR = join14(CWD, ".claude");
|
|
20465
|
+
HOOKS_DIR = join14(CLAUDE_DIR, "hooks");
|
|
20466
|
+
SETTINGS_FILE = join14(CLAUDE_DIR, "settings.json");
|
|
20467
|
+
MCP_FILE2 = join14(CWD, ".mcp.json");
|
|
19768
20468
|
HOOK_NAMES = [
|
|
19769
|
-
"specialists-main-guard.mjs",
|
|
19770
|
-
"beads-edit-gate.mjs",
|
|
19771
|
-
"beads-commit-gate.mjs",
|
|
19772
|
-
"beads-stop-gate.mjs",
|
|
19773
|
-
"beads-close-memory-prompt.mjs",
|
|
19774
20469
|
"specialists-complete.mjs",
|
|
19775
20470
|
"specialists-session-start.mjs"
|
|
19776
20471
|
];
|
|
@@ -19781,27 +20476,27 @@ var exports_setup = {};
|
|
|
19781
20476
|
__export(exports_setup, {
|
|
19782
20477
|
run: () => run14
|
|
19783
20478
|
});
|
|
19784
|
-
import { existsSync as
|
|
20479
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
19785
20480
|
import { homedir as homedir3 } from "node:os";
|
|
19786
|
-
import { join as
|
|
20481
|
+
import { join as join15, resolve } from "node:path";
|
|
19787
20482
|
function ok4(msg) {
|
|
19788
|
-
console.log(` ${
|
|
20483
|
+
console.log(` ${green10("✓")} ${msg}`);
|
|
19789
20484
|
}
|
|
19790
20485
|
function skip2(msg) {
|
|
19791
|
-
console.log(` ${
|
|
20486
|
+
console.log(` ${yellow8("○")} ${msg}`);
|
|
19792
20487
|
}
|
|
19793
20488
|
function resolveTarget(target) {
|
|
19794
20489
|
switch (target) {
|
|
19795
20490
|
case "global":
|
|
19796
|
-
return
|
|
20491
|
+
return join15(homedir3(), ".claude", "CLAUDE.md");
|
|
19797
20492
|
case "agents":
|
|
19798
|
-
return
|
|
20493
|
+
return join15(process.cwd(), "AGENTS.md");
|
|
19799
20494
|
case "project":
|
|
19800
20495
|
default:
|
|
19801
|
-
return
|
|
20496
|
+
return join15(process.cwd(), "CLAUDE.md");
|
|
19802
20497
|
}
|
|
19803
20498
|
}
|
|
19804
|
-
function
|
|
20499
|
+
function parseArgs6() {
|
|
19805
20500
|
const argv = process.argv.slice(3);
|
|
19806
20501
|
let target = "project";
|
|
19807
20502
|
let dryRun = false;
|
|
@@ -19827,29 +20522,29 @@ function parseArgs5() {
|
|
|
19827
20522
|
return { target, dryRun };
|
|
19828
20523
|
}
|
|
19829
20524
|
async function run14() {
|
|
19830
|
-
const { target, dryRun } =
|
|
20525
|
+
const { target, dryRun } = parseArgs6();
|
|
19831
20526
|
const filePath = resolve(resolveTarget(target));
|
|
19832
20527
|
const label = target === "global" ? "~/.claude/CLAUDE.md" : filePath.replace(process.cwd() + "/", "");
|
|
19833
20528
|
console.log(`
|
|
19834
20529
|
${bold9("specialists setup")}
|
|
19835
20530
|
`);
|
|
19836
|
-
console.log(` Target: ${
|
|
20531
|
+
console.log(` Target: ${yellow8(label)}${dryRun ? dim11(" (dry-run)") : ""}
|
|
19837
20532
|
`);
|
|
19838
|
-
if (
|
|
20533
|
+
if (existsSync11(filePath)) {
|
|
19839
20534
|
const existing = readFileSync7(filePath, "utf8");
|
|
19840
20535
|
if (existing.includes(MARKER)) {
|
|
19841
20536
|
skip2(`${label} already contains Specialists Workflow section`);
|
|
19842
20537
|
console.log(`
|
|
19843
|
-
${
|
|
20538
|
+
${dim11("To force-update, remove the ## Specialists Workflow section and re-run.")}
|
|
19844
20539
|
`);
|
|
19845
20540
|
return;
|
|
19846
20541
|
}
|
|
19847
20542
|
if (dryRun) {
|
|
19848
|
-
console.log(
|
|
19849
|
-
console.log(
|
|
20543
|
+
console.log(dim11("─".repeat(60)));
|
|
20544
|
+
console.log(dim11("Would append to existing file:"));
|
|
19850
20545
|
console.log("");
|
|
19851
20546
|
console.log(WORKFLOW_BLOCK);
|
|
19852
|
-
console.log(
|
|
20547
|
+
console.log(dim11("─".repeat(60)));
|
|
19853
20548
|
return;
|
|
19854
20549
|
}
|
|
19855
20550
|
const separator = existing.trimEnd().endsWith(`
|
|
@@ -19861,24 +20556,24 @@ ${bold9("specialists setup")}
|
|
|
19861
20556
|
ok4(`Appended Specialists Workflow section to ${label}`);
|
|
19862
20557
|
} else {
|
|
19863
20558
|
if (dryRun) {
|
|
19864
|
-
console.log(
|
|
19865
|
-
console.log(
|
|
20559
|
+
console.log(dim11("─".repeat(60)));
|
|
20560
|
+
console.log(dim11(`Would create ${label}:`));
|
|
19866
20561
|
console.log("");
|
|
19867
20562
|
console.log(WORKFLOW_BLOCK);
|
|
19868
|
-
console.log(
|
|
20563
|
+
console.log(dim11("─".repeat(60)));
|
|
19869
20564
|
return;
|
|
19870
20565
|
}
|
|
19871
20566
|
writeFileSync4(filePath, WORKFLOW_BLOCK, "utf8");
|
|
19872
20567
|
ok4(`Created ${label} with Specialists Workflow section`);
|
|
19873
20568
|
}
|
|
19874
20569
|
console.log("");
|
|
19875
|
-
console.log(` ${
|
|
20570
|
+
console.log(` ${dim11("Next steps:")}`);
|
|
19876
20571
|
console.log(` • Restart Claude Code to pick up the new context`);
|
|
19877
|
-
console.log(` • Run ${
|
|
19878
|
-
console.log(` • Run ${
|
|
20572
|
+
console.log(` • Run ${yellow8("specialists list")} to see available specialists`);
|
|
20573
|
+
console.log(` • Run ${yellow8("specialist_init")} in a new session to bootstrap context`);
|
|
19879
20574
|
console.log("");
|
|
19880
20575
|
}
|
|
19881
|
-
var bold9 = (s) => `\x1B[1m${s}\x1B[0m`,
|
|
20576
|
+
var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, green10 = (s) => `\x1B[32m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, MARKER = "## Specialists Workflow", WORKFLOW_BLOCK = `## Specialists Workflow
|
|
19882
20577
|
|
|
19883
20578
|
> Injected by \`specialists setup\`. Keep this section — agents use it for context.
|
|
19884
20579
|
|
|
@@ -19948,59 +20643,92 @@ var exports_help = {};
|
|
|
19948
20643
|
__export(exports_help, {
|
|
19949
20644
|
run: () => run15
|
|
19950
20645
|
});
|
|
19951
|
-
function
|
|
19952
|
-
const
|
|
19953
|
-
return [
|
|
19954
|
-
"",
|
|
19955
|
-
bold10(cyan7(label)),
|
|
19956
|
-
...entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(colWidth)} ${dim13(desc)}`)
|
|
19957
|
-
];
|
|
20646
|
+
function formatCommands(entries) {
|
|
20647
|
+
const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
|
|
20648
|
+
return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
|
|
19958
20649
|
}
|
|
19959
20650
|
async function run15() {
|
|
19960
20651
|
const lines = [
|
|
19961
20652
|
"",
|
|
19962
|
-
|
|
20653
|
+
"Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
|
|
20654
|
+
"",
|
|
20655
|
+
bold10("Usage:"),
|
|
20656
|
+
" specialists [command]",
|
|
20657
|
+
" specialists [command] --help",
|
|
20658
|
+
"",
|
|
20659
|
+
bold10("Common flows:"),
|
|
20660
|
+
"",
|
|
20661
|
+
" Tracked work (primary)",
|
|
20662
|
+
' bd create "Task title" -t task -p 1 --json',
|
|
20663
|
+
" specialists run <name> --bead <id> [--context-depth N] [--background]",
|
|
20664
|
+
" specialists feed -f",
|
|
20665
|
+
' bd close <id> --reason "Done"',
|
|
20666
|
+
"",
|
|
20667
|
+
" Ad-hoc work",
|
|
20668
|
+
' specialists run <name> --prompt "..."',
|
|
19963
20669
|
"",
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
...formatGroup("Other", OTHER),
|
|
20670
|
+
" Rules",
|
|
20671
|
+
" --bead is for tracked work",
|
|
20672
|
+
" --prompt is for quick untracked work",
|
|
20673
|
+
" --context-depth defaults to 1 with --bead",
|
|
20674
|
+
" --no-beads does not disable bead reading",
|
|
19970
20675
|
"",
|
|
19971
|
-
|
|
19972
|
-
|
|
20676
|
+
bold10("Core commands:"),
|
|
20677
|
+
...formatCommands(CORE_COMMANDS),
|
|
20678
|
+
"",
|
|
20679
|
+
bold10("Extended commands:"),
|
|
20680
|
+
...formatCommands(EXTENDED_COMMANDS),
|
|
20681
|
+
"",
|
|
20682
|
+
bold10("xtrm worktree commands:"),
|
|
20683
|
+
...formatCommands(WORKTREE_COMMANDS),
|
|
20684
|
+
"",
|
|
20685
|
+
bold10("Examples:"),
|
|
20686
|
+
" specialists init",
|
|
20687
|
+
" specialists list",
|
|
20688
|
+
" specialists run bug-hunt --bead unitAI-123 --background",
|
|
20689
|
+
' specialists run codebase-explorer --prompt "Map the CLI architecture"',
|
|
20690
|
+
" specialists feed -f",
|
|
20691
|
+
" specialists result <job-id>",
|
|
20692
|
+
"",
|
|
20693
|
+
bold10("More help:"),
|
|
20694
|
+
" specialists quickstart Full guide and workflow reference",
|
|
20695
|
+
" specialists run --help Run command details and flags",
|
|
20696
|
+
" specialists init --help Bootstrap behavior and workflow injection",
|
|
20697
|
+
" specialists feed --help Background job monitoring details",
|
|
20698
|
+
"",
|
|
20699
|
+
dim12("Project model: specialists are project-only; user-scope discovery is deprecated."),
|
|
19973
20700
|
""
|
|
19974
20701
|
];
|
|
19975
20702
|
console.log(lines.join(`
|
|
19976
20703
|
`));
|
|
19977
20704
|
}
|
|
19978
|
-
var bold10 = (s) => `\x1B[1m${s}\x1B[0m`,
|
|
20705
|
+
var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, CORE_COMMANDS, EXTENDED_COMMANDS, WORKTREE_COMMANDS;
|
|
19979
20706
|
var init_help = __esm(() => {
|
|
19980
|
-
|
|
19981
|
-
["
|
|
19982
|
-
["
|
|
19983
|
-
["
|
|
19984
|
-
["
|
|
19985
|
-
["
|
|
19986
|
-
|
|
19987
|
-
|
|
19988
|
-
["
|
|
19989
|
-
["
|
|
19990
|
-
["
|
|
20707
|
+
CORE_COMMANDS = [
|
|
20708
|
+
["init", "Bootstrap a project: dirs, workflow injection, project MCP registration"],
|
|
20709
|
+
["list", "List specialists in this project"],
|
|
20710
|
+
["run", "Run a specialist with --bead for tracked work or --prompt for ad-hoc work"],
|
|
20711
|
+
["feed", "Tail job events; use -f to follow all jobs"],
|
|
20712
|
+
["result", "Print final output of a completed background job"],
|
|
20713
|
+
["stop", "Stop a running background job"],
|
|
20714
|
+
["status", "Show health, MCP state, and active jobs"],
|
|
20715
|
+
["doctor", "Diagnose installation/runtime problems"],
|
|
20716
|
+
["quickstart", "Full getting-started guide"],
|
|
20717
|
+
["help", "Show this help"]
|
|
19991
20718
|
];
|
|
19992
|
-
|
|
19993
|
-
["
|
|
19994
|
-
["
|
|
19995
|
-
];
|
|
19996
|
-
JOBS = [
|
|
19997
|
-
["feed", "Tail events for a background job (--follow to stream)"],
|
|
19998
|
-
["result", "Print result of a background job"],
|
|
19999
|
-
["stop", "Send SIGTERM to a running background job"]
|
|
20000
|
-
];
|
|
20001
|
-
OTHER = [
|
|
20719
|
+
EXTENDED_COMMANDS = [
|
|
20720
|
+
["edit", "Edit a specialist field such as model or description"],
|
|
20721
|
+
["models", "List models available on pi"],
|
|
20002
20722
|
["version", "Print installed version"],
|
|
20003
|
-
["
|
|
20723
|
+
["setup", "[deprecated] Use specialists init instead"],
|
|
20724
|
+
["install", "[deprecated] Use specialists init instead"]
|
|
20725
|
+
];
|
|
20726
|
+
WORKTREE_COMMANDS = [
|
|
20727
|
+
["xt pi [name]", "Start a Pi session in a sandboxed xt worktree"],
|
|
20728
|
+
["xt claude [name]", "Start a Claude session in a sandboxed xt worktree"],
|
|
20729
|
+
["xt attach [slug]", "Resume an existing xt worktree session"],
|
|
20730
|
+
["xt worktree list", "List worktrees with runtime and activity"],
|
|
20731
|
+
["xt end", "Close session, push, PR, cleanup"]
|
|
20004
20732
|
];
|
|
20005
20733
|
});
|
|
20006
20734
|
|
|
@@ -26030,6 +26758,9 @@ class Protocol {
|
|
|
26030
26758
|
}
|
|
26031
26759
|
}
|
|
26032
26760
|
async connect(transport) {
|
|
26761
|
+
if (this._transport) {
|
|
26762
|
+
throw new Error("Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.");
|
|
26763
|
+
}
|
|
26033
26764
|
this._transport = transport;
|
|
26034
26765
|
const _onclose = this.transport?.onclose;
|
|
26035
26766
|
this._transport.onclose = () => {
|
|
@@ -26062,6 +26793,10 @@ class Protocol {
|
|
|
26062
26793
|
this._progressHandlers.clear();
|
|
26063
26794
|
this._taskProgressTokens.clear();
|
|
26064
26795
|
this._pendingDebouncedNotifications.clear();
|
|
26796
|
+
for (const controller of this._requestHandlerAbortControllers.values()) {
|
|
26797
|
+
controller.abort();
|
|
26798
|
+
}
|
|
26799
|
+
this._requestHandlerAbortControllers.clear();
|
|
26065
26800
|
const error2 = McpError.fromError(ErrorCode.ConnectionClosed, "Connection closed");
|
|
26066
26801
|
this._transport = undefined;
|
|
26067
26802
|
this.onclose?.();
|
|
@@ -26112,6 +26847,8 @@ class Protocol {
|
|
|
26112
26847
|
sessionId: capturedTransport?.sessionId,
|
|
26113
26848
|
_meta: request.params?._meta,
|
|
26114
26849
|
sendNotification: async (notification) => {
|
|
26850
|
+
if (abortController.signal.aborted)
|
|
26851
|
+
return;
|
|
26115
26852
|
const notificationOptions = { relatedRequestId: request.id };
|
|
26116
26853
|
if (relatedTaskId) {
|
|
26117
26854
|
notificationOptions.relatedTask = { taskId: relatedTaskId };
|
|
@@ -26119,6 +26856,9 @@ class Protocol {
|
|
|
26119
26856
|
await this.notification(notification, notificationOptions);
|
|
26120
26857
|
},
|
|
26121
26858
|
sendRequest: async (r, resultSchema, options) => {
|
|
26859
|
+
if (abortController.signal.aborted) {
|
|
26860
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
26861
|
+
}
|
|
26122
26862
|
const requestOptions = { ...options, relatedRequestId: request.id };
|
|
26123
26863
|
if (relatedTaskId && !requestOptions.relatedTask) {
|
|
26124
26864
|
requestOptions.relatedTask = { taskId: relatedTaskId };
|
|
@@ -26732,6 +27472,62 @@ class ExperimentalServerTasks {
|
|
|
26732
27472
|
requestStream(request, resultSchema, options) {
|
|
26733
27473
|
return this._server.requestStream(request, resultSchema, options);
|
|
26734
27474
|
}
|
|
27475
|
+
createMessageStream(params, options) {
|
|
27476
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27477
|
+
if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
|
|
27478
|
+
throw new Error("Client does not support sampling tools capability.");
|
|
27479
|
+
}
|
|
27480
|
+
if (params.messages.length > 0) {
|
|
27481
|
+
const lastMessage = params.messages[params.messages.length - 1];
|
|
27482
|
+
const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
|
|
27483
|
+
const hasToolResults = lastContent.some((c) => c.type === "tool_result");
|
|
27484
|
+
const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
|
|
27485
|
+
const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
|
|
27486
|
+
const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
|
|
27487
|
+
if (hasToolResults) {
|
|
27488
|
+
if (lastContent.some((c) => c.type !== "tool_result")) {
|
|
27489
|
+
throw new Error("The last message must contain only tool_result content if any is present");
|
|
27490
|
+
}
|
|
27491
|
+
if (!hasPreviousToolUse) {
|
|
27492
|
+
throw new Error("tool_result blocks are not matching any tool_use from the previous message");
|
|
27493
|
+
}
|
|
27494
|
+
}
|
|
27495
|
+
if (hasPreviousToolUse) {
|
|
27496
|
+
const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
|
|
27497
|
+
const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
|
|
27498
|
+
if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
|
|
27499
|
+
throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
|
|
27500
|
+
}
|
|
27501
|
+
}
|
|
27502
|
+
}
|
|
27503
|
+
return this.requestStream({
|
|
27504
|
+
method: "sampling/createMessage",
|
|
27505
|
+
params
|
|
27506
|
+
}, CreateMessageResultSchema, options);
|
|
27507
|
+
}
|
|
27508
|
+
elicitInputStream(params, options) {
|
|
27509
|
+
const clientCapabilities = this._server.getClientCapabilities();
|
|
27510
|
+
const mode = params.mode ?? "form";
|
|
27511
|
+
switch (mode) {
|
|
27512
|
+
case "url": {
|
|
27513
|
+
if (!clientCapabilities?.elicitation?.url) {
|
|
27514
|
+
throw new Error("Client does not support url elicitation.");
|
|
27515
|
+
}
|
|
27516
|
+
break;
|
|
27517
|
+
}
|
|
27518
|
+
case "form": {
|
|
27519
|
+
if (!clientCapabilities?.elicitation?.form) {
|
|
27520
|
+
throw new Error("Client does not support form elicitation.");
|
|
27521
|
+
}
|
|
27522
|
+
break;
|
|
27523
|
+
}
|
|
27524
|
+
}
|
|
27525
|
+
const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
|
|
27526
|
+
return this.requestStream({
|
|
27527
|
+
method: "elicitation/create",
|
|
27528
|
+
params: normalizedParams
|
|
27529
|
+
}, ElicitResultSchema, options);
|
|
27530
|
+
}
|
|
26735
27531
|
async getTask(taskId, options) {
|
|
26736
27532
|
return this._server.getTask({ taskId }, options);
|
|
26737
27533
|
}
|
|
@@ -27206,7 +28002,7 @@ class StdioServerTransport {
|
|
|
27206
28002
|
}
|
|
27207
28003
|
|
|
27208
28004
|
// src/server.ts
|
|
27209
|
-
import { join as
|
|
28005
|
+
import { join as join4 } from "node:path";
|
|
27210
28006
|
|
|
27211
28007
|
// src/constants.ts
|
|
27212
28008
|
var LOG_PREFIX = "[specialists]";
|
|
@@ -27295,25 +28091,49 @@ function createListSpecialistsTool(loader) {
|
|
|
27295
28091
|
|
|
27296
28092
|
// src/tools/specialist/use_specialist.tool.ts
|
|
27297
28093
|
init_zod();
|
|
28094
|
+
init_beads();
|
|
27298
28095
|
var useSpecialistSchema = exports_external.object({
|
|
27299
28096
|
name: exports_external.string().describe("Specialist identifier (e.g. codebase-explorer)"),
|
|
27300
|
-
prompt: exports_external.string().describe("The task or question for the specialist"),
|
|
28097
|
+
prompt: exports_external.string().optional().describe("The task or question for the specialist"),
|
|
28098
|
+
bead_id: exports_external.string().optional().describe("Use an existing bead as the specialist prompt"),
|
|
27301
28099
|
variables: exports_external.record(exports_external.string()).optional().describe("Additional $variable substitutions"),
|
|
27302
28100
|
backend_override: exports_external.string().optional().describe("Force a specific backend (gemini, qwen, anthropic)"),
|
|
27303
|
-
autonomy_level: exports_external.string().optional().describe("Override permission level for this invocation")
|
|
28101
|
+
autonomy_level: exports_external.string().optional().describe("Override permission level for this invocation"),
|
|
28102
|
+
context_depth: exports_external.number().optional().describe("Depth of blocker context injection (0 = none, 1 = immediate blockers, etc.)")
|
|
28103
|
+
}).refine((input) => Boolean(input.prompt?.trim() || input.bead_id), {
|
|
28104
|
+
message: "Either prompt or bead_id is required",
|
|
28105
|
+
path: ["prompt"]
|
|
27304
28106
|
});
|
|
27305
28107
|
function createUseSpecialistTool(runner) {
|
|
27306
28108
|
return {
|
|
27307
28109
|
name: "use_specialist",
|
|
27308
|
-
description: "Run a specialist synchronously and wait for the result. " + "Full lifecycle: load → agents.md → pi session → output. " + "Response includes output, model, durationMs, and beadId (string | undefined). " + "beadId is set when the specialist's beads_integration policy triggered bead creation " + "(default: auto — creates for LOW/MEDIUM/HIGH permission, skips for READ_ONLY). " + "If beadId is present, use `bd update <beadId> --notes` to attach findings or " + "`bd remember` to persist key discoveries for future sessions.",
|
|
28110
|
+
description: "Run a specialist synchronously and wait for the result. " + "Full lifecycle: load → agents.md → pi session → output. " + "Response includes output, model, durationMs, and beadId (string | undefined). " + "beadId is set when the specialist's beads_integration policy triggered bead creation " + "(default: auto — creates for LOW/MEDIUM/HIGH permission, skips for READ_ONLY). " + "If beadId is present, use `bd update <beadId> --notes` to attach findings or " + "`bd remember` to persist key discoveries for future sessions. " + "When bead_id is provided, the source bead becomes the specialist prompt and the tracking bead links back to it. " + "Use context_depth to inject outputs from completed blocking dependencies (depth 1 = immediate blockers, 2 = include their blockers too).",
|
|
27309
28111
|
inputSchema: useSpecialistSchema,
|
|
27310
28112
|
async execute(input, onProgress) {
|
|
28113
|
+
let prompt = input.prompt?.trim() ?? "";
|
|
28114
|
+
let variables = input.variables;
|
|
28115
|
+
if (input.bead_id) {
|
|
28116
|
+
const beadsClient = new BeadsClient;
|
|
28117
|
+
const bead = beadsClient.readBead(input.bead_id);
|
|
28118
|
+
if (!bead) {
|
|
28119
|
+
throw new Error(`Unable to read bead '${input.bead_id}' via bd show --json`);
|
|
28120
|
+
}
|
|
28121
|
+
const blockers = input.context_depth && input.context_depth > 0 ? beadsClient.getBlockers(input.bead_id, input.context_depth) : [];
|
|
28122
|
+
const beadContext = buildBeadContext(bead, { blockers, depth: input.context_depth ?? 0 });
|
|
28123
|
+
prompt = beadContext;
|
|
28124
|
+
variables = {
|
|
28125
|
+
...input.variables ?? {},
|
|
28126
|
+
bead_context: beadContext,
|
|
28127
|
+
bead_id: input.bead_id
|
|
28128
|
+
};
|
|
28129
|
+
}
|
|
27311
28130
|
return runner.run({
|
|
27312
28131
|
name: input.name,
|
|
27313
|
-
prompt
|
|
27314
|
-
variables
|
|
28132
|
+
prompt,
|
|
28133
|
+
variables,
|
|
27315
28134
|
backendOverride: input.backend_override,
|
|
27316
|
-
autonomyLevel: input.autonomy_level
|
|
28135
|
+
autonomyLevel: input.autonomy_level,
|
|
28136
|
+
inputBeadId: input.bead_id
|
|
27317
28137
|
}, onProgress);
|
|
27318
28138
|
}
|
|
27319
28139
|
};
|
|
@@ -27419,14 +28239,14 @@ function createSpecialistStatusTool(loader, circuitBreaker) {
|
|
|
27419
28239
|
async execute(_) {
|
|
27420
28240
|
const list = await loader.list();
|
|
27421
28241
|
const stalenessResults = await Promise.all(list.map((s) => checkStaleness(s)));
|
|
27422
|
-
const { existsSync:
|
|
27423
|
-
const { join:
|
|
27424
|
-
const jobsDir =
|
|
28242
|
+
const { existsSync: existsSync3, readdirSync, readFileSync } = await import("node:fs");
|
|
28243
|
+
const { join: join3 } = await import("node:path");
|
|
28244
|
+
const jobsDir = join3(process.cwd(), ".specialists", "jobs");
|
|
27425
28245
|
const jobs = [];
|
|
27426
|
-
if (
|
|
28246
|
+
if (existsSync3(jobsDir)) {
|
|
27427
28247
|
for (const entry of readdirSync(jobsDir)) {
|
|
27428
|
-
const statusPath =
|
|
27429
|
-
if (!
|
|
28248
|
+
const statusPath = join3(jobsDir, entry, "status.json");
|
|
28249
|
+
if (!existsSync3(statusPath))
|
|
27430
28250
|
continue;
|
|
27431
28251
|
try {
|
|
27432
28252
|
jobs.push(JSON.parse(readFileSync(statusPath, "utf-8")));
|
|
@@ -27638,13 +28458,13 @@ init_zod();
|
|
|
27638
28458
|
// src/tools/specialist/specialist_init.tool.ts
|
|
27639
28459
|
init_zod();
|
|
27640
28460
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
27641
|
-
import { existsSync as
|
|
27642
|
-
import { join as
|
|
28461
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
28462
|
+
import { join as join3 } from "node:path";
|
|
27643
28463
|
var specialistInitSchema = objectType({});
|
|
27644
28464
|
function createSpecialistInitTool(loader, deps) {
|
|
27645
28465
|
const resolved = deps ?? {
|
|
27646
28466
|
bdAvailable: () => spawnSync2("bd", ["--version"], { stdio: "ignore" }).status === 0,
|
|
27647
|
-
beadsExists: () =>
|
|
28467
|
+
beadsExists: () => existsSync3(join3(process.cwd(), ".beads")),
|
|
27648
28468
|
bdInit: () => spawnSync2("bd", ["init"], { stdio: "ignore" })
|
|
27649
28469
|
};
|
|
27650
28470
|
return {
|
|
@@ -27679,7 +28499,7 @@ class SpecialistsServer {
|
|
|
27679
28499
|
const circuitBreaker = new CircuitBreaker;
|
|
27680
28500
|
const loader = new SpecialistLoader;
|
|
27681
28501
|
const hooks = new HookEmitter({
|
|
27682
|
-
tracePath:
|
|
28502
|
+
tracePath: join4(process.cwd(), ".specialists", "trace.jsonl")
|
|
27683
28503
|
});
|
|
27684
28504
|
const beadsClient = new BeadsClient;
|
|
27685
28505
|
const runner = new SpecialistRunner({ loader, hooks, circuitBreaker, beadsClient });
|
|
@@ -27786,8 +28606,8 @@ async function run16() {
|
|
|
27786
28606
|
"",
|
|
27787
28607
|
"Usage: specialists install",
|
|
27788
28608
|
"",
|
|
27789
|
-
"
|
|
27790
|
-
"and installs
|
|
28609
|
+
"Project setup: checks pi/bd/xt prerequisites, registers the MCP server,",
|
|
28610
|
+
"and installs specialists-specific project hooks.",
|
|
27791
28611
|
"",
|
|
27792
28612
|
"No flags — just run it.",
|
|
27793
28613
|
""
|
|
@@ -27808,18 +28628,24 @@ async function run16() {
|
|
|
27808
28628
|
"",
|
|
27809
28629
|
"Usage: specialists list [options]",
|
|
27810
28630
|
"",
|
|
27811
|
-
"List
|
|
28631
|
+
"List specialists in the current project.",
|
|
28632
|
+
"",
|
|
28633
|
+
"What it shows:",
|
|
28634
|
+
" - specialist name",
|
|
28635
|
+
" - model",
|
|
28636
|
+
" - short description",
|
|
27812
28637
|
"",
|
|
27813
28638
|
"Options:",
|
|
27814
|
-
" --
|
|
27815
|
-
" --
|
|
27816
|
-
" --json Output as JSON array",
|
|
28639
|
+
" --category <name> Filter by category tag",
|
|
28640
|
+
" --json Output as JSON array",
|
|
27817
28641
|
"",
|
|
27818
28642
|
"Examples:",
|
|
27819
28643
|
" specialists list",
|
|
27820
|
-
" specialists list --scope project",
|
|
27821
28644
|
" specialists list --category analysis",
|
|
27822
28645
|
" specialists list --json",
|
|
28646
|
+
"",
|
|
28647
|
+
"Project model:",
|
|
28648
|
+
" Specialists are project-only. User-scope discovery is deprecated.",
|
|
27823
28649
|
""
|
|
27824
28650
|
].join(`
|
|
27825
28651
|
`));
|
|
@@ -27849,15 +28675,27 @@ async function run16() {
|
|
|
27849
28675
|
if (wantsHelp()) {
|
|
27850
28676
|
console.log([
|
|
27851
28677
|
"",
|
|
27852
|
-
"Usage: specialists init",
|
|
28678
|
+
"Usage: specialists init [--force-workflow]",
|
|
27853
28679
|
"",
|
|
27854
|
-
"
|
|
27855
|
-
" • Creates specialists/ — put .specialist.yaml files here",
|
|
27856
|
-
" • Creates .specialists/ — runtime data (gitignored)",
|
|
27857
|
-
" • Adds .specialists/ to .gitignore",
|
|
27858
|
-
" • Scaffolds AGENTS.md — context injected into Claude sessions",
|
|
28680
|
+
"Bootstrap a project for specialists. This is the sole onboarding command.",
|
|
27859
28681
|
"",
|
|
27860
|
-
"
|
|
28682
|
+
"What it does:",
|
|
28683
|
+
" • creates specialists/ for project .specialist.yaml files",
|
|
28684
|
+
" • creates .specialists/ runtime dirs (jobs/, ready/)",
|
|
28685
|
+
" • adds .specialists/ to .gitignore",
|
|
28686
|
+
" • injects the managed workflow block into AGENTS.md and CLAUDE.md",
|
|
28687
|
+
" • registers the Specialists MCP server at project scope",
|
|
28688
|
+
"",
|
|
28689
|
+
"Options:",
|
|
28690
|
+
" --force-workflow Overwrite existing managed workflow blocks",
|
|
28691
|
+
"",
|
|
28692
|
+
"Examples:",
|
|
28693
|
+
" specialists init",
|
|
28694
|
+
" specialists init --force-workflow",
|
|
28695
|
+
"",
|
|
28696
|
+
"Notes:",
|
|
28697
|
+
" setup and install are deprecated; use specialists init.",
|
|
28698
|
+
" Safe to run again; existing project state is preserved where possible.",
|
|
27861
28699
|
""
|
|
27862
28700
|
].join(`
|
|
27863
28701
|
`));
|
|
@@ -27904,24 +28742,29 @@ async function run16() {
|
|
|
27904
28742
|
"",
|
|
27905
28743
|
"Usage: specialists run <name> [options]",
|
|
27906
28744
|
"",
|
|
27907
|
-
"Run a specialist
|
|
27908
|
-
"
|
|
28745
|
+
"Run a specialist in foreground or background.",
|
|
28746
|
+
"",
|
|
28747
|
+
"Primary modes:",
|
|
28748
|
+
" tracked: specialists run <name> --bead <id>",
|
|
28749
|
+
' ad-hoc: specialists run <name> --prompt "..."',
|
|
27909
28750
|
"",
|
|
27910
28751
|
"Options:",
|
|
27911
|
-
" --
|
|
27912
|
-
" --
|
|
27913
|
-
" --
|
|
27914
|
-
" --no-beads
|
|
28752
|
+
" --bead <id> Use an existing bead as the prompt source",
|
|
28753
|
+
" --prompt <text> Ad-hoc prompt for untracked work",
|
|
28754
|
+
" --context-depth <n> Dependency context depth when using --bead (default: 1)",
|
|
28755
|
+
" --no-beads Do not create a new tracking bead (does not disable bead reading)",
|
|
28756
|
+
" --background Start async and return a job id",
|
|
28757
|
+
" --model <model> Override the configured model for this run",
|
|
27915
28758
|
"",
|
|
27916
28759
|
"Examples:",
|
|
28760
|
+
" specialists run bug-hunt --bead unitAI-55d",
|
|
28761
|
+
" specialists run bug-hunt --bead unitAI-55d --context-depth 2 --background",
|
|
27917
28762
|
' specialists run code-review --prompt "Audit src/api.ts"',
|
|
27918
|
-
|
|
27919
|
-
" cat brief.md | specialists run deep-analysis",
|
|
27920
|
-
' specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."',
|
|
28763
|
+
" cat brief.md | specialists run report-generator",
|
|
27921
28764
|
"",
|
|
27922
|
-
"
|
|
27923
|
-
"
|
|
27924
|
-
"
|
|
28765
|
+
"Rules:",
|
|
28766
|
+
" Use --bead for tracked work.",
|
|
28767
|
+
" Use --prompt for quick ad-hoc work.",
|
|
27925
28768
|
""
|
|
27926
28769
|
].join(`
|
|
27927
28770
|
`));
|
|
@@ -27936,11 +28779,17 @@ async function run16() {
|
|
|
27936
28779
|
"",
|
|
27937
28780
|
"Usage: specialists status [options]",
|
|
27938
28781
|
"",
|
|
27939
|
-
"Show
|
|
27940
|
-
"
|
|
28782
|
+
"Show current runtime state.",
|
|
28783
|
+
"",
|
|
28784
|
+
"Sections include:",
|
|
28785
|
+
" - discovered specialists",
|
|
28786
|
+
" - pi provider/runtime health",
|
|
28787
|
+
" - beads availability",
|
|
28788
|
+
" - MCP registration hints",
|
|
28789
|
+
" - active background jobs",
|
|
27941
28790
|
"",
|
|
27942
28791
|
"Options:",
|
|
27943
|
-
" --json
|
|
28792
|
+
" --json Output machine-readable JSON",
|
|
27944
28793
|
"",
|
|
27945
28794
|
"Examples:",
|
|
27946
28795
|
" specialists status",
|
|
@@ -27982,20 +28831,24 @@ async function run16() {
|
|
|
27982
28831
|
console.log([
|
|
27983
28832
|
"",
|
|
27984
28833
|
"Usage: specialists feed <job-id> [options]",
|
|
27985
|
-
" specialists feed
|
|
28834
|
+
" specialists feed -f [--forever]",
|
|
28835
|
+
"",
|
|
28836
|
+
"Read background job events.",
|
|
27986
28837
|
"",
|
|
27987
|
-
"
|
|
28838
|
+
"Modes:",
|
|
28839
|
+
" specialists feed <job-id> Replay events for one job",
|
|
28840
|
+
" specialists feed <job-id> -f Follow one job until completion",
|
|
28841
|
+
" specialists feed -f Follow all jobs globally",
|
|
27988
28842
|
"",
|
|
27989
28843
|
"Options:",
|
|
27990
|
-
"
|
|
27991
|
-
"
|
|
28844
|
+
" -f, --follow Follow live updates",
|
|
28845
|
+
" --forever Keep following in global mode even when all jobs complete",
|
|
27992
28846
|
"",
|
|
27993
28847
|
"Examples:",
|
|
27994
|
-
" specialists feed
|
|
27995
|
-
" specialists feed
|
|
27996
|
-
" specialists feed
|
|
27997
|
-
"",
|
|
27998
|
-
"Event types: tool_use · tool_result · text · agent_end · error",
|
|
28848
|
+
" specialists feed 49adda",
|
|
28849
|
+
" specialists feed 49adda --follow",
|
|
28850
|
+
" specialists feed -f",
|
|
28851
|
+
" specialists feed -f --forever",
|
|
27999
28852
|
""
|
|
28000
28853
|
].join(`
|
|
28001
28854
|
`));
|
|
@@ -28033,15 +28886,20 @@ async function run16() {
|
|
|
28033
28886
|
"",
|
|
28034
28887
|
"Usage: specialists doctor",
|
|
28035
28888
|
"",
|
|
28036
|
-
"
|
|
28037
|
-
"
|
|
28038
|
-
"
|
|
28039
|
-
"
|
|
28040
|
-
"
|
|
28041
|
-
"
|
|
28889
|
+
"Diagnose bootstrap and runtime problems.",
|
|
28890
|
+
"",
|
|
28891
|
+
"Checks:",
|
|
28892
|
+
" 1. pi installed and has active providers",
|
|
28893
|
+
" 2. beads installed and .beads/ present",
|
|
28894
|
+
" 3. xtrm-tools availability",
|
|
28895
|
+
" 4. Specialists MCP registration in .mcp.json",
|
|
28896
|
+
" 5. .specialists/ runtime directories",
|
|
28897
|
+
" 6. hook wiring expectations",
|
|
28898
|
+
" 7. zombie job detection",
|
|
28042
28899
|
"",
|
|
28043
|
-
"
|
|
28044
|
-
"
|
|
28900
|
+
"Behavior:",
|
|
28901
|
+
" - prints fix hints for failing checks",
|
|
28902
|
+
" - auto-creates missing runtime directories when possible",
|
|
28045
28903
|
"",
|
|
28046
28904
|
"Examples:",
|
|
28047
28905
|
" specialists doctor",
|