Harden read-only thread handling and refresh Android releases
This commit is contained in:
@@ -39,6 +39,21 @@ function trimToDefined(value) {
|
||||
return trimmed ? trimmed : null;
|
||||
}
|
||||
|
||||
function parseSandboxPolicyType(value) {
|
||||
const raw = trimToDefined(value);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return trimToDefined(parsed?.type) ?? raw;
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function isReadOnlySandboxPolicy(value) {
|
||||
return parseSandboxPolicyType(value) === "read-only";
|
||||
}
|
||||
|
||||
function isPrimaryWorkspaceThread(thread) {
|
||||
return !trimToDefined(thread.agentRole) && !trimToDefined(thread.agentNickname);
|
||||
}
|
||||
@@ -104,7 +119,7 @@ function loadThreadsFromStateDb(stateDbPath) {
|
||||
try {
|
||||
return db
|
||||
.prepare(
|
||||
"SELECT id, cwd, updated_at, archived, title, agent_nickname, agent_role FROM threads WHERE archived = 0 ORDER BY updated_at DESC",
|
||||
"SELECT id, cwd, updated_at, archived, title, sandbox_policy, agent_nickname, agent_role FROM threads WHERE archived = 0 ORDER BY updated_at DESC",
|
||||
)
|
||||
.all()
|
||||
.map((row) => ({
|
||||
@@ -113,6 +128,7 @@ function loadThreadsFromStateDb(stateDbPath) {
|
||||
updatedAtSeconds: Number(row.updated_at),
|
||||
archived: Boolean(row.archived),
|
||||
title: String(row.title ?? ""),
|
||||
sandboxPolicy: typeof row.sandbox_policy === "string" ? row.sandbox_policy : "",
|
||||
agentNickname: typeof row.agent_nickname === "string" ? row.agent_nickname : "",
|
||||
agentRole: typeof row.agent_role === "string" ? row.agent_role : "",
|
||||
}));
|
||||
@@ -206,6 +222,9 @@ export async function discoverCodexProjectCandidates(options = {}) {
|
||||
const groupedCandidates = new Map();
|
||||
for (const thread of threads) {
|
||||
if (!thread?.id || seenThreadIds.has(thread.id)) continue;
|
||||
if (isReadOnlySandboxPolicy(thread.sandboxPolicy)) {
|
||||
continue;
|
||||
}
|
||||
const latestActivitySeconds = latestLogByThread.get(thread.id) ?? thread.updatedAtSeconds;
|
||||
if (!Number.isFinite(latestActivitySeconds) || latestActivitySeconds < cutoffSeconds) {
|
||||
continue;
|
||||
|
||||
@@ -10,6 +10,24 @@ function trimToDefined(value) {
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function parseSandboxPolicyType(value) {
|
||||
const raw = trimToDefined(value);
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
return trimToDefined(parsed?.type) || raw;
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function isReadOnlySandboxPolicy(value) {
|
||||
return parseSandboxPolicyType(value) === "read-only";
|
||||
}
|
||||
|
||||
function resolveResumeTarget(config, task) {
|
||||
const targetThreadRef = trimToDefined(task?.targetCodexThreadRef || task?.targetThreadId);
|
||||
const targetFolderRef = trimToDefined(
|
||||
@@ -74,7 +92,7 @@ function inspectCodexThreadBinding(config, targetThreadRef, targetFolderRef) {
|
||||
const db = new DatabaseSync(stateDbPath, { readonly: true });
|
||||
try {
|
||||
const row = db
|
||||
.prepare("SELECT id, cwd, archived FROM threads WHERE id = ? LIMIT 1")
|
||||
.prepare("SELECT id, cwd, archived, sandbox_policy FROM threads WHERE id = ? LIMIT 1")
|
||||
.get(targetThreadRef);
|
||||
if (!row || row.archived) {
|
||||
return {
|
||||
@@ -82,6 +100,14 @@ function inspectCodexThreadBinding(config, targetThreadRef, targetFolderRef) {
|
||||
};
|
||||
}
|
||||
|
||||
const sandboxPolicyType = parseSandboxPolicyType(row.sandbox_policy);
|
||||
if (isReadOnlySandboxPolicy(row.sandbox_policy)) {
|
||||
return {
|
||||
status: "read_only",
|
||||
sandboxPolicyType,
|
||||
};
|
||||
}
|
||||
|
||||
const workspaceHints = loadThreadWorkspaceHints(
|
||||
trimToDefined(config?.codexGlobalStatePath || defaultCodexPath(".codex-global-state.json")),
|
||||
);
|
||||
@@ -148,6 +174,21 @@ export async function prepareCodexTaskExecution(config, task, outputFile) {
|
||||
};
|
||||
}
|
||||
|
||||
if (bindingInspection.status === "read_only") {
|
||||
return {
|
||||
ok: false,
|
||||
error: buildStructuredTaskBindingError(
|
||||
"LOCAL_AGENT_CODEX_THREAD_READ_ONLY",
|
||||
`LOCAL_AGENT_CODEX_THREAD_READ_ONLY: 目标线程当前是只读会话,已拒绝 codex exec resume。thread=${targetThreadRef} sandbox=${bindingInspection.sandboxPolicyType ?? "read-only"}`,
|
||||
{
|
||||
targetThreadRef,
|
||||
targetCodexFolderRef: resumeTarget.targetFolderRef,
|
||||
sandboxPolicyType: bindingInspection.sandboxPolicyType,
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const folderStat = await stat(resumeTarget.cwd);
|
||||
if (!folderStat.isDirectory()) {
|
||||
|
||||
Reference in New Issue
Block a user