Update formatting on worker
This commit is contained in:
@@ -71,7 +71,8 @@ const textFromPart = (part: Record<string, unknown>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const commandString = (value: unknown) => {
|
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);
|
return stringify(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -82,8 +83,7 @@ const toolNameFromRecord = (record: Record<string, unknown> | null) =>
|
|||||||
record?.toolName ??
|
record?.toolName ??
|
||||||
record?.name ??
|
record?.name ??
|
||||||
record?.function ??
|
record?.function ??
|
||||||
(stringify(record?.type).toLowerCase().includes('exec') ||
|
(stringify(record?.type).toLowerCase().includes('exec') || record?.command
|
||||||
record?.command
|
|
||||||
? 'Command'
|
? 'Command'
|
||||||
: record?.type) ??
|
: record?.type) ??
|
||||||
'tool',
|
'tool',
|
||||||
@@ -132,7 +132,9 @@ const recordLooksLikeTool = (
|
|||||||
recordType.includes('exec_command') ||
|
recordType.includes('exec_command') ||
|
||||||
recordType.includes('command') ||
|
recordType.includes('command') ||
|
||||||
recordType.includes('mcp') ||
|
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 (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);
|
const delta = stringify(msg.delta ?? msg.text);
|
||||||
if (delta) events.push({ kind: 'assistant_delta', content: delta });
|
if (delta) events.push({ kind: 'assistant_delta', content: delta });
|
||||||
}
|
}
|
||||||
@@ -177,7 +182,11 @@ const normalizeCodexMsgEvent = (
|
|||||||
output: toolOutputFromRecord(msg),
|
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);
|
const message = stringify(msg.message ?? msg.error ?? msg);
|
||||||
if (isCodexConfigWarning(message)) {
|
if (isCodexConfigWarning(message)) {
|
||||||
events.push({ kind: 'status', status: message });
|
events.push({ kind: 'status', status: message });
|
||||||
@@ -354,7 +363,8 @@ export const normalizeOpenCodeEvent = (
|
|||||||
const event = asRecord(input);
|
const event = asRecord(input);
|
||||||
if (!event) return [];
|
if (!event) return [];
|
||||||
const type = stringify(event.type);
|
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 events: NormalizedAgentEvent[] = [];
|
||||||
const sessionId = properties.sessionID ?? properties.sessionId;
|
const sessionId = properties.sessionID ?? properties.sessionId;
|
||||||
if (typeof sessionId === 'string' && type.includes('session')) {
|
if (typeof sessionId === 'string' && type.includes('session')) {
|
||||||
@@ -408,7 +418,8 @@ export const normalizeOpenCodeEvent = (
|
|||||||
}
|
}
|
||||||
if (type === 'file.edited') {
|
if (type === 'file.edited') {
|
||||||
const file = properties.file;
|
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') {
|
if (type === 'command.executed') {
|
||||||
events.push({
|
events.push({
|
||||||
@@ -422,7 +433,9 @@ export const normalizeOpenCodeEvent = (
|
|||||||
kind: 'permission_requested',
|
kind: 'permission_requested',
|
||||||
externalRequestId: stringify(properties.permissionID ?? properties.id),
|
externalRequestId: stringify(properties.permissionID ?? properties.id),
|
||||||
title: 'Permission requested',
|
title: 'Permission requested',
|
||||||
body: stringify(properties.permission ?? properties.message ?? properties),
|
body: stringify(
|
||||||
|
properties.permission ?? properties.message ?? properties,
|
||||||
|
),
|
||||||
metadata: stringify(properties),
|
metadata: stringify(properties),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -443,7 +456,11 @@ export const normalizeOpenCodeEvent = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (events.length === 0 && type) {
|
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;
|
return events;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const env = {
|
|||||||
process.env.SPOON_AGENT_CONTAINER_RUNTIME?.trim() ??
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME?.trim() ??
|
||||||
process.env.SPOON_CONTAINER_RUNTIME?.trim() ??
|
process.env.SPOON_CONTAINER_RUNTIME?.trim() ??
|
||||||
'docker',
|
'docker',
|
||||||
containerVolumeOptions: process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS?.trim(),
|
containerVolumeOptions:
|
||||||
|
process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS?.trim(),
|
||||||
containerAccess:
|
containerAccess:
|
||||||
process.env.SPOON_AGENT_CONTAINER_ACCESS?.trim() === 'host_port'
|
process.env.SPOON_AGENT_CONTAINER_ACCESS?.trim() === 'host_port'
|
||||||
? 'host_port'
|
? 'host_port'
|
||||||
|
|||||||
@@ -155,8 +155,7 @@ export const getWorktreeDiff = async (
|
|||||||
if (diff.output.trim()) untrackedDiffs.push(diff.output);
|
if (diff.output.trim()) untrackedDiffs.push(diff.output);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
exitCode:
|
exitCode: trackedDiff.exitCode === 0 && untracked.exitCode === 0 ? 0 : 1,
|
||||||
trackedDiff.exitCode === 0 && untracked.exitCode === 0 ? 0 : 1,
|
|
||||||
output: [trackedDiff.output, ...untrackedDiffs]
|
output: [trackedDiff.output, ...untrackedDiffs]
|
||||||
.filter((part) => part.trim())
|
.filter((part) => part.trim())
|
||||||
.join('\n'),
|
.join('\n'),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createOpencodeClient } from '@opencode-ai/sdk';
|
|
||||||
import type { OpencodeClient } from '@opencode-ai/sdk';
|
import type { OpencodeClient } from '@opencode-ai/sdk';
|
||||||
|
import { createOpencodeClient } from '@opencode-ai/sdk';
|
||||||
|
|
||||||
import type { NormalizedAgentEvent } from './agent-events';
|
import type { NormalizedAgentEvent } from './agent-events';
|
||||||
import { normalizeOpenCodeEvent } from './agent-events';
|
import { normalizeOpenCodeEvent } from './agent-events';
|
||||||
@@ -115,11 +115,13 @@ export const replyOpenCodePermission = async (args: {
|
|||||||
response: 'once' | 'always' | 'reject';
|
response: 'once' | 'always' | 'reject';
|
||||||
directory: string;
|
directory: string;
|
||||||
}) => {
|
}) => {
|
||||||
const result = await args.session.client.postSessionIdPermissionsPermissionId({
|
const result = await args.session.client.postSessionIdPermissionsPermissionId(
|
||||||
path: { id: args.session.sessionId, permissionID: args.permissionId },
|
{
|
||||||
query: { directory: args.directory },
|
path: { id: args.session.sessionId, permissionID: args.permissionId },
|
||||||
body: { response: args.response },
|
query: { directory: args.directory },
|
||||||
});
|
body: { response: args.response },
|
||||||
|
},
|
||||||
|
);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw new Error('OpenCode permission response was rejected.');
|
throw new Error('OpenCode permission response was rejected.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { execa } from 'execa';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { execa } from 'execa';
|
||||||
|
|
||||||
import { env } from '../env';
|
import { env } from '../env';
|
||||||
|
|
||||||
@@ -134,15 +134,9 @@ export const startWorkspaceContainer = async (args: {
|
|||||||
publishTcpPort?: number;
|
publishTcpPort?: number;
|
||||||
}) => {
|
}) => {
|
||||||
await ensureJobImagePulled();
|
await ensureJobImagePulled();
|
||||||
await execa(
|
await execa(containerRuntime(), ['rm', '-f', args.containerName], {
|
||||||
containerRuntime(),
|
reject: false,
|
||||||
[
|
});
|
||||||
'rm',
|
|
||||||
'-f',
|
|
||||||
args.containerName,
|
|
||||||
],
|
|
||||||
{ reject: false },
|
|
||||||
);
|
|
||||||
const result = await execa(
|
const result = await execa(
|
||||||
containerRuntime(),
|
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(
|
const result = await execa(
|
||||||
containerRuntime(),
|
containerRuntime(),
|
||||||
['port', containerName, `${containerPort}/tcp`],
|
['port', containerName, `${containerPort}/tcp`],
|
||||||
@@ -317,14 +314,10 @@ export const stopWorkspaceContainer = async (containerName: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const inspectWorkspaceContainer = async (containerName: string) => {
|
export const inspectWorkspaceContainer = async (containerName: string) => {
|
||||||
const result = await execa(
|
const result = await execa(containerRuntime(), ['inspect', containerName], {
|
||||||
containerRuntime(),
|
all: true,
|
||||||
['inspect', containerName],
|
reject: false,
|
||||||
{
|
});
|
||||||
all: true,
|
|
||||||
reject: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
exists: result.exitCode === 0,
|
exists: result.exitCode === 0,
|
||||||
output: result.all,
|
output: result.all,
|
||||||
|
|||||||
@@ -128,8 +128,9 @@ export const startWorkerServer = () => {
|
|||||||
sendJson(response, 200, await abortWorkspaceAgent(route.jobId));
|
sendJson(response, 200, await abortWorkspaceAgent(route.jobId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const interactionMatch =
|
const interactionMatch = /^interactions\/([^/]+)\/reply$/.exec(
|
||||||
/^interactions\/([^/]+)\/reply$/.exec(route.action);
|
route.action,
|
||||||
|
);
|
||||||
if (request.method === 'POST' && interactionMatch?.[1]) {
|
if (request.method === 'POST' && interactionMatch?.[1]) {
|
||||||
const body = await parseJson<{
|
const body = await parseJson<{
|
||||||
externalRequestId?: string;
|
externalRequestId?: string;
|
||||||
@@ -166,21 +167,21 @@ export const startWorkerServer = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendJson(response, 404, { error: 'Not found' });
|
sendJson(response, 404, { error: 'Not found' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
console.error(
|
console.error(
|
||||||
`Worker HTTP ${request.method ?? 'UNKNOWN'} ${request.url ?? '/'} failed: ${message}`,
|
`Worker HTTP ${request.method ?? 'UNKNOWN'} ${request.url ?? '/'} failed: ${message}`,
|
||||||
);
|
);
|
||||||
const status =
|
const status =
|
||||||
message === 'Unauthorized'
|
message === 'Unauthorized'
|
||||||
? 401
|
? 401
|
||||||
: message.includes('not supported')
|
: message.includes('not supported')
|
||||||
? 409
|
? 409
|
||||||
: 500;
|
: 500;
|
||||||
sendJson(response, status, {
|
sendJson(response, status, {
|
||||||
error: message,
|
error: message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
});
|
||||||
attachTerminalServer(server);
|
attachTerminalServer(server);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Server } from 'node:http';
|
import type { Server } from 'node:http';
|
||||||
import type { Duplex } from 'node:stream';
|
import type { Duplex } from 'node:stream';
|
||||||
|
import type { WebSocket } from 'ws';
|
||||||
import Docker from 'dockerode';
|
import Docker from 'dockerode';
|
||||||
import { WebSocketServer, type WebSocket } from 'ws';
|
import { WebSocketServer } from 'ws';
|
||||||
|
|
||||||
import { env } from './env';
|
import { env } from './env';
|
||||||
import { containerVolumeSuffix, hostWorkspacePath } from './runtime/docker';
|
import { containerVolumeSuffix, hostWorkspacePath } from './runtime/docker';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
import {
|
import {
|
||||||
access,
|
access,
|
||||||
mkdir,
|
mkdir,
|
||||||
@@ -7,7 +8,6 @@ import {
|
|||||||
stat,
|
stat,
|
||||||
writeFile,
|
writeFile,
|
||||||
} from 'node:fs/promises';
|
} from 'node:fs/promises';
|
||||||
import { randomBytes } from 'node:crypto';
|
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { ConvexHttpClient } from 'convex/browser';
|
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 { api } from '@spoon/backend/convex/_generated/api.js';
|
||||||
|
|
||||||
import type { NormalizedAgentEvent } from './agent-events';
|
import type { NormalizedAgentEvent } from './agent-events';
|
||||||
|
import type { OpenCodeSession } from './opencode-session';
|
||||||
import { normalizeCodexJsonLine } from './agent-events';
|
import { normalizeCodexJsonLine } from './agent-events';
|
||||||
import {
|
import {
|
||||||
codexContainerRepo,
|
codexContainerRepo,
|
||||||
@@ -30,7 +31,6 @@ import {
|
|||||||
run,
|
run,
|
||||||
} from './git';
|
} from './git';
|
||||||
import { getInstallationToken, openDraftPullRequest } from './github';
|
import { getInstallationToken, openDraftPullRequest } from './github';
|
||||||
import type { OpenCodeSession } from './opencode-session';
|
|
||||||
import {
|
import {
|
||||||
abortOpenCodeSession,
|
abortOpenCodeSession,
|
||||||
createOpenCodeSession,
|
createOpenCodeSession,
|
||||||
@@ -432,7 +432,7 @@ const opencodeModel = (claim: Claim) => {
|
|||||||
|
|
||||||
const codexModel = (claim: Claim) => {
|
const codexModel = (claim: Claim) => {
|
||||||
const model = claim.aiProviderProfile?.model ?? claim.openai.model;
|
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) =>
|
const codexModelArgs = (claim: Claim) =>
|
||||||
@@ -531,8 +531,7 @@ const handleAgentEvent = async (args: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.kind === 'tool_started' || event.kind === 'tool_completed') {
|
if (event.kind === 'tool_started' || event.kind === 'tool_completed') {
|
||||||
const detail =
|
const detail = event.kind === 'tool_started' ? event.input : event.output;
|
||||||
event.kind === 'tool_started' ? event.input : event.output;
|
|
||||||
await appendMessage({
|
await appendMessage({
|
||||||
jobId,
|
jobId,
|
||||||
role: 'tool',
|
role: 'tool',
|
||||||
@@ -647,22 +646,25 @@ const ensureOpenCodeSession = async (workspace: ActiveWorkspace) => {
|
|||||||
let lastError: unknown;
|
let lastError: unknown;
|
||||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
const session = await createOpenCodeSession({
|
const session = await createOpenCodeSession({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
password,
|
password,
|
||||||
directory: '/workspace/repo',
|
directory: '/workspace/repo',
|
||||||
title: workspace.claim.job.prompt.slice(0, 80) || 'Spoon workspace',
|
title: workspace.claim.job.prompt.slice(0, 80) || 'Spoon workspace',
|
||||||
onEvent: async (event) => {
|
onEvent: async (event) => {
|
||||||
const messageId = workspaceCurrentMessage.get(workspace.claim.job._id);
|
const messageId = workspaceCurrentMessage.get(
|
||||||
|
workspace.claim.job._id,
|
||||||
|
);
|
||||||
if (!messageId) return;
|
if (!messageId) return;
|
||||||
await handleAgentEvent({
|
await handleAgentEvent({
|
||||||
workspace,
|
workspace,
|
||||||
event,
|
event,
|
||||||
assistantMessageId: messageId,
|
assistantMessageId: messageId,
|
||||||
assistantContent:
|
assistantContent: workspaceCurrentContent.get(
|
||||||
workspaceCurrentContent.get(workspace.claim.job._id) ?? {
|
workspace.claim.job._id,
|
||||||
value: '',
|
) ?? {
|
||||||
},
|
value: '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1338,8 +1340,8 @@ const runClaim = async (claim: Claim) => {
|
|||||||
await sendWorkspaceMessage(jobId, systemPromptForJob(claim), {
|
await sendWorkspaceMessage(jobId, systemPromptForJob(claim), {
|
||||||
recordUserMessage: false,
|
recordUserMessage: false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
await appendEvent(
|
await appendEvent(
|
||||||
jobId,
|
jobId,
|
||||||
'error',
|
'error',
|
||||||
@@ -1491,7 +1493,12 @@ export const abortWorkspaceAgent = async (jobId: string) => {
|
|||||||
workspace.agentTurnActive = false;
|
workspace.agentTurnActive = false;
|
||||||
workspace.resolveTurn?.();
|
workspace.resolveTurn?.();
|
||||||
workspace.resolveTurn = undefined;
|
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 };
|
return { success: true };
|
||||||
}
|
}
|
||||||
if (workspace.runtimeMode === 'codex_exec') {
|
if (workspace.runtimeMode === 'codex_exec') {
|
||||||
@@ -1605,15 +1612,22 @@ export const sendWorkspaceMessage = async (
|
|||||||
const secretEnv = Object.fromEntries(
|
const secretEnv = Object.fromEntries(
|
||||||
claim.secrets.map((secret) => [secret.name, secret.value]),
|
claim.secrets.map((secret) => [secret.name, secret.value]),
|
||||||
);
|
);
|
||||||
const result = await run('bash', ['-lc', `opencode run --format json --model ${quoteShell(opencodeModel(claim))} ${quoteShell(prompt)}`], {
|
const result = await run(
|
||||||
cwd: workspace.repoDir,
|
'bash',
|
||||||
env: {
|
[
|
||||||
...aiEnv,
|
'-lc',
|
||||||
...secretEnv,
|
`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({
|
await updateMessage({
|
||||||
messageId: assistantMessageId,
|
messageId: assistantMessageId,
|
||||||
status: result.exitCode === 0 ? 'completed' : 'failed',
|
status: result.exitCode === 0 ? 'completed' : 'failed',
|
||||||
@@ -1634,15 +1648,15 @@ export const sendWorkspaceMessage = async (
|
|||||||
: 'Codex completed without producing an assistant response.',
|
: 'Codex completed without producing an assistant response.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await updateMessage({
|
await updateMessage({
|
||||||
messageId: assistantMessageId,
|
messageId: assistantMessageId,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
content: assistantContent.value,
|
content: assistantContent.value,
|
||||||
});
|
});
|
||||||
workspace.agentTurnActive = false;
|
workspace.agentTurnActive = false;
|
||||||
}
|
}
|
||||||
workspace.agentTurnActive = false;
|
workspace.agentTurnActive = false;
|
||||||
if (claim.job.jobType === 'maintenance_review') {
|
if (claim.job.jobType === 'maintenance_review') {
|
||||||
const decision = parseMaintenanceDecision(assistantContent.value);
|
const decision = parseMaintenanceDecision(assistantContent.value);
|
||||||
if (decision) {
|
if (decision) {
|
||||||
await addArtifact({
|
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({
|
await addArtifact({
|
||||||
jobId: claim.job._id,
|
jobId: claim.job._id,
|
||||||
kind: 'diff',
|
kind: 'diff',
|
||||||
@@ -1669,11 +1683,11 @@ export const sendWorkspaceMessage = async (
|
|||||||
contentType: 'text/x-diff',
|
contentType: 'text/x-diff',
|
||||||
});
|
});
|
||||||
await recordChangedFiles(workspace, 'agent', diff.output);
|
await recordChangedFiles(workspace, 'agent', diff.output);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
workspace.agentTurnActive = false;
|
workspace.agentTurnActive = false;
|
||||||
workspace.resolveTurn?.();
|
workspace.resolveTurn?.();
|
||||||
workspace.resolveTurn = undefined;
|
workspace.resolveTurn = undefined;
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
console.error(`Agent turn failed for job ${claim.job._id}: ${message}`);
|
console.error(`Agent turn failed for job ${claim.job._id}: ${message}`);
|
||||||
await appendEvent(
|
await appendEvent(
|
||||||
claim.job._id,
|
claim.job._id,
|
||||||
|
|||||||
@@ -271,7 +271,8 @@ describe('agent event normalization', () => {
|
|||||||
externalRequestId: 'perm-1',
|
externalRequestId: 'perm-1',
|
||||||
title: 'Permission requested',
|
title: 'Permission requested',
|
||||||
body: 'Run bun test?',
|
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(
|
expect(
|
||||||
|
|||||||
@@ -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 os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { afterEach, describe, expect, test } from 'vitest';
|
import { afterEach, describe, expect, test } from 'vitest';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
type TestWorkspace = {
|
type TestWorkspace = {
|
||||||
@@ -53,7 +52,9 @@ const writeConfig = async (
|
|||||||
config: Record<string, unknown> | string,
|
config: Record<string, unknown> | string,
|
||||||
) => {
|
) => {
|
||||||
const content =
|
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);
|
await writeFile(configPath(workspace), content);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user