@exaudeus/workrail 1.0.0 → 1.1.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 (47) hide show
  1. package/dist/application/services/output-normalizer.d.ts +9 -0
  2. package/dist/application/services/output-normalizer.js +38 -0
  3. package/dist/manifest.json +150 -30
  4. package/dist/mcp/handler-factory.d.ts +7 -0
  5. package/dist/mcp/handler-factory.js +70 -0
  6. package/dist/mcp/handlers/v2-execution.js +329 -65
  7. package/dist/mcp/output-schemas.d.ts +242 -18
  8. package/dist/mcp/output-schemas.js +83 -7
  9. package/dist/mcp/server.js +21 -127
  10. package/dist/mcp/tool-descriptions.js +126 -18
  11. package/dist/mcp/types/workflow-tool-edition.d.ts +28 -0
  12. package/dist/mcp/types/workflow-tool-edition.js +10 -0
  13. package/dist/mcp/v1/tool-registry.d.ts +8 -0
  14. package/dist/mcp/v1/tool-registry.js +49 -0
  15. package/dist/mcp/v2/tool-registry.d.ts +2 -5
  16. package/dist/mcp/v2/tool-registry.js +33 -32
  17. package/dist/mcp/v2/tools.js +6 -6
  18. package/dist/mcp/workflow-tool-edition-selector.d.ts +4 -0
  19. package/dist/mcp/workflow-tool-edition-selector.js +13 -0
  20. package/dist/v2/durable-core/constants.d.ts +1 -0
  21. package/dist/v2/durable-core/constants.js +2 -1
  22. package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +14 -7
  23. package/dist/v2/durable-core/domain/ack-advance-append-plan.js +78 -23
  24. package/dist/v2/durable-core/domain/blocking-decision.d.ts +32 -0
  25. package/dist/v2/durable-core/domain/blocking-decision.js +41 -0
  26. package/dist/v2/durable-core/domain/context-merge.d.ts +8 -0
  27. package/dist/v2/durable-core/domain/context-merge.js +40 -0
  28. package/dist/v2/durable-core/domain/function-definition-expander.d.ts +14 -0
  29. package/dist/v2/durable-core/domain/function-definition-expander.js +66 -0
  30. package/dist/v2/durable-core/domain/gap-builder.d.ts +19 -0
  31. package/dist/v2/durable-core/domain/gap-builder.js +24 -0
  32. package/dist/v2/durable-core/domain/prompt-renderer.d.ts +24 -0
  33. package/dist/v2/durable-core/domain/prompt-renderer.js +167 -0
  34. package/dist/v2/durable-core/domain/reason-model.d.ts +94 -0
  35. package/dist/v2/durable-core/domain/reason-model.js +228 -0
  36. package/dist/v2/durable-core/domain/recap-recovery.d.ts +24 -0
  37. package/dist/v2/durable-core/domain/recap-recovery.js +71 -0
  38. package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +8 -0
  39. package/dist/v2/durable-core/domain/validation-criteria-validator.js +16 -0
  40. package/dist/v2/durable-core/domain/validation-requirements-extractor.d.ts +2 -0
  41. package/dist/v2/durable-core/domain/validation-requirements-extractor.js +58 -0
  42. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +206 -0
  43. package/dist/v2/durable-core/schemas/session/events.d.ts +58 -0
  44. package/dist/v2/durable-core/schemas/session/events.js +9 -0
  45. package/dist/v2/projections/run-context.d.ts +22 -0
  46. package/dist/v2/projections/run-context.js +33 -0
  47. package/package.json +1 -1
@@ -2,6 +2,10 @@ import type { DomainEventV1 } from '../schemas/session/index.js';
2
2
  import { type OutputToAppend } from './outputs.js';
3
3
  import { type Result } from 'neverthrow';
4
4
  import type { SnapshotRef, WorkflowHash } from '../ids/index.js';
