@exaudeus/workrail 3.14.0 → 3.16.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 (156) hide show
  1. package/dist/application/services/validation-engine.js +4 -9
  2. package/dist/application/services/workflow-compiler.js +4 -6
  3. package/dist/application/services/workflow-service.d.ts +2 -0
  4. package/dist/application/services/workflow-service.js +3 -0
  5. package/dist/console/assets/index-BE5PAgPO.js +28 -0
  6. package/dist/console/assets/index-BZNM03t1.css +1 -0
  7. package/dist/console/index.html +2 -2
  8. package/dist/engine/engine-factory.js +2 -2
  9. package/dist/engine/types.d.ts +1 -1
  10. package/dist/env-flags.d.ts +1 -0
  11. package/dist/env-flags.js +4 -0
  12. package/dist/infrastructure/session/HttpServer.d.ts +3 -3
  13. package/dist/infrastructure/session/HttpServer.js +68 -74
  14. package/dist/infrastructure/storage/caching-workflow-storage.d.ts +2 -0
  15. package/dist/infrastructure/storage/caching-workflow-storage.js +15 -6
  16. package/dist/infrastructure/storage/file-workflow-storage.js +3 -4
  17. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +9 -8
  18. package/dist/manifest.json +283 -219
  19. package/dist/mcp/assert-output.d.ts +37 -0
  20. package/dist/mcp/assert-output.js +52 -0
  21. package/dist/mcp/boundary-coercion.d.ts +1 -0
  22. package/dist/mcp/boundary-coercion.js +44 -0
  23. package/dist/mcp/dev-mode.d.ts +1 -0
  24. package/dist/mcp/dev-mode.js +4 -0
  25. package/dist/mcp/handler-factory.js +12 -9
  26. package/dist/mcp/handlers/session.js +8 -9
  27. package/dist/mcp/handlers/shared/request-workflow-reader.d.ts +5 -0
  28. package/dist/mcp/handlers/shared/request-workflow-reader.js +47 -2
  29. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +1 -1
  30. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +4 -5
  31. package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +2 -0
  32. package/dist/mcp/handlers/v2-advance-core/event-builders.js +6 -6
  33. package/dist/mcp/handlers/v2-advance-core/index.d.ts +2 -0
  34. package/dist/mcp/handlers/v2-advance-core/index.js +5 -4
  35. package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +2 -0
  36. package/dist/mcp/handlers/v2-advance-core/input-validation.js +32 -9
  37. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
  38. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +2 -2
  39. package/dist/mcp/handlers/v2-advance-core/outcome-success.d.ts +2 -0
  40. package/dist/mcp/handlers/v2-advance-core/outcome-success.js +1 -1
  41. package/dist/mcp/handlers/v2-checkpoint.d.ts +1 -1
  42. package/dist/mcp/handlers/v2-checkpoint.js +5 -6
  43. package/dist/mcp/handlers/v2-execution/advance.d.ts +4 -2
  44. package/dist/mcp/handlers/v2-execution/advance.js +5 -7
  45. package/dist/mcp/handlers/v2-execution/continue-advance.js +56 -26
  46. package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +1 -1
  47. package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +9 -9
  48. package/dist/mcp/handlers/v2-execution/replay.d.ts +6 -4
  49. package/dist/mcp/handlers/v2-execution/replay.js +47 -30
  50. package/dist/mcp/handlers/v2-execution/start.d.ts +3 -3
  51. package/dist/mcp/handlers/v2-execution/start.js +31 -12
  52. package/dist/mcp/handlers/v2-execution/workflow-object-cache.d.ts +5 -0
  53. package/dist/mcp/handlers/v2-execution/workflow-object-cache.js +19 -0
  54. package/dist/mcp/handlers/v2-execution-helpers.d.ts +1 -0
  55. package/dist/mcp/handlers/v2-execution-helpers.js +23 -7
  56. package/dist/mcp/handlers/v2-resume.d.ts +1 -1
  57. package/dist/mcp/handlers/v2-resume.js +3 -4
  58. package/dist/mcp/handlers/v2-state-conversion.js +5 -1
  59. package/dist/mcp/handlers/v2-workflow.d.ts +100 -0
  60. package/dist/mcp/handlers/v2-workflow.js +155 -31
  61. package/dist/mcp/handlers/workflow.d.ts +2 -5
  62. package/dist/mcp/handlers/workflow.js +15 -12
  63. package/dist/mcp/output-schemas.d.ts +123 -29
  64. package/dist/mcp/output-schemas.js +36 -18
  65. package/dist/mcp/server.js +70 -5
  66. package/dist/mcp/tool-call-timing.d.ts +24 -0
  67. package/dist/mcp/tool-call-timing.js +85 -0
  68. package/dist/mcp/tool-descriptions.js +17 -9
  69. package/dist/mcp/transports/http-entry.js +3 -2
  70. package/dist/mcp/transports/http-listener.d.ts +1 -0
  71. package/dist/mcp/transports/http-listener.js +25 -0
  72. package/dist/mcp/transports/shutdown-hooks.d.ts +4 -1
  73. package/dist/mcp/transports/shutdown-hooks.js +3 -2
  74. package/dist/mcp/transports/stdio-entry.js +6 -28
  75. package/dist/mcp/v2/tools.d.ts +6 -0
  76. package/dist/mcp/v2/tools.js +2 -0
  77. package/dist/mcp/v2-response-formatter.js +2 -4
  78. package/dist/mcp/validation/schema-introspection.d.ts +1 -0
  79. package/dist/mcp/validation/schema-introspection.js +15 -5
  80. package/dist/mcp/validation/suggestion-generator.js +2 -2
  81. package/dist/mcp/workflow-protocol-contracts.js +5 -1
  82. package/dist/runtime/adapters/node-process-signals.d.ts +1 -0
  83. package/dist/runtime/adapters/node-process-signals.js +5 -0
  84. package/dist/runtime/adapters/noop-process-signals.d.ts +1 -0
  85. package/dist/runtime/adapters/noop-process-signals.js +2 -0
  86. package/dist/runtime/ports/process-signals.d.ts +1 -0
  87. package/dist/types/workflow-definition.d.ts +3 -2
  88. package/dist/types/workflow.d.ts +3 -0
  89. package/dist/types/workflow.js +35 -26
  90. package/dist/v2/durable-core/domain/context-template-resolver.js +2 -2
  91. package/dist/v2/durable-core/domain/function-definition-expander.js +2 -17
  92. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +1 -0
  93. package/dist/v2/durable-core/domain/prompt-renderer.js +23 -18
  94. package/dist/v2/durable-core/domain/recap-recovery.js +23 -16
  95. package/dist/v2/durable-core/domain/retrieval-contract.js +13 -7
  96. package/dist/v2/durable-core/session-index.d.ts +22 -0
  97. package/dist/v2/durable-core/session-index.js +58 -0
  98. package/dist/v2/durable-core/sorted-event-log.d.ts +6 -0
  99. package/dist/v2/durable-core/sorted-event-log.js +15 -0
  100. package/dist/v2/infra/local/fs/index.js +8 -8
  101. package/dist/v2/infra/local/session-store/index.d.ts +1 -1
  102. package/dist/v2/infra/local/session-store/index.js +71 -61
  103. package/dist/v2/infra/local/session-summary-provider/index.js +9 -4
  104. package/dist/v2/infra/local/snapshot-store/index.js +2 -1
  105. package/dist/v2/infra/local/workspace-anchor/index.js +4 -1
  106. package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
  107. package/dist/v2/projections/assessment-consequences.d.ts +2 -1
  108. package/dist/v2/projections/assessment-consequences.js +0 -5
  109. package/dist/v2/projections/assessments.d.ts +2 -1
  110. package/dist/v2/projections/assessments.js +2 -4
  111. package/dist/v2/projections/gaps.d.ts +2 -1
  112. package/dist/v2/projections/gaps.js +0 -5
  113. package/dist/v2/projections/preferences.d.ts +2 -1
  114. package/dist/v2/projections/preferences.js +0 -5
  115. package/dist/v2/projections/run-context.d.ts +2 -2
  116. package/dist/v2/projections/run-context.js +0 -5
  117. package/dist/v2/projections/run-dag.js +7 -1
  118. package/dist/v2/projections/run-execution-trace.d.ts +8 -0
  119. package/dist/v2/projections/run-execution-trace.js +124 -0
  120. package/dist/v2/projections/run-status-signals.d.ts +2 -2
  121. package/dist/v2/usecases/console-routes.d.ts +3 -1
  122. package/dist/v2/usecases/console-routes.js +149 -3
  123. package/dist/v2/usecases/console-service.d.ts +2 -0
  124. package/dist/v2/usecases/console-service.js +87 -26
  125. package/dist/v2/usecases/console-types.d.ts +65 -0
  126. package/dist/v2/usecases/worktree-service.js +87 -8
  127. package/package.json +7 -6
  128. package/spec/authoring-spec.json +82 -1
  129. package/spec/workflow-tags.json +132 -0
  130. package/spec/workflow.schema.json +21 -11
  131. package/workflows/adaptive-ticket-creation.json +33 -8
  132. package/workflows/architecture-scalability-audit.json +50 -9
  133. package/workflows/bug-investigation.agentic.v2.json +43 -14
  134. package/workflows/coding-task-workflow-agentic.json +57 -38
  135. package/workflows/coding-task-workflow-agentic.lean.v2.json +129 -34
  136. package/workflows/coding-task-workflow-agentic.v2.json +97 -30
  137. package/workflows/cross-platform-code-conversion.v2.json +175 -48
  138. package/workflows/document-creation-workflow.json +49 -12
  139. package/workflows/documentation-update-workflow.json +9 -2
  140. package/workflows/intelligent-test-case-generation.json +9 -2
  141. package/workflows/learner-centered-course-workflow.json +273 -266
  142. package/workflows/mr-review-workflow.agentic.v2.json +88 -14
  143. package/workflows/personal-learning-materials-creation-branched.json +181 -174
  144. package/workflows/presentation-creation.json +167 -160
  145. package/workflows/production-readiness-audit.json +61 -15
  146. package/workflows/relocation-workflow-us.json +21 -5
  147. package/workflows/routines/tension-driven-design.json +1 -1
  148. package/workflows/scoped-documentation-workflow.json +9 -2
  149. package/workflows/test-artifact-loop-control.json +1 -2
  150. package/workflows/ui-ux-design-workflow.json +334 -0
  151. package/workflows/workflow-diagnose-environment.json +7 -1
  152. package/workflows/workflow-for-workflows.json +514 -484
  153. package/workflows/workflow-for-workflows.v2.json +55 -11
  154. package/workflows/wr.discovery.json +118 -29
  155. package/dist/console/assets/index-DW78t31j.css +0 -1
  156. package/dist/console/assets/index-EsSXrC_a.js +0 -28
