@exaudeus/workrail 3.24.4 → 3.26.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/cli/commands/index.d.ts +6 -0
- package/dist/cli/commands/index.js +14 -1
- package/dist/cli/commands/version.d.ts +6 -0
- package/dist/cli/commands/version.js +14 -0
- package/dist/cli/commands/worktrain-await.d.ts +35 -0
- package/dist/cli/commands/worktrain-await.js +207 -0
- package/dist/cli/commands/worktrain-inbox.d.ts +23 -0
- package/dist/cli/commands/worktrain-inbox.js +82 -0
- package/dist/cli/commands/worktrain-init.d.ts +23 -0
- package/dist/cli/commands/worktrain-init.js +338 -0
- package/dist/cli/commands/worktrain-spawn.d.ts +28 -0
- package/dist/cli/commands/worktrain-spawn.js +106 -0
- package/dist/cli/commands/worktrain-tell.d.ts +25 -0
- package/dist/cli/commands/worktrain-tell.js +32 -0
- package/dist/cli-worktrain.d.ts +2 -0
- package/dist/cli-worktrain.js +169 -0
- package/dist/cli.js +100 -0
- package/dist/config/config-file.d.ts +2 -0
- package/dist/config/config-file.js +55 -0
- package/dist/console/assets/index-8dh0Psu-.css +1 -0
- package/dist/console/assets/{index-TMfptYpQ.js → index-HhtarvD5.js} +10 -10
- package/dist/console/index.html +2 -2
- package/dist/daemon/agent-loop.d.ts +90 -0
- package/dist/daemon/agent-loop.js +214 -0
- package/dist/daemon/pi-mono-loader.d.ts +0 -0
- package/dist/daemon/pi-mono-loader.js +1 -0
- package/dist/daemon/soul-template.d.ts +2 -0
- package/dist/daemon/soul-template.js +22 -0
- package/dist/daemon/workflow-runner.d.ts +63 -0
- package/dist/daemon/workflow-runner.js +689 -0
- package/dist/infrastructure/session/HttpServer.js +2 -2
- package/dist/manifest.json +226 -50
- package/dist/mcp/handlers/v2-execution/start.d.ts +2 -1
- package/dist/mcp/handlers/v2-execution/start.js +4 -3
- package/dist/mcp/output-schemas.d.ts +154 -154
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/transports/bridge-entry.js +20 -2
- package/dist/mcp/transports/bridge-events.d.ts +34 -0
- package/dist/mcp/transports/bridge-events.js +24 -0
- package/dist/mcp/transports/fatal-exit.d.ts +5 -0
- package/dist/mcp/transports/fatal-exit.js +82 -0
- package/dist/mcp/transports/http-entry.js +3 -0
- package/dist/mcp/transports/stdio-entry.js +3 -7
- package/dist/mcp/v2/tools.d.ts +7 -7
- package/dist/trigger/delivery-action.d.ts +37 -0
- package/dist/trigger/delivery-action.js +204 -0
- package/dist/trigger/delivery-client.d.ts +11 -0
- package/dist/trigger/delivery-client.js +27 -0
- package/dist/trigger/index.d.ts +5 -0
- package/dist/trigger/index.js +8 -0
- package/dist/trigger/trigger-listener.d.ts +32 -0
- package/dist/trigger/trigger-listener.js +176 -0
- package/dist/trigger/trigger-router.d.ts +38 -0
- package/dist/trigger/trigger-router.js +343 -0
- package/dist/trigger/trigger-store.d.ts +39 -0
- package/dist/trigger/trigger-store.js +698 -0
- package/dist/trigger/types.d.ts +70 -0
- package/dist/trigger/types.js +10 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +22 -22
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +114 -114
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +454 -454
- package/dist/v2/durable-core/schemas/session/blockers.d.ts +14 -14
- package/dist/v2/durable-core/schemas/session/events.d.ts +93 -93
- package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +4 -4
- package/dist/v2/infra/in-memory/daemon-registry/index.d.ts +14 -0
- package/dist/v2/infra/in-memory/daemon-registry/index.js +32 -0
- package/dist/v2/infra/in-memory/keyed-async-queue/index.d.ts +5 -0
- package/dist/v2/infra/in-memory/keyed-async-queue/index.js +32 -0
- package/dist/v2/usecases/console-routes.d.ts +3 -1
- package/dist/v2/usecases/console-routes.js +132 -1
- package/dist/v2/usecases/console-service.d.ts +2 -0
- package/dist/v2/usecases/console-service.js +18 -2
- package/dist/v2/usecases/console-types.d.ts +2 -0
- package/package.json +6 -2
- package/spec/workflow-tags.json +1 -0
- package/workflows/classify-task-workflow.json +68 -0
- package/workflows/coding-task-workflow-agentic.lean.v2.json +43 -13
- package/workflows/workflow-for-workflows.json +4 -2
- package/workflows/workflow-for-workflows.v2.json +4 -2
- package/dist/console/assets/index-BXRk3te_.css +0 -1
- package/workflows/rich-object-contribution.json +0 -258
|
@@ -170,7 +170,7 @@ export declare const GapRecordedDataV1Schema: z.ZodObject<{
|
|
|
170
170
|
resolvesGapId: string;
|
|
171
171
|
};
|
|
172
172
|
gapId: string;
|
|
173
|
-
severity: "
|
|
173
|
+
severity: "info" | "warning" | "critical";
|
|
174
174
|
evidenceRefs?: ({
|
|
175
175
|
kind: "event";
|
|
176
176
|
eventId: string;
|
|
@@ -200,7 +200,7 @@ export declare const GapRecordedDataV1Schema: z.ZodObject<{
|
|
|
200
200
|
resolvesGapId: string;
|
|
201
201
|
};
|
|
202
202
|
gapId: string;
|
|
203
|
-
severity: "
|
|
203
|
+
severity: "info" | "warning" | "critical";
|
|
204
204
|
evidenceRefs?: ({
|
|
205
205
|
kind: "event";
|
|
206
206
|
eventId: string;
|
|
@@ -47,22 +47,22 @@ export declare const ValidationPerformedDataV1Schema: z.ZodObject<{
|
|
|
47
47
|
}>;
|
|
48
48
|
}, "strict", z.ZodTypeAny, {
|
|
49
49
|
contractRef: string;
|
|
50
|
-
attemptId: string;
|
|
51
|
-
validationId: string;
|
|
52
50
|
result: {
|
|
53
51
|
issues: readonly string[];
|
|
54
52
|
valid: boolean;
|
|
55
53
|
suggestions: readonly string[];
|
|
56
54
|
};
|
|
57
|
-
}, {
|
|
58
|
-
contractRef: string;
|
|
59
55
|
attemptId: string;
|
|
60
56
|
validationId: string;
|
|
57
|
+
}, {
|
|
58
|
+
contractRef: string;
|
|
61
59
|
result: {
|
|
62
60
|
issues: readonly string[];
|
|
63
61
|
valid: boolean;
|
|
64
62
|
suggestions: readonly string[];
|
|
65
63
|
};
|
|
64
|
+
attemptId: string;
|
|
65
|
+
validationId: string;
|
|
66
66
|
}>;
|
|
67
67
|
export type ValidationPerformedDataV1 = z.infer<typeof ValidationPerformedDataV1Schema>;
|
|
68
68
|
export type ValidationPerformedResultV1 = z.infer<typeof ValidationPerformedResultV1Schema>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DaemonEntry {
|
|
2
|
+
readonly sessionId: string;
|
|
3
|
+
readonly workflowId: string;
|
|
4
|
+
readonly startedAtMs: number;
|
|
5
|
+
readonly lastHeartbeatMs: number;
|
|
6
|
+
readonly status: 'running' | 'completed' | 'failed';
|
|
7
|
+
}
|
|
8
|
+
export declare class DaemonRegistry {
|
|
9
|
+
private readonly entries;
|
|
10
|
+
register(sessionId: string, workflowId: string): void;
|
|
11
|
+
heartbeat(sessionId: string): void;
|
|
12
|
+
unregister(sessionId: string, _status?: 'completed' | 'failed'): void;
|
|
13
|
+
snapshot(): ReadonlyMap<string, DaemonEntry>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DaemonRegistry = void 0;
|
|
4
|
+
class DaemonRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.entries = new Map();
|
|
7
|
+
}
|
|
8
|
+
register(sessionId, workflowId) {
|
|
9
|
+
const nowMs = Date.now();
|
|
10
|
+
const entry = {
|
|
11
|
+
sessionId,
|
|
12
|
+
workflowId,
|
|
13
|
+
startedAtMs: nowMs,
|
|
14
|
+
lastHeartbeatMs: nowMs,
|
|
15
|
+
status: 'running',
|
|
16
|
+
};
|
|
17
|
+
this.entries.set(sessionId, entry);
|
|
18
|
+
}
|
|
19
|
+
heartbeat(sessionId) {
|
|
20
|
+
const existing = this.entries.get(sessionId);
|
|
21
|
+
if (!existing)
|
|
22
|
+
return;
|
|
23
|
+
this.entries.set(sessionId, { ...existing, lastHeartbeatMs: Date.now() });
|
|
24
|
+
}
|
|
25
|
+
unregister(sessionId, _status = 'completed') {
|
|
26
|
+
this.entries.delete(sessionId);
|
|
27
|
+
}
|
|
28
|
+
snapshot() {
|
|
29
|
+
return new Map(this.entries);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.DaemonRegistry = DaemonRegistry;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeyedAsyncQueue = void 0;
|
|
4
|
+
class KeyedAsyncQueue {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.queues = new Map();
|
|
7
|
+
}
|
|
8
|
+
enqueue(key, fn) {
|
|
9
|
+
let resolve;
|
|
10
|
+
let reject;
|
|
11
|
+
const result = new Promise((res, rej) => {
|
|
12
|
+
resolve = res;
|
|
13
|
+
reject = rej;
|
|
14
|
+
});
|
|
15
|
+
const tail = (this.queues.get(key) ?? Promise.resolve())
|
|
16
|
+
.then(() => fn())
|
|
17
|
+
.then(resolve, reject)
|
|
18
|
+
.catch(() => {
|
|
19
|
+
})
|
|
20
|
+
.finally(() => {
|
|
21
|
+
if (this.queues.get(key) === tail) {
|
|
22
|
+
this.queues.delete(key);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
this.queues.set(key, tail);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
get activeKeyCount() {
|
|
29
|
+
return this.queues.size;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.KeyedAsyncQueue = KeyedAsyncQueue;
|
|
@@ -2,4 +2,6 @@ import type { Application } from 'express';
|
|
|
2
2
|
import type { ConsoleService } from './console-service.js';
|
|
3
3
|
import type { WorkflowService } from '../../application/services/workflow-service.js';
|
|
4
4
|
import type { ToolCallTimingRingBuffer } from '../../mcp/tool-call-timing.js';
|
|
5
|
-
|
|
5
|
+
import type { TriggerRouter } from '../../trigger/trigger-router.js';
|
|
6
|
+
import type { V2ToolContext } from '../../mcp/types.js';
|
|
7
|
+
export declare function mountConsoleRoutes(app: Application, consoleService: ConsoleService, workflowService?: WorkflowService, timingRingBuffer?: ToolCallTimingRingBuffer, toolCallsPerfFile?: string, serverVersion?: string, v2ToolContext?: V2ToolContext, triggerRouter?: TriggerRouter): () => void;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -10,6 +43,9 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
10
43
|
const worktree_service_js_1 = require("./worktree-service.js");
|
|
11
44
|
const workflow_js_1 = require("../../types/workflow.js");
|
|
12
45
|
const dev_mode_js_1 = require("../../mcp/dev-mode.js");
|
|
46
|
+
const workflow_runner_js_1 = require("../../daemon/workflow-runner.js");
|
|
47
|
+
const start_js_1 = require("../../mcp/handlers/v2-execution/start.js");
|
|
48
|
+
const v2_token_ops_js_1 = require("../../mcp/handlers/v2-token-ops.js");
|
|
13
49
|
function watchSessionsDir(sessionsDir, onChanged) {
|
|
14
50
|
try {
|
|
15
51
|
fs_1.default.mkdirSync(sessionsDir, { recursive: true });
|
|
@@ -53,7 +89,7 @@ function loadWorkflowTags() {
|
|
|
53
89
|
return { version: 0, tags: [], workflows: {} };
|
|
54
90
|
}
|
|
55
91
|
}
|
|
56
|
-
function mountConsoleRoutes(app, consoleService, workflowService, timingRingBuffer, toolCallsPerfFile, serverVersion) {
|
|
92
|
+
function mountConsoleRoutes(app, consoleService, workflowService, timingRingBuffer, toolCallsPerfFile, serverVersion, v2ToolContext, triggerRouter) {
|
|
57
93
|
const sseClients = new Set();
|
|
58
94
|
let sseDebounceTimer = null;
|
|
59
95
|
function broadcastChange() {
|
|
@@ -312,6 +348,101 @@ function mountConsoleRoutes(app, consoleService, workflowService, timingRingBuff
|
|
|
312
348
|
}
|
|
313
349
|
});
|
|
314
350
|
}
|
|
351
|
+
app.post('/api/v2/auto/dispatch', express_1.default.json(), async (req, res) => {
|
|
352
|
+
if (!v2ToolContext) {
|
|
353
|
+
res.status(503).json({ success: false, error: 'Autonomous dispatch requires v2 tools enabled.' });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const body = req.body;
|
|
357
|
+
const workflowId = typeof body.workflowId === 'string' ? body.workflowId.trim() : '';
|
|
358
|
+
const goal = typeof body.goal === 'string' ? body.goal.trim() : '';
|
|
359
|
+
const workspacePath = typeof body.workspacePath === 'string' ? body.workspacePath.trim() : '';
|
|
360
|
+
if (!workflowId || !goal || !workspacePath) {
|
|
361
|
+
res.status(400).json({ success: false, error: 'workflowId, goal, and workspacePath are required.' });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const nodePath = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
365
|
+
const nodeFs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
366
|
+
if (!nodePath.isAbsolute(workspacePath)) {
|
|
367
|
+
res.status(400).json({ success: false, error: 'workspacePath must be an absolute path.' });
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const stat = await nodeFs.stat(workspacePath);
|
|
372
|
+
if (!stat.isDirectory()) {
|
|
373
|
+
res.status(400).json({ success: false, error: 'workspacePath must be an existing directory.' });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
res.status(400).json({ success: false, error: `workspacePath does not exist: ${workspacePath}` });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const context = body.context && typeof body.context === 'object' && !Array.isArray(body.context)
|
|
382
|
+
? body.context
|
|
383
|
+
: undefined;
|
|
384
|
+
const apiKey = process.env['ANTHROPIC_API_KEY'];
|
|
385
|
+
if (!apiKey && !process.env['AWS_PROFILE'] && !process.env['AWS_ACCESS_KEY_ID']) {
|
|
386
|
+
res.status(503).json({ success: false, error: 'No LLM credentials available. Set ANTHROPIC_API_KEY or AWS_PROFILE.' });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const startResult = await (0, start_js_1.executeStartWorkflow)({ workflowId, workspacePath, goal }, v2ToolContext, { is_autonomous: 'true' });
|
|
390
|
+
if (startResult.isErr()) {
|
|
391
|
+
const errDetail = `${startResult.error.kind}${'message' in startResult.error ? `: ${startResult.error.message}` : ''}`;
|
|
392
|
+
res.status(400).json({ success: false, error: `Session creation failed: ${errDetail}` });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const startResponse = startResult.value.response;
|
|
396
|
+
const startContinueToken = startResponse.continueToken;
|
|
397
|
+
let sessionHandle;
|
|
398
|
+
if (startContinueToken) {
|
|
399
|
+
const tokenResult = await (0, v2_token_ops_js_1.parseContinueTokenOrFail)(startContinueToken, v2ToolContext.v2.tokenCodecPorts, v2ToolContext.v2.tokenAliasStore);
|
|
400
|
+
if (tokenResult.isErr()) {
|
|
401
|
+
console.error(`[ConsoleRoutes] Failed to decode session handle from continueToken: ${tokenResult.error.message}`);
|
|
402
|
+
res.status(500).json({ success: false, error: 'Internal error: could not extract session handle.' });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
sessionHandle = tokenResult.value.sessionId;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
sessionHandle = workflowId;
|
|
409
|
+
}
|
|
410
|
+
const trigger = { workflowId, goal, workspacePath, context, _preAllocatedStartResponse: startResponse };
|
|
411
|
+
if (triggerRouter) {
|
|
412
|
+
triggerRouter.dispatch(trigger);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
void (0, workflow_runner_js_1.runWorkflow)(trigger, v2ToolContext, apiKey ?? '').then((result) => {
|
|
416
|
+
if (result._tag === 'success') {
|
|
417
|
+
console.log(`[ConsoleRoutes] Auto dispatch completed: workflowId=${workflowId} stopReason=${result.stopReason}`);
|
|
418
|
+
}
|
|
419
|
+
else if (result._tag === 'timeout') {
|
|
420
|
+
console.log(`[ConsoleRoutes] Auto dispatch timed out: workflowId=${workflowId}`);
|
|
421
|
+
}
|
|
422
|
+
else if (result._tag === 'delivery_failed') {
|
|
423
|
+
console.log(`[ConsoleRoutes] Auto dispatch delivery failed: workflowId=${workflowId}`);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log(`[ConsoleRoutes] Auto dispatch failed: workflowId=${workflowId} error=${result.message}`);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
res.json({ success: true, data: { status: 'dispatched', workflowId, sessionHandle } });
|
|
431
|
+
});
|
|
432
|
+
app.get('/api/v2/triggers', (_req, res) => {
|
|
433
|
+
if (!triggerRouter) {
|
|
434
|
+
res.json({ success: true, data: { triggers: [] } });
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const triggers = triggerRouter.listTriggers().map((t) => ({
|
|
438
|
+
id: t.id,
|
|
439
|
+
provider: t.provider,
|
|
440
|
+
workflowId: t.workflowId,
|
|
441
|
+
workspacePath: t.workspacePath,
|
|
442
|
+
goal: t.goal,
|
|
443
|
+
}));
|
|
444
|
+
res.json({ success: true, data: { triggers } });
|
|
445
|
+
});
|
|
315
446
|
const consoleDist = resolveConsoleDist();
|
|
316
447
|
if (consoleDist) {
|
|
317
448
|
app.use('/console', express_1.default.static(consoleDist, {
|
|
@@ -4,6 +4,7 @@ import type { DataDirPortV2 } from '../ports/data-dir.port.js';
|
|
|
4
4
|
import type { SessionEventLogReadonlyStorePortV2 } from '../ports/session-event-log-store.port.js';
|
|
5
5
|
import type { SnapshotStorePortV2 } from '../ports/snapshot-store.port.js';
|
|
6
6
|
import type { PinnedWorkflowStorePortV2 } from '../ports/pinned-workflow-store.port.js';
|
|
7
|
+
import type { DaemonRegistry } from '../infra/in-memory/daemon-registry/index.js';
|
|
7
8
|
import type { ConsoleSessionListResponse, ConsoleSessionDetail, ConsoleNodeDetail } from './console-types.js';
|
|
8
9
|
export interface ConsoleServicePorts {
|
|
9
10
|
readonly directoryListing: DirectoryListingPortV2;
|
|
@@ -11,6 +12,7 @@ export interface ConsoleServicePorts {
|
|
|
11
12
|
readonly sessionStore: SessionEventLogReadonlyStorePortV2;
|
|
12
13
|
readonly snapshotStore: SnapshotStorePortV2;
|
|
13
14
|
readonly pinnedWorkflowStore: PinnedWorkflowStorePortV2;
|
|
15
|
+
readonly daemonRegistry?: DaemonRegistry;
|
|
14
16
|
}
|
|
15
17
|
export declare class ConsoleService {
|
|
16
18
|
private readonly ports;
|
|
@@ -20,6 +20,7 @@ const DORMANCY_THRESHOLD_MS = (() => {
|
|
|
20
20
|
const override = parseInt(process.env['WORKRAIL_DORMANCY_THRESHOLD_MS'] ?? '', 10);
|
|
21
21
|
return Number.isFinite(override) && override > 0 ? override : 60 * 60 * 1000;
|
|
22
22
|
})();
|
|
23
|
+
const AUTONOMOUS_HEARTBEAT_THRESHOLD_MS = 10 * 60 * 1000;
|
|
23
24
|
class ConsoleService {
|
|
24
25
|
constructor(ports) {
|
|
25
26
|
this.ports = ports;
|
|
@@ -123,12 +124,15 @@ class ConsoleService {
|
|
|
123
124
|
const completionRA = dagRes.isOk()
|
|
124
125
|
? resolveRunCompletionFromDag(dagRes.value, this.ports.snapshotStore)
|
|
125
126
|
: (0, neverthrow_2.okAsync)({});
|
|
127
|
+
const registryEntry = this.ports.daemonRegistry?.snapshot().get(sessionId);
|
|
128
|
+
const isLive = registryEntry !== undefined
|
|
129
|
+
&& (nowMs - registryEntry.lastHeartbeatMs) < AUTONOMOUS_HEARTBEAT_THRESHOLD_MS;
|
|
126
130
|
return neverthrow_1.ResultAsync.combine([
|
|
127
131
|
completionRA,
|
|
128
132
|
workflowNamesRA,
|
|
129
133
|
]).map(([completionMap, workflowNames]) => {
|
|
130
134
|
const dag = dagRes.isOk() ? dagRes.value : undefined;
|
|
131
|
-
return projectSessionSummary(sessionId, truth, completionMap, workflowNames, lastModifiedMs, nowMs, dag);
|
|
135
|
+
return projectSessionSummary(sessionId, truth, completionMap, workflowNames, lastModifiedMs, nowMs, dag, isLive);
|
|
132
136
|
});
|
|
133
137
|
})
|
|
134
138
|
.map((summary) => {
|
|
@@ -414,7 +418,7 @@ function truncateTitle(text, maxLen = 120) {
|
|
|
414
418
|
return text;
|
|
415
419
|
return text.slice(0, maxLen - 1) + '…';
|
|
416
420
|
}
|
|
417
|
-
function projectSessionSummary(sessionId, truth, completionByRunId, workflowNames, lastModifiedMs, nowMs, precomputedDag) {
|
|
421
|
+
function projectSessionSummary(sessionId, truth, completionByRunId, workflowNames, lastModifiedMs, nowMs, precomputedDag, isLive = false) {
|
|
418
422
|
const { events } = truth;
|
|
419
423
|
const health = (0, session_health_js_1.projectSessionHealthV2)(truth);
|
|
420
424
|
if (health.isErr())
|
|
@@ -435,6 +439,14 @@ function projectSessionSummary(sessionId, truth, completionByRunId, workflowName
|
|
|
435
439
|
const gapsRes = sortedEventsRes.isOk() ? (0, gaps_js_1.projectGapsV2)(sortedEventsRes.value) : (0, neverthrow_2.err)(sortedEventsRes.error);
|
|
436
440
|
const sessionTitle = sortedEventsRes.isOk() ? deriveSessionTitle(sortedEventsRes.value) : null;
|
|
437
441
|
const gitBranch = extractGitBranch(events);
|
|
442
|
+
const isAutonomous = (() => {
|
|
443
|
+
if (!sortedEventsRes.isOk())
|
|
444
|
+
return false;
|
|
445
|
+
const contextRes = (0, run_context_js_1.projectRunContextV2)(sortedEventsRes.value);
|
|
446
|
+
if (contextRes.isErr())
|
|
447
|
+
return false;
|
|
448
|
+
return Object.values(contextRes.value.byRunId).some((runCtx) => runCtx.context['is_autonomous'] === 'true');
|
|
449
|
+
})();
|
|
438
450
|
const runs = Object.values(dag.runsById);
|
|
439
451
|
const run = runs[0];
|
|
440
452
|
if (!run) {
|
|
@@ -455,6 +467,8 @@ function projectSessionSummary(sessionId, truth, completionByRunId, workflowName
|
|
|
455
467
|
recapSnippet: null,
|
|
456
468
|
gitBranch,
|
|
457
469
|
lastModifiedMs,
|
|
470
|
+
isAutonomous,
|
|
471
|
+
isLive,
|
|
458
472
|
};
|
|
459
473
|
}
|
|
460
474
|
const workflow = run.workflow;
|
|
@@ -497,6 +511,8 @@ function projectSessionSummary(sessionId, truth, completionByRunId, workflowName
|
|
|
497
511
|
recapSnippet,
|
|
498
512
|
gitBranch,
|
|
499
513
|
lastModifiedMs,
|
|
514
|
+
isAutonomous,
|
|
515
|
+
isLive,
|
|
500
516
|
};
|
|
501
517
|
}
|
|
502
518
|
function projectSessionDetail(sessionId, truth, completionByRunId, stepLabels, workflowNames, skippedStepsMap = {}) {
|
|
@@ -17,6 +17,8 @@ export interface ConsoleSessionSummary {
|
|
|
17
17
|
readonly recapSnippet: string | null;
|
|
18
18
|
readonly gitBranch: string | null;
|
|
19
19
|
readonly lastModifiedMs: number;
|
|
20
|
+
readonly isAutonomous: boolean;
|
|
21
|
+
readonly isLive: boolean;
|
|
20
22
|
}
|
|
21
23
|
export interface ConsoleSessionListResponse {
|
|
22
24
|
readonly sessions: readonly ConsoleSessionSummary[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exaudeus/workrail",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.0",
|
|
4
4
|
"description": "Step-by-step workflow enforcement for AI agents via MCP",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"homepage": "https://github.com/EtienneBBeaulac/workrail#readme",
|
|
14
14
|
"bin": {
|
|
15
|
-
"workrail": "dist/mcp-server.js"
|
|
15
|
+
"workrail": "dist/mcp-server.js",
|
|
16
|
+
"worktrain": "dist/cli-worktrain.js"
|
|
16
17
|
},
|
|
17
18
|
"exports": {
|
|
18
19
|
".": "./dist/mcp-server.js",
|
|
@@ -84,6 +85,8 @@
|
|
|
84
85
|
"prepare": "bash scripts/setup-hooks.sh"
|
|
85
86
|
},
|
|
86
87
|
"dependencies": {
|
|
88
|
+
"@anthropic-ai/bedrock-sdk": "^0.28.1",
|
|
89
|
+
"@anthropic-ai/sdk": "^0.73.0",
|
|
87
90
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
88
91
|
"@scure/base": "1.1.9",
|
|
89
92
|
"ajv": "^8.17.1",
|
|
@@ -110,6 +113,7 @@
|
|
|
110
113
|
"task-management"
|
|
111
114
|
],
|
|
112
115
|
"devDependencies": {
|
|
116
|
+
"@duckdb/node-api": "^1.5.2-r.1",
|
|
113
117
|
"@playwright/test": "^1.55.1",
|
|
114
118
|
"@semantic-release/changelog": "^6.0.3",
|
|
115
119
|
"@semantic-release/exec": "^7.1.0",
|
package/spec/workflow-tags.json
CHANGED
|
@@ -125,6 +125,7 @@
|
|
|
125
125
|
"workflow-diagnose-environment": { "tags": ["investigation"] },
|
|
126
126
|
"workflow-for-workflows": { "tags": ["authoring"] },
|
|
127
127
|
"wr.discovery": { "tags": ["design", "investigation"] },
|
|
128
|
+
"classify-task-workflow": { "tags": ["routines", "coding"] },
|
|
128
129
|
"test-artifact-loop-control": { "tags": ["coding"], "hidden": true },
|
|
129
130
|
"test-session-persistence": { "tags": ["coding"], "hidden": true },
|
|
130
131
|
"test-missing-context": { "tags": ["coding"], "hidden": true }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "classify-task-workflow",
|
|
3
|
+
"name": "Classify Task",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Classifies a software task from the session goal into structured output variables used by coordinator scripts to decide which pipeline phases to run.",
|
|
6
|
+
"about": "## Classify Task Workflow\n\nThis is a fast, single-step classification utility. It reads the session goal and outputs structured variables that coordinator scripts use to decide which pipeline phases to run.\n\n### What it does\n\nGiven a task description, the agent classifies the work along seven dimensions and recommends an ordered pipeline of workflow IDs to execute.\n\n### When to use it\n\nUse this workflow at the start of a coordinator pipeline when you need to decide which downstream workflows to run. It is intentionally fast and cheap -- one LLM step, no subagents, no codebase reads.\n\n### What it produces\n\nA structured classification block in the step notes containing all seven output variables:\n- `taskComplexity` -- Small / Medium / Large\n- `riskLevel` -- Low / Medium / High\n- `hasUI` -- true / false\n- `touchesArchitecture` -- true / false\n- `taskType` -- feature / bug-fix / refactor / investigation / docs / chore\n- `affectedDomains` -- array of likely codebase areas\n- `recommendedPipeline` -- ordered array of workflow IDs\n\n### How to get good results\n\nProvide a specific, concrete task description as the session goal. The more specific the goal, the more accurate the classification. When the goal is ambiguous, the workflow defaults to conservative (higher complexity, more pipeline phases).",
|
|
7
|
+
"examples": [
|
|
8
|
+
"Classify: add real-time presence indicators to the messaging inbox UI",
|
|
9
|
+
"Classify: fix the race condition in the cache invalidation path",
|
|
10
|
+
"Classify: refactor the payment module to use Result types instead of exceptions",
|
|
11
|
+
"Classify: investigate why the build server fails on the integration test suite",
|
|
12
|
+
"Classify: update the onboarding docs to reflect the new CLI commands"
|
|
13
|
+
],
|
|
14
|
+
"validatedAgainstSpecVersion": 3,
|
|
15
|
+
"recommendedPreferences": {
|
|
16
|
+
"recommendedAutonomy": "full_auto_never_stop",
|
|
17
|
+
"recommendedRiskPolicy": "aggressive"
|
|
18
|
+
},
|
|
19
|
+
"metaGuidance": [
|
|
20
|
+
"CONSERVATIVE DEFAULT: when in doubt, classify up -- higher complexity, more pipeline phases. It is safer to recommend an extra phase than to skip a critical one.",
|
|
21
|
+
"STRUCTURED OUTPUT REQUIRED: output must be a labeled structured block. Free-form prose does not satisfy this workflow's contract.",
|
|
22
|
+
"NO TOOLS NEEDED: classify from the goal text alone. Do not read files, run commands, or gather context. This workflow is intentionally cheap.",
|
|
23
|
+
"NO SUBAGENTS: do not delegate. The classification is a single reasoning pass by the main agent.",
|
|
24
|
+
"PIPELINE SELECTION LOGIC: use the provided decision rules in the step procedure. Do not invent new pipeline logic."
|
|
25
|
+
],
|
|
26
|
+
"steps": [
|
|
27
|
+
{
|
|
28
|
+
"id": "classify-task",
|
|
29
|
+
"title": "Classify Task",
|
|
30
|
+
"promptBlocks": {
|
|
31
|
+
"goal": "Read the session goal and classify the software task into all seven required output variables. Produce a structured classification block that a coordinator script can parse reliably.",
|
|
32
|
+
"constraints": [
|
|
33
|
+
"Classify from goal text only. Do not read files, search the codebase, or gather context.",
|
|
34
|
+
"When any dimension is ambiguous, default to the more conservative value (higher complexity, higher risk, more pipeline phases).",
|
|
35
|
+
"All seven output variables are required. An empty or missing variable is a failure.",
|
|
36
|
+
"Output the classification block in the exact format shown in the procedure. Use the exact key names.",
|
|
37
|
+
"Output `affectedDomains` and `recommendedPipeline` as single-line JSON arrays -- no line breaks within the array value."
|
|
38
|
+
],
|
|
39
|
+
"procedure": [
|
|
40
|
+
"1. Read the session goal carefully.",
|
|
41
|
+
"2. Classify each dimension using these rules:\n\n **taskComplexity** (Small / Medium / Large)\n - Small: isolated change, one component, clear path, low ambiguity (e.g. fix a typo, rename a symbol, update a config value)\n - Medium: touches 2-4 components, moderate scope, some design decisions needed\n - Large: cross-cutting change, architectural impact, high ambiguity, or significant new behavior\n - Default: Medium when unsure\n\n **riskLevel** (Low / Medium / High)\n - Low: no user-visible behavior change, reversible, isolated\n - Medium: user-visible change, moderate blast radius, or touches shared infrastructure\n - High: data migration, payment/auth paths, production-critical infrastructure, irreversible changes\n - Default: Medium when unsure\n\n **hasUI** (true / false)\n - true: task mentions UI, frontend, visual, screen, component, design, UX, CSS, layout, accessibility, animation, or any user-facing interface\n - false: otherwise\n - Default: false when unsure\n\n **touchesArchitecture** (true / false)\n - true: task introduces new abstractions, changes system boundaries, affects how components interact, changes APIs, or modifies data models in a non-trivial way\n - false: otherwise (e.g. bug fix in existing logic, docs update, minor refactor within a component)\n - Default: false when unsure\n - Rule: Large tasks always set `touchesArchitecture: true` -- large scope changes affect system structure by definition\n\n **taskType** (feature / bug-fix / refactor / investigation / docs / chore)\n - feature: new user-visible functionality\n - bug-fix: fixing incorrect behavior\n - refactor: restructuring existing code without changing observable behavior\n - investigation: diagnosing or understanding a problem without implementing a fix\n - docs: documentation changes only\n - chore: tooling, CI, deps, build, internal cleanup with no user impact\n - Default: feature when unsure\n\n **affectedDomains** (array)\n - Scan the goal text for known domain keywords and infer likely areas. Use these domain labels: daemon, trigger, console, mcp, schema, engine, workflows, docs, infra, api, auth, payments, mobile, web\n - Include a domain if the goal text strongly implies it, even if not named explicitly\n - Use an empty array only if no domains can be reasonably inferred\n\n **recommendedPipeline** (array of workflow IDs, in execution order)\n Apply these selection rules in order:\n - If taskType = 'bug-fix': prepend 'bug-investigation.agentic.v2'\n - If taskComplexity is Medium or Large: include 'wr.discovery'\n - If hasUI = true: include 'ui-ux-design-workflow'\n - If touchesArchitecture = true OR riskLevel = High: include 'architecture-scalability-audit'\n - If taskType is NOT 'investigation' AND taskType is NOT 'docs': include 'coding-task-workflow-agentic' -- note: chore is included here because chores can require code changes and benefit from review\n - If 'coding-task-workflow-agentic' is included: include 'mr-review-workflow.agentic.v2' after it\n - If riskLevel = High: append 'production-readiness-audit' at the end\n - Order: [bug-investigation?, wr.discovery?, ui-ux-design-workflow?, architecture-scalability-audit?, coding-task-workflow-agentic?, mr-review-workflow.agentic.v2?, production-readiness-audit?]",
|
|
42
|
+
"3. Output the classification block using this exact format:\n\n```\n## Task Classification\n\ntaskComplexity: <Small|Medium|Large>\nriskLevel: <Low|Medium|High>\nhasUI: <true|false>\ntouchesArchitecture: <true|false>\ntaskType: <feature|bug-fix|refactor|investigation|docs|chore>\naffectedDomains: [\"<domain1>\", \"<domain2>\"]\nrecommendedPipeline: [\"<workflow-id-1>\", \"<workflow-id-2>\"]\n```\n\nAfter the block, add 2-4 sentences of reasoning: why you chose the key values, and what made anything uncertain."
|
|
43
|
+
],
|
|
44
|
+
"outputRequired": {
|
|
45
|
+
"taskComplexity": "One of: Small / Medium / Large",
|
|
46
|
+
"riskLevel": "One of: Low / Medium / High",
|
|
47
|
+
"hasUI": "true or false -- does this task touch any UI, frontend, or visual layer?",
|
|
48
|
+
"touchesArchitecture": "true or false -- does this introduce new abstractions, change system boundaries, or affect how components interact?",
|
|
49
|
+
"taskType": "One of: feature / bug-fix / refactor / investigation / docs / chore",
|
|
50
|
+
"affectedDomains": "Array of likely codebase areas (e.g. [\"daemon\", \"console\"]). Use [] only if none can be inferred.",
|
|
51
|
+
"recommendedPipeline": "Ordered array of workflow IDs selected using the pipeline selection rules in the procedure."
|
|
52
|
+
},
|
|
53
|
+
"verify": [
|
|
54
|
+
"All seven output variables are present in the classification block.",
|
|
55
|
+
"Each variable uses the exact key name specified (taskComplexity, riskLevel, hasUI, touchesArchitecture, taskType, affectedDomains, recommendedPipeline).",
|
|
56
|
+
"recommendedPipeline contains only valid workflow IDs from the available library.",
|
|
57
|
+
"If taskType is feature, bug-fix, or refactor, recommendedPipeline includes coding-task-workflow-agentic.",
|
|
58
|
+
"If riskLevel is High, production-readiness-audit is the last item in recommendedPipeline.",
|
|
59
|
+
"If `hasUI` is true, `recommendedPipeline` includes `ui-ux-design-workflow`.",
|
|
60
|
+
"If `touchesArchitecture` is true OR `riskLevel` is High, `recommendedPipeline` includes `architecture-scalability-audit`.",
|
|
61
|
+
"If `taskType` is `bug-fix`, `bug-investigation.agentic.v2` is the first item in `recommendedPipeline`.",
|
|
62
|
+
"If `coding-task-workflow-agentic` is present in `recommendedPipeline`, `mr-review-workflow.agentic.v2` immediately follows it.",
|
|
63
|
+
"If `taskComplexity` is `Large`, `touchesArchitecture` must be `true`."
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|