5
+ type AdvanceOutcomeV1 = Extract<DomainEventV1, {
6
+ kind: 'advance_recorded';
7
+ }>['data']['outcome'];
8
+ type EventToAppendV1 = Omit<DomainEventV1, 'eventIndex' | 'sessionId'>;
5
9
  export declare function buildAckAdvanceAppendPlanV1(args: {
6
10
  readonly sessionId: string;
7
11
  readonly runId: string;
@@ -9,16 +13,18 @@ export declare function buildAckAdvanceAppendPlanV1(args: {
9
13
  readonly workflowHash: WorkflowHash;
10
14
  readonly attemptId: string;
11
15
  readonly nextEventIndex: number;
12
- readonly toNodeId: string;
13
- readonly snapshotRef: SnapshotRef;
14
- readonly causeKind: 'intentional_fork' | 'non_tip_advance';
16
+ readonly outcome?: AdvanceOutcomeV1;
17
+ readonly extraEventsToAppend?: readonly EventToAppendV1[];
18
+ readonly toNodeId?: string;
19
+ readonly snapshotRef?: SnapshotRef;
20
+ readonly causeKind?: 'intentional_fork' | 'non_tip_advance';
15
21
  readonly minted: {
16
22
  readonly advanceRecordedEventId: string;
17
- readonly nodeCreatedEventId: string;
18
- readonly edgeCreatedEventId: string;
19
- readonly outputEventIds: readonly string[];
23
+ readonly nodeCreatedEventId?: string;
24
+ readonly edgeCreatedEventId?: string;
25
+ readonly outputEventIds?: readonly string[];
20
26
  };
21
- readonly outputsToAppend: readonly OutputToAppend[];
27
+ readonly outputsToAppend?: readonly OutputToAppend[];
22
28
  }): Result<{
23
29
  readonly events: readonly DomainEventV1[];
24
30
  readonly snapshotPins: readonly {
@@ -30,3 +36,4 @@ export declare function buildAckAdvanceAppendPlanV1(args: {
30
36
  readonly code: 'INVARIANT_VIOLATION';
31
37
  readonly message: string;
32
38
  }>;
39
+ export {};
@@ -4,27 +4,81 @@ exports.buildAckAdvanceAppendPlanV1 = buildAckAdvanceAppendPlanV1;
4
4
  const outputs_js_1 = require("./outputs.js");
5
5
  const neverthrow_1 = require("neverthrow");
6
6
  function buildAckAdvanceAppendPlanV1(args) {
7
- const { sessionId, runId, fromNodeId, workflowHash, attemptId, nextEventIndex, toNodeId, snapshotRef, causeKind, minted, outputsToAppend, } = args;
7
+ const { sessionId, runId, fromNodeId, workflowHash, attemptId, nextEventIndex, minted, outputsToAppend, extraEventsToAppend, } = args;
8
+ const outcome = args.outcome ??
9
+ (args.toNodeId
10
+ ? { kind: 'advanced', toNodeId: args.toNodeId }
11
+ :
12
+ (() => {
13
+ throw new Error('INVARIANT: buildAckAdvanceAppendPlanV1 requires toNodeId when outcome is omitted');
14
+ })());
8
15
  const advanceDedupeKey = `advance_recorded:${sessionId}:${fromNodeId}:${attemptId}`;
9
- const baseEvents = [
10
- {
11
- v: 1,
12
- eventId: minted.advanceRecordedEventId,
13
- eventIndex: nextEventIndex,
14
- sessionId,
15
- kind: 'advance_recorded',
16
- dedupeKey: advanceDedupeKey,
17
- scope: { runId, nodeId: fromNodeId },
18
- data: {
19
- attemptId,
20
- intent: 'ack_pending',
21
- outcome: { kind: 'advanced', toNodeId },
22
- },
16
+ const advanceRecorded = {
17
+ v: 1,
18
+ eventId: minted.advanceRecordedEventId,
19
+ eventIndex: nextEventIndex,
20
+ sessionId,
21
+ kind: 'advance_recorded',
22
+ dedupeKey: advanceDedupeKey,
23
+ scope: { runId, nodeId: fromNodeId },
24
+ data: {
25
+ attemptId,
26
+ intent: 'ack_pending',
27
+ outcome,
23
28
  },
29
+ };
30
+ const extra = [];
31
+ if (extraEventsToAppend && extraEventsToAppend.length > 0) {
32
+ for (let i = 0; i < extraEventsToAppend.length; i++) {
33
+ const raw = extraEventsToAppend[i];
34
+ if ('eventIndex' in raw) {
35
+ return (0, neverthrow_1.err)({ code: 'INVARIANT_VIOLATION', message: 'extraEventsToAppend must not include eventIndex (assigned by append plan builder)' });
36
+ }
37
+ if ('sessionId' in raw) {
38
+ return (0, neverthrow_1.err)({ code: 'INVARIANT_VIOLATION', message: 'extraEventsToAppend must not include sessionId (assigned by append plan builder)' });
39
+ }
40
+ extra.push({
41
+ ...extraEventsToAppend[i],
42
+ sessionId,
43
+ eventIndex: nextEventIndex + 1 + i,
44
+ });
45
+ }
46
+ }
47
+ const nextIndexAfterExtra = nextEventIndex + 1 + extra.length;
48
+ if (outcome.kind === 'blocked') {
49
+ if (outputsToAppend && outputsToAppend.length > 0) {
50
+ return (0, neverthrow_1.err)({
51
+ code: 'INVARIANT_VIOLATION',
52
+ message: 'blocked outcome cannot include outputsToAppend (no node advancement occurs)',
53
+ });
54
+ }
55
+ return (0, neverthrow_1.ok)({
56
+ events: [advanceRecorded, ...extra],
57
+ snapshotPins: [],
58
+ });
59
+ }
60
+ if (!args.toNodeId || !args.snapshotRef || !args.causeKind) {
61
+ return (0, neverthrow_1.err)({
62
+ code: 'INVARIANT_VIOLATION',
63
+ message: 'advanced outcome requires toNodeId + snapshotRef + causeKind',
64
+ });
65
+ }
66
+ if (!minted.nodeCreatedEventId || !minted.edgeCreatedEventId || !minted.outputEventIds) {
67
+ return (0, neverthrow_1.err)({
68
+ code: 'INVARIANT_VIOLATION',
69
+ message: 'advanced outcome requires minted.nodeCreatedEventId + minted.edgeCreatedEventId + minted.outputEventIds',
70
+ });
71
+ }
72
+ const toNodeId = args.toNodeId;
73
+ const snapshotRef = args.snapshotRef;
74
+ const causeKind = args.causeKind;
75
+ const nodeCreatedEventIndex = nextIndexAfterExtra;
76
+ const edgeCreatedEventIndex = nextIndexAfterExtra + 1;
77
+ const advancedEvents = [
24
78
  {
25
79
  v: 1,
26
80
  eventId: minted.nodeCreatedEventId,
27
- eventIndex: nextEventIndex + 1,
81
+ eventIndex: nodeCreatedEventIndex,
28
82
  sessionId,
29
83
  kind: 'node_created',
30
84
  dedupeKey: `node_created:${sessionId}:${runId}:${toNodeId}`,
@@ -39,7 +93,7 @@ function buildAckAdvanceAppendPlanV1(args) {
39
93
  {
40
94
  v: 1,
41
95
  eventId: minted.edgeCreatedEventId,
42
- eventIndex: nextEventIndex + 2,
96
+ eventIndex: edgeCreatedEventIndex,
43
97
  sessionId,
44
98
  kind: 'edge_created',
45
99
  dedupeKey: `edge_created:${sessionId}:${runId}:${fromNodeId}->${toNodeId}:acked_step`,
@@ -52,8 +106,9 @@ function buildAckAdvanceAppendPlanV1(args) {
52
106
  },
53
107
  },
54
108
  ];
55
- const normalizedOutputs = (0, outputs_js_1.normalizeOutputsForAppend)(outputsToAppend);
56
- if (minted.outputEventIds.length !== normalizedOutputs.length) {
109
+ const normalizedOutputs = (0, outputs_js_1.normalizeOutputsForAppend)(outputsToAppend ?? []);
110
+ const outputEventIds = minted.outputEventIds ?? [];
111
+ if (outputEventIds.length !== normalizedOutputs.length) {
57
112
  return (0, neverthrow_1.err)({
58
113
  code: 'INVARIANT_VIOLATION',
59
114
  message: 'outputEventIds length mismatch (caller must supply exactly one eventId per output event)',
@@ -62,8 +117,8 @@ function buildAckAdvanceAppendPlanV1(args) {
62
117
  const outputEvents = normalizedOutputs.map((o, idx) => {
63
118
  const base = {
64
119
  v: 1,
65
- eventId: minted.outputEventIds[idx],
66
- eventIndex: nextEventIndex + 3 + idx,
120
+ eventId: outputEventIds[idx],
121
+ eventIndex: nextIndexAfterExtra + 2 + idx,
67
122
  sessionId,
68
123
  kind: 'node_output_appended',
69
124
  dedupeKey: `node_output_appended:${sessionId}:${o.outputId}`,
@@ -81,13 +136,13 @@ function buildAckAdvanceAppendPlanV1(args) {
81
136
  }
82
137
  : base;
83
138
  });
84
- const events = [...baseEvents, ...outputEvents];
139
+ const events = [advanceRecorded, ...extra, ...advancedEvents, ...outputEvents];
85
140
  return (0, neverthrow_1.ok)({
86
141
  events,
87
142
  snapshotPins: [
88
143
  {
89
144
  snapshotRef,
90
- eventIndex: nextEventIndex + 1,
145
+ eventIndex: nodeCreatedEventIndex,
91
146
  createdByEventId: minted.nodeCreatedEventId,
92
147
  },
93
148
  ],
@@ -0,0 +1,32 @@
1
+ import { type Result } from 'neverthrow';
2
+ import type { ValidationResult } from '../../../types/validation.js';
3
+ import type { ReasonV1 } from './reason-model.js';
4
+ export type BlockingDecisionError = {
5
+ readonly code: 'INVALID_DELIMITER_SAFE_ID';
6
+ readonly message: string;
7
+ };
8
+ export type OutputRequirementStatus = {
9
+ readonly kind: 'not_required';
10
+ } | {
11
+ readonly kind: 'missing';
12
+ readonly contractRef: string;
13
+ } | {
14
+ readonly kind: 'invalid';
15
+ readonly contractRef: string;
16
+ readonly validation: ValidationResult;
17
+ };
18
+ export type CapabilityRequirementStatus = {
19
+ readonly kind: 'not_required';
20
+ } | {
21
+ readonly kind: 'unknown';
22
+ readonly capability: 'delegation' | 'web_browsing';
23
+ } | {
24
+ readonly kind: 'unavailable';
25
+ readonly capability: 'delegation' | 'web_browsing';
26
+ };
27
+ export declare function detectBlockingReasonsV1(args: {
28
+ readonly missingContextKeys?: readonly string[];
29
+ readonly contextBudgetExceeded?: boolean;
30
+ readonly outputRequirement?: OutputRequirementStatus;
31
+ readonly capabilityRequirement?: CapabilityRequirementStatus;
32
+ }): Result<readonly ReasonV1[], BlockingDecisionError>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectBlockingReasonsV1 = detectBlockingReasonsV1;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const constants_js_1 = require("../constants.js");
6
+ function detectBlockingReasonsV1(args) {
7
+ const reasons = [];
8
+ if (args.contextBudgetExceeded) {
9
+ reasons.push({ kind: 'context_budget_exceeded' });
10
+ }
11
+ if (args.missingContextKeys) {
12
+ for (const key of args.missingContextKeys) {
13
+ if (!constants_js_1.DELIMITER_SAFE_ID_PATTERN.test(key)) {
14
+ return (0, neverthrow_1.err)({
15
+ code: 'INVALID_DELIMITER_SAFE_ID',
16
+ message: `context key must be delimiter-safe: [a-z0-9_-]+ (got: ${key})`,
17
+ });
18
+ }
19
+ reasons.push({ kind: 'missing_context_key', key });
20
+ }
21
+ }
22
+ const outReq = args.outputRequirement;
23
+ if (outReq && outReq.kind !== 'not_required') {
24
+ if (outReq.kind === 'missing') {
25
+ reasons.push({ kind: 'missing_required_output', contractRef: outReq.contractRef });
26
+ }
27
+ if (outReq.kind === 'invalid') {
28
+ reasons.push({ kind: 'invalid_required_output', contractRef: outReq.contractRef });
29
+ }
30
+ }
31
+ const capReq = args.capabilityRequirement;
32
+ if (capReq && capReq.kind !== 'not_required') {
33
+ if (capReq.kind === 'unknown') {
34
+ reasons.push({ kind: 'required_capability_unknown', capability: capReq.capability });
35
+ }
36
+ if (capReq.kind === 'unavailable') {
37
+ reasons.push({ kind: 'required_capability_unavailable', capability: capReq.capability });
38
+ }
39
+ }
40
+ return (0, neverthrow_1.ok)(reasons);
41
+ }
@@ -0,0 +1,8 @@
1
+ import type { JsonObject } from '../canonical/json-types.js';
2
+ import type { Result } from 'neverthrow';
3
+ export type ContextMergeError = {
4
+ readonly code: 'RESERVED_KEY_REJECTED';
5
+ readonly message: string;
6
+ readonly key: string;
7
+ };
8
+ export declare function mergeContext(stored: JsonObject | undefined, delta: JsonObject | undefined): Result<JsonObject, ContextMergeError>;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeContext = mergeContext;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
6
+ function mergeContext(stored, delta) {
7
+ if (!delta)
8
+ return (0, neverthrow_1.ok)(stored ?? {});
9
+ if (!stored)
10
+ return stripNullAndValidate(delta);
11
+ for (const reservedKey of RESERVED_KEYS) {
12
+ if (reservedKey in delta && Object.prototype.hasOwnProperty.call(delta, reservedKey)) {
13
+ return (0, neverthrow_1.err)({
14
+ code: 'RESERVED_KEY_REJECTED',
15
+ message: `Context key '${reservedKey}' is reserved and cannot be used.`,
16
+ key: reservedKey,
17
+ });
18
+ }
19
+ }
20
+ const tombstones = new Set(Object.entries(delta)
21
+ .filter(([_, v]) => v === null)
22
+ .map(([k]) => k));
23
+ const overrides = Object.fromEntries(Object.entries(delta).filter(([_, v]) => v !== null && v !== undefined));
24
+ const mergedEntries = Object.entries(stored)
25
+ .filter(([k]) => !tombstones.has(k))
26
+ .concat(Object.entries(overrides));
27
+ return (0, neverthrow_1.ok)(Object.fromEntries(mergedEntries));
28
+ }
29
+ function stripNullAndValidate(obj) {
30
+ const reservedKey = Object.keys(obj).find(k => RESERVED_KEYS.has(k));
31
+ if (reservedKey) {
32
+ return (0, neverthrow_1.err)({
33
+ code: 'RESERVED_KEY_REJECTED',
34
+ message: `Context key '${reservedKey}' is reserved and cannot be used.`,
35
+ key: reservedKey,
36
+ });
37
+ }
38
+ const entries = Object.entries(obj).filter(([_, v]) => v !== null && v !== undefined);
39
+ return (0, neverthrow_1.ok)(Object.fromEntries(entries));
40
+ }
@@ -0,0 +1,14 @@
1
+ import type { Result } from 'neverthrow';
2
+ import type { Workflow, FunctionDefinition } from '../../../types/workflow.js';
3
+ import type { LoopPathFrameV1 } from '../schemas/execution-snapshot/index.js';
4
+ export type FunctionExpansionError = {
5
+ readonly code: 'FUNCTION_EXPANSION_FAILED';
6
+ readonly message: string;
7
+ };
8
+ export declare function expandFunctionDefinitions(args: {
9
+ readonly workflow: Workflow;
10
+ readonly stepId: string;
11
+ readonly loopPath: readonly LoopPathFrameV1[];
12
+ readonly functionReferences: readonly string[];
13
+ }): Result<readonly FunctionDefinition[], FunctionExpansionError>;
14
+ export declare function formatFunctionDef(def: FunctionDefinition): string;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expandFunctionDefinitions = expandFunctionDefinitions;
4
+ exports.formatFunctionDef = formatFunctionDef;
5
+ const neverthrow_1 = require("neverthrow");
6
+ const workflow_js_1 = require("../../../types/workflow.js");
7
+ 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);
23
+ }
24
+ function getWorkflowScopeDefs(workflow) {
25
+ return workflow.definition.functionDefinitions?.filter(f => !f.scope || f.scope === 'workflow') ?? [];
26
+ }
27
+ function getLoopScopeDefs(args) {
28
+ return args.loopPath.flatMap(frame => {
29
+ const loopStep = findLoopById(args.workflow, String(frame.loopId));
30
+ return loopStep?.functionDefinitions?.filter(f => !f.scope || f.scope === 'loop') ?? [];
31
+ });
32
+ }
33
+ function getStepScopeDefs(args) {
34
+ const step = args.workflow.definition.steps.find(s => s.id === args.stepId);
35
+ return step?.functionDefinitions?.filter(f => !f.scope || f.scope === 'step') ?? [];
36
+ }
37
+ function expandFunctionDefinitions(args) {
38
+ const allDefs = [
39
+ ...getWorkflowScopeDefs(args.workflow),
40
+ ...getLoopScopeDefs({ workflow: args.workflow, loopPath: args.loopPath }),
41
+ ...getStepScopeDefs({ workflow: args.workflow, stepId: args.stepId }),
42
+ ];
43
+ const deduped = new Map();
44
+ for (const def of allDefs) {
45
+ if (!deduped.has(def.name)) {
46
+ deduped.set(def.name, def);
47
+ }
48
+ }
49
+ const filtered = args.functionReferences.length > 0
50
+ ? Array.from(deduped.values()).filter(f => args.functionReferences.includes(f.name))
51
+ : Array.from(deduped.values());
52
+ const scopePriority = { step: 0, loop: 1, workflow: 2 };
53
+ const sorted = filtered.sort((a, b) => {
54
+ const aScope = a.scope ?? 'workflow';
55
+ const bScope = b.scope ?? 'workflow';
56
+ const aPri = scopePriority[aScope] ?? 2;
57
+ const bPri = scopePriority[bScope] ?? 2;
58
+ if (aPri !== bPri)
59
+ return aPri - bPri;
60
+ return a.name.localeCompare(b.name);
61
+ });
62
+ return (0, neverthrow_1.ok)(sorted);
63
+ }
64
+ function formatFunctionDef(def) {
65
+ return `function ${def.name}(...)\n ${def.definition}`;
66
+ }
@@ -0,0 +1,19 @@
1
+ import type { DomainEventV1 } from '../schemas/session/index.js';
2
+ import type { ReasonV1 } from './reason-model.js';
3
+ export type GapEvidenceRefV1 = {
4
+ readonly kind: 'event';
5
+ readonly eventId: string;
6
+ } | {
7
+ readonly kind: 'output';
8
+ readonly outputId: string;
9
+ };
10
+ export declare function buildGapRecordedEventV1(args: {
11
+ readonly eventId: string;
12
+ readonly eventIndex: number;
13
+ readonly sessionId: string;
14
+ readonly runId: string;
15
+ readonly nodeId: string;
16
+ readonly gapId: string;
17
+ readonly reason: ReasonV1;
18
+ readonly evidenceRefs?: readonly GapEvidenceRefV1[];
19
+ }): DomainEventV1;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildGapRecordedEventV1 = buildGapRecordedEventV1;
4
+ const reason_model_js_1 = require("./reason-model.js");
5
+ function buildGapRecordedEventV1(args) {
6
+ const { severity, reason, summary } = (0, reason_model_js_1.reasonToGap)(args.reason);
7
+ return {
8
+ v: 1,
9
+ eventId: args.eventId,
10
+ eventIndex: args.eventIndex,
11
+ sessionId: args.sessionId,
12
+ kind: 'gap_recorded',
13
+ dedupeKey: `gap_recorded:${args.sessionId}:${args.gapId}`,
14
+ scope: { runId: args.runId, nodeId: args.nodeId },
15
+ data: {
16
+ gapId: args.gapId,
17
+ severity,
18
+ reason,
19
+ summary,
20
+ resolution: { kind: 'unresolved' },
21
+ evidenceRefs: args.evidenceRefs,
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,24 @@
1
+ import type { Result } from 'neverthrow';
2
+ import type { Workflow } from '../../../types/workflow.js';
3
+ import type { LoadedSessionTruthV2 } from '../../ports/session-event-log-store.port.js';
4
+ import type { LoopPathFrameV1 } from '../schemas/execution-snapshot/index.js';
5
+ import type { NodeId, RunId } from '../ids/index.js';
6
+ export type PromptRenderError = {
7
+ readonly code: 'RENDER_FAILED';
8
+ readonly message: string;
9
+ };
10
+ export interface StepMetadata {
11
+ readonly stepId: string;
12
+ readonly title: string;
13
+ readonly prompt: string;
14
+ readonly requireConfirmation: boolean;
15
+ }
16
+ export declare function renderPendingPrompt(args: {
17
+ readonly workflow: Workflow;
18
+ readonly stepId: string;
19
+ readonly loopPath: readonly LoopPathFrameV1[];
20
+ readonly truth: LoadedSessionTruthV2;
21
+ readonly runId: RunId;
22
+ readonly nodeId: NodeId;
23
+ readonly rehydrateOnly: boolean;
24
+ }): Result<StepMetadata, PromptRenderError>;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderPendingPrompt = renderPendingPrompt;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const workflow_js_1 = require("../../../types/workflow.js");
6
+ const index_js_1 = require("../ids/index.js");
7
+ const run_dag_js_1 = require("../../projections/run-dag.js");
8
+ const node_outputs_js_1 = require("../../projections/node-outputs.js");
9
+ const recap_recovery_js_1 = require("./recap-recovery.js");
10
+ const function_definition_expander_js_1 = require("./function-definition-expander.js");
11
+ const constants_js_1 = require("../constants.js");
12
+ function buildNonTipSections(args) {
13
+ const sections = [];
14
+ const childSummary = (0, recap_recovery_js_1.buildChildSummary)({ nodeId: args.nodeId, dag: args.run });
15
+ if (childSummary) {
16
+ sections.push(`### Branch Summary\n${childSummary}`);
17
+ }
18
+ if (args.run.preferredTipNodeId && args.run.preferredTipNodeId !== String(args.nodeId)) {
19
+ const downstreamRes = (0, recap_recovery_js_1.collectDownstreamRecap)({
20
+ fromNodeId: args.nodeId,
21
+ toNodeId: (0, index_js_1.asNodeId)(args.run.preferredTipNodeId),
22
+ dag: args.run,
23
+ outputs: args.outputs,
24
+ });
25
+ if (downstreamRes.isOk() && downstreamRes.value.length > 0) {
26
+ sections.push(`### Downstream Recap (Preferred Branch)\n${downstreamRes.value.join('\n\n')}`);
27
+ }
28
+ }
29
+ return sections;
30
+ }
31
+ function buildAncestrySections(args) {
32
+ const ancestryRes = (0, recap_recovery_js_1.collectAncestryRecap)({
33
+ nodeId: args.nodeId,
34
+ dag: args.run,
35
+ outputs: args.outputs,
36
+ includeCurrentNode: false,
37
+ });
38
+ if (ancestryRes.isOk() && ancestryRes.value.length > 0) {
39
+ return [`### Ancestry Recap\n${ancestryRes.value.join('\n\n')}`];
40
+ }
41
+ return [];
42
+ }
43
+ function buildFunctionDefsSections(args) {
44
+ const funcsRes = (0, function_definition_expander_js_1.expandFunctionDefinitions)({
45
+ workflow: args.workflow,
46
+ stepId: args.stepId,
47
+ loopPath: args.loopPath,
48
+ functionReferences: args.functionReferences,
49
+ });
50
+ if (funcsRes.isOk() && funcsRes.value.length > 0) {
51
+ const formatted = funcsRes.value.map(function_definition_expander_js_1.formatFunctionDef).join('\n\n');
52
+ return [`### Function Definitions\n\`\`\`\n${formatted}\n\`\`\``];
53
+ }
54
+ return [];
55
+ }
56
+ function buildRecoverySections(args) {
57
+ const isTip = args.run.tipNodeIds.includes(String(args.nodeId));
58
+ return [
59
+ ...(isTip ? [] : buildNonTipSections({ nodeId: args.nodeId, run: args.run, outputs: args.outputs })),
60
+ ...buildAncestrySections({ nodeId: args.nodeId, run: args.run, outputs: args.outputs }),
61
+ ...buildFunctionDefsSections({
62
+ workflow: args.workflow,
63
+ stepId: args.stepId,
64
+ loopPath: args.loopPath,
65
+ functionReferences: args.functionReferences,
66
+ }),
67
+ ];
68
+ }
69
+ function trimToUtf8Boundary(bytes) {
70
+ const n = bytes.length;
71
+ if (n === 0)
72
+ return bytes;
73
+ let cont = 0;
74
+ for (let i = n - 1; i >= 0 && i >= n - 4; i--) {
75
+ const b = bytes[i];
76
+ if ((b & 192) === 128) {
77
+ cont++;
78
+ }
79
+ else {
80
+ break;
81
+ }
82
+ }
83
+ if (cont === 0)
84
+ return bytes;
85
+ const leadByteIndex = n - cont - 1;
86
+ if (leadByteIndex < 0) {
87
+ return new Uint8Array(0);
88
+ }
89
+ const leadByte = bytes[leadByteIndex];
90
+ const expectedLen = (leadByte & 128) === 0 ? 1 :
91
+ (leadByte & 224) === 192 ? 2 :
92
+ (leadByte & 240) === 224 ? 3 :
93
+ (leadByte & 248) === 240 ? 4 :
94
+ 0;
95
+ const actualLen = cont + 1;
96
+ if (expectedLen === 0 || expectedLen !== actualLen) {
97
+ return bytes.subarray(0, leadByteIndex);
98
+ }
99
+ return bytes;
100
+ }
101
+ function applyPromptBudget(combinedPrompt) {
102
+ const encoder = new TextEncoder();
103
+ const promptBytes = encoder.encode(combinedPrompt);
104
+ if (promptBytes.length <= constants_js_1.RECOVERY_BUDGET_BYTES) {
105
+ return combinedPrompt;
106
+ }
107
+ const markerText = constants_js_1.TRUNCATION_MARKER;
108
+ const omissionNote = `\nOmitted recovery content due to budget constraints.`;
109
+ const suffixBytes = encoder.encode(markerText + omissionNote);
110
+ const maxContentBytes = constants_js_1.RECOVERY_BUDGET_BYTES - suffixBytes.length;
111
+ const truncatedBytes = trimToUtf8Boundary(promptBytes.subarray(0, maxContentBytes));
112
+ const decoder = new TextDecoder('utf-8');
113
+ return decoder.decode(truncatedBytes) + markerText + omissionNote;
114
+ }
115
+ function loadRecoveryProjections(args) {
116
+ const dagRes = (0, run_dag_js_1.projectRunDagV2)(args.truth.events);
117
+ if (dagRes.isErr()) {
118
+ return (0, neverthrow_1.err)('(Recovery context unavailable due to projection failure)');
119
+ }
120
+ const dag = dagRes.value;
121
+ const run = dag.runsById[args.runId];
122
+ if (!run) {
123
+ return (0, neverthrow_1.err)('(Recovery context unavailable: run not found)');
124
+ }
125
+ const outputsRes = (0, node_outputs_js_1.projectNodeOutputsV2)(args.truth.events);
126
+ if (outputsRes.isErr()) {
127
+ return (0, neverthrow_1.err)('(Recovery context unavailable due to outputs projection failure)');
128
+ }
129
+ return (0, neverthrow_1.ok)({ run, outputs: outputsRes.value });
130
+ }
131
+ function renderPendingPrompt(args) {
132
+ const step = (0, workflow_js_1.getStepById)(args.workflow, args.stepId);
133
+ const baseTitle = step?.title ?? args.stepId;
134
+ const basePrompt = step?.prompt ?? `Pending step: ${args.stepId}`;
135
+ const requireConfirmation = Boolean(step?.requireConfirmation);
136
+ const functionReferences = step?.functionReferences ?? [];
137
+ if (!args.rehydrateOnly) {
138
+ return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: basePrompt, requireConfirmation });
139
+ }
140
+ const projectionsRes = loadRecoveryProjections({ truth: args.truth, runId: args.runId });
141
+ if (projectionsRes.isErr()) {
142
+ return (0, neverthrow_1.ok)({
143
+ stepId: args.stepId,
144
+ title: baseTitle,
145
+ prompt: basePrompt + '\n\n' + projectionsRes.error,
146
+ requireConfirmation,
147
+ });
148
+ }
149
+ const { run, outputs } = projectionsRes.value;
150
+ const sections = buildRecoverySections({
151
+ nodeId: args.nodeId,
152
+ dag: run,
153
+ run,
154
+ outputs,
155
+ workflow: args.workflow,
156
+ stepId: args.stepId,
157
+ loopPath: args.loopPath,
158
+ functionReferences,
159
+ });
160
+ if (sections.length === 0) {
161
+ return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: basePrompt, requireConfirmation });
162
+ }
163
+ const recoveryText = `## Recovery Context\n\n${sections.join('\n\n')}`;
164
+ const combinedPrompt = `${basePrompt}\n\n${recoveryText}`;
165
+ const finalPrompt = applyPromptBudget(combinedPrompt);
166
+ return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: finalPrompt, requireConfirmation });
167
+ }