@open-agent-toolkit/cli 0.2.23 → 0.2.24
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/workflow-gates.md +27 -3
- package/assets/public-package-versions.json +4 -4
- package/dist/commands/gate/child-process.d.ts +32 -0
- package/dist/commands/gate/child-process.d.ts.map +1 -0
- package/dist/commands/gate/child-process.js +210 -0
- package/dist/commands/gate/index.d.ts +2 -31
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +24 -150
- package/package.json +2 -2
|
@@ -717,8 +717,21 @@ Each liveness tick keeps stdout-idle time and also reports `processAlive` plus
|
|
|
717
717
|
the latest metadata-only transcript activity as `lastActivityEvidence` when
|
|
718
718
|
available. The probe performs bounded depth-two traversal and compares newest
|
|
719
719
|
mtime plus total size with its spawn baseline. It never reads transcript
|
|
720
|
-
contents
|
|
721
|
-
|
|
720
|
+
contents and never changes pass/fail or receive eligibility.
|
|
721
|
+
|
|
722
|
+
When the hard timeout fires, OAT stops periodic liveness ticks, sends `SIGTERM`,
|
|
723
|
+
schedules the existing `SIGKILL` fallback, and then starts one final transcript
|
|
724
|
+
observation with the timeout timestamp. Once the child closes, result resolution
|
|
725
|
+
waits no longer than the one-second observation grace and does not delay either
|
|
726
|
+
signal. A final sample available before result resolution supersedes older
|
|
727
|
+
in-flight periodic work; otherwise OAT retains the latest valid periodic
|
|
728
|
+
evidence.
|
|
729
|
+
|
|
730
|
+
On a normal child close, OAT waits within the same one-second grace only when a
|
|
731
|
+
periodic transcript observation is already in flight. This preserves late
|
|
732
|
+
evidence but can add up to one second to an otherwise successful execution. The
|
|
733
|
+
one-second observation grace and five-second force-kill fallback are fixed
|
|
734
|
+
safety bounds, not environment-tunable settings.
|
|
722
735
|
|
|
723
736
|
Claude and Cursor evidence has `scope: project-dir`. Codex's date-sharded
|
|
724
737
|
sessions directory is shared across the runtime, so its evidence has
|
|
@@ -726,7 +739,18 @@ sessions directory is shared across the runtime, so its evidence has
|
|
|
726
739
|
attributable to this gate child).” Probe errors fail soft to process and stdout
|
|
727
740
|
telemetry.
|
|
728
741
|
|
|
729
|
-
Timeout and child-failure envelopes include the latest `activityEvidence`.
|
|
742
|
+
Timeout and child-failure JSON envelopes include the latest `activityEvidence`.
|
|
743
|
+
For human timeout and non-refusal child-failure output, OAT prints a second line
|
|
744
|
+
with this shape:
|
|
745
|
+
|
|
746
|
+
```text
|
|
747
|
+
Activity evidence: <runtime> <project|ambient> transcript metadata <changed|did not change> since baseline; observed <milliseconds>ms ago; latest transcript change was <milliseconds>ms before observation.
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
When the evidence scope is ambient, the line ends with `This activity is not
|
|
751
|
+
attributable to this gate child.` Structured refusals omit the extra human
|
|
752
|
+
diagnostic because the refusal itself explains why execution stopped.
|
|
753
|
+
|
|
730
754
|
Before spawn, the gate also writes a transient marker under the system temp
|
|
731
755
|
directory at `oat-gate-runs/<runId>.json`; startup diagnostics print its path.
|
|
732
756
|
The marker records target, runtime, project, review type/scope, start time,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { GateActivityEvidence, GateActivityProbe, GateActivityProbeStatus } from './activity-probes.js';
|
|
2
|
+
export interface ProcessRunOptions {
|
|
3
|
+
activityProbe?: GateActivityProbe;
|
|
4
|
+
cwd: string;
|
|
5
|
+
env: NodeJS.ProcessEnv;
|
|
6
|
+
livenessIntervalMs?: number;
|
|
7
|
+
onLiveness?: (snapshot: GateLivenessSnapshot) => void;
|
|
8
|
+
purpose: 'host-detection' | 'availability' | 'execute';
|
|
9
|
+
stdin: 'ignore' | 'inherit';
|
|
10
|
+
stdio: 'ignore' | 'inherit' | 'pipe';
|
|
11
|
+
stdoutDestination?: 'stdout' | 'stderr';
|
|
12
|
+
timeoutMs: number;
|
|
13
|
+
}
|
|
14
|
+
export interface ProcessRunResult {
|
|
15
|
+
activityEvidence?: GateActivityEvidence;
|
|
16
|
+
exitCode: number;
|
|
17
|
+
refusal?: string;
|
|
18
|
+
stderrBytes: number;
|
|
19
|
+
stdoutBytes: number;
|
|
20
|
+
timedOut?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface GateLivenessSnapshot {
|
|
23
|
+
elapsedMs: number;
|
|
24
|
+
hardBudgetMs: number;
|
|
25
|
+
idleMs: number;
|
|
26
|
+
processAlive: boolean;
|
|
27
|
+
activityProbeStatus?: GateActivityProbeStatus;
|
|
28
|
+
lastActivityEvidence?: GateActivityEvidence;
|
|
29
|
+
}
|
|
30
|
+
export declare function runChildProcess(command: string, args: string[], options: ProcessRunOptions): Promise<ProcessRunResult>;
|
|
31
|
+
export declare function extractStructuredRefusal(output: string): string | undefined;
|
|
32
|
+
//# sourceMappingURL=child-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-process.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/child-process.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,mBAAmB,CAAC;AAS3B,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,OAAO,EAAE,gBAAgB,GAAG,cAAc,GAAG,SAAS,CAAC;IACvD,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;IACrC,iBAAiB,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,mBAAmB,CAAC,EAAE,uBAAuB,CAAC;IAC9C,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAgN3B;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3E"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
// Keep these safety bounds fixed rather than environment-tunable. In particular,
|
|
3
|
+
// changing the force-kill fallback would alter the hard-timeout contract.
|
|
4
|
+
// Revisit the observation grace only if field evidence shows large transcript
|
|
5
|
+
// directories routinely exceed it.
|
|
6
|
+
const FORCE_KILL_GRACE_MS = 5_000;
|
|
7
|
+
const ACTIVITY_OBSERVATION_GRACE_MS = 1_000;
|
|
8
|
+
export async function runChildProcess(command, args, options) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
let timedOut = false;
|
|
11
|
+
let suppressLiveness = false;
|
|
12
|
+
let killTimeout = null;
|
|
13
|
+
let stderrBytes = 0;
|
|
14
|
+
let stdoutBytes = 0;
|
|
15
|
+
let refusal;
|
|
16
|
+
let stdoutLineBuffer = '';
|
|
17
|
+
let stderrLineBuffer = '';
|
|
18
|
+
let latestActivityEvidence;
|
|
19
|
+
let latestActivityProbeStatus;
|
|
20
|
+
let activityObservationSequence = 0;
|
|
21
|
+
let latestActivityEvidenceSequence = 0;
|
|
22
|
+
let livenessProbePending = false;
|
|
23
|
+
let pendingPeriodicObservation;
|
|
24
|
+
let finalActivityObservation;
|
|
25
|
+
const startedAt = Date.now();
|
|
26
|
+
let lastActivityAt = startedAt;
|
|
27
|
+
const child = spawn(command, args, {
|
|
28
|
+
cwd: options.cwd,
|
|
29
|
+
env: options.env,
|
|
30
|
+
stdio: options.stdio === 'pipe'
|
|
31
|
+
? [options.stdin, 'pipe', 'pipe']
|
|
32
|
+
: options.stdio,
|
|
33
|
+
});
|
|
34
|
+
const recordActivity = () => {
|
|
35
|
+
lastActivityAt = Date.now();
|
|
36
|
+
};
|
|
37
|
+
const scanChunk = (buffer, chunk) => {
|
|
38
|
+
const combined = buffer + chunk.toString('utf8');
|
|
39
|
+
const lines = combined.split('\n');
|
|
40
|
+
const remainder = lines.pop() ?? '';
|
|
41
|
+
if (!refusal) {
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
refusal = extractStructuredRefusal(line.replace(/\r$/, ''));
|
|
44
|
+
if (refusal) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return remainder;
|
|
50
|
+
};
|
|
51
|
+
child.stdout?.on('data', (chunk) => {
|
|
52
|
+
stdoutBytes += chunk.byteLength;
|
|
53
|
+
stdoutLineBuffer = scanChunk(stdoutLineBuffer, chunk);
|
|
54
|
+
recordActivity();
|
|
55
|
+
const destination = options.stdoutDestination === 'stderr'
|
|
56
|
+
? process.stderr
|
|
57
|
+
: process.stdout;
|
|
58
|
+
destination.write(chunk);
|
|
59
|
+
});
|
|
60
|
+
child.stderr?.on('data', (chunk) => {
|
|
61
|
+
stderrBytes += chunk.byteLength;
|
|
62
|
+
stderrLineBuffer = scanChunk(stderrLineBuffer, chunk);
|
|
63
|
+
recordActivity();
|
|
64
|
+
process.stderr.write(chunk);
|
|
65
|
+
});
|
|
66
|
+
const processAlive = () => {
|
|
67
|
+
if (child.pid === undefined)
|
|
68
|
+
return false;
|
|
69
|
+
try {
|
|
70
|
+
process.kill(child.pid, 0);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const observeActivity = async (observedAt) => {
|
|
78
|
+
if (!options.activityProbe)
|
|
79
|
+
return undefined;
|
|
80
|
+
const sequence = ++activityObservationSequence;
|
|
81
|
+
try {
|
|
82
|
+
const activityProbeStatus = await options.activityProbe.observe(observedAt);
|
|
83
|
+
const evidence = activityProbeStatus.evidence;
|
|
84
|
+
if (evidence && sequence > latestActivityEvidenceSequence) {
|
|
85
|
+
latestActivityEvidence = evidence;
|
|
86
|
+
latestActivityEvidenceSequence = sequence;
|
|
87
|
+
}
|
|
88
|
+
return activityProbeStatus;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Activity evidence is diagnostic only. Preserve the last valid sample.
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const settleWithinActivityGrace = (observation) => new Promise((resolveObservation) => {
|
|
96
|
+
// Keep this timer referenced so a closed child cannot end the process
|
|
97
|
+
// before the bounded diagnostic observation settles.
|
|
98
|
+
const graceTimeout = setTimeout(resolveObservation, ACTIVITY_OBSERVATION_GRACE_MS);
|
|
99
|
+
const settle = () => {
|
|
100
|
+
clearTimeout(graceTimeout);
|
|
101
|
+
resolveObservation();
|
|
102
|
+
};
|
|
103
|
+
void observation.then(settle, settle);
|
|
104
|
+
});
|
|
105
|
+
const livenessInterval = options.onLiveness && options.livenessIntervalMs
|
|
106
|
+
? setInterval(() => {
|
|
107
|
+
if (livenessProbePending)
|
|
108
|
+
return;
|
|
109
|
+
livenessProbePending = true;
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const observation = (async () => {
|
|
112
|
+
const activityProbeStatus = await observeActivity(now);
|
|
113
|
+
if (activityProbeStatus) {
|
|
114
|
+
latestActivityProbeStatus = activityProbeStatus;
|
|
115
|
+
}
|
|
116
|
+
if (suppressLiveness)
|
|
117
|
+
return;
|
|
118
|
+
options.onLiveness?.({
|
|
119
|
+
elapsedMs: now - startedAt,
|
|
120
|
+
hardBudgetMs: options.timeoutMs,
|
|
121
|
+
idleMs: now - lastActivityAt,
|
|
122
|
+
processAlive: processAlive(),
|
|
123
|
+
...(latestActivityProbeStatus
|
|
124
|
+
? { activityProbeStatus: latestActivityProbeStatus }
|
|
125
|
+
: {}),
|
|
126
|
+
...(latestActivityEvidence
|
|
127
|
+
? { lastActivityEvidence: latestActivityEvidence }
|
|
128
|
+
: {}),
|
|
129
|
+
});
|
|
130
|
+
})();
|
|
131
|
+
pendingPeriodicObservation = observation;
|
|
132
|
+
const clearPending = () => {
|
|
133
|
+
if (pendingPeriodicObservation === observation) {
|
|
134
|
+
pendingPeriodicObservation = undefined;
|
|
135
|
+
}
|
|
136
|
+
livenessProbePending = false;
|
|
137
|
+
};
|
|
138
|
+
void observation.then(clearPending, clearPending);
|
|
139
|
+
}, options.livenessIntervalMs)
|
|
140
|
+
: null;
|
|
141
|
+
livenessInterval?.unref();
|
|
142
|
+
const timeout = setTimeout(() => {
|
|
143
|
+
timedOut = true;
|
|
144
|
+
suppressLiveness = true;
|
|
145
|
+
if (livenessInterval) {
|
|
146
|
+
clearInterval(livenessInterval);
|
|
147
|
+
}
|
|
148
|
+
child.kill('SIGTERM');
|
|
149
|
+
killTimeout = setTimeout(() => {
|
|
150
|
+
child.kill('SIGKILL');
|
|
151
|
+
}, FORCE_KILL_GRACE_MS);
|
|
152
|
+
killTimeout.unref();
|
|
153
|
+
finalActivityObservation = observeActivity(Date.now());
|
|
154
|
+
}, options.timeoutMs);
|
|
155
|
+
timeout.unref();
|
|
156
|
+
child.on('error', (error) => {
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
if (livenessInterval) {
|
|
159
|
+
clearInterval(livenessInterval);
|
|
160
|
+
}
|
|
161
|
+
if (killTimeout) {
|
|
162
|
+
clearTimeout(killTimeout);
|
|
163
|
+
}
|
|
164
|
+
reject(error);
|
|
165
|
+
});
|
|
166
|
+
child.on('close', (code) => {
|
|
167
|
+
suppressLiveness = true;
|
|
168
|
+
clearTimeout(timeout);
|
|
169
|
+
if (livenessInterval) {
|
|
170
|
+
clearInterval(livenessInterval);
|
|
171
|
+
}
|
|
172
|
+
if (killTimeout) {
|
|
173
|
+
clearTimeout(killTimeout);
|
|
174
|
+
}
|
|
175
|
+
void (async () => {
|
|
176
|
+
if (finalActivityObservation) {
|
|
177
|
+
const periodicObservation = pendingPeriodicObservation;
|
|
178
|
+
const finalThenFallback = finalActivityObservation.then(async (activityProbeStatus) => {
|
|
179
|
+
if (!activityProbeStatus?.evidence && periodicObservation) {
|
|
180
|
+
await periodicObservation;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
await settleWithinActivityGrace(finalThenFallback);
|
|
184
|
+
}
|
|
185
|
+
else if (pendingPeriodicObservation) {
|
|
186
|
+
// Preserve evidence already in flight on a normal close. This can add
|
|
187
|
+
// up to the bounded grace to an otherwise successful execution.
|
|
188
|
+
await settleWithinActivityGrace(pendingPeriodicObservation);
|
|
189
|
+
}
|
|
190
|
+
refusal ??=
|
|
191
|
+
extractStructuredRefusal(stdoutLineBuffer.replace(/\r$/, '')) ??
|
|
192
|
+
extractStructuredRefusal(stderrLineBuffer.replace(/\r$/, ''));
|
|
193
|
+
resolve({
|
|
194
|
+
...(latestActivityEvidence
|
|
195
|
+
? { activityEvidence: latestActivityEvidence }
|
|
196
|
+
: {}),
|
|
197
|
+
exitCode: timedOut ? 124 : (code ?? 1),
|
|
198
|
+
...(refusal ? { refusal } : {}),
|
|
199
|
+
stderrBytes,
|
|
200
|
+
stdoutBytes,
|
|
201
|
+
...(timedOut ? { timedOut: true } : {}),
|
|
202
|
+
});
|
|
203
|
+
})();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
export function extractStructuredRefusal(output) {
|
|
208
|
+
const match = output.match(/^OAT_GATE_REFUSAL: (.*)$/m);
|
|
209
|
+
return match?.[1]?.replace(/\r$/, '');
|
|
210
|
+
}
|
|
@@ -5,8 +5,9 @@ import { type ResolvedConfig } from '../../config/resolve.js';
|
|
|
5
5
|
import { type ModelFamily } from '../../providers/identity/family.js';
|
|
6
6
|
import { type IdentityConfidence, type IdentityProvenance } from '../../providers/identity/provenance.js';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
|
-
import { createGateActivityProbe
|
|
8
|
+
import { createGateActivityProbe } from './activity-probes.js';
|
|
9
9
|
import { createBranchLocalGateCli, currentGateCliLaunch, readGateRouteReceipt, removeBranchLocalGateCli } from './branch-local-cli.js';
|
|
10
|
+
import { type ProcessRunOptions, type ProcessRunResult } from './child-process.js';
|
|
10
11
|
import { parseReviewGateVerdict } from './review-verdict.js';
|
|
11
12
|
interface GateCommandDependencies {
|
|
12
13
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
@@ -31,36 +32,6 @@ interface GateCommandDependencies {
|
|
|
31
32
|
removeGateRunMarker: (path: string, warn: (message: string) => void) => Promise<void>;
|
|
32
33
|
writeDiagnostic: (message: string) => void;
|
|
33
34
|
}
|
|
34
|
-
interface ProcessRunOptions {
|
|
35
|
-
activityProbe?: GateActivityProbe;
|
|
36
|
-
cwd: string;
|
|
37
|
-
env: NodeJS.ProcessEnv;
|
|
38
|
-
livenessIntervalMs?: number;
|
|
39
|
-
onLiveness?: (snapshot: GateLivenessSnapshot) => void;
|
|
40
|
-
purpose: 'host-detection' | 'availability' | 'execute';
|
|
41
|
-
stdin: 'ignore' | 'inherit';
|
|
42
|
-
stdio: 'ignore' | 'inherit' | 'pipe';
|
|
43
|
-
stdoutDestination?: 'stdout' | 'stderr';
|
|
44
|
-
timeoutMs: number;
|
|
45
|
-
}
|
|
46
|
-
interface ProcessRunResult {
|
|
47
|
-
activityEvidence?: GateActivityEvidence;
|
|
48
|
-
activityProbeStatus?: GateActivityProbeStatus;
|
|
49
|
-
capturedOutput?: string;
|
|
50
|
-
exitCode: number;
|
|
51
|
-
refusal?: string;
|
|
52
|
-
stderrBytes: number;
|
|
53
|
-
stdoutBytes: number;
|
|
54
|
-
timedOut?: boolean;
|
|
55
|
-
}
|
|
56
|
-
interface GateLivenessSnapshot {
|
|
57
|
-
elapsedMs: number;
|
|
58
|
-
hardBudgetMs: number;
|
|
59
|
-
idleMs: number;
|
|
60
|
-
processAlive: boolean;
|
|
61
|
-
activityProbeStatus?: GateActivityProbeStatus;
|
|
62
|
-
lastActivityEvidence?: GateActivityEvidence;
|
|
63
|
-
}
|
|
64
35
|
type GateTimeoutSource = 'cli' | 'target' | 'config' | 'env' | 'scope-default' | 'default';
|
|
65
36
|
interface GateRunMarker {
|
|
66
37
|
runId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAShE,OAAO,EAWL,KAAK,UAAU,EAIf,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EAEhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAWzB,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAExB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EACL,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
@@ -20,6 +20,7 @@ import { Command } from 'commander';
|
|
|
20
20
|
import YAML from 'yaml';
|
|
21
21
|
import { createGateActivityProbe, } from './activity-probes.js';
|
|
22
22
|
import { createBranchLocalGateCli, currentGateCliLaunch, readGateRouteReceipt, removeBranchLocalGateCli, } from './branch-local-cli.js';
|
|
23
|
+
import { runChildProcess, } from './child-process.js';
|
|
23
24
|
import { parseReviewGateVerdict, severityDisplayName, } from './review-verdict.js';
|
|
24
25
|
import { createGateRouteCommand } from './route.js';
|
|
25
26
|
const DEFAULT_DEPENDENCIES = {
|
|
@@ -75,6 +76,7 @@ const REVIEW_GATE_CONTEXT_NOTE = [
|
|
|
75
76
|
const GATE_CHECK_TIMEOUT_MS = 5_000;
|
|
76
77
|
const GATE_EXEC_TIMEOUT_MS = 15 * 60 * 1_000;
|
|
77
78
|
const GATE_LIVENESS_INTERVAL_MS = 30_000;
|
|
79
|
+
const AMBIENT_ACTIVITY_ATTRIBUTION = 'not attributable to this gate child';
|
|
78
80
|
async function writeGateRunMarker(path, marker, warn) {
|
|
79
81
|
try {
|
|
80
82
|
await mkdir(join(tmpdir(), 'oat-gate-runs'), { recursive: true });
|
|
@@ -239,152 +241,6 @@ function corroborateGateInvocation(expected, actual, targetCorroboration) {
|
|
|
239
241
|
},
|
|
240
242
|
};
|
|
241
243
|
}
|
|
242
|
-
async function runChildProcess(command, args, options) {
|
|
243
|
-
return new Promise((resolve, reject) => {
|
|
244
|
-
let timedOut = false;
|
|
245
|
-
let killTimeout = null;
|
|
246
|
-
let stderrBytes = 0;
|
|
247
|
-
let stdoutBytes = 0;
|
|
248
|
-
let refusal;
|
|
249
|
-
let stdoutLineBuffer = '';
|
|
250
|
-
let stderrLineBuffer = '';
|
|
251
|
-
let latestActivityEvidence;
|
|
252
|
-
let latestActivityProbeStatus;
|
|
253
|
-
let livenessProbePending = false;
|
|
254
|
-
const startedAt = Date.now();
|
|
255
|
-
let lastActivityAt = startedAt;
|
|
256
|
-
const child = spawn(command, args, {
|
|
257
|
-
cwd: options.cwd,
|
|
258
|
-
env: options.env,
|
|
259
|
-
stdio: options.stdio === 'pipe'
|
|
260
|
-
? [options.stdin, 'pipe', 'pipe']
|
|
261
|
-
: options.stdio,
|
|
262
|
-
});
|
|
263
|
-
const recordActivity = () => {
|
|
264
|
-
lastActivityAt = Date.now();
|
|
265
|
-
};
|
|
266
|
-
const scanChunk = (buffer, chunk) => {
|
|
267
|
-
const combined = buffer + chunk.toString('utf8');
|
|
268
|
-
const lines = combined.split('\n');
|
|
269
|
-
const remainder = lines.pop() ?? '';
|
|
270
|
-
if (!refusal) {
|
|
271
|
-
for (const line of lines) {
|
|
272
|
-
refusal = extractStructuredRefusal(line.replace(/\r$/, ''));
|
|
273
|
-
if (refusal) {
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
return remainder;
|
|
279
|
-
};
|
|
280
|
-
child.stdout?.on('data', (chunk) => {
|
|
281
|
-
stdoutBytes += chunk.byteLength;
|
|
282
|
-
stdoutLineBuffer = scanChunk(stdoutLineBuffer, chunk);
|
|
283
|
-
recordActivity();
|
|
284
|
-
const destination = options.stdoutDestination === 'stderr'
|
|
285
|
-
? process.stderr
|
|
286
|
-
: process.stdout;
|
|
287
|
-
destination.write(chunk);
|
|
288
|
-
});
|
|
289
|
-
child.stderr?.on('data', (chunk) => {
|
|
290
|
-
stderrBytes += chunk.byteLength;
|
|
291
|
-
stderrLineBuffer = scanChunk(stderrLineBuffer, chunk);
|
|
292
|
-
recordActivity();
|
|
293
|
-
process.stderr.write(chunk);
|
|
294
|
-
});
|
|
295
|
-
const processAlive = () => {
|
|
296
|
-
if (child.pid === undefined)
|
|
297
|
-
return false;
|
|
298
|
-
try {
|
|
299
|
-
process.kill(child.pid, 0);
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
const livenessInterval = options.onLiveness && options.livenessIntervalMs
|
|
307
|
-
? setInterval(() => {
|
|
308
|
-
if (livenessProbePending)
|
|
309
|
-
return;
|
|
310
|
-
livenessProbePending = true;
|
|
311
|
-
const now = Date.now();
|
|
312
|
-
void (async () => {
|
|
313
|
-
const activityProbeStatus = await options.activityProbe?.observe(now);
|
|
314
|
-
const evidence = activityProbeStatus?.evidence;
|
|
315
|
-
if (activityProbeStatus) {
|
|
316
|
-
latestActivityProbeStatus = activityProbeStatus;
|
|
317
|
-
}
|
|
318
|
-
if (evidence)
|
|
319
|
-
latestActivityEvidence = evidence;
|
|
320
|
-
options.onLiveness?.({
|
|
321
|
-
elapsedMs: now - startedAt,
|
|
322
|
-
hardBudgetMs: options.timeoutMs,
|
|
323
|
-
idleMs: now - lastActivityAt,
|
|
324
|
-
processAlive: processAlive(),
|
|
325
|
-
...(latestActivityProbeStatus
|
|
326
|
-
? { activityProbeStatus: latestActivityProbeStatus }
|
|
327
|
-
: {}),
|
|
328
|
-
...(latestActivityEvidence
|
|
329
|
-
? { lastActivityEvidence: latestActivityEvidence }
|
|
330
|
-
: {}),
|
|
331
|
-
});
|
|
332
|
-
})().finally(() => {
|
|
333
|
-
livenessProbePending = false;
|
|
334
|
-
});
|
|
335
|
-
}, options.livenessIntervalMs)
|
|
336
|
-
: null;
|
|
337
|
-
livenessInterval?.unref();
|
|
338
|
-
const timeout = setTimeout(() => {
|
|
339
|
-
timedOut = true;
|
|
340
|
-
child.kill('SIGTERM');
|
|
341
|
-
killTimeout = setTimeout(() => {
|
|
342
|
-
child.kill('SIGKILL');
|
|
343
|
-
}, 5_000);
|
|
344
|
-
killTimeout.unref();
|
|
345
|
-
}, options.timeoutMs);
|
|
346
|
-
timeout.unref();
|
|
347
|
-
child.on('error', (error) => {
|
|
348
|
-
clearTimeout(timeout);
|
|
349
|
-
if (livenessInterval) {
|
|
350
|
-
clearInterval(livenessInterval);
|
|
351
|
-
}
|
|
352
|
-
if (killTimeout) {
|
|
353
|
-
clearTimeout(killTimeout);
|
|
354
|
-
}
|
|
355
|
-
reject(error);
|
|
356
|
-
});
|
|
357
|
-
child.on('close', (code) => {
|
|
358
|
-
clearTimeout(timeout);
|
|
359
|
-
if (livenessInterval) {
|
|
360
|
-
clearInterval(livenessInterval);
|
|
361
|
-
}
|
|
362
|
-
if (killTimeout) {
|
|
363
|
-
clearTimeout(killTimeout);
|
|
364
|
-
}
|
|
365
|
-
refusal ??=
|
|
366
|
-
extractStructuredRefusal(stdoutLineBuffer.replace(/\r$/, '')) ??
|
|
367
|
-
extractStructuredRefusal(stderrLineBuffer.replace(/\r$/, ''));
|
|
368
|
-
resolve({
|
|
369
|
-
...(latestActivityEvidence
|
|
370
|
-
? { activityEvidence: latestActivityEvidence }
|
|
371
|
-
: {}),
|
|
372
|
-
...(latestActivityProbeStatus
|
|
373
|
-
? { activityProbeStatus: latestActivityProbeStatus }
|
|
374
|
-
: {}),
|
|
375
|
-
exitCode: timedOut ? 124 : (code ?? 1),
|
|
376
|
-
...(refusal ? { refusal } : {}),
|
|
377
|
-
stderrBytes,
|
|
378
|
-
stdoutBytes,
|
|
379
|
-
...(timedOut ? { timedOut: true } : {}),
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
function extractStructuredRefusal(output) {
|
|
385
|
-
const match = output.match(/^OAT_GATE_REFUSAL: (.*)$/m);
|
|
386
|
-
return match?.[1]?.replace(/\r$/, '');
|
|
387
|
-
}
|
|
388
244
|
function isGateWriteLayer(value) {
|
|
389
245
|
return VALID_WRITE_LAYERS.includes(value);
|
|
390
246
|
}
|
|
@@ -1184,7 +1040,7 @@ async function executeTarget(selected, prompt, context, dependencies, timeout) {
|
|
|
1184
1040
|
else {
|
|
1185
1041
|
const activityDescription = lastActivityEvidence
|
|
1186
1042
|
? lastActivityEvidence.scope === 'ambient-runtime'
|
|
1187
|
-
?
|
|
1043
|
+
? `ambient runtime activity (${AMBIENT_ACTIVITY_ATTRIBUTION})`
|
|
1188
1044
|
: 'project-directory activity'
|
|
1189
1045
|
: `${activityProbeStatus?.status ?? 'unavailable'} (${activityProbeStatus?.attemptedPath ?? 'no path'})`;
|
|
1190
1046
|
context.logger.info(`Gate liveness: target=${selected.id} elapsed_ms=${elapsedMs} idle_ms=${idleMs} hard_budget_ms=${hardBudgetMs} process_alive=${processAlive} activity_evidence=${activityDescription}.`);
|
|
@@ -1606,6 +1462,25 @@ function writeReviewGateExecutionFailure(context, payload) {
|
|
|
1606
1462
|
return;
|
|
1607
1463
|
}
|
|
1608
1464
|
context.logger.error(message);
|
|
1465
|
+
if (payload.activityEvidence && !payload.refusal) {
|
|
1466
|
+
context.logger.error(formatGateActivityEvidenceDiagnostic(payload.activityEvidence));
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
function formatGateActivityEvidenceDiagnostic(evidence, now = Date.now()) {
|
|
1470
|
+
const transcriptScope = evidence.scope === 'ambient-runtime'
|
|
1471
|
+
? `${evidence.runtime} ambient transcript`
|
|
1472
|
+
: `${evidence.runtime} project transcript`;
|
|
1473
|
+
const baselineState = evidence.changedSinceBaseline
|
|
1474
|
+
? 'changed since baseline'
|
|
1475
|
+
: 'did not change since baseline';
|
|
1476
|
+
const observedAgoMs = Math.max(0, now - evidence.observedAt);
|
|
1477
|
+
const lastChangeTiming = evidence.lastChangeAt === null
|
|
1478
|
+
? 'latest transcript change time unavailable'
|
|
1479
|
+
: `latest transcript change was ${Math.max(0, evidence.observedAt - evidence.lastChangeAt)}ms before observation`;
|
|
1480
|
+
const attributionWarning = evidence.scope === 'ambient-runtime'
|
|
1481
|
+
? ` This activity is ${AMBIENT_ACTIVITY_ATTRIBUTION}.`
|
|
1482
|
+
: '';
|
|
1483
|
+
return `Activity evidence: ${transcriptScope} metadata ${baselineState}; observed ${observedAgoMs}ms ago; ${lastChangeTiming}.${attributionWarning}`;
|
|
1609
1484
|
}
|
|
1610
1485
|
function writeReviewGateUnexpectedFailure(context, payload) {
|
|
1611
1486
|
const message = payload.error instanceof Error
|
|
@@ -2085,8 +1960,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
2085
1960
|
...routeReceipt,
|
|
2086
1961
|
})}\n`);
|
|
2087
1962
|
const childExitCode = childResult.exitCode;
|
|
2088
|
-
const refusal = childResult.refusal
|
|
2089
|
-
extractStructuredRefusal(childResult.capturedOutput ?? '');
|
|
1963
|
+
const refusal = childResult.refusal;
|
|
2090
1964
|
const writeRefusalFailure = () => {
|
|
2091
1965
|
if (!refusal) {
|
|
2092
1966
|
return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-agent-toolkit/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open Agent Toolkit CLI",
|
|
6
6
|
"homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ora": "^9.0.0",
|
|
35
35
|
"yaml": "2.8.2",
|
|
36
36
|
"zod": "^3.25.76",
|
|
37
|
-
"@open-agent-toolkit/control-plane": "0.2.
|
|
37
|
+
"@open-agent-toolkit/control-plane": "0.2.24"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.0",
|