@@ -47,6 +47,8 @@ const workspace_roots_manager_js_1 = require("./workspace-roots-manager.js");
47
47
  const index_js_2 = require("../v2/infra/local/directory-listing/index.js");
48
48
  const index_js_3 = require("../v2/infra/local/session-summary-provider/index.js");
49
49
  const tool_factory_js_1 = require("./tool-factory.js");
50
+ const dev_mode_js_1 = require("./dev-mode.js");
51
+ const tool_call_timing_js_1 = require("./tool-call-timing.js");
50
52
  const handler_factory_js_1 = require("./handler-factory.js");
51
53
  const workflow_tool_edition_selector_js_1 = require("./workflow-tool-edition-selector.js");
52
54
  const tools_js_1 = require("./tools.js");
@@ -159,8 +161,9 @@ function toMcpTool(tool) {
159
161
  };
160
162
  }
161
163
  async function composeServer() {
162
- await (0, container_js_1.bootstrap)({ runtimeMode: { kind: 'production' } });
164
+ await (0, container_js_1.bootstrap)();
163
165
  const ctx = await createToolContext();
166
+ const timingRingBuffer = new tool_call_timing_js_1.ToolCallTimingRingBuffer(tool_call_timing_js_1.DEFAULT_RING_BUFFER_CAPACITY);
164
167
  if (ctx.v2 && ctx.httpServer && ctx.v2.dataDir && ctx.v2.directoryListing) {
165
168
  const { ConsoleService } = await Promise.resolve().then(() => __importStar(require('../v2/usecases/console-service.js')));
166
169
  const { mountConsoleRoutes } = await Promise.resolve().then(() => __importStar(require('../v2/usecases/console-routes.js')));
@@ -171,7 +174,7 @@ async function composeServer() {
171
174
  snapshotStore: ctx.v2.snapshotStore,
172
175
  pinnedWorkflowStore: ctx.v2.pinnedStore,
173
176
  });
174
- ctx.httpServer.mountRoutes((app) => mountConsoleRoutes(app, consoleService));
177
+ ctx.httpServer.mountRoutes((app) => mountConsoleRoutes(app, consoleService, ctx.workflowService, timingRingBuffer));
175
178
  console.error('[Console] v2 Console API routes mounted at /api/v2/');
176
179
  }
