agentxchain 2.147.0 → 2.149.1
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/dashboard/components/timeline.js +15 -2
- package/package.json +1 -1
- package/scripts/reproduce-bug-54.mjs +623 -0
- package/src/commands/connector.js +23 -4
- package/src/commands/doctor.js +11 -0
- package/src/commands/run.js +18 -3
- package/src/commands/status.js +30 -3
- package/src/commands/step.js +8 -2
- package/src/lib/adapters/local-cli-adapter.js +191 -7
- package/src/lib/claude-local-auth.js +61 -0
- package/src/lib/connector-probe.js +48 -21
- package/src/lib/connector-validate.js +34 -0
- package/src/lib/dispatch-progress.js +32 -6
- package/src/lib/dispatch-streams.js +21 -0
- package/src/lib/governed-state.js +118 -10
- package/src/lib/normalized-config.js +12 -0
- package/src/lib/schemas/agentxchain-config.schema.json +5 -0
- package/src/lib/schemas/turn-result.schema.json +8 -2
- package/src/lib/stale-turn-watchdog.js +31 -6
- package/src/lib/turn-checkpoint.js +112 -1
- package/src/lib/turn-result-validator.js +11 -2
|
@@ -57,6 +57,14 @@ function normalizeFilesChanged(filesChanged) {
|
|
|
57
57
|
return normalizeCheckpointableFiles(filesChanged);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function normalizeGitBaselineRef(ref) {
|
|
61
|
+
if (typeof ref !== 'string' || !ref.startsWith('git:')) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const gitRef = ref.slice(4).trim();
|
|
65
|
+
return gitRef || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
function supportsLegacyFilesChangedRecovery(entry) {
|
|
61
69
|
const artifactType = entry?.artifact?.type;
|
|
62
70
|
return artifactType === 'workspace' || artifactType === 'patch';
|
|
@@ -139,6 +147,85 @@ function buildCheckpointCommit(entry) {
|
|
|
139
147
|
return { subject, body: bodyLines.join('\n') };
|
|
140
148
|
}
|
|
141
149
|
|
|
150
|
+
function diffMissingDeclaredPaths(declaredFiles, stagedFiles) {
|
|
151
|
+
const stagedSet = new Set((Array.isArray(stagedFiles) ? stagedFiles : []).map((value) => value.trim()).filter(Boolean));
|
|
152
|
+
return (Array.isArray(declaredFiles) ? declaredFiles : []).filter((filePath) => !stagedSet.has(filePath));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Partition paths that were missing from the staged-diff into
|
|
157
|
+
* (a) paths genuinely absent from git (untracked or dirty without staging)
|
|
158
|
+
* (b) paths already committed upstream (tracked in HEAD, no pending diff)
|
|
159
|
+
* (c) paths tracked and clean at HEAD but unchanged since the accepted
|
|
160
|
+
* baseline — the BUG-55A wrong-lineage case (actor committed the file
|
|
161
|
+
* off the accepted lineage, e.g. on a throwaway side-branch or the
|
|
162
|
+
* lineage was reset to baseline).
|
|
163
|
+
*
|
|
164
|
+
* BUG-55A completeness must fail on (a) and (c). (c) has a different
|
|
165
|
+
* operator-visible recovery path than (a): the file DID exist in some
|
|
166
|
+
* commit but is not reachable from the accepted baseline + HEAD comparison
|
|
167
|
+
* the governed run relies on. Surface it separately so the CLI can tell
|
|
168
|
+
* the operator which failure they hit.
|
|
169
|
+
*/
|
|
170
|
+
function partitionDeclaredPathsByUpstreamPresence(root, missingPaths, options = {}) {
|
|
171
|
+
const baselineRef = normalizeGitBaselineRef(options.baselineRef);
|
|
172
|
+
const genuinelyMissing = [];
|
|
173
|
+
const divergentFromAcceptedLineage = [];
|
|
174
|
+
const alreadyCommittedUpstream = [];
|
|
175
|
+
for (const filePath of missingPaths) {
|
|
176
|
+
let tracked = false;
|
|
177
|
+
try {
|
|
178
|
+
git(root, ['ls-files', '--error-unmatch', '--', filePath]);
|
|
179
|
+
tracked = true;
|
|
180
|
+
} catch {
|
|
181
|
+
tracked = false;
|
|
182
|
+
}
|
|
183
|
+
if (!tracked) {
|
|
184
|
+
genuinelyMissing.push(filePath);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
let hasDivergence = false;
|
|
188
|
+
try {
|
|
189
|
+
const headDiff = git(root, ['diff', 'HEAD', '--', filePath]);
|
|
190
|
+
const cachedDiff = git(root, ['diff', '--cached', '--', filePath]);
|
|
191
|
+
hasDivergence = Boolean(headDiff) || Boolean(cachedDiff);
|
|
192
|
+
} catch {
|
|
193
|
+
hasDivergence = true;
|
|
194
|
+
}
|
|
195
|
+
if (hasDivergence) {
|
|
196
|
+
genuinelyMissing.push(filePath);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// BUG-55A wrong-branch guard: a path only counts as already checkpointed
|
|
201
|
+
// if the current branch differs from the accepted baseline on that path.
|
|
202
|
+
if (baselineRef) {
|
|
203
|
+
let changedSinceAcceptedBaseline = false;
|
|
204
|
+
try {
|
|
205
|
+
const baselineDiff = git(root, ['diff', baselineRef, 'HEAD', '--', filePath]);
|
|
206
|
+
changedSinceAcceptedBaseline = Boolean(baselineDiff);
|
|
207
|
+
} catch {
|
|
208
|
+
changedSinceAcceptedBaseline = true;
|
|
209
|
+
}
|
|
210
|
+
if (!changedSinceAcceptedBaseline) {
|
|
211
|
+
divergentFromAcceptedLineage.push(filePath);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
alreadyCommittedUpstream.push(filePath);
|
|
217
|
+
}
|
|
218
|
+
// Preserve the pre-existing return shape: `genuinelyMissing` is the union
|
|
219
|
+
// that the completeness gate must fail on. `divergent_from_accepted_lineage`
|
|
220
|
+
// is an additional, operator-facing subcategory that callers can surface
|
|
221
|
+
// without changing the pass/fail contract.
|
|
222
|
+
return {
|
|
223
|
+
genuinelyMissing: [...genuinelyMissing, ...divergentFromAcceptedLineage],
|
|
224
|
+
divergentFromAcceptedLineage,
|
|
225
|
+
alreadyCommittedUpstream,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
142
229
|
export function detectPendingCheckpoint(root, dirtyFiles = []) {
|
|
143
230
|
const actorDirtyFiles = normalizeFilesChanged(dirtyFiles);
|
|
144
231
|
if (actorDirtyFiles.length === 0) return { required: false };
|
|
@@ -233,12 +320,36 @@ export function checkpointAcceptedTurn(root, opts = {}) {
|
|
|
233
320
|
};
|
|
234
321
|
}
|
|
235
322
|
|
|
323
|
+
const rawMissingFromStage = diffMissingDeclaredPaths(filesChanged, staged);
|
|
324
|
+
const { genuinelyMissing, divergentFromAcceptedLineage, alreadyCommittedUpstream } =
|
|
325
|
+
partitionDeclaredPathsByUpstreamPresence(root, rawMissingFromStage, {
|
|
326
|
+
baselineRef: entry?.observed_artifact?.baseline_ref ?? null,
|
|
327
|
+
});
|
|
328
|
+
if (genuinelyMissing.length > 0) {
|
|
329
|
+
const baseMessage = `Checkpoint completeness failure: accepted turn ${entry.turn_id} declared ${filesChanged.length} checkpointable file(s), but Git staged only ${staged.length} and ${genuinelyMissing.length} declared path(s) are absent from git. Missing from checkpoint: ${genuinelyMissing.join(', ')}.`;
|
|
330
|
+
const lineageHint = divergentFromAcceptedLineage.length > 0
|
|
331
|
+
? ` Wrong-lineage paths (tracked at HEAD but unchanged since accepted baseline — actor likely committed off the accepted lineage): ${divergentFromAcceptedLineage.join(', ')}.`
|
|
332
|
+
: '';
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
turn: entry,
|
|
336
|
+
error: `${baseMessage}${lineageHint}`,
|
|
337
|
+
missing_declared_paths: genuinelyMissing,
|
|
338
|
+
divergent_from_accepted_lineage: divergentFromAcceptedLineage,
|
|
339
|
+
already_committed_upstream: alreadyCommittedUpstream,
|
|
340
|
+
staged_paths: staged,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
236
344
|
if (staged.length === 0) {
|
|
237
345
|
return {
|
|
238
346
|
ok: true,
|
|
239
347
|
skipped: true,
|
|
240
348
|
turn: entry,
|
|
241
|
-
reason:
|
|
349
|
+
reason: alreadyCommittedUpstream.length > 0
|
|
350
|
+
? `Accepted turn ${entry.turn_id} has no staged repo changes to checkpoint; all ${alreadyCommittedUpstream.length} declared file(s) already present in HEAD.`
|
|
351
|
+
: `Accepted turn ${entry.turn_id} has no staged repo changes to checkpoint.`,
|
|
352
|
+
already_committed_upstream: alreadyCommittedUpstream,
|
|
242
353
|
};
|
|
243
354
|
}
|
|
244
355
|
|
|
@@ -600,6 +600,15 @@ function validateVerification(tr) {
|
|
|
600
600
|
}
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
if (Array.isArray(v.commands)) {
|
|
604
|
+
for (let i = 0; i < v.commands.length; i++) {
|
|
605
|
+
const command = v.commands[i];
|
|
606
|
+
if (typeof command !== 'string' || command.trim().length === 0) {
|
|
607
|
+
errors.push(`verification.commands[${i}] must be a non-empty string.`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
603
612
|
// machine_evidence exit codes should be consistent with status
|
|
604
613
|
if (Array.isArray(v.machine_evidence)) {
|
|
605
614
|
for (let i = 0; i < v.machine_evidence.length; i++) {
|
|
@@ -608,8 +617,8 @@ function validateVerification(tr) {
|
|
|
608
617
|
errors.push(`verification.machine_evidence[${i}] must be an object.`);
|
|
609
618
|
continue;
|
|
610
619
|
}
|
|
611
|
-
if (typeof entry.command !== 'string') {
|
|
612
|
-
errors.push(`verification.machine_evidence[${i}].command must be a string.`);
|
|
620
|
+
if (typeof entry.command !== 'string' || entry.command.trim().length === 0) {
|
|
621
|
+
errors.push(`verification.machine_evidence[${i}].command must be a non-empty string.`);
|
|
613
622
|
}
|
|
614
623
|
if (typeof entry.exit_code !== 'number' || !Number.isInteger(entry.exit_code)) {
|
|
615
624
|
errors.push(`verification.machine_evidence[${i}].exit_code must be an integer.`);
|