@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.
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-trigger-test.d.ts +21 -0
- package/dist/cli/commands/worktrain-trigger-test.js +123 -0
- package/dist/cli-worktrain.js +65 -0
- package/dist/console-ui/assets/{index-BQFhoMcY.js → index-CecBgrR7.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/modes/implement-shared.d.ts +2 -1
- package/dist/coordinators/modes/implement-shared.js +7 -3
- package/dist/manifest.json +44 -36
- package/dist/mcp/output-schemas.d.ts +2 -2
- package/dist/trigger/adapters/github-queue-poller.js +10 -7
- package/dist/trigger/github-queue-config.d.ts +1 -0
- package/dist/trigger/github-queue-config.js +9 -0
- package/dist/trigger/polling-scheduler.js +8 -1
- package/dist/trigger/trigger-listener.js +296 -1
- package/dist/trigger/trigger-router.d.ts +4 -2
- package/dist/trigger/trigger-router.js +19 -3
- package/dist/trigger/trigger-store.js +10 -0
- package/dist/trigger/types.d.ts +2 -0
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +5 -0
- package/dist/v2/durable-core/schemas/artifacts/review-verdict.js +12 -0
- package/docs/design/connect-adaptive-dispatch-candidates.md +153 -0
- package/docs/design/connect-adaptive-dispatch-design-review.md +88 -0
- package/docs/design/connect-adaptive-dispatch-implementation-plan.md +209 -0
- package/docs/design/queue-label-support-candidates.md +83 -0
- package/docs/design/queue-label-support-design-review.md +62 -0
- package/docs/design/queue-label-support-implementation-plan.md +158 -0
- package/docs/ideas/backlog.md +147 -0
- package/package.json +1 -1
- package/workflows/mr-review-workflow.agentic.v2.json +1 -1
package/dist/manifest.json
CHANGED
|
@@ -238,8 +238,8 @@
|
|
|
238
238
|
"bytes": 31
|
|
239
239
|
},
|
|
240
240
|
"cli-worktrain.js": {
|
|
241
|
-
"sha256": "
|
|
242
|
-
"bytes":
|
|
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": "
|
|
262
|
-
"bytes":
|
|
261
|
+
"sha256": "240dc5f7055f8f4d602d41c4c659c800f80207af084683b1bd0a587cf68bfac0",
|
|
262
|
+
"bytes": 2396
|
|
263
263
|
},
|
|
264
264
|
"cli/commands/index.js": {
|
|
265
|
-
"sha256": "
|
|
266
|
-
"bytes":
|
|
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-
|
|
461
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
530
|
-
"bytes":
|
|
537
|
+
"sha256": "fbad9d91d84d2112b273175618686489a7f106385e0e62d6cab80804d6d0f2d7",
|
|
538
|
+
"bytes": 708
|
|
531
539
|
},
|
|
532
540
|
"coordinators/modes/implement-shared.js": {
|
|
533
|
-
"sha256": "
|
|
534
|
-
"bytes":
|
|
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": "
|
|
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": "
|
|
1622
|
-
"bytes":
|
|
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": "
|
|
1658
|
-
"bytes":
|
|
1665
|
+
"sha256": "4dd68c2b880f808aa67c3b6be1867299c0b49ce5cf7e39f4f093dbd341d1a1e8",
|
|
1666
|
+
"bytes": 800
|
|
1659
1667
|
},
|
|
1660
1668
|
"trigger/github-queue-config.js": {
|
|
1661
|
-
"sha256": "
|
|
1662
|
-
"bytes":
|
|
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": "
|
|
1694
|
-
"bytes":
|
|
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": "
|
|
1702
|
-
"bytes":
|
|
1709
|
+
"sha256": "1c73115b26d9274c86fd3b1e19f5eae2754d58e248949b6eaf0e7b7074b91c8c",
|
|
1710
|
+
"bytes": 25421
|
|
1703
1711
|
},
|
|
1704
1712
|
"trigger/trigger-router.d.ts": {
|
|
1705
|
-
"sha256": "
|
|
1706
|
-
"bytes":
|
|
1713
|
+
"sha256": "44f1bfefafee4955bad74db9e61ecb6a49ea426c46100753f6bdb3a7f7b583dd",
|
|
1714
|
+
"bytes": 2671
|
|
1707
1715
|
},
|
|
1708
1716
|
"trigger/trigger-router.js": {
|
|
1709
|
-
"sha256": "
|
|
1710
|
-
"bytes":
|
|
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": "
|
|
1718
|
-
"bytes":
|
|
1725
|
+
"sha256": "8e85e0bdbd1596e70c23667e84ed380d6f0cee10028cd1a41163cda3467c2bdc",
|
|
1726
|
+
"bytes": 38559
|
|
1719
1727
|
},
|
|
1720
1728
|
"trigger/types.d.ts": {
|
|
1721
|
-
"sha256": "
|
|
1722
|
-
"bytes":
|
|
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": "
|
|
2266
|
-
"bytes":
|
|
2273
|
+
"sha256": "2b9f7ae6b3fafe6c26f266bb249c8c286144eb1f753c803d4358ef81e30d1736",
|
|
2274
|
+
"bytes": 2211
|
|
2267
2275
|
},
|
|
2268
2276
|
"v2/durable-core/schemas/artifacts/review-verdict.js": {
|
|
2269
|
-
"sha256": "
|
|
2270
|
-
"bytes":
|
|
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
|
|
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
|
-
|
|
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,
|
|
47
|
+
dispatchAdaptivePipeline(goal: string, workspace: string, context?: Readonly<Record<string, unknown>>, coordinatorDeps?: AdaptiveCoordinatorDeps, modeExecutors?: ModeExecutors): ReturnType<typeof runAdaptivePipeline>;
|
|
46
48
|
}
|