@exaudeus/workrail 3.36.0 → 3.37.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.
Files changed (45) hide show
  1. package/dist/config/config-file.js +2 -0
  2. package/dist/console-ui/assets/{index-n8cJrS4v.js → index-t8Wi304z.js} +1 -1
  3. package/dist/console-ui/index.html +1 -1
  4. package/dist/daemon/workflow-runner.d.ts +1 -0
  5. package/dist/daemon/workflow-runner.js +3 -6
  6. package/dist/infrastructure/session/SessionManager.js +17 -4
  7. package/dist/manifest.json +25 -17
  8. package/dist/trigger/notification-service.d.ts +42 -0
  9. package/dist/trigger/notification-service.js +164 -0
  10. package/dist/trigger/trigger-listener.js +7 -1
  11. package/dist/trigger/trigger-router.d.ts +3 -1
  12. package/dist/trigger/trigger-router.js +4 -1
  13. package/docs/design/agent-behavior-patterns-discovery.md +312 -0
  14. package/docs/design/agent-engine-communication-discovery.md +390 -0
  15. package/docs/design/agent-loop-architecture-alternatives-discovery.md +531 -0
  16. package/docs/design/agent-loop-error-handling-contract.md +238 -0
  17. package/docs/design/complete-step-approach-validation-discovery.md +344 -0
  18. package/docs/design/daemon-stuck-detection-discovery.md +174 -0
  19. package/docs/design/mcp-server-disconnect-discovery.md +245 -0
  20. package/docs/design/mcp-server-epipe-crash.md +198 -0
  21. package/docs/design/notification-design-candidates.md +131 -0
  22. package/docs/design/notification-design-review.md +84 -0
  23. package/docs/design/notification-implementation-plan.md +181 -0
  24. package/docs/design/spawn-agent-failure-modes.md +161 -0
  25. package/docs/design/spawn-agent-result-handling-implementation-plan.md +186 -0
  26. package/docs/design/stdio-simplification-design-candidates.md +341 -0
  27. package/docs/design/stdio-simplification-design-review.md +93 -0
  28. package/docs/design/stdio-simplification-implementation-plan.md +317 -0
  29. package/docs/design/structured-output-tools-coexist-findings.md +288 -0
  30. package/docs/discovery/coordinator-script-design.md +745 -0
  31. package/docs/discovery/coordinator-ux-discovery.md +471 -0
  32. package/docs/discovery/spawn-agent-failure-modes.md +309 -0
  33. package/docs/discovery/workflow-selection-for-discovery-tasks.md +336 -0
  34. package/docs/discovery/worktrain-status-briefing.md +325 -0
  35. package/docs/discovery/worktrain-status-design-candidates.md +202 -0
  36. package/docs/discovery/worktrain-status-design-review-findings.md +86 -0
  37. package/docs/ideas/backlog.md +608 -0
  38. package/docs/ideas/daemon-structured-output-vs-tool-calls.md +344 -0
  39. package/docs/ideas/design-candidates-backlog-consolidation.md +85 -0
  40. package/docs/ideas/design-review-findings-backlog-consolidation.md +39 -0
  41. package/docs/ideas/implementation_plan_backlog_consolidation.md +117 -0
  42. package/docs/plans/authoring-doc-staleness-enforcement-candidates.md +251 -0
  43. package/docs/plans/authoring-doc-staleness-enforcement-review.md +99 -0
  44. package/docs/plans/authoring-doc-staleness-enforcement.md +463 -0
  45. package/package.json +1 -1
@@ -430,8 +430,8 @@
430
430
  "bytes": 506
431
431
  },
432
432
  "config/config-file.js": {
433
- "sha256": "8d7a93f6153442348c8ef52178a928a722b2c19581097b31bc7de19076096710",
434
- "bytes": 7215
433
+ "sha256": "22006b77ef2c6094c86b97007371b96fbd8792accec652c49bdcccaa40ad327f",
434
+ "bytes": 7277
435
435
  },
436
436
  "config/feature-flags.d.ts": {
437
437
  "sha256": "49cdf81a9c4f31eca560af5257c569143d2138ec996468b949f9807b7ad7802e",
@@ -445,12 +445,12 @@
445
445
  "sha256": "cf9d09641f1c31fffe6c7835b30bbbad52572befec1acab7fb9a0c188431af36",
446
446
  "bytes": 60355
447
447
  },
