@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.
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/worktrain-pipeline.d.ts +17 -0
- package/dist/cli/commands/worktrain-pipeline.js +121 -0
- package/dist/console-ui/assets/{index-BpanIvmi.js → index-B77l3WBR.js} +1 -1
- package/dist/console-ui/index.html +1 -1
- package/dist/coordinators/adaptive-pipeline.d.ts +57 -0
- package/dist/coordinators/adaptive-pipeline.js +104 -0
- package/dist/coordinators/modes/full-pipeline.d.ts +4 -0
- package/dist/coordinators/modes/full-pipeline.js +256 -0
- package/dist/coordinators/modes/implement-shared.d.ts +5 -0
- package/dist/coordinators/modes/implement-shared.js +205 -0
- package/dist/coordinators/modes/implement.d.ts +3 -0
- package/dist/coordinators/modes/implement.js +108 -0
- package/dist/coordinators/modes/quick-review.d.ts +3 -0
- package/dist/coordinators/modes/quick-review.js +37 -0
- package/dist/coordinators/modes/review-only.d.ts +2 -0
- package/dist/coordinators/modes/review-only.js +28 -0
- package/dist/coordinators/routing/route-task.d.ts +21 -0
- package/dist/coordinators/routing/route-task.js +55 -0
- package/dist/manifest.json +107 -35
- package/dist/mcp/output-schemas.d.ts +16 -16
- 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 +6 -1
- package/dist/trigger/trigger-router.js +26 -1
- package/dist/trigger/trigger-store.js +10 -0
- package/dist/trigger/types.d.ts +2 -0
- package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.d.ts +29 -0
- package/dist/v2/durable-core/schemas/artifacts/discovery-handoff.js +26 -0
- package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
- package/dist/v2/durable-core/schemas/artifacts/index.js +7 -1
- 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/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +8 -8
- 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 +160 -0
- package/package.json +1 -1
- 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
|
|
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
|
-
|
|
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) {
|
package/dist/trigger/types.d.ts
CHANGED
|
@@ -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
|
|
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>;
|