@exaudeus/workrail 3.46.0 → 3.48.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 (31) hide show
  1. package/dist/cli/commands/index.d.ts +1 -0
  2. package/dist/cli/commands/index.js +3 -1
  3. package/dist/cli/commands/worktrain-trigger-test.d.ts +21 -0
  4. package/dist/cli/commands/worktrain-trigger-test.js +123 -0
  5. package/dist/cli-worktrain.js +65 -0
  6. package/dist/console-ui/assets/{index-BQFhoMcY.js → index-CecBgrR7.js} +1 -1
  7. package/dist/console-ui/index.html +1 -1
  8. package/dist/coordinators/modes/implement-shared.d.ts +2 -1
  9. package/dist/coordinators/modes/implement-shared.js +7 -3
  10. package/dist/manifest.json +44 -36
  11. package/dist/mcp/output-schemas.d.ts +2 -2
  12. package/dist/trigger/adapters/github-queue-poller.js +10 -7
  13. package/dist/trigger/github-queue-config.d.ts +1 -0
  14. package/dist/trigger/github-queue-config.js +9 -0
  15. package/dist/trigger/polling-scheduler.js +8 -1
  16. package/dist/trigger/trigger-listener.js +296 -1
  17. package/dist/trigger/trigger-router.d.ts +4 -2
  18. package/dist/trigger/trigger-router.js +19 -3
  19. package/dist/trigger/trigger-store.js +10 -0
  20. package/dist/trigger/types.d.ts +2 -0
  21. package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +5 -0
  22. package/dist/v2/durable-core/schemas/artifacts/review-verdict.js +12 -0
  23. package/docs/design/connect-adaptive-dispatch-candidates.md +153 -0
  24. package/docs/design/connect-adaptive-dispatch-design-review.md +88 -0
  25. package/docs/design/connect-adaptive-dispatch-implementation-plan.md +209 -0
  26. package/docs/design/queue-label-support-candidates.md +83 -0
  27. package/docs/design/queue-label-support-design-review.md +62 -0
  28. package/docs/design/queue-label-support-implementation-plan.md +158 -0
  29. package/docs/ideas/backlog.md +147 -0
  30. package/package.json +1 -1
  31. package/workflows/mr-review-workflow.agentic.v2.json +1 -1
@@ -238,8 +238,8 @@
238
238
  "bytes": 31
239
239
  },
240
240
  "cli-worktrain.js": {
241
- "sha256": "75410de5c741ed86839f2834b69ff1dbf18b0284140a765a8a4ffd34257d19a3",
242
- "bytes": 46706
241
+ "sha256": "4f68c32b88c84d71d7a28b4ffd818d2612e5a8c61c9f59edd4415772dfd5b9ab",
242
+ "bytes": 50057
243
243
  },
244
244
  "cli.d.ts": {
245
245
  "sha256": "43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012",
@@ -258,12 +258,12 @@
258
258
  "bytes": 745
259
259
  },
260
260
  "cli/commands/index.d.ts": {
261
- "sha256": "7284fb3ca25bdbe6079fee4ed7f5eee9a82fff5fbc4e056010b52f4261145880",
262
- "bytes": 2251
261
+ "sha256": "240dc5f7055f8f4d602d41c4c659c800f80207af084683b1bd0a587cf68bfac0",
262
+ "bytes": 2396
263
263
  },
264
264
  "cli/commands/index.js": {
265
- "sha256": "8eef74385e22afc6313b4f7022c2e0a6be267ab55d7aa11c9a83b01a5e7c0e61",
266
- "bytes": 5186
265
+ "sha256": "fee00ff3ceb1a416bc3a2cfffcabf90bd5caeb2a1c06408ec4b32acf449da577",
266
+ "bytes": 5490
267
267
  },
268
268
  "cli/commands/init.d.ts": {
269
269
  "sha256": "b5f8b88a072c68509dab3938ba1d6b4a949ad32f8fc55e91c5039b8c77301c1b",
@@ -393,6 +393,14 @@
393
393
  "sha256": "b0286fef461835a0b73070fd278e43af5f3a1fbebbe1c6de1fc39ace4075df8f",
394
394
  "bytes": 1395
395
395
  },
396
+ "cli/commands/worktrain-trigger-test.d.ts": {
397
+ "sha256": "3b85edacabf0657b208892f13b8fb540f794f47f18b5a1263562d3518f7fce43",
398
+ "bytes": 1357
399
+ },
400
+ "cli/commands/worktrain-trigger-test.js": {
401
+ "sha256": "d2153a2110e70cc169596c2357410dd947c4bb69bf6167b64f4bf5b16bd5b8ca",
402
+ "bytes": 6413
403
+ },
396
404
  "cli/interpret-result.d.ts": {
397
405
  "sha256": "255f04350df9c8cf8d5e65ed2fc11d41fa60a7b5ccc818e7728b1c081340a66a",
398
406
  "bytes": 315
@@ -457,8 +465,8 @@
457
465
  "sha256": "5fe866e54f796975dec5d8ba9983aefd86074db212d3fccd64eed04bc9f0b3da",
458
466
  "bytes": 8011
459
467
  },
