Clean up old stuff & fix ui errors
This commit is contained in:
+108
-24
@@ -16,6 +16,11 @@ import { api } from '@spoon/backend/convex/_generated/api.js';
|
||||
|
||||
import type { NormalizedAgentEvent } from './agent-events';
|
||||
import { normalizeCodexJsonLine } from './agent-events';
|
||||
import {
|
||||
codexContainerRepo,
|
||||
codexContainerWorkspace,
|
||||
prepareCodexWorkspaceFiles,
|
||||
} from './codex-runtime';
|
||||
import { env } from './env';
|
||||
import {
|
||||
cloneRepository,
|
||||
@@ -118,7 +123,6 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const client = new ConvexHttpClient(env.convexUrl);
|
||||
const activeWorkspaces = new Map<string, ActiveWorkspace>();
|
||||
const jobContainerWorkspace = '/workspace';
|
||||
|
||||
const appendEvent = async (
|
||||
jobId: Id<'agentJobs'>,
|
||||
@@ -442,6 +446,10 @@ const prepareCodexAuth = async (workspace: ActiveWorkspace) => {
|
||||
if (!secret) {
|
||||
throw new Error('Codex auth profile is missing auth.json contents.');
|
||||
}
|
||||
await prepareCodexWorkspaceFiles({
|
||||
workdir: workspace.workdir,
|
||||
repoDir: workspace.repoDir,
|
||||
});
|
||||
const codexAuthPath = path.join(workspace.workdir, '.codex', 'auth.json');
|
||||
await writeJsonFile(codexAuthPath, secret);
|
||||
|
||||
@@ -688,14 +696,18 @@ const runCodexTurn = async (args: {
|
||||
? commandToShell(
|
||||
`codex exec resume --json --model ${quoteShell(
|
||||
codexModel(workspace.claim),
|
||||
)} ${quoteShell(workspace.codexSessionId)} ${quoteShell(prompt)}`,
|
||||
)} --dangerously-bypass-approvals-and-sandbox ${quoteShell(
|
||||
workspace.codexSessionId,
|
||||
)} ${quoteShell(prompt)}`,
|
||||
)
|
||||
: commandToShell(
|
||||
`codex exec --json --model ${quoteShell(
|
||||
codexModel(workspace.claim),
|
||||
)} --sandbox workspace-write ${quoteShell(prompt)}`,
|
||||
)} --dangerously-bypass-approvals-and-sandbox --cd ${quoteShell(
|
||||
codexContainerRepo,
|
||||
)} ${quoteShell(prompt)}`,
|
||||
);
|
||||
const aiEnv = providerEnvironment(workspace.claim, jobContainerWorkspace);
|
||||
const aiEnv = providerEnvironment(workspace.claim, codexContainerWorkspace);
|
||||
const secretEnv = Object.fromEntries(
|
||||
workspace.claim.secrets.map((secret) => [secret.name, secret.value]),
|
||||
);
|
||||
@@ -958,6 +970,89 @@ const fileChangedType = async (repoDir: string, filePath: string) => {
|
||||
return 'modified' as const;
|
||||
};
|
||||
|
||||
const sensitiveWorkspacePath = (filePath: string) => {
|
||||
const parts = filePath.split('/');
|
||||
if (parts.includes('.git') || parts.includes('.codex')) return true;
|
||||
const name = parts.at(-1) ?? filePath;
|
||||
if (name === '.env') return true;
|
||||
if (name.startsWith('.env.') && name !== '.env.example') return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const changedFilesFromStatus = async (
|
||||
repoDir: string,
|
||||
redact: (value: string) => string,
|
||||
) => {
|
||||
const status = await run('git', ['status', '--short'], {
|
||||
cwd: repoDir,
|
||||
redact,
|
||||
timeoutMs: 60_000,
|
||||
});
|
||||
if (status.exitCode !== 0) return [];
|
||||
return status.output
|
||||
.split('\n')
|
||||
.map((line) => {
|
||||
if (line.length < 4) return null;
|
||||
const code = line.slice(0, 2);
|
||||
const rawPath = line.slice(3).trim();
|
||||
if (!rawPath) return null;
|
||||
const filePath = rawPath.includes(' -> ')
|
||||
? rawPath.split(' -> ').at(-1)?.trim()
|
||||
: rawPath;
|
||||
if (!filePath || sensitiveWorkspacePath(filePath)) return null;
|
||||
const changeType = code.includes('D')
|
||||
? 'deleted'
|
||||
: code.includes('R')
|
||||
? 'renamed'
|
||||
: code.includes('A') || code.includes('?')
|
||||
? 'added'
|
||||
: 'modified';
|
||||
return {
|
||||
path: filePath,
|
||||
changeType,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
value,
|
||||
): value is {
|
||||
path: string;
|
||||
changeType: 'added' | 'modified' | 'deleted' | 'renamed';
|
||||
} => Boolean(value),
|
||||
);
|
||||
};
|
||||
|
||||
const recordChangedFiles = async (
|
||||
workspace: ActiveWorkspace,
|
||||
source: 'agent' | 'command',
|
||||
diff: string,
|
||||
) => {
|
||||
const changes = await changedFilesFromStatus(
|
||||
workspace.repoDir,
|
||||
workspace.redact,
|
||||
);
|
||||
for (const change of changes) {
|
||||
await recordWorkspaceChange({
|
||||
jobId: workspace.claim.job._id,
|
||||
path: change.path,
|
||||
source,
|
||||
changeType: change.changeType,
|
||||
diff: truncate(diff, 50_000),
|
||||
});
|
||||
}
|
||||
if (changes.length > 0) {
|
||||
await appendEvent(
|
||||
workspace.claim.job._id,
|
||||
'info',
|
||||
'edit',
|
||||
`Workspace has ${changes.length} changed file${
|
||||
changes.length === 1 ? '' : 's'
|
||||
}.`,
|
||||
JSON.stringify(changes),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const materializeEnvFile = async (workspace: ActiveWorkspace) => {
|
||||
const { claim, repoDir } = workspace;
|
||||
if (!claim.job.materializeEnvFile || !claim.job.envFilePath) return;
|
||||
@@ -1085,6 +1180,9 @@ const runClaim = async (claim: Claim) => {
|
||||
].filter(Boolean);
|
||||
const redact = createRedactor(secretValues);
|
||||
try {
|
||||
if ((claim.job.runtime ?? 'opencode') !== 'opencode') {
|
||||
throw new Error('Legacy OpenAI direct jobs are no longer supported.');
|
||||
}
|
||||
await updateStatus(jobId, 'preparing');
|
||||
await appendEvent(jobId, 'info', 'clone', 'Creating installation token.');
|
||||
if (!claim.github.installationId) {
|
||||
@@ -1251,16 +1349,11 @@ export const runWorkspaceCommand = async (jobId: string, command: string) => {
|
||||
redact: workspace.redact,
|
||||
});
|
||||
await updateStatus(workspace.claim.job._id, 'running');
|
||||
await recordWorkspaceChange({
|
||||
jobId: workspace.claim.job._id,
|
||||
path: '.',
|
||||
source: 'command',
|
||||
changeType: 'modified',
|
||||
diff: truncate(
|
||||
(await getWorktreeDiff(workspace.repoDir, workspace.redact)).output,
|
||||
50_000,
|
||||
),
|
||||
});
|
||||
await recordChangedFiles(
|
||||
workspace,
|
||||
'command',
|
||||
(await getWorktreeDiff(workspace.repoDir, workspace.redact)).output,
|
||||
);
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
@@ -1347,9 +1440,6 @@ export const sendWorkspaceMessage = async (jobId: string, prompt: string) => {
|
||||
await appendEvent(claim.job._id, 'info', 'plan', 'Sending message to agent.');
|
||||
|
||||
try {
|
||||
if ((claim.job.runtime ?? 'opencode') !== 'opencode') {
|
||||
throw new Error('Legacy OpenAI direct jobs are no longer supported.');
|
||||
}
|
||||
workspace.agentTurnActive = true;
|
||||
const assistantMessageId = await appendMessage({
|
||||
jobId: claim.job._id,
|
||||
@@ -1430,13 +1520,7 @@ export const sendWorkspaceMessage = async (jobId: string, prompt: string) => {
|
||||
content: truncate(diff.output, 200_000),
|
||||
contentType: 'text/x-diff',
|
||||
});
|
||||
await recordWorkspaceChange({
|
||||
jobId: claim.job._id,
|
||||
path: '.',
|
||||
source: 'agent',
|
||||
changeType: 'modified',
|
||||
diff: truncate(diff.output, 50_000),
|
||||
});
|
||||
await recordChangedFiles(workspace, 'agent', diff.output);
|
||||
} catch (error) {
|
||||
workspace.agentTurnActive = false;
|
||||
workspace.resolveTurn?.();
|
||||
|
||||
Reference in New Issue
Block a user