@exaudeus/workrail 3.33.0 → 3.34.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.
Files changed (38) hide show
  1. package/dist/cli-worktrain.js +167 -8
  2. package/dist/console-ui/assets/{index-BuJFLLfY.js → index-C1JXnwZS.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/daemon/agent-loop.d.ts +1 -0
  5. package/dist/daemon/agent-loop.js +1 -1
  6. package/dist/daemon/daemon-events.d.ts +17 -1
  7. package/dist/daemon/workflow-runner.d.ts +1 -1
  8. package/dist/daemon/workflow-runner.js +96 -21
  9. package/dist/manifest.json +43 -67
  10. package/dist/mcp/handlers/v2-error-mapping.d.ts +3 -0
  11. package/dist/mcp/handlers/v2-error-mapping.js +2 -0
  12. package/dist/mcp/handlers/v2-execution/advance.js +25 -0
  13. package/dist/mcp/handlers/v2-execution/continue-advance.js +7 -0
  14. package/dist/mcp/transports/http-entry.js +0 -7
  15. package/dist/mcp/transports/stdio-entry.js +0 -8
  16. package/dist/mcp-server.d.ts +0 -2
  17. package/dist/mcp-server.js +1 -42
  18. package/dist/v2/durable-core/domain/observation-builder.d.ts +3 -0
  19. package/dist/v2/durable-core/domain/observation-builder.js +2 -2
  20. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +2 -1
  21. package/dist/v2/durable-core/domain/prompt-renderer.js +10 -0
  22. package/dist/v2/usecases/console-service.js +65 -14
  23. package/dist/v2/usecases/console-types.d.ts +1 -0
  24. package/docs/design/bridge-removal-pr-a-candidates.md +115 -0
  25. package/docs/design/bridge-removal-pr-a-design-review.md +79 -0
  26. package/docs/design/bridge-removal-pr-a-implementation-plan.md +203 -0
  27. package/docs/discovery/design-candidates.md +180 -0
  28. package/docs/discovery/design-review-findings.md +110 -0
  29. package/docs/discovery/wr-discovery-goal-reframing.md +303 -0
  30. package/docs/ideas/backlog.md +266 -0
  31. package/package.json +1 -1
  32. package/workflows/wr.discovery.json +58 -7
  33. package/dist/mcp/transports/bridge-entry.d.ts +0 -102
  34. package/dist/mcp/transports/bridge-entry.js +0 -454
  35. package/dist/mcp/transports/bridge-events.d.ts +0 -55
  36. package/dist/mcp/transports/bridge-events.js +0 -24
  37. package/dist/mcp/transports/primary-tombstone.d.ts +0 -21
  38. package/dist/mcp/transports/primary-tombstone.js +0 -51
@@ -66,6 +66,10 @@ const BASH_TIMEOUT_MS = 5 * 60 * 1000;
66
66
  const MAX_SESSION_RECAP_NOTES = 3;
67
67
  const MAX_SESSION_NOTE_CHARS = 800;
68
68
  const DEFAULT_SESSION_TIMEOUT_MINUTES = 30;
69
+ const DEFAULT_MAX_TURNS = 50;
70
+ function withWorkrailSession(sid) {
71
+ return sid != null ? { workrailSessionId: sid } : {};
72
+ }
69
73
  exports.DAEMON_SESSIONS_DIR = path.join(os.homedir(), '.workrail', 'daemon-sessions');
70
74
  const MAX_ORPHAN_AGE_MS = 2 * 60 * 60 * 1000;
71
75
  const WORKRAIL_DIR = path.join(os.homedir(), '.workrail');
@@ -305,6 +309,13 @@ function getSchemas() {
305
309
  type: 'string',
306
310
  description: 'Notes on what you did in this step (10-30 lines, markdown).',
307
311
  },
312
+ artifacts: {
313
+ type: 'array',
314
+ items: {},
315
+ description: 'Optional structured artifacts to attach to this step. ' +
316
+ 'Include wr.assessment objects here when the step requires an assessment gate. ' +
317
+ 'Example: [{ "kind": "wr.assessment", "assessmentId": "<id>", "dimensions": { "<dimensionId>": "high" } }]',
318
+ },
308
319
  context: {
309
320
  type: 'object',
310
321
  additionalProperties: true,
@@ -343,17 +354,21 @@ function makeContinueWorkflowTool(sessionId, ctx, onAdvance, onComplete, schemas
343
354
  return {
344
355
  name: 'continue_workflow',
345
356
  description: 'Advance the WorkRail workflow to the next step. Call this after completing all work ' +
346
- 'required by the current step. Include your notes in notesMarkdown.',
357
+ 'required by the current step. Include your notes in notesMarkdown. ' +
358
+ 'When the step requires an assessment gate, include wr.assessment objects in artifacts.',
347
359
  inputSchema: schemas['ContinueWorkflowParams'],
348
360
  label: 'Continue Workflow',
349
361
  execute: async (_toolCallId, params) => {
350
362
  console.log(`[WorkflowRunner] Tool: continue_workflow sessionId=${sessionId}`);
351
- emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'continue_workflow', summary: params.intent ?? 'advance', ...(workrailSessionId != null ? { workrailSessionId } : {}) });
363
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'continue_workflow', summary: params.intent ?? 'advance', ...withWorkrailSession(workrailSessionId) });
352
364
  const result = await _executeContinueWorkflowFn({
353
365
  continueToken: params.continueToken,
354
366
  intent: (params.intent ?? 'advance'),
355
- output: params.notesMarkdown
356
- ? { notesMarkdown: params.notesMarkdown }
367
+ output: (params.notesMarkdown || params.artifacts?.length)
368
+ ? {
369
+ ...(params.notesMarkdown ? { notesMarkdown: params.notesMarkdown } : {}),
370
+ ...(params.artifacts ? { artifacts: params.artifacts } : {}),
371
+ }
357
372
  : undefined,
358
373
  context: params.context,
359
374
  }, ctx);
@@ -439,7 +454,7 @@ function makeBashTool(workspacePath, schemas, sessionId, emitter, workrailSessio
439
454
  execute: async (_toolCallId, params) => {
440
455
  console.log(`[WorkflowRunner] Tool: bash "${String(params.command).slice(0, 80)}"`);
441
456
  if (sessionId)
442
- emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Bash', summary: String(params.command).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
457
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Bash', summary: String(params.command).slice(0, 80), ...withWorkrailSession(workrailSessionId) });
443
458
  const cwd = params.cwd ?? workspacePath;
444
459
  try {
445
460
  const { stdout, stderr } = await execAsync(params.command, {
@@ -483,7 +498,7 @@ function makeReadTool(schemas, sessionId, emitter, workrailSessionId) {
483
498
  label: 'Read',
484
499
  execute: async (_toolCallId, params) => {
485
500
  if (sessionId)
486
- emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary: String(params.filePath).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
501
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Read', summary: String(params.filePath).slice(0, 80), ...withWorkrailSession(workrailSessionId) });
487
502
  const content = await fs.readFile(params.filePath, 'utf8');
488
503
  return {
489
504
  content: [{ type: 'text', text: content }],
@@ -500,7 +515,7 @@ function makeWriteTool(schemas, sessionId, emitter, workrailSessionId) {
500
515
  label: 'Write',
501
516
  execute: async (_toolCallId, params) => {
502
517
  if (sessionId)
503
- emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Write', summary: String(params.filePath).slice(0, 80), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
518
+ emitter?.emit({ kind: 'tool_called', sessionId, toolName: 'Write', summary: String(params.filePath).slice(0, 80), ...withWorkrailSession(workrailSessionId) });
504
519
  await fs.mkdir(path.dirname(params.filePath), { recursive: true });
505
520
  await fs.writeFile(params.filePath, params.content, 'utf8');
506
521
  return {
@@ -516,7 +531,7 @@ async function appendIssueAsync(issuesDir, sessionId, record) {
516
531
  const line = JSON.stringify({ ...record, ts: Date.now() }) + '\n';
517
532
  await fs.appendFile(filePath, line, 'utf8');
518
533
  }
519
- function makeReportIssueTool(sessionId, emitter, issuesDirOverride) {
534
+ function makeReportIssueTool(sessionId, emitter, workrailSessionId, issuesDirOverride, onIssueSummary) {
520
535
  const issuesDir = issuesDirOverride ?? path.join(os.homedir(), '.workrail', 'issues');
521
536
  return {
522
537
  name: 'report_issue',
@@ -586,7 +601,9 @@ function makeReportIssueTool(sessionId, emitter, issuesDirOverride) {
586
601
  severity: record.severity,
587
602
  summary: record.summary,
588
603
  ...(record.continueToken !== undefined && { continueToken: record.continueToken }),
604
+ ...(workrailSessionId != null ? { workrailSessionId } : {}),
589
605
  });
606
+ onIssueSummary?.(record.summary);
590
607
  const isFatal = record.severity === 'fatal';
591
608
  const message = isFatal
592
609
  ? `FATAL issue recorded. Call continue_workflow with notes explaining the blocker, then the session will end.`
@@ -724,11 +741,17 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
724
741
  let isComplete = false;
725
742
  let pendingSteerText = null;
726
743
  let lastStepNotes;
744
+ let stepAdvanceCount = 0;
745
+ const lastNToolCalls = [];
746
+ const STUCK_REPEAT_THRESHOLD = 3;
747
+ const issueSummaries = [];
748
+ const MAX_ISSUE_SUMMARIES = 10;
727
749
  const onAdvance = (stepText, _continueToken) => {
728
750
  pendingSteerText = stepText;
751
+ stepAdvanceCount++;
729
752
  if (workrailSessionId !== null)
730
753
  daemonRegistry?.heartbeat(workrailSessionId);
731
- emitter?.emit({ kind: 'step_advanced', sessionId, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
754
+ emitter?.emit({ kind: 'step_advanced', sessionId, ...withWorkrailSession(workrailSessionId) });
732
755
  };
733
756
  const onComplete = (notes) => {
734
757
  isComplete = true;
@@ -769,7 +792,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
769
792
  }
770
793
  if (firstStep.isComplete) {
771
794
  await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => { });
772
- emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop', ...(workrailSessionId != null ? { workrailSessionId } : {}) });
795
+ emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: 'stop', ...withWorkrailSession(workrailSessionId) });
773
796
  if (workrailSessionId !== null)
774
797
  daemonRegistry?.unregister(workrailSessionId, 'completed');
775
798
  return { _tag: 'success', workflowId: trigger.workflowId, stopReason: 'stop' };
@@ -780,7 +803,11 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
780
803
  makeBashTool(trigger.workspacePath, schemas, sessionId, emitter, workrailSessionId),
781
804
  makeReadTool(schemas, sessionId, emitter, workrailSessionId),
782
805
  makeWriteTool(schemas, sessionId, emitter, workrailSessionId),
783
- makeReportIssueTool(sessionId, emitter),
806
+ makeReportIssueTool(sessionId, emitter, workrailSessionId, undefined, (summary) => {
807
+ if (issueSummaries.length < MAX_ISSUE_SUMMARIES) {
808
+ issueSummaries.push(summary);
809
+ }
810
+ }),
784
811
  ];
785
812
  const [soulContent, workspaceContext, sessionNotes] = await Promise.all([
786
813
  loadDaemonSoul(trigger.soulFile),
@@ -797,7 +824,13 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
797
824
  '\n\nComplete all step work, then call continue_workflow with your notes to begin.';
798
825
  const agentCallbacks = {
799
826
  onLlmTurnStarted: ({ messageCount }) => {
800
- emitter?.emit({ kind: 'llm_turn_started', sessionId, messageCount });
827
+ emitter?.emit({
828
+ kind: 'llm_turn_started',
829
+ sessionId,
830
+ messageCount,
831
+ modelId,
832
+ ...withWorkrailSession(workrailSessionId),
833
+ });
801
834
  },
802
835
  onLlmTurnCompleted: ({ stopReason, outputTokens, inputTokens, toolNamesRequested }) => {
803
836
  emitter?.emit({
@@ -807,16 +840,21 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
807
840
  outputTokens,
808
841
  inputTokens,
809
842
  toolNamesRequested,
843
+ ...withWorkrailSession(workrailSessionId),
810
844
  });
811
845
  },
812
846
  onToolCallStarted: ({ toolName, argsSummary }) => {
813
- emitter?.emit({ kind: 'tool_call_started', sessionId, toolName, argsSummary });
847
+ emitter?.emit({ kind: 'tool_call_started', sessionId, toolName, argsSummary, ...withWorkrailSession(workrailSessionId) });
848
+ lastNToolCalls.push({ toolName, argsSummary });
849
+ if (lastNToolCalls.length > STUCK_REPEAT_THRESHOLD) {
850
+ lastNToolCalls.shift();
851
+ }
814
852
  },
815
853
  onToolCallCompleted: ({ toolName, durationMs, resultSummary }) => {
816
- emitter?.emit({ kind: 'tool_call_completed', sessionId, toolName, durationMs, resultSummary });
854
+ emitter?.emit({ kind: 'tool_call_completed', sessionId, toolName, durationMs, resultSummary, ...withWorkrailSession(workrailSessionId) });
817
855
  },
818
856
  onToolCallFailed: ({ toolName, durationMs, errorMessage }) => {
819
- emitter?.emit({ kind: 'tool_call_failed', sessionId, toolName, durationMs, errorMessage });
857
+ emitter?.emit({ kind: 'tool_call_failed', sessionId, toolName, durationMs, errorMessage, ...withWorkrailSession(workrailSessionId) });
820
858
  },
821
859
  };
822
860
  const agent = new agent_loop_js_1.AgentLoop({
@@ -828,7 +866,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
828
866
  callbacks: agentCallbacks,
829
867
  });
830
868
  const sessionTimeoutMs = (trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES) * 60 * 1000;
831
- const maxTurns = trigger.agentConfig?.maxTurns ?? 0;
869
+ const maxTurns = trigger.agentConfig?.maxTurns ?? DEFAULT_MAX_TURNS;
832
870
  let timeoutReason = null;
833
871
  let turnCount = 0;
834
872
  const unsubscribe = agent.subscribe(async (event) => {
@@ -837,7 +875,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
837
875
  for (const toolResult of event.toolResults) {
838
876
  if (toolResult.isError) {
839
877
  const errorText = toolResult.result?.content[0]?.text ?? 'tool error';
840
- emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
878
+ emitter?.emit({ kind: 'tool_error', sessionId, toolName: toolResult.toolName, error: errorText.slice(0, 200), ...withWorkrailSession(workrailSessionId) });
841
879
  }
842
880
  }
843
881
  turnCount++;
@@ -846,6 +884,38 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
846
884
  agent.abort();
847
885
  return;
848
886
  }
887
+ if (lastNToolCalls.length === STUCK_REPEAT_THRESHOLD &&
888
+ lastNToolCalls.every((c) => c.toolName === lastNToolCalls[0]?.toolName && c.argsSummary === lastNToolCalls[0]?.argsSummary)) {
889
+ emitter?.emit({
890
+ kind: 'agent_stuck',
891
+ sessionId,
892
+ reason: 'repeated_tool_call',
893
+ detail: `Same tool+args called ${STUCK_REPEAT_THRESHOLD} times: ${lastNToolCalls[0]?.toolName ?? 'unknown'}`,
894
+ toolName: lastNToolCalls[0]?.toolName,
895
+ argsSummary: lastNToolCalls[0]?.argsSummary,
896
+ ...withWorkrailSession(workrailSessionId),
897
+ });
898
+ }
899
+ if (maxTurns > 0 &&
900
+ turnCount >= Math.floor(maxTurns * 0.8) &&
901
+ stepAdvanceCount === 0) {
902
+ emitter?.emit({
903
+ kind: 'agent_stuck',
904
+ sessionId,
905
+ reason: 'no_progress',
906
+ detail: `${turnCount} turns used, 0 step advances (${maxTurns} turn limit)`,
907
+ ...withWorkrailSession(workrailSessionId),
908
+ });
909
+ }
910
+ if (timeoutReason !== null) {
911
+ emitter?.emit({
912
+ kind: 'agent_stuck',
913
+ sessionId,
914
+ reason: 'timeout_imminent',
915
+ detail: `${timeoutReason === 'wall_clock' ? 'Wall-clock timeout' : 'Max-turn limit'} reached`,
916
+ ...withWorkrailSession(workrailSessionId),
917
+ });
918
+ }
849
919
  if (pendingSteerText !== null && !isComplete) {
850
920
  const text = pendingSteerText;
851
921
  pendingSteerText = null;
@@ -893,12 +963,12 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
893
963
  console.log(`[WorkflowRunner] Agent loop ended: sessionId=${sessionId} stopReason=${stopReason}${errorMessage ? ` error=${errorMessage.slice(0, 120)}` : ''}`);
894
964
  }
895
965
  if (timeoutReason !== null) {
896
- emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'timeout', detail: timeoutReason, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
966
+ emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'timeout', detail: timeoutReason, ...withWorkrailSession(workrailSessionId) });
897
967
  if (workrailSessionId !== null)
898
968
  daemonRegistry?.unregister(workrailSessionId, 'failed');
899
969
  const limitDescription = timeoutReason === 'wall_clock'
900
970
  ? `${trigger.agentConfig?.maxSessionMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES} minutes`
901
- : `${trigger.agentConfig?.maxTurns} turns`;
971
+ : `${trigger.agentConfig?.maxTurns ?? DEFAULT_MAX_TURNS} turns`;
902
972
  return {
903
973
  _tag: 'timeout',
904
974
  workflowId: trigger.workflowId,
@@ -909,14 +979,19 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
909
979
  }
910
980
  if (stopReason === 'error' || errorMessage) {
911
981
  const errMsg = errorMessage ?? 'Agent stopped with error reason';
912
- emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200), ...(workrailSessionId != null ? { workrailSessionId } : {}) });
982
+ emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'error', detail: errMsg.slice(0, 200), ...withWorkrailSession(workrailSessionId) });
913
983
  if (workrailSessionId !== null)
914
984
  daemonRegistry?.unregister(workrailSessionId, 'failed');
985
+ const lastToolCalled = lastNToolCalls.length > 0 ? lastNToolCalls[lastNToolCalls.length - 1] : null;
915
986
  const stuckMarker = `\n\nWORKTRAIN_STUCK: ${JSON.stringify({
916
987
  reason: 'session_error',
917
988
  error: errMsg.slice(0, 500),
918
989
  workflowId: trigger.workflowId,
919
990
  sessionId,
991
+ turnCount,
992
+ stepAdvanceCount,
993
+ ...(lastToolCalled !== null && { lastToolCalled }),
994
+ ...(issueSummaries.length > 0 && { issueSummaries }),
920
995
  })}`;
921
996
  return {
922
997
  _tag: 'error',
@@ -928,7 +1003,7 @@ async function runWorkflow(trigger, ctx, apiKey, daemonRegistry, emitter) {
928
1003
  }
929
1004
  await fs.unlink(path.join(exports.DAEMON_SESSIONS_DIR, `${sessionId}.json`)).catch(() => {
930
1005
  });
931
- emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: stopReason, ...(workrailSessionId != null ? { workrailSessionId } : {}) });
1006
+ emitter?.emit({ kind: 'session_completed', sessionId, workflowId: trigger.workflowId, outcome: 'success', detail: stopReason, ...withWorkrailSession(workrailSessionId) });
932
1007
  if (workrailSessionId !== null)
933
1008
  daemonRegistry?.unregister(workrailSessionId, 'completed');
934
1009
  return {
@@ -238,8 +238,8 @@
238
238
  "bytes": 31
239
239
  },
240
240
  "cli-worktrain.js": {
241
- "sha256": "95ddb85b341ae910a12b201f5e0c464fd04e3c9c012fe5c85d6669d0ccbea4e2",
242
- "bytes": 23028
241
+ "sha256": "018aa3bd80eee73b09bb865480ca856033e615c68c5aef51ef64e792beb66440",
242
+ "bytes": 29958
243
243
  },
244
244
  "cli.d.ts": {
245
245
  "sha256": "43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012",
@@ -445,12 +445,12 @@
445
445
  "sha256": "cf9d09641f1c31fffe6c7835b30bbbad52572befec1acab7fb9a0c188431af36",
446
446
  "bytes": 60355
447
447
  },
448
- "console-ui/assets/index-BuJFLLfY.js": {
449
- "sha256": "5270e62f91abdcc3354397a7086846754ff92a62c3bce7e954678b1aaafc2cdc",
448
+ "console-ui/assets/index-C1JXnwZS.js": {
449
+ "sha256": "aa1a4da8e109af4f86075ed2d5e9bff4d007310e2ac10db960300c0dd9c1368a",
450
450
  "bytes": 754653
451
451
  },
452
452
  "console-ui/index.html": {
453
- "sha256": "0f9e8aadf99ca291a4a784b641975c54408fd1ed1e069c7adc503973798cc647",
453
+ "sha256": "d6dadcb0ec4637edfd9b71b00c90c53f9e9920c6cd71a12446d606a5fe7018e4",
454
454
  "bytes": 417
455
455
  },
456
456
  "console/standalone-console.d.ts": {
@@ -470,16 +470,16 @@
470
470
  "bytes": 3493
471
471
  },
472
472
  "daemon/agent-loop.d.ts": {
473
- "sha256": "bb873fd41dae8f2a4e1eed5549d0f85f5b8c6e0ad421ec2c0ac4a767ed21252a",
474
- "bytes": 3745
473
+ "sha256": "a05b27f2cdc7bacd35bb41b9b271367ba9d1f550c9cbf873f1a9b4fa092e8bbb",
474
+ "bytes": 3779
475
475
  },
476
476
  "daemon/agent-loop.js": {
477
- "sha256": "c457a38dcf42c3e44a511e699b2e64353d40ec4cb2e3bf7c49fb6a5b4d4f4b11",
478
- "bytes": 9794
477
+ "sha256": "aa47bea99cf9a5ce35d2bc375b6dd51a9fbb68d36f2b185ee0a98176f67d647d",
478
+ "bytes": 9803
479
479
  },
480
480
  "daemon/daemon-events.d.ts": {
481
- "sha256": "3d090ec0804f7a59d0ab3f8e45321e6a5c9aebde60d5d805e58654d36cf838c6",
482
- "bytes": 3581
481
+ "sha256": "2195c771ec4829b37291c4a2f96c022269792b080d323f0a03623625287da4eb",
482
+ "bytes": 4196
483
483
  },
484
484
  "daemon/daemon-events.js": {
485
485
  "sha256": "b6841eef4634bb266faf81961c1e387b535dd64a74d58582f3f2bad8c3469d95",
@@ -502,12 +502,12 @@
502
502
  "bytes": 1009
503
503
  },
504
504
  "daemon/workflow-runner.d.ts": {
505
- "sha256": "9862fc8fc8a6652a6326e5b5b87df3006f15a9bd871efbe62990e5de4261e44e",
506
- "bytes": 3590
505
+ "sha256": "7c2b4283551676702906aeceb553eb1c329c254679d95a2dd2dc980c484d55dc",
506
+ "bytes": 3669
507
507
  },
508
508
  "daemon/workflow-runner.js": {
509
- "sha256": "89c6514eb2528c9c164af46f120c5f1550f9d858ef8db9edc5e83602fe48d296",
510
- "bytes": 44428
509
+ "sha256": "222605a6db78865108296f8302c617fdb1cb7791d1e84601eaba9aa26e1fd4c8",
510
+ "bytes": 47963
511
511
  },
512
512
  "di/container.d.ts": {
513
513
  "sha256": "003bb7fb7478d627524b9b1e76bd0a963a243794a687ff233b96dc0e33a06d9f",
@@ -790,12 +790,12 @@
790
790
  "bytes": 6002
791
791
  },
792
792
  "mcp-server.d.ts": {
793
- "sha256": "01d30540da2d1fbada4da5cb54fc287b13a53940d5cd6e9ccb0fd29fc3a71720",
794
- "bytes": 409
793
+ "sha256": "7ead2e703f41c763d04b37a1cf433380bec3551fbde206af6694fe9286ad4714",
794
+ "bytes": 203
795
795
  },
796
796
  "mcp-server.js": {
797
- "sha256": "3445ca2f12e70a07ce693ed6efde67826ebab00f85716954def4ee82f86da0cd",
798
- "bytes": 3668
797
+ "sha256": "5a7aae510601b6e1e53c0e977cfa0d47828cd255fc36911ec6e854bd9704fc4e",
798
+ "bytes": 1551
799
799
  },
800
800
  "mcp/assert-output.d.ts": {
801
801
  "sha256": "f1b821c3652423b15a09d2d1c5a042ee565a503c3d7196bd8220fbe697e0dc75",
@@ -974,12 +974,12 @@
974
974
  "bytes": 7199
975
975
  },
976
976
  "mcp/handlers/v2-error-mapping.d.ts": {
977
- "sha256": "1cf58654dd6f70a0e35b75435c835a410658a2c7220d1b86561124476c8742fa",
978
- "bytes": 1798
977
+ "sha256": "4522217a754afc6a2ee3834368decb7fb22ebf9d4c72fc0de9890903d652d76c",
978
+ "bytes": 1887
979
979
  },
980
980
  "mcp/handlers/v2-error-mapping.js": {
981
- "sha256": "7963acbc9d600741d8881c2659ec2dd914a631cf1587efb64498923da83abefe",
982
- "bytes": 10680
981
+ "sha256": "2893d7e0cc2b690155cfe9f944aa0017d73754b3934c2ebff00293889f6a10f8",
982
+ "bytes": 10948
983
983
  },
984
984
  "mcp/handlers/v2-execution-helpers.d.ts": {
985
985
  "sha256": "d43b4f7eed9bddeaa6f2eda19695147311d6a7ca38ad05457474df9a87ca3a0d",
@@ -1002,16 +1002,16 @@
1002
1002
  "bytes": 2116
1003
1003
  },
1004
1004
  "mcp/handlers/v2-execution/advance.js": {
1005
- "sha256": "904ebe3c7d252c7320786799b826ace2e2ac6ee89b291ad603aa998a330c963c",
1006
- "bytes": 2654
1005
+ "sha256": "849726273445bfc7e10352b6a0896a12b3aad7df952e63fe88016d59ce9c8e85",
1006
+ "bytes": 3828
1007
1007
  },
1008
1008
  "mcp/handlers/v2-execution/continue-advance.d.ts": {
1009
1009
  "sha256": "6de9b9bb05dee9bbc2c3729960601eca4eb59da5b2e2d2baa5acf20fd14cb118",
1010
1010
  "bytes": 2070
1011
1011
  },
1012
1012
  "mcp/handlers/v2-execution/continue-advance.js": {
1013
- "sha256": "70226fbb3b88e576564661913c5dc52ef72d46a05ad732cae401addc77eaeaea",
1014
- "bytes": 9836
1013
+ "sha256": "30e82a97410d19105859b49fcdb0711a602eb25d0a1ce2cc55a190f696926f1b",
1014
+ "bytes": 10227
1015
1015
  },
1016
1016
  "mcp/handlers/v2-execution/continue-rehydrate.d.ts": {
1017
1017
  "sha256": "8412323cb149f7818ed19400758da9e0452b35a18d080afd88955a03bd965b92",
@@ -1205,22 +1205,6 @@
1205
1205
  "sha256": "bdea37dfe3f2ef98be01899b067f841c645bda69c23dddd9e181f94b4b157c5e",
1206
1206
  "bytes": 8822
1207
1207
  },
1208
- "mcp/transports/bridge-entry.d.ts": {
1209
- "sha256": "ba4c1df0691feeb79f4c7f9a8347807c86dbbb180cc4d5ba66740b6f8f216231",
1210
- "bytes": 3556
1211
- },
1212
- "mcp/transports/bridge-entry.js": {
1213
- "sha256": "f48e2c8700fec74daa5a9a47ab59143a44eba85ea938a408e2970029d53e23b5",
1214
- "bytes": 20196
1215
- },
1216
- "mcp/transports/bridge-events.d.ts": {
1217
- "sha256": "46dda12ba2c77f5c36080cf94023ebd128282aa3fd550c5ae446ddd8b4014d3a",
1218
- "bytes": 1438
1219
- },
1220
- "mcp/transports/bridge-events.js": {
1221
- "sha256": "40e0eeb42aec6fb2906ac3da1d9f017cb9b9de56125736d4e90667239c5f019b",
1222
- "bytes": 955
1223
- },
1224
1208
  "mcp/transports/fatal-exit.d.ts": {
1225
1209
  "sha256": "e51b420a4f4b9d21cff8c79180618fb0ee43d17f4fb402b1fa05d05e9cd0b4bc",
1226
1210
  "bytes": 490
@@ -1234,8 +1218,8 @@
1234
1218
  "bytes": 70
1235
1219
  },
1236
1220
  "mcp/transports/http-entry.js": {
1237
- "sha256": "d322f7c7cb9b1628f881e8cc2b579f9e1872ead4754051a57927c131492e045c",
1238
- "bytes": 4043
1221
+ "sha256": "bace094c4e67408b846ba8f1e2084bd576a9125d99cfd553e91acfe983322694",
1222
+ "bytes": 3636
1239
1223
  },
1240
1224
  "mcp/transports/http-listener.d.ts": {
1241
1225
  "sha256": "6c6cd6dcfe110ed8fa1dc2f9c96caba55959555f98048d3694ac104f42d6d51a",
@@ -1245,14 +1229,6 @@
1245
1229
  "sha256": "d9b7deb9015e55b5b1e9b3e415b74a7fbbe8b6afb94bbd5a5f7957cd1a7f3f50",
1246
1230
  "bytes": 3534
1247
1231
  },
1248
- "mcp/transports/primary-tombstone.d.ts": {
1249
- "sha256": "b03c75684b5191752b9a0494a5a6c2b463e8dcf6d2d22d7e265cb6f94454513a",
1250
- "bytes": 891
1251
- },
1252
- "mcp/transports/primary-tombstone.js": {
1253
- "sha256": "faec43962d124e5a296ec1c4419693f33ea2c9fd25e26831bd9c486389001c74",
1254
- "bytes": 1545
1255
- },
1256
1232
  "mcp/transports/shutdown-hooks.d.ts": {
1257
1233
  "sha256": "abf3799e44183c8a7e3614e2f14c1d7c8c4bb4ce0b720d3005ee165e4dd6f211",
1258
1234
  "bytes": 547
@@ -1266,8 +1242,8 @@
1266
1242
  "bytes": 59
1267
1243
  },
1268
1244
  "mcp/transports/stdio-entry.js": {
1269
- "sha256": "cedb82176a7a6507afc03ceb5ffb6b43ed57e83b3c55ade030aeb38aa141242c",
1270
- "bytes": 4804
1245
+ "sha256": "907ddc69b7d182f6cc2c03aa375e45adec400a7af263c709914edd92af47cb2a",
1246
+ "bytes": 4360
1271
1247
  },
1272
1248
  "mcp/transports/transport-mode.d.ts": {
1273
1249
  "sha256": "1c59128ab0174bd2a113fff17521e6339ca367f2b8980c2f2c164ec393c10518",
@@ -1918,12 +1894,12 @@
1918
1894
  "bytes": 1709
1919
1895
  },
1920
1896
  "v2/durable-core/domain/observation-builder.d.ts": {
1921
- "sha256": "22841567fb4cc5a4b6582951d07a33eaab8546ad55e53d4459f61f5e56e442a4",
1922
- "bytes": 619
1897
+ "sha256": "7053a137d41ee443cf878b95eb19d08c632f17fb304c9e0ea25efaad42689f64",
1898
+ "bytes": 692
1923
1899
  },
1924
1900
  "v2/durable-core/domain/observation-builder.js": {
1925
- "sha256": "df833dd1e0663acd4daf99cf27dc599b2c5e4a9e9c60ef88c1b0932892f9c62a",
1926
- "bytes": 1951
1901
+ "sha256": "6ed532d8d3087c87fcb462c4ad5d792a1fea742c5f78b26c75c18adb23b78458",
1902
+ "bytes": 1935
1927
1903
  },
1928
1904
  "v2/durable-core/domain/outputs.d.ts": {
1929
1905
  "sha256": "adc32e4b86c8036eac61096fe83371140c7de140db414227041a8854435f8f54",
@@ -1934,12 +1910,12 @@
1934
1910
  "bytes": 942
1935
1911
  },
1936
1912
  "v2/durable-core/domain/prompt-renderer.d.ts": {
1937
- "sha256": "da680f8f51d526c2ea9b169a4667212a5cc187f539f5d0dc6dcc969a3b1dea02",
1938
- "bytes": 1295
1913
+ "sha256": "e362b644bf9fbac3b704cdf4195bf44cce12b4028c88265445147ddebb0c7239",
1914
+ "bytes": 1482
1939
1915
  },
1940
1916
  "v2/durable-core/domain/prompt-renderer.js": {
1941
- "sha256": "9bb35163d6b40c739a3cf4773dfc97ba76f51b638a4524b88eb4e2e548f70f03",
1942
- "bytes": 19065
1917
+ "sha256": "513d49cb4e2cbe28e1c3567251626f1a10fb9df7a8be28317dc1357ec9929c68",
1918
+ "bytes": 19757
1943
1919
  },
1944
1920
  "v2/durable-core/domain/reason-model.d.ts": {
1945
1921
  "sha256": "a944e7e0d9b3c73468488263cb0aa1e446c023f8084fd2af53cbda3f3bfcd37a",
@@ -2930,12 +2906,12 @@
2930
2906
  "bytes": 1701
2931
2907
  },
2932
2908
  "v2/usecases/console-service.js": {
2933
- "sha256": "5cdef324f7f596d6fc31093646e395858be7ae1bf8a6b76f1ed68edc6c5fa57c",
2934
- "bytes": 36184
2909
+ "sha256": "b14048889eca919325680e1e61d786519ba863567235134a844f32c23c75b830",
2910
+ "bytes": 38111
2935
2911
  },
2936
2912
  "v2/usecases/console-types.d.ts": {
2937
- "sha256": "f25576a77f9be887b9a6e41192c1fb26c7f9433b4bbc851393a2e3a708948d72",
2938
- "bytes": 7685
2913
+ "sha256": "b2f0161db2bd9dba2d75226b51a91f55f657566951a82e7ac0b692f038e36596",
2914
+ "bytes": 7716
2939
2915
  },
2940
2916
  "v2/usecases/console-types.js": {
2941
2917
  "sha256": "d43aa81f5bc89faa359e0f97c814ba25155591ff078fbb9bfd40f8c7c9683230",
@@ -26,6 +26,9 @@ export type InternalError = {
26
26
  } | {
27
27
  readonly kind: 'token_scope_mismatch';
28
28
  readonly message: string;
29
+ } | {
30
+ readonly kind: 'blocked_attempt_limit_exceeded';
31
+ readonly message: string;
29
32
  };
30
33
  export declare function isInternalError(e: unknown): e is InternalError;
31
34
  export declare function normalizeTokenErrorMessage(message: string): string;
@@ -124,6 +124,8 @@ function mapInternalErrorToToolError(e) {
124
124
  return internalError('WorkRail could not compute the next workflow step. This is not caused by your input.', (0, v2_execution_helpers_js_1.internalSuggestion)('Retry the call.', 'WorkRail could not compute the next step.'));
125
125
  case 'advance_next_missing_context':
126
126
  return (0, types_js_1.errNotRetryable)('PRECONDITION_FAILED', e.message, { suggestion: 'Set the required context variable in the `context` field of your continue_workflow output. The variable must be a JSON array.' });
127
+ case 'blocked_attempt_limit_exceeded':
128
+ return (0, types_js_1.errNotRetryable)('PRECONDITION_FAILED', e.message, { suggestion: 'Submit a valid wr.assessment artifact with the correct dimensions. Use the format shown in the blocked step prompt.' });
127
129
  default:
128
130
  const _exhaustive = e;
129
131
  return internalError('WorkRail encountered an unexpected error. This is not caused by your input.', (0, v2_execution_helpers_js_1.internalSuggestion)('Retry the call.', 'WorkRail has an internal error.'));
@@ -3,6 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.advanceAndRecord = advanceAndRecord;
4
4
  const neverthrow_1 = require("neverthrow");
5
5
  const v2_advance_core_js_1 = require("../v2-advance-core.js");
6
+ const MAX_BLOCKED_ATTEMPT_RETRIES = 3;
7
+ function countBlockedAttemptChainDepth(nodeId, lockedIndex) {
8
+ let depth = 0;
9
+ let currentId = String(nodeId);
10
+ while (currentId !== null) {
11
+ const nodeEvent = lockedIndex.nodeCreatedByNodeId.get(currentId);
12
+ if (!nodeEvent || nodeEvent.data.nodeKind !== 'blocked_attempt') {
13
+ break;
14
+ }
15
+ depth += 1;
16
+ currentId = nodeEvent.data.parentNodeId ?? null;
17
+ }
18
+ return depth;
19
+ }
6
20
  function advanceAndRecord(args) {
7
21
  const { truth, sessionId, runId, nodeId, attemptId, workflowHash, dedupeKey, inputContext, inputOutput, lock, pinnedWorkflow, snapshotStore, sessionStore, sha256, idFactory } = args;
8
22
  const hasRun = args.lockedIndex.runStartedByRunId.has(String(runId));
@@ -29,6 +43,17 @@ function advanceAndRecord(args) {
29
43
  message: 'Cannot retry a terminal blocked_attempt node (blocked.kind=terminal_block).',
30
44
  });
31
45
  }
46
+ const chainDepth = countBlockedAttemptChainDepth(nodeId, args.lockedIndex);
47
+ if (chainDepth >= MAX_BLOCKED_ATTEMPT_RETRIES) {
48
+ return (0, neverthrow_1.errAsync)({
49
+ kind: 'blocked_attempt_limit_exceeded',
50
+ message: `Assessment gate failed after ${MAX_BLOCKED_ATTEMPT_RETRIES} attempts. ` +
51
+ `Submit a valid wr.assessment artifact. Required format:\n` +
52
+ `\`\`\`json\n` +
53
+ `{ "artifacts": [{ "kind": "wr.assessment", "assessmentId": "<id>", "dimensions": { "<dimensionId>": "high" } }] }\n` +
54
+ `\`\`\``,
55
+ });
56
+ }
32
57
  return (0, v2_advance_core_js_1.executeAdvanceCore)({
33
58
  mode: { kind: 'retry', blockedNodeId: nodeId, blockedSnapshot: snap },
34
59
  truth, sessionId, runId, attemptId, workflowHash, dedupeKey,
@@ -158,6 +158,13 @@ function handleAdvanceIntent(args) {
158
158
  suggestion: 'Set the required context variable in the `context` field of your continue_workflow output. The variable must be a JSON array.',
159
159
  };
160
160
  }
161
+ if (cause.kind === 'blocked_attempt_limit_exceeded') {
162
+ return {
163
+ kind: 'precondition_failed',
164
+ message: cause.message,
165
+ suggestion: 'Submit a valid wr.assessment artifact with the correct dimensions. Use the format shown in the error message.',
166
+ };
167
+ }
161
168
  return {
162
169
  kind: 'invariant_violation',
163
170
  message: `Advance failed due to internal error: ${cause.kind}`,
@@ -41,16 +41,12 @@ const server_js_1 = require("../server.js");
41
41
  const http_listener_js_1 = require("./http-listener.js");
42
42
  const shutdown_hooks_js_1 = require("./shutdown-hooks.js");
43
43
  const fatal_exit_js_1 = require("./fatal-exit.js");
44
- const primary_tombstone_js_1 = require("./primary-tombstone.js");
45
- const bridge_events_js_1 = require("./bridge-events.js");
46
44
  const crypto = __importStar(require("crypto"));
47
45
  const express_1 = __importDefault(require("express"));
48
46
  const HTTP_PORT_SCAN_END = 3199;
49
47
  async function startHttpServer(port) {
50
48
  (0, fatal_exit_js_1.registerFatalHandlers)('http');
51
49
  (0, fatal_exit_js_1.logStartup)('http', { port });
52
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'primary_started', transport: 'http', port });
53
- (0, primary_tombstone_js_1.clearTombstone)();
54
50
  const { server, ctx } = await (0, server_js_1.composeServer)();
55
51
  const scanEnd = Math.max(port, HTTP_PORT_SCAN_END);
56
52
  const listener = await (0, http_listener_js_1.bindWithPortFallback)(port, scanEnd);
@@ -76,9 +72,6 @@ async function startHttpServer(port) {
76
72
  console.error(`[Transport] MCP endpoint: http://localhost:${boundPort}/mcp`);
77
73
  (0, shutdown_hooks_js_1.wireShutdownHooks)({
78
74
  onBeforeTerminate: async () => {
79
- if (boundPort != null) {
80
- (0, primary_tombstone_js_1.writeTombstone)(boundPort, process.pid);
81
- }
82
75
  await listener.stop();
83
76
  await ctx.httpServer?.stop();
84
77
  },
@@ -37,8 +37,6 @@ exports.startStdioServer = startStdioServer;
37
37
  const server_js_1 = require("../server.js");
38
38
  const shutdown_hooks_js_1 = require("./shutdown-hooks.js");
39
39
  const fatal_exit_js_1 = require("./fatal-exit.js");
40
- const primary_tombstone_js_1 = require("./primary-tombstone.js");
41
- const bridge_events_js_1 = require("./bridge-events.js");
42
40
  const INITIAL_ROOTS_TIMEOUT_MS = 1000;
43
41
  async function fetchInitialRootsWithTimeout(server) {
44
42
  return Promise.race([
@@ -51,8 +49,6 @@ async function fetchInitialRootsWithTimeout(server) {
51
49
  async function startStdioServer() {
52
50
  (0, fatal_exit_js_1.registerFatalHandlers)('stdio');
53
51
  (0, fatal_exit_js_1.logStartup)('stdio');
54
- (0, bridge_events_js_1.logBridgeEvent)({ kind: 'primary_started', transport: 'stdio' });
55
- (0, primary_tombstone_js_1.clearTombstone)();
56
52
  const { server, ctx, rootsManager } = await (0, server_js_1.composeServer)();
57
53
  (0, fatal_exit_js_1.registerGracefulShutdown)(async () => { await ctx.httpServer?.stop(); });
58
54
  const { StdioServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
@@ -104,10 +100,6 @@ async function startStdioServer() {
104
100
  (0, shutdown_hooks_js_1.wireStdinShutdown)();
105
101
  (0, shutdown_hooks_js_1.wireShutdownHooks)({
106
102
  onBeforeTerminate: async () => {
107
- const port = ctx.httpServer?.getPort();
108
- if (port != null) {
109
- (0, primary_tombstone_js_1.writeTombstone)(port, process.pid);
110
- }
111
103
  await ctx.httpServer?.stop();
112
104
  },
113
105
  });
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  export { startStdioServer } from './mcp/transports/stdio-entry.js';
3
3
  export { startHttpServer } from './mcp/transports/http-entry.js';
4
- export { startBridgeServer, detectHealthyPrimary } from './mcp/transports/bridge-entry.js';
5
4
  export { composeServer } from './mcp/server.js';
6
- export declare function waitForStdinReadable(timeoutMs: number, stdin?: NodeJS.ReadableStream): Promise<boolean>;