@exaudeus/workrail 3.15.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 (139) hide show
  1. package/dist/application/services/workflow-service.d.ts +2 -0
  2. package/dist/application/services/workflow-service.js +3 -0
  3. package/dist/console/assets/index-BE5PAgPO.js +28 -0
  4. package/dist/console/assets/index-BZNM03t1.css +1 -0
  5. package/dist/console/index.html +2 -2
  6. package/dist/env-flags.d.ts +1 -0
  7. package/dist/env-flags.js +4 -0
  8. package/dist/infrastructure/session/HttpServer.d.ts +3 -3
  9. package/dist/infrastructure/session/HttpServer.js +68 -74
  10. package/dist/infrastructure/storage/caching-workflow-storage.d.ts +2 -0
  11. package/dist/infrastructure/storage/caching-workflow-storage.js +15 -6
  12. package/dist/infrastructure/storage/file-workflow-storage.js +3 -4
  13. package/dist/infrastructure/storage/schema-validating-workflow-storage.js +9 -8
  14. package/dist/manifest.json +257 -193
  15. package/dist/mcp/assert-output.d.ts +37 -0
  16. package/dist/mcp/assert-output.js +52 -0
  17. package/dist/mcp/boundary-coercion.d.ts +1 -0
  18. package/dist/mcp/boundary-coercion.js +44 -0
  19. package/dist/mcp/dev-mode.d.ts +1 -0
  20. package/dist/mcp/dev-mode.js +4 -0
  21. package/dist/mcp/handler-factory.js +12 -9
  22. package/dist/mcp/handlers/session.js +8 -9
  23. package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +2 -0
  24. package/dist/mcp/handlers/v2-advance-core/event-builders.js +6 -6
  25. package/dist/mcp/handlers/v2-advance-core/index.d.ts +2 -0
  26. package/dist/mcp/handlers/v2-advance-core/index.js +4 -3
  27. package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +2 -0
  28. package/dist/mcp/handlers/v2-advance-core/input-validation.js +32 -9
  29. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
  30. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +1 -1
  31. package/dist/mcp/handlers/v2-advance-core/outcome-success.d.ts +2 -0
  32. package/dist/mcp/handlers/v2-advance-core/outcome-success.js +1 -1
  33. package/dist/mcp/handlers/v2-checkpoint.d.ts +1 -1
  34. package/dist/mcp/handlers/v2-checkpoint.js +5 -6
  35. package/dist/mcp/handlers/v2-execution/advance.d.ts +4 -2
  36. package/dist/mcp/handlers/v2-execution/advance.js +5 -7
  37. package/dist/mcp/handlers/v2-execution/continue-advance.js +56 -26
  38. package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +1 -1
  39. package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +9 -9
  40. package/dist/mcp/handlers/v2-execution/replay.d.ts +6 -4
  41. package/dist/mcp/handlers/v2-execution/replay.js +47 -30
  42. package/dist/mcp/handlers/v2-execution/start.d.ts +2 -3
  43. package/dist/mcp/handlers/v2-execution/start.js +11 -11
  44. package/dist/mcp/handlers/v2-execution/workflow-object-cache.d.ts +5 -0
  45. package/dist/mcp/handlers/v2-execution/workflow-object-cache.js +19 -0
  46. package/dist/mcp/handlers/v2-execution-helpers.d.ts +1 -0
  47. package/dist/mcp/handlers/v2-execution-helpers.js +23 -7
  48. package/dist/mcp/handlers/v2-resume.d.ts +1 -1
  49. package/dist/mcp/handlers/v2-resume.js +3 -4
  50. package/dist/mcp/handlers/v2-state-conversion.js +5 -1
  51. package/dist/mcp/handlers/v2-workflow.d.ts +80 -0
  52. package/dist/mcp/handlers/v2-workflow.js +36 -21
  53. package/dist/mcp/handlers/workflow.d.ts +2 -5
  54. package/dist/mcp/handlers/workflow.js +15 -12
  55. package/dist/mcp/output-schemas.d.ts +20 -27
  56. package/dist/mcp/output-schemas.js +5 -7
  57. package/dist/mcp/server.js +22 -4
  58. package/dist/mcp/tool-call-timing.d.ts +24 -0
  59. package/dist/mcp/tool-call-timing.js +85 -0
  60. package/dist/mcp/transports/http-entry.js +3 -2
  61. package/dist/mcp/transports/http-listener.d.ts +1 -0
  62. package/dist/mcp/transports/http-listener.js +25 -0
  63. package/dist/mcp/transports/shutdown-hooks.d.ts +4 -1
  64. package/dist/mcp/transports/shutdown-hooks.js +3 -2
  65. package/dist/mcp/transports/stdio-entry.js +6 -28
  66. package/dist/mcp/v2-response-formatter.js +2 -4
  67. package/dist/mcp/validation/schema-introspection.d.ts +1 -0
  68. package/dist/mcp/validation/schema-introspection.js +15 -5
  69. package/dist/mcp/validation/suggestion-generator.js +2 -2
  70. package/dist/runtime/adapters/node-process-signals.d.ts +1 -0
  71. package/dist/runtime/adapters/node-process-signals.js +5 -0
  72. package/dist/runtime/adapters/noop-process-signals.d.ts +1 -0
  73. package/dist/runtime/adapters/noop-process-signals.js +2 -0
  74. package/dist/runtime/ports/process-signals.d.ts +1 -0
  75. package/dist/types/workflow-definition.d.ts +2 -0
  76. package/dist/types/workflow.d.ts +3 -0
  77. package/dist/types/workflow.js +35 -26
  78. package/dist/v2/durable-core/domain/context-template-resolver.js +2 -2
  79. package/dist/v2/durable-core/domain/function-definition-expander.js +2 -17
  80. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +1 -0
  81. package/dist/v2/durable-core/domain/prompt-renderer.js +23 -18
  82. package/dist/v2/durable-core/domain/recap-recovery.js +23 -16
  83. package/dist/v2/durable-core/domain/retrieval-contract.js +13 -7
  84. package/dist/v2/durable-core/session-index.d.ts +22 -0
  85. package/dist/v2/durable-core/session-index.js +58 -0
  86. package/dist/v2/durable-core/sorted-event-log.d.ts +6 -0
  87. package/dist/v2/durable-core/sorted-event-log.js +15 -0
  88. package/dist/v2/infra/local/fs/index.js +8 -8
  89. package/dist/v2/infra/local/session-store/index.d.ts +1 -1
  90. package/dist/v2/infra/local/session-store/index.js +71 -61
  91. package/dist/v2/infra/local/session-summary-provider/index.js +9 -4
  92. package/dist/v2/infra/local/snapshot-store/index.js +2 -1
  93. package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
  94. package/dist/v2/projections/assessment-consequences.d.ts +2 -1
  95. package/dist/v2/projections/assessment-consequences.js +0 -5
  96. package/dist/v2/projections/assessments.d.ts +2 -1
  97. package/dist/v2/projections/assessments.js +2 -4
  98. package/dist/v2/projections/gaps.d.ts +2 -1
  99. package/dist/v2/projections/gaps.js +0 -5
  100. package/dist/v2/projections/preferences.d.ts +2 -1
  101. package/dist/v2/projections/preferences.js +0 -5
  102. package/dist/v2/projections/run-context.d.ts +2 -2
  103. package/dist/v2/projections/run-context.js +0 -5
  104. package/dist/v2/projections/run-dag.js +7 -1
  105. package/dist/v2/projections/run-execution-trace.d.ts +8 -0
  106. package/dist/v2/projections/run-execution-trace.js +124 -0
  107. package/dist/v2/projections/run-status-signals.d.ts +2 -2
  108. package/dist/v2/usecases/console-routes.d.ts +3 -1
  109. package/dist/v2/usecases/console-routes.js +123 -25
  110. package/dist/v2/usecases/console-service.d.ts +1 -0
  111. package/dist/v2/usecases/console-service.js +83 -25
  112. package/dist/v2/usecases/console-types.d.ts +53 -0
  113. package/dist/v2/usecases/worktree-service.js +32 -1
  114. package/package.json +6 -5
  115. package/spec/workflow.schema.json +18 -0
  116. package/workflows/adaptive-ticket-creation.json +23 -16
  117. package/workflows/architecture-scalability-audit.json +29 -22
  118. package/workflows/bug-investigation.agentic.v2.json +7 -0
  119. package/workflows/coding-task-workflow-agentic.json +7 -0
  120. package/workflows/coding-task-workflow-agentic.lean.v2.json +16 -8
  121. package/workflows/coding-task-workflow-agentic.v2.json +7 -0
  122. package/workflows/cross-platform-code-conversion.v2.json +7 -0
  123. package/workflows/document-creation-workflow.json +15 -8
  124. package/workflows/documentation-update-workflow.json +15 -8
  125. package/workflows/intelligent-test-case-generation.json +7 -0
  126. package/workflows/learner-centered-course-workflow.json +9 -2
  127. package/workflows/mr-review-workflow.agentic.v2.json +7 -0
  128. package/workflows/personal-learning-materials-creation-branched.json +15 -8
  129. package/workflows/presentation-creation.json +12 -5
  130. package/workflows/production-readiness-audit.json +7 -0
  131. package/workflows/relocation-workflow-us.json +39 -32
  132. package/workflows/scoped-documentation-workflow.json +33 -26
  133. package/workflows/ui-ux-design-workflow.json +7 -0
  134. package/workflows/workflow-diagnose-environment.json +6 -0
  135. package/workflows/workflow-for-workflows.json +7 -0
  136. package/workflows/workflow-for-workflows.v2.json +23 -11
  137. package/workflows/wr.discovery.json +8 -1
  138. package/dist/console/assets/index-BZYIjrzJ.js +0 -28
  139. package/dist/console/assets/index-OLCKbDdm.css +0 -1