177
180
  ctx.httpServer?.finalize();
@@ -190,13 +193,14 @@ async function composeServer() {
190
193
  }
191
194
  const rootsManager = new workspace_roots_manager_js_1.WorkspaceRootsManager();
192
195
  const { Server } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/index.js')));
193
- const { CallToolRequestSchema, ListToolsRequestSchema, } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
196
+ const { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/types.js')));
194
197
  const server = new Server({
195
198
  name: 'workrail-server',
196
199
  version: '0.1.0',
197
200
  }, {
198
201
  capabilities: {
199
202
  tools: {},
203
+ resources: {},
200
204
  },
201
205
  });
202
206
  const tools = workflowEdition.tools.map(toMcpTool);
@@ -213,19 +217,80 @@ async function composeServer() {
213
217
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
214
218
  tools,
215
219
  }));
220
+ const timingSink = dev_mode_js_1.DEV_MODE
221
+ ? (0, tool_call_timing_js_1.composeSinks)((0, tool_call_timing_js_1.createRingBufferSink)(timingRingBuffer), (0, tool_call_timing_js_1.createDevPerfSink)())
222
+ : (0, tool_call_timing_js_1.createRingBufferSink)(timingRingBuffer);
223
+ if (dev_mode_js_1.DEV_MODE) {
224
+ console.error('[PerfTrace] WORKRAIL_DEV=1 -- tool call timing active');
225
+ }
216
226
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
217
227
  const { name, arguments: args } = request.params;
228
+ const handlerStartMs = Date.now();
229
+ const handlerStartHr = performance.now();
218
230
  const handler = handlers[name];
219
231
  if (!handler) {
220
- return {
232
+ const unknownResult = {
221
233
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
222
234
  isError: true,
223
235
  };
236
+ const durationMs = Math.round((performance.now() - handlerStartHr) * 100) / 100;
237
+ try {
238
+ timingSink({ toolName: name ?? '(unknown)', startedAtMs: handlerStartMs, durationMs, outcome: 'unknown_tool' });
239
+ }
240
+ catch {
241
+ }
242
+ return unknownResult;
224
243
  }
225
244
  const requestCtx = ctx.v2
226
245
  ? { ...ctx, v2: { ...ctx.v2, resolvedRootUris: rootsManager.getCurrentRootUris() } }
227
246
  : ctx;
228
- return handler(args ?? {}, requestCtx);
247
+ return (0, tool_call_timing_js_1.withToolCallTiming)(name, () => handler(args ?? {}, requestCtx), timingSink);
248
+ });
249
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
250
+ resources: [
251
+ {
252
+ uri: 'workrail://tags',
253
+ name: 'WorkRail Tag Catalog',
254
+ description: 'Closed-set tag definitions for workflow discovery. ' +
255
+ 'Read this before calling list_workflows — it tells you which tags exist ' +
256
+ 'and when to use each one, so you can call list_workflows with tags=[...] ' +
257
+ 'instead of loading all 36+ workflows into context.',
258
+ mimeType: 'application/json',
259
+ },
260
+ ],
261
+ }));
262
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
263
+ const uri = request.params?.uri ?? '';
264
+ if (uri !== 'workrail://tags') {
265
+ return {
266
+ contents: [],
267
+ isError: true,
268
+ _meta: { error: `Unknown resource: ${uri}` },
269
+ };
270
+ }
271
+ try {
272
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
273
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
274
+ const tagsPath = path.resolve(__dirname, '../../spec/workflow-tags.json');
275
+ const raw = fs.readFileSync(tagsPath, 'utf-8');
276
+ return {
277
+ contents: [
278
+ {
279
+ uri: 'workrail://tags',
280
+ mimeType: 'application/json',
281
+ text: raw,
282
+ },
283
+ ],
284
+ };
285
+ }
286
+ catch (err) {
287
+ const message = err instanceof Error ? err.message : String(err);
288
+ return {
289
+ contents: [],
290
+ isError: true,
291
+ _meta: { error: `Failed to read tag catalog: ${message}` },
292
+ };
293
+ }
229
294
  });