460
- "console-ui/assets/index-BQFhoMcY.js": {
461
- "sha256": "722cfaf2b1b513c79b9f4d62cc79bd0f59c953c959480a8745efbdccf80df3d8",
468
+ "console-ui/assets/index-CecBgrR7.js": {
469
+ "sha256": "aa4e83d99ec40281b7f05dc6a327906c87ba2ab599de923df19fadcc00058ce7",
462
470
  "bytes": 760528
463
471
  },
464
472
  "console-ui/assets/index-DGj8EsFR.css": {
@@ -466,7 +474,7 @@
466
474
  "bytes": 60631
467
475
  },
468
476
  "console-ui/index.html": {
469
- "sha256": "b4d9928edd90bb6778c3685eb511c9e7600937720b6212e018a017fbbbe7b8c5",
477
+ "sha256": "4a1e537834ad38c21ab5c1df676e6b3927df05d79b1d25d2a27b1081504e9e20",
470
478
  "bytes": 417
471
479
  },
472
480
  "console/standalone-console.d.ts": {
@@ -526,12 +534,12 @@
526
534
  "bytes": 11782
527
535
  },
528
536
  "coordinators/modes/implement-shared.d.ts": {
529
- "sha256": "64e3ba7e31bf9965b74645d7157a85fb67d9df183696a4d792cec870b22047e0",
530
- "bytes": 554
537
+ "sha256": "fbad9d91d84d2112b273175618686489a7f106385e0e62d6cab80804d6d0f2d7",
538
+ "bytes": 708
531
539
  },
532
540
  "coordinators/modes/implement-shared.js": {
533
- "sha256": "c39e7b1c088663a75f86b2dd81326940537c53408c7182a55f2b1288796cd002",
534
- "bytes": 11023
541
+ "sha256": "a1727713839630d279377e9607b26068821a5a393bfbd42696222fb97ec4fe5f",
542
+ "bytes": 11400
535
543
  },
536
544
  "coordinators/modes/implement.d.ts": {
537
545
  "sha256": "23919c24d62a0bf15296a52fbc594cca8b1b34e6f8d98dcf7dede8d97ad4cabb",
@@ -1222,7 +1230,7 @@
1222
1230
  "bytes": 7991
1223
1231
  },
1224
1232
  "mcp/output-schemas.d.ts": {
1225
- "sha256": "bdc67c2adadeeca632aae3a3f0df8aa521c952194300b0bd823bdd7abed805d2",
1233
+ "sha256": "e41c4f996a7de03d96e52f1812e36c2d839f69b2ba421ae1831f6ae266cf59c5",
1226
1234
  "bytes": 93176
1227
1235
  },
1228
1236
  "mcp/output-schemas.js": {
@@ -1618,8 +1626,8 @@
1618
1626
  "bytes": 1363
1619
1627
  },
1620
1628
  "trigger/adapters/github-queue-poller.js": {
1621
- "sha256": "73270636634fb8673c63b0c0347e6e5d7f676606d36b87a4c1aadc11f1939eb2",
1622
- "bytes": 7340
1629
+ "sha256": "ea6c402fe42dd3bac1b15e001d572d5e2a503f3ac0a3d3dac4175f80e603bb35",
1630
+ "bytes": 7442
1623
1631
  },
1624
1632
  "trigger/adapters/gitlab-poller.d.ts": {
1625
1633
  "sha256": "f685490fafad77194fdd0f0bbaf80dbc56730aeb344853da365199a120fbe399",
@@ -1654,12 +1662,12 @@
1654
1662
  "bytes": 1269
1655
1663
  },
1656
1664
  "trigger/github-queue-config.d.ts": {
1657
- "sha256": "bff922c1b435da55e02b4b11d3fde9f06e999f13b8ac0494788cac4a4fc4c432",
1658
- "bytes": 766
1665
+ "sha256": "4dd68c2b880f808aa67c3b6be1867299c0b49ce5cf7e39f4f093dbd341d1a1e8",
1666
+ "bytes": 800
1659
1667
  },
1660
1668
  "trigger/github-queue-config.js": {
1661
- "sha256": "a0ec09a7725ac1ee1adb9d41fdcb4c9545128f0b9515c6de4df9535d4bec1acc",
1662
- "bytes": 6857
1669
+ "sha256": "dd508fc3f9f58a70cdc076029154a3c1610569c611471e9b67bf1cd4be64a520",
1670
+ "bytes": 7347
1663
1671
  },
1664
1672
  "trigger/index.d.ts": {
1665
1673
  "sha256": "a9cfd053714173e2a8cc5a282fd5b09a5c3f3001304d507facd0e12de9cc0733",
@@ -1690,36 +1698,36 @@
1690
1698
  "bytes": 792
1691
1699
  },
1692
1700
  "trigger/polling-scheduler.js": {
1693
- "sha256": "ec3c73739c9dfbbe3b2d7a55237fd35a290d6aab7bbc30ec5408bde69b2c95e0",
1694
- "bytes": 19769
1701
+ "sha256": "dd5532f3dff75377685fb68d78820cb5916f823626b91d6306ad7aa3d9801be5",
1702
+ "bytes": 20188
1695
1703
  },
1696
1704
  "trigger/trigger-listener.d.ts": {
1697
1705
  "sha256": "92e971ab8f47c3c867860cffc01f54c4aae54fcc4ae199b9469210f4ce639423",
1698
1706
  "bytes": 1639
1699
1707
  },
1700
1708
  "trigger/trigger-listener.js": {
1701
- "sha256": "347ce68b2b6d14be5277488e7d54ac0d9e80173407ee12dbb2f7df90765bac3c",
1702
- "bytes": 10668
1709
+ "sha256": "1c73115b26d9274c86fd3b1e19f5eae2754d58e248949b6eaf0e7b7074b91c8c",
1710
+ "bytes": 25421
1703
1711
  },
1704
1712
  "trigger/trigger-router.d.ts": {
1705
- "sha256": "b3154af4d32d6d71ae38fb6befc54850d137a12b2f602a34247b0c5f9b57a29d",
1706
- "bytes": 2519
1713
+ "sha256": "44f1bfefafee4955bad74db9e61ecb6a49ea426c46100753f6bdb3a7f7b583dd",
1714
+ "bytes": 2671
1707
1715
  },
1708
1716
  "trigger/trigger-router.js": {
1709
- "sha256": "682bf6e3a9d0fa5e25378c361813fdc52e5b828dff7267fd084d83f242585492",
1710
- "bytes": 18144
1717
+ "sha256": "1f84935ad94e36be5befbef34d10deb7467c708e6d06cd36903843806f4b49a6",
1718
+ "bytes": 19067
1711
1719
  },
1712
1720
  "trigger/trigger-store.d.ts": {
1713
1721
  "sha256": "7afb05127d55bc3757a550dd15d4b797766b3fff29d1bfe76b303764b93322e7",
1714
1722
  "bytes": 1588
1715
1723
  },
1716
1724
  "trigger/trigger-store.js": {
1717
- "sha256": "8015b54da7ab5b1cec78e1ecea099d5b457eaa5de267bb1ed52cbb75eca207c4",
1718
- "bytes": 38148
1725
+ "sha256": "8e85e0bdbd1596e70c23667e84ed380d6f0cee10028cd1a41163cda3467c2bdc",
1726
+ "bytes": 38559
1719
1727
  },
1720
1728
  "trigger/types.d.ts": {
1721
- "sha256": "a1336ad769dbe4760e7acd3b2a92961251492aa29245b5d906ad89011ea934fa",
1722
- "bytes": 3587
1729
+ "sha256": "611f047631d8334a966acb7d1a71f5aa0d8cda65da127e07081b363290bcfdc2",
1730
+ "bytes": 3654
1723
1731
  },
1724
1732
  "trigger/types.js": {
1725
1733
  "sha256": "45b4e4f23a6d1a2b07350196871b0c53840e5d8142b47f7acedd2f40ae7a6b73",
@@ -2262,12 +2270,12 @@
2262
2270
  "bytes": 2115
2263
2271
  },
2264
2272
  "v2/durable-core/schemas/artifacts/review-verdict.d.ts": {
2265
- "sha256": "3190c4c99a6f7fe85d56b4e2f16ee2239d5f8b1addf995b76359f231909cc3d3",
2266
- "bytes": 1562
2273
+ "sha256": "2b9f7ae6b3fafe6c26f266bb249c8c286144eb1f753c803d4358ef81e30d1736",
2274
+ "bytes": 2211
2267
2275
  },
2268
2276
  "v2/durable-core/schemas/artifacts/review-verdict.js": {
2269
- "sha256": "72915617cb08834e37a65c45b3eb84ff04c84d6e4a12c9e87be5a90a3d80cee5",
2270
- "bytes": 1206
2277
+ "sha256": "67e03f956523b68783f0d01414bf04992070204d8bb828cdbb303bd0d914a220",
2278
+ "bytes": 1557
2271
2279
  },
2272
2280
  "v2/durable-core/schemas/compiled-workflow/index.d.ts": {
2273
2281
  "sha256": "66e5a5e8d87e1c4743acfad71fa81c22775d377b566701d71c17227a4f4c969c",
@@ -2547,14 +2547,14 @@ export declare const CreateSessionOutputSchema: z.ZodObject<{
2547
2547
  path: string;
2548
2548
  sessionId: string;
2549
2549
  workflowId: string;
2550
- dashboardUrl: string | null;
2551
2550
  createdAt: string;
2551
+ dashboardUrl: string | null;
2552
2552
  }, {
2553
2553
  path: string;
2554
2554
  sessionId: string;
2555
2555
  workflowId: string;
2556
- dashboardUrl: string | null;
2557
2556
  createdAt: string;
2557
+ dashboardUrl: string | null;
2558
2558
  }>;
2559
2559
  export declare const UpdateSessionOutputSchema: z.ZodObject<{
2560
2560
  updatedAt: z.ZodString;
@@ -57,19 +57,22 @@ function checkRateLimit(response) {
57
57
  return false;
58
58
  }
59
59
  async function pollGitHubQueueIssues(source, config, fetchFn = globalThis.fetch) {
60
- if (config.type !== 'assignee') {
61
- return (0, result_js_1.err)({
62
- kind: 'not_implemented',
63
- message: `Queue type '${config.type}' is not implemented. Only 'assignee' is supported.`,
64
- });
65
- }
66
60
  const [owner, repo] = source.repo.split('/');
67
61
  const url = new URL(`https://api.github.com/repos/${owner}/${repo}/issues`);
68
62
  url.searchParams.set('state', 'open');
69
63
  url.searchParams.set('per_page', '100');
70
- if (config.user) {
64
+ if (config.type === 'assignee' && config.user) {
71
65
  url.searchParams.set('assignee', config.user);
72
66
  }
67
+ else if (config.type === 'label' && config.queueLabel) {
68
+ url.searchParams.set('labels', config.queueLabel);
69
+ }
70
+ else {
71
+ return (0, result_js_1.err)({
72
+ kind: 'not_implemented',
73
+ message: `Queue type "${config.type}" is not yet implemented`,
74
+ });
75
+ }
73
76
  let response;
74
77
  try {
75
78
  response = await fetchFn(url.toString(), {
@@ -2,6 +2,7 @@ import type { Result } from '../runtime/result.js';
2
2
  export interface GitHubQueueConfig {
3
3
  readonly type: 'assignee' | 'label' | 'mention' | 'query';
4
4
  readonly user?: string;
5
+ readonly queueLabel?: string;
5
6
  readonly name?: string;
6
7
  readonly handle?: string;
7
8
  readonly search?: string;
@@ -123,10 +123,18 @@ async function loadQueueConfig(configPath = exports.WORKRAIL_CONFIG_PATH, env =
123
123
  }
124
124
  excludeLabels = rawExcludeLabels;
125
125
  }
126
+ if (rawType === 'label') {
127
+ const rawQueueLabel = q['queueLabel'];
128
+ if (typeof rawQueueLabel !== 'string' || !rawQueueLabel.trim()) {
129
+ return (0, result_js_1.err)('config.queue.queueLabel is required when type is "label"');
130
+ }
131
+ }
126
132
  const rawUser = q['user'];
127
133
  const user = typeof rawUser === 'string' && rawUser.trim() ? rawUser.trim() : undefined;
128
134
  const rawName = q['name'];
129
135
  const labelName = typeof rawName === 'string' && rawName.trim() ? rawName.trim() : undefined;
136
+ const rawQueueLabel = q['queueLabel'];
137
+ const queueLabel = typeof rawQueueLabel === 'string' && rawQueueLabel.trim() ? rawQueueLabel.trim() : undefined;
130
138
  const rawHandle = q['handle'];
131
139
  const handle = typeof rawHandle === 'string' && rawHandle.trim() ? rawHandle.trim() : undefined;
132
140
  const rawSearch = q['search'];
@@ -140,6 +148,7 @@ async function loadQueueConfig(configPath = exports.WORKRAIL_CONFIG_PATH, env =
140
148
  return (0, result_js_1.ok)({
141
149
  type: rawType,
142
150
  ...(user !== undefined ? { user } : {}),
151
+ ...(queueLabel !== undefined ? { queueLabel } : {}),
143
152
  ...(labelName !== undefined ? { name: labelName } : {}),
144
153
  ...(handle !== undefined ? { handle } : {}),
145
154
  ...(search !== undefined ? { search } : {}),
@@ -286,7 +286,14 @@ class PollingScheduler {
286
286
  const maturityReason = describeMaturityReason(top.maturity);
287
287
  console.log(`[QueuePoll] selected #${top.issue.number} "${top.issue.title}" maturity=${top.maturity} reason="${maturityReason}"`);
288
288
  await appendQueuePollLog({ event: 'task_selected', issueNumber: top.issue.number, title: top.issue.title, maturity: top.maturity, reason: maturityReason, ts: new Date().toISOString() });
289
- this.router.dispatch(workflowTrigger);
289
+ if ('dispatchAdaptivePipeline' in this.router &&
290
+ typeof this.router.dispatchAdaptivePipeline === 'function') {
291
+ void this.router.dispatchAdaptivePipeline(workflowTrigger.goal, workflowTrigger.workspacePath, workflowTrigger.context);
292
+ console.log(`[QueuePoll] dispatched via adaptivePipeline goal="${workflowTrigger.goal.slice(0, 80)}"`);
293
+ }
294
+ else {
295
+ this.router.dispatch(workflowTrigger);
296
+ }
290
297
  for (let i = 1; i < candidates.length; i++) {
291
298
  const { issue, maturity } = candidates[i];
292
299
  console.log(`[QueuePoll] skipped #${issue.number} "${issue.title}" reason=lower_priority_${maturity}`);
@@ -41,6 +41,12 @@ exports.startTriggerListener = startTriggerListener;
41
41
  require("reflect-metadata");
42
42
  const express_1 = __importDefault(require("express"));
43
43
  const http = __importStar(require("node:http"));
44
+ const fs = __importStar(require("node:fs"));
45
+ const os = __importStar(require("node:os"));
46
+ const path = __importStar(require("node:path"));
47
+ const node_child_process_1 = require("node:child_process");
48
+ const node_util_1 = require("node:util");
49
+ const node_crypto_1 = require("node:crypto");
44
50
  const trigger_store_js_1 = require("./trigger-store.js");
45
51
  const trigger_router_js_1 = require("./trigger-router.js");
46
52
  const config_file_js_1 = require("../config/config-file.js");
@@ -49,6 +55,12 @@ const workflow_runner_js_1 = require("../daemon/workflow-runner.js");
49
55
  const types_js_1 = require("./types.js");
50
56
  const polling_scheduler_js_1 = require("./polling-scheduler.js");
51
57
  const polled_event_store_js_1 = require("./polled-event-store.js");
58
+ const index_js_1 = require("../context-assembly/index.js");
59
+ const infra_js_1 = require("../context-assembly/infra.js");
60
+ const quick_review_js_1 = require("../coordinators/modes/quick-review.js");
61
+ const review_only_js_1 = require("../coordinators/modes/review-only.js");
62
+ const implement_js_1 = require("../coordinators/modes/implement.js");
63
+ const full_pipeline_js_1 = require("../coordinators/modes/full-pipeline.js");
52
64
  const DEFAULT_TRIGGER_PORT = 3200;
53
65
  function createTriggerApp(router) {
54
66
  const app = (0, express_1.default)();
@@ -184,8 +196,291 @@ async function startTriggerListener(ctx, options) {
184
196
  ? new notification_service_js_1.NotificationService({ macOs: notifyMacOs, webhookUrl: notifyWebhook })
185
197
  : undefined;
186
198
  const steerRegistry = new Map();
199
+ const DAEMON_CONSOLE_PORT = 3456;
200
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
201
+ const coordinatorDeps = {
202
+ spawnSession: async (workflowId, goal, workspace, context) => {
203
+ const url = `http://127.0.0.1:${DAEMON_CONSOLE_PORT}/api/v2/auto/dispatch`;
204
+ try {
205
+ const response = await globalThis.fetch(url, {
206
+ method: 'POST',
207
+ headers: { 'Content-Type': 'application/json' },
208
+ body: JSON.stringify({
209
+ workflowId,
210
+ goal,
211
+ workspacePath: workspace,
212
+ ...(context !== undefined ? { context } : {}),
213
+ }),
214
+ signal: AbortSignal.timeout(30000),
215
+ });
216
+ const body = await response.json();
217
+ if (!response.ok) {
218
+ const errMsg = typeof body['error'] === 'string' ? body['error'] : `HTTP ${response.status}`;
219
+ if (response.status === 503) {
220
+ return { kind: 'err', error: `WorkTrain daemon is not ready: ${errMsg}` };
221
+ }
222
+ return { kind: 'err', error: `Dispatch failed: ${errMsg}` };
223
+ }
224
+ if (body['success'] !== true || typeof body['data'] !== 'object') {
225
+ return { kind: 'err', error: 'Unexpected response from dispatch endpoint' };
226
+ }
227
+ const data = body['data'];
228
+ const handle = typeof data['sessionHandle'] === 'string' ? data['sessionHandle'] : '';
229
+ if (!handle) {
230
+ return { kind: 'err', error: 'Dispatch succeeded but no session handle returned' };
231
+ }
232
+ return { kind: 'ok', value: handle };
233
+ }
234
+ catch (e) {
235
+ const msg = e instanceof Error ? e.message : String(e);
236
+ const isConnRefused = msg.includes('ECONNREFUSED') || msg.includes('fetch failed');
237
+ if (isConnRefused) {
238
+ return { kind: 'err', error: `Could not connect to WorkTrain daemon on port ${DAEMON_CONSOLE_PORT}. Ensure the daemon is running.` };
239
+ }
240
+ if (e instanceof Error && e.name === 'TimeoutError') {
241
+ return { kind: 'err', error: `Daemon request timed out after 30s` };
242
+ }
243
+ return { kind: 'err', error: `Dispatch request failed: ${msg}` };
244
+ }
245
+ },
246
+ contextAssembler: (0, index_js_1.createContextAssembler)({
247
+ execGit: async (args, cwd) => {
248
+ try {
249
+ const { stdout } = await execFileAsync('git', [...args], { cwd });
250
+ return { kind: 'ok', value: stdout };
251
+ }
252
+ catch (e) {
253
+ return { kind: 'err', error: e instanceof Error ? e.message : String(e) };
254
+ }
255
+ },
256
+ execGh: async (args, cwd) => {
257
+ try {
258
+ const { stdout } = await execFileAsync('gh', [...args], { cwd });
259
+ return { kind: 'ok', value: stdout };
260
+ }
261
+ catch (e) {
262
+ return { kind: 'err', error: e instanceof Error ? e.message : String(e) };
263
+ }
264
+ },
265
+ listRecentSessions: (0, infra_js_1.createListRecentSessions)(),
266
+ nowIso: () => new Date().toISOString(),
267
+ }),
268
+ awaitSessions: async (handles, timeoutMs) => {
269
+ const { executeWorktrainAwaitCommand } = await Promise.resolve().then(() => __importStar(require('../cli/commands/worktrain-await.js')));
270
+ let resolvedResult = null;
271
+ await executeWorktrainAwaitCommand({
272
+ fetch: (url) => globalThis.fetch(url),
273
+ readFile: (p) => fs.promises.readFile(p, 'utf-8'),
274
+ stdout: (line) => {
275
+ try {
276
+ resolvedResult = JSON.parse(line);
277
+ }
278
+ catch { }
279
+ },
280
+ stderr: (line) => process.stderr.write(line + '\n'),
281
+ homedir: os.homedir,
282
+ joinPath: path.join,
283
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
284
+ now: () => Date.now(),
285
+ }, {
286
+ sessions: [...handles].join(','),
287
+ mode: 'all',
288
+ timeout: `${Math.round(timeoutMs / 1000)}s`,
289
+ port: DAEMON_CONSOLE_PORT,
290
+ });
291
+ if (resolvedResult === null) {
292
+ process.stderr.write(`[WARN coord:reason=await_failed] awaitSessions: could not get session results -- daemon may be unreachable or timed out. Returning all ${handles.length} session(s) as failed.\n`);
293
+ }
294
+ return resolvedResult ?? {
295
+ results: [...handles].map((h) => ({
296
+ handle: h,
297
+ outcome: 'failed',
298
+ status: null,
299
+ durationMs: 0,
300
+ })),
301
+ allSucceeded: false,
302
+ };
303
+ },
304
+ getAgentResult: async (sessionHandle) => {
305
+ const emptyResult = { recapMarkdown: null, artifacts: [] };
306
+ try {
307
+ const sessionUrl = `http://127.0.0.1:${DAEMON_CONSOLE_PORT}/api/v2/sessions/${encodeURIComponent(sessionHandle)}`;
308
+ const sessionRes = await globalThis.fetch(sessionUrl, { signal: AbortSignal.timeout(30000) });
309
+ if (!sessionRes.ok) {
310
+ process.stderr.write(`[WARN coord:reason=http_error status=${sessionRes.status} handle=${sessionHandle.slice(0, 16)}] getAgentResult: session fetch returned HTTP ${sessionRes.status}\n`);
311
+ return emptyResult;
312
+ }
313
+ const sessionBody = await sessionRes.json();
314
+ if (sessionBody['success'] !== true) {
315
+ return emptyResult;
316
+ }
317
+ const data = sessionBody['data'];
318
+ if (!data)
319
+ return emptyResult;
320
+ const runs = data['runs'];
321
+ if (!Array.isArray(runs) || runs.length === 0)
322
+ return emptyResult;
323
+ const firstRun = runs[0];
324
+ const tipNodeId = typeof firstRun['preferredTipNodeId'] === 'string'
325
+ ? firstRun['preferredTipNodeId']
326
+ : null;
327
+ if (!tipNodeId)
328
+ return emptyResult;
329
+ const allNodes = Array.isArray(firstRun['nodes'])
330
+ ? firstRun['nodes']
331
+ : [];
332
+ const allNodeIds = allNodes
333
+ .map((n) => (typeof n['nodeId'] === 'string' ? n['nodeId'] : null))
334
+ .filter((id) => id !== null);
335
+ const nodeIdsToFetch = allNodeIds.length > 0 ? allNodeIds : [tipNodeId];
336
+ const baseNodeUrl = `http://127.0.0.1:${DAEMON_CONSOLE_PORT}/api/v2/sessions/${encodeURIComponent(sessionHandle)}/nodes/`;
337
+ let recap = null;
338
+ const collectedArtifacts = [];
339
+ for (const nodeId of nodeIdsToFetch) {
340
+ try {
341
+ const nodeRes = await globalThis.fetch(baseNodeUrl + encodeURIComponent(nodeId), { signal: AbortSignal.timeout(30000) });
342
+ if (!nodeRes.ok)
343
+ continue;
344
+ const nodeBody = await nodeRes.json();
345
+ if (nodeBody['success'] !== true)
346
+ continue;
347
+ const nodeData = nodeBody['data'];
348
+ if (!nodeData)
349
+ continue;
350
+ if (nodeId === tipNodeId) {
351
+ recap = typeof nodeData['recapMarkdown'] === 'string' ? nodeData['recapMarkdown'] : null;
352
+ }
353
+ const nodeArtifacts = nodeData['artifacts'];
354
+ if (Array.isArray(nodeArtifacts) && nodeArtifacts.length > 0) {
355
+ collectedArtifacts.push(...nodeArtifacts);
356
+ }
357
+ }
358
+ catch (nodeErr) {
359
+ const msg = nodeErr instanceof Error ? nodeErr.message : String(nodeErr);
360
+ process.stderr.write(`[WARN coord:reason=node_exception handle=${sessionHandle.slice(0, 16)} node=${nodeId.slice(0, 16)}] getAgentResult: ${msg}\n`);
361
+ }
362
+ }
363
+ return { recapMarkdown: recap, artifacts: collectedArtifacts };
364
+ }
365
+ catch (e) {
366
+ const msg = e instanceof Error ? e.message : String(e);
367
+ process.stderr.write(`[WARN coord:reason=exception handle=${sessionHandle.slice(0, 16)}] getAgentResult: ${msg}\n`);
368
+ return emptyResult;
369
+ }
370
+ },
371
+ listOpenPRs: async (workspace) => {
372
+ try {
373
+ const { stdout } = await execFileAsync('gh', ['pr', 'list', '--json', 'number,title,headRefName'], {
374
+ cwd: workspace,
375
+ timeout: 30000,
376
+ });
377
+ const parsed = JSON.parse(stdout);
378
+ return parsed.map((p) => ({ number: p.number, title: p.title, headRef: p.headRefName }));
379
+ }
380
+ catch {
381
+ return [];
382
+ }
383
+ },
384
+ mergePR: async (prNumber, workspace) => {
385
+ try {
386
+ await execFileAsync('gh', ['pr', 'merge', String(prNumber), '--squash', '--auto'], {
387
+ cwd: workspace,
388
+ timeout: 60000,
389
+ });
390
+ return { kind: 'ok', value: undefined };
391
+ }
392
+ catch (e) {
393
+ const msg = e instanceof Error ? e.message : String(e);
394
+ return { kind: 'err', error: msg };
395
+ }
396
+ },
397
+ writeFile: async (filePath, content) => {
398
+ await fs.promises.writeFile(filePath, content, 'utf-8');
399
+ },
400
+ readFile: (filePath) => fs.promises.readFile(filePath, 'utf-8'),
401
+ appendFile: (filePath, content) => fs.promises.appendFile(filePath, content, 'utf-8'),
402
+ mkdir: (dirPath, opts) => fs.promises.mkdir(dirPath, opts),
403
+ homedir: os.homedir,
404
+ joinPath: path.join,
405
+ nowIso: () => new Date().toISOString(),
406
+ generateId: () => (0, node_crypto_1.randomUUID)(),
407
+ stderr: (line) => process.stderr.write(line + '\n'),
408
+ now: () => Date.now(),
409
+ port: DAEMON_CONSOLE_PORT,
410
+ fileExists: (p) => fs.existsSync(p),
411
+ archiveFile: (src, dest) => fs.promises.rename(src, dest),
412
+ pollForPR: async (branchPattern, timeoutMs) => {
413
+ const pollIntervalMs = 30000;
414
+ const deadline = Date.now() + timeoutMs;
415
+ while (Date.now() < deadline) {
416
+ try {
417
+ const { stdout } = await execFileAsync('gh', ['pr', 'list', '--head', branchPattern, '--json', 'url', '--limit', '1'], { timeout: 30000 });
418
+ const parsed = JSON.parse(stdout);
419
+ if (parsed.length > 0 && parsed[0] && parsed[0].url) {
420
+ return parsed[0].url;
421
+ }
422
+ }
423
+ catch {
424
+ }
425
+ const remaining = deadline - Date.now();
426
+ if (remaining <= 0)
427
+ break;
428
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remaining)));
429
+ }
430
+ return null;
431
+ },
432
+ postToOutbox: async (message, metadata) => {
433
+ const workrailDir = path.join(os.homedir(), '.workrail');
434
+ const outboxPath = path.join(workrailDir, 'outbox.jsonl');
435
+ await fs.promises.mkdir(workrailDir, { recursive: true });
436
+ const entry = JSON.stringify({
437
+ id: (0, node_crypto_1.randomUUID)(),
438
+ message,
439
+ metadata,
440
+ timestamp: new Date().toISOString(),
441
+ });
442
+ await fs.promises.appendFile(outboxPath, entry + '\n', 'utf-8');
443
+ },
444
+ pollOutboxAck: async (requestId, timeoutMs) => {
445
+ const pollIntervalMs = 5 * 60 * 1000;
446
+ const workrailDir = path.join(os.homedir(), '.workrail');
447
+ const outboxPath = path.join(workrailDir, 'outbox.jsonl');
448
+ const cursorPath = path.join(workrailDir, 'inbox-cursor.json');
449
+ let snapshotCount = 0;
450
+ try {
451
+ const outboxContent = await fs.promises.readFile(outboxPath, 'utf-8');
452
+ snapshotCount = outboxContent.split('\n').filter((l) => l.trim() !== '').length;
453
+ }
454
+ catch {
455
+ }
456
+ void requestId;
457
+ const deadline = Date.now() + timeoutMs;
458
+ while (Date.now() < deadline) {
459
+ const remaining = deadline - Date.now();
460
+ if (remaining <= 0)
461
+ break;
462
+ await new Promise((resolve) => setTimeout(resolve, Math.min(pollIntervalMs, remaining)));
463
+ try {
464
+ const cursorContent = await fs.promises.readFile(cursorPath, 'utf-8');
465
+ const cursor = JSON.parse(cursorContent);
466
+ if (typeof cursor.lastReadCount === 'number' && cursor.lastReadCount > snapshotCount) {
467
+ return 'acked';
468
+ }
469
+ }
470
+ catch {
471
+ }
472
+ }
473
+ return 'timeout';
474
+ },
475
+ };
476
+ const modeExecutors = {
477
+ runQuickReview: quick_review_js_1.runQuickReviewPipeline,
478
+ runReviewOnly: review_only_js_1.runReviewOnlyPipeline,
479
+ runImplement: implement_js_1.runImplementPipeline,
480
+ runFull: full_pipeline_js_1.runFullPipeline,
481
+ };
187
482
  const runWorkflowFn = options.runWorkflowFn ?? workflow_runner_js_1.runWorkflow;
188
- const router = new trigger_router_js_1.TriggerRouter(triggerIndex, ctx, apiKey, runWorkflowFn, undefined, maxConcurrentSessions, options.emitter, notificationService, steerRegistry);
483
+ const router = new trigger_router_js_1.TriggerRouter(triggerIndex, ctx, apiKey, runWorkflowFn, undefined, maxConcurrentSessions, options.emitter, notificationService, steerRegistry, coordinatorDeps, modeExecutors);
189
484
  const app = createTriggerApp(router);
190
485
  const allTriggers = [...triggerIndex.values()];
191
486
  const polledEventStore = new polled_event_store_js_1.PolledEventStore(env);
@@ -36,11 +36,13 @@ export declare class TriggerRouter {
36
36
  private readonly emitter;
37
37
  private readonly notificationService;
38
38
  private readonly steerRegistry;
39
- constructor(index: ReadonlyMap<string, TriggerDefinition>, ctx: V2ToolContext, apiKey: string, runWorkflowFn: RunWorkflowFn, execFn?: ExecFn, maxConcurrentSessions?: number, emitter?: DaemonEventEmitter, notificationService?: NotificationService, steerRegistry?: SteerRegistry);
39
+ private readonly _coordinatorDeps;
40
+ private readonly _modeExecutors;
41
+ constructor(index: ReadonlyMap<string, TriggerDefinition>, ctx: V2ToolContext, apiKey: string, runWorkflowFn: RunWorkflowFn, execFn?: ExecFn, maxConcurrentSessions?: number, emitter?: DaemonEventEmitter, notificationService?: NotificationService, steerRegistry?: SteerRegistry, coordinatorDeps?: AdaptiveCoordinatorDeps, modeExecutors?: ModeExecutors);
40
42
  get activeSessions(): number;
41
43
  get maxConcurrentSessions(): number;
42
44
  route(event: WebhookEvent): RouteResult;
43
45
  dispatch(workflowTrigger: WorkflowTrigger): string;
44
46
  listTriggers(): readonly TriggerDefinition[];
45
- dispatchAdaptivePipeline(goal: string, workspace: string, coordinatorDeps: AdaptiveCoordinatorDeps, modeExecutors: ModeExecutors, context?: Readonly<Record<string, unknown>>): ReturnType<typeof runAdaptivePipeline>;
47
+ dispatchAdaptivePipeline(goal: string, workspace: string, context?: Readonly<Record<string, unknown>>, coordinatorDeps?: AdaptiveCoordinatorDeps, modeExecutors?: ModeExecutors): ReturnType<typeof runAdaptivePipeline>;
46
48
  }