@@ -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
+ }
@@ -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
  }
@@ -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) {
@@ -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
  }
@@ -4,5 +4,7 @@ exports.NoopProcessSignals = void 0;
4
4
  class NoopProcessSignals {
5
5
  on(_signal, _handler) {
6
6
  }
7
+ once(_signal, _handler) {
8
+ }
7
9
  }
8
10
  exports.NoopProcessSignals = NoopProcessSignals;
@@ -1,4 +1,5 @@
1
1
  export type ProcessSignal = NodeJS.Signals | 'exit';
2
2
  export interface ProcessSignals {
3
3
  on(signal: ProcessSignal, handler: () => void | Promise<void>): void;
4
+ once(signal: ProcessSignal, handler: () => void | Promise<void>): void;
4
5
  }
@@ -134,6 +134,8 @@ export interface WorkflowDefinition {
134
134
  readonly extensionPoints?: readonly ExtensionPoint[];
135
135
  readonly references?: readonly WorkflowReference[];
136
136
  readonly validatedAgainstSpecVersion?: number;
137
+ readonly about?: string;
138
+ readonly examples?: readonly string[];
137
139
  }
138
140
  export declare function isLoopStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is LoopStepDefinition;
139
141
  export declare function isWorkflowStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is WorkflowStepDefinition;