448
- "console-ui/assets/index-n8cJrS4v.js": {
449
- "sha256": "a676f6e7685eebaf2edb1feaa6cfe382131ce9faaee8efe6974e4073d935af7b",
448
+ "console-ui/assets/index-t8Wi304z.js": {
449
+ "sha256": "d0e010095f9ab0dc8e88d61cd9b8cec4bee7537433854e0f192e73ad4cf2bd5a",
450
450
  "bytes": 754955
451
451
  },
452
452
  "console-ui/index.html": {
453
- "sha256": "14f1536e9c93a887a2c7c2a30c8297a0ae537c20f0f552cc2d79d1ee07728b7f",
453
+ "sha256": "e4bd9c7cdc0070c4706e010726ad02d0d26d867e34132e543c2a149211dfd848",
454
454
  "bytes": 417
455
455
  },
456
456
  "console/standalone-console.d.ts": {
@@ -502,12 +502,12 @@
502
502
  "bytes": 1009
503
503
  },
504
504
  "daemon/workflow-runner.d.ts": {
505
- "sha256": "0d6b407a188357fd00cc534f8af754e83d9f0f1a5f5f599d8b39f92ffeab8904",
506
- "bytes": 4496
505
+ "sha256": "d62587e9c7da974ff986d2d9cb67f0b30f7f3cb98a469cf98daf1d6fd16fa897",
506
+ "bytes": 4593
507
507
  },
508
508
  "daemon/workflow-runner.js": {
509
- "sha256": "d7a7781ac06354b73e3d8859aebd654986255f653ac8df49b2e4a68b3da2ae9b",
510
- "bytes": 63759
509
+ "sha256": "e3784aa04ead526de3ac9103d40967b32e3095ae3021ac13237034290be4ba4c",
510
+ "bytes": 63597
511
511
  },
512
512
  "di/container.d.ts": {
513
513
  "sha256": "003bb7fb7478d627524b9b1e76bd0a963a243794a687ff233b96dc0e33a06d9f",
@@ -690,8 +690,8 @@
690
690
  "bytes": 2524
691
691
  },
692
692
  "infrastructure/session/SessionManager.js": {
693
- "sha256": "7044259c881b35c72ccbfece31be867eb5315df1384417924ac49d4cee11aee6",
694
- "bytes": 21110
693
+ "sha256": "b39366c0be96268e6dfeb6d081d68b3b284fea2e709accc9f2c799fb95b5cda8",
694
+ "bytes": 21438
695
695
  },
696
696
  "infrastructure/session/index.d.ts": {
697
697
  "sha256": "52957847da70fcece6a553334bd899efd8ede55cc31f3aa5eb5e3d4bb70e3862",
@@ -1557,6 +1557,14 @@
1557
1557
  "sha256": "b8668c607788d560b38cf203750395e84eaa3164fff5711cac8f87f469714592",
1558
1558
  "bytes": 1222
1559
1559
  },
1560
+ "trigger/notification-service.d.ts": {
1561
+ "sha256": "c78406d3748953548f7879df8ac60cecd5e42f2f3b283f777343168ce2470b8d",
1562
+ "bytes": 1572
1563
+ },
1564
+ "trigger/notification-service.js": {
1565
+ "sha256": "693f617adc30b3a4fcebeca6a78b0da1c58819001660c017a4d0901652d675b8",
1566
+ "bytes": 6373
1567
+ },
1560
1568
  "trigger/polled-event-store.d.ts": {
1561
1569
  "sha256": "2952a25804177b2389d4273bfc41192477d100bc26100683861dedf28520dec1",
1562
1570
  "bytes": 1011
@@ -1578,16 +1586,16 @@
1578
1586
  "bytes": 1529
1579
1587
  },
1580
1588
  "trigger/trigger-listener.js": {
1581
- "sha256": "f2de9f72a7ce75ee47d26c45b5d2e93f10def16098aa718ec69b145862e4b493",
1582
- "bytes": 10021
1589
+ "sha256": "23f1eed165ae7ec03b2c46ff6d6fdf46f631319d5d58d3a993f710d2732e41f1",
1590
+ "bytes": 10585
1583
1591
  },
1584
1592
  "trigger/trigger-router.d.ts": {
1585
- "sha256": "c60fa099ea236255d2a51799f3f8c550af1990d167565a976ecb9ec2eb42c6ae",
1586
- "bytes": 1855
1593
+ "sha256": "5293a744ac4763380716ec7c0b31f16531b9a666d08a3524c6c7993486a728b6",
1594
+ "bytes": 2010
1587
1595
  },
1588
1596
  "trigger/trigger-router.js": {
1589
- "sha256": "16241f6376d2b6718ee1df4d1873270fd3c0cac69bdeaf2cca9fdaad2ca2bd33",
1590
- "bytes": 15336
1597
+ "sha256": "e7b620d2b23a5e74f6fd3b5a39a5299d19d38f745401c545277c399336ef5eaf",
1598
+ "bytes": 15565
1591
1599
  },
1592
1600
  "trigger/trigger-store.d.ts": {
1593
1601
  "sha256": "7afb05127d55bc3757a550dd15d4b797766b3fff29d1bfe76b303764b93322e7",
@@ -0,0 +1,42 @@
1
+ import type { WorkflowRunResult } from '../daemon/workflow-runner.js';
2
+ export type ExecFileNotifyFn = (file: string, args: readonly string[], options: {
3
+ timeout: number;
4
+ }, callback: (error: Error | null) => void) => void;
5
+ export type FetchNotifyFn = (url: string, init: {
6
+ method: string;
7
+ headers: Record<string, string>;
8
+ body: string;
9
+ signal: AbortSignal;
10
+ }) => Promise<{
11
+ ok: boolean;
12
+ status: number;
13
+ }>;
14
+ export interface NotificationConfig {
15
+ readonly macOs: boolean;
16
+ readonly webhookUrl?: string;
17
+ readonly execFileFn?: ExecFileNotifyFn;
18
+ readonly fetchFn?: FetchNotifyFn;
19
+ readonly platformFn?: () => NodeJS.Platform;
20
+ }
21
+ export interface NotificationPayload {
22
+ readonly event: 'session_completed';
23
+ readonly workflowId: string;
24
+ readonly outcome: 'success' | 'error' | 'timeout' | 'delivery_failed';
25
+ readonly detail: string;
26
+ readonly goal: string;
27
+ readonly timestamp: string;
28
+ }
29
+ export declare function buildNotificationBody(result: WorkflowRunResult, goal: string): string;
30
+ export declare function buildOutcome(result: WorkflowRunResult): NotificationPayload['outcome'];
31
+ export declare function buildDetail(result: WorkflowRunResult): string;
32
+ export declare class NotificationService {
33
+ private readonly _macOsEnabled;
34
+ private readonly _webhookUrl;
35
+ private readonly _execFileFn;
36
+ private readonly _fetchFn;
37
+ constructor(config: NotificationConfig);
38
+ notify(result: WorkflowRunResult, goal: string): void;
39
+ private _doNotify;
40
+ private _notifyMacOs;
41
+ private _notifyWebhook;
42
+ }
@@ -0,0 +1,164 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NotificationService = void 0;
37
+ exports.buildNotificationBody = buildNotificationBody;
38
+ exports.buildOutcome = buildOutcome;
39
+ exports.buildDetail = buildDetail;
40
+ const childProcess = __importStar(require("node:child_process"));
41
+ const os = __importStar(require("node:os"));
42
+ function buildNotificationBody(result, goal) {
43
+ const truncated = goal.length > 60 ? `${goal.slice(0, 57)}...` : goal;
44
+ switch (result._tag) {
45
+ case 'success':
46
+ return `Session completed: ${truncated}`;
47
+ case 'error':
48
+ return `Session failed: ${truncated}`;
49
+ case 'timeout':
50
+ return `Session timed out: ${truncated}`;
51
+ case 'delivery_failed':
52
+ return `Session completed but result delivery failed: ${truncated}`;
53
+ }
54
+ }
55
+ function buildOutcome(result) {
56
+ return result._tag;
57
+ }
58
+ function buildDetail(result) {
59
+ switch (result._tag) {
60
+ case 'success':
61
+ return `stopReason: ${result.stopReason}`;
62
+ case 'error':
63
+ return result.message;
64
+ case 'timeout':
65
+ return result.message;
66
+ case 'delivery_failed':
67
+ return `stopReason: ${result.stopReason}; deliveryError: ${result.deliveryError}`;
68
+ }
69
+ }
70
+ class NotificationService {
71
+ constructor(config) {
72
+ const getPlatform = config.platformFn ?? os.platform.bind(os);
73
+ if (config.macOs && getPlatform() !== 'darwin') {
74
+ console.warn('[NotificationService] WORKTRAIN_NOTIFY_MACOS=true but platform is not darwin ' +
75
+ `(platform: ${getPlatform()}). macOS notifications are disabled.`);
76
+ this._macOsEnabled = false;
77
+ }
78
+ else {
79
+ this._macOsEnabled = config.macOs;
80
+ }
81
+ if (config.webhookUrl !== undefined && config.webhookUrl !== '') {
82
+ let valid = false;
83
+ try {
84
+ const parsed = new URL(config.webhookUrl);
85
+ valid = parsed.protocol === 'http:' || parsed.protocol === 'https:';
86
+ }
87
+ catch {
88
+ valid = false;
89
+ }
90
+ if (!valid) {
91
+ console.warn(`[NotificationService] WORKTRAIN_NOTIFY_WEBHOOK is not a valid http(s) URL ` +
92
+ `("${config.webhookUrl}"). Webhook notifications are disabled.`);
93
+ this._webhookUrl = undefined;
94
+ }
95
+ else {
96
+ this._webhookUrl = config.webhookUrl;
97
+ }
98
+ }
99
+ else {
100
+ this._webhookUrl = undefined;
101
+ }
102
+ this._execFileFn = config.execFileFn ?? ((file, args, options, callback) => {
103
+ childProcess.execFile(file, args, options, callback);
104
+ });
105
+ this._fetchFn = config.fetchFn ?? ((url, init) => globalThis.fetch(url, init));
106
+ }
107
+ notify(result, goal) {
108
+ void this._doNotify(result, goal).catch(() => {
109
+ });
110
+ }
111
+ async _doNotify(result, goal) {
112
+ const body = buildNotificationBody(result, goal);
113
+ const deliveries = [];
114
+ if (this._macOsEnabled) {
115
+ deliveries.push(this._notifyMacOs(body, result.workflowId));
116
+ }
117
+ if (this._webhookUrl !== undefined) {
118
+ deliveries.push(this._notifyWebhook(result, goal));
119
+ }
120
+ await Promise.allSettled(deliveries);
121
+ }
122
+ _notifyMacOs(body, workflowId) {
123
+ const script = `display notification ${JSON.stringify(body)} with title "WorkTrain" subtitle ${JSON.stringify(workflowId)}`;
124
+ return new Promise((resolve) => {
125
+ this._execFileFn('osascript', ['-e', script], { timeout: 5000 }, (error) => {
126
+ if (error) {
127
+ console.warn(`[NotificationService] macOS notification failed: ${error.message}`);
128
+ }
129
+ resolve();
130
+ });
131
+ });
132
+ }
133
+ async _notifyWebhook(result, goal) {
134
+ const url = this._webhookUrl;
135
+ const payload = {
136
+ event: 'session_completed',
137
+ workflowId: result.workflowId,
138
+ outcome: buildOutcome(result),
139
+ detail: buildDetail(result),
140
+ goal,
141
+ timestamp: new Date().toISOString(),
142
+ };
143
+ const controller = new AbortController();
144
+ const timer = setTimeout(() => controller.abort(), 30000);
145
+ try {
146
+ const res = await this._fetchFn(url, {
147
+ method: 'POST',
148
+ headers: { 'Content-Type': 'application/json' },
149
+ body: JSON.stringify(payload),
150
+ signal: controller.signal,
151
+ });
152
+ if (!res.ok) {
153
+ console.warn(`[NotificationService] Webhook notification failed: HTTP ${res.status} from ${url}`);
154
+ }
155
+ }
156
+ catch (e) {
157
+ console.warn(`[NotificationService] Webhook notification error: ${String(e)}`);
158
+ }
159
+ finally {
160
+ clearTimeout(timer);
161
+ }
162
+ }
163
+ }
164
+ exports.NotificationService = NotificationService;
@@ -44,6 +44,7 @@ const http = __importStar(require("node:http"));
44
44
  const trigger_store_js_1 = require("./trigger-store.js");
45
45
  const trigger_router_js_1 = require("./trigger-router.js");
46
46
  const config_file_js_1 = require("../config/config-file.js");
47
+ const notification_service_js_1 = require("./notification-service.js");
47
48
  const workflow_runner_js_1 = require("../daemon/workflow-runner.js");
48
49
  const types_js_1 = require("./types.js");
49
50
  const polling_scheduler_js_1 = require("./polling-scheduler.js");
@@ -177,8 +178,13 @@ async function startTriggerListener(ctx, options) {
177
178
  : undefined;
178
179
  const parsed = parseInt(maxConcurrencyRaw ?? '', 10);
179
180
  const maxConcurrentSessions = !isNaN(parsed) ? parsed : undefined;
181
+ const notifyMacOs = (workrailConfig.kind === 'ok' && workrailConfig.value['WORKTRAIN_NOTIFY_MACOS'] === 'true');
182
+ const notifyWebhook = workrailConfig.kind === 'ok' ? workrailConfig.value['WORKTRAIN_NOTIFY_WEBHOOK'] : undefined;
183
+ const notificationService = (notifyMacOs || (notifyWebhook !== undefined && notifyWebhook !== ''))
184
+ ? new notification_service_js_1.NotificationService({ macOs: notifyMacOs, webhookUrl: notifyWebhook })
185
+ : undefined;
180
186
  const runWorkflowFn = options.runWorkflowFn ?? workflow_runner_js_1.runWorkflow;
181
- const router = new trigger_router_js_1.TriggerRouter(triggerIndex, ctx, apiKey, runWorkflowFn, undefined, maxConcurrentSessions, options.emitter);
187
+ const router = new trigger_router_js_1.TriggerRouter(triggerIndex, ctx, apiKey, runWorkflowFn, undefined, maxConcurrentSessions, options.emitter, notificationService);
182
188
  const app = createTriggerApp(router);
183
189
  const allTriggers = [...triggerIndex.values()];
184
190
  const polledEventStore = new polled_event_store_js_1.PolledEventStore(env);
@@ -3,6 +3,7 @@ import type { V2ToolContext } from '../mcp/types.js';
3
3
  import type { TriggerDefinition, WebhookEvent } from './types.js';
4
4
  import type { ExecFn } from './delivery-action.js';
5
5
  import type { DaemonEventEmitter } from '../daemon/daemon-events.js';
6
+ import type { NotificationService } from './notification-service.js';
6
7
  export type RouteError = {
7
8
  readonly kind: 'not_found';
8
9
  readonly triggerId: string;
@@ -31,7 +32,8 @@ export declare class TriggerRouter {
31
32
  private readonly semaphore;
32
33
  private readonly _maxConcurrentSessions;
33
34
  private readonly emitter;
34
- constructor(index: ReadonlyMap<string, TriggerDefinition>, ctx: V2ToolContext, apiKey: string, runWorkflowFn: RunWorkflowFn, execFn?: ExecFn, maxConcurrentSessions?: number, emitter?: DaemonEventEmitter);
35
+ private readonly notificationService;
36
+ constructor(index: ReadonlyMap<string, TriggerDefinition>, ctx: V2ToolContext, apiKey: string, runWorkflowFn: RunWorkflowFn, execFn?: ExecFn, maxConcurrentSessions?: number, emitter?: DaemonEventEmitter, notificationService?: NotificationService);
35
37
  get activeSessions(): number;
36
38
  get maxConcurrentSessions(): number;
37
39
  route(event: WebhookEvent): RouteResult;
@@ -182,7 +182,7 @@ class Semaphore {
182
182
  }
183
183
  const DEFAULT_MAX_CONCURRENT_SESSIONS = 3;
184
184
  class TriggerRouter {
185
- constructor(index, ctx, apiKey, runWorkflowFn, execFn, maxConcurrentSessions, emitter) {
185
+ constructor(index, ctx, apiKey, runWorkflowFn, execFn, maxConcurrentSessions, emitter, notificationService) {
186
186
  this.index = index;
187
187
  this.ctx = ctx;
188
188
  this.apiKey = apiKey;
@@ -190,6 +190,7 @@ class TriggerRouter {
190
190
  this.queue = new index_js_1.KeyedAsyncQueue();
191
191
  this.execFn = execFn ?? execFileAsync;
192
192
  this.emitter = emitter;
193
+ this.notificationService = notificationService;
193
194
  const requested = maxConcurrentSessions ?? DEFAULT_MAX_CONCURRENT_SESSIONS;
194
195
  const cap = Number.isNaN(requested) ? DEFAULT_MAX_CONCURRENT_SESSIONS : requested;
195
196
  if (cap < 1) {
@@ -301,6 +302,7 @@ class TriggerRouter {
301
302
  console.log(`[TriggerRouter] Workflow failed: triggerId=${trigger.id} ` +
302
303
  `workflowId=${trigger.workflowId} error=${result.message} stopReason=${result.stopReason}`);
303
304
  }
305
+ this.notificationService?.notify(result, workflowTrigger.goal);
304
306
  await maybeRunDelivery(trigger.id, trigger, originalResult, this.execFn);
305
307
  });
306
308
  return { _tag: 'enqueued', triggerId: trigger.id };
@@ -336,6 +338,7 @@ class TriggerRouter {
336
338
  console.log(`[TriggerRouter] Dispatch failed: workflowId=${workflowTrigger.workflowId} ` +
337
339
  `error=${result.message} stopReason=${result.stopReason}`);
338
340
  }
341
+ this.notificationService?.notify(result, workflowTrigger.goal);
339
342
  });
340
343
  return workflowTrigger.workflowId;
341
344
  }