From 65aae853692c51d85b04d6d76370ac99d38e33a4 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Wed, 24 Jun 2026 08:40:52 -0400 Subject: [PATCH] Update formatting on worker --- apps/agent-worker/src/agent-events.ts | 37 +++++--- apps/agent-worker/src/env.ts | 3 +- apps/agent-worker/src/git.ts | 3 +- apps/agent-worker/src/opencode-session.ts | 14 +-- apps/agent-worker/src/runtime/docker.ts | 31 +++---- apps/agent-worker/src/server.ts | 29 ++++--- apps/agent-worker/src/terminal.ts | 3 +- apps/agent-worker/src/worker.ts | 86 +++++++++++-------- .../tests/unit/agent-events.test.ts | 3 +- .../tests/unit/codex-runtime.test.ts | 9 +- .../tests/unit/infisical-account.test.ts | 5 +- 11 files changed, 130 insertions(+), 93 deletions(-) diff --git a/apps/agent-worker/src/agent-events.ts b/apps/agent-worker/src/agent-events.ts index 7322265..d6f07a6 100644 --- a/apps/agent-worker/src/agent-events.ts +++ b/apps/agent-worker/src/agent-events.ts @@ -71,7 +71,8 @@ const textFromPart = (part: Record) => { }; const commandString = (value: unknown) => { - if (Array.isArray(value)) return value.map((part) => stringify(part)).join(' '); + if (Array.isArray(value)) + return value.map((part) => stringify(part)).join(' '); return stringify(value); }; @@ -82,8 +83,7 @@ const toolNameFromRecord = (record: Record | null) => record?.toolName ?? record?.name ?? record?.function ?? - (stringify(record?.type).toLowerCase().includes('exec') || - record?.command + (stringify(record?.type).toLowerCase().includes('exec') || record?.command ? 'Command' : record?.type) ?? 'tool', @@ -132,7 +132,9 @@ const recordLooksLikeTool = ( recordType.includes('exec_command') || recordType.includes('command') || recordType.includes('mcp') || - Boolean(record?.tool ?? record?.tool_name ?? record?.name ?? record?.command) + Boolean( + record?.tool ?? record?.tool_name ?? record?.name ?? record?.command, + ) ); }; @@ -153,7 +155,10 @@ const normalizeCodexMsgEvent = ( ); if (sessionId) events.push({ kind: 'session', sessionId }); } - if (msgType === 'agent_message_delta' || msgType === 'agent_reasoning_delta') { + if ( + msgType === 'agent_message_delta' || + msgType === 'agent_reasoning_delta' + ) { const delta = stringify(msg.delta ?? msg.text); if (delta) events.push({ kind: 'assistant_delta', content: delta }); } @@ -177,7 +182,11 @@ const normalizeCodexMsgEvent = ( output: toolOutputFromRecord(msg), }); } - if (msgType === 'error' || msgType === 'turn_failed' || msgType === 'task_error') { + if ( + msgType === 'error' || + msgType === 'turn_failed' || + msgType === 'task_error' + ) { const message = stringify(msg.message ?? msg.error ?? msg); if (isCodexConfigWarning(message)) { events.push({ kind: 'status', status: message }); @@ -354,7 +363,8 @@ export const normalizeOpenCodeEvent = ( const event = asRecord(input); if (!event) return []; const type = stringify(event.type); - const properties = asRecord(event.properties) ?? asRecord(event.data) ?? event; + const properties = + asRecord(event.properties) ?? asRecord(event.data) ?? event; const events: NormalizedAgentEvent[] = []; const sessionId = properties.sessionID ?? properties.sessionId; if (typeof sessionId === 'string' && type.includes('session')) { @@ -408,7 +418,8 @@ export const normalizeOpenCodeEvent = ( } if (type === 'file.edited') { const file = properties.file; - if (typeof file === 'string') events.push({ kind: 'file_edited', path: file }); + if (typeof file === 'string') + events.push({ kind: 'file_edited', path: file }); } if (type === 'command.executed') { events.push({ @@ -422,7 +433,9 @@ export const normalizeOpenCodeEvent = ( kind: 'permission_requested', externalRequestId: stringify(properties.permissionID ?? properties.id), title: 'Permission requested', - body: stringify(properties.permission ?? properties.message ?? properties), + body: stringify( + properties.permission ?? properties.message ?? properties, + ), metadata: stringify(properties), }); } @@ -443,7 +456,11 @@ export const normalizeOpenCodeEvent = ( }); } if (events.length === 0 && type) { - events.push({ kind: 'status', status: type, metadata: stringify(properties) }); + events.push({ + kind: 'status', + status: type, + metadata: stringify(properties), + }); } return events; }; diff --git a/apps/agent-worker/src/env.ts b/apps/agent-worker/src/env.ts index 38270a0..3c7c079 100644 --- a/apps/agent-worker/src/env.ts +++ b/apps/agent-worker/src/env.ts @@ -25,7 +25,8 @@ export const env = { process.env.SPOON_AGENT_CONTAINER_RUNTIME?.trim() ?? process.env.SPOON_CONTAINER_RUNTIME?.trim() ?? 'docker', - containerVolumeOptions: process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS?.trim(), + containerVolumeOptions: + process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS?.trim(), containerAccess: process.env.SPOON_AGENT_CONTAINER_ACCESS?.trim() === 'host_port' ? 'host_port' diff --git a/apps/agent-worker/src/git.ts b/apps/agent-worker/src/git.ts index 6a7a3f3..7b1392f 100644 --- a/apps/agent-worker/src/git.ts +++ b/apps/agent-worker/src/git.ts @@ -155,8 +155,7 @@ export const getWorktreeDiff = async ( if (diff.output.trim()) untrackedDiffs.push(diff.output); } return { - exitCode: - trackedDiff.exitCode === 0 && untracked.exitCode === 0 ? 0 : 1, + exitCode: trackedDiff.exitCode === 0 && untracked.exitCode === 0 ? 0 : 1, output: [trackedDiff.output, ...untrackedDiffs] .filter((part) => part.trim()) .join('\n'), diff --git a/apps/agent-worker/src/opencode-session.ts b/apps/agent-worker/src/opencode-session.ts index 1beb1e5..7b8ff1c 100644 --- a/apps/agent-worker/src/opencode-session.ts +++ b/apps/agent-worker/src/opencode-session.ts @@ -1,5 +1,5 @@ -import { createOpencodeClient } from '@opencode-ai/sdk'; import type { OpencodeClient } from '@opencode-ai/sdk'; +import { createOpencodeClient } from '@opencode-ai/sdk'; import type { NormalizedAgentEvent } from './agent-events'; import { normalizeOpenCodeEvent } from './agent-events'; @@ -115,11 +115,13 @@ export const replyOpenCodePermission = async (args: { response: 'once' | 'always' | 'reject'; directory: string; }) => { - const result = await args.session.client.postSessionIdPermissionsPermissionId({ - path: { id: args.session.sessionId, permissionID: args.permissionId }, - query: { directory: args.directory }, - body: { response: args.response }, - }); + const result = await args.session.client.postSessionIdPermissionsPermissionId( + { + path: { id: args.session.sessionId, permissionID: args.permissionId }, + query: { directory: args.directory }, + body: { response: args.response }, + }, + ); if (result.error) { throw new Error('OpenCode permission response was rejected.'); } diff --git a/apps/agent-worker/src/runtime/docker.ts b/apps/agent-worker/src/runtime/docker.ts index f335cf1..b747575 100644 --- a/apps/agent-worker/src/runtime/docker.ts +++ b/apps/agent-worker/src/runtime/docker.ts @@ -1,5 +1,5 @@ -import { execa } from 'execa'; import path from 'node:path'; +import { execa } from 'execa'; import { env } from '../env'; @@ -134,15 +134,9 @@ export const startWorkspaceContainer = async (args: { publishTcpPort?: number; }) => { await ensureJobImagePulled(); - await execa( - containerRuntime(), - [ - 'rm', - '-f', - args.containerName, - ], - { reject: false }, - ); + await execa(containerRuntime(), ['rm', '-f', args.containerName], { + reject: false, + }); const result = await execa( containerRuntime(), [ @@ -177,7 +171,10 @@ export const startWorkspaceContainer = async (args: { }; }; -const getPublishedPort = async (containerName: string, containerPort: number) => { +const getPublishedPort = async ( + containerName: string, + containerPort: number, +) => { const result = await execa( containerRuntime(), ['port', containerName, `${containerPort}/tcp`], @@ -317,14 +314,10 @@ export const stopWorkspaceContainer = async (containerName: string) => { }; export const inspectWorkspaceContainer = async (containerName: string) => { - const result = await execa( - containerRuntime(), - ['inspect', containerName], - { - all: true, - reject: false, - }, - ); + const result = await execa(containerRuntime(), ['inspect', containerName], { + all: true, + reject: false, + }); return { exists: result.exitCode === 0, output: result.all, diff --git a/apps/agent-worker/src/server.ts b/apps/agent-worker/src/server.ts index 94a9851..89574dc 100644 --- a/apps/agent-worker/src/server.ts +++ b/apps/agent-worker/src/server.ts @@ -128,8 +128,9 @@ export const startWorkerServer = () => { sendJson(response, 200, await abortWorkspaceAgent(route.jobId)); return; } - const interactionMatch = - /^interactions\/([^/]+)\/reply$/.exec(route.action); + const interactionMatch = /^interactions\/([^/]+)\/reply$/.exec( + route.action, + ); if (request.method === 'POST' && interactionMatch?.[1]) { const body = await parseJson<{ externalRequestId?: string; @@ -166,21 +167,21 @@ export const startWorkerServer = () => { return; } sendJson(response, 404, { error: 'Not found' }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); console.error( `Worker HTTP ${request.method ?? 'UNKNOWN'} ${request.url ?? '/'} failed: ${message}`, ); - const status = - message === 'Unauthorized' - ? 401 - : message.includes('not supported') - ? 409 - : 500; - sendJson(response, status, { - error: message, - }); - } + const status = + message === 'Unauthorized' + ? 401 + : message.includes('not supported') + ? 409 + : 500; + sendJson(response, status, { + error: message, + }); + } })(); }); attachTerminalServer(server); diff --git a/apps/agent-worker/src/terminal.ts b/apps/agent-worker/src/terminal.ts index 1778e87..50febed 100644 --- a/apps/agent-worker/src/terminal.ts +++ b/apps/agent-worker/src/terminal.ts @@ -1,7 +1,8 @@ import type { Server } from 'node:http'; import type { Duplex } from 'node:stream'; +import type { WebSocket } from 'ws'; import Docker from 'dockerode'; -import { WebSocketServer, type WebSocket } from 'ws'; +import { WebSocketServer } from 'ws'; import { env } from './env'; import { containerVolumeSuffix, hostWorkspacePath } from './runtime/docker'; diff --git a/apps/agent-worker/src/worker.ts b/apps/agent-worker/src/worker.ts index 0da656e..6268646 100644 --- a/apps/agent-worker/src/worker.ts +++ b/apps/agent-worker/src/worker.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'node:crypto'; import { access, mkdir, @@ -7,7 +8,6 @@ import { stat, writeFile, } from 'node:fs/promises'; -import { randomBytes } from 'node:crypto'; import path from 'node:path'; import { ConvexHttpClient } from 'convex/browser'; @@ -15,6 +15,7 @@ import type { Id } from '@spoon/backend/convex/_generated/dataModel.js'; import { api } from '@spoon/backend/convex/_generated/api.js'; import type { NormalizedAgentEvent } from './agent-events'; +import type { OpenCodeSession } from './opencode-session'; import { normalizeCodexJsonLine } from './agent-events'; import { codexContainerRepo, @@ -30,7 +31,6 @@ import { run, } from './git'; import { getInstallationToken, openDraftPullRequest } from './github'; -import type { OpenCodeSession } from './opencode-session'; import { abortOpenCodeSession, createOpenCodeSession, @@ -432,7 +432,7 @@ const opencodeModel = (claim: Claim) => { const codexModel = (claim: Claim) => { const model = claim.aiProviderProfile?.model ?? claim.openai.model; - return model.includes('/') ? model.split('/').at(-1) ?? model : model; + return model.includes('/') ? (model.split('/').at(-1) ?? model) : model; }; const codexModelArgs = (claim: Claim) => @@ -531,8 +531,7 @@ const handleAgentEvent = async (args: { return; } if (event.kind === 'tool_started' || event.kind === 'tool_completed') { - const detail = - event.kind === 'tool_started' ? event.input : event.output; + const detail = event.kind === 'tool_started' ? event.input : event.output; await appendMessage({ jobId, role: 'tool', @@ -647,22 +646,25 @@ const ensureOpenCodeSession = async (workspace: ActiveWorkspace) => { let lastError: unknown; for (let attempt = 0; attempt < 20; attempt += 1) { try { - const session = await createOpenCodeSession({ - baseUrl, + const session = await createOpenCodeSession({ + baseUrl, password, directory: '/workspace/repo', title: workspace.claim.job.prompt.slice(0, 80) || 'Spoon workspace', onEvent: async (event) => { - const messageId = workspaceCurrentMessage.get(workspace.claim.job._id); + const messageId = workspaceCurrentMessage.get( + workspace.claim.job._id, + ); if (!messageId) return; await handleAgentEvent({ workspace, event, assistantMessageId: messageId, - assistantContent: - workspaceCurrentContent.get(workspace.claim.job._id) ?? { - value: '', - }, + assistantContent: workspaceCurrentContent.get( + workspace.claim.job._id, + ) ?? { + value: '', + }, }); }, }); @@ -1338,8 +1340,8 @@ const runClaim = async (claim: Claim) => { await sendWorkspaceMessage(jobId, systemPromptForJob(claim), { recordUserMessage: false, }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); await appendEvent( jobId, 'error', @@ -1491,7 +1493,12 @@ export const abortWorkspaceAgent = async (jobId: string) => { workspace.agentTurnActive = false; workspace.resolveTurn?.(); workspace.resolveTurn = undefined; - await appendEvent(workspace.claim.job._id, 'warn', 'cleanup', 'Agent turn aborted.'); + await appendEvent( + workspace.claim.job._id, + 'warn', + 'cleanup', + 'Agent turn aborted.', + ); return { success: true }; } if (workspace.runtimeMode === 'codex_exec') { @@ -1605,15 +1612,22 @@ export const sendWorkspaceMessage = async ( const secretEnv = Object.fromEntries( claim.secrets.map((secret) => [secret.name, secret.value]), ); - const result = await run('bash', ['-lc', `opencode run --format json --model ${quoteShell(opencodeModel(claim))} ${quoteShell(prompt)}`], { - cwd: workspace.repoDir, - env: { - ...aiEnv, - ...secretEnv, + const result = await run( + 'bash', + [ + '-lc', + `opencode run --format json --model ${quoteShell(opencodeModel(claim))} ${quoteShell(prompt)}`, + ], + { + cwd: workspace.repoDir, + env: { + ...aiEnv, + ...secretEnv, + }, + redact, + timeoutMs: env.jobTimeoutMs, }, - redact, - timeoutMs: env.jobTimeoutMs, - }); + ); await updateMessage({ messageId: assistantMessageId, status: result.exitCode === 0 ? 'completed' : 'failed', @@ -1634,15 +1648,15 @@ export const sendWorkspaceMessage = async ( : 'Codex completed without producing an assistant response.', ); } - await updateMessage({ - messageId: assistantMessageId, + await updateMessage({ + messageId: assistantMessageId, status: 'completed', content: assistantContent.value, - }); - workspace.agentTurnActive = false; - } - workspace.agentTurnActive = false; - if (claim.job.jobType === 'maintenance_review') { + }); + workspace.agentTurnActive = false; + } + workspace.agentTurnActive = false; + if (claim.job.jobType === 'maintenance_review') { const decision = parseMaintenanceDecision(assistantContent.value); if (decision) { await addArtifact({ @@ -1660,7 +1674,7 @@ export const sendWorkspaceMessage = async ( }); } } - const diff = await getWorktreeDiff(workspace.repoDir, redact); + const diff = await getWorktreeDiff(workspace.repoDir, redact); await addArtifact({ jobId: claim.job._id, kind: 'diff', @@ -1669,11 +1683,11 @@ export const sendWorkspaceMessage = async ( contentType: 'text/x-diff', }); await recordChangedFiles(workspace, 'agent', diff.output); - } catch (error) { - workspace.agentTurnActive = false; - workspace.resolveTurn?.(); - workspace.resolveTurn = undefined; - const message = error instanceof Error ? error.message : String(error); + } catch (error) { + workspace.agentTurnActive = false; + workspace.resolveTurn?.(); + workspace.resolveTurn = undefined; + const message = error instanceof Error ? error.message : String(error); console.error(`Agent turn failed for job ${claim.job._id}: ${message}`); await appendEvent( claim.job._id, diff --git a/apps/agent-worker/tests/unit/agent-events.test.ts b/apps/agent-worker/tests/unit/agent-events.test.ts index d0b2fdf..da4baae 100644 --- a/apps/agent-worker/tests/unit/agent-events.test.ts +++ b/apps/agent-worker/tests/unit/agent-events.test.ts @@ -271,7 +271,8 @@ describe('agent event normalization', () => { externalRequestId: 'perm-1', title: 'Permission requested', body: 'Run bun test?', - metadata: '{\n "permissionID": "perm-1",\n "message": "Run bun test?"\n}', + metadata: + '{\n "permissionID": "perm-1",\n "message": "Run bun test?"\n}', }); expect( diff --git a/apps/agent-worker/tests/unit/codex-runtime.test.ts b/apps/agent-worker/tests/unit/codex-runtime.test.ts index ef75c80..577698f 100644 --- a/apps/agent-worker/tests/unit/codex-runtime.test.ts +++ b/apps/agent-worker/tests/unit/codex-runtime.test.ts @@ -1,4 +1,11 @@ -import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises'; +import { + mkdir, + mkdtemp, + readFile, + rm, + stat, + writeFile, +} from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, test } from 'vitest'; diff --git a/apps/agent-worker/tests/unit/infisical-account.test.ts b/apps/agent-worker/tests/unit/infisical-account.test.ts index 22a27d9..fdc4ff9 100644 --- a/apps/agent-worker/tests/unit/infisical-account.test.ts +++ b/apps/agent-worker/tests/unit/infisical-account.test.ts @@ -3,7 +3,6 @@ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; - import { afterEach, beforeEach, describe, expect, test } from 'vitest'; type TestWorkspace = { @@ -53,7 +52,9 @@ const writeConfig = async ( config: Record | string, ) => { const content = - typeof config === 'string' ? config : `${JSON.stringify(config, null, 2)}\n`; + typeof config === 'string' + ? config + : `${JSON.stringify(config, null, 2)}\n`; await writeFile(configPath(workspace), content); };