@@ -3,6 +3,9 @@ import { WorkflowSource } from './workflow-source';
3
3
  export interface Workflow {
4
4
  readonly definition: WorkflowDefinition;
5
5
  readonly source: WorkflowSource;
6
+ readonly stepById: ReadonlyMap<string, WorkflowStepDefinition | LoopStepDefinition>;
7
+ readonly parentLoopByStepId: ReadonlyMap<string, LoopStepDefinition>;
8
+ readonly loopById: ReadonlyMap<string, LoopStepDefinition>;
6
9
  }
7
10
  export interface WorkflowSummary {
8
11
  readonly id: string;
@@ -9,10 +9,35 @@ exports.getAllStepIds = getAllStepIds;
9
9
  exports.isWorkflow = isWorkflow;
10
10
  exports.isWorkflowDefinition = isWorkflowDefinition;
11
11
  const workflow_source_1 = require("./workflow-source");
12
+ function buildWorkflowIndices(definition) {
13
+ const stepById = new Map();
14
+ const parentLoopByStepId = new Map();
15
+ const loopById = new Map();
16
+ function indexSteps(steps, parentLoop) {
17
+ for (const step of steps) {
18
+ stepById.set(step.id, step);
19
+ if (parentLoop !== null) {
20
+ parentLoopByStepId.set(step.id, parentLoop);
21
+ }
22
+ if ('type' in step && step.type === 'loop') {
23
+ loopById.set(step.id, step);
24
+ if (Array.isArray(step.body)) {
25
+ indexSteps(step.body, step);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ indexSteps(definition.steps ?? [], null);
31
+ return { stepById, parentLoopByStepId, loopById };
32
+ }
12
33
  function createWorkflow(definition, source) {
34
+ const { stepById, parentLoopByStepId, loopById } = buildWorkflowIndices(definition);
13
35
  return Object.freeze({
14
36
  definition,
15
- source
37
+ source,
38
+ stepById,
39
+ parentLoopByStepId,
40
+ loopById,
16
41
  });
17
42
  }
18
43
  function toWorkflowSummary(workflow) {
@@ -31,32 +56,10 @@ function toWorkflowSourceInfo(source) {
31
56
  });
32
57
  }
33
58
  function getStepById(workflow, stepId) {
34
- const steps = workflow.definition.steps;
35
- for (const step of steps) {
36
- if (step.id === stepId) {
37
- return step;
38
- }
39
- if ('type' in step && step.type === 'loop' && Array.isArray(step.body)) {
40
- for (const bodyStep of step.body) {
41
- if (bodyStep.id === stepId) {
42
- return bodyStep;
43
- }
44
- }
45
- }
46
- }
47
- return null;
59
+ return workflow.stepById.get(stepId) ?? null;
48
60
  }
49
61
  function getAllStepIds(workflow) {
50
- const ids = [];
51
- for (const step of workflow.definition.steps) {
52
- ids.push(step.id);
53
- if ('type' in step && step.type === 'loop' && Array.isArray(step.body)) {
54
- for (const bodyStep of step.body) {
55
- ids.push(bodyStep.id);
56
- }
57
- }
58
- }
59
- return Object.freeze(ids);
62
+ return Object.freeze([...workflow.stepById.keys()]);
60
63
  }
61
64
  function isWorkflow(obj) {
62
65
  if (!obj || typeof obj !== 'object')
@@ -64,10 +67,16 @@ function isWorkflow(obj) {
64
67
  const candidate = obj;
65
68
  return ('definition' in candidate &&
66
69
  'source' in candidate &&
70
+ 'stepById' in candidate &&
71
+ 'parentLoopByStepId' in candidate &&
72
+ 'loopById' in candidate &&
67
73
  candidate['definition'] !== null &&
68
74
  typeof candidate['definition'] === 'object' &&
69
75
  candidate['source'] !== null &&
70
- typeof candidate['source'] === 'object');
76
+ typeof candidate['source'] === 'object' &&
77
+ candidate['stepById'] instanceof Map &&
78
+ candidate['parentLoopByStepId'] instanceof Map &&
79
+ candidate['loopById'] instanceof Map);
71
80
  }
72
81
  function isWorkflowDefinition(obj) {
73
82
  if (!obj || typeof obj !== 'object')
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CONTEXT_TOKEN_PATTERN = void 0;
4
4
  exports.resolveContextTemplates = resolveContextTemplates;
5
5
  exports.CONTEXT_TOKEN_PATTERN = /\{\{(?!wr\.)([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)\}\}/;
6
- const CONTEXT_TOKEN_RE_G = new RegExp(exports.CONTEXT_TOKEN_PATTERN.source, 'g');
7
6
  function resolveDotPath(base, path) {
8
7
  let current = base;
9
8
  for (const segment of path) {
@@ -16,7 +15,8 @@ function resolveDotPath(base, path) {
16
15
  function resolveContextTemplates(template, context) {
17
16
  if (!template.includes('{{'))
18
17
  return template;
19
- return template.replace(CONTEXT_TOKEN_RE_G, (_match, dotPath) => {
18
+ const re = new RegExp(exports.CONTEXT_TOKEN_PATTERN.source, 'g');
19
+ return template.replace(re, (_match, dotPath) => {
20
20
  const value = resolveDotPath(context, dotPath.split('.'));
21
21
  if (value === undefined || value === null) {
22
22
  return `[unset: ${dotPath}]`;
@@ -3,23 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.expandFunctionDefinitions = expandFunctionDefinitions;
4
4
  exports.formatFunctionDef = formatFunctionDef;
5
5
  const neverthrow_1 = require("neverthrow");
6
- const workflow_js_1 = require("../../../types/workflow.js");
7
6
  function findLoopById(workflow, loopId) {
8
- function searchSteps(steps) {
9
- for (const step of steps) {
10
- if (!(0, workflow_js_1.isLoopStepDefinition)(step))
11
- continue;
12
- if (step.id === loopId)
13
- return step;
14
- if (Array.isArray(step.body)) {
15
- const found = searchSteps(step.body);
16
- if (found)
17
- return found;
18
- }
19
- }
20
- return null;
21
- }
22
- return searchSteps(workflow.definition.steps);
7
+ return workflow.loopById.get(loopId) ?? null;
23
8
  }
24
9
  function getWorkflowScopeDefs(workflow) {
25
10
  return workflow.definition.functionDefinitions?.filter(f => !f.scope || f.scope === 'workflow') ?? [];
@@ -31,7 +16,7 @@ function getLoopScopeDefs(args) {
31
16
  });
32
17
  }
33
18
  function getStepScopeDefs(args) {
34
- const step = args.workflow.definition.steps.find(s => s.id === args.stepId);
19
+ const step = args.workflow.stepById.get(args.stepId);
35
20
  return step?.functionDefinitions?.filter(f => !f.scope || f.scope === 'step') ?? [];
36
21
  }
37
22
  function expandFunctionDefinitions(args) {
@@ -24,4 +24,5 @@ export declare function renderPendingPrompt(args: {
24
24
  readonly runId: RunId;
25
25
  readonly nodeId: NodeId;
26
26
  readonly rehydrateOnly: boolean;
27
+ readonly precomputedIndex?: import('../session-index.js').SessionIndex;
27
28
  }): Result<StepMetadata, PromptRenderError>;
@@ -13,9 +13,11 @@ const constants_js_1 = require("../constants.js");
13
13
  const validation_requirements_extractor_js_1 = require("./validation-requirements-extractor.js");
14
14
  const index_js_2 = require("../schemas/artifacts/index.js");
15
15
  const run_context_js_1 = require("../../projections/run-context.js");
16
+ const sorted_event_log_js_1 = require("../sorted-event-log.js");
16
17
  const condition_evaluator_js_1 = require("../../../utils/condition-evaluator.js");
17
18
  const context_template_resolver_js_1 = require("./context-template-resolver.js");
18
19
  const retrieval_contract_js_1 = require("./retrieval-contract.js");
20
+ const env_flags_js_1 = require("../../../env-flags.js");
19
21
  function buildNonTipSegments(args) {
20
22
  const segments = [];
21
23
  const childSummary = (0, recap_recovery_js_1.buildChildSummary)({ nodeId: args.nodeId, dag: args.run });
@@ -86,15 +88,7 @@ function buildRecoverySegments(args) {
86
88
  ];
87
89
  }
88
90
  function resolveParentLoopStep(workflow, stepId) {
89
- for (const step of workflow.definition.steps) {
90
- if ((0, workflow_js_1.isLoopStepDefinition)(step) && Array.isArray(step.body)) {
91
- for (const bodyStep of step.body) {
92
- if (bodyStep.id === stepId)
93
- return step;
94
- }
95
- }
96
- }
97
- return undefined;
91
+ return workflow.parentLoopByStepId.get(stepId);
98
92
  }
99
93
  function buildLoopRenderContext(loopStep, iteration, sessionContext) {
100
94
  const iterationVar = loopStep.loop.iterationVar || 'currentIteration';
@@ -238,11 +232,13 @@ function renderPendingPrompt(args) {
238
232
  const isExitStep = outputContract?.contractRef === index_js_2.LOOP_CONTROL_CONTRACT_REF;
239
233
  const loopStep = resolveParentLoopStep(args.workflow, args.stepId);
240
234
  const maxIterations = loopStep?.loop.maxIterations;
241
- const sessionContext = (0, run_context_js_1.projectRunContextV2)(args.truth.events).match((ok) => (ok.byRunId[String(args.runId)]?.context ?? {}), (e) => {
242
- console.warn(`[prompt-renderer] Context projection failed for step '${args.stepId}' — ` +
243
- `{{varName}} tokens will render as [unset:...]: ${e.message}`);
244
- return {};
245
- });
235
+ const sessionContext = args.precomputedIndex
236
+ ? (args.precomputedIndex.runContextByRunId.get(String(args.runId)) ?? {})
237
+ : (0, sorted_event_log_js_1.asSortedEventLog)(args.truth.events).andThen((sorted) => (0, run_context_js_1.projectRunContextV2)(sorted)).match((ok) => (ok.byRunId[String(args.runId)]?.context ?? {}), (e) => {
238
+ console.warn(`[prompt-renderer] Context projection failed for step '${args.stepId}' — ` +
239
+ `{{varName}} tokens will render as [unset:...]: ${e.message}`);
240
+ return {};
241
+ });
246
242
  const loopIterationFrame = args.loopPath.at(-1);
247
243
  const loopRenderContext = loopStep && loopIterationFrame
248
244
  ? buildLoopRenderContext(loopStep, loopIterationFrame.iteration, sessionContext)
@@ -250,7 +246,7 @@ function renderPendingPrompt(args) {
250
246
  const renderContext = { ...sessionContext, ...loopRenderContext };
251
247
  const basePrompt = (0, context_template_resolver_js_1.resolveContextTemplates)(step.prompt ?? '', renderContext);
252
248
  const baseTitle = (0, context_template_resolver_js_1.resolveContextTemplates)(step.title, renderContext);
253
- const cleanResponseFormat = process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT === 'true';
249
+ const cleanResponseFormat = env_flags_js_1.CLEAN_RESPONSE_FORMAT;
254
250
  const loopBanner = buildLoopContextBanner({ loopPath: args.loopPath, isExitStep, maxIterations, cleanFormat: cleanResponseFormat });
255
251
  const validationCriteria = step.validationCriteria;
256
252
  const requirements = (0, validation_requirements_extractor_js_1.extractValidationRequirements)(validationCriteria);
@@ -279,7 +275,9 @@ function renderPendingPrompt(args) {
279
275
  if (cleanResponseFormat) {
280
276
  return '';
281
277
  }
282
- const hasPriorNotes = hasPriorNotesInRun({ truth: args.truth, runId: args.runId });
278
+ const hasPriorNotes = args.precomputedIndex
279
+ ? args.precomputedIndex.hasPriorNotesByRunId.has(String(args.runId))
280
+ : hasPriorNotesInRun({ truth: args.truth, runId: args.runId });
283
281
  if (hasPriorNotes && !args.rehydrateOnly) {
284
282
  return '\n\n**NOTES REQUIRED (System):** Include `output.notesMarkdown` when advancing.\n\n' +
285
283
  'Scope: this step only — WorkRail concatenates notes automatically.\n' +
@@ -321,8 +319,15 @@ function renderPendingPrompt(args) {
321
319
  const fragmentSuffix = promptFragments && promptFragments.length > 0
322
320
  ? assembleFragmentedPrompt(promptFragments, renderContext)
323
321
  : '';
324
- const enhancedPrompt = loopBanner + basePrompt + requirementsSection + contractSection + assessmentSection + notesSection
325
- + (fragmentSuffix ? '\n\n' + fragmentSuffix : '');
322
+ const enhancedPrompt = [
323
+ loopBanner,
324
+ basePrompt,
325
+ requirementsSection,
326
+ contractSection,
327
+ assessmentSection,
328
+ notesSection,
329
+ fragmentSuffix ? '\n\n' + fragmentSuffix : '',
330
+ ].join('');
326
331
  if (!args.rehydrateOnly) {
327
332
  return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, agentRole, requireConfirmation });
328
333
  }