230
295
  return { server, ctx, rootsManager, rootsReader: rootsManager, tools, handlers };
231
296
  }
@@ -0,0 +1,24 @@
1
+ export type ToolCallOutcome = 'success' | 'error' | 'unknown_tool';
2
+ export interface ToolCallTiming {
3
+ readonly toolName: string;
4
+ readonly startedAtMs: number;
5
+ readonly durationMs: number;
6
+ readonly outcome: ToolCallOutcome;
7
+ }
8
+ export type ToolCallTimingSink = (timing: ToolCallTiming) => void;
9
+ export declare const noopToolCallTimingSink: ToolCallTimingSink;
10
+ export declare class ToolCallTimingRingBuffer {
11
+ private readonly capacity;
12
+ private readonly buffer;
13
+ private head;
14
+ private count;
15
+ constructor(capacity: number);
16
+ push(timing: ToolCallTiming): void;
17
+ recent(limit?: number): readonly ToolCallTiming[];
18
+ get size(): number;
19
+ }
20
+ export declare const DEFAULT_RING_BUFFER_CAPACITY = 100;
21
+ export declare function createRingBufferSink(buffer: ToolCallTimingRingBuffer): ToolCallTimingSink;
22
+ export declare function createDevPerfSink(): ToolCallTimingSink;
23
+ export declare function composeSinks(...sinks: ToolCallTimingSink[]): ToolCallTimingSink;
24
+ export declare function withToolCallTiming<T>(toolName: string, handler: () => Promise<T>, sink: ToolCallTimingSink): Promise<T>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_RING_BUFFER_CAPACITY = exports.ToolCallTimingRingBuffer = exports.noopToolCallTimingSink = void 0;
4
+ exports.createRingBufferSink = createRingBufferSink;
5
+ exports.createDevPerfSink = createDevPerfSink;
6
+ exports.composeSinks = composeSinks;
7
+ exports.withToolCallTiming = withToolCallTiming;
8
+ const noopToolCallTimingSink = () => { };
9
+ exports.noopToolCallTimingSink = noopToolCallTimingSink;
10
+ class ToolCallTimingRingBuffer {
11
+ constructor(capacity) {
12
+ this.capacity = capacity;
13
+ this.head = 0;
14
+ this.count = 0;
15
+ if (capacity < 1)
16
+ throw new Error('Ring buffer capacity must be >= 1');
17
+ this.buffer = new Array(capacity).fill(undefined);
18
+ }
19
+ push(timing) {
20
+ this.buffer[this.head] = timing;
21
+ this.head = (this.head + 1) % this.capacity;
22
+ if (this.count < this.capacity)
23
+ this.count++;
24
+ }
25
+ recent(limit) {
26
+ const n = limit !== undefined ? Math.min(limit, this.count) : this.count;
27
+ const results = [];
28
+ for (let i = 1; i <= n; i++) {
29
+ const idx = (this.head - i + this.capacity) % this.capacity;
30
+ const entry = this.buffer[idx];
31
+ if (entry !== undefined)
32
+ results.push(entry);
33
+ }
34
+ return results;
35
+ }
36
+ get size() {
37
+ return this.count;
38
+ }
39
+ }
40
+ exports.ToolCallTimingRingBuffer = ToolCallTimingRingBuffer;
41
+ exports.DEFAULT_RING_BUFFER_CAPACITY = 100;
42
+ function createRingBufferSink(buffer) {
43
+ return (timing) => {
44
+ buffer.push(timing);
45
+ };
46
+ }
47
+ function createDevPerfSink() {
48
+ return (timing) => {
49
+ const outcomeLabel = timing.outcome === 'success' ? 'OK' : timing.outcome.toUpperCase();
50
+ const line = `[PerfTrace] ${timing.toolName} ${timing.durationMs.toFixed(1)}ms [${outcomeLabel}]`;
51
+ console.error(line);
52
+ };
53
+ }
54
+ function composeSinks(...sinks) {
55
+ return (timing) => {
56
+ for (const sink of sinks) {
57
+ try {
58
+ sink(timing);
59
+ }
60
+ catch { }
61
+ }
62
+ };
63
+ }
64
+ async function withToolCallTiming(toolName, handler, sink) {
65
+ const startedAtMs = Date.now();
66
+ const startHr = performance.now();
67
+ let outcome = 'error';
68
+ try {
69
+ const result = await handler();
70
+ outcome = result?.isError === true ? 'error' : 'success';
71
+ return result;
72
+ }
73
+ catch (err) {
74
+ outcome = 'error';
75
+ throw err;
76
+ }
77
+ finally {
78
+ const durationMs = Math.round((performance.now() - startHr) * 100) / 100;
79
+ try {
80
+ sink({ toolName, startedAtMs, durationMs, outcome });
81
+ }
82
+ catch {
83
+ }
84
+ }
85
+ }
@@ -48,12 +48,17 @@ This tool provides:
48
48
  - Field descriptions and validation rules
49
49
  - Examples of valid patterns and formats
