ccqa 1.8.2 → 1.8.3
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/dist/bin/ccqa.mjs +84 -2
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -6932,6 +6932,68 @@ function verifySessionRestores(statePath, verifyUrl) {
|
|
|
6932
6932
|
]);
|
|
6933
6933
|
}
|
|
6934
6934
|
}
|
|
6935
|
+
/**
|
|
6936
|
+
* Non-destructive mid-run health probe of an already-running live session.
|
|
6937
|
+
* Reads the current page URL (`eval location.href` — the same read
|
|
6938
|
+
* {@link verifySessionRestores} makes, and it does NOT navigate, so it never
|
|
6939
|
+
* clobbers the page the model is working on) and detects only the unambiguous
|
|
6940
|
+
* signals that agent-browser's daemon was replaced/wedged mid-run:
|
|
6941
|
+
*
|
|
6942
|
+
* - the probe exits non-zero — the daemon is wedged or was replaced
|
|
6943
|
+
* (`spawnAB`'s hard timeout bounds a hung daemon, so this returns rather
|
|
6944
|
+
* than hanging the run);
|
|
6945
|
+
* - the page is blank/absent (`about:blank`, empty, `chrome://…`) — a
|
|
6946
|
+
* restarted daemon comes up with no page and no in-memory auth-state.
|
|
6947
|
+
*
|
|
6948
|
+
* Deliberately NOT flagged: a non-blank page that left the verify URL's origin.
|
|
6949
|
+
* That can't be told apart from a spec legitimately roaming to another origin
|
|
6950
|
+
* mid-flow (e.g. Slack → a separate admin app it logs into at runtime), and a
|
|
6951
|
+
* false "unhealthy" would re-inject the saved state and wipe auth the spec
|
|
6952
|
+
* acquired live — breaking a spec that was fine. Missing a same-origin-ish
|
|
6953
|
+
* sign-in wall just leaves that step failing as before (no regression), so the
|
|
6954
|
+
* asymmetry favours only firing on a provably dead daemon. `verifyUrl` is still
|
|
6955
|
+
* required (it's the re-anchor target for recovery) but no longer compared here.
|
|
6956
|
+
*/
|
|
6957
|
+
function checkLiveSessionHealth(sessionName) {
|
|
6958
|
+
const probe = spawnAB([
|
|
6959
|
+
"--session",
|
|
6960
|
+
sessionName,
|
|
6961
|
+
"eval",
|
|
6962
|
+
"location.href"
|
|
6963
|
+
]);
|
|
6964
|
+
if (probe.status !== 0) return {
|
|
6965
|
+
healthy: false,
|
|
6966
|
+
reason: (probe.stderr || probe.stdout || `probe exited ${probe.status}`).trim()
|
|
6967
|
+
};
|
|
6968
|
+
const href = unwrapEvalString(probe.stdout);
|
|
6969
|
+
if (!href || href === "about:blank" || href.startsWith("chrome://") || href.startsWith("chrome-error://")) return {
|
|
6970
|
+
healthy: false,
|
|
6971
|
+
reason: `blank/absent page (${href || "empty"})`
|
|
6972
|
+
};
|
|
6973
|
+
return { healthy: true };
|
|
6974
|
+
}
|
|
6975
|
+
/**
|
|
6976
|
+
* Recover a live session whose daemon was replaced mid-run (detected by
|
|
6977
|
+
* {@link checkLiveSessionHealth}). The restart drops the in-memory auth-state
|
|
6978
|
+
* injected at run start, so the session fell to a sign-in wall. Re-boot +
|
|
6979
|
+
* re-attach the saved state ({@link loadStateIntoSession} is idempotent —
|
|
6980
|
+
* `state load` is load-only, never writes back) and then navigate to
|
|
6981
|
+
* `verifyUrl`, a known signed-in page, so the retrying model has an
|
|
6982
|
+
* authenticated anchor to continue from instead of a login screen. Returns the
|
|
6983
|
+
* injection result; the trailing `open` is best-effort (a failed nav still
|
|
6984
|
+
* leaves the state attached for the model's own next navigation).
|
|
6985
|
+
*/
|
|
6986
|
+
function recoverLiveSession(sessionName, statePath, verifyUrl) {
|
|
6987
|
+
const injected = loadStateIntoSession(sessionName, statePath);
|
|
6988
|
+
if (!injected.ok) return injected;
|
|
6989
|
+
spawnAB([
|
|
6990
|
+
"--session",
|
|
6991
|
+
sessionName,
|
|
6992
|
+
"open",
|
|
6993
|
+
verifyUrl
|
|
6994
|
+
]);
|
|
6995
|
+
return { ok: true };
|
|
6996
|
+
}
|
|
6935
6997
|
/** Take the last non-empty line of `agent-browser eval` stdout and JSON-unquote it. */
|
|
6936
6998
|
function unwrapEvalString(stdout) {
|
|
6937
6999
|
const lines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
@@ -7514,6 +7576,7 @@ async function runLiveExecutor(input) {
|
|
|
7514
7576
|
const stepResults = [];
|
|
7515
7577
|
let overallFailed = false;
|
|
7516
7578
|
const statePath = input.statePath ?? null;
|
|
7579
|
+
const verifyUrl = input.verifyUrl ?? null;
|
|
7517
7580
|
const promptPrefix = buildLiveSystemPromptPrefix({
|
|
7518
7581
|
title: input.spec.title,
|
|
7519
7582
|
allSteps: input.steps,
|
|
@@ -7545,12 +7608,25 @@ async function runLiveExecutor(input) {
|
|
|
7545
7608
|
const systemPrompt = promptPrefix + buildLiveSystemPromptStepSection(step$1) + suffixBlock + langDirective;
|
|
7546
7609
|
const userPrompt = buildLiveUserPrompt(step$1);
|
|
7547
7610
|
let attempt = 0;
|
|
7611
|
+
let recoveredOnce = false;
|
|
7548
7612
|
let lastOutcome = null;
|
|
7549
|
-
|
|
7550
|
-
if (attempt > 0) info(` retry ${attempt}/${retries} for ${step$1.id}`);
|
|
7613
|
+
for (;;) {
|
|
7551
7614
|
lastOutcome = await executeStepAttempt(step$1, paths, systemPrompt, userPrompt);
|
|
7552
7615
|
if (lastOutcome.status === "passed") break;
|
|
7616
|
+
if (!recoveredOnce && statePath && verifyUrl) {
|
|
7617
|
+
const health = checkLiveSessionHealth(input.sessionName);
|
|
7618
|
+
if (!health.healthy) {
|
|
7619
|
+
warn(`session lost mid-step for ${step$1.id} (${health.reason}); re-injecting auth-state and retrying`);
|
|
7620
|
+
const rec = recoverLiveSession(input.sessionName, statePath, verifyUrl);
|
|
7621
|
+
if (!rec.ok) warn(`session recovery failed: ${rec.error}`);
|
|
7622
|
+
recoveredOnce = true;
|
|
7623
|
+
attempt++;
|
|
7624
|
+
continue;
|
|
7625
|
+
}
|
|
7626
|
+
}
|
|
7627
|
+
if (attempt >= retries) break;
|
|
7553
7628
|
attempt++;
|
|
7629
|
+
info(` retry ${attempt}/${retries} for ${step$1.id}`);
|
|
7554
7630
|
}
|
|
7555
7631
|
const outcome = lastOutcome;
|
|
7556
7632
|
stepResults.push({
|
|
@@ -8101,6 +8177,7 @@ async function resolveSessionState(names, hubCtx, profile, verify = verifySessio
|
|
|
8101
8177
|
const profileFlag = profile ? ` --profile ${profile}` : "";
|
|
8102
8178
|
const loaded = [];
|
|
8103
8179
|
const broken = [];
|
|
8180
|
+
let verifyUrl;
|
|
8104
8181
|
for (const name of names) {
|
|
8105
8182
|
let state;
|
|
8106
8183
|
try {
|
|
@@ -8115,6 +8192,7 @@ async function resolveSessionState(names, hubCtx, profile, verify = verifySessio
|
|
|
8115
8192
|
}
|
|
8116
8193
|
const embedded = state[SESSION_VERIFY_URL_KEY];
|
|
8117
8194
|
if (typeof embedded === "string") {
|
|
8195
|
+
verifyUrl ??= embedded;
|
|
8118
8196
|
const memoKey = `${resolvedProfile}/${name}`;
|
|
8119
8197
|
if (!verifiedSessions.has(memoKey)) {
|
|
8120
8198
|
const tmp = await writeMergedTempState(mergeStorageStates([state]));
|
|
@@ -8139,6 +8217,7 @@ async function resolveSessionState(names, hubCtx, profile, verify = verifySessio
|
|
|
8139
8217
|
return {
|
|
8140
8218
|
ok: true,
|
|
8141
8219
|
statePath,
|
|
8220
|
+
...verifyUrl ? { verifyUrl } : {},
|
|
8142
8221
|
cleanup: () => removeTempStateDir(statePath)
|
|
8143
8222
|
};
|
|
8144
8223
|
}
|
|
@@ -8167,6 +8246,7 @@ async function runOneSpec(args) {
|
|
|
8167
8246
|
meta("session", sessionName);
|
|
8168
8247
|
opts.teardown?.trackSession(sessionName);
|
|
8169
8248
|
let statePath = null;
|
|
8249
|
+
let verifyUrl = null;
|
|
8170
8250
|
let cleanupSession = null;
|
|
8171
8251
|
if (spec.session && spec.session.length > 0) {
|
|
8172
8252
|
const resolution = await resolveSessionState(spec.session, opts.hubContext ?? null, opts.profile);
|
|
@@ -8181,6 +8261,7 @@ async function runOneSpec(args) {
|
|
|
8181
8261
|
};
|
|
8182
8262
|
}
|
|
8183
8263
|
statePath = resolution.statePath;
|
|
8264
|
+
verifyUrl = resolution.verifyUrl ?? null;
|
|
8184
8265
|
cleanupSession = resolution.cleanup;
|
|
8185
8266
|
meta("state", spec.session.join(", "));
|
|
8186
8267
|
}
|
|
@@ -8196,6 +8277,7 @@ async function runOneSpec(args) {
|
|
|
8196
8277
|
runDir,
|
|
8197
8278
|
sessionName,
|
|
8198
8279
|
statePath,
|
|
8280
|
+
verifyUrl,
|
|
8199
8281
|
systemPromptSuffix: userPromptSuffix,
|
|
8200
8282
|
model: opts.model,
|
|
8201
8283
|
language: opts.language,
|
package/dist/package.json
CHANGED