@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
@@ -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
  }
@@ -23,8 +23,7 @@ export interface AssessmentDefinition {
23
23
  readonly dimensions: readonly AssessmentDimensionDefinition[];
24
24
  }
25
25
  export interface AssessmentConsequenceTriggerDefinition {
26
- readonly dimensionId: string;
27
- readonly equalsLevel: string;
26
+ readonly anyEqualsLevel: string;
28
27
  }
29
28
  export interface AssessmentFollowupRequiredEffectDefinition {
30
29
  readonly kind: 'require_followup';
@@ -135,6 +134,8 @@ export interface WorkflowDefinition {
135
134
  readonly extensionPoints?: readonly ExtensionPoint[];
136
135
  readonly references?: readonly WorkflowReference[];
137
136
  readonly validatedAgainstSpecVersion?: number;
137
+ readonly about?: string;
138
+ readonly examples?: readonly string[];
138
139
  }
139
140
  export declare function isLoopStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is LoopStepDefinition;
140
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
  }
@@ -6,18 +6,21 @@ exports.buildChildSummary = buildChildSummary;
6
6
  const neverthrow_1 = require("neverthrow");
7
7
  const constants_js_1 = require("../constants.js");
8
8
  function collectAncestryRecap(args) {
9
- const buildChain = (cur, visited) => {
10
- if (!cur || visited.has(cur))
11
- return [];
12
- const nodeData = args.dag.nodesById[cur];
13
- const parent = nodeData?.parentNodeId ?? null;
14
- const newVisited = new Set([...visited, cur]);
15
- return [cur, ...buildChain(parent, newVisited)];
9
+ const buildChain = (start) => {
10
+ const result = [];
11
+ const visited = new Set();
12
+ let cur = start;
13
+ while (cur !== null && !visited.has(cur)) {
14
+ visited.add(cur);
15
+ result.push(cur);
16
+ cur = args.dag.nodesById[cur]?.parentNodeId ?? null;
17
+ }
18
+ return result;
16
19
  };
17
20
  const startNode = args.includeCurrentNode
18
21
  ? String(args.nodeId)
19
22
  : args.dag.nodesById[String(args.nodeId)]?.parentNodeId ?? null;
20
- const chain = buildChain(startNode, new Set());
23
+ const chain = buildChain(startNode);
21
24
  const recaps = chain.flatMap((nodeId) => {
22
25
  const nodeOutputs = args.outputs.nodesById[nodeId];
23
26
  if (!nodeOutputs)
@@ -35,15 +38,19 @@ function collectAncestryRecap(args) {
35
38
  return (0, neverthrow_1.ok)([...recaps].reverse());
36
39
  }
37
40
  function collectDownstreamRecap(args) {
38
- const buildPathBackward = (cur, visited) => {
39
- if (!cur || cur === String(args.fromNodeId) || visited.has(cur))
40
- return [];
41
- const nodeData = args.dag.nodesById[cur];
42
- const parent = nodeData?.parentNodeId ?? null;
43
- const newVisited = new Set([...visited, cur]);
44
- return [cur, ...buildPathBackward(parent, newVisited)];
41
+ const buildPathBackward = (start) => {
42
+ const result = [];
43
+ const visited = new Set();
44
+ const fromId = String(args.fromNodeId);
45
+ let cur = start;
46
+ while (cur !== null && cur !== fromId && !visited.has(cur)) {
47
+ visited.add(cur);
48
+ result.push(cur);
49
+ cur = args.dag.nodesById[cur]?.parentNodeId ?? null;
50
+ }
51
+ return result;
45
52
  };
46
- const pathBackward = buildPathBackward(String(args.toNodeId), new Set());
53
+ const pathBackward = buildPathBackward(String(args.toNodeId));
47
54
  const recaps = [...pathBackward].reverse().flatMap((nodeId) => {
48
55
  const nodeOutputs = args.outputs.nodesById[nodeId];
49
56
  if (!nodeOutputs)
@@ -63,17 +63,21 @@ exports.RESUME_PREVIEW_CONTRACT = {
63
63
  };
64
64
  const encoder = new TextEncoder();
65
65
  const decoder = new TextDecoder('utf-8');
66
+ const TIER_PRIORITY = Object.fromEntries(exports.REHYDRATE_RETRIEVAL_CONTRACT.tiers.map((t) => [t.tier, t.priority]));
67
+ const TIER_RETENTION = Object.fromEntries(exports.REHYDRATE_RETRIEVAL_CONTRACT.tiers.map((t) => [t.tier, t.retention]));
68
+ const RESUME_PREVIEW_TIER_PRIORITY = Object.fromEntries(exports.RESUME_PREVIEW_CONTRACT.tiers.map((t) => [t.tier, t.priority]));
69
+ const RESUME_PREVIEW_TIER_MAX_BYTES = Object.fromEntries(exports.RESUME_PREVIEW_CONTRACT.tiers.map((t) => [t.tier, t.maxBytes]));
66
70
  function getTierPriority(tier) {
67
- return exports.REHYDRATE_RETRIEVAL_CONTRACT.tiers.find((candidate) => candidate.tier === tier)?.priority ?? Number.MAX_SAFE_INTEGER;
71
+ return TIER_PRIORITY[tier] ?? Number.MAX_SAFE_INTEGER;
68
72
  }
69
73
  function getTierRetention(tier) {
70
- return exports.REHYDRATE_RETRIEVAL_CONTRACT.tiers.find((candidate) => candidate.tier === tier)?.retention ?? 'tail';
74
+ return TIER_RETENTION[tier] ?? 'tail';
71
75
  }
72
76
  function getResumePreviewTierPriority(tier) {
73
- return exports.RESUME_PREVIEW_CONTRACT.tiers.find((candidate) => candidate.tier === tier)?.priority ?? Number.MAX_SAFE_INTEGER;
77
+ return RESUME_PREVIEW_TIER_PRIORITY[tier] ?? Number.MAX_SAFE_INTEGER;
74
78
  }
75
79
  function getResumePreviewTierMaxBytes(tier) {
76
- return exports.RESUME_PREVIEW_CONTRACT.tiers.find((candidate) => candidate.tier === tier)?.maxBytes ?? exports.RESUME_PREVIEW_CONTRACT.budgetBytes;
80
+ return RESUME_PREVIEW_TIER_MAX_BYTES[tier] ?? exports.RESUME_PREVIEW_CONTRACT.budgetBytes;
77
81
  }
78
82
  function compareAscii(a, b) {
79
83
  return a < b ? -1 : a > b ? 1 : 0;
@@ -283,7 +287,8 @@ function renderBudgetedRehydrateRecovery(args) {
283
287
  const initiallyIncludedTiers = tiersInOrder.filter((tier) => (sectionsByTier.get(tier) ?? []).length > 0);
284
288
  let includedTiers = initiallyIncludedTiers;
285
289
  let recoveryText = renderFromTiers(includedTiers);
286
- while (encoder.encode(recoveryText).length > constants_js_1.RECOVERY_BUDGET_BYTES) {
290
+ let recoveryBytes = encoder.encode(recoveryText).length;
291
+ while (recoveryBytes > constants_js_1.RECOVERY_BUDGET_BYTES) {
287
292
  const droppableTierIndex = [...includedTiers]
288
293
  .reverse()
289
294
  .findIndex((tier) => getTierRetention(tier) === 'tail');
@@ -293,9 +298,10 @@ function renderBudgetedRehydrateRecovery(args) {
293
298
  const actualIndex = includedTiers.length - 1 - droppableTierIndex;
294
299
  includedTiers = includedTiers.filter((_, index) => index !== actualIndex);
295
300
  recoveryText = renderFromTiers(includedTiers);
301
+ recoveryBytes = encoder.encode(recoveryText).length;
296
302
  }
297
303
  const omittedTierCount = initiallyIncludedTiers.length - includedTiers.length;
298
- const needsSuffix = omittedTierCount > 0 || encoder.encode(recoveryText).length > constants_js_1.RECOVERY_BUDGET_BYTES || includedTiers.length === 0;
304
+ const needsSuffix = omittedTierCount > 0 || recoveryBytes > constants_js_1.RECOVERY_BUDGET_BYTES || includedTiers.length === 0;
299
305
  const finalText = recoveryText.length === 0
300
306
  ? trimFinalRecoveryText(args.header, initiallyIncludedTiers.length)
301
307
  : !needsSuffix
@@ -305,6 +311,6 @@ function renderBudgetedRehydrateRecovery(args) {
305
311
  text: finalText,
306
312
  includedTiers,
307
313
  omittedTierCount,
308
- truncatedWithinTier: encoder.encode(recoveryText).length > constants_js_1.RECOVERY_BUDGET_BYTES || includedTiers.length === 0,
314
+ truncatedWithinTier: recoveryBytes > constants_js_1.RECOVERY_BUDGET_BYTES || includedTiers.length === 0,
309
315
  };
310
316
  }
@@ -0,0 +1,22 @@
1
+ import type { Brand } from '../../runtime/brand.js';
2
+ import type { SortedEventLog } from './sorted-event-log.js';
3
+ import type { DomainEventV1 } from './schemas/session/index.js';
4
+ import type { JsonObject } from './canonical/json-types.js';
5
+ export interface SessionIndexData {
6
+ readonly sortedEvents: SortedEventLog;
7
+ readonly runStartedByRunId: ReadonlyMap<string, Extract<DomainEventV1, {
8
+ kind: 'run_started';
9
+ }>>;
10
+ readonly nodeCreatedByNodeId: ReadonlyMap<string, Extract<DomainEventV1, {
11
+ kind: 'node_created';
12
+ }>>;
13
+ readonly hasChildEdgeByFromNodeId: ReadonlySet<string>;
14
+ readonly advanceRecordedByDedupeKey: ReadonlyMap<string, Extract<DomainEventV1, {
15
+ kind: 'advance_recorded';
16
+ }>>;
17
+ readonly nextEventIndex: number;
18
+ readonly hasPriorNotesByRunId: ReadonlySet<string>;
19
+ readonly runContextByRunId: ReadonlyMap<string, JsonObject>;
20
+ }
21
+ export type SessionIndex = Brand<SessionIndexData, 'v2.SessionIndex'>;
22
+ export declare function buildSessionIndex(events: SortedEventLog): SessionIndex;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSessionIndex = buildSessionIndex;
4
+ const constants_js_1 = require("./constants.js");
5
+ function buildSessionIndex(events) {
6
+ const runStartedByRunId = new Map();
7
+ const nodeCreatedByNodeId = new Map();
8
+ const hasChildEdgeByFromNodeId = new Set();
9
+ const advanceRecordedByDedupeKey = new Map();
10
+ const hasPriorNotesByRunId = new Set();
11
+ const runContextByRunId = new Map();
12
+ for (const event of events) {
13
+ switch (event.kind) {
14
+ case 'run_started':
15
+ runStartedByRunId.set(event.scope.runId, event);
16
+ break;
17
+ case 'node_created':
18
+ nodeCreatedByNodeId.set(event.scope.nodeId, event);
19
+ break;
20
+ case 'edge_created':
21
+ hasChildEdgeByFromNodeId.add(event.data.fromNodeId);
22
+ break;
23
+ case 'advance_recorded':
24
+ advanceRecordedByDedupeKey.set(event.dedupeKey, event);
25
+ break;
26
+ case 'node_output_appended': {
27
+ const outputEvt = event;
28
+ if (outputEvt.data.outputChannel === constants_js_1.OUTPUT_CHANNEL.RECAP &&
29
+ outputEvt.data.payload.payloadKind === constants_js_1.PAYLOAD_KIND.NOTES) {
30
+ hasPriorNotesByRunId.add(event.scope.runId);
31
+ }
32
+ break;
33
+ }
34
+ case 'context_set': {
35
+ const ctx = event.data.context;
36
+ if (ctx && typeof ctx === 'object' && !Array.isArray(ctx)) {
37
+ runContextByRunId.set(event.scope.runId, ctx);
38
+ }
39
+ break;
40
+ }
41
+ default:
42
+ break;
43
+ }
44
+ }
45
+ const lastEvent = events[events.length - 1];
46
+ const nextEventIndex = lastEvent !== undefined ? lastEvent.eventIndex + 1 : 0;
47
+ const data = {
48
+ sortedEvents: events,
49
+ runStartedByRunId,
50
+ nodeCreatedByNodeId,
51
+ hasChildEdgeByFromNodeId,
52
+ advanceRecordedByDedupeKey,
53
+ nextEventIndex,
54
+ hasPriorNotesByRunId,
55
+ runContextByRunId,
56
+ };
57
+ return data;
58
+ }
@@ -0,0 +1,6 @@
1
+ import type { Result } from 'neverthrow';
2
+ import type { Brand } from '../../runtime/brand.js';
3
+ import type { DomainEventV1 } from './schemas/session/index.js';
4
+ import type { ProjectionError } from '../projections/projection-error.js';
5
+ export type SortedEventLog = Brand<readonly DomainEventV1[], 'v2.SortedEventLog'>;
6
+ export declare function asSortedEventLog(events: readonly DomainEventV1[]): Result<SortedEventLog, ProjectionError>;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.asSortedEventLog = asSortedEventLog;
4
+ const neverthrow_1 = require("neverthrow");
5
+ function asSortedEventLog(events) {
6
+ for (let i = 1; i < events.length; i++) {
7
+ if (events[i].eventIndex <= events[i - 1].eventIndex) {
8
+ return (0, neverthrow_1.err)({
9
+ code: 'PROJECTION_INVARIANT_VIOLATION',
10
+ message: 'Events must be sorted by eventIndex strictly ascending (no duplicates)',
11
+ });
12
+ }
13
+ }
14
+ return (0, neverthrow_1.ok)(events);
15
+ }
@@ -166,18 +166,18 @@ class NodeFileSystemV2 {
166
166
  readdirWithMtime(dirPath) {
167
167
  return neverthrow_1.ResultAsync.fromPromise((async () => {
168
168
  const entries = await fs.readdir(dirPath);
169
+ const results = await Promise.allSettled(entries.map((name) => fs.stat(path.join(dirPath, name)).then((s) => ({ name, mtimeMs: s.mtimeMs }))));
169
170
  const withMtime = [];
170
171
  let skipped = 0;
171
- for (const name of entries) {
172
- try {
173
- const stats = await fs.stat(path.join(dirPath, name));
174
- withMtime.push({ name, mtimeMs: stats.mtimeMs });
172
+ for (let i = 0; i < results.length; i++) {
173
+ const result = results[i];
174
+ if (result.status === 'fulfilled') {
175
+ withMtime.push(result.value);
175
176
  }
176
- catch (e) {
177
- const code = nodeErrorCode(e);
177
+ else {
178
+ const code = nodeErrorCode(result.reason);
178
179
  skipped++;
179
- console.error(`[workrail:session-enum] Skipping ${name}: stat failed (${code ?? 'unknown'}: ${e instanceof Error ? e.message : String(e)})`);
180
- continue;
180
+ console.error(`[workrail:session-enum] Skipping ${entries[i]}: stat failed (${code ?? 'unknown'}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)})`);
181
181
  }
182
182
  }
183
183
  if (skipped > 0) {
@@ -10,7 +10,7 @@ export declare class LocalSessionEventLogStoreV2 implements SessionEventLogReado
10
10
  private readonly fs;
11
11
  private readonly sha256;
12
12
  constructor(dataDir: DataDirPortV2, fs: FileSystemPortV2, sha256: Sha256PortV2);
13
- append(lock: WithHealthySessionLock, plan: AppendPlanV2): ResultAsync<void, SessionEventLogStoreError>;
13
+ append(lock: WithHealthySessionLock, plan: AppendPlanV2, preloadedTruth?: LoadedSessionTruthV2): ResultAsync<void, SessionEventLogStoreError>;
14
14
  load(sessionId: SessionId): ResultAsync<LoadedSessionTruthV2, SessionEventLogStoreError>;
15
15
  loadValidatedPrefix(sessionId: SessionId): ResultAsync<LoadedValidatedPrefixV2, SessionEventLogStoreError>;
16
16
  private appendImpl;