@exaudeus/workrail 3.45.0 → 3.47.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 (47) 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-pipeline.d.ts +17 -0
  4. package/dist/cli/commands/worktrain-pipeline.js +121 -0
  5. package/dist/console-ui/assets/{index-BpanIvmi.js → index-B77l3WBR.js} +1 -1
  6. package/dist/console-ui/index.html +1 -1
  7. package/dist/coordinators/adaptive-pipeline.d.ts +57 -0
  8. package/dist/coordinators/adaptive-pipeline.js +104 -0
  9. package/dist/coordinators/modes/full-pipeline.d.ts +4 -0
  10. package/dist/coordinators/modes/full-pipeline.js +256 -0
  11. package/dist/coordinators/modes/implement-shared.d.ts +5 -0
  12. package/dist/coordinators/modes/implement-shared.js +205 -0
  13. package/dist/coordinators/modes/implement.d.ts +3 -0
  14. package/dist/coordinators/modes/implement.js +108 -0
  15. package/dist/coordinators/modes/quick-review.d.ts +3 -0
  16. package/dist/coordinators/modes/quick-review.js +37 -0
  17. package/dist/coordinators/modes/review-only.d.ts +2 -0
  18. package/dist/coordinators/modes/review-only.js +28 -0
  19. package/dist/coordinators/routing/route-task.d.ts +21 -0
  20. package/dist/coordinators/routing/route-task.js +55 -0
  21. package/dist/manifest.json +107 -35
  22. package/dist/mcp/output-schemas.d.ts +16 -16
  23. package/dist/trigger/adapters/github-queue-poller.js +10 -7
  24. package/dist/trigger/github-queue-config.d.ts +1 -0
  25. package/dist/trigger/github-queue-config.js +9 -0
  26. package/dist/trigger/polling-scheduler.js +8 -1
  27. package/dist/trigger/trigger-listener.js +296 -1
  28. package/dist/trigger/trigger-router.d.ts +6 -1
  29. package/dist/trigger/trigger-router.js +26 -1
  30. package/dist/trigger/trigger-store.js +10 -0
  31. package/dist/trigger/types.d.ts +2 -0
  32. package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.d.ts +29 -0
  33. package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.js +26 -0
  34. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
  35. package/dist/v2/durable-core/schemas/artifacts/index.js +7 -1
  36. package/dist/v2/durable-core/schemas/artifacts/review-verdict.d.ts +5 -0
  37. package/dist/v2/durable-core/schemas/artifacts/review-verdict.js +12 -0
  38. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
  39. package/docs/design/connect-adaptive-dispatch-candidates.md +153 -0
  40. package/docs/design/connect-adaptive-dispatch-design-review.md +88 -0
  41. package/docs/design/connect-adaptive-dispatch-implementation-plan.md +209 -0
  42. package/docs/design/queue-label-support-candidates.md +83 -0
  43. package/docs/design/queue-label-support-design-review.md +62 -0
  44. package/docs/design/queue-label-support-implementation-plan.md +158 -0
  45. package/docs/ideas/backlog.md +160 -0
  46. package/package.json +1 -1
  47. package/workflows/mr-review-workflow.agentic.v2.json +1 -1
@@ -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);
@@ -4,6 +4,8 @@ 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
6
  import type { NotificationService } from './notification-service.js';
7
+ import type { AdaptiveCoordinatorDeps, ModeExecutors } from '../coordinators/adaptive-pipeline.js';
8
+ import { runAdaptivePipeline } from '../coordinators/adaptive-pipeline.js';
7
9
  export type RouteError = {
8
10
  readonly kind: 'not_found';
9
11
  readonly triggerId: string;
@@ -34,10 +36,13 @@ export declare class TriggerRouter {
34
36
  private readonly emitter;
35
37
  private readonly notificationService;
36
38
  private readonly steerRegistry;
37
- 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);
38
42
  get activeSessions(): number;
39
43
  get maxConcurrentSessions(): number;
40
44
  route(event: WebhookEvent): RouteResult;
41
45
  dispatch(workflowTrigger: WorkflowTrigger): string;
42
46
  listTriggers(): readonly TriggerDefinition[];
47
+ dispatchAdaptivePipeline(goal: string, workspace: string, context?: Readonly<Record<string, unknown>>, coordinatorDeps?: AdaptiveCoordinatorDeps, modeExecutors?: ModeExecutors): ReturnType<typeof runAdaptivePipeline>;
43
48
  }
