@exaudeus/workrail 3.26.1 → 3.27.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.
@@ -40,6 +40,7 @@ exports.DAEMON_SOUL_TEMPLATE = exports.DAEMON_SOUL_DEFAULT = exports.DAEMON_SESS
40
40
  exports.readDaemonSessionState = readDaemonSessionState;
41
41
  exports.readAllDaemonSessions = readAllDaemonSessions;
42
42
  exports.runStartupRecovery = runStartupRecovery;
43
+ exports.makeContinueWorkflowTool = makeContinueWorkflowTool;
43
44
  exports.makeBashTool = makeBashTool;
44
45
  exports.buildSessionRecap = buildSessionRecap;
45
46
  exports.buildSystemPrompt = buildSystemPrompt;
@@ -337,7 +338,7 @@ function getSchemas() {
337
338
  };
338
339
  return _schemas;
339
340
  }
340
- function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas) {
341
+ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas, _executeContinueWorkflowFn = index_js_1.executeContinueWorkflow) {
341
342
  return {
342
343
  name: 'continue_workflow',
343
344
  description: 'Advance the WorkRail workflow to the next step. Call this after completing all work ' +
@@ -345,7 +346,8 @@ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas
345
346
  inputSchema: schemas['ContinueWorkflowParams'],
346
347
  label: 'Continue Workflow',
347
348
  execute: async (_toolCallId, params) => {
348
- const result = await (0, index_js_1.executeContinueWorkflow)({
349
+ console.log(`[WorkflowRunner] Tool: continue_workflow sessionId=${sessionId}`);
350
+ const result = await _executeContinueWorkflowFn({
349
351
  continueToken: params.continueToken,
350
352
  intent: (params.intent ?? 'advance'),
351
353
  output: params.notesMarkdown
@@ -359,8 +361,50 @@ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas
359
361
  const out = result.value.response;
360
362
  const continueToken = out.continueToken ?? '';
361
363
  const checkpointToken = out.checkpointToken ?? null;
362
- if (continueToken) {
363
- await persistTokens(sessionId, continueToken, checkpointToken);
364
+ const persistToken = (out.kind === 'blocked' ? out.nextCall?.params.continueToken : undefined) ?? continueToken;
365
+ if (persistToken) {
366
+ await persistTokens(sessionId, persistToken, checkpointToken);
367
+ }
368
+ if (out.kind === 'blocked') {
369
+ const retryToken = out.nextCall?.params.continueToken ?? continueToken;
370
+ const lines = ['## Step blocked -- action required\n'];
371
+ for (const blocker of out.blockers.blockers) {
372
+ lines.push(blocker.message);
373
+ if (blocker.suggestedFix) {
374
+ lines.push(`\nWhat to do: ${blocker.suggestedFix}`);
375
+ }
376
+ lines.push('');
377
+ }
378
+ if (out.validation) {
379
+ if (out.validation.issues.length > 0) {
380
+ lines.push('**Issues:**');
381
+ for (const issue of out.validation.issues)
382
+ lines.push(`- ${issue}`);
383
+ lines.push('');
384
+ }
385
+ if (out.validation.suggestions.length > 0) {
386
+ lines.push('**Suggestions:**');
387
+ for (const s of out.validation.suggestions)
388
+ lines.push(`- ${s}`);
389
+ lines.push('');
390
+ }
391
+ }
392
+ if (out.assessmentFollowup) {
393
+ lines.push(`**Follow-up required:** ${out.assessmentFollowup.title}`);
394
+ lines.push(out.assessmentFollowup.guidance);
395
+ lines.push('');
396
+ }
397
+ if (out.retryable) {
398
+ lines.push(`Retry the same step with corrected output.\n\ncontinueToken: ${retryToken}`);
399
+ }
400
+ else {
401
+ lines.push(`You cannot proceed without resolving this. Inform the user and wait for their response, then call continue_workflow.\n\ncontinueToken: ${retryToken}`);
402
+ }
403
+ const feedback = lines.join('\n');
404
+ return {
405
+ content: [{ type: 'text', text: feedback }],
406
+ details: out,
407
+ };
364
408
  }
365
409
  if (out.isComplete) {
366
410
  onComplete(params.notesMarkdown);
@@ -391,6 +435,7 @@ function makeBashTool(workspacePath, schemas) {
391
435
  inputSchema: schemas['BashParams'],
392
436
  label: 'Bash',
393
437
  execute: async (_toolCallId, params) => {
438
+ console.log(`[WorkflowRunner] Tool: bash "${String(params.command).slice(0, 80)}"`);
394
439
  const cwd = params.cwd ?? workspacePath;
395
440
  try {
396
441
  const { stdout, stderr } = await execAsync(params.command, {
@@ -637,6 +682,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
637
682
  reject(new Error('Workflow timed out'));
638
683
  }, sessionTimeoutMs);
639
684
  });
685
+ console.log(`[WorkflowRunner] Agent loop started: sessionId=${sessionId} workflowId=${trigger.workflowId} modelId=${modelId}`);
640
686
  await Promise.race([agent.prompt(buildUserMessage(initialPrompt)), timeoutPromise])
641
687
  .catch((err) => {
642
688
  agent.abort();
@@ -662,6 +708,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry) {
662
708
  unsubscribe();
663
709
  if (timeoutHandle !== undefined)
664
710
  clearTimeout(timeoutHandle);
711
+ console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
665
712
  }
666
713
  if (timeoutReason !== null) {
667
714
  daemonRegistry?.unregister(sessionId, 'failed');
@@ -425,11 +425,17 @@ let HttpServer = class HttpServer {
425
425
  const lockData = JSON.parse(lockContent);
426
426
  const { reclaim, reason } = this.shouldReclaimLock(lockData);
427
427
  if (!reclaim) {
428
- console.error(`[Dashboard] Secondary mode: primary lock valid (PID ${lockData.pid}), yielding`);
428
+ try {
429
+ process.stderr.write(`[Dashboard] Secondary mode: primary lock valid (PID ${lockData.pid}), yielding\n`);
430
+ }
431
+ catch { }
429
432
  return false;
430
433
  }
431
434
  else {
432
- console.error(`[Dashboard] Lock reclaim needed: ${reason}`);
435
+ try {
436
+ process.stderr.write(`[Dashboard] Lock reclaim needed: ${reason}\n`);
437
+ }
438
+ catch { }
433
439
  }
434
440
  const tempPath = `${this.lockFile}.${process.pid}.${Date.now()}`;
435
441
  const newLockData = {
@@ -459,7 +465,24 @@ let HttpServer = class HttpServer {
459
465
  throw err;
460
466
  }
461
467
  }
462
- console.error('[Dashboard] Lock reclaimed successfully');
468
+ try {
469
+ const writtenContent = await promises_1.default.readFile(this.lockFile, 'utf-8');
470
+ const writtenData = JSON.parse(writtenContent);
471
+ if (writtenData.pid !== process.pid) {
472
+ try {
473
+ process.stderr.write(`[Dashboard] Lost lock election (winner PID ${writtenData.pid}), yielding\n`);
474
+ }
475
+ catch { }
476
+ return false;
477
+ }
478
+ }
479
+ catch {
480
+ return await this.tryBecomePrimary();
481
+ }
482
+ try {
483
+ process.stderr.write('[Dashboard] Lock reclaimed successfully\n');
484
+ }
485
+ catch { }
463
486
  this.isPrimary = true;
464
487
  this.setupPrimaryCleanup();
465
488
  this.heartbeat.start();
@@ -468,10 +491,16 @@ let HttpServer = class HttpServer {
468
491
  catch (error) {
469
492
  await promises_1.default.unlink(tempPath).catch(() => { });
470
493
  if (error.code === 'ENOENT') {
471
- console.error('[Dashboard] Lock deleted during reclaim, trying fresh');
494
+ try {
495
+ process.stderr.write('[Dashboard] Lock deleted during reclaim, trying fresh\n');
496
+ }
497
+ catch { }
472
498
  return await this.tryBecomePrimary();
473
499
  }
474
- console.error('[Dashboard] Lock reclaim failed:', error.message);
500
+ try {
501
+ process.stderr.write(`[Dashboard] Lock reclaim failed: ${error.message}\n`);
502
+ }
503
+ catch { }
475
504
  return false;
476
505
  }
477
506
  }
@@ -479,7 +508,10 @@ let HttpServer = class HttpServer {
479
508
  if (error.code === 'ENOENT') {
480
509
  return await this.tryBecomePrimary();
481
510
  }
482
- console.error('[Dashboard] Lock file corrupted, attempting fresh claim');
511
+ try {
512
+ process.stderr.write('[Dashboard] Lock file corrupted, attempting fresh claim\n');
513
+ }
514
+ catch { }
483
515
  await promises_1.default.unlink(this.lockFile).catch(() => { });
484
516
  return await this.tryBecomePrimary();
485
517
  }
@@ -574,14 +606,34 @@ let HttpServer = class HttpServer {
574
606
  }
575
607
  printBanner() {
576
608
  const line = '═'.repeat(60);
577
- console.error(`\n${line}`);
578
- console.error(`🔧 Workrail MCP Server Started`);
579
- console.error(line);
580
- console.error(`📊 Dashboard: ${this.baseUrl} ${this.isPrimary ? '(PRIMARY - All Projects)' : '(Legacy Mode)'}`);
581
- console.error(`💾 Sessions: ${this.sessionManager.getSessionsRoot()}`);
582
- console.error(`🏗️ Project: ${this.sessionManager.getProjectId()}`);
583
- console.error(line);
584
- console.error();
609
+ try {
610
+ process.stderr.write(`\n${line}\n`);
611
+ }
612
+ catch { }
613
+ try {
614
+ process.stderr.write(`🔧 Workrail MCP Server Started\n`);
615
+ }
616
+ catch { }
617
+ try {
618
+ process.stderr.write(`${line}\n`);
619
+ }
620
+ catch { }
621
+ try {
622
+ process.stderr.write(`📊 Dashboard: ${this.baseUrl} ${this.isPrimary ? '(PRIMARY - All Projects)' : '(Legacy Mode)'}\n`);
623
+ }
624
+ catch { }
625
+ try {
626
+ process.stderr.write(`💾 Sessions: ${this.sessionManager.getSessionsRoot()}\n`);
627
+ }
628
+ catch { }
629
+ try {
630
+ process.stderr.write(`🏗️ Project: ${this.sessionManager.getProjectId()}\n`);
631
+ }
632
+ catch { }
633
+ try {
634
+ process.stderr.write(`${line}\n\n`);
635
+ }
636
+ catch { }
585
637
  }
586
638
  async openDashboard(sessionId) {
587
639
  if (!this.baseUrl) {
@@ -246,8 +246,8 @@
246
246
  "bytes": 31
247
247
  },
248
248
  "cli.js": {
249
- "sha256": "6d4c8dcb090ec7bdd765fdb855c1b39a9969ff854b9d37a363333cde7a75a1bb",
250
- "bytes": 12273
249
+ "sha256": "d144366ebfb945c4947770899e2774516d594a085582ca3ee50a0cd46c081723",
250
+ "bytes": 13297
251
251
  },
252
252
  "cli/commands/cleanup.d.ts": {
253
253
  "sha256": "efe1f9e2ecd58e92007ed38b9581a3852c2babe4b3f2a97237dccd878eebe7ec",
@@ -429,12 +429,12 @@
429
429
  "sha256": "cf9d09641f1c31fffe6c7835b30bbbad52572befec1acab7fb9a0c188431af36",
430
430
  "bytes": 60355
431
431
  },
432
- "console/assets/index-HhtarvD5.js": {
433
- "sha256": "08f261e8ee93433485c04dfceb497517787ffa0c9e871415a7a0aca5d05fe731",
432
+ "console/assets/index-FtTaDku8.js": {
433
+ "sha256": "625240493d1fa1987817a5799b9f45244eac9e4c195a1a979c6be8c32e313eb2",
434
434
  "bytes": 754653
435
435
  },
436
436
  "console/index.html": {
437
- "sha256": "c9d211c6ed52bc75fb22ae55d4e9bb349f22dcde4a03911603749fb0a68facdf",
437
+ "sha256": "293f13ff188d761de3d29dddb7ed9c6ff2e1fc804989b6c43f43c6aca7da7f13",
438
438
  "bytes": 417
439
439
  },
440
440
  "core/error-handler.d.ts": {
@@ -470,12 +470,12 @@
470
470
  "bytes": 1009
471
471
  },
472
472
  "daemon/workflow-runner.d.ts": {
473
- "sha256": "31917d6322216bdb971c3023a9991d850a71bd25434846039e1024ed9983a22b",
474
- "bytes": 2791
473
+ "sha256": "049d32530519c1a9b82959df91e144a6324b0b9dfe7b6ed507624c450cc2fb77",
474
+ "bytes": 3178
475
475
  },
476
476
  "daemon/workflow-runner.js": {
477
- "sha256": "6dea8cd52c9e885b1b56a8c2485727e1a1db47c79912433514c37626209f780a",
478
- "bytes": 29981
477
+ "sha256": "0243e7e779f08edf8258fd3946f815c0ff6cc4fd10eaa1511d358da31cbf1f68",
478
+ "bytes": 32730
479
479
  },
480
480
  "di/container.d.ts": {
481
481
  "sha256": "003bb7fb7478d627524b9b1e76bd0a963a243794a687ff233b96dc0e33a06d9f",
@@ -634,8 +634,8 @@
634
634
  "bytes": 2025
635
635
  },
636
636
  "infrastructure/session/HttpServer.js": {
637
- "sha256": "cb3c733c5dfd4e998e0170d60e1689390454afdeb9c8a5838fd9af3a9da2fa56",
638
- "bytes": 31241
637
+ "sha256": "914e38d3f9ed1264075d226e24e60a32d8a2f1d425631404873a1be62f067386",
638
+ "bytes": 32736
639
639
  },
640
640
  "infrastructure/session/SessionDataNormalizer.d.ts": {
641
641
  "sha256": "c89bb5e00d7d01fb4aa6d0095602541de53c425c6b99b67fa8367eb29cb63e9e",
@@ -762,8 +762,8 @@
762
762
  "bytes": 409
763
763
  },
764
764
  "mcp-server.js": {
765
- "sha256": "1ab44bb05d75fe445fce6ea819c489e93e1d39ffedcd7a80c566e7085a0dd796",
766
- "bytes": 3577
765
+ "sha256": "3445ca2f12e70a07ce693ed6efde67826ebab00f85716954def4ee82f86da0cd",
766
+ "bytes": 3668
767
767
  },
768
768
  "mcp/assert-output.d.ts": {
769
769
  "sha256": "f1b821c3652423b15a09d2d1c5a042ee565a503c3d7196bd8220fbe697e0dc75",
@@ -1122,8 +1122,8 @@
1122
1122
  "bytes": 960
1123
1123
  },
1124
1124
  "mcp/server.js": {
1125
- "sha256": "b60d150f98355033ef172a58e7c2b12f01848f0bbb4a2b633276791ffce96637",
1126
- "bytes": 16178
1125
+ "sha256": "655e05345310422de6487e9f045e04c53266eb0c71d4a6482a605c67761eb25e",
1126
+ "bytes": 16962
1127
1127
  },
1128
1128
  "mcp/step-content-envelope.d.ts": {
1129
1129
  "sha256": "19bd63c4d4de1d5d93393d346625d28ffd1bebdc320d4ba4e694cb740ec97d3b",
@@ -1174,36 +1174,36 @@
1174
1174
  "bytes": 8822
1175
1175
  },
1176
1176
  "mcp/transports/bridge-entry.d.ts": {
1177
- "sha256": "83be835fd6beba67c8eb6f1ec23a998599ca13008522dd472d443d824601bbbe",
1178
- "bytes": 2496
1177
+ "sha256": "ba4c1df0691feeb79f4c7f9a8347807c86dbbb180cc4d5ba66740b6f8f216231",
1178
+ "bytes": 3556
1179
1179
  },
1180
1180
  "mcp/transports/bridge-entry.js": {
1181
- "sha256": "e975252f9c7b9c79ad19f3f380eeae46f97d6b229a0f7130780c1620f85fea0a",
1182
- "bytes": 13220
1181
+ "sha256": "f48e2c8700fec74daa5a9a47ab59143a44eba85ea938a408e2970029d53e23b5",
1182
+ "bytes": 20196
1183
1183
  },
1184
1184
  "mcp/transports/bridge-events.d.ts": {
1185
- "sha256": "e0e8a7a8477e05840aa0d1a1e563c466d6c0c7af8d0a123b22a4338e0a18e32b",
1186
- "bytes": 879
1185
+ "sha256": "ba4034309d5a4d762166c058079a9b461d0c403c5b329d2f5ee3bf4bd4f446e1",
1186
+ "bytes": 1324
1187
1187
  },
1188
1188
  "mcp/transports/bridge-events.js": {
1189
1189
  "sha256": "40e0eeb42aec6fb2906ac3da1d9f017cb9b9de56125736d4e90667239c5f019b",
1190
1190
  "bytes": 955
1191
1191
  },
1192
1192
  "mcp/transports/fatal-exit.d.ts": {
1193
- "sha256": "c4a6fe6f17cab799577bd527e6bd6df404b01855de05b0edb9176a81cfa104db",
1194
- "bytes": 380
1193
+ "sha256": "e51b420a4f4b9d21cff8c79180618fb0ee43d17f4fb402b1fa05d05e9cd0b4bc",
1194
+ "bytes": 490
1195
1195
  },
1196
1196
  "mcp/transports/fatal-exit.js": {
1197
- "sha256": "519d281c8affd4672c4232ba8b718bb15d6a1ace214a9f6896d01e6092d0fe13",
1198
- "bytes": 2756
1197
+ "sha256": "3b9f7b93cbb672b8491fd6fd73e4c799cccf7e51567edf0b9fc0faccbc1f41c8",
1198
+ "bytes": 3593
1199
1199
  },
1200
1200
  "mcp/transports/http-entry.d.ts": {
1201
1201
  "sha256": "35d313b120dcf38643de9462559163581b89943fe432706986252e8b698b9507",
1202
1202
  "bytes": 70
1203
1203
  },
1204
1204
  "mcp/transports/http-entry.js": {
1205
- "sha256": "f070de7c59b9b33db9c1a04a289fb0f83852832e8370bd09bc57ee33ff2d37a9",
1206
- "bytes": 3477
1205
+ "sha256": "31ab4e9f518e1f9b6fd46a8b7e221639029abea51e0cbfe22e5008fecdc46e6b",
1206
+ "bytes": 3887
1207
1207
  },
1208
1208
  "mcp/transports/http-listener.d.ts": {
1209
1209
  "sha256": "6c6cd6dcfe110ed8fa1dc2f9c96caba55959555f98048d3694ac104f42d6d51a",
@@ -1213,21 +1213,29 @@
1213
1213
  "sha256": "d9b7deb9015e55b5b1e9b3e415b74a7fbbe8b6afb94bbd5a5f7957cd1a7f3f50",
1214
1214
  "bytes": 3534
1215
1215
  },
1216
+ "mcp/transports/primary-tombstone.d.ts": {
1217
+ "sha256": "b03c75684b5191752b9a0494a5a6c2b463e8dcf6d2d22d7e265cb6f94454513a",
1218
+ "bytes": 891
1219
+ },
1220
+ "mcp/transports/primary-tombstone.js": {
1221
+ "sha256": "faec43962d124e5a296ec1c4419693f33ea2c9fd25e26831bd9c486389001c74",
1222
+ "bytes": 1545
1223
+ },
1216
1224
  "mcp/transports/shutdown-hooks.d.ts": {
1217
1225
  "sha256": "abf3799e44183c8a7e3614e2f14c1d7c8c4bb4ce0b720d3005ee165e4dd6f211",
1218
1226
  "bytes": 547
1219
1227
  },
1220
1228
  "mcp/transports/shutdown-hooks.js": {
1221
- "sha256": "b44d0daaabfc4a896a910691637473d238127157f762743fec35625b6d12d4eb",
1222
- "bytes": 2701
1229
+ "sha256": "e9b6261e223700d1d0d6253edff824c5ed6c42778afba2b18575b48923876e01",
1230
+ "bytes": 3082
1223
1231
  },
1224
1232
  "mcp/transports/stdio-entry.d.ts": {
1225
1233
  "sha256": "4ced3c9e00ef67555781dea74315290eea8a9dbffd38155bc00c3fb07b0c1794",
1226
1234
  "bytes": 59
1227
1235
  },
1228
1236
  "mcp/transports/stdio-entry.js": {
1229
- "sha256": "2213a3989715b972806c5a28566e0cd3a92da580382c154025fd0b5e023a4f98",
1230
- "bytes": 3908
1237
+ "sha256": "3acac4d05321d0bccb00aae61053b58327afc95bd4a05c1b2b4e853f774c036e",
1238
+ "bytes": 4299
1231
1239
  },
1232
1240
  "mcp/transports/transport-mode.d.ts": {
1233
1241
  "sha256": "1c59128ab0174bd2a113fff17521e6339ca367f2b8980c2f2c164ec393c10518",
@@ -1493,6 +1501,14 @@
1493
1501
  "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
1494
1502
  "bytes": 77
1495
1503
  },
1504
+ "trigger/daemon-console.d.ts": {
1505
+ "sha256": "8c1dca4a4683e4a96a8dc007df556309da0d2a2076ff0981c3b80f148cb5b3ad",
1506
+ "bytes": 937
1507
+ },
1508
+ "trigger/daemon-console.js": {
1509
+ "sha256": "51056eff122fea677803e8f17f800aa94c03cc796210f8d2ef40e767710444d0",
1510
+ "bytes": 5448
1511
+ },
1496
1512
  "trigger/delivery-action.d.ts": {
1497
1513
  "sha256": "58109eaa7124d2864e1d872ff0148548932d2fa4d7e672f5fd83d4bbe40afdda",
1498
1514
  "bytes": 1188
@@ -246,27 +246,43 @@ async function composeServer() {
246
246
  console.error('[PerfTrace] WORKRAIL_DEV=1 -- tool call timing active');
247
247
  }
248
248
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
249
- const { name, arguments: args } = request.params;
250
- const handlerStartMs = Date.now();
251
- const handlerStartHr = performance.now();
252
- const handler = handlers[name];
253
- if (!handler) {
254
- const unknownResult = {
255
- content: [{ type: 'text', text: `Unknown tool: ${name}` }],
249
+ try {
250
+ const { name, arguments: args } = request.params;
251
+ const handlerStartMs = Date.now();
252
+ const handlerStartHr = performance.now();
253
+ const handler = handlers[name];
254
+ if (!handler) {
255
+ const unknownResult = {
256
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
257
+ isError: true,
258
+ };
259
+ const durationMs = Math.round((performance.now() - handlerStartHr) * 100) / 100;
260
+ try {
261
+ timingSink({ toolName: name ?? '(unknown)', startedAtMs: handlerStartMs, durationMs, outcome: 'unknown_tool' });
262
+ }
263
+ catch {
264
+ }
265
+ return unknownResult;
266
+ }
267
+ const requestCtx = ctx.v2
268
+ ? { ...ctx, v2: { ...ctx.v2, resolvedRootUris: rootsManager.getCurrentRootUris() } }
269
+ : ctx;
270
+ return await (0, tool_call_timing_js_1.withToolCallTiming)(name, () => handler(args ?? {}, requestCtx), timingSink);
271
+ }
272
+ catch (err) {
273
+ process.stderr.write(`[WorkRail] Unhandled exception at CallTool boundary: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
274
+ return {
275
+ content: [{
276
+ type: 'text',
277
+ text: JSON.stringify({
278
+ code: 'INTERNAL_ERROR',
279
+ message: 'WorkRail encountered an unexpected error. This is not caused by your input.',
280
+ retry: { kind: 'retryable', suggestedDelayMs: 0 },
281
+ }),
282
+ }],
256
283
  isError: true,
257
284
  };
258
- const durationMs = Math.round((performance.now() - handlerStartHr) * 100) / 100;
259
- try {
260
- timingSink({ toolName: name ?? '(unknown)', startedAtMs: handlerStartMs, durationMs, outcome: 'unknown_tool' });
261
- }
262
- catch {
263
- }
264
- return unknownResult;
265
285
  }
266
- const requestCtx = ctx.v2
267
- ? { ...ctx, v2: { ...ctx.v2, resolvedRootUris: rootsManager.getCurrentRootUris() } }
268
- : ctx;
269
- return (0, tool_call_timing_js_1.withToolCallTiming)(name, () => handler(args ?? {}, requestCtx), timingSink);
270
286
  });
271
287
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
272
288
  resources: [
@@ -4,6 +4,8 @@ export interface BridgeConfig {
4
4
  readonly reconnectMaxAttempts: number;
5
5
  readonly forwardTimeoutMs: number;
6
6
  readonly maxRespawnAttempts: number;
7
+ readonly spawnLockStaleMs: number;
8
+ readonly waitForPrimaryPollMs: number;
7
9
  }
8
10
  export declare const DEFAULT_BRIDGE_CONFIG: BridgeConfig;
9
11
  type HttpBridgeTransport = {
@@ -20,6 +22,8 @@ export type ConnectionState = {
20
22
  readonly attempt: number;
21
23
  readonly maxAttempts: number;
22
24
  readonly respawnBudget: number;
25
+ } | {
26
+ readonly kind: 'waiting_for_primary';
23
27
  } | {
24
28
  readonly kind: 'closed';
25
29
  };
@@ -30,6 +34,12 @@ export type ReconnectOutcome = {
30
34
  } | {
31
35
  readonly kind: 'aborted';
32
36
  };
37
+ export type SpawnLockResult = {
38
+ readonly kind: 'acquired';
39
+ } | {
40
+ readonly kind: 'skipped';
41
+ readonly reason: string;
42
+ };
33
43
  export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
34
44
  export type SpawnLike = (command: string, args: ReadonlyArray<string>, opts: {
35
45
  readonly env: NodeJS.ProcessEnv;
@@ -38,11 +48,31 @@ export type SpawnLike = (command: string, args: ReadonlyArray<string>, opts: {
38
48
  }) => {
39
49
  unref: () => void;
40
50
  };
51
+ export type WriteFileSyncLike = (path: string, content: string, opts: {
52
+ flag: 'wx';
53
+ }) => void;
54
+ export type StatSyncLike = (path: string) => {
55
+ mtimeMs: number;
56
+ };
57
+ export type UnlinkSyncLike = (path: string) => void;
58
+ export type HealthResponse = {
59
+ readonly port: number;
60
+ readonly pid: number;
61
+ };
41
62
  export declare function detectHealthyPrimary(port: number, opts?: {
42
63
  retries?: number;
43
64
  baseDelayMs?: number;
44
65
  fetch?: FetchLike;
45
- }): Promise<number | null>;
66
+ }): Promise<HealthResponse | null>;
67
+ export declare function spawnLockPath(port: number): string;
68
+ export declare function acquireSpawnLock(port: number, staleMs: number, deps?: {
69
+ readonly writeFileSync?: WriteFileSyncLike;
70
+ readonly statSync?: StatSyncLike;
71
+ readonly unlinkSync?: UnlinkSyncLike;
72
+ }): SpawnLockResult;
73
+ export declare function releaseSpawnLock(port: number, deps?: {
74
+ readonly unlinkSync?: UnlinkSyncLike;
75
+ }): void;
46
76
  export declare function spawnPrimary(port: number, deps: {
47
77
  spawn: SpawnLike;
48
78
  fetch?: FetchLike;
@@ -57,6 +87,7 @@ type OutcomeHandlerDeps = {
57
87
  readonly setConnectionState: (state: ConnectionState) => void;
58
88
  readonly performShutdown: (reason: string) => void;
59
89
  readonly startReconnectLoop: () => void;
90
+ readonly startWaitLoop: () => void;
60
91
  readonly triggerSpawn: () => Promise<void>;
61
92
  readonly config: Pick<BridgeConfig, 'reconnectMaxAttempts'>;
62
93
  };
@@ -66,5 +97,6 @@ export declare function handleReconnectOutcome(outcome: ReconnectOutcome, reconn
66
97
  export declare function startBridgeServer(primaryPort: number, config?: BridgeConfig, deps?: {
67
98
  spawn?: SpawnLike;
68
99
  fetch?: FetchLike;
100
+ originalPrimaryPid?: number;
69
101
  }): Promise<void>;
70
102
  export {};