@open-agent-toolkit/cli 0.1.69 → 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/docs/cli-utilities/configuration.md +27 -0
- package/assets/docs/cli-utilities/index.md +1 -1
- package/assets/docs/cli-utilities/workflow-gates.md +126 -3
- package/assets/docs/reference/cli-reference.md +5 -1
- package/assets/docs/workflows/projects/dispatch-ceiling.md +14 -5
- package/assets/docs/workflows/projects/orchestration-model.md +21 -0
- package/assets/docs/workflows/projects/review-flavors.md +9 -0
- 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/references/gate-inventory.md +3 -0
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +3 -0
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +3 -0
- package/assets/skills/oat-project-plan-writing/SKILL.md +23 -33
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +3 -0
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +3 -0
- package/assets/skills/oat-project-review-provide/SKILL.md +62 -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 +29 -0
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +438 -32
- 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/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,
|
|
@@ -203,6 +243,12 @@ async function runChildProcess(command, args, options) {
|
|
|
203
243
|
let killTimeout = null;
|
|
204
244
|
let stderrBytes = 0;
|
|
205
245
|
let stdoutBytes = 0;
|
|
246
|
+
let refusal;
|
|
247
|
+
let stdoutLineBuffer = '';
|
|
248
|
+
let stderrLineBuffer = '';
|
|
249
|
+
let latestActivityEvidence;
|
|
250
|
+
let latestActivityProbeStatus;
|
|
251
|
+
let livenessProbePending = false;
|
|
206
252
|
const startedAt = Date.now();
|
|
207
253
|
let lastActivityAt = startedAt;
|
|
208
254
|
const child = spawn(command, args, {
|
|
@@ -215,23 +261,71 @@ async function runChildProcess(command, args, options) {
|
|
|
215
261
|
const recordActivity = () => {
|
|
216
262
|
lastActivityAt = Date.now();
|
|
217
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
|
+
};
|
|
218
278
|
child.stdout?.on('data', (chunk) => {
|
|
219
279
|
stdoutBytes += chunk.byteLength;
|
|
280
|
+
stdoutLineBuffer = scanChunk(stdoutLineBuffer, chunk);
|
|
220
281
|
recordActivity();
|
|
221
282
|
process.stdout.write(chunk);
|
|
222
283
|
});
|
|
223
284
|
child.stderr?.on('data', (chunk) => {
|
|
224
285
|
stderrBytes += chunk.byteLength;
|
|
286
|
+
stderrLineBuffer = scanChunk(stderrLineBuffer, chunk);
|
|
225
287
|
recordActivity();
|
|
226
288
|
process.stderr.write(chunk);
|
|
227
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
|
+
};
|
|
228
301
|
const livenessInterval = options.onLiveness && options.livenessIntervalMs
|
|
229
302
|
? setInterval(() => {
|
|
303
|
+
if (livenessProbePending)
|
|
304
|
+
return;
|
|
305
|
+
livenessProbePending = true;
|
|
230
306
|
const now = Date.now();
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
235
329
|
});
|
|
236
330
|
}, options.livenessIntervalMs)
|
|
237
331
|
: null;
|
|
@@ -263,8 +357,18 @@ async function runChildProcess(command, args, options) {
|
|
|
263
357
|
if (killTimeout) {
|
|
264
358
|
clearTimeout(killTimeout);
|
|
265
359
|
}
|
|
360
|
+
refusal ??=
|
|
361
|
+
extractStructuredRefusal(stdoutLineBuffer.replace(/\r$/, '')) ??
|
|
362
|
+
extractStructuredRefusal(stderrLineBuffer.replace(/\r$/, ''));
|
|
266
363
|
resolve({
|
|
364
|
+
...(latestActivityEvidence
|
|
365
|
+
? { activityEvidence: latestActivityEvidence }
|
|
366
|
+
: {}),
|
|
367
|
+
...(latestActivityProbeStatus
|
|
368
|
+
? { activityProbeStatus: latestActivityProbeStatus }
|
|
369
|
+
: {}),
|
|
267
370
|
exitCode: timedOut ? 124 : (code ?? 1),
|
|
371
|
+
...(refusal ? { refusal } : {}),
|
|
268
372
|
stderrBytes,
|
|
269
373
|
stdoutBytes,
|
|
270
374
|
...(timedOut ? { timedOut: true } : {}),
|
|
@@ -272,6 +376,10 @@ async function runChildProcess(command, args, options) {
|
|
|
272
376
|
});
|
|
273
377
|
});
|
|
274
378
|
}
|
|
379
|
+
function extractStructuredRefusal(output) {
|
|
380
|
+
const match = output.match(/^OAT_GATE_REFUSAL: (.*)$/m);
|
|
381
|
+
return match?.[1]?.replace(/\r$/, '');
|
|
382
|
+
}
|
|
275
383
|
function isGateWriteLayer(value) {
|
|
276
384
|
return VALID_WRITE_LAYERS.includes(value);
|
|
277
385
|
}
|
|
@@ -353,17 +461,131 @@ function parseNumericFlag(value, flag, defaultValue) {
|
|
|
353
461
|
}
|
|
354
462
|
return parsed;
|
|
355
463
|
}
|
|
356
|
-
function
|
|
357
|
-
const
|
|
358
|
-
if (!
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
const parsed = Number(rawValue);
|
|
362
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
363
|
-
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}.`);
|
|
364
468
|
}
|
|
365
469
|
return parsed;
|
|
366
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
|
+
}
|
|
367
589
|
function resolveGateLivenessIntervalMs(env) {
|
|
368
590
|
const rawValue = env.OAT_GATE_LIVENESS_INTERVAL_MS?.trim();
|
|
369
591
|
if (!rawValue) {
|
|
@@ -431,6 +653,9 @@ function parseExecTargetConfig(options) {
|
|
|
431
653
|
...(options.priority !== undefined
|
|
432
654
|
? { priority: parseNumericFlag(options.priority, '--priority', 0) }
|
|
433
655
|
: {}),
|
|
656
|
+
...(options.timeoutMs !== undefined
|
|
657
|
+
? { timeoutMs: parseGateTimeoutFlag(options.timeoutMs, '--timeout-ms') }
|
|
658
|
+
: {}),
|
|
434
659
|
...(options.invocationModel !== undefined ||
|
|
435
660
|
options.invocationReasoningEffort !== undefined
|
|
436
661
|
? {
|
|
@@ -873,7 +1098,7 @@ async function resolveSelectedExecTarget(targets, options, producerIdentity, con
|
|
|
873
1098
|
}
|
|
874
1099
|
return attachDiversityMetadata(selected, avoid, producerIdentity);
|
|
875
1100
|
}
|
|
876
|
-
async function executeTarget(selected, prompt, context, dependencies) {
|
|
1101
|
+
async function executeTarget(selected, prompt, context, dependencies, timeout) {
|
|
877
1102
|
const [command, baseArgs] = argvHead(selected.target.baseCommand);
|
|
878
1103
|
if (!command) {
|
|
879
1104
|
throw new Error(`Exec target "${selected.id}" has an empty base command.`);
|
|
@@ -881,21 +1106,39 @@ async function executeTarget(selected, prompt, context, dependencies) {
|
|
|
881
1106
|
const modelArgs = selected.model && !findPinnedModelArg(selected.target.baseCommand)
|
|
882
1107
|
? ['--model', selected.model]
|
|
883
1108
|
: [];
|
|
884
|
-
const timeoutMs = resolveGateExecTimeoutMs(dependencies.processEnv);
|
|
885
1109
|
const livenessIntervalMs = resolveGateLivenessIntervalMs(dependencies.processEnv);
|
|
886
|
-
|
|
887
|
-
|
|
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}).`);
|
|
888
1127
|
}
|
|
889
1128
|
try {
|
|
890
1129
|
return await dependencies.runProcess(command, [...baseArgs, ...modelArgs, ...prompt], {
|
|
891
1130
|
cwd: context.cwd,
|
|
892
1131
|
env: dependencies.processEnv,
|
|
1132
|
+
...(activityProbe ? { activityProbe } : {}),
|
|
893
1133
|
livenessIntervalMs,
|
|
894
|
-
onLiveness: ({ elapsedMs, hardBudgetMs, idleMs }) => {
|
|
1134
|
+
onLiveness: ({ elapsedMs, hardBudgetMs, idleMs, processAlive, activityProbeStatus, lastActivityEvidence, }) => {
|
|
895
1135
|
const telemetry = {
|
|
896
1136
|
elapsedMs,
|
|
897
1137
|
hardBudgetMs,
|
|
898
1138
|
idleMs,
|
|
1139
|
+
processAlive,
|
|
1140
|
+
...(activityProbeStatus ? { activityProbeStatus } : {}),
|
|
1141
|
+
...(lastActivityEvidence ? { lastActivityEvidence } : {}),
|
|
899
1142
|
target: selected.id,
|
|
900
1143
|
type: 'gate-liveness',
|
|
901
1144
|
};
|
|
@@ -903,13 +1146,18 @@ async function executeTarget(selected, prompt, context, dependencies) {
|
|
|
903
1146
|
dependencies.writeDiagnostic(`${JSON.stringify(telemetry)}\n`);
|
|
904
1147
|
}
|
|
905
1148
|
else {
|
|
906
|
-
|
|
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}.`);
|
|
907
1155
|
}
|
|
908
1156
|
},
|
|
909
1157
|
purpose: 'execute',
|
|
910
1158
|
stdin: 'ignore',
|
|
911
1159
|
stdio: 'pipe',
|
|
912
|
-
timeoutMs,
|
|
1160
|
+
timeoutMs: timeout.timeoutMs,
|
|
913
1161
|
});
|
|
914
1162
|
}
|
|
915
1163
|
catch (error) {
|
|
@@ -1283,9 +1531,11 @@ function writeReviewGateResult(context, payload) {
|
|
|
1283
1531
|
context.logger.info(payload.handoff);
|
|
1284
1532
|
}
|
|
1285
1533
|
function writeReviewGateExecutionFailure(context, payload) {
|
|
1286
|
-
const message = payload.
|
|
1287
|
-
? `Review did not complete:
|
|
1288
|
-
:
|
|
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}.`;
|
|
1289
1539
|
if (context.json) {
|
|
1290
1540
|
context.logger.json({
|
|
1291
1541
|
status: 'review_failed',
|
|
@@ -1304,9 +1554,16 @@ function writeReviewGateExecutionFailure(context, payload) {
|
|
|
1304
1554
|
...(payload.timeoutMs !== undefined
|
|
1305
1555
|
? { timeoutMs: payload.timeoutMs }
|
|
1306
1556
|
: {}),
|
|
1557
|
+
...(payload.timeoutSource !== undefined
|
|
1558
|
+
? { timeoutSource: payload.timeoutSource }
|
|
1559
|
+
: {}),
|
|
1307
1560
|
...(payload.noOutputProduced !== undefined
|
|
1308
1561
|
? { noOutputProduced: payload.noOutputProduced }
|
|
1309
1562
|
: {}),
|
|
1563
|
+
...(payload.refusal ? { refusal: payload.refusal } : {}),
|
|
1564
|
+
...(payload.activityEvidence
|
|
1565
|
+
? { activityEvidence: payload.activityEvidence }
|
|
1566
|
+
: {}),
|
|
1310
1567
|
message,
|
|
1311
1568
|
});
|
|
1312
1569
|
return;
|
|
@@ -1526,7 +1783,21 @@ async function runCrossProviderExec(prompt, options, context, dependencies) {
|
|
|
1526
1783
|
const producerIdentity = parseProducerIdentityOption(options.producerIdentity);
|
|
1527
1784
|
const selected = await resolveSelectedExecTarget(targets, options, producerIdentity, context, dependencies);
|
|
1528
1785
|
logGateDiversity(selected, context);
|
|
1529
|
-
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);
|
|
1530
1801
|
process.exitCode = result.exitCode;
|
|
1531
1802
|
}
|
|
1532
1803
|
catch (error) {
|
|
@@ -1535,6 +1806,9 @@ async function runCrossProviderExec(prompt, options, context, dependencies) {
|
|
|
1535
1806
|
}
|
|
1536
1807
|
async function runReviewGate(prompt, options, context, dependencies) {
|
|
1537
1808
|
const runId = randomUUID();
|
|
1809
|
+
let runMarkerPath;
|
|
1810
|
+
let runMarkerWritten = false;
|
|
1811
|
+
let branchLocalGateCli;
|
|
1538
1812
|
let postSelectionContext;
|
|
1539
1813
|
try {
|
|
1540
1814
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
@@ -1555,6 +1829,22 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1555
1829
|
});
|
|
1556
1830
|
const selected = await resolveSelectedExecTarget(targets, options, producerIdentity, context, dependencies);
|
|
1557
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
|
+
});
|
|
1558
1848
|
const dispatchReport = buildGateDispatchReport(gateInvocation, options.reviewScope?.trim() || 'gate-review');
|
|
1559
1849
|
postSelectionContext = {
|
|
1560
1850
|
project: projectPath,
|
|
@@ -1581,9 +1871,61 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1581
1871
|
: []),
|
|
1582
1872
|
prompt.join(' '),
|
|
1583
1873
|
]);
|
|
1584
|
-
|
|
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`);
|
|
1585
1922
|
const childExitCode = childResult.exitCode;
|
|
1586
|
-
|
|
1923
|
+
const refusal = childResult.refusal ??
|
|
1924
|
+
extractStructuredRefusal(childResult.capturedOutput ?? '');
|
|
1925
|
+
const writeRefusalFailure = () => {
|
|
1926
|
+
if (!refusal) {
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1587
1929
|
writeReviewGateExecutionFailure(context, {
|
|
1588
1930
|
runId,
|
|
1589
1931
|
target: selected.id,
|
|
@@ -1591,13 +1933,18 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1591
1933
|
projectResolutionSource: reviewProject.source,
|
|
1592
1934
|
exitCode: childExitCode,
|
|
1593
1935
|
timedOut: childResult.timedOut ?? false,
|
|
1594
|
-
timeoutMs:
|
|
1936
|
+
timeoutMs: timeout.timeoutMs,
|
|
1937
|
+
timeoutSource: timeout.source,
|
|
1938
|
+
refusal,
|
|
1939
|
+
...(childResult.activityEvidence
|
|
1940
|
+
? { activityEvidence: childResult.activityEvidence }
|
|
1941
|
+
: {}),
|
|
1595
1942
|
gateInvocation,
|
|
1596
1943
|
dispatchReport,
|
|
1597
1944
|
});
|
|
1598
|
-
process.exitCode =
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1945
|
+
process.exitCode = 1;
|
|
1946
|
+
return true;
|
|
1947
|
+
};
|
|
1601
1948
|
const after = await listReviewGateArtifactCandidates({
|
|
1602
1949
|
repoRoot,
|
|
1603
1950
|
effective,
|
|
@@ -1608,6 +1955,30 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1608
1955
|
before,
|
|
1609
1956
|
after,
|
|
1610
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
|
+
}
|
|
1611
1982
|
if (childResult.timedOut &&
|
|
1612
1983
|
artifactResolution.matchingArtifactPaths.length === 0 &&
|
|
1613
1984
|
!artifactResolution.diagnosticArtifact) {
|
|
@@ -1618,8 +1989,12 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1618
1989
|
projectResolutionSource: reviewProject.source,
|
|
1619
1990
|
exitCode: childExitCode,
|
|
1620
1991
|
timedOut: true,
|
|
1621
|
-
timeoutMs:
|
|
1992
|
+
timeoutMs: timeout.timeoutMs,
|
|
1993
|
+
timeoutSource: timeout.source,
|
|
1622
1994
|
noOutputProduced: childResult.stdoutBytes + childResult.stderrBytes === 0,
|
|
1995
|
+
...(childResult.activityEvidence
|
|
1996
|
+
? { activityEvidence: childResult.activityEvidence }
|
|
1997
|
+
: {}),
|
|
1623
1998
|
gateInvocation,
|
|
1624
1999
|
dispatchReport,
|
|
1625
2000
|
});
|
|
@@ -1660,6 +2035,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1660
2035
|
if (producedArtifact.containingProject !== reviewProject.path ||
|
|
1661
2036
|
(reviewProject.source === 'declared' &&
|
|
1662
2037
|
initialTargetCorroboration.project !== 'matched')) {
|
|
2038
|
+
if (writeRefusalFailure()) {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
1663
2041
|
writeReviewGateTargetingFailure(context, {
|
|
1664
2042
|
runId,
|
|
1665
2043
|
target: selected.id,
|
|
@@ -1681,6 +2059,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1681
2059
|
}
|
|
1682
2060
|
if (!producedArtifact.generatedAt ||
|
|
1683
2061
|
!Number.isFinite(producedArtifact.generatedTime)) {
|
|
2062
|
+
if (writeRefusalFailure()) {
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
1684
2065
|
writeReviewGateArtifactValidationFailure(context, {
|
|
1685
2066
|
runId,
|
|
1686
2067
|
target: selected.id,
|
|
@@ -1708,6 +2089,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1708
2089
|
});
|
|
1709
2090
|
}
|
|
1710
2091
|
catch (error) {
|
|
2092
|
+
if (writeRefusalFailure()) {
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
1711
2095
|
const detail = error instanceof Error ? error.message : String(error);
|
|
1712
2096
|
writeReviewGateArtifactValidationFailure(context, {
|
|
1713
2097
|
runId,
|
|
@@ -1729,6 +2113,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1729
2113
|
const corroboration = corroborateGateInvocation(gateInvocation, verdict.gateInvocation, targetCorroboration);
|
|
1730
2114
|
if (corroboration.run !== 'matched' ||
|
|
1731
2115
|
corroboration.invocation !== 'matched') {
|
|
2116
|
+
if (writeRefusalFailure()) {
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
1732
2119
|
const missing = corroboration.run === 'missing' ||
|
|
1733
2120
|
corroboration.invocation === 'missing';
|
|
1734
2121
|
writeReviewGateArtifactValidationFailure(context, {
|
|
@@ -1750,6 +2137,9 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1750
2137
|
return;
|
|
1751
2138
|
}
|
|
1752
2139
|
if (verdict.invocation !== 'gate') {
|
|
2140
|
+
if (writeRefusalFailure()) {
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
1753
2143
|
writeReviewGateArtifactValidationFailure(context, {
|
|
1754
2144
|
runId,
|
|
1755
2145
|
target: selected.id,
|
|
@@ -1809,6 +2199,14 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1809
2199
|
writeError(context, error);
|
|
1810
2200
|
}
|
|
1811
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
|
+
}
|
|
1812
2210
|
}
|
|
1813
2211
|
export function createGateCommand(overrides = {}) {
|
|
1814
2212
|
const dependencies = {
|
|
@@ -1816,6 +2214,10 @@ export function createGateCommand(overrides = {}) {
|
|
|
1816
2214
|
...overrides,
|
|
1817
2215
|
};
|
|
1818
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
|
+
}));
|
|
1819
2221
|
cmd
|
|
1820
2222
|
.command('resolve')
|
|
1821
2223
|
.description('Print the resolved gate configuration for a skill')
|
|
@@ -1849,11 +2251,13 @@ export function createGateCommand(overrides = {}) {
|
|
|
1849
2251
|
});
|
|
1850
2252
|
cmd
|
|
1851
2253
|
.command('cross-provider-exec')
|
|
2254
|
+
.alias('exec')
|
|
1852
2255
|
.description('Run a prompt through an alternate configured runtime target')
|
|
1853
2256
|
.option('--target <id>', 'Run this exact exec target')
|
|
1854
2257
|
.option('--avoid <mode>', 'Avoidance mode: same-family, same-runtime, or none')
|
|
1855
2258
|
.option('--current-runtime <runtime>', 'Override detected runtime for testing or manual routing')
|
|
1856
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})`)
|
|
1857
2261
|
.argument('<prompt...>', 'Prompt arguments appended to the target command')
|
|
1858
2262
|
.action(async (prompt, options, command) => {
|
|
1859
2263
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
@@ -1866,6 +2270,7 @@ export function createGateCommand(overrides = {}) {
|
|
|
1866
2270
|
.option('--avoid <mode>', 'Avoidance mode: same-family, same-runtime, or none')
|
|
1867
2271
|
.option('--current-runtime <runtime>', 'Override detected runtime for testing or manual routing')
|
|
1868
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})`)
|
|
1869
2274
|
.option('--project <path-or-name>', 'Project path or name to review; defaults to the active project')
|
|
1870
2275
|
.option('--review-scope <scope>', 'Review scope hint for the provider')
|
|
1871
2276
|
.option('--review-type <type>', 'Review type hint for the provider')
|
|
@@ -1887,6 +2292,7 @@ export function createGateCommand(overrides = {}) {
|
|
|
1887
2292
|
.option('--invocation-model <model>', 'Configured invocation model or provider-default')
|
|
1888
2293
|
.option('--invocation-reasoning-effort <effort>', 'Configured reasoning effort or provider-default')
|
|
1889
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})`)
|
|
1890
2296
|
.option('--disable', 'Disable this exec target in the selected layer')
|
|
1891
2297
|
.option('--layer <layer>', 'Config layer to write: shared, local, or user')
|
|
1892
2298
|
.action(async (targetId, options, command) => {
|