@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.
Files changed (2) hide show
  1. package/dist/index.js +139 -17
  2. 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 join2 } from "node:path";
24571
+ import { join as join3 } from "node:path";
24572
24572
 
24573
24573
  // src/constants.ts
24574
- var LOG_PREFIX = "[UAI-MCP]";
24574
+ var LOG_PREFIX = "[specialists]";
24575
24575
  var MCP_CONFIG = {
24576
- SERVER_NAME: "unitAI",
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 UnitAIServer {
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: join2(process.cwd(), ".unitai", "trace.jsonl")
25736
+ tracePath: join3(process.cwd(), ".specialists", "trace.jsonl")
25618
25737
  });
25619
- const runner = new SpecialistRunner({ loader, hooks, circuitBreaker });
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: "unitai", data: msg }
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(`UnitAI MCP Server v2 started — ${this.tools.length} tools registered`);
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 Unified AI MCP Tool server...");
25706
- const server = new UnitAIServer;
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.0.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",