@exaudeus/workrail 3.5.0 → 3.6.1
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/README.md +4 -1
- package/dist/application/services/validation-engine.js +50 -0
- package/dist/config/feature-flags.js +8 -0
- package/dist/engine/engine-factory.js +4 -2
- package/dist/manifest.json +102 -54
- package/dist/mcp/handler-factory.js +21 -4
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +6 -1
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +22 -4
- package/dist/mcp/handlers/v2-execution/index.d.ts +6 -1
- package/dist/mcp/handlers/v2-execution/index.js +13 -3
- package/dist/mcp/handlers/v2-execution/start.d.ts +9 -1
- package/dist/mcp/handlers/v2-execution/start.js +74 -36
- package/dist/mcp/handlers/v2-execution-helpers.d.ts +2 -0
- package/dist/mcp/handlers/v2-execution-helpers.js +2 -0
- package/dist/mcp/handlers/v2-reference-resolver.d.ts +14 -0
- package/dist/mcp/handlers/v2-reference-resolver.js +112 -0
- package/dist/mcp/handlers/v2-resolve-refs-envelope.d.ts +5 -0
- package/dist/mcp/handlers/v2-resolve-refs-envelope.js +17 -0
- package/dist/mcp/handlers/v2-workflow.js +2 -0
- package/dist/mcp/output-schemas.d.ts +38 -0
- package/dist/mcp/output-schemas.js +8 -0
- package/dist/mcp/render-envelope.d.ts +21 -0
- package/dist/mcp/render-envelope.js +59 -0
- package/dist/mcp/response-supplements.d.ts +17 -0
- package/dist/mcp/response-supplements.js +58 -0
- package/dist/mcp/step-content-envelope.d.ts +32 -0
- package/dist/mcp/step-content-envelope.js +13 -0
- package/dist/mcp/transports/stdio-entry.js +19 -6
- package/dist/mcp/v2-response-formatter.d.ts +11 -1
- package/dist/mcp/v2-response-formatter.js +168 -1
- package/dist/mcp/workflow-protocol-contracts.js +9 -7
- package/dist/types/workflow-definition.d.ts +16 -0
- package/dist/types/workflow-definition.js +1 -0
- package/dist/utils/condition-evaluator.d.ts +1 -0
- package/dist/utils/condition-evaluator.js +7 -0
- package/dist/v2/durable-core/domain/context-template-resolver.d.ts +2 -0
- package/dist/v2/durable-core/domain/context-template-resolver.js +26 -0
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +2 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +93 -15
- package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +256 -0
- package/dist/v2/durable-core/schemas/compiled-workflow/index.js +30 -0
- package/package.json +4 -1
- package/spec/authoring-spec.provenance.json +77 -0
- package/spec/authoring-spec.schema.json +370 -0
- package/workflows/coding-task-workflow-agentic.lean.v2.json +132 -30
- package/workflows/workflow-for-workflows.json +27 -1
|
@@ -36,6 +36,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.startStdioServer = startStdioServer;
|
|
37
37
|
const server_js_1 = require("../server.js");
|
|
38
38
|
const shutdown_hooks_js_1 = require("./shutdown-hooks.js");
|
|
39
|
+
const INITIAL_ROOTS_TIMEOUT_MS = 1000;
|
|
40
|
+
async function fetchInitialRootsWithTimeout(server) {
|
|
41
|
+
return Promise.race([
|
|
42
|
+
server.listRoots(),
|
|
43
|
+
new Promise((resolve) => {
|
|
44
|
+
setTimeout(() => resolve(null), INITIAL_ROOTS_TIMEOUT_MS);
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
39
48
|
async function startStdioServer() {
|
|
40
49
|
const { server, ctx, rootsManager } = await (0, server_js_1.composeServer)();
|
|
41
50
|
const { StdioServerTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/server/stdio.js')));
|
|
@@ -52,15 +61,19 @@ async function startStdioServer() {
|
|
|
52
61
|
});
|
|
53
62
|
const transport = new StdioServerTransport();
|
|
54
63
|
await server.connect(transport);
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
console.error('[Transport] WorkRail MCP Server running on stdio');
|
|
65
|
+
void fetchInitialRootsWithTimeout(server)
|
|
66
|
+
.then((result) => {
|
|
67
|
+
if (result == null) {
|
|
68
|
+
console.error('[Roots] Initial roots probe timed out; workspace context will use server CWD fallback');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
57
71
|
rootsManager.updateRootUris(result.roots.map((r) => r.uri));
|
|
58
72
|
console.error(`[Roots] Initial workspace roots: ${result.roots.map((r) => r.uri).join(', ') || '(none)'}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
73
|
+
})
|
|
74
|
+
.catch(() => {
|
|
61
75
|
console.error('[Roots] Client does not support roots/list; workspace context will use server CWD fallback');
|
|
62
|
-
}
|
|
63
|
-
console.error('[Transport] WorkRail MCP Server running on stdio');
|
|
76
|
+
});
|
|
64
77
|
(0, shutdown_hooks_js_1.wireShutdownHooks)({
|
|
65
78
|
onBeforeTerminate: async () => {
|
|
66
79
|
await ctx.httpServer?.stop();
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { type FormattedSupplement } from './response-supplements.js';
|
|
2
|
+
export interface FormattedReferences {
|
|
3
|
+
readonly kind: 'references';
|
|
4
|
+
readonly text: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FormattedResponse {
|
|
7
|
+
readonly primary: string;
|
|
8
|
+
readonly references?: FormattedReferences;
|
|
9
|
+
readonly supplements?: readonly FormattedSupplement[];
|
|
10
|
+
}
|
|
11
|
+
export declare function formatV2ExecutionResponse(data: unknown): FormattedResponse | null;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatV2ExecutionResponse = formatV2ExecutionResponse;
|
|
4
|
+
const render_envelope_js_1 = require("./render-envelope.js");
|
|
5
|
+
const response_supplements_js_1 = require("./response-supplements.js");
|
|
4
6
|
function isV2ExecutionResponse(data) {
|
|
5
7
|
if (typeof data !== 'object' || data === null)
|
|
6
8
|
return false;
|
|
@@ -207,9 +209,160 @@ function formatSuccess(data) {
|
|
|
207
209
|
}
|
|
208
210
|
return lines.join('\n');
|
|
209
211
|
}
|
|
212
|
+
function isCleanResponseFormat() {
|
|
213
|
+
return process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT === 'true';
|
|
214
|
+
}
|
|
215
|
+
const CLEAN_ADVANCE_FOOTERS = [
|
|
216
|
+
'WorkRail: when done, call continue_workflow with your notes. Token:',
|
|
217
|
+
'WorkRail: advance with continue_workflow when ready. Include your notes. Token:',
|
|
218
|
+
'WorkRail: call continue_workflow with your notes to move on. Token:',
|
|
219
|
+
'WorkRail: finished? continue_workflow with notes. Token:',
|
|
220
|
+
];
|
|
221
|
+
const CLEAN_REHYDRATE_FOOTERS = [
|
|
222
|
+
'WorkRail: you are resuming this step. When ready, call continue_workflow with your notes. Token:',
|
|
223
|
+
'WorkRail: picking up where you left off. Advance with continue_workflow and notes. Token:',
|
|
224
|
+
'WorkRail: resuming. Call continue_workflow with notes when done. Token:',
|
|
225
|
+
];
|
|
226
|
+
function pickFooter(variants, stepId) {
|
|
227
|
+
if (!stepId)
|
|
228
|
+
return variants[0];
|
|
229
|
+
let hash = 0;
|
|
230
|
+
for (let i = 0; i < stepId.length; i++) {
|
|
231
|
+
hash = ((hash << 5) - hash + stepId.charCodeAt(i)) | 0;
|
|
232
|
+
}
|
|
233
|
+
return variants[Math.abs(hash) % variants.length];
|
|
234
|
+
}
|
|
235
|
+
function formatCleanComplete(_data) {
|
|
236
|
+
return 'Workflow complete. No further steps.';
|
|
237
|
+
}
|
|
238
|
+
function formatCleanBlocked(data) {
|
|
239
|
+
const firstBlocker = data.blockers.blockers[0];
|
|
240
|
+
const heading = firstBlocker ? (BLOCKER_HEADING[firstBlocker.code] ?? firstBlocker.code) : 'Blocked';
|
|
241
|
+
const lines = [`Blocked: ${heading}`, ''];
|
|
242
|
+
for (const b of data.blockers.blockers) {
|
|
243
|
+
lines.push(b.message);
|
|
244
|
+
if (b.suggestedFix) {
|
|
245
|
+
lines.push('');
|
|
246
|
+
lines.push(`What to do: ${b.suggestedFix}`);
|
|
247
|
+
}
|
|
248
|
+
lines.push('');
|
|
249
|
+
}
|
|
250
|
+
if (data.validation) {
|
|
251
|
+
if (data.validation.issues.length > 0) {
|
|
252
|
+
lines.push('Issues:');
|
|
253
|
+
for (const issue of data.validation.issues)
|
|
254
|
+
lines.push(`- ${issue}`);
|
|
255
|
+
lines.push('');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const token = data.retryContinueToken ?? data.nextCall?.params.continueToken ?? data.continueToken;
|
|
259
|
+
if (token) {
|
|
260
|
+
lines.push(`WorkRail: retry with corrected output. Token: ${token}`);
|
|
261
|
+
}
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}
|
|
264
|
+
function formatCleanRehydrate(data) {
|
|
265
|
+
const lines = [];
|
|
266
|
+
if (data.pending) {
|
|
267
|
+
lines.push(data.pending.prompt);
|
|
268
|
+
lines.push('');
|
|
269
|
+
}
|
|
270
|
+
const token = data.nextCall?.params.continueToken ?? data.continueToken;
|
|
271
|
+
lines.push('---');
|
|
272
|
+
if (token) {
|
|
273
|
+
const footer = pickFooter(CLEAN_REHYDRATE_FOOTERS, data.pending?.stepId);
|
|
274
|
+
lines.push(`${footer} ${token}`);
|
|
275
|
+
}
|
|
276
|
+
const driftBlock = formatBindingDriftWarnings(data);
|
|
277
|
+
if (driftBlock) {
|
|
278
|
+
lines.push(driftBlock);
|
|
279
|
+
}
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|
|
282
|
+
function formatCleanSuccess(data) {
|
|
283
|
+
const lines = [];
|
|
284
|
+
if (data.pending) {
|
|
285
|
+
lines.push(data.pending.prompt);
|
|
286
|
+
lines.push('');
|
|
287
|
+
}
|
|
288
|
+
const token = data.nextCall?.params.continueToken ?? data.continueToken;
|
|
289
|
+
lines.push('---');
|
|
290
|
+
if (token) {
|
|
291
|
+
const footer = pickFooter(CLEAN_ADVANCE_FOOTERS, data.pending?.stepId);
|
|
292
|
+
lines.push(`${footer} ${token}`);
|
|
293
|
+
}
|
|
294
|
+
const driftBlock = formatBindingDriftWarnings(data);
|
|
295
|
+
if (driftBlock) {
|
|
296
|
+
lines.push(driftBlock);
|
|
297
|
+
}
|
|
298
|
+
return lines.join('\n');
|
|
299
|
+
}
|
|
300
|
+
function deriveRenderInput(data) {
|
|
301
|
+
const envelope = (0, render_envelope_js_1.getV2ExecutionRenderEnvelope)(data);
|
|
302
|
+
if (envelope != null) {
|
|
303
|
+
return isV2ExecutionResponse(envelope.response)
|
|
304
|
+
? { response: envelope.response, lifecycle: envelope.lifecycle, contentEnvelope: envelope.contentEnvelope }
|
|
305
|
+
: null;
|
|
306
|
+
}
|
|
307
|
+
return isV2ExecutionResponse(data)
|
|
308
|
+
? { response: data, lifecycle: 'advance' }
|
|
309
|
+
: null;
|
|
310
|
+
}
|
|
311
|
+
function renderReferencesSection(contentEnvelope, lifecycle) {
|
|
312
|
+
if (contentEnvelope == null)
|
|
313
|
+
return null;
|
|
314
|
+
const refs = contentEnvelope.references;
|
|
315
|
+
if (refs.length === 0)
|
|
316
|
+
return null;
|
|
317
|
+
switch (lifecycle) {
|
|
318
|
+
case 'start': {
|
|
319
|
+
const lines = ['Workflow References:', ''];
|
|
320
|
+
for (const ref of refs) {
|
|
321
|
+
const displayPath = ref.status === 'resolved' ? ref.resolvedPath : ref.source;
|
|
322
|
+
const statusTag = ref.status === 'unresolved' ? ' [unresolved]' : ref.status === 'pinned' ? ' [pinned]' : '';
|
|
323
|
+
const authority = ref.authoritative ? ' (authoritative)' : '';
|
|
324
|
+
const resolveTag = ref.resolveFrom === 'package' ? ' [package]' : '';
|
|
325
|
+
lines.push(`- **${ref.title}**${authority}${statusTag}${resolveTag}`);
|
|
326
|
+
lines.push(` Path: ${displayPath}`);
|
|
327
|
+
lines.push(` Purpose: ${ref.purpose}`);
|
|
328
|
+
lines.push('');
|
|
329
|
+
}
|
|
330
|
+
return { kind: 'references', text: lines.join('\n').trimEnd() };
|
|
331
|
+
}
|
|
332
|
+
case 'rehydrate': {
|
|
333
|
+
const lines = ['Workflow References (reminder):', ''];
|
|
334
|
+
for (const ref of refs) {
|
|
335
|
+
const displayPath = ref.status === 'resolved' ? ref.resolvedPath : ref.source;
|
|
336
|
+
const statusTag = ref.status === 'unresolved' ? ' [unresolved]' : ref.status === 'pinned' ? ' [pinned]' : '';
|
|
337
|
+
const resolveTag = ref.resolveFrom === 'package' ? ' [package]' : '';
|
|
338
|
+
lines.push(`- ${ref.title}${statusTag}${resolveTag}: ${displayPath}`);
|
|
339
|
+
}
|
|
340
|
+
return { kind: 'references', text: lines.join('\n').trimEnd() };
|
|
341
|
+
}
|
|
342
|
+
case 'advance':
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
210
346
|
function formatV2ExecutionResponse(data) {
|
|
211
|
-
|
|
347
|
+
const renderInput = deriveRenderInput(data);
|
|
348
|
+
if (!renderInput)
|
|
212
349
|
return null;
|
|
350
|
+
const cleanFormat = isCleanResponseFormat();
|
|
351
|
+
const { response, lifecycle, contentEnvelope } = renderInput;
|
|
352
|
+
const references = renderReferencesSection(contentEnvelope, lifecycle);
|
|
353
|
+
if (cleanFormat) {
|
|
354
|
+
return {
|
|
355
|
+
...formatV2Clean(response),
|
|
356
|
+
...(references != null ? { references } : {}),
|
|
357
|
+
supplements: (0, response_supplements_js_1.buildResponseSupplements)({ lifecycle, cleanFormat }),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
primary: formatV2Classic(response),
|
|
362
|
+
...(references != null ? { references } : {}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function formatV2Classic(data) {
|
|
213
366
|
if (data.nextIntent === 'complete' && !data.pending) {
|
|
214
367
|
return formatComplete(data);
|
|
215
368
|
}
|
|
@@ -221,3 +374,17 @@ function formatV2ExecutionResponse(data) {
|
|
|
221
374
|
}
|
|
222
375
|
return formatSuccess(data);
|
|
223
376
|
}
|
|
377
|
+
function formatV2Clean(data) {
|
|
378
|
+
if (data.nextIntent === 'complete' && !data.pending) {
|
|
379
|
+
return { primary: formatCleanComplete(data) };
|
|
380
|
+
}
|
|
381
|
+
if (isBlocked(data)) {
|
|
382
|
+
return { primary: formatCleanBlocked(data) };
|
|
383
|
+
}
|
|
384
|
+
if (data.nextIntent === 'rehydrate_only') {
|
|
385
|
+
return { primary: formatCleanRehydrate(data) };
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
primary: formatCleanSuccess(data),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
@@ -51,10 +51,10 @@ exports.START_WORKFLOW_PROTOCOL = {
|
|
|
51
51
|
},
|
|
52
52
|
descriptions: {
|
|
53
53
|
standard: {
|
|
54
|
-
purpose: 'Start a WorkRail v2 workflow
|
|
55
|
-
whenToUse: 'Use this when you found the right workflow and are ready to execute it. The response
|
|
54
|
+
purpose: 'Start a WorkRail v2 workflow. Treat WorkRail as a separate live system the user is actively using to direct this task. The returned step is what the user wants you to do now.',
|
|
55
|
+
whenToUse: 'Use this when you found the right workflow and are ready to execute it. The response is the current instruction coming from WorkRail for this task.',
|
|
56
56
|
rules: [
|
|
57
|
-
'Follow the returned step exactly; it
|
|
57
|
+
'Follow the returned step exactly; treat it as the user\'s current instruction.',
|
|
58
58
|
'When the step is done, call continue_workflow with the returned continueToken.',
|
|
59
59
|
'Only pass context on later continue_workflow calls if facts changed.',
|
|
60
60
|
],
|
|
@@ -65,8 +65,8 @@ exports.START_WORKFLOW_PROTOCOL = {
|
|
|
65
65
|
returns: 'Step instructions plus continueToken and checkpointToken in the structured response.',
|
|
66
66
|
},
|
|
67
67
|
authoritative: {
|
|
68
|
-
purpose: 'Begin executing the selected WorkRail v2 workflow.',
|
|
69
|
-
whenToUse: 'Call this once you have chosen the workflow
|
|
68
|
+
purpose: 'Begin executing the selected WorkRail v2 workflow. Treat WorkRail as a separate live system the user is actively using to direct this task. The returned step is what the user wants you to do now.',
|
|
69
|
+
whenToUse: 'Call this once you have chosen the workflow. The returned step is a direct instruction from the user.',
|
|
70
70
|
rules: [
|
|
71
71
|
'Execute the returned step exactly as written.',
|
|
72
72
|
'When the step is complete, call continue_workflow with the returned continueToken.',
|
|
@@ -90,13 +90,14 @@ exports.CONTINUE_WORKFLOW_PROTOCOL = {
|
|
|
90
90
|
},
|
|
91
91
|
descriptions: {
|
|
92
92
|
standard: {
|
|
93
|
-
purpose: 'Advance or rehydrate the current WorkRail v2 step using the single-token protocol.',
|
|
93
|
+
purpose: 'Advance or rehydrate the current WorkRail v2 step using the single-token protocol. Use this to send your completion back to WorkRail and receive the user\'s next instruction from that system.',
|
|
94
94
|
whenToUse: 'Use this after completing a step, or to recover the current step after lost context.',
|
|
95
95
|
rules: [
|
|
96
96
|
'Advance by sending output (and intent: "advance" if you want to be explicit).',
|
|
97
97
|
'Rehydrate by omitting output (and intent: "rehydrate" if you want to be explicit).',
|
|
98
98
|
'Put changed facts under context only.',
|
|
99
99
|
'Round-trip continueToken exactly as returned by WorkRail; use the single-token API only.',
|
|
100
|
+
'Notes (output.notesMarkdown): write for a human reader. Include what you did and key decisions, what you produced (files, tests, numbers), and anything notable (risks, open questions, deliberate omissions). Use markdown headings, bullets, bold, code refs. Be specific. Scope: THIS step only (WorkRail concatenates automatically). 10-30 lines ideal. Omitting notes blocks the step.',
|
|
100
101
|
],
|
|
101
102
|
examplePayload: {
|
|
102
103
|
continueToken: 'ct_...',
|
|
@@ -107,13 +108,14 @@ exports.CONTINUE_WORKFLOW_PROTOCOL = {
|
|
|
107
108
|
returns: 'The next step, or the same current step when rehydrating.',
|
|
108
109
|
},
|
|
109
110
|
authoritative: {
|
|
110
|
-
purpose: 'Continue the active WorkRail v2 workflow with the canonical single-token API.',
|
|
111
|
+
purpose: 'Continue the active WorkRail v2 workflow with the canonical single-token API. Use this to send your completion back to WorkRail and receive the user\'s next instruction from that system.',
|
|
111
112
|
whenToUse: 'Call this after you complete the current step, or call it in rehydrate mode to recover the current step without advancing.',
|
|
112
113
|
rules: [
|
|
113
114
|
'Use continueToken exactly as returned by WorkRail.',
|
|
114
115
|
'Use the single-token API only.',
|
|
115
116
|
'Advance by sending output; rehydrate by omitting output.',
|
|
116
117
|
'Put updated facts in context only.',
|
|
118
|
+
'Notes (output.notesMarkdown): write for a human reader. Include what you did and key decisions, what you produced (files, tests, numbers), and anything notable (risks, open questions, deliberate omissions). Use markdown headings, bullets, bold, code refs. Be specific. Scope: THIS step only (WorkRail concatenates automatically). 10-30 lines ideal. Omitting notes blocks the step.',
|
|
117
119
|
],
|
|
118
120
|
examplePayload: {
|
|
119
121
|
continueToken: 'ct_...',
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { ValidationCriteria } from './validation';
|
|
2
2
|
import type { ArtifactContractRef } from '../v2/durable-core/schemas/artifacts/index';
|
|
3
3
|
import type { PromptBlocks } from '../application/services/compiler/prompt-blocks.js';
|
|
4
|
+
import type { Condition } from '../utils/condition-evaluator.js';
|
|
4
5
|
export interface OutputContract {
|
|
5
6
|
readonly contractRef: ArtifactContractRef;
|
|
6
7
|
readonly required?: boolean;
|
|
7
8
|
}
|
|
9
|
+
export interface PromptFragment {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly when?: Condition;
|
|
12
|
+
readonly text: string;
|
|
13
|
+
}
|
|
8
14
|
export interface WorkflowStepDefinition {
|
|
9
15
|
readonly id: string;
|
|
10
16
|
readonly title: string;
|
|
@@ -19,6 +25,7 @@ export interface WorkflowStepDefinition {
|
|
|
19
25
|
readonly outputContract?: OutputContract;
|
|
20
26
|
readonly notesOptional?: boolean;
|
|
21
27
|
readonly templateCall?: TemplateCall;
|
|
28
|
+
readonly promptFragments?: readonly PromptFragment[];
|
|
22
29
|
readonly functionDefinitions?: readonly FunctionDefinition[];
|
|
23
30
|
readonly functionCalls?: readonly FunctionCall[];
|
|
24
31
|
readonly functionReferences?: readonly string[];
|
|
@@ -79,6 +86,14 @@ export interface WorkflowRecommendedPreferences {
|
|
|
79
86
|
readonly recommendedAutonomy?: 'guided' | 'full_auto_stop_on_user_deps' | 'full_auto_never_stop';
|
|
80
87
|
readonly recommendedRiskPolicy?: 'conservative' | 'balanced' | 'aggressive';
|
|
81
88
|
}
|
|
89
|
+
export interface WorkflowReference {
|
|
90
|
+
readonly id: string;
|
|
91
|
+
readonly title: string;
|
|
92
|
+
readonly source: string;
|
|
93
|
+
readonly purpose: string;
|
|
94
|
+
readonly authoritative: boolean;
|
|
95
|
+
readonly resolveFrom?: 'workspace' | 'package';
|
|
96
|
+
}
|
|
82
97
|
export interface WorkflowDefinition {
|
|
83
98
|
readonly id: string;
|
|
84
99
|
readonly name: string;
|
|
@@ -92,6 +107,7 @@ export interface WorkflowDefinition {
|
|
|
92
107
|
readonly recommendedPreferences?: WorkflowRecommendedPreferences;
|
|
93
108
|
readonly features?: readonly string[];
|
|
94
109
|
readonly extensionPoints?: readonly ExtensionPoint[];
|
|
110
|
+
readonly references?: readonly WorkflowReference[];
|
|
95
111
|
}
|
|
96
112
|
export declare function isLoopStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is LoopStepDefinition;
|
|
97
113
|
export declare function isWorkflowStepDefinition(step: WorkflowStepDefinition | LoopStepDefinition): step is WorkflowStepDefinition;
|
|
@@ -34,5 +34,6 @@ function createWorkflowDefinition(definition) {
|
|
|
34
34
|
metaGuidance: definition.metaGuidance ? Object.freeze([...definition.metaGuidance]) : undefined,
|
|
35
35
|
functionDefinitions: definition.functionDefinitions ? Object.freeze([...definition.functionDefinitions]) : undefined,
|
|
36
36
|
extensionPoints: definition.extensionPoints ? Object.freeze([...definition.extensionPoints]) : undefined,
|
|
37
|
+
references: definition.references ? Object.freeze(definition.references.map(ref => Object.freeze({ ...ref }))) : undefined,
|
|
37
38
|
});
|
|
38
39
|
}
|
|
@@ -98,6 +98,12 @@ function evaluateConditionUnsafe(condition, context) {
|
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
if (condition.in !== undefined) {
|
|
102
|
+
if (!Array.isArray(condition.in)) {
|
|
103
|
+
throw new Error('in operator requires an array');
|
|
104
|
+
}
|
|
105
|
+
return condition.in.some(item => lenientEquals(value, item));
|
|
106
|
+
}
|
|
101
107
|
return !!value;
|
|
102
108
|
}
|
|
103
109
|
if (condition.and !== undefined) {
|
|
@@ -124,6 +130,7 @@ function validateCondition(condition) {
|
|
|
124
130
|
const supportedKeys = [
|
|
125
131
|
'var', 'equals', 'not_equals', 'gt', 'gte', 'lt', 'lte',
|
|
126
132
|
'contains', 'startsWith', 'endsWith', 'matches',
|
|
133
|
+
'in',
|
|
127
134
|
'and', 'or', 'not'
|
|
128
135
|
];
|
|
129
136
|
const conditionKeys = Object.keys(condition);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONTEXT_TOKEN_PATTERN = void 0;
|
|
4
|
+
exports.resolveContextTemplates = resolveContextTemplates;
|
|
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
|
+
function resolveDotPath(base, path) {
|
|
8
|
+
let current = base;
|
|
9
|
+
for (const segment of path) {
|
|
10
|
+
if (current === null || typeof current !== 'object')
|
|
11
|
+
return undefined;
|
|
12
|
+
current = current[segment];
|
|
13
|
+
}
|
|
14
|
+
return current;
|
|
15
|
+
}
|
|
16
|
+
function resolveContextTemplates(template, context) {
|
|
17
|
+
if (!template.includes('{{'))
|
|
18
|
+
return template;
|
|
19
|
+
return template.replace(CONTEXT_TOKEN_RE_G, (_match, dotPath) => {
|
|
20
|
+
const value = resolveDotPath(context, dotPath.split('.'));
|
|
21
|
+
if (value === undefined || value === null) {
|
|
22
|
+
return `[unset: ${dotPath}]`;
|
|
23
|
+
}
|
|
24
|
+
return String(value);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Result } from 'neverthrow';
|
|
2
2
|
import type { Workflow } from '../../../types/workflow.js';
|
|
3
|
+
import type { PromptFragment } from '../../../types/workflow-definition.js';
|
|
3
4
|
import type { LoadedSessionTruthV2 } from '../../ports/session-event-log-store.port.js';
|
|
4
5
|
import type { LoopPathFrameV1 } from '../schemas/execution-snapshot/index.js';
|
|
5
6
|
import type { NodeId, RunId } from '../ids/index.js';
|
|
@@ -7,6 +8,7 @@ export type PromptRenderError = {
|
|
|
7
8
|
readonly code: 'RENDER_FAILED';
|
|
8
9
|
readonly message: string;
|
|
9
10
|
};
|
|
11
|
+
export declare function assembleFragmentedPrompt(fragments: readonly PromptFragment[], context: Record<string, unknown>): string;
|
|
10
12
|
export interface StepMetadata {
|
|
11
13
|
readonly stepId: string;
|
|
12
14
|
readonly title: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleFragmentedPrompt = assembleFragmentedPrompt;
|
|
3
4
|
exports.renderPendingPrompt = renderPendingPrompt;
|
|
4
5
|
const neverthrow_1 = require("neverthrow");
|
|
5
6
|
const workflow_js_1 = require("../../../types/workflow.js");
|
|
@@ -11,6 +12,9 @@ const function_definition_expander_js_1 = require("./function-definition-expande
|
|
|
11
12
|
const constants_js_1 = require("../constants.js");
|
|
12
13
|
const validation_requirements_extractor_js_1 = require("./validation-requirements-extractor.js");
|
|
13
14
|
const index_js_2 = require("../schemas/artifacts/index.js");
|
|
15
|
+
const run_context_js_1 = require("../../projections/run-context.js");
|
|
16
|
+
const condition_evaluator_js_1 = require("../../../utils/condition-evaluator.js");
|
|
17
|
+
const context_template_resolver_js_1 = require("./context-template-resolver.js");
|
|
14
18
|
function buildNonTipSections(args) {
|
|
15
19
|
const sections = [];
|
|
16
20
|
const childSummary = (0, recap_recovery_js_1.buildChildSummary)({ nodeId: args.nodeId, dag: args.run });
|
|
@@ -55,6 +59,12 @@ function buildFunctionDefsSections(args) {
|
|
|
55
59
|
}
|
|
56
60
|
return [];
|
|
57
61
|
}
|
|
62
|
+
function hasPriorNotesInRun(args) {
|
|
63
|
+
return args.truth.events.some((e) => e.kind === constants_js_1.EVENT_KIND.NODE_OUTPUT_APPENDED &&
|
|
64
|
+
e.scope.runId === args.runId &&
|
|
65
|
+
e.data.outputChannel === constants_js_1.OUTPUT_CHANNEL.RECAP &&
|
|
66
|
+
e.data.payload.payloadKind === constants_js_1.PAYLOAD_KIND.NOTES);
|
|
67
|
+
}
|
|
58
68
|
function buildRecoverySections(args) {
|
|
59
69
|
const isTip = args.run.tipNodeIds.includes(String(args.nodeId));
|
|
60
70
|
return [
|
|
@@ -114,18 +124,35 @@ function applyPromptBudget(combinedPrompt) {
|
|
|
114
124
|
const decoder = new TextDecoder('utf-8');
|
|
115
125
|
return decoder.decode(truncatedBytes) + markerText + omissionNote;
|
|
116
126
|
}
|
|
117
|
-
function
|
|
127
|
+
function resolveParentLoopStep(workflow, stepId) {
|
|
118
128
|
for (const step of workflow.definition.steps) {
|
|
119
129
|
if ((0, workflow_js_1.isLoopStepDefinition)(step) && Array.isArray(step.body)) {
|
|
120
130
|
for (const bodyStep of step.body) {
|
|
121
|
-
if (bodyStep.id === stepId)
|
|
122
|
-
return step
|
|
123
|
-
}
|
|
131
|
+
if (bodyStep.id === stepId)
|
|
132
|
+
return step;
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
return undefined;
|
|
128
137
|
}
|
|
138
|
+
function buildLoopRenderContext(loopStep, iteration, sessionContext) {
|
|
139
|
+
const iterationVar = loopStep.loop.iterationVar || 'currentIteration';
|
|
140
|
+
const forEachVars = () => {
|
|
141
|
+
if (loopStep.loop.type !== 'forEach' || !loopStep.loop.items)
|
|
142
|
+
return {};
|
|
143
|
+
const items = sessionContext[loopStep.loop.items];
|
|
144
|
+
if (!Array.isArray(items))
|
|
145
|
+
return {};
|
|
146
|
+
return {
|
|
147
|
+
[loopStep.loop.itemVar || 'currentItem']: items[iteration],
|
|
148
|
+
[loopStep.loop.indexVar || 'currentIndex']: iteration,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
[iterationVar]: iteration + 1,
|
|
153
|
+
...forEachVars(),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
129
156
|
function buildScopeInstruction(iteration, maxIterations) {
|
|
130
157
|
if (iteration <= 1)
|
|
131
158
|
return 'Focus on what the first pass missed — do not re-litigate settled findings.';
|
|
@@ -139,6 +166,15 @@ function buildLoopContextBanner(args) {
|
|
|
139
166
|
const current = args.loopPath[args.loopPath.length - 1];
|
|
140
167
|
const iterationNumber = current.iteration + 1;
|
|
141
168
|
const maxIter = args.maxIterations;
|
|
169
|
+
if (args.cleanFormat) {
|
|
170
|
+
if (current.iteration === 0) {
|
|
171
|
+
const bound = maxIter !== undefined ? ` (up to ${maxIter} passes)` : '';
|
|
172
|
+
return `This is an iterative step${bound}. A decision step after your work determines whether another pass is needed.\n\n`;
|
|
173
|
+
}
|
|
174
|
+
const ofMax = maxIter !== undefined ? ` of ${maxIter}` : '';
|
|
175
|
+
const scope = buildScopeInstruction(current.iteration, maxIter);
|
|
176
|
+
return `Pass ${iterationNumber}${ofMax}. ${scope} Build on your previous work.\n\n`;
|
|
177
|
+
}
|
|
142
178
|
if (current.iteration === 0) {
|
|
143
179
|
const bound = maxIter !== undefined ? ` (up to ${maxIter} passes)` : '';
|
|
144
180
|
return [
|
|
@@ -184,6 +220,12 @@ function formatOutputContractRequirements(outputContract) {
|
|
|
184
220
|
];
|
|
185
221
|
}
|
|
186
222
|
}
|
|
223
|
+
function assembleFragmentedPrompt(fragments, context) {
|
|
224
|
+
return fragments
|
|
225
|
+
.filter(f => (0, condition_evaluator_js_1.evaluateCondition)(f.when, context))
|
|
226
|
+
.map(f => (0, context_template_resolver_js_1.resolveContextTemplates)(f.text, context))
|
|
227
|
+
.join('\n\n');
|
|
228
|
+
}
|
|
187
229
|
function loadRecoveryProjections(args) {
|
|
188
230
|
const dagRes = (0, run_dag_js_1.projectRunDagV2)(args.truth.events);
|
|
189
231
|
if (dagRes.isErr()) {
|
|
@@ -208,8 +250,6 @@ function renderPendingPrompt(args) {
|
|
|
208
250
|
message: `Step '${args.stepId}' not found in workflow '${args.workflow.definition.id}'`,
|
|
209
251
|
});
|
|
210
252
|
}
|
|
211
|
-
const baseTitle = step.title;
|
|
212
|
-
const basePrompt = step.prompt;
|
|
213
253
|
const agentRole = step.agentRole;
|
|
214
254
|
const requireConfirmation = Boolean(step.requireConfirmation);
|
|
215
255
|
const functionReferences = step.functionReferences ?? [];
|
|
@@ -217,22 +257,51 @@ function renderPendingPrompt(args) {
|
|
|
217
257
|
? step.outputContract
|
|
218
258
|
: undefined;
|
|
219
259
|
const isExitStep = outputContract?.contractRef === index_js_2.LOOP_CONTROL_CONTRACT_REF;
|
|
220
|
-
const
|
|
221
|
-
const
|
|
260
|
+
const loopStep = resolveParentLoopStep(args.workflow, args.stepId);
|
|
261
|
+
const maxIterations = loopStep?.loop.maxIterations;
|
|
262
|
+
const sessionContext = (0, run_context_js_1.projectRunContextV2)(args.truth.events).match((ok) => (ok.byRunId[String(args.runId)]?.context ?? {}), (e) => {
|
|
263
|
+
console.warn(`[prompt-renderer] Context projection failed for step '${args.stepId}' — ` +
|
|
264
|
+
`{{varName}} tokens will render as [unset:...]: ${e.message}`);
|
|
265
|
+
return {};
|
|
266
|
+
});
|
|
267
|
+
const loopIterationFrame = args.loopPath.at(-1);
|
|
268
|
+
const loopRenderContext = loopStep && loopIterationFrame
|
|
269
|
+
? buildLoopRenderContext(loopStep, loopIterationFrame.iteration, sessionContext)
|
|
270
|
+
: {};
|
|
271
|
+
const renderContext = { ...sessionContext, ...loopRenderContext };
|
|
272
|
+
const basePrompt = (0, context_template_resolver_js_1.resolveContextTemplates)(step.prompt ?? '', renderContext);
|
|
273
|
+
const baseTitle = (0, context_template_resolver_js_1.resolveContextTemplates)(step.title, renderContext);
|
|
274
|
+
const cleanResponseFormat = process.env.WORKRAIL_CLEAN_RESPONSE_FORMAT === 'true';
|
|
275
|
+
const loopBanner = buildLoopContextBanner({ loopPath: args.loopPath, isExitStep, maxIterations, cleanFormat: cleanResponseFormat });
|
|
222
276
|
const validationCriteria = step.validationCriteria;
|
|
223
277
|
const requirements = (0, validation_requirements_extractor_js_1.extractValidationRequirements)(validationCriteria);
|
|
224
278
|
const requirementsSection = requirements.length > 0
|
|
225
|
-
?
|
|
279
|
+
? cleanResponseFormat
|
|
280
|
+
? `\n\n${requirements.map(r => `- ${r}`).join('\n')}`
|
|
281
|
+
: `\n\n**OUTPUT REQUIREMENTS:**\n${requirements.map(r => `- ${r}`).join('\n')}`
|
|
226
282
|
: '';
|
|
227
283
|
const contractRequirements = formatOutputContractRequirements(outputContract);
|
|
228
284
|
const contractSection = contractRequirements.length > 0
|
|
229
|
-
?
|
|
285
|
+
? cleanResponseFormat
|
|
286
|
+
? `\n\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
|
|
287
|
+
: `\n\n**OUTPUT REQUIREMENTS (System):**\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
|
|
230
288
|
: '';
|
|
231
289
|
const isNotesOptional = outputContract !== undefined ||
|
|
232
290
|
('notesOptional' in step && step.notesOptional === true);
|
|
233
|
-
const notesSection =
|
|
234
|
-
|
|
235
|
-
|
|
291
|
+
const notesSection = (() => {
|
|
292
|
+
if (isNotesOptional)
|
|
293
|
+
return '';
|
|
294
|
+
if (cleanResponseFormat) {
|
|
295
|
+
return '';
|
|
296
|
+
}
|
|
297
|
+
const hasPriorNotes = hasPriorNotesInRun({ truth: args.truth, runId: args.runId });
|
|
298
|
+
if (hasPriorNotes && !args.rehydrateOnly) {
|
|
299
|
+
return '\n\n**NOTES REQUIRED (System):** Include `output.notesMarkdown` when advancing.\n\n' +
|
|
300
|
+
'Scope: this step only — WorkRail concatenates notes automatically.\n' +
|
|
301
|
+
'Include: what you did, what you produced, and anything notable.\n' +
|
|
302
|
+
'Be specific. Omitting notes will block this step.';
|
|
303
|
+
}
|
|
304
|
+
return '\n\n**NOTES REQUIRED (System):** You must include `output.notesMarkdown` when advancing. ' +
|
|
236
305
|
'These notes are displayed to the user in a markdown viewer and serve as the durable record of your work. Write them for a human reader.\n\n' +
|
|
237
306
|
'Include:\n' +
|
|
238
307
|
'- **What you did** and the key decisions or trade-offs you made\n' +
|
|
@@ -260,7 +329,15 @@ function renderPendingPrompt(args) {
|
|
|
260
329
|
'> ### Open questions\n' +
|
|
261
330
|
'> - Should refresh tokens be rotated on every use? Current impl reuses until expiry.\n\n' +
|
|
262
331
|
'Omitting notes will block this step — use the `retryAckToken` to fix and retry.';
|
|
263
|
-
|
|
332
|
+
})();
|
|
333
|
+
const promptFragments = 'promptFragments' in step
|
|
334
|
+
? step.promptFragments
|
|
335
|
+
: undefined;
|
|
336
|
+
const fragmentSuffix = promptFragments && promptFragments.length > 0
|
|
337
|
+
? assembleFragmentedPrompt(promptFragments, renderContext)
|
|
338
|
+
: '';
|
|
339
|
+
const enhancedPrompt = loopBanner + basePrompt + requirementsSection + contractSection + notesSection
|
|
340
|
+
+ (fragmentSuffix ? '\n\n' + fragmentSuffix : '');
|
|
264
341
|
if (!args.rehydrateOnly) {
|
|
265
342
|
return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, agentRole, requireConfirmation });
|
|
266
343
|
}
|
|
@@ -288,7 +365,8 @@ function renderPendingPrompt(args) {
|
|
|
288
365
|
if (sections.length === 0) {
|
|
289
366
|
return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, agentRole, requireConfirmation });
|
|
290
367
|
}
|
|
291
|
-
const
|
|
368
|
+
const recoveryHeader = cleanResponseFormat ? 'Your previous work:' : '## Recovery Context';
|
|
369
|
+
const recoveryText = `${recoveryHeader}\n\n${sections.join('\n\n')}`;
|
|
292
370
|
const combinedPrompt = `${enhancedPrompt}\n\n${recoveryText}`;
|
|
293
371
|
const finalPrompt = applyPromptBudget(combinedPrompt);
|
|
294
372
|
return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: finalPrompt, agentRole, requireConfirmation });
|