@exaudeus/workrail 1.14.2 → 1.15.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/validation-engine.js +3 -3
- package/dist/manifest.json +36 -28
- package/dist/mcp/handler-factory.js +10 -1
- package/dist/mcp/handlers/v2-execution-helpers.js +1 -1
- package/dist/mcp/server.js +2 -0
- package/dist/mcp/tool-descriptions.js +30 -52
- package/dist/mcp/v2/tools.js +7 -1
- package/dist/mcp/v2-response-formatter.d.ts +1 -0
- package/dist/mcp/v2-response-formatter.js +200 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +55 -6
- package/dist/v2/durable-core/domain/reason-model.js +1 -1
- package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +4 -4
- package/dist/v2/durable-core/schemas/artifacts/loop-control.js +5 -3
- package/dist/v2/usecases/console-routes.js +10 -0
- package/dist/v2/usecases/console-service.d.ts +8 -3
- package/dist/v2/usecases/console-service.js +421 -23
- package/dist/v2/usecases/console-types.d.ts +48 -0
- package/package.json +1 -1
- package/spec/workflow.schema.json +7 -3
- package/workflows/coding-task-workflow-agentic.json +5 -4
- package/workflows/design-thinking-workflow-autonomous.agentic.json +4 -4
- package/workflows/design-thinking-workflow.json +12 -12
- package/workflows/mr-review-workflow.agentic.json +2 -2
- package/workflows/mr-review-workflow.json +3 -3
- package/workflows/test-artifact-loop-control.json +4 -3
|
@@ -114,6 +114,25 @@ function applyPromptBudget(combinedPrompt) {
|
|
|
114
114
|
const decoder = new TextDecoder('utf-8');
|
|
115
115
|
return decoder.decode(truncatedBytes) + markerText + omissionNote;
|
|
116
116
|
}
|
|
117
|
+
function buildLoopContextBanner(args) {
|
|
118
|
+
if (args.loopPath.length === 0 || args.isExitStep)
|
|
119
|
+
return '';
|
|
120
|
+
const current = args.loopPath[args.loopPath.length - 1];
|
|
121
|
+
const iterationLabel = `Iteration ${current.iteration + 1}`;
|
|
122
|
+
return [
|
|
123
|
+
`---`,
|
|
124
|
+
`**LOOP: ${current.loopId} | ${iterationLabel}** — This step repeats intentionally; the workflow is not stuck or broken.`,
|
|
125
|
+
``,
|
|
126
|
+
`Choose the instruction that matches your current task:`,
|
|
127
|
+
`- **Drafting / updating**: incorporate amendments discovered in previous iterations before writing.`,
|
|
128
|
+
`- **Auditing / reviewing**: look for what previous passes *missed*, not what they already caught.`,
|
|
129
|
+
`- **Applying changes**: follow prior findings precisely; don't re-debate settled decisions.`,
|
|
130
|
+
``,
|
|
131
|
+
`Prior iteration work is visible in the **Ancestry Recap** section below (if present).`,
|
|
132
|
+
`---`,
|
|
133
|
+
``,
|
|
134
|
+
].join('\n');
|
|
135
|
+
}
|
|
117
136
|
function formatOutputContractRequirements(outputContract) {
|
|
118
137
|
const contractRef = outputContract?.contractRef;
|
|
119
138
|
if (!contractRef)
|
|
@@ -123,7 +142,8 @@ function formatOutputContractRequirements(outputContract) {
|
|
|
123
142
|
return [
|
|
124
143
|
`Artifact contract: ${index_js_2.LOOP_CONTROL_CONTRACT_REF}`,
|
|
125
144
|
`Provide an artifact with kind: "wr.loop_control"`,
|
|
126
|
-
`
|
|
145
|
+
`Required field: decision ("continue" | "stop")`,
|
|
146
|
+
`Optional field: loopId — omit unless targeting a specific named loop`,
|
|
127
147
|
];
|
|
128
148
|
default:
|
|
129
149
|
return [
|
|
@@ -154,14 +174,16 @@ function renderPendingPrompt(args) {
|
|
|
154
174
|
const basePrompt = step?.prompt ?? `Pending step: ${args.stepId}`;
|
|
155
175
|
const requireConfirmation = Boolean(step?.requireConfirmation);
|
|
156
176
|
const functionReferences = step?.functionReferences ?? [];
|
|
177
|
+
const outputContract = step && typeof step === 'object' && 'outputContract' in step
|
|
178
|
+
? step.outputContract
|
|
179
|
+
: undefined;
|
|
180
|
+
const isExitStep = outputContract?.contractRef === index_js_2.LOOP_CONTROL_CONTRACT_REF;
|
|
181
|
+
const loopBanner = buildLoopContextBanner({ loopPath: args.loopPath, isExitStep });
|
|
157
182
|
const validationCriteria = step?.validationCriteria;
|
|
158
183
|
const requirements = (0, validation_requirements_extractor_js_1.extractValidationRequirements)(validationCriteria);
|
|
159
184
|
const requirementsSection = requirements.length > 0
|
|
160
185
|
? `\n\n**OUTPUT REQUIREMENTS:**\n${requirements.map(r => `- ${r}`).join('\n')}`
|
|
161
186
|
: '';
|
|
162
|
-
const outputContract = step && typeof step === 'object' && 'outputContract' in step
|
|
163
|
-
? step.outputContract
|
|
164
|
-
: undefined;
|
|
165
187
|
const contractRequirements = formatOutputContractRequirements(outputContract);
|
|
166
188
|
const contractSection = contractRequirements.length > 0
|
|
167
189
|
? `\n\n**OUTPUT REQUIREMENTS (System):**\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
|
|
@@ -170,8 +192,35 @@ function renderPendingPrompt(args) {
|
|
|
170
192
|
(step !== null && step !== undefined && 'notesOptional' in step && step.notesOptional === true);
|
|
171
193
|
const notesSection = isNotesOptional
|
|
172
194
|
? ''
|
|
173
|
-
: '\n\n**NOTES REQUIRED (System):** You must include `output.notesMarkdown`
|
|
174
|
-
|
|
195
|
+
: '\n\n**NOTES REQUIRED (System):** You must include `output.notesMarkdown` when advancing. ' +
|
|
196
|
+
'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' +
|
|
197
|
+
'Include:\n' +
|
|
198
|
+
'- **What you did** and the key decisions or trade-offs you made\n' +
|
|
199
|
+
'- **What you produced** — files changed, functions added, test results, specific numbers\n' +
|
|
200
|
+
'- **Anything notable** — risks, open questions, things you deliberately chose NOT to do and why\n\n' +
|
|
201
|
+
'Formatting: Use markdown headings, bullet lists, `code references`, and **bold** for emphasis. ' +
|
|
202
|
+
'Be specific — file paths, function names, counts, not vague summaries. ' +
|
|
203
|
+
'10–30 lines is ideal. Too short is worse than too long.\n\n' +
|
|
204
|
+
'Scope: THIS step only — WorkRail concatenates notes across steps automatically. Never repeat previous step notes.\n\n' +
|
|
205
|
+
'Example of BAD notes:\n' +
|
|
206
|
+
'> Reviewed the code and found some issues. Made improvements to error handling.\n\n' +
|
|
207
|
+
'Example of GOOD notes:\n' +
|
|
208
|
+
'> ## Review: Authentication Module\n' +
|
|
209
|
+
'> **Files examined:** `src/auth/oauth2.ts`, `src/auth/middleware.ts`, `tests/auth.test.ts`\n' +
|
|
210
|
+
'>\n' +
|
|
211
|
+
'> ### Key findings\n' +
|
|
212
|
+
'> - Token refresh logic in `refreshAccessToken()` silently swallows network errors — changed to propagate as `AuthRefreshError`\n' +
|
|
213
|
+
'> - Added missing `audience` validation in JWT verification (was accepting any audience)\n' +
|
|
214
|
+
'> - **3 Critical**, 2 Major, 4 Minor findings total\n' +
|
|
215
|
+
'>\n' +
|
|
216
|
+
'> ### Decisions\n' +
|
|
217
|
+
'> - Did NOT flag the deprecated `passport` import — it\'s used only in the legacy path scheduled for removal in Q2\n' +
|
|
218
|
+
'> - Recommended extracting token storage into a `TokenStore` interface for testability\n' +
|
|
219
|
+
'>\n' +
|
|
220
|
+
'> ### Open questions\n' +
|
|
221
|
+
'> - Should refresh tokens be rotated on every use? Current impl reuses until expiry.\n\n' +
|
|
222
|
+
'Omitting notes will block this step — use the `retryAckToken` to fix and retry.';
|
|
223
|
+
const enhancedPrompt = loopBanner + basePrompt + requirementsSection + contractSection + notesSection;
|
|
175
224
|
if (!args.rehydrateOnly) {
|
|
176
225
|
return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, requireConfirmation });
|
|
177
226
|
}
|
|
@@ -180,7 +180,7 @@ function reasonToBlocker(reason) {
|
|
|
180
180
|
code: 'MISSING_REQUIRED_NOTES',
|
|
181
181
|
pointer: { kind: 'workflow_step', stepId },
|
|
182
182
|
message: `Step "${stepId}" requires notes documenting your work (output.notesMarkdown).`,
|
|
183
|
-
suggestedFix: 'Add output.notesMarkdown with
|
|
183
|
+
suggestedFix: 'Add output.notesMarkdown with a detailed recap: what you did, key decisions/trade-offs, what you produced (files, paths, numbers), and anything notable. Use markdown formatting. 10–30 lines. Use the retryAckToken to retry without rehydrating.',
|
|
184
184
|
}))
|
|
185
185
|
.andThen(ensureBlockerTextBudgets);
|
|
186
186
|
case 'required_capability_unknown':
|
|
@@ -21,7 +21,7 @@ export declare const LoopControlMetadataV1Schema: z.ZodOptional<z.ZodObject<{
|
|
|
21
21
|
export type LoopControlMetadataV1 = z.infer<typeof LoopControlMetadataV1Schema>;
|
|
22
22
|
export declare const LoopControlArtifactV1Schema: z.ZodObject<{
|
|
23
23
|
kind: z.ZodLiteral<"wr.loop_control">;
|
|
24
|
-
loopId: z.ZodString
|
|
24
|
+
loopId: z.ZodOptional<z.ZodString>;
|
|
25
25
|
decision: z.ZodEnum<["continue", "stop"]>;
|
|
26
26
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
27
27
|
reason: z.ZodOptional<z.ZodString>;
|
|
@@ -41,8 +41,8 @@ export declare const LoopControlArtifactV1Schema: z.ZodObject<{
|
|
|
41
41
|
}>>;
|
|
42
42
|
}, "strict", z.ZodTypeAny, {
|
|
43
43
|
kind: "wr.loop_control";
|
|
44
|
-
loopId: string;
|
|
45
44
|
decision: "continue" | "stop";
|
|
45
|
+
loopId?: string | undefined;
|
|
46
46
|
metadata?: {
|
|
47
47
|
reason?: string | undefined;
|
|
48
48
|
issuesFound?: number | undefined;
|
|
@@ -51,8 +51,8 @@ export declare const LoopControlArtifactV1Schema: z.ZodObject<{
|
|
|
51
51
|
} | undefined;
|
|
52
52
|
}, {
|
|
53
53
|
kind: "wr.loop_control";
|
|
54
|
-
loopId: string;
|
|
55
54
|
decision: "continue" | "stop";
|
|
55
|
+
loopId?: string | undefined;
|
|
56
56
|
metadata?: {
|
|
57
57
|
reason?: string | undefined;
|
|
58
58
|
issuesFound?: number | undefined;
|
|
@@ -63,4 +63,4 @@ export declare const LoopControlArtifactV1Schema: z.ZodObject<{
|
|
|
63
63
|
export type LoopControlArtifactV1 = z.infer<typeof LoopControlArtifactV1Schema>;
|
|
64
64
|
export declare function isLoopControlArtifact(artifact: unknown): artifact is LoopControlArtifactV1;
|
|
65
65
|
export declare function parseLoopControlArtifact(artifact: unknown): LoopControlArtifactV1 | null;
|
|
66
|
-
export declare function findLoopControlArtifact(artifacts: readonly unknown[],
|
|
66
|
+
export declare function findLoopControlArtifact(artifacts: readonly unknown[], expectedLoopId: string): LoopControlArtifactV1 | null;
|
|
@@ -21,7 +21,7 @@ exports.LoopControlMetadataV1Schema = zod_1.z
|
|
|
21
21
|
exports.LoopControlArtifactV1Schema = zod_1.z
|
|
22
22
|
.object({
|
|
23
23
|
kind: zod_1.z.literal('wr.loop_control'),
|
|
24
|
-
loopId: zod_1.z.string().min(1).max(64).regex(constants_js_1.DELIMITER_SAFE_ID_PATTERN, 'loopId must be delimiter-safe: [a-z0-9_-]+'),
|
|
24
|
+
loopId: zod_1.z.string().min(1).max(64).regex(constants_js_1.DELIMITER_SAFE_ID_PATTERN, 'loopId must be delimiter-safe: [a-z0-9_-]+').optional(),
|
|
25
25
|
decision: exports.LoopControlDecisionSchema,
|
|
26
26
|
metadata: exports.LoopControlMetadataV1Schema,
|
|
27
27
|
})
|
|
@@ -35,13 +35,15 @@ function parseLoopControlArtifact(artifact) {
|
|
|
35
35
|
const result = exports.LoopControlArtifactV1Schema.safeParse(artifact);
|
|
36
36
|
return result.success ? result.data : null;
|
|
37
37
|
}
|
|
38
|
-
function findLoopControlArtifact(artifacts,
|
|
38
|
+
function findLoopControlArtifact(artifacts, expectedLoopId) {
|
|
39
39
|
for (let i = artifacts.length - 1; i >= 0; i--) {
|
|
40
40
|
const artifact = artifacts[i];
|
|
41
41
|
if (!isLoopControlArtifact(artifact))
|
|
42
42
|
continue;
|
|
43
43
|
const parsed = parseLoopControlArtifact(artifact);
|
|
44
|
-
if (parsed
|
|
44
|
+
if (!parsed)
|
|
45
|
+
continue;
|
|
46
|
+
if (parsed.loopId === undefined || parsed.loopId === expectedLoopId) {
|
|
45
47
|
return parsed;
|
|
46
48
|
}
|
|
47
49
|
}
|
|
@@ -29,6 +29,16 @@ function mountConsoleRoutes(app, consoleService) {
|
|
|
29
29
|
res.status(status).json({ success: false, error: error.message });
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
+
app.get('/api/v2/sessions/:sessionId/nodes/:nodeId', async (req, res) => {
|
|
33
|
+
const { sessionId, nodeId } = req.params;
|
|
34
|
+
const result = await consoleService.getNodeDetail(sessionId, nodeId);
|
|
35
|
+
result.match((data) => res.json({ success: true, data }), (error) => {
|
|
36
|
+
const status = error.code === 'NODE_NOT_FOUND' ? 404
|
|
37
|
+
: error.code === 'SESSION_LOAD_FAILED' ? 404
|
|
38
|
+
: 500;
|
|
39
|
+
res.status(status).json({ success: false, error: error.message });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
32
42
|
const consoleDist = resolveConsoleDist();
|
|
33
43
|
if (consoleDist) {
|
|
34
44
|
app.use('/console', express_1.default.static(consoleDist));
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ResultAsync } from 'neverthrow';
|
|
2
2
|
import type { DirectoryListingPortV2 } from '../ports/directory-listing.port.js';
|
|
3
3
|
import type { DataDirPortV2 } from '../ports/data-dir.port.js';
|
|
4
4
|
import type { SessionEventLogReadonlyStorePortV2 } from '../ports/session-event-log-store.port.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type { SnapshotStorePortV2 } from '../ports/snapshot-store.port.js';
|
|
6
|
+
import type { PinnedWorkflowStorePortV2 } from '../ports/pinned-workflow-store.port.js';
|
|
7
|
+
import type { ConsoleSessionListResponse, ConsoleSessionDetail, ConsoleNodeDetail } from './console-types.js';
|
|
6
8
|
export interface ConsoleServicePorts {
|
|
7
9
|
readonly directoryListing: DirectoryListingPortV2;
|
|
8
10
|
readonly dataDir: DataDirPortV2;
|
|
9
11
|
readonly sessionStore: SessionEventLogReadonlyStorePortV2;
|
|
12
|
+
readonly snapshotStore: SnapshotStorePortV2;
|
|
13
|
+
readonly pinnedWorkflowStore: PinnedWorkflowStorePortV2;
|
|
10
14
|
}
|
|
11
15
|
export declare class ConsoleService {
|
|
12
16
|
private readonly ports;
|
|
13
17
|
constructor(ports: ConsoleServicePorts);
|
|
14
18
|
getSessionList(): ResultAsync<ConsoleSessionListResponse, ConsoleServiceError>;
|
|
15
19
|
getSessionDetail(sessionIdStr: string): ResultAsync<ConsoleSessionDetail, ConsoleServiceError>;
|
|
20
|
+
getNodeDetail(sessionIdStr: string, nodeId: string): ResultAsync<ConsoleNodeDetail, ConsoleServiceError>;
|
|
16
21
|
private collectSessionSummaries;
|
|
17
22
|
private loadSessionSummary;
|
|
18
23
|
}
|
|
19
24
|
export interface ConsoleServiceError {
|
|
20
|
-
readonly code: 'ENUMERATION_FAILED' | 'SESSION_LOAD_FAILED';
|
|
25
|
+
readonly code: 'ENUMERATION_FAILED' | 'SESSION_LOAD_FAILED' | 'NODE_NOT_FOUND';
|
|
21
26
|
readonly message: string;
|
|
22
27
|
}
|