@pruddiman/dispatch 1.5.0-beta.30f06ab → 1.5.0-beta.c0bcc82

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/cli.js CHANGED
@@ -11920,9 +11920,18 @@ var init_database = __esm({
11920
11920
  // src/mcp/state/manager.ts
11921
11921
  import { randomUUID as randomUUID6 } from "crypto";
11922
11922
  function registerLiveRun(runId) {
11923
- liveRuns.set(runId, { runId, callbacks: [] });
11923
+ liveRuns.set(runId, { runId, callbacks: [], completionCallbacks: [] });
11924
11924
  }
11925
11925
  function unregisterLiveRun(runId) {
11926
+ const run = liveRuns.get(runId);
11927
+ if (run) {
11928
+ for (const cb of run.completionCallbacks) {
11929
+ try {
11930
+ cb();
11931
+ } catch {
11932
+ }
11933
+ }
11934
+ }
11926
11935
  liveRuns.delete(runId);
11927
11936
  }
11928
11937
  function addLogCallback(runId, cb) {
@@ -11943,6 +11952,48 @@ function emitLog(runId, message, level = "info") {
11943
11952
  }
11944
11953
  }
11945
11954
  }
11955
+ function isLiveRun(runId) {
11956
+ return liveRuns.has(runId);
11957
+ }
11958
+ function addCompletionCallback(runId, cb) {
11959
+ const run = liveRuns.get(runId);
11960
+ if (run) {
11961
+ run.completionCallbacks.push(cb);
11962
+ }
11963
+ }
11964
+ function waitForRunCompletion(runId, waitMs, getStatus) {
11965
+ const effectiveWait = Math.min(Math.max(waitMs, 0), 12e4);
11966
+ if (effectiveWait <= 0) return Promise.resolve(false);
11967
+ const currentStatus = getStatus();
11968
+ if (currentStatus !== null && currentStatus !== "running") {
11969
+ return Promise.resolve(true);
11970
+ }
11971
+ return new Promise((resolve6) => {
11972
+ let settled = false;
11973
+ let pollTimer;
11974
+ let timeoutTimer;
11975
+ const cleanup = () => {
11976
+ if (pollTimer) clearInterval(pollTimer);
11977
+ if (timeoutTimer) clearTimeout(timeoutTimer);
11978
+ };
11979
+ const settle = (completed) => {
11980
+ if (settled) return;
11981
+ settled = true;
11982
+ cleanup();
11983
+ resolve6(completed);
11984
+ };
11985
+ if (isLiveRun(runId)) {
11986
+ addCompletionCallback(runId, () => settle(true));
11987
+ }
11988
+ pollTimer = setInterval(() => {
11989
+ const s = getStatus();
11990
+ if (s !== null && s !== "running") {
11991
+ settle(true);
11992
+ }
11993
+ }, 2e3);
11994
+ timeoutTimer = setTimeout(() => settle(false), effectiveWait);
11995
+ });
11996
+ }
11946
11997
  function assertRunStatus(value) {
11947
11998
  if (RUN_STATUSES.includes(value)) return value;
11948
11999
  throw new Error(`Invalid RunStatus from database: "${value}"`);
@@ -12210,9 +12261,36 @@ var init_fork_run = __esm({
12210
12261
  }
12211
12262
  });
12212
12263
 
12264
+ // src/mcp/tools/_resolve-config.ts
12265
+ import { join as join17 } from "path";
12266
+ async function loadMcpConfig(cwd, overrides) {
12267
+ const config = await loadConfig(join17(cwd, ".dispatch"));
12268
+ const provider = overrides?.provider ?? config.provider;
12269
+ if (!provider) {
12270
+ throw new Error(
12271
+ "Missing required configuration: provider. Run 'dispatch config' to set up defaults."
12272
+ );
12273
+ }
12274
+ let source = overrides?.source ?? config.source;
12275
+ if (!source) {
12276
+ const detected = await detectDatasource(cwd);
12277
+ if (detected) {
12278
+ source = detected;
12279
+ }
12280
+ }
12281
+ return { ...config, provider, source };
12282
+ }
12283
+ var init_resolve_config = __esm({
12284
+ "src/mcp/tools/_resolve-config.ts"() {
12285
+ "use strict";
12286
+ init_config();
12287
+ init_datasources();
12288
+ }
12289
+ });
12290
+
12213
12291
  // src/mcp/tools/spec.ts
12214
12292
  import { z as z2 } from "zod";
12215
- import { join as join17, resolve as resolve4, sep as sep2 } from "path";
12293
+ import { join as join18, resolve as resolve4, sep as sep2 } from "path";
12216
12294
  import { readdir as readdir2, readFile as readFile11 } from "fs/promises";
12217
12295
  function registerSpecTools(server, cwd) {
12218
12296
  server.tool(
@@ -12222,21 +12300,39 @@ function registerSpecTools(server, cwd) {
12222
12300
  issues: z2.string().describe(
12223
12301
  "Comma-separated issue IDs (e.g. '42,43'), a glob pattern (e.g. 'drafts/*.md'), or an inline description."
12224
12302
  ),
12225
- provider: z2.enum(PROVIDER_NAMES).optional().describe("Agent provider name (default: opencode)"),
12226
- source: z2.enum(DATASOURCE_NAMES).optional().describe("Issue datasource: github, azdevops, md"),
12303
+ provider: z2.enum(PROVIDER_NAMES).optional().describe("Agent provider name (default: from config)"),
12304
+ source: z2.enum(DATASOURCE_NAMES).optional().describe("Issue datasource: github, azdevops, md (default: from config)"),
12227
12305
  concurrency: z2.number().int().min(1).max(32).optional().describe("Max parallel spec generations"),
12228
12306
  dryRun: z2.boolean().optional().describe("Preview without generating")
12229
12307
  },
12230
12308
  async (args) => {
12309
+ let config;
12310
+ try {
12311
+ config = await loadMcpConfig(cwd, { provider: args.provider, source: args.source });
12312
+ } catch (err) {
12313
+ return {
12314
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
12315
+ isError: true
12316
+ };
12317
+ }
12231
12318
  const runId = createSpecRun({ cwd, issues: args.issues });
12232
12319
  forkDispatchRun(runId, server, {
12233
12320
  type: "spec",
12234
12321
  cwd,
12235
12322
  opts: {
12236
12323
  issues: args.issues,
12237
- provider: args.provider ?? "opencode",
12238
- issueSource: args.source,
12239
- concurrency: args.concurrency,
12324
+ provider: config.provider,
12325
+ model: config.model,
12326
+ issueSource: config.source,
12327
+ org: config.org,
12328
+ project: config.project,
12329
+ workItemType: config.workItemType,
12330
+ iteration: config.iteration,
12331
+ area: config.area,
12332
+ concurrency: args.concurrency ?? config.concurrency,
12333
+ specTimeout: config.specTimeout,
12334
+ specWarnTimeout: config.specWarnTimeout,
12335
+ specKillTimeout: config.specKillTimeout,
12240
12336
  dryRun: args.dryRun,
12241
12337
  cwd
12242
12338
  }
@@ -12261,7 +12357,7 @@ function registerSpecTools(server, cwd) {
12261
12357
  "List spec files in the .dispatch/specs directory.",
12262
12358
  {},
12263
12359
  async () => {
12264
- const specsDir = join17(cwd, ".dispatch", "specs");
12360
+ const specsDir = join18(cwd, ".dispatch", "specs");
12265
12361
  let files = [];
12266
12362
  let dirError;
12267
12363
  try {
@@ -12291,7 +12387,7 @@ function registerSpecTools(server, cwd) {
12291
12387
  },
12292
12388
  async (args) => {
12293
12389
  const specsDir = resolve4(cwd, ".dispatch", "specs");
12294
- const candidatePath = args.file.includes("/") || args.file.includes("\\") ? resolve4(specsDir, args.file) : join17(specsDir, args.file);
12390
+ const candidatePath = args.file.includes("/") || args.file.includes("\\") ? resolve4(specsDir, args.file) : join18(specsDir, args.file);
12295
12391
  if (!candidatePath.startsWith(specsDir + sep2) && candidatePath !== specsDir) {
12296
12392
  return {
12297
12393
  content: [{ type: "text", text: `Access denied: path must be inside the specs directory` }],
@@ -12335,21 +12431,36 @@ function registerSpecTools(server, cwd) {
12335
12431
  );
12336
12432
  server.tool(
12337
12433
  "spec_run_status",
12338
- "Get the status of a specific spec generation run.",
12434
+ "Get the status of a specific spec generation run. Use waitMs to hold the response until the run completes or the timeout elapses.",
12339
12435
  {
12340
- runId: z2.string().describe("The runId returned by spec_generate")
12436
+ runId: z2.string().describe("The runId returned by spec_generate"),
12437
+ waitMs: z2.number().int().min(0).max(12e4).optional().default(0).describe("Hold response until run completes or timeout (ms). 0 = return immediately.")
12341
12438
  },
12342
12439
  async (args) => {
12343
12440
  try {
12344
- const run = getSpecRun(args.runId);
12441
+ let run = getSpecRun(args.runId);
12345
12442
  if (!run) {
12346
12443
  return {
12347
12444
  content: [{ type: "text", text: `Run ${args.runId} not found` }],
12348
12445
  isError: true
12349
12446
  };
12350
12447
  }
12448
+ if (run.status === "running" && args.waitMs > 0) {
12449
+ const completed = await waitForRunCompletion(
12450
+ args.runId,
12451
+ args.waitMs,
12452
+ () => getSpecRun(args.runId)?.status ?? null
12453
+ );
12454
+ if (completed) {
12455
+ run = getSpecRun(args.runId);
12456
+ }
12457
+ }
12458
+ const response = { ...run };
12459
+ if (run.status === "running") {
12460
+ response.retryAfterMs = 5e3;
12461
+ }
12351
12462
  return {
12352
- content: [{ type: "text", text: JSON.stringify(run) }]
12463
+ content: [{ type: "text", text: JSON.stringify(response) }]
12353
12464
  };
12354
12465
  } catch (err) {
12355
12466
  return {
@@ -12367,6 +12478,7 @@ var init_spec2 = __esm({
12367
12478
  init_interface2();
12368
12479
  init_interface();
12369
12480
  init_fork_run();
12481
+ init_resolve_config();
12370
12482
  }
12371
12483
  });
12372
12484
 
@@ -12378,8 +12490,8 @@ function registerDispatchTools(server, cwd) {
12378
12490
  "Execute dispatch pipeline for one or more issue IDs. Returns a runId immediately; progress is pushed via logging notifications.",
12379
12491
  {
12380
12492
  issueIds: z3.array(z3.string()).min(1).describe("Issue IDs to dispatch (e.g. ['42', '43'])"),
12381
- provider: z3.enum(PROVIDER_NAMES).optional().describe("Agent provider (default: opencode)"),
12382
- source: z3.enum(DATASOURCE_NAMES).optional().describe("Issue datasource: github, azdevops, md"),
12493
+ provider: z3.enum(PROVIDER_NAMES).optional().describe("Agent provider (default: from config)"),
12494
+ source: z3.enum(DATASOURCE_NAMES).optional().describe("Issue datasource: github, azdevops, md (default: from config)"),
12383
12495
  concurrency: z3.number().int().min(1).max(32).optional().describe("Max parallel tasks"),
12384
12496
  noPlan: z3.boolean().optional().describe("Skip the planner agent"),
12385
12497
  noBranch: z3.boolean().optional().describe("Skip branch creation and PR lifecycle"),
@@ -12387,6 +12499,15 @@ function registerDispatchTools(server, cwd) {
12387
12499
  retries: z3.number().int().min(0).max(10).optional().describe("Retry attempts per task")
12388
12500
  },
12389
12501
  async (args) => {
12502
+ let config;
12503
+ try {
12504
+ config = await loadMcpConfig(cwd, { provider: args.provider, source: args.source });
12505
+ } catch (err) {
12506
+ return {
12507
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
12508
+ isError: true
12509
+ };
12510
+ }
12390
12511
  const runId = createRun({ cwd, issueIds: args.issueIds });
12391
12512
  forkDispatchRun(runId, server, {
12392
12513
  type: "dispatch",
@@ -12394,9 +12515,20 @@ function registerDispatchTools(server, cwd) {
12394
12515
  opts: {
12395
12516
  issueIds: args.issueIds,
12396
12517
  dryRun: false,
12397
- provider: args.provider ?? "opencode",
12398
- source: args.source,
12399
- concurrency: args.concurrency ?? 1,
12518
+ provider: config.provider,
12519
+ model: config.model,
12520
+ fastProvider: config.fastProvider,
12521
+ fastModel: config.fastModel,
12522
+ agents: config.agents,
12523
+ source: config.source,
12524
+ org: config.org,
12525
+ project: config.project,
12526
+ workItemType: config.workItemType,
12527
+ iteration: config.iteration,
12528
+ area: config.area,
12529
+ username: config.username,
12530
+ planTimeout: config.planTimeout,
12531
+ concurrency: args.concurrency ?? config.concurrency ?? 1,
12400
12532
  noPlan: args.noPlan,
12401
12533
  noBranch: args.noBranch,
12402
12534
  noWorktree: args.noWorktree,
@@ -12413,15 +12545,28 @@ function registerDispatchTools(server, cwd) {
12413
12545
  "Preview tasks that would be dispatched for the given issue IDs without executing anything.",
12414
12546
  {
12415
12547
  issueIds: z3.array(z3.string()).min(1).describe("Issue IDs to preview"),
12416
- source: z3.enum(DATASOURCE_NAMES).optional().describe("Issue datasource: github, azdevops, md")
12548
+ source: z3.enum(DATASOURCE_NAMES).optional().describe("Issue datasource: github, azdevops, md (default: from config)")
12417
12549
  },
12418
12550
  async (args) => {
12419
12551
  try {
12552
+ const config = await loadMcpConfig(cwd, { source: args.source });
12420
12553
  const orchestrator = await boot9({ cwd });
12421
12554
  const result = await orchestrator.orchestrate({
12422
12555
  issueIds: args.issueIds,
12423
12556
  dryRun: true,
12424
- source: args.source
12557
+ provider: config.provider,
12558
+ model: config.model,
12559
+ fastProvider: config.fastProvider,
12560
+ fastModel: config.fastModel,
12561
+ agents: config.agents,
12562
+ source: config.source,
12563
+ org: config.org,
12564
+ project: config.project,
12565
+ workItemType: config.workItemType,
12566
+ iteration: config.iteration,
12567
+ area: config.area,
12568
+ username: config.username,
12569
+ planTimeout: config.planTimeout
12425
12570
  });
12426
12571
  return {
12427
12572
  content: [{ type: "text", text: JSON.stringify(result) }]
@@ -12443,31 +12588,47 @@ var init_dispatch = __esm({
12443
12588
  init_interface2();
12444
12589
  init_interface();
12445
12590
  init_fork_run();
12591
+ init_resolve_config();
12446
12592
  }
12447
12593
  });
12448
12594
 
12449
12595
  // src/mcp/tools/monitor.ts
12450
12596
  import { z as z4 } from "zod";
12451
- import { join as join18 } from "path";
12597
+ import { join as join19 } from "path";
12452
12598
  function registerMonitorTools(server, cwd) {
12453
12599
  server.tool(
12454
12600
  "status_get",
12455
- "Get the current status of a dispatch or spec run, including per-task details.",
12601
+ "Get the current status of a dispatch or spec run, including per-task details. Use waitMs to hold the response until the run completes or the timeout elapses.",
12456
12602
  {
12457
- runId: z4.string().describe("The runId returned by dispatch_run or spec_generate")
12603
+ runId: z4.string().describe("The runId returned by dispatch_run or spec_generate"),
12604
+ waitMs: z4.number().int().min(0).max(12e4).optional().default(0).describe("Hold response until run completes or timeout (ms). 0 = return immediately.")
12458
12605
  },
12459
12606
  async (args) => {
12460
12607
  try {
12461
- const run = getRun(args.runId);
12608
+ let run = getRun(args.runId);
12462
12609
  if (!run) {
12463
12610
  return {
12464
12611
  content: [{ type: "text", text: `Run ${args.runId} not found` }],
12465
12612
  isError: true
12466
12613
  };
12467
12614
  }
12615
+ if (run.status === "running" && args.waitMs > 0) {
12616
+ const completed = await waitForRunCompletion(
12617
+ args.runId,
12618
+ args.waitMs,
12619
+ () => getRun(args.runId)?.status ?? null
12620
+ );
12621
+ if (completed) {
12622
+ run = getRun(args.runId);
12623
+ }
12624
+ }
12468
12625
  const tasks = getTasksForRun(args.runId);
12626
+ const response = { run, tasks };
12627
+ if (run.status === "running") {
12628
+ response.retryAfterMs = 5e3;
12629
+ }
12469
12630
  return {
12470
- content: [{ type: "text", text: JSON.stringify({ run, tasks }) }]
12631
+ content: [{ type: "text", text: JSON.stringify(response) }]
12471
12632
  };
12472
12633
  } catch (err) {
12473
12634
  return {
@@ -12511,7 +12672,7 @@ function registerMonitorTools(server, cwd) {
12511
12672
  },
12512
12673
  async (args) => {
12513
12674
  try {
12514
- const config = await loadConfig(join18(cwd, ".dispatch"));
12675
+ const config = await loadConfig(join19(cwd, ".dispatch"));
12515
12676
  const sourceName = args.source ?? config.source;
12516
12677
  if (!sourceName) {
12517
12678
  return {
@@ -12556,7 +12717,7 @@ function registerMonitorTools(server, cwd) {
12556
12717
  },
12557
12718
  async (args) => {
12558
12719
  try {
12559
- const config = await loadConfig(join18(cwd, ".dispatch"));
12720
+ const config = await loadConfig(join19(cwd, ".dispatch"));
12560
12721
  const sourceName = args.source ?? config.source;
12561
12722
  if (!sourceName) {
12562
12723
  return {
@@ -12604,7 +12765,6 @@ var init_monitor = __esm({
12604
12765
 
12605
12766
  // src/mcp/tools/recovery.ts
12606
12767
  import { z as z5 } from "zod";
12607
- import { join as join19 } from "path";
12608
12768
  function registerRecoveryTools(server, cwd) {
12609
12769
  server.tool(
12610
12770
  "run_retry",
@@ -12629,7 +12789,15 @@ function registerRecoveryTools(server, cwd) {
12629
12789
  content: [{ type: "text", text: JSON.stringify({ message: "No failed tasks found", originalRunId: args.runId }) }]
12630
12790
  };
12631
12791
  }
12632
- const config = await loadConfig(join19(cwd, ".dispatch"));
12792
+ let config;
12793
+ try {
12794
+ config = await loadMcpConfig(cwd, { provider: args.provider });
12795
+ } catch (err) {
12796
+ return {
12797
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
12798
+ isError: true
12799
+ };
12800
+ }
12633
12801
  const issueIds = issueIdsSchema.parse(JSON.parse(originalRun.issueIds));
12634
12802
  const newRunId = createRun({ cwd, issueIds });
12635
12803
  forkDispatchRun(newRunId, server, {
@@ -12638,8 +12806,19 @@ function registerRecoveryTools(server, cwd) {
12638
12806
  opts: {
12639
12807
  issueIds,
12640
12808
  dryRun: false,
12641
- provider: args.provider ?? config.provider ?? "opencode",
12809
+ provider: config.provider,
12810
+ model: config.model,
12811
+ fastProvider: config.fastProvider,
12812
+ fastModel: config.fastModel,
12813
+ agents: config.agents,
12642
12814
  source: config.source,
12815
+ org: config.org,
12816
+ project: config.project,
12817
+ workItemType: config.workItemType,
12818
+ iteration: config.iteration,
12819
+ area: config.area,
12820
+ username: config.username,
12821
+ planTimeout: config.planTimeout,
12643
12822
  concurrency: args.concurrency ?? config.concurrency ?? 1,
12644
12823
  force: true
12645
12824
  }
@@ -12673,7 +12852,15 @@ function registerRecoveryTools(server, cwd) {
12673
12852
  isError: true
12674
12853
  };
12675
12854
  }
12676
- const config = await loadConfig(join19(cwd, ".dispatch"));
12855
+ let config;
12856
+ try {
12857
+ config = await loadMcpConfig(cwd, { provider: args.provider });
12858
+ } catch (err) {
12859
+ return {
12860
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
12861
+ isError: true
12862
+ };
12863
+ }
12677
12864
  const issueIds = issueIdsSchema.parse(JSON.parse(originalRun.issueIds));
12678
12865
  const newRunId = createRun({ cwd, issueIds });
12679
12866
  forkDispatchRun(newRunId, server, {
@@ -12682,8 +12869,19 @@ function registerRecoveryTools(server, cwd) {
12682
12869
  opts: {
12683
12870
  issueIds,
12684
12871
  dryRun: false,
12685
- provider: args.provider ?? config.provider ?? "opencode",
12872
+ provider: config.provider,
12873
+ model: config.model,
12874
+ fastProvider: config.fastProvider,
12875
+ fastModel: config.fastModel,
12876
+ agents: config.agents,
12686
12877
  source: config.source,
12878
+ org: config.org,
12879
+ project: config.project,
12880
+ workItemType: config.workItemType,
12881
+ iteration: config.iteration,
12882
+ area: config.area,
12883
+ username: config.username,
12884
+ planTimeout: config.planTimeout,
12687
12885
  concurrency: 1,
12688
12886
  force: true
12689
12887
  }
@@ -12699,9 +12897,9 @@ var init_recovery = __esm({
12699
12897
  "src/mcp/tools/recovery.ts"() {
12700
12898
  "use strict";
12701
12899
  init_manager();
12702
- init_config();
12703
12900
  init_interface2();
12704
12901
  init_fork_run();
12902
+ init_resolve_config();
12705
12903
  issueIdsSchema = z5.array(z5.string());
12706
12904
  }
12707
12905
  });
@@ -13305,7 +13503,7 @@ async function main() {
13305
13503
  process.exit(0);
13306
13504
  }
13307
13505
  if (args.version) {
13308
- console.log(`dispatch v${"1.5.0-beta.30f06ab"}`);
13506
+ console.log(`dispatch v${"1.5.0-beta.c0bcc82"}`);
13309
13507
  process.exit(0);
13310
13508
  }
13311
13509
  const orchestrator = await boot9({ cwd: args.cwd });