50
50
  - Schema version and metadata information`,
51
- list_workflows: `Lists available workflows using WorkRail v2 (feature-flagged). Returns workflow metadata plus pinned snapshot hashes for deterministic execution.
51
+ list_workflows: `Lists available workflows. When a workflow exists for the user's request, following it means following the user's structured instructions.
52
52
 
53
- Use this to discover workflows before attempting multi-step tasks. When a workflow exists for the user's request, following it means following the user's structured instructions.
53
+ Discovery pattern (token-efficient do this):
54
+ - Preferred: read the workrail://tags MCP resource — tag definitions with no tool call.
55
+ - Or: call with no tags → returns tagSummary (~500 tokens) with when-to-use phrases per tag.
56
+ - Then: call with tags=["<tagId>"] → returns the filtered workflow list for that tag.
57
+ - Shortcut: if a workflow ID in tagSummary examples[] already matches, call start_workflow directly — skip the second list call.
58
+ - Multiple tags: if more than one tag fits, pick the most specific one.
54
59
 
55
- Always pass workspacePath so project-scoped workflow variants are resolved against the correct workspace instead of the server's fallback directory. Shared MCP servers cannot infer this safely.`,
56
- inspect_workflow: `Inspect a workflow structure before starting it (WorkRail v2, feature-flagged).
60
+ Always pass workspacePath so project-scoped workflow variants are resolved against the correct workspace. Shared MCP servers cannot infer this safely.`,
61
+ inspect_workflow: `Inspect a workflow structure before starting it.
57
62
 
58
63
  Use this to understand what steps the workflow will guide you through. The workflow is a step-by-step plan the user (or workflow author) created for this type of task.
59
64
 
@@ -139,14 +144,17 @@ Returns validation result with specific errors and suggestions if invalid.`,
139
144
  get_workflow_schema: `Get the workflow JSON schema for creating or editing workflows.
140
145
 
141
146
  Returns the complete schema definition including required fields, valid patterns, and constraints. Use this as reference when authoring workflow JSON.`,
142
- list_workflows: `List available workflows via WorkRail v2 (feature-flagged).
147
+ list_workflows: `Lists available workflows. When a workflow exists for the user's request, you MUST use it instead of improvising your own approach.
143
148
 
144
- Workflows are the user's pre-defined instructions for complex tasks. When a workflow exists for the user's request, you MUST use it instead of improvising your own approach.
145
-
146
- Returns stable workflow metadata and pinned snapshot hashes (workflowHash) for deterministic execution.
149
+ Discovery pattern (token-efficient do this):
150
+ - Preferred: read the workrail://tags MCP resource — tag definitions with no tool call.
151
+ - Or: call with no tags returns tagSummary (~500 tokens) with when-to-use phrases per tag.
152
+ - Then: call with tags=["<tagId>"] → returns the filtered workflow list for that tag.
153
+ - Shortcut: if a workflow ID in tagSummary examples[] already matches, call start_workflow directly — skip the second list call.
154
+ - Multiple tags: if more than one tag fits, pick the most specific one.
147
155
 
