@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.
Files changed (82) hide show
  1. package/dist/cli/commands/index.d.ts +6 -0
  2. package/dist/cli/commands/index.js +14 -1
  3. package/dist/cli/commands/version.d.ts +6 -0
  4. package/dist/cli/commands/version.js +14 -0
  5. package/dist/cli/commands/worktrain-await.d.ts +35 -0
  6. package/dist/cli/commands/worktrain-await.js +207 -0
  7. package/dist/cli/commands/worktrain-inbox.d.ts +23 -0
  8. package/dist/cli/commands/worktrain-inbox.js +82 -0
  9. package/dist/cli/commands/worktrain-init.d.ts +23 -0
  10. package/dist/cli/commands/worktrain-init.js +338 -0
  11. package/dist/cli/commands/worktrain-spawn.d.ts +28 -0
  12. package/dist/cli/commands/worktrain-spawn.js +106 -0
  13. package/dist/cli/commands/worktrain-tell.d.ts +25 -0
  14. package/dist/cli/commands/worktrain-tell.js +32 -0
  15. package/dist/cli-worktrain.d.ts +2 -0
  16. package/dist/cli-worktrain.js +169 -0
  17. package/dist/cli.js +100 -0
  18. package/dist/config/config-file.d.ts +2 -0
  19. package/dist/config/config-file.js +55 -0
  20. package/dist/console/assets/index-8dh0Psu-.css +1 -0
  21. package/dist/console/assets/{index-TMfptYpQ.js → index-HhtarvD5.js} +10 -10
  22. package/dist/console/index.html +2 -2
  23. package/dist/daemon/agent-loop.d.ts +90 -0
  24. package/dist/daemon/agent-loop.js +214 -0
  25. package/dist/daemon/pi-mono-loader.d.ts +0 -0
  26. package/dist/daemon/pi-mono-loader.js +1 -0
  27. package/dist/daemon/soul-template.d.ts +2 -0
  28. package/dist/daemon/soul-template.js +22 -0
  29. package/dist/daemon/workflow-runner.d.ts +63 -0
  30. package/dist/daemon/workflow-runner.js +689 -0
  31. package/dist/infrastructure/session/HttpServer.js +2 -2
  32. package/dist/manifest.json +226 -50
  33. package/dist/mcp/handlers/v2-execution/start.d.ts +2 -1
  34. package/dist/mcp/handlers/v2-execution/start.js +4 -3
  35. package/dist/mcp/output-schemas.d.ts +154 -154
  36. package/dist/mcp/server.js +1 -1
  37. package/dist/mcp/transports/bridge-entry.js +20 -2
  38. package/dist/mcp/transports/bridge-events.d.ts +34 -0
  39. package/dist/mcp/transports/bridge-events.js +24 -0
  40. package/dist/mcp/transports/fatal-exit.d.ts +5 -0
  41. package/dist/mcp/transports/fatal-exit.js +82 -0
  42. package/dist/mcp/transports/http-entry.js +3 -0
  43. package/dist/mcp/transports/stdio-entry.js +3 -7
  44. package/dist/mcp/v2/tools.d.ts +7 -7
  45. package/dist/trigger/delivery-action.d.ts +37 -0
  46. package/dist/trigger/delivery-action.js +204 -0
  47. package/dist/trigger/delivery-client.d.ts +11 -0
  48. package/dist/trigger/delivery-client.js +27 -0
  49. package/dist/trigger/index.d.ts +5 -0
  50. package/dist/trigger/index.js +8 -0
  51. package/dist/trigger/trigger-listener.d.ts +32 -0
  52. package/dist/trigger/trigger-listener.js +176 -0
  53. package/dist/trigger/trigger-router.d.ts +38 -0
  54. package/dist/trigger/trigger-router.js +343 -0
  55. package/dist/trigger/trigger-store.d.ts +39 -0
  56. package/dist/trigger/trigger-store.js +698 -0
  57. package/dist/trigger/types.d.ts +70 -0
  58. package/dist/trigger/types.js +10 -0
  59. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +22 -22
  60. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +114 -114
  61. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +454 -454
  62. package/dist/v2/durable-core/schemas/session/blockers.d.ts +14 -14
  63. package/dist/v2/durable-core/schemas/session/events.d.ts +93 -93
  64. package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
  65. package/dist/v2/durable-core/schemas/session/validation-event.d.ts +4 -4
  66. package/dist/v2/infra/in-memory/daemon-registry/index.d.ts +14 -0
  67. package/dist/v2/infra/in-memory/daemon-registry/index.js +32 -0
  68. package/dist/v2/infra/in-memory/keyed-async-queue/index.d.ts +5 -0
  69. package/dist/v2/infra/in-memory/keyed-async-queue/index.js +32 -0
  70. package/dist/v2/usecases/console-routes.d.ts +3 -1
  71. package/dist/v2/usecases/console-routes.js +132 -1
  72. package/dist/v2/usecases/console-service.d.ts +2 -0
  73. package/dist/v2/usecases/console-service.js +18 -2
  74. package/dist/v2/usecases/console-types.d.ts +2 -0
  75. package/package.json +6 -2
  76. package/spec/workflow-tags.json +1 -0
  77. package/workflows/classify-task-workflow.json +68 -0
  78. package/workflows/coding-task-workflow-agentic.lean.v2.json +43 -13
  79. package/workflows/workflow-for-workflows.json +4 -2
  80. package/workflows/workflow-for-workflows.v2.json +4 -2
  81. package/dist/console/assets/index-BXRk3te_.css +0 -1
  82. 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: "warning" | "info" | "critical";
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: "warning" | "info" | "critical";
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,5 @@
1
+ export declare class KeyedAsyncQueue {
2
+ private readonly queues;
3
+ enqueue<T>(key: string, fn: () => Promise<T>): Promise<T>;
4
+ get activeKeyCount(): number;
5
+ }
@@ -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
- export declare function mountConsoleRoutes(app: Application, consoleService: ConsoleService, workflowService?: WorkflowService, timingRingBuffer?: ToolCallTimingRingBuffer, toolCallsPerfFile?: string, serverVersion?: string): () => void;
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.24.4",
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",
@@ -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
+ }