@jaggerxtrm/specialists 2.0.2 → 2.1.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/dist/index.js +139 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24568,12 +24568,12 @@ class StdioServerTransport {
|
|
|
24568
24568
|
}
|
|
24569
24569
|
|
|
24570
24570
|
// src/server.ts
|
|
24571
|
-
import { join as
|
|
24571
|
+
import { join as join3 } from "node:path";
|
|
24572
24572
|
|
|
24573
24573
|
// src/constants.ts
|
|
24574
|
-
var LOG_PREFIX = "[
|
|
24574
|
+
var LOG_PREFIX = "[specialists]";
|
|
24575
24575
|
var MCP_CONFIG = {
|
|
24576
|
-
SERVER_NAME: "
|
|
24576
|
+
SERVER_NAME: "specialists",
|
|
24577
24577
|
VERSION: "1.0.0",
|
|
24578
24578
|
CAPABILITIES: {
|
|
24579
24579
|
tools: {},
|
|
@@ -24748,6 +24748,7 @@ var SpecialistSchema = objectType({
|
|
|
24748
24748
|
capabilities: CapabilitiesSchema,
|
|
24749
24749
|
communication: CommunicationSchema,
|
|
24750
24750
|
validation: ValidationSchema,
|
|
24751
|
+
beads_integration: enumType(["auto", "always", "never"]).default("auto"),
|
|
24751
24752
|
heartbeat: unknownType().optional()
|
|
24752
24753
|
})
|
|
24753
24754
|
});
|
|
@@ -25078,6 +25079,66 @@ class PiAgentSession {
|
|
|
25078
25079
|
}
|
|
25079
25080
|
}
|
|
25080
25081
|
|
|
25082
|
+
// src/specialist/beads.ts
|
|
25083
|
+
import { spawnSync } from "node:child_process";
|
|
25084
|
+
|
|
25085
|
+
class BeadsClient {
|
|
25086
|
+
available;
|
|
25087
|
+
constructor() {
|
|
25088
|
+
this.available = BeadsClient.checkAvailable();
|
|
25089
|
+
if (!this.available) {
|
|
25090
|
+
console.warn("[specialists] bd CLI not found — beads tracking disabled");
|
|
25091
|
+
}
|
|
25092
|
+
}
|
|
25093
|
+
static checkAvailable() {
|
|
25094
|
+
const result = spawnSync("bd", ["--version"], { stdio: "ignore" });
|
|
25095
|
+
return result.status === 0;
|
|
25096
|
+
}
|
|
25097
|
+
isAvailable() {
|
|
25098
|
+
return this.available;
|
|
25099
|
+
}
|
|
25100
|
+
createBead(specialistName) {
|
|
25101
|
+
if (!this.available)
|
|
25102
|
+
return null;
|
|
25103
|
+
const result = spawnSync("bd", ["q", `specialist:${specialistName}`, "--type", "task", "--labels", "specialist"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
25104
|
+
if (result.status !== 0)
|
|
25105
|
+
return null;
|
|
25106
|
+
const id = result.stdout?.trim();
|
|
25107
|
+
return id || null;
|
|
25108
|
+
}
|
|
25109
|
+
closeBead(id, status, durationMs, model) {
|
|
25110
|
+
if (!this.available || !id)
|
|
25111
|
+
return;
|
|
25112
|
+
const reason = `${status}, ${Math.round(durationMs)}ms, ${model}`;
|
|
25113
|
+
spawnSync("bd", ["close", id, "-r", reason], { stdio: "ignore" });
|
|
25114
|
+
}
|
|
25115
|
+
auditBead(id, toolName, model, exitCode) {
|
|
25116
|
+
if (!this.available || !id)
|
|
25117
|
+
return;
|
|
25118
|
+
spawnSync("bd", [
|
|
25119
|
+
"audit",
|
|
25120
|
+
"record",
|
|
25121
|
+
"--kind",
|
|
25122
|
+
"tool_call",
|
|
25123
|
+
"--tool-name",
|
|
25124
|
+
toolName,
|
|
25125
|
+
"--model",
|
|
25126
|
+
model,
|
|
25127
|
+
"--issue-id",
|
|
25128
|
+
id,
|
|
25129
|
+
"--exit-code",
|
|
25130
|
+
String(exitCode)
|
|
25131
|
+
], { stdio: "ignore" });
|
|
25132
|
+
}
|
|
25133
|
+
}
|
|
25134
|
+
function shouldCreateBead(beadsIntegration, permissionRequired) {
|
|
25135
|
+
if (beadsIntegration === "never")
|
|
25136
|
+
return false;
|
|
25137
|
+
if (beadsIntegration === "always")
|
|
25138
|
+
return true;
|
|
25139
|
+
return permissionRequired !== "READ_ONLY";
|
|
25140
|
+
}
|
|
25141
|
+
|
|
25081
25142
|
// src/specialist/runner.ts
|
|
25082
25143
|
class SpecialistRunner {
|
|
25083
25144
|
deps;
|
|
@@ -25086,8 +25147,8 @@ class SpecialistRunner {
|
|
|
25086
25147
|
this.deps = deps;
|
|
25087
25148
|
this.sessionFactory = deps.sessionFactory ?? PiAgentSession.create.bind(PiAgentSession);
|
|
25088
25149
|
}
|
|
25089
|
-
async run(options, onProgress, onEvent, onMeta, onKillRegistered) {
|
|
25090
|
-
const { loader, hooks, circuitBreaker } = this.deps;
|
|
25150
|
+
async run(options, onProgress, onEvent, onMeta, onKillRegistered, onBeadCreated) {
|
|
25151
|
+
const { loader, hooks, circuitBreaker, beadsClient } = this.deps;
|
|
25091
25152
|
const invocationId = crypto.randomUUID();
|
|
25092
25153
|
const start = Date.now();
|
|
25093
25154
|
const spec = await loader.get(options.name);
|
|
@@ -25142,6 +25203,13 @@ You have access via Bash:
|
|
|
25142
25203
|
timeout_ms: execution.timeout_ms,
|
|
25143
25204
|
permission_level: permissionLevel
|
|
25144
25205
|
});
|
|
25206
|
+
const beadsIntegration = spec.specialist.beads_integration ?? "auto";
|
|
25207
|
+
let beadId;
|
|
25208
|
+
if (beadsClient && shouldCreateBead(beadsIntegration, execution.permission_required)) {
|
|
25209
|
+
beadId = beadsClient.createBead(metadata.name) ?? undefined;
|
|
25210
|
+
if (beadId)
|
|
25211
|
+
onBeadCreated?.(beadId);
|
|
25212
|
+
}
|
|
25145
25213
|
let output;
|
|
25146
25214
|
let session;
|
|
25147
25215
|
try {
|
|
@@ -25179,6 +25247,8 @@ You have access via Bash:
|
|
|
25179
25247
|
circuitBreaker.recordSuccess(model);
|
|
25180
25248
|
} catch (err) {
|
|
25181
25249
|
circuitBreaker.recordFailure(model);
|
|
25250
|
+
if (beadId)
|
|
25251
|
+
beadsClient?.closeBead(beadId, "ERROR", Date.now() - start, model);
|
|
25182
25252
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
25183
25253
|
status: "ERROR",
|
|
25184
25254
|
duration_ms: Date.now() - start,
|
|
@@ -25198,12 +25268,17 @@ You have access via Bash:
|
|
|
25198
25268
|
duration_ms: durationMs,
|
|
25199
25269
|
output_valid: true
|
|
25200
25270
|
});
|
|
25271
|
+
if (beadId) {
|
|
25272
|
+
beadsClient?.closeBead(beadId, "COMPLETE", durationMs, model);
|
|
25273
|
+
beadsClient?.auditBead(beadId, metadata.name, model, 0);
|
|
25274
|
+
}
|
|
25201
25275
|
return {
|
|
25202
25276
|
output,
|
|
25203
25277
|
backend: session.meta.backend,
|
|
25204
25278
|
model,
|
|
25205
25279
|
durationMs,
|
|
25206
|
-
specialistVersion: metadata.version
|
|
25280
|
+
specialistVersion: metadata.version,
|
|
25281
|
+
beadId
|
|
25207
25282
|
};
|
|
25208
25283
|
}
|
|
25209
25284
|
async startAsync(options, registry2) {
|
|
@@ -25218,7 +25293,7 @@ You have access via Bash:
|
|
|
25218
25293
|
model: "?",
|
|
25219
25294
|
specialistVersion
|
|
25220
25295
|
});
|
|
25221
|
-
this.run(options, (text) => registry2.appendOutput(jobId, text), (eventType) => registry2.setCurrentEvent(jobId, eventType), (meta) => registry2.setMeta(jobId, meta), (killFn) => registry2.setKillFn(jobId, killFn)).then((result) => registry2.complete(jobId, result)).catch((err) => registry2.fail(jobId, err));
|
|
25296
|
+
this.run(options, (text) => registry2.appendOutput(jobId, text), (eventType) => registry2.setCurrentEvent(jobId, eventType), (meta) => registry2.setMeta(jobId, meta), (killFn) => registry2.setKillFn(jobId, killFn), (beadId) => registry2.setBeadId(jobId, beadId)).then((result) => registry2.complete(jobId, result)).catch((err) => registry2.fail(jobId, err));
|
|
25222
25297
|
return jobId;
|
|
25223
25298
|
}
|
|
25224
25299
|
}
|
|
@@ -25478,6 +25553,12 @@ class JobRegistry {
|
|
|
25478
25553
|
if (meta.model)
|
|
25479
25554
|
job.model = meta.model;
|
|
25480
25555
|
}
|
|
25556
|
+
setBeadId(id, beadId) {
|
|
25557
|
+
const job = this.jobs.get(id);
|
|
25558
|
+
if (!job)
|
|
25559
|
+
return;
|
|
25560
|
+
job.beadId = beadId;
|
|
25561
|
+
}
|
|
25481
25562
|
setKillFn(id, killFn) {
|
|
25482
25563
|
const job = this.jobs.get(id);
|
|
25483
25564
|
if (!job)
|
|
@@ -25499,6 +25580,8 @@ class JobRegistry {
|
|
|
25499
25580
|
job.model = result.model;
|
|
25500
25581
|
job.specialistVersion = result.specialistVersion;
|
|
25501
25582
|
job.endedAtMs = Date.now();
|
|
25583
|
+
if (result.beadId)
|
|
25584
|
+
job.beadId = result.beadId;
|
|
25502
25585
|
}
|
|
25503
25586
|
fail(id, err) {
|
|
25504
25587
|
const job = this.jobs.get(id);
|
|
@@ -25535,7 +25618,8 @@ class JobRegistry {
|
|
|
25535
25618
|
model: job.model,
|
|
25536
25619
|
specialist_version: job.specialistVersion,
|
|
25537
25620
|
duration_ms: (job.endedAtMs ?? Date.now()) - job.startedAtMs,
|
|
25538
|
-
error: job.error
|
|
25621
|
+
error: job.error,
|
|
25622
|
+
beadId: job.beadId
|
|
25539
25623
|
};
|
|
25540
25624
|
}
|
|
25541
25625
|
delete(id) {
|
|
@@ -25606,17 +25690,53 @@ function createStopSpecialistTool(registry2) {
|
|
|
25606
25690
|
};
|
|
25607
25691
|
}
|
|
25608
25692
|
|
|
25693
|
+
// src/tools/specialist/specialist_init.tool.ts
|
|
25694
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
25695
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
25696
|
+
import { join as join2 } from "node:path";
|
|
25697
|
+
var specialistInitSchema = objectType({});
|
|
25698
|
+
function createSpecialistInitTool(loader, deps) {
|
|
25699
|
+
const resolved = deps ?? {
|
|
25700
|
+
bdAvailable: () => spawnSync2("bd", ["--version"], { stdio: "ignore" }).status === 0,
|
|
25701
|
+
beadsExists: () => existsSync2(join2(process.cwd(), ".beads")),
|
|
25702
|
+
bdInit: () => spawnSync2("bd", ["init"], { stdio: "ignore" })
|
|
25703
|
+
};
|
|
25704
|
+
return {
|
|
25705
|
+
name: "specialist_init",
|
|
25706
|
+
description: "Session bootstrap: initializes beads in the project if not already set up, " + "then returns available specialists. Call at session start for orientation.",
|
|
25707
|
+
inputSchema: specialistInitSchema,
|
|
25708
|
+
async execute(_input) {
|
|
25709
|
+
const available = resolved.bdAvailable();
|
|
25710
|
+
let initialized = false;
|
|
25711
|
+
if (available) {
|
|
25712
|
+
if (resolved.beadsExists()) {
|
|
25713
|
+
initialized = true;
|
|
25714
|
+
} else {
|
|
25715
|
+
const result = resolved.bdInit();
|
|
25716
|
+
initialized = result.status === 0;
|
|
25717
|
+
}
|
|
25718
|
+
}
|
|
25719
|
+
const specialists = await loader.list();
|
|
25720
|
+
return {
|
|
25721
|
+
specialists,
|
|
25722
|
+
beads: { available, initialized }
|
|
25723
|
+
};
|
|
25724
|
+
}
|
|
25725
|
+
};
|
|
25726
|
+
}
|
|
25727
|
+
|
|
25609
25728
|
// src/server.ts
|
|
25610
|
-
class
|
|
25729
|
+
class SpecialistsServer {
|
|
25611
25730
|
server;
|
|
25612
25731
|
tools;
|
|
25613
25732
|
constructor() {
|
|
25614
25733
|
const circuitBreaker = new CircuitBreaker;
|
|
25615
25734
|
const loader = new SpecialistLoader;
|
|
25616
25735
|
const hooks = new HookEmitter({
|
|
25617
|
-
tracePath:
|
|
25736
|
+
tracePath: join3(process.cwd(), ".specialists", "trace.jsonl")
|
|
25618
25737
|
});
|
|
25619
|
-
const
|
|
25738
|
+
const beadsClient = new BeadsClient;
|
|
25739
|
+
const runner = new SpecialistRunner({ loader, hooks, circuitBreaker, beadsClient });
|
|
25620
25740
|
const registry2 = new JobRegistry;
|
|
25621
25741
|
this.tools = [
|
|
25622
25742
|
createListSpecialistsTool(loader),
|
|
@@ -25625,7 +25745,8 @@ class UnitAIServer {
|
|
|
25625
25745
|
createSpecialistStatusTool(loader, circuitBreaker),
|
|
25626
25746
|
createStartSpecialistTool(runner, registry2),
|
|
25627
25747
|
createPollSpecialistTool(registry2),
|
|
25628
|
-
createStopSpecialistTool(registry2)
|
|
25748
|
+
createStopSpecialistTool(registry2),
|
|
25749
|
+
createSpecialistInitTool(loader)
|
|
25629
25750
|
];
|
|
25630
25751
|
this.server = new Server({ name: MCP_CONFIG.SERVER_NAME, version: MCP_CONFIG.VERSION }, { capabilities: MCP_CONFIG.CAPABILITIES });
|
|
25631
25752
|
this.setupHandlers();
|
|
@@ -25639,7 +25760,8 @@ class UnitAIServer {
|
|
|
25639
25760
|
specialist_status: exports_external.object({}),
|
|
25640
25761
|
start_specialist: startSpecialistSchema,
|
|
25641
25762
|
poll_specialist: pollSpecialistSchema,
|
|
25642
|
-
stop_specialist: stopSpecialistSchema
|
|
25763
|
+
stop_specialist: stopSpecialistSchema,
|
|
25764
|
+
specialist_init: specialistInitSchema
|
|
25643
25765
|
};
|
|
25644
25766
|
this.toolSchemas = schemaMap;
|
|
25645
25767
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -25665,7 +25787,7 @@ class UnitAIServer {
|
|
|
25665
25787
|
const onProgress = (msg) => {
|
|
25666
25788
|
this.server.notification({
|
|
25667
25789
|
method: "notifications/message",
|
|
25668
|
-
params: { level: "info", logger: "
|
|
25790
|
+
params: { level: "info", logger: "specialists", data: msg }
|
|
25669
25791
|
}).catch(() => {});
|
|
25670
25792
|
};
|
|
25671
25793
|
try {
|
|
@@ -25689,7 +25811,7 @@ class UnitAIServer {
|
|
|
25689
25811
|
try {
|
|
25690
25812
|
const transport = new StdioServerTransport;
|
|
25691
25813
|
await this.server.connect(transport);
|
|
25692
|
-
logger.info(`
|
|
25814
|
+
logger.info(`Specialists MCP Server v2 started — ${this.tools.length} tools registered`);
|
|
25693
25815
|
} catch (error2) {
|
|
25694
25816
|
logger.error("Failed to start server", error2);
|
|
25695
25817
|
process.exit(1);
|
|
@@ -25702,8 +25824,8 @@ class UnitAIServer {
|
|
|
25702
25824
|
|
|
25703
25825
|
// src/index.ts
|
|
25704
25826
|
async function main() {
|
|
25705
|
-
logger.info("Starting
|
|
25706
|
-
const server = new
|
|
25827
|
+
logger.info("Starting Specialists MCP Server...");
|
|
25828
|
+
const server = new SpecialistsServer;
|
|
25707
25829
|
await server.start();
|
|
25708
25830
|
}
|
|
25709
25831
|
main().catch((error2) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaggerxtrm/specialists",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|