148
156
  Pass workspacePath on every call so project-scoped workflow variants are resolved against the correct workspace. Shared MCP servers cannot infer this safely.`,
149
- inspect_workflow: `Inspect a workflow you are considering following (WorkRail v2, feature-flagged).
157
+ inspect_workflow: `Inspect a workflow you are considering following.
150
158
 
151
159
  Use this to understand the workflow's structure before starting. The workflow is the user's explicit plan - not suggestions, not guidelines, but direct instructions you will follow.
152
160
 
@@ -42,9 +42,11 @@ const http_listener_js_1 = require("./http-listener.js");
42
42
  const shutdown_hooks_js_1 = require("./shutdown-hooks.js");
43
43
  const crypto = __importStar(require("crypto"));
44
44
  const express_1 = __importDefault(require("express"));
45
+ const HTTP_PORT_SCAN_END = 3199;
45
46
  async function startHttpServer(port) {
46
47
  const { server, ctx } = await (0, server_js_1.composeServer)();
47
- const listener = (0, http_listener_js_1.createHttpListener)(port);
48
+ const scanEnd = Math.max(port, HTTP_PORT_SCAN_END);
49
+ const listener = await (0, http_listener_js_1.bindWithPortFallback)(port, scanEnd);
48
50
  const { StreamableHTTPServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/streamableHttp.js')));
49
51
  const transport = new StreamableHTTPServerTransport({
50
52
  sessionIdGenerator: () => crypto.randomUUID(),
@@ -54,7 +56,6 @@ async function startHttpServer(port) {
54
56
  listener.app.post('/mcp', (req, res) => transport.handleRequest(req, res, req.body));
55
57
  listener.app.get('/mcp', (req, res) => transport.handleRequest(req, res));
56
58
  listener.app.delete('/mcp', (req, res) => transport.handleRequest(req, res));
57
- await listener.start();
58
59
  await server.connect(transport);
59
60
  const boundPort = listener.getBoundPort();
60
61
  console.error('[Transport] WorkRail MCP Server running on HTTP');
@@ -7,3 +7,4 @@ export interface HttpListener {
7
7
  stop(): Promise<void>;
8
8
  }
9
9
  export declare function createHttpListener(requestedPort: number): HttpListener;
10
+ export declare function bindWithPortFallback(startPort: number, endPort: number): Promise<HttpListener>;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createHttpListener = createHttpListener;
7
+ exports.bindWithPortFallback = bindWithPortFallback;
7
8
  const express_1 = __importDefault(require("express"));
8
9
  const http_1 = require("http");
9
10
  function createHttpListener(requestedPort) {
@@ -62,3 +63,27 @@ function createHttpListener(requestedPort) {
62
63
  },
63
64
  };
64
65
  }
66
+ async function bindWithPortFallback(startPort, endPort) {
67
+ let lastError;
68
+ for (let port = startPort; port <= endPort; port++) {
69
+ const listener = createHttpListener(port);
70
+ try {
71
+ await listener.start();
72
+ if (port !== startPort) {
73
+ console.error(`[HttpListener] Port ${startPort} unavailable; bound to fallback port ${port}`);
74
+ }
75
+ return listener;
76
+ }
77
+ catch (err) {
78
+ const nodeErr = err;
79
+ if (nodeErr.code === 'EADDRINUSE' ||
80
+ (err instanceof Error && err.message.includes('already in use'))) {
81
+ lastError = err instanceof Error ? err : new Error(String(err));
82
+ continue;
83
+ }
84
+ throw err;
85
+ }
86
+ }
87
+ throw new Error(`[HttpListener] No available port in range ${startPort}-${endPort}. ` +
88
+ `Last error: ${lastError?.message}`);
89
+ }
@@ -2,4 +2,7 @@ export interface ShutdownHookOptions {
2
2
  readonly onBeforeTerminate: () => Promise<void>;
3
3
  }
4
4
  export declare function wireShutdownHooks(opts: ShutdownHookOptions): void;
5
- export declare function wireStdinShutdown(): void;
5
+ export interface StdinShutdownOptions {
6
+ readonly stdin?: NodeJS.ReadableStream;
7
+ }
8
+ export declare function wireStdinShutdown(opts?: StdinShutdownOptions): void;
@@ -29,9 +29,10 @@ function wireShutdownHooks(opts) {
29
29
  })();
30
30
  });
31
31
  }
32
- function wireStdinShutdown() {
32
+ function wireStdinShutdown(opts) {
33
33
  const shutdownEvents = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ShutdownEvents);
34
- process.stdin.on('end', () => {
34
+ const stdin = opts?.stdin ?? process.stdin;
35
+ stdin.once('end', () => {
35
36
  console.error('[MCP] stdin closed, initiating shutdown');
36
37
  shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGHUP' });
37
38
  });
@@ -35,8 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.startStdioServer = startStdioServer;
37
37
  const server_js_1 = require("../server.js");
38
- const container_js_1 = require("../../di/container.js");
39
- const tokens_js_1 = require("../../di/tokens.js");
38
+ const shutdown_hooks_js_1 = require("./shutdown-hooks.js");
40
39
  const INITIAL_ROOTS_TIMEOUT_MS = 1000;
41
40
  async function fetchInitialRootsWithTimeout(server) {
42
41
  return Promise.race([
@@ -75,31 +74,10 @@ async function startStdioServer() {
75
74
  .catch(() => {
76
75
  console.error('[Roots] Client does not support roots/list; workspace context will use server CWD fallback');
77
76
  });
78
- const shutdownEvents = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ShutdownEvents);
79
- const processSignals = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ProcessSignals);
80
- const terminator = container_js_1.container.resolve(tokens_js_1.DI.Runtime.ProcessTerminator);
81
- processSignals.on('SIGINT', () => shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGINT' }));
82
- processSignals.on('SIGTERM', () => shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGTERM' }));
83
- processSignals.on('SIGHUP', () => shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGHUP' }));
84
- process.stdin.on('end', () => {
85
- console.error('[MCP] stdin closed, initiating shutdown');
86
- shutdownEvents.emit({ kind: 'shutdown_requested', signal: 'SIGHUP' });
87
- });
88
- let shutdownStarted = false;
89
- shutdownEvents.onShutdown((event) => {
90
- if (shutdownStarted)
91
- return;
92
- shutdownStarted = true;
93
- void (async () => {
94
- try {
95
- console.error(`[Shutdown] Requested by ${event.signal}. Stopping services...`);
96
- await ctx.httpServer?.stop();
97
- terminator.terminate({ kind: 'success' });
98
- }
99
- catch (err) {
100
- console.error('[Shutdown] Error while stopping services:', err);
101
- terminator.terminate({ kind: 'failure' });
102
- }
103
- })();
77
+ (0, shutdown_hooks_js_1.wireStdinShutdown)();
78
+ (0, shutdown_hooks_js_1.wireShutdownHooks)({
79
+ onBeforeTerminate: async () => {
80
+ await ctx.httpServer?.stop();
81
+ },
104
82
  });
105
83
  }
@@ -3,12 +3,15 @@ import type { ToolAnnotations } from '../tool-factory.js';
3
3
  export declare const V2ListWorkflowsInput: z.ZodObject<{
4
4
  workspacePath: z.ZodEffects<z.ZodString, string, string>;
5
5
  includeSources: z.ZodOptional<z.ZodBoolean>;
6
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
7
  }, "strip", z.ZodTypeAny, {
7
8
  workspacePath: string;
8
9
  includeSources?: boolean | undefined;
10
+ tags?: string[] | undefined;
9
11
  }, {
10
12
  workspacePath: string;
11
13
  includeSources?: boolean | undefined;
14
+ tags?: string[] | undefined;
12
15
  }>;
13
16
  export type V2ListWorkflowsInput = z.infer<typeof V2ListWorkflowsInput>;
14
17
  export declare const V2InspectWorkflowInput: z.ZodObject<{
@@ -28,12 +31,15 @@ export type V2InspectWorkflowInput = z.infer<typeof V2InspectWorkflowInput>;
28
31
  export declare const V2StartWorkflowInput: z.ZodObject<{
29
32
  workflowId: z.ZodString;
30
33
  workspacePath: z.ZodEffects<z.ZodString, string, string>;
34
+ goal: z.ZodString;
31
35
  }, "strip", z.ZodTypeAny, {
32
36
  workflowId: string;
33
37
  workspacePath: string;
38
+ goal: string;
34
39
  }, {
35
40
  workflowId: string;
36
41
  workspacePath: string;
42
+ goal: string;
37
43
  }>;
38
44
  export type V2StartWorkflowInput = z.infer<typeof V2StartWorkflowInput>;
39
45
  export declare const V2ContinueWorkflowInputShape: z.ZodObject<{
@@ -17,6 +17,7 @@ const optionalWorkspacePathField = workspacePathField.optional();
17
17
  exports.V2ListWorkflowsInput = zod_1.z.object({
18
18
  workspacePath: workspacePathField.describe('Required. Absolute path to your current workspace directory (e.g. the "Workspace:" value from your system parameters). WorkRail uses this to resolve project-scoped workflow variants against the correct workspace for discovery-sensitive workflow listing. Shared MCP servers cannot infer this safely.'),
19
19
  includeSources: zod_1.z.boolean().optional().describe('When true, includes a source catalog in the response showing where workflows come from (built-in, project-scoped, rooted-sharing, external), with effective and shadowed workflow counts per source. Omit or set false for the default workflow-list-only response.'),
20
+ tags: zod_1.z.array(zod_1.z.string()).optional().describe('Filter by one or more domain tags (e.g. ["coding"], ["review_audit", "investigation"]). When present, returns the full workflow list for workflows matching any of the specified tags. When absent, returns a compact tagSummary instead of the full list — use this first to discover which tags exist, then call again with a specific tag. Valid tags: coding, review_audit, investigation, design, documentation, tickets, learning, routines, authoring.'),
20
21
  });
21
22
  exports.V2InspectWorkflowInput = zod_1.z.object({
22
23
  workflowId: zod_1.z.string().min(1).regex(/^([a-z0-9_-]+|[a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)$/, 'Workflow ID must be a valid legacy ID (e.g. my-workflow) or namespaced ID (e.g. wr.discovery)').describe('The workflow ID to inspect'),
@@ -26,6 +27,7 @@ exports.V2InspectWorkflowInput = zod_1.z.object({
26
27
  exports.V2StartWorkflowInput = zod_1.z.object({
27
28
  workflowId: zod_1.z.string().min(1).regex(/^([a-z0-9_-]+|[a-z][a-z0-9_-]+\.[a-z][a-z0-9_-]+)$/, 'Workflow ID must be a valid legacy ID (e.g. my-workflow) or namespaced ID (e.g. wr.discovery)').describe('The workflow ID to start'),
28
29
  workspacePath: workspacePathField.describe('Required. Absolute path to your current workspace directory (e.g. the "Workspace:" value from your system parameters). WorkRail uses this to resolve the correct project-scoped workflow variant and to anchor the session to the correct repo for future resume_session discovery. Shared MCP servers cannot infer this safely.'),
30
+ goal: zod_1.z.string().min(1).describe('A short sentence describing what you are trying to accomplish (e.g. "implement OAuth refresh token rotation", "review PR #47 before merge", "investigate why the build fails on CI").'),
29
31
  });
30
32
  exports.V2ContinueWorkflowInputShape = zod_1.z.object({
31
33
  workspacePath: optionalWorkspacePathField,
@@ -4,6 +4,7 @@ exports.formatV2ExecutionResponse = formatV2ExecutionResponse;
4
4
  exports.formatV2ResumeResponse = formatV2ResumeResponse;
5
5
  const render_envelope_js_1 = require("./render-envelope.js");
6
6
  const response_supplements_js_1 = require("./response-supplements.js");
7
+ const env_flags_js_1 = require("../env-flags.js");
7
8
  function isV2ExecutionResponse(data) {
8
9
  if (typeof data !== 'object' || data === null)
9
10
  return false;
@@ -216,9 +217,6 @@ function formatSuccess(data) {
216
217
  }
217
218
  return lines.join('\n');
218
219
  }
219
- function isCleanResponseFormat() {
220
- return process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT === 'true';
221
- }
222
220
  const CLEAN_ADVANCE_FOOTERS = [
223
221
  'WorkRail: when done, call continue_workflow with your notes. Token:',
224
222
  'WorkRail: advance with continue_workflow when ready. Include your notes. Token:',
@@ -354,7 +352,7 @@ function formatV2ExecutionResponse(data) {
354
352
  const renderInput = deriveRenderInput(data);
355
353
  if (!renderInput)
356
354
  return null;
357
- const cleanFormat = isCleanResponseFormat();
355
+ const cleanFormat = env_flags_js_1.CLEAN_RESPONSE_FORMAT;
358
356
  const { response, lifecycle, contentEnvelope } = renderInput;
359
357
  const references = renderReferencesSection(contentEnvelope, lifecycle);
360
358
  if (cleanFormat) {
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ export declare function getCachedShape(schema: z.ZodObject<z.ZodRawShape>): z.ZodRawShape;
2
3
  export declare function extractExpectedKeys(schema: z.ZodType): readonly string[];
3
4
  export declare function extractRequiredKeys(schema: z.ZodType): readonly string[];
4
5
  export declare function findUnknownKeys(args: unknown, schema: z.ZodType): readonly string[];
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCachedShape = getCachedShape;
3
4
  exports.extractExpectedKeys = extractExpectedKeys;
4
5
  exports.extractRequiredKeys = extractRequiredKeys;
5
6
  exports.findUnknownKeys = findUnknownKeys;
@@ -8,9 +9,18 @@ exports.generateExampleValue = generateExampleValue;
8
9
  exports.generateTemplate = generateTemplate;
9
10
  exports.extractEnumValues = extractEnumValues;
10
11
  const zod_1 = require("zod");
12
+ const shapeCache = new WeakMap();
13
+ function getCachedShape(schema) {
14
+ const cached = shapeCache.get(schema);
15
+ if (cached !== undefined)
16
+ return cached;
17
+ const shape = schema._def.shape();
18
+ shapeCache.set(schema, shape);
19
+ return shape;
20
+ }
11
21
  function extractExpectedKeys(schema) {
12
22
  if (schema instanceof zod_1.z.ZodObject) {
13
- return Object.keys(schema._def.shape());
23
+ return Object.keys(getCachedShape(schema));
14
24
  }
15
25
  return [];
16
26
  }
@@ -18,7 +28,7 @@ function extractRequiredKeys(schema) {
18
28
  if (!(schema instanceof zod_1.z.ZodObject)) {
19
29
  return [];
20
30
  }
21
- const shape = schema._def.shape();
31
+ const shape = getCachedShape(schema);
22
32
  const required = [];
23
33
  for (const [key, value] of Object.entries(shape)) {
24
34
  const field = value;
@@ -55,7 +65,7 @@ function generateExampleValue(schema, depth = 0, maxDepth = 3, includeOptional =
55
65
  return generateExampleValue(schema._def.innerType, depth, maxDepth, includeOptional);
56
66
  }
57
67
  if (schema instanceof zod_1.z.ZodObject) {
58
- const shape = schema._def.shape();
68
+ const shape = getCachedShape(schema);
59
69
  const result = {};
60
70
  for (const [key, value] of Object.entries(shape)) {
61
71
  const field = value;
@@ -124,7 +134,7 @@ function extractEnumValues(schema, path) {
124
134
  let current = schema;
125
135
  for (const part of parts) {
126
136
  if (current instanceof zod_1.z.ZodObject) {
127
- const shape = current._def.shape();
137
+ const shape = getCachedShape(current);
128
138
  const field = shape[part];
129
139
  if (!field)
130
140
  return [];
@@ -133,7 +143,7 @@ function extractEnumValues(schema, path) {
133
143
  else if (current instanceof zod_1.z.ZodOptional) {
134
144
  current = current._def.innerType;
135
145
  if (current instanceof zod_1.z.ZodObject) {
136
- const shape = current._def.shape();
146
+ const shape = getCachedShape(current);
137
147
  const field = shape[part];
138
148
  if (!field)
139
149
  return [];
@@ -38,7 +38,7 @@ function generateMissingRequiredSuggestions(missingKeys, schema, config) {
38
38
  if (!(schema instanceof zod_1.z.ZodObject)) {
39
39
  return [];
40
40
  }
41
- const shape = schema._def.shape();
41
+ const shape = (0, schema_introspection_js_1.getCachedShape)(schema);
42
42
  const suggestions = [];
43
43
  for (const key of missingKeys) {
44
44
  const field = shape[key];
@@ -118,7 +118,7 @@ function patchTemplateForFailedOptionals(correctTemplate, args, zodErrors, schem
118
118
  if (args === null || typeof args !== 'object' || Array.isArray(args))
119
119
  return correctTemplate;
120
120
  const argsObj = args;
121
- const shape = schema._def.shape();
121
+ const shape = (0, schema_introspection_js_1.getCachedShape)(schema);
122
122
  const patched = { ...correctTemplate };
123
123
  let changed = false;
124
124
  for (const error of zodErrors) {
@@ -46,7 +46,7 @@ function findAliasFieldConflicts(value, aliasMap) {
46
46
  }
47
47
  exports.START_WORKFLOW_PROTOCOL = {
48
48
  canonicalParams: {
49
- required: ['workflowId', 'workspacePath'],
49
+ required: ['workflowId', 'workspacePath', 'goal'],
50
50
  optional: [],
51
51
  },
52
52
  descriptions: {
@@ -57,11 +57,13 @@ exports.START_WORKFLOW_PROTOCOL = {
57
57
  'Follow the returned step exactly; treat it as the user\'s current instruction.',
58
58
  'When the step is done, call continue_workflow with the returned continueToken.',
59
59
  'Always pass workspacePath. Shared MCP servers cannot safely infer which repo/workspace you mean.',
60
+ 'Always pass goal. A short sentence describing what you are trying to accomplish (e.g. "implement OAuth refresh token rotation").',
60
61
  'Only pass context on later continue_workflow calls if facts changed.',
61
62
  ],
62
63
  examplePayload: {
63
64
  workflowId: 'coding-task-workflow-agentic',
64
65
  workspacePath: '/Users/you/git/my-project',
66
+ goal: 'implement OAuth refresh token rotation',
65
67
  },
66
68
  returns: 'Step instructions plus continueToken and checkpointToken in the structured response.',
67
69
  },
@@ -72,10 +74,12 @@ exports.START_WORKFLOW_PROTOCOL = {
72
74
  'Execute the returned step exactly as written.',
73
75
  'When the step is complete, call continue_workflow with the returned continueToken.',
74
76
  'Pass workspacePath on every call. Shared MCP servers cannot safely infer the correct workspace.',
77
+ 'Pass goal on every call. A short sentence describing what you are trying to accomplish.',
75
78
  ],
76
79
  examplePayload: {
77
80
  workflowId: 'coding-task-workflow-agentic',
78
81
  workspacePath: '/Users/you/git/my-project',
82
+ goal: 'implement OAuth refresh token rotation',
79
83
  },
80
84
  returns: 'Step instructions plus continueToken and checkpointToken in the structured response.',
81
85
  },
@@ -1,4 +1,5 @@
1
1
  import type { ProcessSignal, ProcessSignals } from '../ports/process-signals.js';
2
2
  export declare class NodeProcessSignals implements ProcessSignals {
3
3
  on(signal: ProcessSignal, handler: () => void | Promise<void>): void;
4
+ once(signal: ProcessSignal, handler: () => void | Promise<void>): void;
4
5
  }
@@ -7,5 +7,10 @@ class NodeProcessSignals {
7
7
  void handler();
8
8
  });
9
9
  }
10
+ once(signal, handler) {
11
+ process.once(signal, () => {
12
+ void handler();
13
+ });
14
+ }
10
15
  }
11
16
  exports.NodeProcessSignals = NodeProcessSignals;
@@ -1,4 +1,5 @@
1
1
  import type { ProcessSignal, ProcessSignals } from '../ports/process-signals.js';
2
2
  export declare class NoopProcessSignals implements ProcessSignals {
3
3
  on(_signal: ProcessSignal, _handler: () => void | Promise<void>): void;
4
+ once(_signal: ProcessSignal, _handler: () => void | Promise<void>): void;
4
5
  }