@jaggerxtrm/specialists 2.1.20 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -122
- package/bin/install.js +33 -4
- package/dist/index.js +790 -84
- package/hooks/beads-close-memory-prompt.mjs +47 -0
- package/hooks/specialists-complete.mjs +60 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17435,6 +17435,7 @@ var init_schema = __esm(() => {
|
|
|
17435
17435
|
model: stringType(),
|
|
17436
17436
|
fallback_model: stringType().optional(),
|
|
17437
17437
|
timeout_ms: numberType().default(120000),
|
|
17438
|
+
stall_timeout_ms: numberType().optional(),
|
|
17438
17439
|
response_format: enumType(["text", "json", "markdown"]).default("text"),
|
|
17439
17440
|
permission_required: enumType(["READ_ONLY", "LOW", "MEDIUM", "HIGH"]).default("READ_ONLY"),
|
|
17440
17441
|
preferred_profile: stringType().optional(),
|
|
@@ -17455,7 +17456,8 @@ var init_schema = __esm(() => {
|
|
|
17455
17456
|
inject_output: booleanType().default(false)
|
|
17456
17457
|
})).optional(),
|
|
17457
17458
|
references: arrayType(unknownType()).optional(),
|
|
17458
|
-
tools: arrayType(stringType()).optional()
|
|
17459
|
+
tools: arrayType(stringType()).optional(),
|
|
17460
|
+
paths: arrayType(stringType()).optional()
|
|
17459
17461
|
}).optional();
|
|
17460
17462
|
CapabilitiesSchema = objectType({
|
|
17461
17463
|
file_scope: arrayType(stringType()).optional(),
|
|
@@ -17570,6 +17572,19 @@ class SpecialistLoader {
|
|
|
17570
17572
|
if (existsSync(filePath)) {
|
|
17571
17573
|
const content = await readFile(filePath, "utf-8");
|
|
17572
17574
|
const spec = await parseSpecialist(content);
|
|
17575
|
+
const rawPaths = spec.specialist.skills?.paths;
|
|
17576
|
+
if (rawPaths?.length) {
|
|
17577
|
+
const home = homedir();
|
|
17578
|
+
const fileDir = dir.path;
|
|
17579
|
+
const resolved = rawPaths.map((p) => {
|
|
17580
|
+
if (p.startsWith("~/"))
|
|
17581
|
+
return join(home, p.slice(2));
|
|
17582
|
+
if (p.startsWith("./"))
|
|
17583
|
+
return join(fileDir, p.slice(2));
|
|
17584
|
+
return p;
|
|
17585
|
+
});
|
|
17586
|
+
spec.specialist.skills.paths = resolved;
|
|
17587
|
+
}
|
|
17573
17588
|
this.cache.set(name, spec);
|
|
17574
17589
|
return spec;
|
|
17575
17590
|
}
|
|
@@ -17631,6 +17646,10 @@ function mapPermissionToTools(level) {
|
|
|
17631
17646
|
return "read,bash,grep,find,ls";
|
|
17632
17647
|
case "BASH_ONLY":
|
|
17633
17648
|
return "bash";
|
|
17649
|
+
case "LOW":
|
|
17650
|
+
case "MEDIUM":
|
|
17651
|
+
case "HIGH":
|
|
17652
|
+
return "read,bash,edit,write,grep,find,ls";
|
|
17634
17653
|
default:
|
|
17635
17654
|
return;
|
|
17636
17655
|
}
|
|
@@ -17645,6 +17664,7 @@ class PiAgentSession {
|
|
|
17645
17664
|
_agentEndReceived = false;
|
|
17646
17665
|
_killed = false;
|
|
17647
17666
|
_lineBuffer = "";
|
|
17667
|
+
_pendingCommand;
|
|
17648
17668
|
meta;
|
|
17649
17669
|
constructor(options, meta) {
|
|
17650
17670
|
this.options = options;
|
|
@@ -17719,6 +17739,12 @@ class PiAgentSession {
|
|
|
17719
17739
|
return;
|
|
17720
17740
|
}
|
|
17721
17741
|
const { type } = event;
|
|
17742
|
+
if (type === "response") {
|
|
17743
|
+
const handler = this._pendingCommand;
|
|
17744
|
+
this._pendingCommand = undefined;
|
|
17745
|
+
handler?.(event);
|
|
17746
|
+
return;
|
|
17747
|
+
}
|
|
17722
17748
|
if (type === "message_start" && event.message?.role === "assistant") {
|
|
17723
17749
|
const { provider, model } = event.message ?? {};
|
|
17724
17750
|
if (provider || model) {
|
|
@@ -17783,17 +17809,66 @@ class PiAgentSession {
|
|
|
17783
17809
|
}
|
|
17784
17810
|
}
|
|
17785
17811
|
}
|
|
17812
|
+
sendCommand(cmd) {
|
|
17813
|
+
return new Promise((resolve, reject) => {
|
|
17814
|
+
if (!this.proc?.stdin) {
|
|
17815
|
+
reject(new Error("No stdin available"));
|
|
17816
|
+
return;
|
|
17817
|
+
}
|
|
17818
|
+
this._pendingCommand = resolve;
|
|
17819
|
+
this.proc.stdin.write(JSON.stringify(cmd) + `
|
|
17820
|
+
`, (err) => {
|
|
17821
|
+
if (err) {
|
|
17822
|
+
this._pendingCommand = undefined;
|
|
17823
|
+
reject(err);
|
|
17824
|
+
}
|
|
17825
|
+
});
|
|
17826
|
+
});
|
|
17827
|
+
}
|
|
17786
17828
|
async prompt(task) {
|
|
17787
17829
|
const msg = JSON.stringify({ type: "prompt", message: task }) + `
|
|
17788
17830
|
`;
|
|
17789
17831
|
this.proc?.stdin?.write(msg);
|
|
17790
|
-
this.proc?.stdin?.end();
|
|
17791
17832
|
}
|
|
17792
|
-
async waitForDone() {
|
|
17793
|
-
|
|
17833
|
+
async waitForDone(timeout) {
|
|
17834
|
+
const donePromise = this._donePromise;
|
|
17835
|
+
if (!timeout)
|
|
17836
|
+
return donePromise;
|
|
17837
|
+
return Promise.race([
|
|
17838
|
+
donePromise,
|
|
17839
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Specialist timed out after ${timeout}ms`)), timeout))
|
|
17840
|
+
]);
|
|
17794
17841
|
}
|
|
17795
17842
|
async getLastOutput() {
|
|
17796
|
-
|
|
17843
|
+
if (!this.proc?.stdin || !this.proc.stdin.writable) {
|
|
17844
|
+
return this._lastOutput;
|
|
17845
|
+
}
|
|
17846
|
+
try {
|
|
17847
|
+
const response = await Promise.race([
|
|
17848
|
+
this.sendCommand({ type: "get_last_assistant_text" }),
|
|
17849
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000))
|
|
17850
|
+
]);
|
|
17851
|
+
return response?.data?.text ?? this._lastOutput;
|
|
17852
|
+
} catch {
|
|
17853
|
+
return this._lastOutput;
|
|
17854
|
+
}
|
|
17855
|
+
}
|
|
17856
|
+
async getState() {
|
|
17857
|
+
try {
|
|
17858
|
+
const response = await Promise.race([
|
|
17859
|
+
this.sendCommand({ type: "get_state" }),
|
|
17860
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 5000))
|
|
17861
|
+
]);
|
|
17862
|
+
return response?.data;
|
|
17863
|
+
} catch {
|
|
17864
|
+
return null;
|
|
17865
|
+
}
|
|
17866
|
+
}
|
|
17867
|
+
async close() {
|
|
17868
|
+
if (this._killed)
|
|
17869
|
+
return;
|
|
17870
|
+
this.proc?.stdin?.end();
|
|
17871
|
+
await this._donePromise.catch(() => {});
|
|
17797
17872
|
}
|
|
17798
17873
|
kill() {
|
|
17799
17874
|
if (this._killed)
|
|
@@ -17940,9 +18015,9 @@ class SpecialistRunner {
|
|
|
17940
18015
|
estimated_tokens: Math.ceil(renderedTask.length / 4),
|
|
17941
18016
|
system_prompt_present: !!prompt.system
|
|
17942
18017
|
});
|
|
18018
|
+
const { readFile: readFile2 } = await import("node:fs/promises");
|
|
17943
18019
|
let agentsMd = prompt.system ?? "";
|
|
17944
18020
|
if (prompt.skill_inherit) {
|
|
17945
|
-
const { readFile: readFile2 } = await import("node:fs/promises");
|
|
17946
18021
|
const skillContent = await readFile2(prompt.skill_inherit, "utf-8").catch(() => "");
|
|
17947
18022
|
if (skillContent)
|
|
17948
18023
|
agentsMd += `
|
|
@@ -17950,6 +18025,17 @@ class SpecialistRunner {
|
|
|
17950
18025
|
---
|
|
17951
18026
|
# Service Knowledge
|
|
17952
18027
|
|
|
18028
|
+
${skillContent}`;
|
|
18029
|
+
}
|
|
18030
|
+
const skillPaths = spec.specialist.skills?.paths ?? [];
|
|
18031
|
+
for (const skillPath of skillPaths) {
|
|
18032
|
+
const skillContent = await readFile2(skillPath, "utf-8").catch(() => "");
|
|
18033
|
+
if (skillContent)
|
|
18034
|
+
agentsMd += `
|
|
18035
|
+
|
|
18036
|
+
---
|
|
18037
|
+
# Skill: ${skillPath}
|
|
18038
|
+
|
|
17953
18039
|
${skillContent}`;
|
|
17954
18040
|
}
|
|
17955
18041
|
if (spec.specialist.capabilities?.diagnostic_scripts?.length) {
|
|
@@ -17998,10 +18084,11 @@ You have access via Bash:
|
|
|
17998
18084
|
await session.start();
|
|
17999
18085
|
onKillRegistered?.(session.kill.bind(session));
|
|
18000
18086
|
await session.prompt(renderedTask);
|
|
18001
|
-
await session.waitForDone();
|
|
18087
|
+
await session.waitForDone(execution.timeout_ms);
|
|
18002
18088
|
sessionBackend = session.meta.backend;
|
|
18003
18089
|
output = await session.getLastOutput();
|
|
18004
18090
|
sessionBackend = session.meta.backend;
|
|
18091
|
+
await session.close();
|
|
18005
18092
|
const postScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "post") ?? [];
|
|
18006
18093
|
for (const script of postScripts)
|
|
18007
18094
|
runScript(script.path);
|
|
@@ -18242,23 +18329,128 @@ var init_list = __esm(() => {
|
|
|
18242
18329
|
};
|
|
18243
18330
|
});
|
|
18244
18331
|
|
|
18332
|
+
// src/cli/models.ts
|
|
18333
|
+
var exports_models = {};
|
|
18334
|
+
__export(exports_models, {
|
|
18335
|
+
run: () => run4
|
|
18336
|
+
});
|
|
18337
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18338
|
+
function parsePiModels() {
|
|
18339
|
+
const r = spawnSync3("pi", ["--list-models"], {
|
|
18340
|
+
encoding: "utf8",
|
|
18341
|
+
stdio: "pipe",
|
|
18342
|
+
timeout: 8000
|
|
18343
|
+
});
|
|
18344
|
+
if (r.status !== 0 || r.error)
|
|
18345
|
+
return null;
|
|
18346
|
+
return r.stdout.split(`
|
|
18347
|
+
`).slice(1).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
18348
|
+
const cols = line.split(/\s+/);
|
|
18349
|
+
return {
|
|
18350
|
+
provider: cols[0] ?? "",
|
|
18351
|
+
model: cols[1] ?? "",
|
|
18352
|
+
context: cols[2] ?? "",
|
|
18353
|
+
maxOut: cols[3] ?? "",
|
|
18354
|
+
thinking: cols[4] === "yes",
|
|
18355
|
+
images: cols[5] === "yes"
|
|
18356
|
+
};
|
|
18357
|
+
}).filter((m) => m.provider && m.model);
|
|
18358
|
+
}
|
|
18359
|
+
function parseArgs2(argv) {
|
|
18360
|
+
const out = {};
|
|
18361
|
+
for (let i = 0;i < argv.length; i++) {
|
|
18362
|
+
if (argv[i] === "--provider" && argv[i + 1]) {
|
|
18363
|
+
out.provider = argv[++i];
|
|
18364
|
+
continue;
|
|
18365
|
+
}
|
|
18366
|
+
if (argv[i] === "--used") {
|
|
18367
|
+
out.used = true;
|
|
18368
|
+
continue;
|
|
18369
|
+
}
|
|
18370
|
+
}
|
|
18371
|
+
return out;
|
|
18372
|
+
}
|
|
18373
|
+
async function run4() {
|
|
18374
|
+
const args = parseArgs2(process.argv.slice(3));
|
|
18375
|
+
const loader = new SpecialistLoader;
|
|
18376
|
+
const specialists = await loader.list();
|
|
18377
|
+
const usedBy = new Map;
|
|
18378
|
+
for (const s of specialists) {
|
|
18379
|
+
const key = s.model;
|
|
18380
|
+
if (!usedBy.has(key))
|
|
18381
|
+
usedBy.set(key, []);
|
|
18382
|
+
usedBy.get(key).push(s.name);
|
|
18383
|
+
}
|
|
18384
|
+
const allModels = parsePiModels();
|
|
18385
|
+
if (!allModels) {
|
|
18386
|
+
console.error("pi not found or failed — run specialists install");
|
|
18387
|
+
process.exit(1);
|
|
18388
|
+
}
|
|
18389
|
+
let models = allModels;
|
|
18390
|
+
if (args.provider) {
|
|
18391
|
+
models = models.filter((m) => m.provider.toLowerCase().includes(args.provider.toLowerCase()));
|
|
18392
|
+
}
|
|
18393
|
+
if (args.used) {
|
|
18394
|
+
models = models.filter((m) => usedBy.has(`${m.provider}/${m.model}`));
|
|
18395
|
+
}
|
|
18396
|
+
if (models.length === 0) {
|
|
18397
|
+
console.log("No models match.");
|
|
18398
|
+
return;
|
|
18399
|
+
}
|
|
18400
|
+
const byProvider = new Map;
|
|
18401
|
+
for (const m of models) {
|
|
18402
|
+
if (!byProvider.has(m.provider))
|
|
18403
|
+
byProvider.set(m.provider, []);
|
|
18404
|
+
byProvider.get(m.provider).push(m);
|
|
18405
|
+
}
|
|
18406
|
+
const total = models.length;
|
|
18407
|
+
console.log(`
|
|
18408
|
+
${bold2(`Models on pi`)} ${dim2(`(${total} total)`)}
|
|
18409
|
+
`);
|
|
18410
|
+
for (const [provider, pModels] of byProvider) {
|
|
18411
|
+
console.log(` ${cyan2(provider)} ${dim2(`${pModels.length} model${pModels.length !== 1 ? "s" : ""}`)}`);
|
|
18412
|
+
const modelWidth = Math.max(...pModels.map((m) => m.model.length));
|
|
18413
|
+
for (const m of pModels) {
|
|
18414
|
+
const key = `${m.provider}/${m.model}`;
|
|
18415
|
+
const inUse = usedBy.get(key);
|
|
18416
|
+
const flags = [
|
|
18417
|
+
m.thinking ? green("thinking") : dim2("·"),
|
|
18418
|
+
m.images ? dim2("images") : ""
|
|
18419
|
+
].filter(Boolean).join(" ");
|
|
18420
|
+
const ctx = dim2(`ctx ${m.context}`);
|
|
18421
|
+
const usedLabel = inUse ? ` ${yellow2("←")} ${dim2(inUse.join(", "))}` : "";
|
|
18422
|
+
console.log(` ${m.model.padEnd(modelWidth)} ${ctx.padEnd(18)} ${flags}${usedLabel}`);
|
|
18423
|
+
}
|
|
18424
|
+
console.log();
|
|
18425
|
+
}
|
|
18426
|
+
if (!args.used) {
|
|
18427
|
+
console.log(dim2(` --provider <name> filter by provider`));
|
|
18428
|
+
console.log(dim2(` --used only show models used by your specialists`));
|
|
18429
|
+
console.log();
|
|
18430
|
+
}
|
|
18431
|
+
}
|
|
18432
|
+
var bold2 = (s) => `\x1B[1m${s}\x1B[0m`, dim2 = (s) => `\x1B[2m${s}\x1B[0m`, cyan2 = (s) => `\x1B[36m${s}\x1B[0m`, yellow2 = (s) => `\x1B[33m${s}\x1B[0m`, green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
18433
|
+
var init_models = __esm(() => {
|
|
18434
|
+
init_loader();
|
|
18435
|
+
});
|
|
18436
|
+
|
|
18245
18437
|
// src/cli/init.ts
|
|
18246
18438
|
var exports_init = {};
|
|
18247
18439
|
__export(exports_init, {
|
|
18248
|
-
run: () =>
|
|
18440
|
+
run: () => run5
|
|
18249
18441
|
});
|
|
18250
18442
|
import { existsSync as existsSync3, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18251
18443
|
import { join as join5 } from "node:path";
|
|
18252
18444
|
function ok(msg) {
|
|
18253
|
-
console.log(` ${
|
|
18445
|
+
console.log(` ${green2("✓")} ${msg}`);
|
|
18254
18446
|
}
|
|
18255
18447
|
function skip(msg) {
|
|
18256
|
-
console.log(` ${
|
|
18448
|
+
console.log(` ${yellow3("○")} ${msg}`);
|
|
18257
18449
|
}
|
|
18258
|
-
async function
|
|
18450
|
+
async function run5() {
|
|
18259
18451
|
const cwd = process.cwd();
|
|
18260
18452
|
console.log(`
|
|
18261
|
-
${
|
|
18453
|
+
${bold3("specialists init")}
|
|
18262
18454
|
`);
|
|
18263
18455
|
const specialistsDir = join5(cwd, "specialists");
|
|
18264
18456
|
if (existsSync3(specialistsDir)) {
|
|
@@ -18267,6 +18459,32 @@ ${bold2("specialists init")}
|
|
|
18267
18459
|
mkdirSync(specialistsDir, { recursive: true });
|
|
18268
18460
|
ok("created specialists/");
|
|
18269
18461
|
}
|
|
18462
|
+
const runtimeDir = join5(cwd, ".specialists");
|
|
18463
|
+
if (existsSync3(runtimeDir)) {
|
|
18464
|
+
skip(".specialists/ already exists");
|
|
18465
|
+
} else {
|
|
18466
|
+
mkdirSync(join5(runtimeDir, "jobs"), { recursive: true });
|
|
18467
|
+
mkdirSync(join5(runtimeDir, "ready"), { recursive: true });
|
|
18468
|
+
ok("created .specialists/ (jobs/, ready/)");
|
|
18469
|
+
}
|
|
18470
|
+
const gitignorePath = join5(cwd, ".gitignore");
|
|
18471
|
+
if (existsSync3(gitignorePath)) {
|
|
18472
|
+
const existing = readFileSync(gitignorePath, "utf-8");
|
|
18473
|
+
if (existing.includes(GITIGNORE_ENTRY)) {
|
|
18474
|
+
skip(".gitignore already has .specialists/ entry");
|
|
18475
|
+
} else {
|
|
18476
|
+
const separator = existing.endsWith(`
|
|
18477
|
+
`) ? "" : `
|
|
18478
|
+
`;
|
|
18479
|
+
writeFileSync(gitignorePath, existing + separator + GITIGNORE_ENTRY + `
|
|
18480
|
+
`, "utf-8");
|
|
18481
|
+
ok("added .specialists/ to .gitignore");
|
|
18482
|
+
}
|
|
18483
|
+
} else {
|
|
18484
|
+
writeFileSync(gitignorePath, GITIGNORE_ENTRY + `
|
|
18485
|
+
`, "utf-8");
|
|
18486
|
+
ok("created .gitignore with .specialists/ entry");
|
|
18487
|
+
}
|
|
18270
18488
|
const agentsPath = join5(cwd, "AGENTS.md");
|
|
18271
18489
|
if (existsSync3(agentsPath)) {
|
|
18272
18490
|
const existing = readFileSync(agentsPath, "utf-8");
|
|
@@ -18283,15 +18501,15 @@ ${bold2("specialists init")}
|
|
|
18283
18501
|
ok("created AGENTS.md with Specialists section");
|
|
18284
18502
|
}
|
|
18285
18503
|
console.log(`
|
|
18286
|
-
${
|
|
18504
|
+
${bold3("Done!")}
|
|
18287
18505
|
`);
|
|
18288
|
-
console.log(` ${
|
|
18289
|
-
console.log(` 1. Add your specialists to ${
|
|
18290
|
-
console.log(` 2. Run ${
|
|
18506
|
+
console.log(` ${dim3("Next steps:")}`);
|
|
18507
|
+
console.log(` 1. Add your specialists to ${yellow3("specialists/")}`);
|
|
18508
|
+
console.log(` 2. Run ${yellow3("specialists list")} to verify they are discovered`);
|
|
18291
18509
|
console.log(` 3. Restart Claude Code to pick up AGENTS.md changes
|
|
18292
18510
|
`);
|
|
18293
18511
|
}
|
|
18294
|
-
var
|
|
18512
|
+
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/";
|
|
18295
18513
|
var init_init = __esm(() => {
|
|
18296
18514
|
AGENTS_BLOCK = `
|
|
18297
18515
|
## Specialists
|
|
@@ -18306,10 +18524,10 @@ specialist without user intervention.
|
|
|
18306
18524
|
// src/cli/edit.ts
|
|
18307
18525
|
var exports_edit = {};
|
|
18308
18526
|
__export(exports_edit, {
|
|
18309
|
-
run: () =>
|
|
18527
|
+
run: () => run6
|
|
18310
18528
|
});
|
|
18311
18529
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18312
|
-
function
|
|
18530
|
+
function parseArgs3(argv) {
|
|
18313
18531
|
const name = argv[0];
|
|
18314
18532
|
if (!name || name.startsWith("--")) {
|
|
18315
18533
|
console.error("Usage: specialists edit <name> --<field> <value> [--dry-run]");
|
|
@@ -18371,8 +18589,8 @@ function setIn(doc2, path, value) {
|
|
|
18371
18589
|
node.set(leaf, value);
|
|
18372
18590
|
}
|
|
18373
18591
|
}
|
|
18374
|
-
async function
|
|
18375
|
-
const args =
|
|
18592
|
+
async function run6() {
|
|
18593
|
+
const args = parseArgs3(process.argv.slice(3));
|
|
18376
18594
|
const { name, field, value, dryRun, scope } = args;
|
|
18377
18595
|
const loader = new SpecialistLoader;
|
|
18378
18596
|
const all = await loader.list();
|
|
@@ -18380,7 +18598,7 @@ async function run5() {
|
|
|
18380
18598
|
if (!match) {
|
|
18381
18599
|
const hint = scope ? ` (scope: ${scope})` : "";
|
|
18382
18600
|
console.error(`Error: specialist "${name}" not found${hint}`);
|
|
18383
|
-
console.error(` Run ${
|
|
18601
|
+
console.error(` Run ${yellow4("specialists list")} to see available specialists`);
|
|
18384
18602
|
process.exit(1);
|
|
18385
18603
|
}
|
|
18386
18604
|
const raw = readFileSync2(match.filePath, "utf-8");
|
|
@@ -18396,10 +18614,10 @@ async function run5() {
|
|
|
18396
18614
|
const updated = doc2.toString();
|
|
18397
18615
|
if (dryRun) {
|
|
18398
18616
|
console.log(`
|
|
18399
|
-
${
|
|
18617
|
+
${bold4(`[dry-run] ${match.filePath}`)}
|
|
18400
18618
|
`);
|
|
18401
|
-
console.log(
|
|
18402
|
-
console.log(
|
|
18619
|
+
console.log(dim4("--- current"));
|
|
18620
|
+
console.log(dim4(`+++ updated`));
|
|
18403
18621
|
const oldLines = raw.split(`
|
|
18404
18622
|
`);
|
|
18405
18623
|
const newLines = updated.split(`
|
|
@@ -18407,8 +18625,8 @@ ${bold3(`[dry-run] ${match.filePath}`)}
|
|
|
18407
18625
|
newLines.forEach((line, i) => {
|
|
18408
18626
|
if (line !== oldLines[i]) {
|
|
18409
18627
|
if (oldLines[i] !== undefined)
|
|
18410
|
-
console.log(
|
|
18411
|
-
console.log(
|
|
18628
|
+
console.log(dim4(`- ${oldLines[i]}`));
|
|
18629
|
+
console.log(green3(`+ ${line}`));
|
|
18412
18630
|
}
|
|
18413
18631
|
});
|
|
18414
18632
|
console.log();
|
|
@@ -18416,9 +18634,9 @@ ${bold3(`[dry-run] ${match.filePath}`)}
|
|
|
18416
18634
|
}
|
|
18417
18635
|
writeFileSync2(match.filePath, updated, "utf-8");
|
|
18418
18636
|
const displayValue = field === "tags" ? `[${typedValue.join(", ")}]` : String(typedValue);
|
|
18419
|
-
console.log(`${
|
|
18637
|
+
console.log(`${green3("✓")} ${bold4(name)}: ${yellow4(field)} = ${displayValue}` + dim4(` (${match.filePath})`));
|
|
18420
18638
|
}
|
|
18421
|
-
var
|
|
18639
|
+
var bold4 = (s) => `\x1B[1m${s}\x1B[0m`, green3 = (s) => `\x1B[32m${s}\x1B[0m`, yellow4 = (s) => `\x1B[33m${s}\x1B[0m`, dim4 = (s) => `\x1B[2m${s}\x1B[0m`, FIELD_MAP, VALID_PERMISSIONS;
|
|
18422
18640
|
var init_edit = __esm(() => {
|
|
18423
18641
|
init_dist();
|
|
18424
18642
|
init_loader();
|
|
@@ -18433,21 +18651,220 @@ var init_edit = __esm(() => {
|
|
|
18433
18651
|
VALID_PERMISSIONS = ["READ_ONLY", "LOW", "MEDIUM", "HIGH"];
|
|
18434
18652
|
});
|
|
18435
18653
|
|
|
18654
|
+
// src/specialist/supervisor.ts
|
|
18655
|
+
import {
|
|
18656
|
+
closeSync,
|
|
18657
|
+
existsSync as existsSync4,
|
|
18658
|
+
mkdirSync as mkdirSync2,
|
|
18659
|
+
openSync,
|
|
18660
|
+
readdirSync,
|
|
18661
|
+
readFileSync as readFileSync3,
|
|
18662
|
+
renameSync,
|
|
18663
|
+
rmSync,
|
|
18664
|
+
statSync,
|
|
18665
|
+
writeFileSync as writeFileSync3,
|
|
18666
|
+
writeSync
|
|
18667
|
+
} from "node:fs";
|
|
18668
|
+
import { join as join6 } from "node:path";
|
|
18669
|
+
|
|
18670
|
+
class Supervisor {
|
|
18671
|
+
opts;
|
|
18672
|
+
constructor(opts) {
|
|
18673
|
+
this.opts = opts;
|
|
18674
|
+
}
|
|
18675
|
+
jobDir(id) {
|
|
18676
|
+
return join6(this.opts.jobsDir, id);
|
|
18677
|
+
}
|
|
18678
|
+
statusPath(id) {
|
|
18679
|
+
return join6(this.jobDir(id), "status.json");
|
|
18680
|
+
}
|
|
18681
|
+
resultPath(id) {
|
|
18682
|
+
return join6(this.jobDir(id), "result.txt");
|
|
18683
|
+
}
|
|
18684
|
+
eventsPath(id) {
|
|
18685
|
+
return join6(this.jobDir(id), "events.jsonl");
|
|
18686
|
+
}
|
|
18687
|
+
readyDir() {
|
|
18688
|
+
return join6(this.opts.jobsDir, "..", "ready");
|
|
18689
|
+
}
|
|
18690
|
+
readStatus(id) {
|
|
18691
|
+
const path = this.statusPath(id);
|
|
18692
|
+
if (!existsSync4(path))
|
|
18693
|
+
return null;
|
|
18694
|
+
try {
|
|
18695
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
18696
|
+
} catch {
|
|
18697
|
+
return null;
|
|
18698
|
+
}
|
|
18699
|
+
}
|
|
18700
|
+
listJobs() {
|
|
18701
|
+
if (!existsSync4(this.opts.jobsDir))
|
|
18702
|
+
return [];
|
|
18703
|
+
const jobs = [];
|
|
18704
|
+
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18705
|
+
const path = join6(this.opts.jobsDir, entry, "status.json");
|
|
18706
|
+
if (!existsSync4(path))
|
|
18707
|
+
continue;
|
|
18708
|
+
try {
|
|
18709
|
+
jobs.push(JSON.parse(readFileSync3(path, "utf-8")));
|
|
18710
|
+
} catch {}
|
|
18711
|
+
}
|
|
18712
|
+
return jobs.sort((a, b) => b.started_at_ms - a.started_at_ms);
|
|
18713
|
+
}
|
|
18714
|
+
writeStatusFile(id, data) {
|
|
18715
|
+
const path = this.statusPath(id);
|
|
18716
|
+
const tmp = path + ".tmp";
|
|
18717
|
+
writeFileSync3(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
18718
|
+
renameSync(tmp, path);
|
|
18719
|
+
}
|
|
18720
|
+
updateStatus(id, updates) {
|
|
18721
|
+
const current = this.readStatus(id);
|
|
18722
|
+
if (!current)
|
|
18723
|
+
return;
|
|
18724
|
+
this.writeStatusFile(id, { ...current, ...updates });
|
|
18725
|
+
}
|
|
18726
|
+
gc() {
|
|
18727
|
+
if (!existsSync4(this.opts.jobsDir))
|
|
18728
|
+
return;
|
|
18729
|
+
const cutoff = Date.now() - JOB_TTL_DAYS * 86400000;
|
|
18730
|
+
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18731
|
+
const dir = join6(this.opts.jobsDir, entry);
|
|
18732
|
+
try {
|
|
18733
|
+
const stat2 = statSync(dir);
|
|
18734
|
+
if (!stat2.isDirectory())
|
|
18735
|
+
continue;
|
|
18736
|
+
if (stat2.mtimeMs < cutoff)
|
|
18737
|
+
rmSync(dir, { recursive: true, force: true });
|
|
18738
|
+
} catch {}
|
|
18739
|
+
}
|
|
18740
|
+
}
|
|
18741
|
+
crashRecovery() {
|
|
18742
|
+
if (!existsSync4(this.opts.jobsDir))
|
|
18743
|
+
return;
|
|
18744
|
+
for (const entry of readdirSync(this.opts.jobsDir)) {
|
|
18745
|
+
const statusPath = join6(this.opts.jobsDir, entry, "status.json");
|
|
18746
|
+
if (!existsSync4(statusPath))
|
|
18747
|
+
continue;
|
|
18748
|
+
try {
|
|
18749
|
+
const s = JSON.parse(readFileSync3(statusPath, "utf-8"));
|
|
18750
|
+
if (s.status !== "running" && s.status !== "starting")
|
|
18751
|
+
continue;
|
|
18752
|
+
if (!s.pid)
|
|
18753
|
+
continue;
|
|
18754
|
+
try {
|
|
18755
|
+
process.kill(s.pid, 0);
|
|
18756
|
+
} catch {
|
|
18757
|
+
const tmp = statusPath + ".tmp";
|
|
18758
|
+
const updated = { ...s, status: "error", error: "Process crashed or was killed" };
|
|
18759
|
+
writeFileSync3(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
18760
|
+
renameSync(tmp, statusPath);
|
|
18761
|
+
}
|
|
18762
|
+
} catch {}
|
|
18763
|
+
}
|
|
18764
|
+
}
|
|
18765
|
+
async run() {
|
|
18766
|
+
const { runner, runOptions, jobsDir } = this.opts;
|
|
18767
|
+
this.gc();
|
|
18768
|
+
this.crashRecovery();
|
|
18769
|
+
const id = crypto.randomUUID().slice(0, 6);
|
|
18770
|
+
const dir = this.jobDir(id);
|
|
18771
|
+
const startedAtMs = Date.now();
|
|
18772
|
+
mkdirSync2(dir, { recursive: true });
|
|
18773
|
+
mkdirSync2(this.readyDir(), { recursive: true });
|
|
18774
|
+
const initialStatus = {
|
|
18775
|
+
id,
|
|
18776
|
+
specialist: runOptions.name,
|
|
18777
|
+
status: "starting",
|
|
18778
|
+
started_at_ms: startedAtMs,
|
|
18779
|
+
pid: process.pid
|
|
18780
|
+
};
|
|
18781
|
+
this.writeStatusFile(id, initialStatus);
|
|
18782
|
+
const eventsFd = openSync(this.eventsPath(id), "a");
|
|
18783
|
+
const appendEvent = (obj) => {
|
|
18784
|
+
try {
|
|
18785
|
+
writeSync(eventsFd, JSON.stringify({ t: Date.now(), ...obj }) + `
|
|
18786
|
+
`);
|
|
18787
|
+
} catch {}
|
|
18788
|
+
};
|
|
18789
|
+
let textLogged = false;
|
|
18790
|
+
let currentTool = "";
|
|
18791
|
+
try {
|
|
18792
|
+
const result = await runner.run(runOptions, (delta) => {
|
|
18793
|
+
const toolMatch = delta.match(/⚙ (.+?)…/);
|
|
18794
|
+
if (toolMatch) {
|
|
18795
|
+
currentTool = toolMatch[1];
|
|
18796
|
+
this.updateStatus(id, { current_tool: currentTool });
|
|
18797
|
+
}
|
|
18798
|
+
}, (eventType) => {
|
|
18799
|
+
const now = Date.now();
|
|
18800
|
+
this.updateStatus(id, {
|
|
18801
|
+
status: "running",
|
|
18802
|
+
current_event: eventType,
|
|
18803
|
+
last_event_at_ms: now,
|
|
18804
|
+
elapsed_s: Math.round((now - startedAtMs) / 1000)
|
|
18805
|
+
});
|
|
18806
|
+
if (LOGGED_EVENTS.has(eventType)) {
|
|
18807
|
+
const tool = eventType === "toolcall" || eventType === "tool_execution_end" ? currentTool : undefined;
|
|
18808
|
+
appendEvent({ type: eventType, ...tool ? { tool } : {} });
|
|
18809
|
+
} else if (eventType === "text" && !textLogged) {
|
|
18810
|
+
textLogged = true;
|
|
18811
|
+
appendEvent({ type: "text" });
|
|
18812
|
+
}
|
|
18813
|
+
}, (meta) => {
|
|
18814
|
+
this.updateStatus(id, { model: meta.model, backend: meta.backend });
|
|
18815
|
+
appendEvent({ type: "meta", model: meta.model, backend: meta.backend });
|
|
18816
|
+
}, (_killFn) => {}, (beadId) => {
|
|
18817
|
+
this.updateStatus(id, { bead_id: beadId });
|
|
18818
|
+
});
|
|
18819
|
+
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
18820
|
+
writeFileSync3(this.resultPath(id), result.output, "utf-8");
|
|
18821
|
+
this.updateStatus(id, {
|
|
18822
|
+
status: "done",
|
|
18823
|
+
elapsed_s: elapsed,
|
|
18824
|
+
last_event_at_ms: Date.now(),
|
|
18825
|
+
model: result.model,
|
|
18826
|
+
backend: result.backend,
|
|
18827
|
+
bead_id: result.beadId
|
|
18828
|
+
});
|
|
18829
|
+
appendEvent({ type: "agent_end", elapsed_s: elapsed });
|
|
18830
|
+
writeFileSync3(join6(this.readyDir(), id), "", "utf-8");
|
|
18831
|
+
return id;
|
|
18832
|
+
} catch (err) {
|
|
18833
|
+
const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
|
|
18834
|
+
this.updateStatus(id, {
|
|
18835
|
+
status: "error",
|
|
18836
|
+
elapsed_s: elapsed,
|
|
18837
|
+
error: err?.message ?? String(err)
|
|
18838
|
+
});
|
|
18839
|
+
appendEvent({ type: "error", message: err?.message ?? String(err) });
|
|
18840
|
+
throw err;
|
|
18841
|
+
} finally {
|
|
18842
|
+
closeSync(eventsFd);
|
|
18843
|
+
}
|
|
18844
|
+
}
|
|
18845
|
+
}
|
|
18846
|
+
var JOB_TTL_DAYS, LOGGED_EVENTS;
|
|
18847
|
+
var init_supervisor = __esm(() => {
|
|
18848
|
+
JOB_TTL_DAYS = Number(process.env.SPECIALISTS_JOB_TTL_DAYS ?? 7);
|
|
18849
|
+
LOGGED_EVENTS = new Set(["thinking", "toolcall", "tool_execution_end", "done"]);
|
|
18850
|
+
});
|
|
18851
|
+
|
|
18436
18852
|
// src/cli/run.ts
|
|
18437
18853
|
var exports_run = {};
|
|
18438
18854
|
__export(exports_run, {
|
|
18439
|
-
run: () =>
|
|
18855
|
+
run: () => run7
|
|
18440
18856
|
});
|
|
18441
|
-
import { join as
|
|
18442
|
-
async function
|
|
18857
|
+
import { join as join7 } from "node:path";
|
|
18858
|
+
async function parseArgs4(argv) {
|
|
18443
18859
|
const name = argv[0];
|
|
18444
18860
|
if (!name || name.startsWith("--")) {
|
|
18445
|
-
console.error('Usage: specialists run <name> [--prompt "..."] [--model <model>] [--no-beads]');
|
|
18861
|
+
console.error('Usage: specialists run <name> [--prompt "..."] [--model <model>] [--no-beads] [--background]');
|
|
18446
18862
|
process.exit(1);
|
|
18447
18863
|
}
|
|
18448
18864
|
let prompt = "";
|
|
18449
18865
|
let model;
|
|
18450
18866
|
let noBeads = false;
|
|
18867
|
+
let background = false;
|
|
18451
18868
|
for (let i = 1;i < argv.length; i++) {
|
|
18452
18869
|
const token = argv[i];
|
|
18453
18870
|
if (token === "--prompt" && argv[i + 1]) {
|
|
@@ -18462,10 +18879,14 @@ async function parseArgs3(argv) {
|
|
|
18462
18879
|
noBeads = true;
|
|
18463
18880
|
continue;
|
|
18464
18881
|
}
|
|
18882
|
+
if (token === "--background") {
|
|
18883
|
+
background = true;
|
|
18884
|
+
continue;
|
|
18885
|
+
}
|
|
18465
18886
|
}
|
|
18466
18887
|
if (!prompt) {
|
|
18467
18888
|
if (process.stdin.isTTY) {
|
|
18468
|
-
process.stderr.write(
|
|
18889
|
+
process.stderr.write(dim5("Prompt (Ctrl+D when done): "));
|
|
18469
18890
|
}
|
|
18470
18891
|
prompt = await new Promise((resolve) => {
|
|
18471
18892
|
let buf = "";
|
|
@@ -18476,13 +18897,13 @@ async function parseArgs3(argv) {
|
|
|
18476
18897
|
process.stdin.on("end", () => resolve(buf.trim()));
|
|
18477
18898
|
});
|
|
18478
18899
|
}
|
|
18479
|
-
return { name, prompt, model, noBeads };
|
|
18900
|
+
return { name, prompt, model, noBeads, background };
|
|
18480
18901
|
}
|
|
18481
|
-
async function
|
|
18482
|
-
const args = await
|
|
18902
|
+
async function run7() {
|
|
18903
|
+
const args = await parseArgs4(process.argv.slice(3));
|
|
18483
18904
|
const loader = new SpecialistLoader;
|
|
18484
18905
|
const circuitBreaker = new CircuitBreaker;
|
|
18485
|
-
const hooks = new HookEmitter({ tracePath:
|
|
18906
|
+
const hooks = new HookEmitter({ tracePath: join7(process.cwd(), ".specialists", "trace.jsonl") });
|
|
18486
18907
|
const beadsClient = args.noBeads ? null : new BeadsClient;
|
|
18487
18908
|
const runner = new SpecialistRunner({
|
|
18488
18909
|
loader,
|
|
@@ -18490,8 +18911,26 @@ async function run6() {
|
|
|
18490
18911
|
circuitBreaker,
|
|
18491
18912
|
beadsClient: beadsClient ?? undefined
|
|
18492
18913
|
});
|
|
18914
|
+
if (args.background) {
|
|
18915
|
+
const jobsDir = join7(process.cwd(), ".specialists", "jobs");
|
|
18916
|
+
const supervisor = new Supervisor({
|
|
18917
|
+
runner,
|
|
18918
|
+
runOptions: { name: args.name, prompt: args.prompt, backendOverride: args.model },
|
|
18919
|
+
jobsDir
|
|
18920
|
+
});
|
|
18921
|
+
try {
|
|
18922
|
+
const jobId = await supervisor.run();
|
|
18923
|
+
process.stdout.write(`Job started: ${jobId}
|
|
18924
|
+
`);
|
|
18925
|
+
} catch (err) {
|
|
18926
|
+
process.stderr.write(`Error: ${err?.message ?? err}
|
|
18927
|
+
`);
|
|
18928
|
+
process.exit(1);
|
|
18929
|
+
}
|
|
18930
|
+
return;
|
|
18931
|
+
}
|
|
18493
18932
|
process.stderr.write(`
|
|
18494
|
-
${
|
|
18933
|
+
${bold5(`Running ${cyan3(args.name)}`)}
|
|
18495
18934
|
|
|
18496
18935
|
`);
|
|
18497
18936
|
let beadId;
|
|
@@ -18499,7 +18938,7 @@ ${bold4(`Running ${cyan2(args.name)}`)}
|
|
|
18499
18938
|
name: args.name,
|
|
18500
18939
|
prompt: args.prompt,
|
|
18501
18940
|
backendOverride: args.model
|
|
18502
|
-
}, (delta) => process.stdout.write(delta), undefined, (meta) => process.stderr.write(
|
|
18941
|
+
}, (delta) => process.stdout.write(delta), undefined, (meta) => process.stderr.write(dim5(`
|
|
18503
18942
|
[${meta.backend} / ${meta.model}]
|
|
18504
18943
|
|
|
18505
18944
|
`)), (killFn) => {
|
|
@@ -18513,7 +18952,7 @@ Interrupted.
|
|
|
18513
18952
|
});
|
|
18514
18953
|
}, (id) => {
|
|
18515
18954
|
beadId = id;
|
|
18516
|
-
process.stderr.write(
|
|
18955
|
+
process.stderr.write(dim5(`
|
|
18517
18956
|
[bead: ${id}]
|
|
18518
18957
|
`));
|
|
18519
18958
|
});
|
|
@@ -18525,48 +18964,49 @@ Interrupted.
|
|
|
18525
18964
|
const footer = [
|
|
18526
18965
|
beadId ? `bead ${beadId}` : "",
|
|
18527
18966
|
`${secs}s`,
|
|
18528
|
-
|
|
18967
|
+
dim5(result.model)
|
|
18529
18968
|
].filter(Boolean).join(" ");
|
|
18530
18969
|
process.stderr.write(`
|
|
18531
|
-
${
|
|
18970
|
+
${green4("✓")} ${footer}
|
|
18532
18971
|
|
|
18533
18972
|
`);
|
|
18534
18973
|
}
|
|
18535
|
-
var
|
|
18974
|
+
var bold5 = (s) => `\x1B[1m${s}\x1B[0m`, dim5 = (s) => `\x1B[2m${s}\x1B[0m`, green4 = (s) => `\x1B[32m${s}\x1B[0m`, cyan3 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
18536
18975
|
var init_run = __esm(() => {
|
|
18537
18976
|
init_loader();
|
|
18538
18977
|
init_runner();
|
|
18539
18978
|
init_hooks();
|
|
18540
18979
|
init_beads();
|
|
18980
|
+
init_supervisor();
|
|
18541
18981
|
});
|
|
18542
18982
|
|
|
18543
18983
|
// src/cli/status.ts
|
|
18544
18984
|
var exports_status = {};
|
|
18545
18985
|
__export(exports_status, {
|
|
18546
|
-
run: () =>
|
|
18986
|
+
run: () => run8
|
|
18547
18987
|
});
|
|
18548
|
-
import { spawnSync as
|
|
18549
|
-
import { existsSync as
|
|
18550
|
-
import { join as
|
|
18988
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
18989
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
18990
|
+
import { join as join8 } from "node:path";
|
|
18551
18991
|
function ok2(msg) {
|
|
18552
|
-
console.log(` ${
|
|
18992
|
+
console.log(` ${green5("✓")} ${msg}`);
|
|
18553
18993
|
}
|
|
18554
18994
|
function warn(msg) {
|
|
18555
|
-
console.log(` ${
|
|
18995
|
+
console.log(` ${yellow5("○")} ${msg}`);
|
|
18556
18996
|
}
|
|
18557
18997
|
function fail(msg) {
|
|
18558
18998
|
console.log(` ${red("✗")} ${msg}`);
|
|
18559
18999
|
}
|
|
18560
19000
|
function info(msg) {
|
|
18561
|
-
console.log(` ${
|
|
19001
|
+
console.log(` ${dim6(msg)}`);
|
|
18562
19002
|
}
|
|
18563
19003
|
function section(label) {
|
|
18564
19004
|
const line = "─".repeat(Math.max(0, 38 - label.length));
|
|
18565
19005
|
console.log(`
|
|
18566
|
-
${
|
|
19006
|
+
${bold6(`── ${label} ${line}`)}`);
|
|
18567
19007
|
}
|
|
18568
19008
|
function cmd(bin, args) {
|
|
18569
|
-
const r =
|
|
19009
|
+
const r = spawnSync4(bin, args, {
|
|
18570
19010
|
encoding: "utf8",
|
|
18571
19011
|
stdio: "pipe",
|
|
18572
19012
|
timeout: 5000
|
|
@@ -18574,102 +19014,323 @@ function cmd(bin, args) {
|
|
|
18574
19014
|
return { ok: r.status === 0 && !r.error, stdout: (r.stdout ?? "").trim() };
|
|
18575
19015
|
}
|
|
18576
19016
|
function isInstalled(bin) {
|
|
18577
|
-
return
|
|
19017
|
+
return spawnSync4("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
19018
|
+
}
|
|
19019
|
+
function formatElapsed(s) {
|
|
19020
|
+
if (s.elapsed_s === undefined)
|
|
19021
|
+
return "...";
|
|
19022
|
+
const m = Math.floor(s.elapsed_s / 60);
|
|
19023
|
+
const sec = s.elapsed_s % 60;
|
|
19024
|
+
return m > 0 ? `${m}m${sec.toString().padStart(2, "0")}s` : `${sec}s`;
|
|
19025
|
+
}
|
|
19026
|
+
function statusColor(status) {
|
|
19027
|
+
switch (status) {
|
|
19028
|
+
case "running":
|
|
19029
|
+
return cyan4(status);
|
|
19030
|
+
case "done":
|
|
19031
|
+
return green5(status);
|
|
19032
|
+
case "error":
|
|
19033
|
+
return red(status);
|
|
19034
|
+
case "starting":
|
|
19035
|
+
return yellow5(status);
|
|
19036
|
+
default:
|
|
19037
|
+
return status;
|
|
19038
|
+
}
|
|
18578
19039
|
}
|
|
18579
|
-
async function
|
|
19040
|
+
async function run8() {
|
|
18580
19041
|
console.log(`
|
|
18581
|
-
${
|
|
19042
|
+
${bold6("specialists status")}
|
|
18582
19043
|
`);
|
|
18583
19044
|
section("Specialists");
|
|
18584
19045
|
const loader = new SpecialistLoader;
|
|
18585
19046
|
const all = await loader.list();
|
|
18586
19047
|
if (all.length === 0) {
|
|
18587
|
-
warn(`no specialists found — run ${
|
|
19048
|
+
warn(`no specialists found — run ${yellow5("specialists init")} to scaffold`);
|
|
18588
19049
|
} else {
|
|
18589
19050
|
const byScope = all.reduce((acc, s) => {
|
|
18590
19051
|
acc[s.scope] = (acc[s.scope] ?? 0) + 1;
|
|
18591
19052
|
return acc;
|
|
18592
19053
|
}, {});
|
|
18593
19054
|
const scopeSummary = Object.entries(byScope).map(([scope, n]) => `${n} ${scope}`).join(", ");
|
|
18594
|
-
ok2(`${all.length} found ${
|
|
19055
|
+
ok2(`${all.length} found ${dim6(`(${scopeSummary})`)}`);
|
|
18595
19056
|
for (const s of all) {
|
|
18596
19057
|
const staleness = await checkStaleness(s);
|
|
18597
19058
|
if (staleness === "AGED") {
|
|
18598
|
-
warn(`${s.name} ${red("AGED")} ${
|
|
19059
|
+
warn(`${s.name} ${red("AGED")} ${dim6(s.scope)}`);
|
|
18599
19060
|
} else if (staleness === "STALE") {
|
|
18600
|
-
warn(`${s.name} ${
|
|
19061
|
+
warn(`${s.name} ${yellow5("STALE")} ${dim6(s.scope)}`);
|
|
18601
19062
|
}
|
|
18602
19063
|
}
|
|
18603
19064
|
}
|
|
18604
19065
|
section("pi (coding agent runtime)");
|
|
18605
19066
|
if (!isInstalled("pi")) {
|
|
18606
|
-
fail(`pi not installed — run ${
|
|
19067
|
+
fail(`pi not installed — run ${yellow5("specialists install")}`);
|
|
18607
19068
|
} else {
|
|
18608
19069
|
const version2 = cmd("pi", ["--version"]);
|
|
18609
19070
|
const models = cmd("pi", ["--list-models"]);
|
|
18610
19071
|
const providers = new Set(models.stdout.split(`
|
|
18611
19072
|
`).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean));
|
|
18612
19073
|
const vStr = version2.ok ? `v${version2.stdout}` : "unknown version";
|
|
18613
|
-
const pStr = providers.size > 0 ? `${providers.size} provider${providers.size > 1 ? "s" : ""} active ${
|
|
19074
|
+
const pStr = providers.size > 0 ? `${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim6(`(${[...providers].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
|
|
18614
19075
|
ok2(`${vStr} — ${pStr}`);
|
|
18615
19076
|
}
|
|
18616
19077
|
section("beads (issue tracker)");
|
|
18617
19078
|
if (!isInstalled("bd")) {
|
|
18618
|
-
fail(`bd not installed — run ${
|
|
19079
|
+
fail(`bd not installed — run ${yellow5("specialists install")}`);
|
|
18619
19080
|
} else {
|
|
18620
19081
|
const bdVersion = cmd("bd", ["--version"]);
|
|
18621
|
-
ok2(`bd installed${bdVersion.ok ? ` ${
|
|
18622
|
-
if (
|
|
19082
|
+
ok2(`bd installed${bdVersion.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
|
|
19083
|
+
if (existsSync5(join8(process.cwd(), ".beads"))) {
|
|
18623
19084
|
ok2(".beads/ present in project");
|
|
18624
19085
|
} else {
|
|
18625
|
-
warn(`.beads/ not found — run ${
|
|
19086
|
+
warn(`.beads/ not found — run ${yellow5("bd init")} to enable issue tracking`);
|
|
18626
19087
|
}
|
|
18627
19088
|
}
|
|
18628
19089
|
section("MCP");
|
|
18629
19090
|
const specialistsBin = cmd("which", ["specialists"]);
|
|
18630
19091
|
if (!specialistsBin.ok) {
|
|
18631
|
-
fail(`specialists not installed globally — run ${
|
|
19092
|
+
fail(`specialists not installed globally — run ${yellow5("npm install -g @jaggerxtrm/specialists")}`);
|
|
18632
19093
|
} else {
|
|
18633
|
-
ok2(`specialists binary installed ${
|
|
19094
|
+
ok2(`specialists binary installed ${dim6(specialistsBin.stdout)}`);
|
|
18634
19095
|
info(`verify registration: claude mcp get specialists`);
|
|
18635
19096
|
info(`re-register: specialists install`);
|
|
18636
19097
|
}
|
|
19098
|
+
const jobsDir = join8(process.cwd(), ".specialists", "jobs");
|
|
19099
|
+
if (existsSync5(jobsDir)) {
|
|
19100
|
+
const supervisor = new Supervisor({
|
|
19101
|
+
runner: null,
|
|
19102
|
+
runOptions: null,
|
|
19103
|
+
jobsDir
|
|
19104
|
+
});
|
|
19105
|
+
const jobs = supervisor.listJobs();
|
|
19106
|
+
if (jobs.length > 0) {
|
|
19107
|
+
section("Active Jobs");
|
|
19108
|
+
for (const job of jobs) {
|
|
19109
|
+
const elapsed = formatElapsed(job);
|
|
19110
|
+
const detail = job.status === "error" ? red(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
|
|
19111
|
+
console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
|
|
19112
|
+
}
|
|
19113
|
+
}
|
|
19114
|
+
}
|
|
18637
19115
|
console.log();
|
|
18638
19116
|
}
|
|
18639
|
-
var
|
|
19117
|
+
var bold6 = (s) => `\x1B[1m${s}\x1B[0m`, dim6 = (s) => `\x1B[2m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, yellow5 = (s) => `\x1B[33m${s}\x1B[0m`, red = (s) => `\x1B[31m${s}\x1B[0m`, cyan4 = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
18640
19118
|
var init_status = __esm(() => {
|
|
18641
19119
|
init_loader();
|
|
19120
|
+
init_supervisor();
|
|
19121
|
+
});
|
|
19122
|
+
|
|
19123
|
+
// src/cli/result.ts
|
|
19124
|
+
var exports_result = {};
|
|
19125
|
+
__export(exports_result, {
|
|
19126
|
+
run: () => run9
|
|
19127
|
+
});
|
|
19128
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
19129
|
+
import { join as join9 } from "node:path";
|
|
19130
|
+
async function run9() {
|
|
19131
|
+
const jobId = process.argv[3];
|
|
19132
|
+
if (!jobId) {
|
|
19133
|
+
console.error("Usage: specialists result <job-id>");
|
|
19134
|
+
process.exit(1);
|
|
19135
|
+
}
|
|
19136
|
+
const jobsDir = join9(process.cwd(), ".specialists", "jobs");
|
|
19137
|
+
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19138
|
+
const status = supervisor.readStatus(jobId);
|
|
19139
|
+
if (!status) {
|
|
19140
|
+
console.error(`No job found: ${jobId}`);
|
|
19141
|
+
process.exit(1);
|
|
19142
|
+
}
|
|
19143
|
+
if (status.status === "running" || status.status === "starting") {
|
|
19144
|
+
process.stderr.write(`${dim7(`Job ${jobId} is still ${status.status}. Use 'specialists feed --job ${jobId}' to follow.`)}
|
|
19145
|
+
`);
|
|
19146
|
+
process.exit(1);
|
|
19147
|
+
}
|
|
19148
|
+
if (status.status === "error") {
|
|
19149
|
+
process.stderr.write(`${red2(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
|
|
19150
|
+
`);
|
|
19151
|
+
process.exit(1);
|
|
19152
|
+
}
|
|
19153
|
+
const resultPath = join9(jobsDir, jobId, "result.txt");
|
|
19154
|
+
if (!existsSync6(resultPath)) {
|
|
19155
|
+
console.error(`Result file not found for job ${jobId}`);
|
|
19156
|
+
process.exit(1);
|
|
19157
|
+
}
|
|
19158
|
+
process.stdout.write(readFileSync4(resultPath, "utf-8"));
|
|
19159
|
+
}
|
|
19160
|
+
var dim7 = (s) => `\x1B[2m${s}\x1B[0m`, red2 = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
19161
|
+
var init_result = __esm(() => {
|
|
19162
|
+
init_supervisor();
|
|
19163
|
+
});
|
|
19164
|
+
|
|
19165
|
+
// src/cli/feed.ts
|
|
19166
|
+
var exports_feed = {};
|
|
19167
|
+
__export(exports_feed, {
|
|
19168
|
+
run: () => run10
|
|
19169
|
+
});
|
|
19170
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, watchFile } from "node:fs";
|
|
19171
|
+
import { join as join10 } from "node:path";
|
|
19172
|
+
function formatEvent(line) {
|
|
19173
|
+
try {
|
|
19174
|
+
const e = JSON.parse(line);
|
|
19175
|
+
const ts = new Date(e.t).toISOString().slice(11, 19);
|
|
19176
|
+
const type = e.type ?? "?";
|
|
19177
|
+
const extra = e.tool ? ` ${cyan5(e.tool)}` : e.model ? ` ${dim8(e.model)}` : e.message ? ` ${red3(e.message)}` : "";
|
|
19178
|
+
return `${dim8(ts)} ${type}${extra}`;
|
|
19179
|
+
} catch {
|
|
19180
|
+
return line;
|
|
19181
|
+
}
|
|
19182
|
+
}
|
|
19183
|
+
function printLines(content, from) {
|
|
19184
|
+
const lines = content.split(`
|
|
19185
|
+
`).filter(Boolean);
|
|
19186
|
+
for (let i = from;i < lines.length; i++) {
|
|
19187
|
+
console.log(formatEvent(lines[i]));
|
|
19188
|
+
}
|
|
19189
|
+
return lines.length;
|
|
19190
|
+
}
|
|
19191
|
+
async function run10() {
|
|
19192
|
+
const argv = process.argv.slice(3);
|
|
19193
|
+
let jobId;
|
|
19194
|
+
let follow = false;
|
|
19195
|
+
for (let i = 0;i < argv.length; i++) {
|
|
19196
|
+
if (argv[i] === "--job" && argv[i + 1]) {
|
|
19197
|
+
jobId = argv[++i];
|
|
19198
|
+
continue;
|
|
19199
|
+
}
|
|
19200
|
+
if (argv[i] === "--follow" || argv[i] === "-f") {
|
|
19201
|
+
follow = true;
|
|
19202
|
+
continue;
|
|
19203
|
+
}
|
|
19204
|
+
if (!jobId && !argv[i].startsWith("--"))
|
|
19205
|
+
jobId = argv[i];
|
|
19206
|
+
}
|
|
19207
|
+
if (!jobId) {
|
|
19208
|
+
console.error("Usage: specialists feed --job <job-id> [--follow]");
|
|
19209
|
+
process.exit(1);
|
|
19210
|
+
}
|
|
19211
|
+
const jobsDir = join10(process.cwd(), ".specialists", "jobs");
|
|
19212
|
+
const eventsPath = join10(jobsDir, jobId, "events.jsonl");
|
|
19213
|
+
if (!existsSync7(eventsPath)) {
|
|
19214
|
+
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19215
|
+
if (!supervisor.readStatus(jobId)) {
|
|
19216
|
+
console.error(`No job found: ${jobId}`);
|
|
19217
|
+
process.exit(1);
|
|
19218
|
+
}
|
|
19219
|
+
console.log(dim8("No events yet."));
|
|
19220
|
+
return;
|
|
19221
|
+
}
|
|
19222
|
+
const content = readFileSync5(eventsPath, "utf-8");
|
|
19223
|
+
let linesRead = printLines(content, 0);
|
|
19224
|
+
if (!follow)
|
|
19225
|
+
return;
|
|
19226
|
+
process.stderr.write(dim8(`Following ${jobId}... (Ctrl+C to stop)
|
|
19227
|
+
`));
|
|
19228
|
+
await new Promise((resolve) => {
|
|
19229
|
+
watchFile(eventsPath, { interval: 500 }, () => {
|
|
19230
|
+
try {
|
|
19231
|
+
const updated = readFileSync5(eventsPath, "utf-8");
|
|
19232
|
+
linesRead = printLines(updated, linesRead);
|
|
19233
|
+
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19234
|
+
const status = supervisor.readStatus(jobId);
|
|
19235
|
+
if (status && status.status !== "running" && status.status !== "starting") {
|
|
19236
|
+
const finalMsg = status.status === "done" ? `
|
|
19237
|
+
${yellow6("Job complete.")} Run: specialists result ${jobId}` : `
|
|
19238
|
+
${red3(`Job ${status.status}.`)} ${status.error ?? ""}`;
|
|
19239
|
+
process.stderr.write(finalMsg + `
|
|
19240
|
+
`);
|
|
19241
|
+
resolve();
|
|
19242
|
+
}
|
|
19243
|
+
} catch {}
|
|
19244
|
+
});
|
|
19245
|
+
});
|
|
19246
|
+
}
|
|
19247
|
+
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`;
|
|
19248
|
+
var init_feed = __esm(() => {
|
|
19249
|
+
init_supervisor();
|
|
19250
|
+
});
|
|
19251
|
+
|
|
19252
|
+
// src/cli/stop.ts
|
|
19253
|
+
var exports_stop = {};
|
|
19254
|
+
__export(exports_stop, {
|
|
19255
|
+
run: () => run11
|
|
19256
|
+
});
|
|
19257
|
+
import { join as join11 } from "node:path";
|
|
19258
|
+
async function run11() {
|
|
19259
|
+
const jobId = process.argv[3];
|
|
19260
|
+
if (!jobId) {
|
|
19261
|
+
console.error("Usage: specialists stop <job-id>");
|
|
19262
|
+
process.exit(1);
|
|
19263
|
+
}
|
|
19264
|
+
const jobsDir = join11(process.cwd(), ".specialists", "jobs");
|
|
19265
|
+
const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
|
|
19266
|
+
const status = supervisor.readStatus(jobId);
|
|
19267
|
+
if (!status) {
|
|
19268
|
+
console.error(`No job found: ${jobId}`);
|
|
19269
|
+
process.exit(1);
|
|
19270
|
+
}
|
|
19271
|
+
if (status.status === "done" || status.status === "error") {
|
|
19272
|
+
process.stderr.write(`${dim9(`Job ${jobId} is already ${status.status}.`)}
|
|
19273
|
+
`);
|
|
19274
|
+
return;
|
|
19275
|
+
}
|
|
19276
|
+
if (!status.pid) {
|
|
19277
|
+
process.stderr.write(`${red4(`No PID recorded for job ${jobId}.`)}
|
|
19278
|
+
`);
|
|
19279
|
+
process.exit(1);
|
|
19280
|
+
}
|
|
19281
|
+
try {
|
|
19282
|
+
process.kill(status.pid, "SIGTERM");
|
|
19283
|
+
process.stdout.write(`${green6("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
|
|
19284
|
+
`);
|
|
19285
|
+
} catch (err) {
|
|
19286
|
+
if (err.code === "ESRCH") {
|
|
19287
|
+
process.stderr.write(`${red4(`Process ${status.pid} not found.`)} Job may have already completed.
|
|
19288
|
+
`);
|
|
19289
|
+
} else {
|
|
19290
|
+
process.stderr.write(`${red4("Error:")} ${err.message}
|
|
19291
|
+
`);
|
|
19292
|
+
process.exit(1);
|
|
19293
|
+
}
|
|
19294
|
+
}
|
|
19295
|
+
}
|
|
19296
|
+
var green6 = (s) => `\x1B[32m${s}\x1B[0m`, red4 = (s) => `\x1B[31m${s}\x1B[0m`, dim9 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
19297
|
+
var init_stop = __esm(() => {
|
|
19298
|
+
init_supervisor();
|
|
18642
19299
|
});
|
|
18643
19300
|
|
|
18644
19301
|
// src/cli/help.ts
|
|
18645
19302
|
var exports_help = {};
|
|
18646
19303
|
__export(exports_help, {
|
|
18647
|
-
run: () =>
|
|
19304
|
+
run: () => run12
|
|
18648
19305
|
});
|
|
18649
|
-
async function
|
|
19306
|
+
async function run12() {
|
|
18650
19307
|
const lines = [
|
|
18651
19308
|
"",
|
|
18652
|
-
|
|
19309
|
+
bold7("specialists <command>"),
|
|
18653
19310
|
"",
|
|
18654
19311
|
"Commands:",
|
|
18655
|
-
...COMMANDS.map(([cmd2, desc]) => ` ${cmd2.padEnd(COL_WIDTH)} ${
|
|
19312
|
+
...COMMANDS.map(([cmd2, desc]) => ` ${cmd2.padEnd(COL_WIDTH)} ${dim10(desc)}`),
|
|
18656
19313
|
"",
|
|
18657
|
-
|
|
19314
|
+
dim10("Run 'specialists <command> --help' for command-specific options."),
|
|
18658
19315
|
""
|
|
18659
19316
|
];
|
|
18660
19317
|
console.log(lines.join(`
|
|
18661
19318
|
`));
|
|
18662
19319
|
}
|
|
18663
|
-
var
|
|
19320
|
+
var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, COMMANDS, COL_WIDTH;
|
|
18664
19321
|
var init_help = __esm(() => {
|
|
18665
19322
|
COMMANDS = [
|
|
18666
19323
|
["install", "Full-stack installer: pi, beads, dolt, MCP registration, hooks"],
|
|
18667
19324
|
["list", "List available specialists with model and description"],
|
|
19325
|
+
["models", "List models available on pi, flagged with thinking/images support"],
|
|
18668
19326
|
["version", "Print installed version"],
|
|
18669
19327
|
["init", "Initialize specialists in the current project"],
|
|
18670
19328
|
["edit", "Edit a specialist field (e.g. --model, --description)"],
|
|
18671
|
-
["run", "Run a specialist with a prompt"],
|
|
18672
|
-
["
|
|
19329
|
+
["run", "Run a specialist with a prompt (--background for async)"],
|
|
19330
|
+
["result", "Print result of a background job"],
|
|
19331
|
+
["feed", "Tail events for a background job (--follow to stream)"],
|
|
19332
|
+
["stop", "Send SIGTERM to a running background job"],
|
|
19333
|
+
["status", "Show system health (pi, beads, MCP, jobs)"],
|
|
18673
19334
|
["help", "Show this help message"]
|
|
18674
19335
|
];
|
|
18675
19336
|
COL_WIDTH = Math.max(...COMMANDS.map(([cmd2]) => cmd2.length));
|
|
@@ -26046,7 +26707,7 @@ var runParallelSchema = objectType({
|
|
|
26046
26707
|
function createRunParallelTool(runner) {
|
|
26047
26708
|
return {
|
|
26048
26709
|
name: "run_parallel",
|
|
26049
|
-
description: "Execute multiple specialists concurrently. Returns aggregated results.",
|
|
26710
|
+
description: "[DEPRECATED v3] Execute multiple specialists concurrently. Returns aggregated results. Prefer CLI background jobs for async work.",
|
|
26050
26711
|
inputSchema: runParallelSchema,
|
|
26051
26712
|
async execute(input, onProgress) {
|
|
26052
26713
|
if (input.merge_strategy === "pipeline") {
|
|
@@ -26085,11 +26746,26 @@ var BACKENDS2 = ["gemini", "qwen", "anthropic", "openai"];
|
|
|
26085
26746
|
function createSpecialistStatusTool(loader, circuitBreaker) {
|
|
26086
26747
|
return {
|
|
26087
26748
|
name: "specialist_status",
|
|
26088
|
-
description: "System health: backend circuit breaker states, loaded specialists, staleness.",
|
|
26749
|
+
description: "System health: backend circuit breaker states, loaded specialists, staleness. Also shows active background jobs from .specialists/jobs/.",
|
|
26089
26750
|
inputSchema: exports_external.object({}),
|
|
26090
26751
|
async execute(_) {
|
|
26091
26752
|
const list = await loader.list();
|
|
26092
26753
|
const stalenessResults = await Promise.all(list.map((s) => checkStaleness(s)));
|
|
26754
|
+
const { existsSync: existsSync2, readdirSync, readFileSync } = await import("node:fs");
|
|
26755
|
+
const { join: join2 } = await import("node:path");
|
|
26756
|
+
const jobsDir = join2(process.cwd(), ".specialists", "jobs");
|
|
26757
|
+
const jobs = [];
|
|
26758
|
+
if (existsSync2(jobsDir)) {
|
|
26759
|
+
for (const entry of readdirSync(jobsDir)) {
|
|
26760
|
+
const statusPath = join2(jobsDir, entry, "status.json");
|
|
26761
|
+
if (!existsSync2(statusPath))
|
|
26762
|
+
continue;
|
|
26763
|
+
try {
|
|
26764
|
+
jobs.push(JSON.parse(readFileSync(statusPath, "utf-8")));
|
|
26765
|
+
} catch {}
|
|
26766
|
+
}
|
|
26767
|
+
jobs.sort((a, b) => b.started_at_ms - a.started_at_ms);
|
|
26768
|
+
}
|
|
26093
26769
|
return {
|
|
26094
26770
|
loaded_count: list.length,
|
|
26095
26771
|
backends_health: Object.fromEntries(BACKENDS2.map((b) => [b, circuitBreaker.getState(b)])),
|
|
@@ -26099,6 +26775,15 @@ function createSpecialistStatusTool(loader, circuitBreaker) {
|
|
|
26099
26775
|
category: s.category,
|
|
26100
26776
|
version: s.version,
|
|
26101
26777
|
staleness: stalenessResults[i]
|
|
26778
|
+
})),
|
|
26779
|
+
background_jobs: jobs.map((j) => ({
|
|
26780
|
+
id: j.id,
|
|
26781
|
+
specialist: j.specialist,
|
|
26782
|
+
status: j.status,
|
|
26783
|
+
elapsed_s: j.elapsed_s,
|
|
26784
|
+
current_event: j.current_event,
|
|
26785
|
+
bead_id: j.bead_id,
|
|
26786
|
+
error: j.error
|
|
26102
26787
|
}))
|
|
26103
26788
|
};
|
|
26104
26789
|
}
|
|
@@ -26224,7 +26909,7 @@ var startSpecialistSchema = exports_external.object({
|
|
|
26224
26909
|
function createStartSpecialistTool(runner, registry2) {
|
|
26225
26910
|
return {
|
|
26226
26911
|
name: "start_specialist",
|
|
26227
|
-
description: "Start a specialist asynchronously. Returns job_id immediately. " + "Use poll_specialist to track progress, receive output delta, and retrieve beadId " + "(the beads issue auto-created for this run, if beads_integration policy applies). " + "Use stop_specialist to cancel. Enables true parallel execution of multiple specialists.",
|
|
26912
|
+
description: "[DEPRECATED v3] Start a specialist asynchronously. Returns job_id immediately. Prefer CLI: `specialists run <name> --background`. " + "Use poll_specialist to track progress, receive output delta, and retrieve beadId " + "(the beads issue auto-created for this run, if beads_integration policy applies). " + "Use stop_specialist to cancel. Enables true parallel execution of multiple specialists.",
|
|
26228
26913
|
inputSchema: startSpecialistSchema,
|
|
26229
26914
|
async execute(input) {
|
|
26230
26915
|
const jobId = await runner.startAsync({
|
|
@@ -26247,7 +26932,7 @@ var pollSpecialistSchema = exports_external.object({
|
|
|
26247
26932
|
function createPollSpecialistTool(registry2) {
|
|
26248
26933
|
return {
|
|
26249
26934
|
name: "poll_specialist",
|
|
26250
|
-
description: "Poll a running specialist job. Returns status (running|done|error|cancelled), " + "delta (new tokens since cursor), next_cursor, and full output when done. " + "Pass next_cursor back as cursor on each subsequent poll to receive only new content. " + "Response also includes beadId (string | undefined) once the specialist has started — " + "this is the beads issue tracking this run. If present after status=done, consider: " + '`bd update <beadId> --notes "<key finding>"` to attach results, or ' + '`bd remember "<insight>"` to persist discoveries across sessions.',
|
|
26935
|
+
description: "[DEPRECATED v3] Poll a running specialist job. Returns status (running|done|error|cancelled), " + "delta (new tokens since cursor), next_cursor, and full output when done. " + "Pass next_cursor back as cursor on each subsequent poll to receive only new content. " + "Response also includes beadId (string | undefined) once the specialist has started — " + "this is the beads issue tracking this run. If present after status=done, consider: " + '`bd update <beadId> --notes "<key finding>"` to attach results, or ' + '`bd remember "<insight>"` to persist discoveries across sessions.',
|
|
26251
26936
|
inputSchema: pollSpecialistSchema,
|
|
26252
26937
|
async execute(input) {
|
|
26253
26938
|
const snapshot = registry2.snapshot(input.job_id, input.cursor ?? 0);
|
|
@@ -26267,7 +26952,7 @@ var stopSpecialistSchema = exports_external.object({
|
|
|
26267
26952
|
function createStopSpecialistTool(registry2) {
|
|
26268
26953
|
return {
|
|
26269
26954
|
name: "stop_specialist",
|
|
26270
|
-
description: "Cancel a running specialist job. Kills the pi process immediately and sets status to cancelled. Subsequent poll_specialist calls return status: cancelled with output buffered up to that point.",
|
|
26955
|
+
description: "[DEPRECATED v3] Cancel a running specialist job. Prefer CLI: `specialists stop <id>`. Kills the pi process immediately and sets status to cancelled. Subsequent poll_specialist calls return status: cancelled with output buffered up to that point.",
|
|
26271
26956
|
inputSchema: stopSpecialistSchema,
|
|
26272
26957
|
async execute(input) {
|
|
26273
26958
|
const result = registry2.cancel(input.job_id);
|
|
@@ -26405,6 +27090,11 @@ class SpecialistsServer {
|
|
|
26405
27090
|
const transport = new StdioServerTransport;
|
|
26406
27091
|
await this.server.connect(transport);
|
|
26407
27092
|
logger.info(`Specialists MCP Server v2 started — ${this.tools.length} tools registered`);
|
|
27093
|
+
process.on("SIGTERM", async () => {
|
|
27094
|
+
logger.info("SIGTERM received — shutting down");
|
|
27095
|
+
await this.stop();
|
|
27096
|
+
process.exit(0);
|
|
27097
|
+
});
|
|
26408
27098
|
} catch (error2) {
|
|
26409
27099
|
logger.error("Failed to start server", error2);
|
|
26410
27100
|
process.exit(1);
|
|
@@ -26417,7 +27107,7 @@ class SpecialistsServer {
|
|
|
26417
27107
|
|
|
26418
27108
|
// src/index.ts
|
|
26419
27109
|
var sub = process.argv[2];
|
|
26420
|
-
async function
|
|
27110
|
+
async function run13() {
|
|
26421
27111
|
if (sub === "install") {
|
|
26422
27112
|
const { run: handler } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
26423
27113
|
return handler();
|
|
@@ -26430,6 +27120,10 @@ async function run9() {
|
|
|
26430
27120
|
const { run: handler } = await Promise.resolve().then(() => (init_list(), exports_list));
|
|
26431
27121
|
return handler();
|
|
26432
27122
|
}
|
|
27123
|
+
if (sub === "models") {
|
|
27124
|
+
const { run: handler } = await Promise.resolve().then(() => (init_models(), exports_models));
|
|
27125
|
+
return handler();
|
|
27126
|
+
}
|
|
26433
27127
|
if (sub === "init") {
|
|
26434
27128
|
const { run: handler } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
26435
27129
|
return handler();
|
|
@@ -26446,6 +27140,18 @@ async function run9() {
|
|
|
26446
27140
|
const { run: handler } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
26447
27141
|
return handler();
|
|
26448
27142
|
}
|
|
27143
|
+
if (sub === "result") {
|
|
27144
|
+
const { run: handler } = await Promise.resolve().then(() => (init_result(), exports_result));
|
|
27145
|
+
return handler();
|
|
27146
|
+
}
|
|
27147
|
+
if (sub === "feed") {
|
|
27148
|
+
const { run: handler } = await Promise.resolve().then(() => (init_feed(), exports_feed));
|
|
27149
|
+
return handler();
|
|
27150
|
+
}
|
|
27151
|
+
if (sub === "stop") {
|
|
27152
|
+
const { run: handler } = await Promise.resolve().then(() => (init_stop(), exports_stop));
|
|
27153
|
+
return handler();
|
|
27154
|
+
}
|
|
26449
27155
|
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
26450
27156
|
const { run: handler } = await Promise.resolve().then(() => (init_help(), exports_help));
|
|
26451
27157
|
return handler();
|
|
@@ -26459,7 +27165,7 @@ Run 'specialists help' to see available commands.`);
|
|
|
26459
27165
|
const server = new SpecialistsServer;
|
|
26460
27166
|
await server.start();
|
|
26461
27167
|
}
|
|
26462
|
-
|
|
27168
|
+
run13().catch((error2) => {
|
|
26463
27169
|
logger.error(`Fatal error: ${error2}`);
|
|
26464
27170
|
process.exit(1);
|
|
26465
27171
|
});
|