@open-agent-toolkit/cli 0.1.66 → 0.1.72
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/assets/agents/oat-reviewer.md +0 -1
- package/assets/docs/cli-utilities/configuration.md +27 -0
- package/assets/docs/cli-utilities/index.md +1 -1
- package/assets/docs/cli-utilities/workflow-gates.md +147 -5
- package/assets/docs/reference/cli-reference.md +5 -1
- package/assets/docs/workflows/projects/dispatch-ceiling.md +25 -8
- package/assets/docs/workflows/projects/orchestration-model.md +21 -0
- package/assets/docs/workflows/projects/pr-flow.md +12 -1
- package/assets/docs/workflows/projects/review-flavors.md +9 -0
- package/assets/docs/workflows/projects/reviews.md +21 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-dispatch-subagents/SKILL.md +21 -1
- package/assets/skills/oat-dispatch-subagents/references/provider-claude.md +21 -0
- package/assets/skills/oat-dispatch-subagents/references/provider-codex.md +16 -0
- package/assets/skills/oat-dispatch-subagents/references/provider-cursor.md +21 -0
- package/assets/skills/oat-project-autonomous/SKILL.md +1 -1
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +5 -2
- package/assets/skills/oat-project-complete/SKILL.md +20 -2
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +5 -2
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +12 -1
- package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +24 -9
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +5 -2
- package/assets/skills/oat-project-implement/references/phase-execution.md +5 -2
- package/assets/skills/oat-project-next/SKILL.md +21 -6
- package/assets/skills/oat-project-plan/SKILL.md +6 -1
- package/assets/skills/oat-project-plan-writing/SKILL.md +41 -34
- package/assets/skills/oat-project-pr-final/SKILL.md +20 -5
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +5 -2
- package/assets/skills/oat-project-pr-progress/SKILL.md +18 -3
- package/assets/skills/oat-project-progress/SKILL.md +4 -1
- package/assets/skills/oat-project-quick-start/SKILL.md +6 -1
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +5 -2
- package/assets/skills/oat-project-review-provide/SKILL.md +68 -2
- package/assets/skills/oat-project-review-receive/SKILL.md +46 -18
- package/assets/skills/oat-project-review-receive-remote/SKILL.md +41 -8
- package/dist/commands/cleanup/project/project.utils.d.ts.map +1 -1
- package/dist/commands/cleanup/project/project.utils.js +19 -1
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +65 -1
- package/dist/commands/gate/__fixtures__/fake-runtime.d.mts +3 -0
- package/dist/commands/gate/__fixtures__/fake-runtime.d.mts.map +1 -0
- package/dist/commands/gate/__fixtures__/fake-runtime.mjs +167 -0
- package/dist/commands/gate/activity-probes.d.ts +35 -0
- package/dist/commands/gate/activity-probes.d.ts.map +1 -0
- package/dist/commands/gate/activity-probes.js +135 -0
- package/dist/commands/gate/branch-local-cli.d.ts +31 -0
- package/dist/commands/gate/branch-local-cli.d.ts.map +1 -0
- package/dist/commands/gate/branch-local-cli.js +116 -0
- package/dist/commands/gate/index.d.ts +31 -0
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +465 -31
- package/dist/commands/gate/route.d.ts +22 -0
- package/dist/commands/gate/route.d.ts.map +1 -0
- package/dist/commands/gate/route.js +109 -0
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +100 -16
- package/dist/commands/review/latest.d.ts +1 -0
- package/dist/commands/review/latest.d.ts.map +1 -1
- package/dist/commands/review/latest.js +96 -22
- package/dist/config/oat-config.d.ts +9 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +23 -0
- package/dist/config/resolve.js +7 -0
- package/package.json +2 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
4
5
|
import { basename, isAbsolute, join, relative } from 'node:path';
|
|
5
6
|
import { buildCommandContext, } from '../../app/command-context.js';
|
|
6
7
|
import { getFrontmatterBlock, parseFrontmatterScalarFields, parseGeneratedTime, } from '../shared/frontmatter.js';
|
|
7
8
|
import { readGlobalOptions } from '../shared/shared.utils.js';
|
|
8
|
-
import {
|
|
9
|
+
import { parseJsonConfig } from '../../config/json.js';
|
|
10
|
+
import { BUILTIN_EXEC_TARGETS, MAX_GATE_TIMEOUT_MS, MIN_GATE_TIMEOUT_MS, isValidGateTimeoutMs, readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
9
11
|
import { resolveEffectiveConfig, resolveExecTargetViews, resolveExecTargets, resolveGate, } from '../../config/resolve.js';
|
|
10
12
|
import { dirExists, fileExists } from '../../fs/io.js';
|
|
11
13
|
import { normalizeToPosixPath, resolveProjectRoot, validateRealPathWithinScope, } from '../../fs/paths.js';
|
|
@@ -15,7 +17,10 @@ import { resolveIdentityConfidence, } from '../../providers/identity/provenance.
|
|
|
15
17
|
import { parseDispatchStamps } from '../../providers/identity/stamp.js';
|
|
16
18
|
import { Command } from 'commander';
|
|
17
19
|
import YAML from 'yaml';
|
|
20
|
+
import { createGateActivityProbe, } from './activity-probes.js';
|
|
21
|
+
import { createBranchLocalGateCli, currentGateCliLaunch, readGateRouteReceipt, removeBranchLocalGateCli, } from './branch-local-cli.js';
|
|
18
22
|
import { parseReviewGateVerdict, severityDisplayName, } from './review-verdict.js';
|
|
23
|
+
import { createGateRouteCommand } from './route.js';
|
|
19
24
|
const DEFAULT_DEPENDENCIES = {
|
|
20
25
|
buildCommandContext,
|
|
21
26
|
resolveProjectRoot,
|
|
@@ -26,9 +31,16 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
26
31
|
readUserConfig,
|
|
27
32
|
writeUserConfig,
|
|
28
33
|
resolveEffectiveConfig,
|
|
34
|
+
createGateActivityProbe,
|
|
35
|
+
createBranchLocalGateCli,
|
|
36
|
+
currentGateCliLaunch,
|
|
37
|
+
removeBranchLocalGateCli,
|
|
38
|
+
readGateRouteReceipt,
|
|
29
39
|
runProcess: runChildProcess,
|
|
30
40
|
parseReviewGateVerdict,
|
|
31
41
|
processEnv: process.env,
|
|
42
|
+
writeGateRunMarker,
|
|
43
|
+
removeGateRunMarker,
|
|
32
44
|
writeDiagnostic: (message) => process.stderr.write(message),
|
|
33
45
|
};
|
|
34
46
|
const VALID_ON_FAILURE = ['block', 'prompt', 'warn'];
|
|
@@ -61,6 +73,33 @@ const REVIEW_GATE_CONTEXT_NOTE = [
|
|
|
61
73
|
const GATE_CHECK_TIMEOUT_MS = 5_000;
|
|
62
74
|
const GATE_EXEC_TIMEOUT_MS = 15 * 60 * 1_000;
|
|
63
75
|
const GATE_LIVENESS_INTERVAL_MS = 30_000;
|
|
76
|
+
async function writeGateRunMarker(path, marker, warn) {
|
|
77
|
+
try {
|
|
78
|
+
await mkdir(join(tmpdir(), 'oat-gate-runs'), { recursive: true });
|
|
79
|
+
await writeFile(path, `${JSON.stringify(marker, null, 2)}\n`, 'utf8');
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
84
|
+
warn(`Unable to write gate run marker ${path}: ${detail}`);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function removeGateRunMarker(path, warn) {
|
|
89
|
+
try {
|
|
90
|
+
await rm(path);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error &&
|
|
94
|
+
typeof error === 'object' &&
|
|
95
|
+
'code' in error &&
|
|
96
|
+
error.code === 'ENOENT') {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
100
|
+
warn(`Unable to remove gate run marker ${path}: ${detail}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
64
103
|
function reviewGateProjectContext(project) {
|
|
65
104
|
return [
|
|
66
105
|
`Resolved OAT project path: ${project.path}. Run the review for this project path.`,
|
|
@@ -158,6 +197,7 @@ function buildGateDispatchReport(invocation, scope) {
|
|
|
158
197
|
}
|
|
159
198
|
function gateInvocationPromptContext(invocation) {
|
|
160
199
|
const frontmatter = YAML.stringify({
|
|
200
|
+
oat_gate_headless: true,
|
|
161
201
|
oat_gate_run_id: invocation.runId,
|
|
162
202
|
oat_gate_target: invocation.targetId,
|
|
163
203
|
oat_gate_runtime: invocation.runtime,
|
|
@@ -201,6 +241,14 @@ async function runChildProcess(command, args, options) {
|
|
|
201
241
|
return new Promise((resolve, reject) => {
|
|
202
242
|
let timedOut = false;
|
|
203
243
|
let killTimeout = null;
|
|
244
|
+
let stderrBytes = 0;
|
|
245
|
+
let stdoutBytes = 0;
|
|
246
|
+
let refusal;
|
|
247
|
+
let stdoutLineBuffer = '';
|
|
248
|
+
let stderrLineBuffer = '';
|
|
249
|
+
let latestActivityEvidence;
|
|
250
|
+
let latestActivityProbeStatus;
|
|
251
|
+
let livenessProbePending = false;
|
|
204
252
|
const startedAt = Date.now();
|
|
205
253
|
let lastActivityAt = startedAt;
|
|
206
254
|
const child = spawn(command, args, {
|
|
@@ -213,21 +261,71 @@ async function runChildProcess(command, args, options) {
|
|
|
213
261
|
const recordActivity = () => {
|
|
214
262
|
lastActivityAt = Date.now();
|
|
215
263
|
};
|
|
264
|
+
const scanChunk = (buffer, chunk) => {
|
|
265
|
+
const combined = buffer + chunk.toString('utf8');
|
|
266
|
+
const lines = combined.split('\n');
|
|
267
|
+
const remainder = lines.pop() ?? '';
|
|
268
|
+
if (!refusal) {
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
refusal = extractStructuredRefusal(line.replace(/\r$/, ''));
|
|
271
|
+
if (refusal) {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return remainder;
|
|
277
|
+
};
|
|
216
278
|
child.stdout?.on('data', (chunk) => {
|
|
279
|
+
stdoutBytes += chunk.byteLength;
|
|
280
|
+
stdoutLineBuffer = scanChunk(stdoutLineBuffer, chunk);
|
|
217
281
|
recordActivity();
|
|
218
282
|
process.stdout.write(chunk);
|
|
219
283
|
});
|
|
220
284
|
child.stderr?.on('data', (chunk) => {
|
|
285
|
+
stderrBytes += chunk.byteLength;
|
|
286
|
+
stderrLineBuffer = scanChunk(stderrLineBuffer, chunk);
|
|
221
287
|
recordActivity();
|
|
222
288
|
process.stderr.write(chunk);
|
|
223
289
|
});
|
|
290
|
+
const processAlive = () => {
|
|
291
|
+
if (child.pid === undefined)
|
|
292
|
+
return false;
|
|
293
|
+
try {
|
|
294
|
+
process.kill(child.pid, 0);
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
224
301
|
const livenessInterval = options.onLiveness && options.livenessIntervalMs
|
|
225
302
|
? setInterval(() => {
|
|
303
|
+
if (livenessProbePending)
|
|
304
|
+
return;
|
|
305
|
+
livenessProbePending = true;
|
|
226
306
|
const now = Date.now();
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
307
|
+
void (async () => {
|
|
308
|
+
const activityProbeStatus = await options.activityProbe?.observe(now);
|
|
309
|
+
const evidence = activityProbeStatus?.evidence;
|
|
310
|
+
if (activityProbeStatus) {
|
|
311
|
+
latestActivityProbeStatus = activityProbeStatus;
|
|
312
|
+
}
|
|
313
|
+
if (evidence)
|
|
314
|
+
latestActivityEvidence = evidence;
|
|
315
|
+
options.onLiveness?.({
|
|
316
|
+
elapsedMs: now - startedAt,
|
|
317
|
+
hardBudgetMs: options.timeoutMs,
|
|
318
|
+
idleMs: now - lastActivityAt,
|
|
319
|
+
processAlive: processAlive(),
|
|
320
|
+
...(latestActivityProbeStatus
|
|
321
|
+
? { activityProbeStatus: latestActivityProbeStatus }
|
|
322
|
+
: {}),
|
|
323
|
+
...(latestActivityEvidence
|
|
324
|
+
? { lastActivityEvidence: latestActivityEvidence }
|
|
325
|
+
: {}),
|
|
326
|
+
});
|
|
327
|
+
})().finally(() => {
|
|
328
|
+
livenessProbePending = false;
|
|
231
329
|
});
|
|
232
330
|
}, options.livenessIntervalMs)
|
|
233
331
|
: null;
|
|
@@ -259,13 +357,29 @@ async function runChildProcess(command, args, options) {
|
|
|
259
357
|
if (killTimeout) {
|
|
260
358
|
clearTimeout(killTimeout);
|
|
261
359
|
}
|
|
360
|
+
refusal ??=
|
|
361
|
+
extractStructuredRefusal(stdoutLineBuffer.replace(/\r$/, '')) ??
|
|
362
|
+
extractStructuredRefusal(stderrLineBuffer.replace(/\r$/, ''));
|
|
262
363
|
resolve({
|
|
364
|
+
...(latestActivityEvidence
|
|
365
|
+
? { activityEvidence: latestActivityEvidence }
|
|
366
|
+
: {}),
|
|
367
|
+
...(latestActivityProbeStatus
|
|
368
|
+
? { activityProbeStatus: latestActivityProbeStatus }
|
|
369
|
+
: {}),
|
|
263
370
|
exitCode: timedOut ? 124 : (code ?? 1),
|
|
371
|
+
...(refusal ? { refusal } : {}),
|
|
372
|
+
stderrBytes,
|
|
373
|
+
stdoutBytes,
|
|
264
374
|
...(timedOut ? { timedOut: true } : {}),
|
|
265
375
|
});
|
|
266
376
|
});
|
|
267
377
|
});
|
|
268
378
|
}
|
|
379
|
+
function extractStructuredRefusal(output) {
|
|
380
|
+
const match = output.match(/^OAT_GATE_REFUSAL: (.*)$/m);
|
|
381
|
+
return match?.[1]?.replace(/\r$/, '');
|
|
382
|
+
}
|
|
269
383
|
function isGateWriteLayer(value) {
|
|
270
384
|
return VALID_WRITE_LAYERS.includes(value);
|
|
271
385
|
}
|
|
@@ -347,17 +461,131 @@ function parseNumericFlag(value, flag, defaultValue) {
|
|
|
347
461
|
}
|
|
348
462
|
return parsed;
|
|
349
463
|
}
|
|
350
|
-
function
|
|
351
|
-
const
|
|
352
|
-
if (!
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
const parsed = Number(rawValue);
|
|
356
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
357
|
-
return GATE_EXEC_TIMEOUT_MS;
|
|
464
|
+
function parseGateTimeoutFlag(value, flag) {
|
|
465
|
+
const parsed = Number(value);
|
|
466
|
+
if (!isValidGateTimeoutMs(parsed)) {
|
|
467
|
+
throw new Error(`${flag} must be an integer between ${MIN_GATE_TIMEOUT_MS} and ${MAX_GATE_TIMEOUT_MS}.`);
|
|
358
468
|
}
|
|
359
469
|
return parsed;
|
|
360
470
|
}
|
|
471
|
+
function resolveGateExecTimeout(input) {
|
|
472
|
+
if (input.cliTimeoutMs !== undefined) {
|
|
473
|
+
return {
|
|
474
|
+
timeoutMs: parseGateTimeoutFlag(input.cliTimeoutMs, '--timeout-ms'),
|
|
475
|
+
source: 'cli',
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
const warned = new Set();
|
|
479
|
+
const warnOnce = (key, message) => {
|
|
480
|
+
if (!warned.has(key)) {
|
|
481
|
+
warned.add(key);
|
|
482
|
+
input.warn(message);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
for (const persisted of input.rawPersisted?.target ?? []) {
|
|
486
|
+
if (isValidGateTimeoutMs(persisted.value)) {
|
|
487
|
+
return { timeoutMs: persisted.value, source: 'target' };
|
|
488
|
+
}
|
|
489
|
+
warnOnce(`target:${persisted.layer}`, `Ignoring invalid target.timeoutMs from ${persisted.layer} config; using the next timeout source.`);
|
|
490
|
+
}
|
|
491
|
+
if ((input.rawPersisted?.target.length ?? 0) === 0 &&
|
|
492
|
+
input.target.timeoutMs !== undefined) {
|
|
493
|
+
if (isValidGateTimeoutMs(input.target.timeoutMs)) {
|
|
494
|
+
return { timeoutMs: input.target.timeoutMs, source: 'target' };
|
|
495
|
+
}
|
|
496
|
+
input.warn('Ignoring invalid target.timeoutMs; using the next timeout source.');
|
|
497
|
+
}
|
|
498
|
+
const reviewType = input.reviewType?.trim().toLowerCase();
|
|
499
|
+
if (reviewType === 'code' || reviewType === 'artifact') {
|
|
500
|
+
const key = `workflow.gateTimeouts.${reviewType}`;
|
|
501
|
+
for (const persisted of input.rawPersisted?.workflow ?? []) {
|
|
502
|
+
if (isValidGateTimeoutMs(persisted.value)) {
|
|
503
|
+
return { timeoutMs: persisted.value, source: 'config' };
|
|
504
|
+
}
|
|
505
|
+
warnOnce(`${key}:${persisted.layer}`, `Ignoring invalid ${key} from ${persisted.layer} config; using the next timeout source.`);
|
|
506
|
+
}
|
|
507
|
+
const entry = input.effective.resolved[key];
|
|
508
|
+
if ((input.rawPersisted?.workflow.length ?? 0) === 0 &&
|
|
509
|
+
entry?.value !== null &&
|
|
510
|
+
entry?.value !== undefined) {
|
|
511
|
+
if (isValidGateTimeoutMs(entry.value)) {
|
|
512
|
+
return { timeoutMs: entry.value, source: 'config' };
|
|
513
|
+
}
|
|
514
|
+
warnOnce(`${key}:${entry.source}`, `Ignoring invalid ${key} from ${entry.source}; using the next timeout source.`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const envValue = input.env.OAT_GATE_EXEC_TIMEOUT_MS?.trim();
|
|
518
|
+
if (envValue) {
|
|
519
|
+
const parsed = Number(envValue);
|
|
520
|
+
if (isValidGateTimeoutMs(parsed)) {
|
|
521
|
+
return { timeoutMs: parsed, source: 'env' };
|
|
522
|
+
}
|
|
523
|
+
warnOnce('env', 'Ignoring invalid OAT_GATE_EXEC_TIMEOUT_MS; using the next timeout source.');
|
|
524
|
+
}
|
|
525
|
+
const scope = input.reviewScope?.trim().toLowerCase() ?? '';
|
|
526
|
+
if (reviewType === 'artifact') {
|
|
527
|
+
return { timeoutMs: 900_000, source: 'scope-default' };
|
|
528
|
+
}
|
|
529
|
+
if (reviewType === 'code') {
|
|
530
|
+
if (/^p\d+-t\d+$/.test(scope)) {
|
|
531
|
+
return { timeoutMs: 900_000, source: 'scope-default' };
|
|
532
|
+
}
|
|
533
|
+
if (scope === 'final' ||
|
|
534
|
+
/^p\d+$/.test(scope) ||
|
|
535
|
+
/^p\d+-p\d+$/.test(scope)) {
|
|
536
|
+
return { timeoutMs: 1_800_000, source: 'scope-default' };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return { timeoutMs: GATE_EXEC_TIMEOUT_MS, source: 'default' };
|
|
540
|
+
}
|
|
541
|
+
function rawRecord(value) {
|
|
542
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
543
|
+
? value
|
|
544
|
+
: null;
|
|
545
|
+
}
|
|
546
|
+
async function readRawConfig(path) {
|
|
547
|
+
try {
|
|
548
|
+
return rawRecord(parseJsonConfig(await readFile(path, 'utf8'), path)) ?? {};
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
if (typeof error === 'object' &&
|
|
552
|
+
error !== null &&
|
|
553
|
+
'code' in error &&
|
|
554
|
+
error.code === 'ENOENT') {
|
|
555
|
+
return {};
|
|
556
|
+
}
|
|
557
|
+
throw error;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async function readRawGateTimeoutLayers(input) {
|
|
561
|
+
const configs = await Promise.all([
|
|
562
|
+
readRawConfig(join(input.repoRoot, '.oat', 'config.local.json')),
|
|
563
|
+
readRawConfig(join(input.repoRoot, '.oat', 'config.json')),
|
|
564
|
+
readRawConfig(join(input.userConfigDir, 'config.json')),
|
|
565
|
+
]);
|
|
566
|
+
const layers = ['local', 'shared', 'user'];
|
|
567
|
+
const target = [];
|
|
568
|
+
const workflow = [];
|
|
569
|
+
const reviewType = input.reviewType?.trim().toLowerCase();
|
|
570
|
+
for (const [index, config] of configs.entries()) {
|
|
571
|
+
const layer = layers[index];
|
|
572
|
+
const workflowConfig = rawRecord(config.workflow);
|
|
573
|
+
const gates = rawRecord(workflowConfig?.gates);
|
|
574
|
+
const execTargets = rawRecord(gates?.execTargets);
|
|
575
|
+
const rawTarget = rawRecord(execTargets?.[input.targetId]);
|
|
576
|
+
if (rawTarget &&
|
|
577
|
+
Object.prototype.hasOwnProperty.call(rawTarget, 'timeoutMs')) {
|
|
578
|
+
target.push({ layer, value: rawTarget.timeoutMs });
|
|
579
|
+
}
|
|
580
|
+
const gateTimeouts = rawRecord(workflowConfig?.gateTimeouts);
|
|
581
|
+
if ((reviewType === 'code' || reviewType === 'artifact') &&
|
|
582
|
+
gateTimeouts &&
|
|
583
|
+
Object.prototype.hasOwnProperty.call(gateTimeouts, reviewType)) {
|
|
584
|
+
workflow.push({ layer, value: gateTimeouts[reviewType] });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return { target, workflow };
|
|
588
|
+
}
|
|
361
589
|
function resolveGateLivenessIntervalMs(env) {
|
|
362
590
|
const rawValue = env.OAT_GATE_LIVENESS_INTERVAL_MS?.trim();
|
|
363
591
|
if (!rawValue) {
|
|
@@ -425,6 +653,9 @@ function parseExecTargetConfig(options) {
|
|
|
425
653
|
...(options.priority !== undefined
|
|
426
654
|
? { priority: parseNumericFlag(options.priority, '--priority', 0) }
|
|
427
655
|
: {}),
|
|
656
|
+
...(options.timeoutMs !== undefined
|
|
657
|
+
? { timeoutMs: parseGateTimeoutFlag(options.timeoutMs, '--timeout-ms') }
|
|
658
|
+
: {}),
|
|
428
659
|
...(options.invocationModel !== undefined ||
|
|
429
660
|
options.invocationReasoningEffort !== undefined
|
|
430
661
|
? {
|
|
@@ -867,7 +1098,7 @@ async function resolveSelectedExecTarget(targets, options, producerIdentity, con
|
|
|
867
1098
|
}
|
|
868
1099
|
return attachDiversityMetadata(selected, avoid, producerIdentity);
|
|
869
1100
|
}
|
|
870
|
-
async function executeTarget(selected, prompt, context, dependencies) {
|
|
1101
|
+
async function executeTarget(selected, prompt, context, dependencies, timeout) {
|
|
871
1102
|
const [command, baseArgs] = argvHead(selected.target.baseCommand);
|
|
872
1103
|
if (!command) {
|
|
873
1104
|
throw new Error(`Exec target "${selected.id}" has an empty base command.`);
|
|
@@ -875,21 +1106,39 @@ async function executeTarget(selected, prompt, context, dependencies) {
|
|
|
875
1106
|
const modelArgs = selected.model && !findPinnedModelArg(selected.target.baseCommand)
|
|
876
1107
|
? ['--model', selected.model]
|
|
877
1108
|
: [];
|
|
878
|
-
const timeoutMs = resolveGateExecTimeoutMs(dependencies.processEnv);
|
|
879
1109
|
const livenessIntervalMs = resolveGateLivenessIntervalMs(dependencies.processEnv);
|
|
880
|
-
|
|
881
|
-
|
|
1110
|
+
const activityProbe = await dependencies.createGateActivityProbe({
|
|
1111
|
+
runtime: selected.target.runtime,
|
|
1112
|
+
cwd: context.cwd,
|
|
1113
|
+
home: context.home,
|
|
1114
|
+
spawnedAt: Date.now(),
|
|
1115
|
+
});
|
|
1116
|
+
if (context.json) {
|
|
1117
|
+
dependencies.writeDiagnostic(`${JSON.stringify({
|
|
1118
|
+
type: 'gate-start',
|
|
1119
|
+
target: selected.id,
|
|
1120
|
+
runtime: selected.target.runtime,
|
|
1121
|
+
timeoutMs: timeout.timeoutMs,
|
|
1122
|
+
timeoutSource: timeout.source,
|
|
1123
|
+
})}\n`);
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
context.logger.info(`Running gate target ${selected.id} (${selected.target.runtime}); timeout=${timeout.timeoutMs}ms (source=${timeout.source}).`);
|
|
882
1127
|
}
|
|
883
1128
|
try {
|
|
884
1129
|
return await dependencies.runProcess(command, [...baseArgs, ...modelArgs, ...prompt], {
|
|
885
1130
|
cwd: context.cwd,
|
|
886
1131
|
env: dependencies.processEnv,
|
|
1132
|
+
...(activityProbe ? { activityProbe } : {}),
|
|
887
1133
|
livenessIntervalMs,
|
|
888
|
-
onLiveness: ({ elapsedMs, hardBudgetMs, idleMs }) => {
|
|
1134
|
+
onLiveness: ({ elapsedMs, hardBudgetMs, idleMs, processAlive, activityProbeStatus, lastActivityEvidence, }) => {
|
|
889
1135
|
const telemetry = {
|
|
890
1136
|
elapsedMs,
|
|
891
1137
|
hardBudgetMs,
|
|
892
1138
|
idleMs,
|
|
1139
|
+
processAlive,
|
|
1140
|
+
...(activityProbeStatus ? { activityProbeStatus } : {}),
|
|
1141
|
+
...(lastActivityEvidence ? { lastActivityEvidence } : {}),
|
|
893
1142
|
target: selected.id,
|
|
894
1143
|
type: 'gate-liveness',
|
|
895
1144
|
};
|
|
@@ -897,13 +1146,18 @@ async function executeTarget(selected, prompt, context, dependencies) {
|
|
|
897
1146
|
dependencies.writeDiagnostic(`${JSON.stringify(telemetry)}\n`);
|
|
898
1147
|
}
|
|
899
1148
|
else {
|
|
900
|
-
|
|
1149
|
+
const activityDescription = lastActivityEvidence
|
|
1150
|
+
? lastActivityEvidence.scope === 'ambient-runtime'
|
|
1151
|
+
? 'ambient runtime activity (not attributable to this gate child)'
|
|
1152
|
+
: 'project-directory activity'
|
|
1153
|
+
: `${activityProbeStatus?.status ?? 'unavailable'} (${activityProbeStatus?.attemptedPath ?? 'no path'})`;
|
|
1154
|
+
context.logger.info(`Gate liveness: target=${selected.id} elapsed_ms=${elapsedMs} idle_ms=${idleMs} hard_budget_ms=${hardBudgetMs} process_alive=${processAlive} activity_evidence=${activityDescription}.`);
|
|
901
1155
|
}
|
|
902
1156
|
},
|
|
903
1157
|
purpose: 'execute',
|
|
904
1158
|
stdin: 'ignore',
|
|
905
1159
|
stdio: 'pipe',
|
|
906
|
-
timeoutMs,
|
|
1160
|
+
timeoutMs: timeout.timeoutMs,
|
|
907
1161
|
});
|
|
908
1162
|
}
|
|
909
1163
|
catch (error) {
|
|
@@ -1277,9 +1531,11 @@ function writeReviewGateResult(context, payload) {
|
|
|
1277
1531
|
context.logger.info(payload.handoff);
|
|
1278
1532
|
}
|
|
1279
1533
|
function writeReviewGateExecutionFailure(context, payload) {
|
|
1280
|
-
const message = payload.
|
|
1281
|
-
? `Review did not complete:
|
|
1282
|
-
:
|
|
1534
|
+
const message = payload.refusal
|
|
1535
|
+
? `Review did not complete: reviewer refused the headless route (${payload.refusal}).`
|
|
1536
|
+
: payload.timedOut
|
|
1537
|
+
? `Review did not complete: target ${payload.target} timed out after ${payload.timeoutMs}ms.`
|
|
1538
|
+
: `Review did not complete: target ${payload.target} exited with code ${payload.exitCode}.`;
|
|
1283
1539
|
if (context.json) {
|
|
1284
1540
|
context.logger.json({
|
|
1285
1541
|
status: 'review_failed',
|
|
@@ -1298,6 +1554,16 @@ function writeReviewGateExecutionFailure(context, payload) {
|
|
|
1298
1554
|
...(payload.timeoutMs !== undefined
|
|
1299
1555
|
? { timeoutMs: payload.timeoutMs }
|
|
1300
1556
|
: {}),
|
|
1557
|
+
...(payload.timeoutSource !== undefined
|
|
1558
|
+
? { timeoutSource: payload.timeoutSource }
|
|
1559
|
+
: {}),
|
|
1560
|
+
...(payload.noOutputProduced !== undefined
|
|
1561
|
+
? { noOutputProduced: payload.noOutputProduced }
|
|
1562
|
+
: {}),
|
|
1563
|
+
...(payload.refusal ? { refusal: payload.refusal } : {}),
|
|
1564
|
+
...(payload.activityEvidence
|
|
1565
|
+
? { activityEvidence: payload.activityEvidence }
|
|
1566
|
+
: {}),
|
|
1301
1567
|
message,
|
|
1302
1568
|
});
|
|
1303
1569
|
return;
|
|
@@ -1517,7 +1783,21 @@ async function runCrossProviderExec(prompt, options, context, dependencies) {
|
|
|
1517
1783
|
const producerIdentity = parseProducerIdentityOption(options.producerIdentity);
|
|
1518
1784
|
const selected = await resolveSelectedExecTarget(targets, options, producerIdentity, context, dependencies);
|
|
1519
1785
|
logGateDiversity(selected, context);
|
|
1520
|
-
const
|
|
1786
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
1787
|
+
const rawPersisted = await readRawGateTimeoutLayers({
|
|
1788
|
+
repoRoot,
|
|
1789
|
+
userConfigDir: join(context.home, '.oat'),
|
|
1790
|
+
targetId: selected.id,
|
|
1791
|
+
});
|
|
1792
|
+
const timeout = resolveGateExecTimeout({
|
|
1793
|
+
cliTimeoutMs: options.timeoutMs,
|
|
1794
|
+
target: selected.target,
|
|
1795
|
+
effective,
|
|
1796
|
+
env: dependencies.processEnv,
|
|
1797
|
+
warn: context.logger.warn,
|
|
1798
|
+
rawPersisted,
|
|
1799
|
+
});
|
|
1800
|
+
const result = await executeTarget(selected, prompt, context, dependencies, timeout);
|
|
1521
1801
|
process.exitCode = result.exitCode;
|
|
1522
1802
|
}
|
|
1523
1803
|
catch (error) {
|
|
@@ -1526,6 +1806,9 @@ async function runCrossProviderExec(prompt, options, context, dependencies) {
|
|
|
1526
1806
|
}
|
|
1527
1807
|
async function runReviewGate(prompt, options, context, dependencies) {
|
|
1528
1808
|
const runId = randomUUID();
|
|
1809
|
+
let runMarkerPath;
|
|
1810
|
+
let runMarkerWritten = false;
|
|
1811
|
+
let branchLocalGateCli;
|
|
1529
1812
|
let postSelectionContext;
|
|
1530
1813
|
try {
|
|
1531
1814
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
@@ -1546,6 +1829,22 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1546
1829
|
});
|
|
1547
1830
|
const selected = await resolveSelectedExecTarget(targets, options, producerIdentity, context, dependencies);
|
|
1548
1831
|
const gateInvocation = createGateInvocationMetadata(runId, selected);
|
|
1832
|
+
const rawPersisted = await readRawGateTimeoutLayers({
|
|
1833
|
+
repoRoot,
|
|
1834
|
+
userConfigDir,
|
|
1835
|
+
targetId: selected.id,
|
|
1836
|
+
reviewType: options.reviewType,
|
|
1837
|
+
});
|
|
1838
|
+
const timeout = resolveGateExecTimeout({
|
|
1839
|
+
cliTimeoutMs: options.timeoutMs,
|
|
1840
|
+
target: selected.target,
|
|
1841
|
+
effective,
|
|
1842
|
+
reviewType: options.reviewType,
|
|
1843
|
+
reviewScope: options.reviewScope,
|
|
1844
|
+
env: dependencies.processEnv,
|
|
1845
|
+
warn: context.logger.warn,
|
|
1846
|
+
rawPersisted,
|
|
1847
|
+
});
|
|
1549
1848
|
const dispatchReport = buildGateDispatchReport(gateInvocation, options.reviewScope?.trim() || 'gate-review');
|
|
1550
1849
|
postSelectionContext = {
|
|
1551
1850
|
project: projectPath,
|
|
@@ -1572,9 +1871,61 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1572
1871
|
: []),
|
|
1573
1872
|
prompt.join(' '),
|
|
1574
1873
|
]);
|
|
1575
|
-
|
|
1874
|
+
runMarkerPath = join(tmpdir(), 'oat-gate-runs', `${runId}.json`);
|
|
1875
|
+
runMarkerWritten = await dependencies.writeGateRunMarker(runMarkerPath, {
|
|
1876
|
+
runId,
|
|
1877
|
+
targetId: selected.id,
|
|
1878
|
+
runtime: selected.target.runtime,
|
|
1879
|
+
reviewType: options.reviewType?.trim() || null,
|
|
1880
|
+
reviewScope: options.reviewScope?.trim() || null,
|
|
1881
|
+
project: projectPath,
|
|
1882
|
+
startedAt: new Date().toISOString(),
|
|
1883
|
+
budgetMs: timeout.timeoutMs,
|
|
1884
|
+
budgetSource: timeout.source,
|
|
1885
|
+
}, (message) => context.logger.warn(message));
|
|
1886
|
+
if (runMarkerWritten) {
|
|
1887
|
+
if (context.json) {
|
|
1888
|
+
dependencies.writeDiagnostic(`${JSON.stringify({
|
|
1889
|
+
type: 'gate-run-marker',
|
|
1890
|
+
runId,
|
|
1891
|
+
path: runMarkerPath,
|
|
1892
|
+
})}\n`);
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
context.logger.info(`Gate run marker: ${runMarkerPath}.`);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
branchLocalGateCli = await dependencies.createBranchLocalGateCli({
|
|
1899
|
+
runId,
|
|
1900
|
+
launch: dependencies.currentGateCliLaunch(),
|
|
1901
|
+
});
|
|
1902
|
+
const childResult = await executeTarget(selected, [reviewPrompt], context, {
|
|
1903
|
+
...dependencies,
|
|
1904
|
+
processEnv: {
|
|
1905
|
+
...dependencies.processEnv,
|
|
1906
|
+
OAT_GATE_HEADLESS: '1',
|
|
1907
|
+
OAT_NON_INTERACTIVE: '1',
|
|
1908
|
+
OAT_GATE_RUN_ID: runId,
|
|
1909
|
+
OAT_GATE_RUNTIME: gateInvocation.runtime,
|
|
1910
|
+
OAT_INVOCATION_MODEL: gateInvocation.model,
|
|
1911
|
+
OAT_GATE_CLI_PATH: branchLocalGateCli.cliPath,
|
|
1912
|
+
OAT_GATE_CLI_ROOT: branchLocalGateCli.cliRoot,
|
|
1913
|
+
OAT_GATE_ROUTE_RECEIPT_PATH: branchLocalGateCli.routeReceiptPath,
|
|
1914
|
+
},
|
|
1915
|
+
}, timeout);
|
|
1916
|
+
const routeReceipt = await dependencies.readGateRouteReceipt(branchLocalGateCli.routeReceiptPath, branchLocalGateCli.cliRoot, selected.target.runtime);
|
|
1917
|
+
dependencies.writeDiagnostic(`${JSON.stringify({
|
|
1918
|
+
type: 'gate-route',
|
|
1919
|
+
target: selected.id,
|
|
1920
|
+
...routeReceipt,
|
|
1921
|
+
})}\n`);
|
|
1576
1922
|
const childExitCode = childResult.exitCode;
|
|
1577
|
-
|
|
1923
|
+
const refusal = childResult.refusal ??
|
|
1924
|
+
extractStructuredRefusal(childResult.capturedOutput ?? '');
|
|
1925
|
+
const writeRefusalFailure = () => {
|
|
1926
|
+
if (!refusal) {
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1578
1929
|
writeReviewGateExecutionFailure(context, {
|
|
1579
1930
|
runId,
|
|
1580
1931
|
target: selected.id,
|
|
@@ -1582,13 +1933,18 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1582
1933
|
projectResolutionSource: reviewProject.source,
|
|
1583
1934
|
exitCode: childExitCode,
|
|
1584
1935
|
timedOut: childResult.timedOut ?? false,
|
|
1585
|
-
timeoutMs:
|
|
1936
|
+
timeoutMs: timeout.timeoutMs,
|
|
1937
|
+
timeoutSource: timeout.source,
|
|
1938
|
+
refusal,
|
|
1939
|
+
...(childResult.activityEvidence
|
|
1940
|
+
? { activityEvidence: childResult.activityEvidence }
|
|
1941
|
+
: {}),
|
|
1586
1942
|
gateInvocation,
|
|
1587
1943
|
dispatchReport,
|
|
1588
1944
|
});
|
|
1589
|
-
process.exitCode =
|
|
1590
|
-
return;
|
|
1591
|
-
}
|
|
1945
|
+
process.exitCode = 1;
|
|
1946
|
+
return true;
|
|
1947
|
+
};
|
|
1592
1948
|
const after = await listReviewGateArtifactCandidates({
|
|
1593
1949
|
repoRoot,
|
|
1594
1950
|
effective,
|
|
@@ -1599,6 +1955,52 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1599
1955
|
before,
|
|
1600
1956
|
after,
|
|
1601
1957
|
});
|
|
1958
|
+
if (!artifactResolution.artifact && writeRefusalFailure()) {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (childExitCode !== 0 &&
|
|
1962
|
+
!childResult.timedOut &&
|
|
1963
|
+
!artifactResolution.artifact) {
|
|
1964
|
+
writeReviewGateExecutionFailure(context, {
|
|
1965
|
+
runId,
|
|
1966
|
+
target: selected.id,
|
|
1967
|
+
project: projectPath,
|
|
1968
|
+
projectResolutionSource: reviewProject.source,
|
|
1969
|
+
exitCode: childExitCode,
|
|
1970
|
+
timedOut: childResult.timedOut ?? false,
|
|
1971
|
+
timeoutMs: timeout.timeoutMs,
|
|
1972
|
+
timeoutSource: timeout.source,
|
|
1973
|
+
...(childResult.activityEvidence
|
|
1974
|
+
? { activityEvidence: childResult.activityEvidence }
|
|
1975
|
+
: {}),
|
|
1976
|
+
gateInvocation,
|
|
1977
|
+
dispatchReport,
|
|
1978
|
+
});
|
|
1979
|
+
process.exitCode = childExitCode;
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
if (childResult.timedOut &&
|
|
1983
|
+
artifactResolution.matchingArtifactPaths.length === 0 &&
|
|
1984
|
+
!artifactResolution.diagnosticArtifact) {
|
|
1985
|
+
writeReviewGateExecutionFailure(context, {
|
|
1986
|
+
runId,
|
|
1987
|
+
target: selected.id,
|
|
1988
|
+
project: projectPath,
|
|
1989
|
+
projectResolutionSource: reviewProject.source,
|
|
1990
|
+
exitCode: childExitCode,
|
|
1991
|
+
timedOut: true,
|
|
1992
|
+
timeoutMs: timeout.timeoutMs,
|
|
1993
|
+
timeoutSource: timeout.source,
|
|
1994
|
+
noOutputProduced: childResult.stdoutBytes + childResult.stderrBytes === 0,
|
|
1995
|
+
...(childResult.activityEvidence
|
|
1996
|
+
? { activityEvidence: childResult.activityEvidence }
|
|
1997
|
+
: {}),
|
|
1998
|
+
gateInvocation,
|
|
1999
|
+
dispatchReport,
|
|
2000
|
+
});
|
|
2001
|
+
process.exitCode = childExitCode;
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
1602
2004
|
const initialTargetCorroboration = corroborateReviewTarget({
|
|
1603
2005
|
repoRoot,
|
|
1604
2006
|
reviewProject,
|
|
@@ -1633,6 +2035,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1633
2035
|
if (producedArtifact.containingProject !== reviewProject.path ||
|
|
1634
2036
|
(reviewProject.source === 'declared' &&
|
|
1635
2037
|
initialTargetCorroboration.project !== 'matched')) {
|
|
2038
|
+
if (writeRefusalFailure()) {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
1636
2041
|
writeReviewGateTargetingFailure(context, {
|
|
1637
2042
|
runId,
|
|
1638
2043
|
target: selected.id,
|
|
@@ -1654,6 +2059,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1654
2059
|
}
|
|
1655
2060
|
if (!producedArtifact.generatedAt ||
|
|
1656
2061
|
!Number.isFinite(producedArtifact.generatedTime)) {
|
|
2062
|
+
if (writeRefusalFailure()) {
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
1657
2065
|
writeReviewGateArtifactValidationFailure(context, {
|
|
1658
2066
|
runId,
|
|
1659
2067
|
target: selected.id,
|
|
@@ -1681,6 +2089,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1681
2089
|
});
|
|
1682
2090
|
}
|
|
1683
2091
|
catch (error) {
|
|
2092
|
+
if (writeRefusalFailure()) {
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
1684
2095
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1685
2096
|
writeReviewGateArtifactValidationFailure(context, {
|
|
1686
2097
|
runId,
|
|
@@ -1702,6 +2113,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1702
2113
|
const corroboration = corroborateGateInvocation(gateInvocation, verdict.gateInvocation, targetCorroboration);
|
|
1703
2114
|
if (corroboration.run !== 'matched' ||
|
|
1704
2115
|
corroboration.invocation !== 'matched') {
|
|
2116
|
+
if (writeRefusalFailure()) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
1705
2119
|
const missing = corroboration.run === 'missing' ||
|
|
1706
2120
|
corroboration.invocation === 'missing';
|
|
1707
2121
|
writeReviewGateArtifactValidationFailure(context, {
|
|
@@ -1723,6 +2137,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1723
2137
|
return;
|
|
1724
2138
|
}
|
|
1725
2139
|
if (verdict.invocation !== 'gate') {
|
|
2140
|
+
if (writeRefusalFailure()) {
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
1726
2143
|
writeReviewGateArtifactValidationFailure(context, {
|
|
1727
2144
|
runId,
|
|
1728
2145
|
target: selected.id,
|
|
@@ -1766,6 +2183,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1766
2183
|
gateInvocation,
|
|
1767
2184
|
dispatchReport,
|
|
1768
2185
|
corroboration,
|
|
2186
|
+
...(childResult.timedOut ? { lateCompletion: true } : {}),
|
|
1769
2187
|
});
|
|
1770
2188
|
process.exitCode = blocking ? 1 : 0;
|
|
1771
2189
|
}
|
|
@@ -1781,6 +2199,14 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1781
2199
|
writeError(context, error);
|
|
1782
2200
|
}
|
|
1783
2201
|
}
|
|
2202
|
+
finally {
|
|
2203
|
+
if (branchLocalGateCli) {
|
|
2204
|
+
await dependencies.removeBranchLocalGateCli(branchLocalGateCli);
|
|
2205
|
+
}
|
|
2206
|
+
if (runMarkerPath) {
|
|
2207
|
+
await dependencies.removeGateRunMarker(runMarkerPath, (message) => context.logger.warn(message));
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
1784
2210
|
}
|
|
1785
2211
|
export function createGateCommand(overrides = {}) {
|
|
1786
2212
|
const dependencies = {
|
|
@@ -1788,6 +2214,10 @@ export function createGateCommand(overrides = {}) {
|
|
|
1788
2214
|
...overrides,
|
|
1789
2215
|
};
|
|
1790
2216
|
const cmd = new Command('gate').description('Resolve and manage workflow gate configuration');
|
|
2217
|
+
cmd.addCommand(createGateRouteCommand({
|
|
2218
|
+
buildCommandContext: dependencies.buildCommandContext,
|
|
2219
|
+
processEnv: dependencies.processEnv,
|
|
2220
|
+
}));
|
|
1791
2221
|
cmd
|
|
1792
2222
|
.command('resolve')
|
|
1793
2223
|
.description('Print the resolved gate configuration for a skill')
|
|
@@ -1821,11 +2251,13 @@ export function createGateCommand(overrides = {}) {
|
|
|
1821
2251
|
});
|
|
1822
2252
|
cmd
|
|
1823
2253
|
.command('cross-provider-exec')
|
|
2254
|
+
.alias('exec')
|
|
1824
2255
|
.description('Run a prompt through an alternate configured runtime target')
|
|
1825
2256
|
.option('--target <id>', 'Run this exact exec target')
|
|
1826
2257
|
.option('--avoid <mode>', 'Avoidance mode: same-family, same-runtime, or none')
|
|
1827
2258
|
.option('--current-runtime <runtime>', 'Override detected runtime for testing or manual routing')
|
|
1828
2259
|
.option('--producer-identity <identity>', 'Producer identity as <value>:<declared|observed|inferred|unknown>')
|
|
2260
|
+
.option('--timeout-ms <milliseconds>', `Gate timeout in milliseconds (${MIN_GATE_TIMEOUT_MS}-${MAX_GATE_TIMEOUT_MS})`)
|
|
1829
2261
|
.argument('<prompt...>', 'Prompt arguments appended to the target command')
|
|
1830
2262
|
.action(async (prompt, options, command) => {
|
|
1831
2263
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
@@ -1838,6 +2270,7 @@ export function createGateCommand(overrides = {}) {
|
|
|
1838
2270
|
.option('--avoid <mode>', 'Avoidance mode: same-family, same-runtime, or none')
|
|
1839
2271
|
.option('--current-runtime <runtime>', 'Override detected runtime for testing or manual routing')
|
|
1840
2272
|
.option('--producer-identity <identity>', 'Producer identity as <value>:<declared|observed|inferred|unknown>')
|
|
2273
|
+
.option('--timeout-ms <milliseconds>', `Gate timeout in milliseconds (${MIN_GATE_TIMEOUT_MS}-${MAX_GATE_TIMEOUT_MS})`)
|
|
1841
2274
|
.option('--project <path-or-name>', 'Project path or name to review; defaults to the active project')
|
|
1842
2275
|
.option('--review-scope <scope>', 'Review scope hint for the provider')
|
|
1843
2276
|
.option('--review-type <type>', 'Review type hint for the provider')
|
|
@@ -1859,6 +2292,7 @@ export function createGateCommand(overrides = {}) {
|
|
|
1859
2292
|
.option('--invocation-model <model>', 'Configured invocation model or provider-default')
|
|
1860
2293
|
.option('--invocation-reasoning-effort <effort>', 'Configured reasoning effort or provider-default')
|
|
1861
2294
|
.option('--priority <number>', 'Target priority, higher wins')
|
|
2295
|
+
.option('--timeout-ms <milliseconds>', `Target gate timeout in milliseconds (${MIN_GATE_TIMEOUT_MS}-${MAX_GATE_TIMEOUT_MS})`)
|
|
1862
2296
|
.option('--disable', 'Disable this exec target in the selected layer')
|
|
1863
2297
|
.option('--layer <layer>', 'Config layer to write: shared, local, or user')
|
|
1864
2298
|
.action(async (targetId, options, command) => {
|