export async function runWorker(): Promise<void> {
const startTime = Date.now();
// 1. Read task payload from /workspace/task.json
const raw = readFileSync(TASK_PATH, "utf-8");
const payload: TaskPayload = JSON.parse(raw);
const { task, systemPrompt, llmConfig } = payload;
// 2. Enable distributed tracing
enableTracing("/workspace");
let workerSpan: Span | undefined;
if (payload.trace) {
const tracer = Tracer.fromPropagated(payload.trace);
workerSpan = tracer.startSpan("sandbox.worker", {
taskId: task.id,
agentId: `sandbox-${task.id}`,
});
}
// 3. Write worker instructions as AGENTS.md in parent dir
// (Pi auto-discovers AGENTS.md by walking up from cwd)
if (systemPrompt) {
writeFileSync(WORKER_AGENTS_MD_PATH, systemPrompt, "utf-8");
}
// 4. Register LLM model with Pi's ModelRegistry
const authStorage = Reflect.construct(AuthStorage, []) as AuthStorage;
const modelRegistry = new ModelRegistry(authStorage);
modelRegistry.registerProvider("glm5", {
baseUrl: llmConfig.endpoint,
apiKey: llmConfig.apiKey || "no-key-needed",
api: "openai-completions",
models: [{
id: llmConfig.model,
name: llmConfig.model,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 131072,
maxTokens: llmConfig.maxTokens,
}],
});
const model = modelRegistry.find("glm5", llmConfig.model);
if (!model) {
throw new Error(`Model "${llmConfig.model}" not found in registry`);
}
const startSha = safeExec("git rev-parse HEAD", WORK_DIR);
// 5. Create Pi agent session with FULL tool suite
const { session } = await createAgentSession({
cwd: WORK_DIR,
model,
tools: fullPiTools, // [read, write, edit, bash, grep, find, ls]
authStorage,
modelRegistry,
sessionManager: SessionManager.inMemory(),
settingsManager: SettingsManager.inMemory(),
thinkingLevel: "off",
});
let toolCallCount = 0;
let lastAssistantMessage = "";
// 6. Subscribe to Pi events to track tool calls
session.subscribe((event: unknown) => {
if (event.type === "tool_execution_start") {
toolCallCount++;
}
if (event.type === "message_end" && event.message.role === "assistant") {
// Extract final assistant text
lastAssistantMessage = extractTextFromContent(event.message.content);
}
});
// 7. Prompt agent with task
const prompt = buildTaskPrompt(task);
await session.prompt(prompt);
const stats = session.getSessionStats();
const tokensUsed = stats.tokens.total;
session.dispose();
// 8. Detect empty LLM responses (bug fix)
const isEmptyResponse = tokensUsed === 0 && toolCallCount === 0;
if (isEmptyResponse) {
log("WARNING: LLM returned empty response (0 tokens, 0 tool calls). Marking as failed.");
}
// 9. Ensure .gitignore exists (scaffold safety)
if (!existsSync(`${WORK_DIR}/.gitignore`)) {
writeFileSync(`${WORK_DIR}/.gitignore`, GITIGNORE_ESSENTIALS, "utf-8");
}
// 10. Safety-net commit (only if agent did work)
if (!isEmptyResponse) {
safeExec("git add -A", WORK_DIR);
const stagedFiles = safeExec("git diff --cached --name-only", WORK_DIR);
if (stagedFiles) {
safeExec(`git commit -m "feat(${task.id}): auto-commit uncommitted changes"`, WORK_DIR);
}
}
// 11. Post-agent build check
let buildExitCode: number | null = null;
if (!isEmptyResponse && existsSync(`${WORK_DIR}/tsconfig.json`)) {
try {
execSync("npx tsc --noEmit", { cwd: WORK_DIR, encoding: "utf-8", timeout: 60_000 });
buildExitCode = 0;
} catch (buildErr: unknown) {
buildExitCode = hasStatusCode(buildErr) ? buildErr.status : 1;
}
}
// 12. Extract diff stats (exclude artifacts)
const diff = safeExec(`git diff ${startSha} --no-color -- . ':!node_modules'`, WORK_DIR);
const numstat = safeExec(`git diff ${startSha} --numstat`, WORK_DIR);
const filesCreatedRaw = safeExec(`git diff ${startSha} --diff-filter=A --name-only`, WORK_DIR);
const filesChangedRaw = safeExec(`git diff ${startSha} --name-only`, WORK_DIR);
const filesChanged = filesChangedRaw.split("\n").filter(Boolean).filter((f) => !isArtifact(f));
const filesCreated = filesCreatedRaw.split("\n").filter(Boolean).filter((f) => !isArtifact(f));
// Parse numstat for line counts
let linesAdded = 0;
let linesRemoved = 0;
if (numstat) {
for (const line of numstat.split("\n")) {
const [addedRaw, removedRaw, filePath] = line.split("\t");
if (filePath && !isArtifact(filePath)) {
linesAdded += parseInt(addedRaw, 10) || 0;
linesRemoved += parseInt(removedRaw, 10) || 0;
}
}
}
// 13. Build handoff
const handoff: Handoff = {
taskId: task.id,
status: isEmptyResponse ? "failed" : "complete",
summary: isEmptyResponse
? "Task failed: LLM returned empty response (0 tokens, 0 tool calls). Possible API/endpoint failure."
: lastAssistantMessage || "Task completed (no final message captured).",
diff,
filesChanged,
concerns: isEmptyResponse
? ["Empty LLM response — possible API failure or model endpoint issue"]
: buildExitCode !== null && buildExitCode !== 0
? [`Post-agent build check failed (tsc exit code ${buildExitCode})`]
: [],
suggestions: isEmptyResponse ? ["Check LLM endpoint connectivity"] : [],
buildExitCode,
metrics: {
linesAdded,
linesRemoved,
filesCreated: filesCreated.length,
filesModified: Math.max(0, filesChanged.length - filesCreated.length),
tokensUsed,
toolCallCount,
durationMs: Date.now() - startTime,
},
};
writeResult(handoff);
}