@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.
- package/dist/application/services/output-normalizer.d.ts +9 -0
- package/dist/application/services/output-normalizer.js +38 -0
- package/dist/manifest.json +150 -30
- package/dist/mcp/handler-factory.d.ts +7 -0
- package/dist/mcp/handler-factory.js +70 -0
- package/dist/mcp/handlers/v2-execution.js +329 -65
- package/dist/mcp/output-schemas.d.ts +242 -18
- package/dist/mcp/output-schemas.js +83 -7
- package/dist/mcp/server.js +21 -127
- package/dist/mcp/tool-descriptions.js +126 -18
- package/dist/mcp/types/workflow-tool-edition.d.ts +28 -0
- package/dist/mcp/types/workflow-tool-edition.js +10 -0
- package/dist/mcp/v1/tool-registry.d.ts +8 -0
- package/dist/mcp/v1/tool-registry.js +49 -0
- package/dist/mcp/v2/tool-registry.d.ts +2 -5
- package/dist/mcp/v2/tool-registry.js +33 -32
- package/dist/mcp/v2/tools.js +6 -6
- package/dist/mcp/workflow-tool-edition-selector.d.ts +4 -0
- package/dist/mcp/workflow-tool-edition-selector.js +13 -0
- package/dist/v2/durable-core/constants.d.ts +1 -0
- package/dist/v2/durable-core/constants.js +2 -1
- package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +14 -7
- package/dist/v2/durable-core/domain/ack-advance-append-plan.js +78 -23
- package/dist/v2/durable-core/domain/blocking-decision.d.ts +32 -0
- package/dist/v2/durable-core/domain/blocking-decision.js +41 -0
- package/dist/v2/durable-core/domain/context-merge.d.ts +8 -0
- package/dist/v2/durable-core/domain/context-merge.js +40 -0
- package/dist/v2/durable-core/domain/function-definition-expander.d.ts +14 -0
- package/dist/v2/durable-core/domain/function-definition-expander.js +66 -0
- package/dist/v2/durable-core/domain/gap-builder.d.ts +19 -0
- package/dist/v2/durable-core/domain/gap-builder.js +24 -0
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +24 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +167 -0
- package/dist/v2/durable-core/domain/reason-model.d.ts +94 -0
- package/dist/v2/durable-core/domain/reason-model.js +228 -0
- package/dist/v2/durable-core/domain/recap-recovery.d.ts +24 -0
- package/dist/v2/durable-core/domain/recap-recovery.js +71 -0
- package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +8 -0
- package/dist/v2/durable-core/domain/validation-criteria-validator.js +16 -0
- package/dist/v2/durable-core/domain/validation-requirements-extractor.d.ts +2 -0
- package/dist/v2/durable-core/domain/validation-requirements-extractor.js +58 -0
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +206 -0
- package/dist/v2/durable-core/schemas/session/events.d.ts +58 -0
- package/dist/v2/durable-core/schemas/session/events.js +9 -0
- package/dist/v2/projections/run-context.d.ts +22 -0
- package/dist/v2/projections/run-context.js +33 -0
- package/package.json +1 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { AutonomyV2 } from '../schemas/session/preferences.js';
|
|
2
|
+
import { type Result } from 'neverthrow';
|
|
3
|
+
export type CapabilityV2 = 'delegation' | 'web_browsing';
|
|
4
|
+
export type GapSeverityV1 = 'info' | 'warning' | 'critical';
|
|
5
|
+
export type UserOnlyDependencyReasonV1 = 'needs_user_secret_or_token' | 'needs_user_account_access' | 'needs_user_artifact' | 'needs_user_choice' | 'needs_user_approval' | 'needs_user_environment_action';
|
|
6
|
+
export type GapReasonV1 = {
|
|
7
|
+
readonly category: 'user_only_dependency';
|
|
8
|
+
readonly detail: UserOnlyDependencyReasonV1;
|
|
9
|
+
} | {
|
|
10
|
+
readonly category: 'contract_violation';
|
|
11
|
+
readonly detail: 'missing_required_output' | 'invalid_required_output';
|
|
12
|
+
} | {
|
|
13
|
+
readonly category: 'capability_missing';
|
|
14
|
+
readonly detail: 'required_capability_unavailable' | 'required_capability_unknown';
|
|
15
|
+
} | {
|
|
16
|
+
readonly category: 'unexpected';
|
|
17
|
+
readonly detail: 'invariant_violation' | 'storage_corruption_detected';
|
|
18
|
+
};
|
|
19
|
+
export type BlockerCodeV1 = 'USER_ONLY_DEPENDENCY' | 'MISSING_REQUIRED_OUTPUT' | 'INVALID_REQUIRED_OUTPUT' | 'REQUIRED_CAPABILITY_UNKNOWN' | 'REQUIRED_CAPABILITY_UNAVAILABLE' | 'INVARIANT_VIOLATION' | 'STORAGE_CORRUPTION_DETECTED';
|
|
20
|
+
export type BlockerPointerV1 = {
|
|
21
|
+
readonly kind: 'context_key';
|
|
22
|
+
readonly key: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'context_budget';
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'output_contract';
|
|
27
|
+
readonly contractRef: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'capability';
|
|
30
|
+
readonly capability: CapabilityV2;
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: 'workflow_step';
|
|
33
|
+
readonly stepId: string;
|
|
34
|
+
};
|
|
35
|
+
export type BlockerV1 = {
|
|
36
|
+
readonly code: BlockerCodeV1;
|
|
37
|
+
readonly pointer: BlockerPointerV1;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
readonly suggestedFix?: string;
|
|
40
|
+
};
|
|
41
|
+
export type BlockerReportV1 = {
|
|
42
|
+
blockers: BlockerV1[];
|
|
43
|
+
};
|
|
44
|
+
export type ReasonV1 = {
|
|
45
|
+
readonly kind: 'missing_context_key';
|
|
46
|
+
readonly key: string;
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: 'context_budget_exceeded';
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: 'missing_required_output';
|
|
51
|
+
readonly contractRef: string;
|
|
52
|
+
} | {
|
|
53
|
+
readonly kind: 'invalid_required_output';
|
|
54
|
+
readonly contractRef: string;
|
|
55
|
+
} | {
|
|
56
|
+
readonly kind: 'required_capability_unknown';
|
|
57
|
+
readonly capability: CapabilityV2;
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: 'required_capability_unavailable';
|
|
60
|
+
readonly capability: CapabilityV2;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: 'user_only_dependency';
|
|
63
|
+
readonly detail: UserOnlyDependencyReasonV1;
|
|
64
|
+
readonly stepId: string;
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: 'invariant_violation';
|
|
67
|
+
} | {
|
|
68
|
+
readonly kind: 'storage_corruption_detected';
|
|
69
|
+
};
|
|
70
|
+
export type ReasonModelError = {
|
|
71
|
+
readonly code: 'INVALID_DELIMITER_SAFE_ID';
|
|
72
|
+
readonly message: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly code: 'INVALID_CONTRACT_REF';
|
|
75
|
+
readonly message: string;
|
|
76
|
+
} | {
|
|
77
|
+
readonly code: 'BLOCKER_MESSAGE_TOO_LARGE';
|
|
78
|
+
readonly message: string;
|
|
79
|
+
} | {
|
|
80
|
+
readonly code: 'BLOCKER_SUGGESTED_FIX_TOO_LARGE';
|
|
81
|
+
readonly message: string;
|
|
82
|
+
} | {
|
|
83
|
+
readonly code: 'INVARIANT_VIOLATION';
|
|
84
|
+
readonly message: string;
|
|
85
|
+
};
|
|
86
|
+
export declare function reasonToGap(reason: ReasonV1): {
|
|
87
|
+
readonly severity: GapSeverityV1;
|
|
88
|
+
readonly reason: GapReasonV1;
|
|
89
|
+
readonly summary: string;
|
|
90
|
+
};
|
|
91
|
+
export declare function blockerSortKey(b: BlockerV1): string;
|
|
92
|
+
export declare function reasonToBlocker(reason: ReasonV1): Result<BlockerV1, ReasonModelError>;
|
|
93
|
+
export declare function buildBlockerReport(reasons: readonly ReasonV1[]): Result<BlockerReportV1, ReasonModelError>;
|
|
94
|
+
export declare function shouldBlock(autonomy: AutonomyV2, reasons: readonly ReasonV1[]): boolean;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reasonToGap = reasonToGap;
|
|
4
|
+
exports.blockerSortKey = blockerSortKey;
|
|
5
|
+
exports.reasonToBlocker = reasonToBlocker;
|
|
6
|
+
exports.buildBlockerReport = buildBlockerReport;
|
|
7
|
+
exports.shouldBlock = shouldBlock;
|
|
8
|
+
const constants_js_1 = require("../constants.js");
|
|
9
|
+
const neverthrow_1 = require("neverthrow");
|
|
10
|
+
function utf8ByteLength(s) {
|
|
11
|
+
return new TextEncoder().encode(s).length;
|
|
12
|
+
}
|
|
13
|
+
function ensureDelimiterSafeId(label, value) {
|
|
14
|
+
if (!constants_js_1.DELIMITER_SAFE_ID_PATTERN.test(value)) {
|
|
15
|
+
return (0, neverthrow_1.err)({
|
|
16
|
+
code: 'INVALID_DELIMITER_SAFE_ID',
|
|
17
|
+
message: `${label} must be delimiter-safe: [a-z0-9_-]+`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return (0, neverthrow_1.ok)(value);
|
|
21
|
+
}
|
|
22
|
+
function ensureContractRef(contractRef) {
|
|
23
|
+
if (contractRef.trim().length === 0) {
|
|
24
|
+
return (0, neverthrow_1.err)({ code: 'INVALID_CONTRACT_REF', message: 'contractRef must be non-empty' });
|
|
25
|
+
}
|
|
26
|
+
return (0, neverthrow_1.ok)(contractRef);
|
|
27
|
+
}
|
|
28
|
+
function ensureBlockerTextBudgets(blocker) {
|
|
29
|
+
if (utf8ByteLength(blocker.message) > constants_js_1.MAX_BLOCKER_MESSAGE_BYTES) {
|
|
30
|
+
return (0, neverthrow_1.err)({
|
|
31
|
+
code: 'BLOCKER_MESSAGE_TOO_LARGE',
|
|
32
|
+
message: `blocker.message exceeds ${constants_js_1.MAX_BLOCKER_MESSAGE_BYTES} UTF-8 bytes`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (blocker.suggestedFix && utf8ByteLength(blocker.suggestedFix) > constants_js_1.MAX_BLOCKER_SUGGESTED_FIX_BYTES) {
|
|
36
|
+
return (0, neverthrow_1.err)({
|
|
37
|
+
code: 'BLOCKER_SUGGESTED_FIX_TOO_LARGE',
|
|
38
|
+
message: `blocker.suggestedFix exceeds ${constants_js_1.MAX_BLOCKER_SUGGESTED_FIX_BYTES} UTF-8 bytes`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return (0, neverthrow_1.ok)(blocker);
|
|
42
|
+
}
|
|
43
|
+
function reasonToGap(reason) {
|
|
44
|
+
switch (reason.kind) {
|
|
45
|
+
case 'user_only_dependency':
|
|
46
|
+
return {
|
|
47
|
+
severity: 'critical',
|
|
48
|
+
reason: { category: 'user_only_dependency', detail: reason.detail },
|
|
49
|
+
summary: `User-only dependency: ${reason.detail} (stepId=${reason.stepId})`,
|
|
50
|
+
};
|
|
51
|
+
case 'missing_required_output':
|
|
52
|
+
return {
|
|
53
|
+
severity: 'critical',
|
|
54
|
+
reason: { category: 'contract_violation', detail: 'missing_required_output' },
|
|
55
|
+
summary: `Missing required output for contractRef=${reason.contractRef}`,
|
|
56
|
+
};
|
|
57
|
+
case 'invalid_required_output':
|
|
58
|
+
return {
|
|
59
|
+
severity: 'critical',
|
|
60
|
+
reason: { category: 'contract_violation', detail: 'invalid_required_output' },
|
|
61
|
+
summary: `Invalid required output for contractRef=${reason.contractRef}`,
|
|
62
|
+
};
|
|
63
|
+
case 'required_capability_unknown':
|
|
64
|
+
return {
|
|
65
|
+
severity: 'critical',
|
|
66
|
+
reason: { category: 'capability_missing', detail: 'required_capability_unknown' },
|
|
67
|
+
summary: `Required capability status unknown: ${reason.capability}`,
|
|
68
|
+
};
|
|
69
|
+
case 'required_capability_unavailable':
|
|
70
|
+
return {
|
|
71
|
+
severity: 'critical',
|
|
72
|
+
reason: { category: 'capability_missing', detail: 'required_capability_unavailable' },
|
|
73
|
+
summary: `Required capability unavailable: ${reason.capability}`,
|
|
74
|
+
};
|
|
75
|
+
case 'storage_corruption_detected':
|
|
76
|
+
return {
|
|
77
|
+
severity: 'critical',
|
|
78
|
+
reason: { category: 'unexpected', detail: 'storage_corruption_detected' },
|
|
79
|
+
summary: 'Storage corruption detected',
|
|
80
|
+
};
|
|
81
|
+
case 'missing_context_key':
|
|
82
|
+
return {
|
|
83
|
+
severity: 'critical',
|
|
84
|
+
reason: { category: 'unexpected', detail: 'invariant_violation' },
|
|
85
|
+
summary: `Missing required context key: ${reason.key}`,
|
|
86
|
+
};
|
|
87
|
+
case 'context_budget_exceeded':
|
|
88
|
+
return {
|
|
89
|
+
severity: 'critical',
|
|
90
|
+
reason: { category: 'unexpected', detail: 'invariant_violation' },
|
|
91
|
+
summary: 'Context budget exceeded',
|
|
92
|
+
};
|
|
93
|
+
case 'invariant_violation':
|
|
94
|
+
return {
|
|
95
|
+
severity: 'critical',
|
|
96
|
+
reason: { category: 'unexpected', detail: 'invariant_violation' },
|
|
97
|
+
summary: 'Invariant violation',
|
|
98
|
+
};
|
|
99
|
+
default: {
|
|
100
|
+
const _exhaustive = reason;
|
|
101
|
+
return _exhaustive;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function blockerSortKey(b) {
|
|
106
|
+
const p = b.pointer;
|
|
107
|
+
let ptrStable;
|
|
108
|
+
switch (p.kind) {
|
|
109
|
+
case 'context_key':
|
|
110
|
+
ptrStable = p.key;
|
|
111
|
+
break;
|
|
112
|
+
case 'output_contract':
|
|
113
|
+
ptrStable = p.contractRef;
|
|
114
|
+
break;
|
|
115
|
+
case 'capability':
|
|
116
|
+
ptrStable = p.capability;
|
|
117
|
+
break;
|
|
118
|
+
case 'workflow_step':
|
|
119
|
+
ptrStable = p.stepId;
|
|
120
|
+
break;
|
|
121
|
+
case 'context_budget':
|
|
122
|
+
ptrStable = '';
|
|
123
|
+
break;
|
|
124
|
+
default: {
|
|
125
|
+
const _exhaustive = p;
|
|
126
|
+
ptrStable = _exhaustive;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return `${b.code}|${p.kind}|${ptrStable}`;
|
|
130
|
+
}
|
|
131
|
+
function reasonToBlocker(reason) {
|
|
132
|
+
switch (reason.kind) {
|
|
133
|
+
case 'missing_context_key':
|
|
134
|
+
return ensureDelimiterSafeId('context_key.key', reason.key)
|
|
135
|
+
.map((key) => ({
|
|
136
|
+
code: 'INVARIANT_VIOLATION',
|
|
137
|
+
pointer: { kind: 'context_key', key },
|
|
138
|
+
message: `Missing required context key: ${key}`,
|
|
139
|
+
suggestedFix: `Include context.${key} (delimiter-safe key) in the next continue_workflow call.`,
|
|
140
|
+
}))
|
|
141
|
+
.andThen(ensureBlockerTextBudgets);
|
|
142
|
+
case 'context_budget_exceeded':
|
|
143
|
+
return ensureBlockerTextBudgets({
|
|
144
|
+
code: 'INVARIANT_VIOLATION',
|
|
145
|
+
pointer: { kind: 'context_budget' },
|
|
146
|
+
message: 'Context exceeded the allowed budget or was non-serializable.',
|
|
147
|
+
suggestedFix: 'Remove large blobs from context and pass only small external inputs (IDs, paths, parameters).',
|
|
148
|
+
});
|
|
149
|
+
case 'missing_required_output':
|
|
150
|
+
return ensureContractRef(reason.contractRef)
|
|
151
|
+
.map((contractRef) => ({
|
|
152
|
+
code: 'MISSING_REQUIRED_OUTPUT',
|
|
153
|
+
pointer: { kind: 'output_contract', contractRef },
|
|
154
|
+
message: `Missing required output (contractRef=${contractRef}).`,
|
|
155
|
+
suggestedFix: 'Provide output.notesMarkdown that satisfies the step output requirements.',
|
|
156
|
+
}))
|
|
157
|
+
.andThen(ensureBlockerTextBudgets);
|
|
158
|
+
case 'invalid_required_output':
|
|
159
|
+
return ensureContractRef(reason.contractRef)
|
|
160
|
+
.map((contractRef) => ({
|
|
161
|
+
code: 'INVALID_REQUIRED_OUTPUT',
|
|
162
|
+
pointer: { kind: 'output_contract', contractRef },
|
|
163
|
+
message: `Invalid output for contractRef=${contractRef}.`,
|
|
164
|
+
suggestedFix: 'Adjust output.notesMarkdown to satisfy validation and retry continue_workflow with the same ackToken.',
|
|
165
|
+
}))
|
|
166
|
+
.andThen(ensureBlockerTextBudgets);
|
|
167
|
+
case 'required_capability_unknown':
|
|
168
|
+
return ensureBlockerTextBudgets({
|
|
169
|
+
code: 'REQUIRED_CAPABILITY_UNKNOWN',
|
|
170
|
+
pointer: { kind: 'capability', capability: reason.capability },
|
|
171
|
+
message: `Required capability status is unknown: ${reason.capability}.`,
|
|
172
|
+
suggestedFix: 'Probe the capability (or run the required tool) and retry.',
|
|
173
|
+
});
|
|
174
|
+
case 'required_capability_unavailable':
|
|
175
|
+
return ensureBlockerTextBudgets({
|
|
176
|
+
code: 'REQUIRED_CAPABILITY_UNAVAILABLE',
|
|
177
|
+
pointer: { kind: 'capability', capability: reason.capability },
|
|
178
|
+
message: `Required capability is unavailable: ${reason.capability}.`,
|
|
179
|
+
suggestedFix: 'Enable the capability or choose an alternate approach that does not require it.',
|
|
180
|
+
});
|
|
181
|
+
case 'user_only_dependency':
|
|
182
|
+
return ensureDelimiterSafeId('workflow_step.stepId', reason.stepId)
|
|
183
|
+
.map((stepId) => ({
|
|
184
|
+
code: 'USER_ONLY_DEPENDENCY',
|
|
185
|
+
pointer: { kind: 'workflow_step', stepId },
|
|
186
|
+
message: `Step requires user input: ${reason.detail}.`,
|
|
187
|
+
suggestedFix: 'Ask the user for the required input/approval and retry.',
|
|
188
|
+
}))
|
|
189
|
+
.andThen(ensureBlockerTextBudgets);
|
|
190
|
+
case 'invariant_violation':
|
|
191
|
+
return ensureBlockerTextBudgets({
|
|
192
|
+
code: 'INVARIANT_VIOLATION',
|
|
193
|
+
pointer: { kind: 'context_budget' },
|
|
194
|
+
message: 'Invariant violation: execution cannot safely proceed.',
|
|
195
|
+
suggestedFix: 'Inspect the durable event log and pinned workflow snapshot for mismatches.',
|
|
196
|
+
});
|
|
197
|
+
case 'storage_corruption_detected':
|
|
198
|
+
return ensureBlockerTextBudgets({
|
|
199
|
+
code: 'STORAGE_CORRUPTION_DETECTED',
|
|
200
|
+
pointer: { kind: 'context_budget' },
|
|
201
|
+
message: 'Storage corruption detected: durable session data failed validation.',
|
|
202
|
+
suggestedFix: 'Stop and investigate the session store; do not continue advancing this session.',
|
|
203
|
+
});
|
|
204
|
+
default: {
|
|
205
|
+
const _exhaustive = reason;
|
|
206
|
+
return _exhaustive;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function buildBlockerReport(reasons) {
|
|
211
|
+
if (reasons.length === 0) {
|
|
212
|
+
return (0, neverthrow_1.err)({ code: 'INVARIANT_VIOLATION', message: 'buildBlockerReport requires at least one reason' });
|
|
213
|
+
}
|
|
214
|
+
const blockers = [];
|
|
215
|
+
for (const reason of reasons) {
|
|
216
|
+
const b = reasonToBlocker(reason);
|
|
217
|
+
if (b.isErr())
|
|
218
|
+
return (0, neverthrow_1.err)(b.error);
|
|
219
|
+
blockers.push(b.value);
|
|
220
|
+
}
|
|
221
|
+
blockers.sort((a, b) => blockerSortKey(a).localeCompare(blockerSortKey(b)));
|
|
222
|
+
return (0, neverthrow_1.ok)({ blockers: blockers.slice(0, constants_js_1.MAX_BLOCKERS) });
|
|
223
|
+
}
|
|
224
|
+
function shouldBlock(autonomy, reasons) {
|
|
225
|
+
if (reasons.length === 0)
|
|
226
|
+
return false;
|
|
227
|
+
return autonomy !== 'full_auto_never_stop';
|
|
228
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { RunDagRunV2 } from '../../../v2/projections/run-dag.js';
|
|
3
|
+
import type { NodeOutputsProjectionV2 } from '../../../v2/projections/node-outputs.js';
|
|
4
|
+
import type { NodeId } from '../ids/index.js';
|
|
5
|
+
export type RecapRecoveryError = {
|
|
6
|
+
readonly code: 'RECAP_RECOVERY_FAILED';
|
|
7
|
+
readonly message: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function collectAncestryRecap(args: {
|
|
10
|
+
readonly nodeId: NodeId;
|
|
11
|
+
readonly dag: RunDagRunV2;
|
|
12
|
+
readonly outputs: NodeOutputsProjectionV2;
|
|
13
|
+
readonly includeCurrentNode: boolean;
|
|
14
|
+
}): Result<readonly string[], RecapRecoveryError>;
|
|
15
|
+
export declare function collectDownstreamRecap(args: {
|
|
16
|
+
readonly fromNodeId: NodeId;
|
|
17
|
+
readonly toNodeId: NodeId;
|
|
18
|
+
readonly dag: RunDagRunV2;
|
|
19
|
+
readonly outputs: NodeOutputsProjectionV2;
|
|
20
|
+
}): Result<readonly string[], RecapRecoveryError>;
|
|
21
|
+
export declare function buildChildSummary(args: {
|
|
22
|
+
readonly nodeId: NodeId;
|
|
23
|
+
readonly dag: RunDagRunV2;
|
|
24
|
+
}): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectAncestryRecap = collectAncestryRecap;
|
|
4
|
+
exports.collectDownstreamRecap = collectDownstreamRecap;
|
|
5
|
+
exports.buildChildSummary = buildChildSummary;
|
|
6
|
+
const neverthrow_1 = require("neverthrow");
|
|
7
|
+
function collectAncestryRecap(args) {
|
|
8
|
+
const buildChain = (cur, visited) => {
|
|
9
|
+
if (!cur || visited.has(cur))
|
|
10
|
+
return [];
|
|
11
|
+
const nodeData = args.dag.nodesById[cur];
|
|
12
|
+
const parent = nodeData?.parentNodeId ?? null;
|
|
13
|
+
const newVisited = new Set([...visited, cur]);
|
|
14
|
+
return [cur, ...buildChain(parent, newVisited)];
|
|
15
|
+
};
|
|
16
|
+
const startNode = args.includeCurrentNode
|
|
17
|
+
? String(args.nodeId)
|
|
18
|
+
: args.dag.nodesById[String(args.nodeId)]?.parentNodeId ?? null;
|
|
19
|
+
const chain = buildChain(startNode, new Set());
|
|
20
|
+
const recaps = chain.flatMap((nodeId) => {
|
|
21
|
+
const nodeOutputs = args.outputs.nodesById[nodeId];
|
|
22
|
+
if (!nodeOutputs)
|
|
23
|
+
return [];
|
|
24
|
+
return nodeOutputs.currentByChannel.recap
|
|
25
|
+
.filter(r => r.payload.payloadKind === 'notes')
|
|
26
|
+
.map(r => {
|
|
27
|
+
if (r.payload.payloadKind === 'notes') {
|
|
28
|
+
return r.payload.notesMarkdown;
|
|
29
|
+
}
|
|
30
|
+
return '';
|
|
31
|
+
})
|
|
32
|
+
.filter(s => s.length > 0);
|
|
33
|
+
});
|
|
34
|
+
return (0, neverthrow_1.ok)([...recaps].reverse());
|
|
35
|
+
}
|
|
36
|
+
function collectDownstreamRecap(args) {
|
|
37
|
+
const buildPathBackward = (cur, visited) => {
|
|
38
|
+
if (!cur || cur === String(args.fromNodeId) || visited.has(cur))
|
|
39
|
+
return [];
|
|
40
|
+
const nodeData = args.dag.nodesById[cur];
|
|
41
|
+
const parent = nodeData?.parentNodeId ?? null;
|
|
42
|
+
const newVisited = new Set([...visited, cur]);
|
|
43
|
+
return [cur, ...buildPathBackward(parent, newVisited)];
|
|
44
|
+
};
|
|
45
|
+
const pathBackward = buildPathBackward(String(args.toNodeId), new Set());
|
|
46
|
+
const recaps = [...pathBackward].reverse().flatMap((nodeId) => {
|
|
47
|
+
const nodeOutputs = args.outputs.nodesById[nodeId];
|
|
48
|
+
if (!nodeOutputs)
|
|
49
|
+
return [];
|
|
50
|
+
return nodeOutputs.currentByChannel.recap
|
|
51
|
+
.filter(r => r.payload.payloadKind === 'notes')
|
|
52
|
+
.map(r => {
|
|
53
|
+
if (r.payload.payloadKind === 'notes') {
|
|
54
|
+
return r.payload.notesMarkdown;
|
|
55
|
+
}
|
|
56
|
+
return '';
|
|
57
|
+
})
|
|
58
|
+
.filter(s => s.length > 0);
|
|
59
|
+
});
|
|
60
|
+
return (0, neverthrow_1.ok)(recaps);
|
|
61
|
+
}
|
|
62
|
+
function buildChildSummary(args) {
|
|
63
|
+
const children = args.dag.edges.filter((e) => e.fromNodeId === args.nodeId);
|
|
64
|
+
const count = children.length;
|
|
65
|
+
if (count === 0)
|
|
66
|
+
return '';
|
|
67
|
+
if (count === 1) {
|
|
68
|
+
return `This node has 1 child. Preferred branch tip: ${args.dag.preferredTipNodeId ?? 'unknown'}`;
|
|
69
|
+
}
|
|
70
|
+
return `This node has ${count} children. Preferred branch tip: ${args.dag.preferredTipNodeId ?? 'unknown'}`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ValidationCriteria, ValidationResult } from '../../../types/validation.js';
|
|
2
|
+
import type { OutputRequirementStatus } from './blocking-decision.js';
|
|
3
|
+
export declare const VALIDATION_CRITERIA_CONTRACT_REF: "wr.validationCriteria";
|
|
4
|
+
export declare function getOutputRequirementStatusV1(args: {
|
|
5
|
+
readonly validationCriteria: ValidationCriteria | undefined;
|
|
6
|
+
readonly notesMarkdown: string | undefined;
|
|
7
|
+
readonly validation: ValidationResult | undefined;
|
|
8
|
+
}): OutputRequirementStatus;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VALIDATION_CRITERIA_CONTRACT_REF = void 0;
|
|
4
|
+
exports.getOutputRequirementStatusV1 = getOutputRequirementStatusV1;
|
|
5
|
+
exports.VALIDATION_CRITERIA_CONTRACT_REF = 'wr.validationCriteria';
|
|
6
|
+
function getOutputRequirementStatusV1(args) {
|
|
7
|
+
if (!args.validationCriteria)
|
|
8
|
+
return { kind: 'not_required' };
|
|
9
|
+
if (!args.notesMarkdown) {
|
|
10
|
+
return { kind: 'missing', contractRef: exports.VALIDATION_CRITERIA_CONTRACT_REF };
|
|
11
|
+
}
|
|
12
|
+
if (args.validation && !args.validation.valid) {
|
|
13
|
+
return { kind: 'invalid', contractRef: exports.VALIDATION_CRITERIA_CONTRACT_REF, validation: args.validation };
|
|
14
|
+
}
|
|
15
|
+
return { kind: 'not_required' };
|
|
16
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractValidationRequirements = extractValidationRequirements;
|
|
4
|
+
const validation_js_1 = require("../../../types/validation.js");
|
|
5
|
+
function extractValidationRequirements(criteria) {
|
|
6
|
+
if (!criteria)
|
|
7
|
+
return [];
|
|
8
|
+
try {
|
|
9
|
+
const requirements = extractRequirementsRecursive(criteria);
|
|
10
|
+
return requirements.slice(0, 5);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function extractRequirementsRecursive(criteria) {
|
|
17
|
+
const requirements = [];
|
|
18
|
+
if ((0, validation_js_1.isValidationRule)(criteria)) {
|
|
19
|
+
const formatted = formatRule(criteria);
|
|
20
|
+
if (formatted)
|
|
21
|
+
requirements.push(formatted);
|
|
22
|
+
return requirements;
|
|
23
|
+
}
|
|
24
|
+
if ((0, validation_js_1.isValidationComposition)(criteria)) {
|
|
25
|
+
if (criteria.and) {
|
|
26
|
+
for (const sub of criteria.and) {
|
|
27
|
+
requirements.push(...extractRequirementsRecursive(sub));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return requirements;
|
|
32
|
+
}
|
|
33
|
+
function formatRule(rule) {
|
|
34
|
+
switch (rule.type) {
|
|
35
|
+
case 'contains':
|
|
36
|
+
if (!rule.value)
|
|
37
|
+
return null;
|
|
38
|
+
return `Must contain: "${rule.value}"`;
|
|
39
|
+
case 'regex':
|
|
40
|
+
if (!rule.pattern)
|
|
41
|
+
return null;
|
|
42
|
+
const flags = rule.flags ? ` (flags: ${rule.flags})` : '';
|
|
43
|
+
return `Must match pattern: ${rule.pattern}${flags}`;
|
|
44
|
+
case 'length':
|
|
45
|
+
const parts = [];
|
|
46
|
+
if (rule.min !== undefined)
|
|
47
|
+
parts.push(`≥${rule.min} chars`);
|
|
48
|
+
if (rule.max !== undefined)
|
|
49
|
+
parts.push(`≤${rule.max} chars`);
|
|
50
|
+
if (parts.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
return `Length: ${parts.join(', ')}`;
|
|
53
|
+
case 'schema':
|
|
54
|
+
return null;
|
|
55
|
+
default:
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|