@@ -42,6 +42,7 @@ const assert_never_js_1 = require("../runtime/assert-never.js");
42
42
  const index_js_1 = require("../v2/infra/in-memory/keyed-async-queue/index.js");
43
43
  const delivery_client_js_1 = require("./delivery-client.js");
44
44
  const delivery_action_js_1 = require("./delivery-action.js");
45
+ const adaptive_pipeline_js_1 = require("../coordinators/adaptive-pipeline.js");
45
46
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
46
47
  function interpolateGoalTemplate(template, staticGoal, payload, triggerId) {
47
48
  const TOKEN_RE = /\{\{([^}]+)\}\}/g;
@@ -203,7 +204,7 @@ class Semaphore {
203
204
  }
204
205
  const DEFAULT_MAX_CONCURRENT_SESSIONS = 3;
205
206
  class TriggerRouter {
206
- constructor(index, ctx, apiKey, runWorkflowFn, execFn, maxConcurrentSessions, emitter, notificationService, steerRegistry) {
207
+ constructor(index, ctx, apiKey, runWorkflowFn, execFn, maxConcurrentSessions, emitter, notificationService, steerRegistry, coordinatorDeps, modeExecutors) {
207
208
  this.index = index;
208
209
  this.ctx = ctx;
209
210
  this.apiKey = apiKey;
@@ -213,6 +214,8 @@ class TriggerRouter {
213
214
  this.emitter = emitter;
214
215
  this.notificationService = notificationService;
215
216
  this.steerRegistry = steerRegistry;
217
+ this._coordinatorDeps = coordinatorDeps;
218
+ this._modeExecutors = modeExecutors;
216
219
  const requested = maxConcurrentSessions ?? DEFAULT_MAX_CONCURRENT_SESSIONS;
217
220
  const cap = Number.isNaN(requested) ? DEFAULT_MAX_CONCURRENT_SESSIONS : requested;
218
221
  if (cap < 1) {
@@ -384,5 +387,27 @@ class TriggerRouter {
384
387
  listTriggers() {
385
388
  return [...this.index.values()];
386
389
  }
390
+ async dispatchAdaptivePipeline(goal, workspace, context, coordinatorDeps, modeExecutors) {
391
+ const effectiveDeps = coordinatorDeps ?? this._coordinatorDeps;
392
+ const effectiveExecutors = modeExecutors ?? this._modeExecutors;
393
+ if (effectiveDeps === undefined || effectiveExecutors === undefined) {
394
+ console.warn('[TriggerRouter] dispatchAdaptivePipeline called but coordinatorDeps not injected -- ' +
395
+ 'adaptive dispatch disabled. Inject coordinatorDeps and modeExecutors in the ' +
396
+ 'TriggerRouter constructor to activate. Returning escalated outcome.');
397
+ return {
398
+ kind: 'escalated',
399
+ escalationReason: {
400
+ phase: 'dispatch',
401
+ reason: 'coordinatorDeps or modeExecutors not injected into TriggerRouter',
402
+ },
403
+ };
404
+ }
405
+ const opts = {
406
+ goal,
407
+ workspace,
408
+ taskCandidate: context,
409
+ };
410
+ return (0, adaptive_pipeline_js_1.runAdaptivePipeline)(effectiveDeps, opts, effectiveExecutors);
411
+ }
387
412
  }
388
413
  exports.TriggerRouter = TriggerRouter;
@@ -400,6 +400,12 @@ function setTriggerField(trigger, key, value) {
400
400
  case 'branchPrefix':
401
401
  trigger.branchPrefix = value;
402
402
  break;
403
+ case 'queueType':
404
+ trigger.queueType = value;
405
+ break;
406
+ case 'queueLabel':
407
+ trigger.queueLabel = value;
408
+ break;
403
409
  default:
404
410
  break;
405
411
  }
@@ -780,11 +786,15 @@ function validateAndResolveTrigger(raw, env, workspaces = {}) {
780
786
  }
781
787
  queuePollIntervalSeconds = asNumber;
782
788
  }
789
+ const rawQueueType = raw.queueType?.trim();
790
+ const rawQueueLabel = raw.queueLabel?.trim();
783
791
  pollingSource = {
784
792
  provider: 'github_queue_poll',
785
793
  repo: queueSrc.repo.trim(),
786
794
  token: queueTokenResult.value,
787
795
  pollIntervalSeconds: queuePollIntervalSeconds,
796
+ ...(rawQueueType ? { queueType: rawQueueType } : {}),
797
+ ...(rawQueueLabel ? { queueLabel: rawQueueLabel } : {}),
788
798
  };
789
799
  }
790
800
  else if (raw.source) {
@@ -38,6 +38,8 @@ export interface GitHubQueuePollingSource {
38
38
  readonly repo: string;
39
39
  readonly token: string;
40
40
  readonly pollIntervalSeconds: number;
41
+ readonly queueType?: string;
42
+ readonly queueLabel?: string;
41
43
  }
42
44
  export interface TaskCandidate {
43
45
  readonly issueNumber: number;
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ export declare const DISCOVERY_HANDOFF_CONTRACT_REF: "wr.contracts.discovery_handoff";
3
+ export declare const DiscoveryHandoffArtifactV1Schema: z.ZodObject<{
4
+ kind: z.ZodLiteral<"wr.discovery_handoff">;
5
+ version: z.ZodLiteral<1>;
6
+ selectedDirection: z.ZodString;
7
+ designDocPath: z.ZodString;
8
+ confidenceBand: z.ZodEnum<["high", "medium", "low"]>;
9
+ keyInvariants: z.ZodArray<z.ZodString, "many">;
10
+ }, "strict", z.ZodTypeAny, {
11
+ kind: "wr.discovery_handoff";
12
+ version: 1;
13
+ selectedDirection: string;
14
+ designDocPath: string;
15
+ confidenceBand: "low" | "high" | "medium";
16
+ keyInvariants: string[];
17
+ }, {
18
+ kind: "wr.discovery_handoff";
19
+ version: 1;
20
+ selectedDirection: string;
21
+ designDocPath: string;
22
+ confidenceBand: "low" | "high" | "medium";
23
+ keyInvariants: string[];
24
+ }>;
25
+ export type DiscoveryHandoffArtifactV1 = z.infer<typeof DiscoveryHandoffArtifactV1Schema>;
26
+ export declare function isDiscoveryHandoffArtifact(artifact: unknown): artifact is {
27
+ readonly kind: 'wr.discovery_handoff';
28
+ };
29
+ export declare function parseDiscoveryHandoffArtifact(artifact: unknown): DiscoveryHandoffArtifactV1 | null;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiscoveryHandoffArtifactV1Schema = exports.DISCOVERY_HANDOFF_CONTRACT_REF = void 0;
4
+ exports.isDiscoveryHandoffArtifact = isDiscoveryHandoffArtifact;
5
+ exports.parseDiscoveryHandoffArtifact = parseDiscoveryHandoffArtifact;
6
+ const zod_1 = require("zod");
7
+ exports.DISCOVERY_HANDOFF_CONTRACT_REF = 'wr.contracts.discovery_handoff';
8
+ exports.DiscoveryHandoffArtifactV1Schema = zod_1.z
9
+ .object({
10
+ kind: zod_1.z.literal('wr.discovery_handoff'),
11
+ version: zod_1.z.literal(1),
12
+ selectedDirection: zod_1.z.string().min(1),
13
+ designDocPath: zod_1.z.string(),
14
+ confidenceBand: zod_1.z.enum(['high', 'medium', 'low']),
15
+ keyInvariants: zod_1.z.array(zod_1.z.string().min(1)),
16
+ })
17
+ .strict();
18
+ function isDiscoveryHandoffArtifact(artifact) {
19
+ return (typeof artifact === 'object' &&
20
+ artifact !== null &&
21
+ artifact.kind === 'wr.discovery_handoff');
22
+ }
23
+ function parseDiscoveryHandoffArtifact(artifact) {
24
+ const result = exports.DiscoveryHandoffArtifactV1Schema.safeParse(artifact);
25
+ return result.success ? result.data : null;
26
+ }
@@ -2,6 +2,7 @@ export { ASSESSMENT_CONTRACT_REF, AssessmentArtifactV1Schema, AssessmentDimensio
2
2
  export { LOOP_CONTROL_CONTRACT_REF, LoopControlDecisionSchema, LoopControlMetadataV1Schema, LoopControlArtifactV1Schema, isLoopControlArtifact, parseLoopControlArtifact, findLoopControlArtifact, type LoopControlDecision, type LoopControlMetadataV1, type LoopControlArtifactV1, } from './loop-control.js';
3
3
  export { COORDINATOR_SIGNAL_CONTRACT_REF, CoordinatorSignalKindSchema, CoordinatorSignalArtifactV1Schema, isCoordinatorSignalArtifact, parseCoordinatorSignalArtifact, type CoordinatorSignalKind, type CoordinatorSignalArtifactV1, } from './coordinator-signal.js';
4
4
  export { REVIEW_VERDICT_CONTRACT_REF, ReviewVerdictArtifactV1Schema, isReviewVerdictArtifact, parseReviewVerdictArtifact, type ReviewVerdictArtifactV1, } from './review-verdict.js';
5
- export declare const ARTIFACT_CONTRACT_REFS: readonly ["wr.contracts.assessment", "wr.contracts.loop_control", "wr.contracts.coordinator_signal", "wr.contracts.review_verdict"];
5
+ export { DISCOVERY_HANDOFF_CONTRACT_REF, DiscoveryHandoffArtifactV1Schema, isDiscoveryHandoffArtifact, parseDiscoveryHandoffArtifact, type DiscoveryHandoffArtifactV1, } from './discovery-handoff.js';
6
+ export declare const ARTIFACT_CONTRACT_REFS: readonly ["wr.contracts.assessment", "wr.contracts.loop_control", "wr.contracts.coordinator_signal", "wr.contracts.review_verdict", "wr.contracts.discovery_handoff"];
6
7
  export type ArtifactContractRef = (typeof ARTIFACT_CONTRACT_REFS)[number];
7
8
  export declare function isValidContractRef(ref: string): ref is ArtifactContractRef;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ARTIFACT_CONTRACT_REFS = exports.parseReviewVerdictArtifact = exports.isReviewVerdictArtifact = exports.ReviewVerdictArtifactV1Schema = exports.REVIEW_VERDICT_CONTRACT_REF = exports.parseCoordinatorSignalArtifact = exports.isCoordinatorSignalArtifact = exports.CoordinatorSignalArtifactV1Schema = exports.CoordinatorSignalKindSchema = exports.COORDINATOR_SIGNAL_CONTRACT_REF = exports.findLoopControlArtifact = exports.parseLoopControlArtifact = exports.isLoopControlArtifact = exports.LoopControlArtifactV1Schema = exports.LoopControlMetadataV1Schema = exports.LoopControlDecisionSchema = exports.LOOP_CONTROL_CONTRACT_REF = exports.parseAssessmentArtifact = exports.isAssessmentArtifact = exports.AssessmentDimensionSubmissionSchema = exports.AssessmentArtifactV1Schema = exports.ASSESSMENT_CONTRACT_REF = void 0;
3
+ exports.ARTIFACT_CONTRACT_REFS = exports.parseDiscoveryHandoffArtifact = exports.isDiscoveryHandoffArtifact = exports.DiscoveryHandoffArtifactV1Schema = exports.DISCOVERY_HANDOFF_CONTRACT_REF = exports.parseReviewVerdictArtifact = exports.isReviewVerdictArtifact = exports.ReviewVerdictArtifactV1Schema = exports.REVIEW_VERDICT_CONTRACT_REF = exports.parseCoordinatorSignalArtifact = exports.isCoordinatorSignalArtifact = exports.CoordinatorSignalArtifactV1Schema = exports.CoordinatorSignalKindSchema = exports.COORDINATOR_SIGNAL_CONTRACT_REF = exports.findLoopControlArtifact = exports.parseLoopControlArtifact = exports.isLoopControlArtifact = exports.LoopControlArtifactV1Schema = exports.LoopControlMetadataV1Schema = exports.LoopControlDecisionSchema = exports.LOOP_CONTROL_CONTRACT_REF = exports.parseAssessmentArtifact = exports.isAssessmentArtifact = exports.AssessmentDimensionSubmissionSchema = exports.AssessmentArtifactV1Schema = exports.ASSESSMENT_CONTRACT_REF = void 0;
4
4
  exports.isValidContractRef = isValidContractRef;
5
5
  var assessment_js_1 = require("./assessment.js");
6
6
  Object.defineProperty(exports, "ASSESSMENT_CONTRACT_REF", { enumerable: true, get: function () { return assessment_js_1.ASSESSMENT_CONTRACT_REF; } });
@@ -27,11 +27,17 @@ Object.defineProperty(exports, "REVIEW_VERDICT_CONTRACT_REF", { enumerable: true
27
27
  Object.defineProperty(exports, "ReviewVerdictArtifactV1Schema", { enumerable: true, get: function () { return review_verdict_js_1.ReviewVerdictArtifactV1Schema; } });
28
28
  Object.defineProperty(exports, "isReviewVerdictArtifact", { enumerable: true, get: function () { return review_verdict_js_1.isReviewVerdictArtifact; } });
29
29
  Object.defineProperty(exports, "parseReviewVerdictArtifact", { enumerable: true, get: function () { return review_verdict_js_1.parseReviewVerdictArtifact; } });
30
+ var discovery_handoff_js_1 = require("./discovery-handoff.js");
31
+ Object.defineProperty(exports, "DISCOVERY_HANDOFF_CONTRACT_REF", { enumerable: true, get: function () { return discovery_handoff_js_1.DISCOVERY_HANDOFF_CONTRACT_REF; } });
32
+ Object.defineProperty(exports, "DiscoveryHandoffArtifactV1Schema", { enumerable: true, get: function () { return discovery_handoff_js_1.DiscoveryHandoffArtifactV1Schema; } });
33
+ Object.defineProperty(exports, "isDiscoveryHandoffArtifact", { enumerable: true, get: function () { return discovery_handoff_js_1.isDiscoveryHandoffArtifact; } });
34
+ Object.defineProperty(exports, "parseDiscoveryHandoffArtifact", { enumerable: true, get: function () { return discovery_handoff_js_1.parseDiscoveryHandoffArtifact; } });
30
35
  exports.ARTIFACT_CONTRACT_REFS = [
31
36
  'wr.contracts.assessment',
32
37
  'wr.contracts.loop_control',
33
38
  'wr.contracts.coordinator_signal',
34
39
  'wr.contracts.review_verdict',
40
+ 'wr.contracts.discovery_handoff',
35
41
  ];
36
42
  function isValidContractRef(ref) {
37
43
  return exports.ARTIFACT_CONTRACT_REFS.includes(ref);
@@ -7,12 +7,15 @@ export declare const ReviewVerdictArtifactV1Schema: z.ZodObject<{
7
7
  findings: z.ZodArray<z.ZodObject<{
8
8
  severity: z.ZodEnum<["critical", "major", "minor", "nit"]>;
9
9
  summary: z.ZodString;
10
+ findingCategory: z.ZodOptional<z.ZodEnum<["correctness", "security", "architecture", "ux", "performance", "testing", "style"]>>;
10
11
  }, "strict", z.ZodTypeAny, {
11
12
  severity: "critical" | "minor" | "major" | "nit";
12
13
  summary: string;
14
+ findingCategory?: "correctness" | "security" | "architecture" | "ux" | "performance" | "testing" | "style" | undefined;
13
15
  }, {
14
16
  severity: "critical" | "minor" | "major" | "nit";
15
17
  summary: string;
18
+ findingCategory?: "correctness" | "security" | "architecture" | "ux" | "performance" | "testing" | "style" | undefined;
16
19
  }>, "many">;
17
20
  summary: z.ZodString;
18
21
  }, "strict", z.ZodTypeAny, {
@@ -23,6 +26,7 @@ export declare const ReviewVerdictArtifactV1Schema: z.ZodObject<{
23
26
  findings: {
24
27
  severity: "critical" | "minor" | "major" | "nit";
25
28
  summary: string;
29
+ findingCategory?: "correctness" | "security" | "architecture" | "ux" | "performance" | "testing" | "style" | undefined;
26
30
  }[];
27
31
  }, {
28
32
  kind: "wr.review_verdict";
@@ -32,6 +36,7 @@ export declare const ReviewVerdictArtifactV1Schema: z.ZodObject<{
32
36
  findings: {
33
37
  severity: "critical" | "minor" | "major" | "nit";
34
38
  summary: string;
39
+ findingCategory?: "correctness" | "security" | "architecture" | "ux" | "performance" | "testing" | "style" | undefined;
35
40
  }[];
36
41
  }>;
37
42
  export type ReviewVerdictArtifactV1 = z.infer<typeof ReviewVerdictArtifactV1Schema>;