fix: harden executor session handling
This commit is contained in:
+92
-17
@@ -516,6 +516,59 @@ function extractAgyConversationId(output: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractAgyConversationIdFromLog(logFile: string): string | undefined {
|
||||
try {
|
||||
const log = fs.readFileSync(logFile, "utf8");
|
||||
const matches = [
|
||||
...log.matchAll(/Print mode: conversation=([0-9a-f-]{36})\b/gi),
|
||||
...log.matchAll(/Created conversation ([0-9a-f-]{36})\b/gi),
|
||||
];
|
||||
return matches.at(-1)?.[1];
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function agyConversationCacheFile(): string {
|
||||
const homeDir = process.env.HOME || os.homedir();
|
||||
return path.join(homeDir, ".gemini", "antigravity-cli", "cache", "last_conversations.json");
|
||||
}
|
||||
|
||||
function getAgyWorkspaceConversationId(repoRoot: string): string | undefined {
|
||||
try {
|
||||
const parsed = JSON.parse(fs.readFileSync(agyConversationCacheFile(), "utf8")) as Record<string, unknown>;
|
||||
const id = parsed[repoRoot];
|
||||
return typeof id === "string" && /^[0-9a-f-]{36}$/i.test(id) ? id : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function extractAgyFailureReason(stdout: string, stderr: string): string | undefined {
|
||||
const output = `${stdout}\n${stderr}`.trim();
|
||||
if (!output) return "Agy exited without producing output.";
|
||||
|
||||
const diagnostic = output
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.find((line) =>
|
||||
/no output produced/i.test(line)
|
||||
|| /headless mode cannot prompt/i.test(line)
|
||||
|| /permission .*auto-denied/i.test(line)
|
||||
|| /tool required .* permission/i.test(line));
|
||||
return diagnostic;
|
||||
}
|
||||
|
||||
function agyCommonArgs(config: ExecutorConfig, timeoutSeconds?: number): string[] {
|
||||
return [
|
||||
"--dangerously-skip-permissions",
|
||||
"--mode", "accept-edits",
|
||||
...(timeoutSeconds ? ["--print-timeout", `${timeoutSeconds}s`] : []),
|
||||
...(config.agent ? ["--agent", config.agent] : []),
|
||||
...(config.model ? ["--model", config.model] : []),
|
||||
];
|
||||
}
|
||||
|
||||
class AgyAdapter implements ExecutorAdapter {
|
||||
readonly kind: ExecutorKind = "agy";
|
||||
|
||||
@@ -534,11 +587,13 @@ class AgyAdapter implements ExecutorAdapter {
|
||||
async start(opts: ExecutorStartOpts): Promise<ExecutorResult> {
|
||||
const bin = resolveExecutable(opts.config, "agy");
|
||||
const prompt = buildStartPrompt(opts);
|
||||
const logFile = path.join(opts.runDir, `cycle-${opts.cycleIndex}-agy.log`);
|
||||
const previousWorkspaceConversationId = getAgyWorkspaceConversationId(opts.repoRoot);
|
||||
|
||||
const args: string[] = [
|
||||
...(opts.config.agent ? ["--agent", opts.config.agent] : []),
|
||||
...(opts.config.model ? ["--model", opts.config.model] : []),
|
||||
"--print", "--mode", "accept-edits", "--", prompt,
|
||||
...agyCommonArgs(opts.config, opts.timeoutSeconds),
|
||||
"--log-file", logFile,
|
||||
"--print", prompt,
|
||||
];
|
||||
|
||||
const startMs = Date.now();
|
||||
@@ -550,7 +605,12 @@ class AgyAdapter implements ExecutorAdapter {
|
||||
});
|
||||
const durationMs = Date.now() - startMs;
|
||||
|
||||
const conversationId = extractAgyConversationId(child.stdout);
|
||||
const conversationId = extractAgyConversationId(child.stdout)
|
||||
?? extractAgyConversationIdFromLog(logFile)
|
||||
?? (() => {
|
||||
const currentId = getAgyWorkspaceConversationId(opts.repoRoot);
|
||||
return currentId !== previousWorkspaceConversationId ? currentId : undefined;
|
||||
})();
|
||||
const handle: AgySessionHandle = {
|
||||
kind: "agy",
|
||||
...(conversationId ? { conversationId } : {}),
|
||||
@@ -558,13 +618,15 @@ class AgyAdapter implements ExecutorAdapter {
|
||||
};
|
||||
|
||||
const report = child.stdout + (child.stderr ? `\n[stderr]\n${child.stderr}` : "");
|
||||
const failureReason = child.error?.message
|
||||
?? extractAgyFailureReason(child.stdout, child.stderr);
|
||||
return {
|
||||
exitCode: child.status,
|
||||
report,
|
||||
durationMs,
|
||||
sessionHandle: handle,
|
||||
failed: child.status !== 0 || !!child.error,
|
||||
...(child.error ? { failureReason: child.error.message } : {}),
|
||||
failed: child.status !== 0 || !!failureReason,
|
||||
...(failureReason ? { failureReason } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -572,22 +634,25 @@ class AgyAdapter implements ExecutorAdapter {
|
||||
const handle = opts.handle as AgySessionHandle;
|
||||
const bin = resolveExecutable(opts.config, "agy");
|
||||
const prompt = buildFixPrompt(opts);
|
||||
const logFile = path.join(opts.runDir, `cycle-${opts.cycleIndex}-agy.log`);
|
||||
const conversationId = handle.conversationId
|
||||
?? getAgyWorkspaceConversationId(opts.repoRoot);
|
||||
|
||||
let args: string[];
|
||||
if (handle.conversationId) {
|
||||
if (conversationId) {
|
||||
args = [
|
||||
"--conversation", handle.conversationId,
|
||||
...(opts.config.agent ? ["--agent", opts.config.agent] : []),
|
||||
...(opts.config.model ? ["--model", opts.config.model] : []),
|
||||
"--print", "--mode", "accept-edits", "--", prompt,
|
||||
...agyCommonArgs(opts.config, opts.timeoutSeconds),
|
||||
"--conversation", conversationId,
|
||||
"--log-file", logFile,
|
||||
"--print", prompt,
|
||||
];
|
||||
} else {
|
||||
// Degraded mode: use --continue (best effort, may pick wrong session in concurrent setups)
|
||||
args = [
|
||||
...agyCommonArgs(opts.config, opts.timeoutSeconds),
|
||||
"--continue",
|
||||
...(opts.config.agent ? ["--agent", opts.config.agent] : []),
|
||||
...(opts.config.model ? ["--model", opts.config.model] : []),
|
||||
"--print", "--mode", "accept-edits", "--", prompt,
|
||||
"--log-file", logFile,
|
||||
"--print", prompt,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -601,13 +666,23 @@ class AgyAdapter implements ExecutorAdapter {
|
||||
const durationMs = Date.now() - startMs;
|
||||
|
||||
const report = child.stdout + (child.stderr ? `\n[stderr]\n${child.stderr}` : "");
|
||||
const capturedConversationId = conversationId
|
||||
?? extractAgyConversationId(child.stdout)
|
||||
?? extractAgyConversationIdFromLog(logFile);
|
||||
const updatedHandle: AgySessionHandle = {
|
||||
kind: "agy",
|
||||
...(capturedConversationId ? { conversationId: capturedConversationId } : {}),
|
||||
degraded: !capturedConversationId,
|
||||
};
|
||||
const failureReason = child.error?.message
|
||||
?? extractAgyFailureReason(child.stdout, child.stderr);
|
||||
return {
|
||||
exitCode: child.status,
|
||||
report,
|
||||
durationMs,
|
||||
sessionHandle: handle,
|
||||
failed: child.status !== 0 || !!child.error,
|
||||
...(child.error ? { failureReason: child.error.message } : {}),
|
||||
sessionHandle: updatedHandle,
|
||||
failed: child.status !== 0 || !!failureReason,
|
||||
...(failureReason ? { failureReason } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user