agentxchain 2.155.73 → 2.156.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agentxchain.js +22 -0
- package/dashboard/app.js +54 -0
- package/dashboard/components/org-audit-trail.js +161 -0
- package/dashboard/components/org-history.js +140 -0
- package/dashboard/components/org-overview.js +145 -0
- package/dashboard/components/org-runs.js +168 -0
- package/dashboard/index.html +4 -0
- package/package.json +2 -1
- package/src/commands/ci-report.js +80 -0
- package/src/commands/doctor.js +22 -1
- package/src/commands/intake-approve.js +1 -0
- package/src/commands/replay.js +1 -0
- package/src/commands/run.js +47 -0
- package/src/commands/serve.js +64 -0
- package/src/commands/step.js +16 -0
- package/src/commands/verify.js +1 -0
- package/src/lib/adapters/local-cli-adapter.js +147 -0
- package/src/lib/api/execution-worker.js +192 -0
- package/src/lib/api/hosted-runner.js +494 -0
- package/src/lib/api/job-queue.js +152 -0
- package/src/lib/api/org-state-aggregator.js +428 -0
- package/src/lib/api/project-registry.js +148 -0
- package/src/lib/api/protocol-bridge.js +476 -0
- package/src/lib/approval-policy.js +12 -0
- package/src/lib/ci-reporter.js +188 -0
- package/src/lib/claude-local-auth.js +75 -1
- package/src/lib/connector-probe.js +21 -0
- package/src/lib/continuous-run.js +9 -0
- package/src/lib/dashboard/bridge-server.js +10 -5
- package/src/lib/governed-state.js +1 -1
- package/src/lib/intake.js +32 -6
- package/src/lib/normalized-config.js +2 -0
- package/src/lib/scope-overlap.js +214 -0
- package/src/lib/turn-checkpoint.js +21 -0
- package/src/lib/turn-result-validator.js +5 -0
- package/src/lib/validation.js +11 -3
- package/src/lib/verification-replay.js +125 -4
- package/src/lib/vision-reader.js +7 -2
|
@@ -8,7 +8,7 @@ import { isOperationalPath } from './repo-observer.js';
|
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS = 30_000;
|
|
10
10
|
|
|
11
|
-
export function replayVerificationMachineEvidence({ root, verification, timeoutMs = DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS }) {
|
|
11
|
+
export function replayVerificationMachineEvidence({ root, verification, timeoutMs = DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS, allowCommandExecution = false }) {
|
|
12
12
|
const verifiedAt = new Date().toISOString();
|
|
13
13
|
const machineEvidence = Array.isArray(verification?.machine_evidence)
|
|
14
14
|
? verification.machine_evidence
|
|
@@ -28,6 +28,27 @@ export function replayVerificationMachineEvidence({ root, verification, timeoutM
|
|
|
28
28
|
return payload;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// SECURITY (verification-replay hardening): agent-declared machine_evidence
|
|
32
|
+
// commands are UNTRUSTED. Fail-safe default — they are recorded but NOT executed
|
|
33
|
+
// unless the operator has explicitly opted in (allowCommandExecution). When
|
|
34
|
+
// executed, they never run through a shell (see replayEvidenceCommand).
|
|
35
|
+
if (!allowCommandExecution) {
|
|
36
|
+
payload.overall = 'not_executed';
|
|
37
|
+
payload.reason = 'Command replay disabled: agent-declared verification commands are not executed unless explicitly enabled by the operator.';
|
|
38
|
+
payload.commands = machineEvidence.map((entry, index) => ({
|
|
39
|
+
index,
|
|
40
|
+
command: entry?.command ?? null,
|
|
41
|
+
declared_exit_code: entry?.exit_code,
|
|
42
|
+
actual_exit_code: null,
|
|
43
|
+
matched: false,
|
|
44
|
+
executed: false,
|
|
45
|
+
timed_out: false,
|
|
46
|
+
signal: null,
|
|
47
|
+
error: null,
|
|
48
|
+
}));
|
|
49
|
+
return payload;
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
const workspaceGuard = createReplayWorkspaceGuard(root);
|
|
32
53
|
try {
|
|
33
54
|
payload.commands = machineEvidence.map((entry, index) => replayEvidenceCommand(root, entry, index, timeoutMs));
|
|
@@ -41,11 +62,33 @@ export function replayVerificationMachineEvidence({ root, verification, timeoutM
|
|
|
41
62
|
return payload;
|
|
42
63
|
}
|
|
43
64
|
|
|
65
|
+
// SECURITY: agent-declared commands are NEVER run through a shell. The command is
|
|
66
|
+
// resolved to an argv vector and executed with shell:false, so shell metacharacters
|
|
67
|
+
// (; | && $() backticks globs) cannot chain, pipe, expand, or substitute. (A single
|
|
68
|
+
// declared binary still runs on the host — that is gated by the explicit
|
|
69
|
+
// allowCommandExecution opt-in upstream; a future sandbox/allowlist can constrain
|
|
70
|
+
// which binaries are permitted.)
|
|
44
71
|
export function replayEvidenceCommand(root, entry, index, timeoutMs = DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS) {
|
|
45
|
-
const
|
|
72
|
+
const argv = resolveEvidenceArgv(entry);
|
|
73
|
+
if (argv.length === 0) {
|
|
74
|
+
return {
|
|
75
|
+
index,
|
|
76
|
+
command: entry?.command ?? null,
|
|
77
|
+
declared_exit_code: entry?.exit_code,
|
|
78
|
+
actual_exit_code: null,
|
|
79
|
+
matched: false,
|
|
80
|
+
executed: false,
|
|
81
|
+
timed_out: false,
|
|
82
|
+
signal: null,
|
|
83
|
+
error: 'No executable command or argv vector could be resolved for this evidence entry.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const [file, ...args] = argv;
|
|
88
|
+
const result = spawnSync(file, args, {
|
|
46
89
|
cwd: root,
|
|
47
90
|
encoding: 'utf8',
|
|
48
|
-
shell:
|
|
91
|
+
shell: false,
|
|
49
92
|
timeout: timeoutMs,
|
|
50
93
|
maxBuffer: 1024 * 1024,
|
|
51
94
|
});
|
|
@@ -56,16 +99,94 @@ export function replayEvidenceCommand(root, entry, index, timeoutMs = DEFAULT_VE
|
|
|
56
99
|
|
|
57
100
|
return {
|
|
58
101
|
index,
|
|
59
|
-
command: entry.command,
|
|
102
|
+
command: entry.command ?? argv.join(' '),
|
|
60
103
|
declared_exit_code: entry.exit_code,
|
|
61
104
|
actual_exit_code: actualExitCode,
|
|
62
105
|
matched: actualExitCode === entry.exit_code,
|
|
106
|
+
executed: true,
|
|
63
107
|
timed_out: timedOut,
|
|
64
108
|
signal: result.signal || null,
|
|
65
109
|
error: errorMessage,
|
|
66
110
|
};
|
|
67
111
|
}
|
|
68
112
|
|
|
113
|
+
// Resolve an evidence entry to an argv vector for shell-free execution.
|
|
114
|
+
// Prefers a structured `argv` string array; falls back to safe-tokenizing `command`.
|
|
115
|
+
export function resolveEvidenceArgv(entry) {
|
|
116
|
+
if (Array.isArray(entry?.argv) && entry.argv.length > 0 && entry.argv.every((a) => typeof a === 'string')) {
|
|
117
|
+
return entry.argv.slice();
|
|
118
|
+
}
|
|
119
|
+
if (typeof entry?.command === 'string' && entry.command.trim()) {
|
|
120
|
+
return tokenizeCommand(entry.command.trim());
|
|
121
|
+
}
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Minimal SAFE tokenizer: splits on whitespace, honoring single and double quotes
|
|
126
|
+
// and backslash escapes (so e.g. `node -e "{\"ok\":true}"` keeps its escaped quotes).
|
|
127
|
+
// Performs NO shell expansion — variables, globs, pipes, chaining, and command
|
|
128
|
+
// substitution are preserved as literal characters, never interpreted.
|
|
129
|
+
export function tokenizeCommand(command) {
|
|
130
|
+
if (typeof command !== 'string') {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const tokens = [];
|
|
134
|
+
let current = '';
|
|
135
|
+
let hasToken = false;
|
|
136
|
+
let i = 0;
|
|
137
|
+
const n = command.length;
|
|
138
|
+
while (i < n) {
|
|
139
|
+
const ch = command[i];
|
|
140
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
141
|
+
if (hasToken) {
|
|
142
|
+
tokens.push(current);
|
|
143
|
+
current = '';
|
|
144
|
+
hasToken = false;
|
|
145
|
+
}
|
|
146
|
+
i += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
hasToken = true;
|
|
150
|
+
if (ch === "'") {
|
|
151
|
+
// Single-quoted: literal run until the next single quote (no escapes, POSIX-style).
|
|
152
|
+
i += 1;
|
|
153
|
+
while (i < n && command[i] !== "'") {
|
|
154
|
+
current += command[i];
|
|
155
|
+
i += 1;
|
|
156
|
+
}
|
|
157
|
+
i += 1; // consume closing quote (or run off the end)
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (ch === '"') {
|
|
161
|
+
// Double-quoted: backslash escapes only the structural `"` and `\` characters.
|
|
162
|
+
i += 1;
|
|
163
|
+
while (i < n && command[i] !== '"') {
|
|
164
|
+
if (command[i] === '\\' && i + 1 < n && (command[i + 1] === '"' || command[i + 1] === '\\')) {
|
|
165
|
+
current += command[i + 1];
|
|
166
|
+
i += 2;
|
|
167
|
+
} else {
|
|
168
|
+
current += command[i];
|
|
169
|
+
i += 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
i += 1; // consume closing quote (or run off the end)
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (ch === '\\' && i + 1 < n) {
|
|
176
|
+
// Bare backslash escape: take the next character literally.
|
|
177
|
+
current += command[i + 1];
|
|
178
|
+
i += 2;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
current += ch;
|
|
182
|
+
i += 1;
|
|
183
|
+
}
|
|
184
|
+
if (hasToken) {
|
|
185
|
+
tokens.push(current);
|
|
186
|
+
}
|
|
187
|
+
return tokens;
|
|
188
|
+
}
|
|
189
|
+
|
|
69
190
|
export function summarizeVerificationReplay(payload) {
|
|
70
191
|
if (!payload) {
|
|
71
192
|
return null;
|
package/src/lib/vision-reader.js
CHANGED
|
@@ -16,6 +16,11 @@ import { join, resolve as pathResolve, isAbsolute } from 'node:path';
|
|
|
16
16
|
import { createHash } from 'node:crypto';
|
|
17
17
|
|
|
18
18
|
const ROADMAP_TRACKING_ANNOTATION_PATTERN = /<!--\s*tracking\s*:[\s\S]*?-->/i;
|
|
19
|
+
// RB-3: a milestone left unchecked but annotated as delivered/done/shipped is
|
|
20
|
+
// already-built work — skip it so the loop derives genuinely undelivered work
|
|
21
|
+
// instead of re-picking finished items. Distinct from generic comments like
|
|
22
|
+
// `<!-- owner: dev -->`, which stay actionable.
|
|
23
|
+
const ROADMAP_DELIVERED_ANNOTATION_PATTERN = /<!--[^>]*\b(delivered|shipped|implemented|completed|done)\b[^>]*-->/i;
|
|
19
24
|
|
|
20
25
|
export function stripRoadmapTrackingAnnotations(text) {
|
|
21
26
|
if (typeof text !== 'string') return '';
|
|
@@ -266,7 +271,7 @@ export function deriveRoadmapCandidates(root, roadmapPath = '.planning/ROADMAP.m
|
|
|
266
271
|
|
|
267
272
|
const uncheckedMatch = line.match(/^\s*[-*]\s+\[\s\]\s+(.+?)\s*$/);
|
|
268
273
|
if (!uncheckedMatch || !currentMilestone) continue;
|
|
269
|
-
if (ROADMAP_TRACKING_ANNOTATION_PATTERN.test(line)) continue;
|
|
274
|
+
if (ROADMAP_TRACKING_ANNOTATION_PATTERN.test(line) || ROADMAP_DELIVERED_ANNOTATION_PATTERN.test(line)) continue;
|
|
270
275
|
|
|
271
276
|
const goal = stripRoadmapTrackingAnnotations(uncheckedMatch[1]);
|
|
272
277
|
if (!goal) continue;
|
|
@@ -493,7 +498,7 @@ export function detectRoadmapExhaustedVisionOpen(root, visionPath, roadmapPath =
|
|
|
493
498
|
continue;
|
|
494
499
|
}
|
|
495
500
|
if (currentMilestone && /^\s*[-*]\s+\[\s\]/.test(line)) {
|
|
496
|
-
if (ROADMAP_TRACKING_ANNOTATION_PATTERN.test(line)) continue;
|
|
501
|
+
if (ROADMAP_TRACKING_ANNOTATION_PATTERN.test(line) || ROADMAP_DELIVERED_ANNOTATION_PATTERN.test(line)) continue;
|
|
497
502
|
hasUnchecked = true;
|
|
498
503
|
}
|
|
499
504
|
}
|