@granular-software/sdk 0.4.46 → 0.4.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19888,6 +19888,202 @@ async function writeJson(filePath, value) {
19888
19888
  await promises.writeFile(filePath, `${JSON.stringify(value, null, 2)}
19889
19889
  `);
19890
19890
  }
19891
+ function safePathSegment(value, fallback) {
19892
+ const normalized = (value || "").trim().toLowerCase();
19893
+ const sanitized = normalized.replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
19894
+ return sanitized || fallback;
19895
+ }
19896
+ function extractToolCalls(rawGeneration) {
19897
+ const raw = asRecord6(rawGeneration);
19898
+ if (!raw) return null;
19899
+ const choices = asArray3(raw.choices);
19900
+ const firstChoiceMessage = asRecord6(choices[0]);
19901
+ const message = asRecord6(firstChoiceMessage?.message);
19902
+ const toolCalls = asArray3(message?.tool_calls);
19903
+ if (toolCalls.length > 0) return toolCalls;
19904
+ return asArray3(raw.tool_calls).length > 0 ? asArray3(raw.tool_calls) : null;
19905
+ }
19906
+ function buildTurnMdxReport(input) {
19907
+ const {
19908
+ status,
19909
+ conversation,
19910
+ request,
19911
+ requestTimestamp,
19912
+ turnLog,
19913
+ responseText,
19914
+ terminalKind,
19915
+ actionSummary = [],
19916
+ promptInteractions = [],
19917
+ result,
19918
+ prompts = [],
19919
+ verification,
19920
+ error
19921
+ } = input;
19922
+ const iterationLines = [];
19923
+ for (const iteration of turnLog.iterations) {
19924
+ iterationLines.push(`### Iteration ${iteration.iteration}`);
19925
+ iterationLines.push(
19926
+ `- Request: ${iteration.request}`,
19927
+ `- Generation duration: ${iteration.generationDurationMs !== void 0 ? `${iteration.generationDurationMs}ms` : "unknown"}`
19928
+ );
19929
+ if (iteration.templateId) {
19930
+ iterationLines.push(
19931
+ `- Template: ${iteration.templateId}@${iteration.templateVersion || "unknown"}`
19932
+ );
19933
+ }
19934
+ if (iteration.templateHash) {
19935
+ iterationLines.push(`- Template hash: ${iteration.templateHash}`);
19936
+ }
19937
+ if (iteration.promptInstanceHash) {
19938
+ iterationLines.push(`- Prompt hash: ${iteration.promptInstanceHash}`);
19939
+ }
19940
+ iterationLines.push("", "#### Full prompt sent to LLM", "");
19941
+ iterationLines.push(fenced(iteration.systemPrompt, "text"));
19942
+ if (iteration.generationReply?.trim()) {
19943
+ iterationLines.push(
19944
+ "",
19945
+ "#### LLM reply text",
19946
+ iteration.generationReply.trim(),
19947
+ ""
19948
+ );
19949
+ }
19950
+ if (iteration.generatedCode?.trim()) {
19951
+ iterationLines.push("#### Generated code", "", fenced(iteration.generatedCode.trim(), "ts"));
19952
+ }
19953
+ const toolCalls = extractToolCalls(iteration.rawGeneration);
19954
+ iterationLines.push("#### Tool calls / raw generation", "");
19955
+ if (toolCalls) {
19956
+ iterationLines.push(fenced(JSON.stringify(toolCalls, null, 2), "json"));
19957
+ } else if (iteration.rawGeneration) {
19958
+ iterationLines.push(fenced(JSON.stringify(iteration.rawGeneration, null, 2), "json"));
19959
+ } else {
19960
+ iterationLines.push("_No tool call information._");
19961
+ }
19962
+ if (iteration.tokenUsage) {
19963
+ iterationLines.push("", "#### Token usage", ...formatTokenUsage(iteration.tokenUsage));
19964
+ }
19965
+ if (iteration.responseText?.trim()) {
19966
+ iterationLines.push("", "#### Runtime/prompt outcome", iteration.responseText);
19967
+ }
19968
+ if (iteration.actionSummary?.length) {
19969
+ iterationLines.push("", "#### Action summary", "");
19970
+ iterationLines.push(...iteration.actionSummary.map((line) => `- ${line}`));
19971
+ }
19972
+ if (iteration.continuation) {
19973
+ iterationLines.push("", "#### Continuation", "", jsonBlock(iteration.continuation));
19974
+ }
19975
+ if (iteration.result !== void 0) {
19976
+ iterationLines.push("", "#### Result", "", jsonBlock(iteration.result));
19977
+ }
19978
+ if (iteration.error) {
19979
+ iterationLines.push("", `#### Error`, "", iteration.error);
19980
+ }
19981
+ iterationLines.push("", "---", "");
19982
+ }
19983
+ const promptEventLines = conversation.promptEvents.filter((event) => event.receivedAt >= requestTimestamp).map((event, index) => {
19984
+ const prompt = event.prompt;
19985
+ return `${index + 1}. ${prompt.type} ${prompt.title || ""} ${prompt.message ? `\u2014 ${prompt.message}` : ""}`;
19986
+ });
19987
+ const historyLines = conversation.history.map((entry, index) => {
19988
+ const label = `${index + 1}. ${entry.role}`;
19989
+ const detail = entry.content || (entry.code ? "(code)" : "");
19990
+ return `${label}: ${detail || "(no text)"}`;
19991
+ });
19992
+ const lines = [
19993
+ "# Local Agent Query Report",
19994
+ "",
19995
+ `- timestamp: ${new Date(requestTimestamp).toISOString()}`,
19996
+ `- turn: ${turnLog.turnNumber} (${turnLog.turnId})`,
19997
+ `- status: ${status}`,
19998
+ "",
19999
+ "## Table of contents",
20000
+ "- [Metadata](#metadata)",
20001
+ "- [User query and context](#user-query-and-context)",
20002
+ "- [Conversation history](#conversation-history)",
20003
+ "- [Harness loop iterations](#harness-loop-iterations)",
20004
+ "- [Result and interactions](#result-and-interactions)",
20005
+ "",
20006
+ "## Metadata",
20007
+ `- Ontology: ${conversation.environment.ontologyId}`,
20008
+ `- Subject: ${conversation.environment.subjectId}`,
20009
+ `- Session: ${conversation.environment.sessionId}`,
20010
+ `- Environment: ${conversation.environment.environmentId}`,
20011
+ `- Sandbox: ${conversation.environment.sandboxId}`,
20012
+ `- Permission profile: ${conversation.environment.permissionProfileId}`,
20013
+ `- Conversation artifacts: ${turnLog.turnDir}`,
20014
+ "",
20015
+ "## User query and context",
20016
+ "",
20017
+ "### Request",
20018
+ fenced(request, "text"),
20019
+ "",
20020
+ "### Open prompts at/after request time"
20021
+ ];
20022
+ if (promptEventLines.length) {
20023
+ lines.push(...promptEventLines.map((line) => `- ${line}`));
20024
+ } else {
20025
+ lines.push("- _None");
20026
+ }
20027
+ lines.push(
20028
+ "",
20029
+ "## Conversation history",
20030
+ ...historyLines.map((line) => `- ${line}`),
20031
+ "",
20032
+ "## Harness loop iterations",
20033
+ "",
20034
+ ...iterationLines,
20035
+ "## Result and interactions",
20036
+ ""
20037
+ );
20038
+ if (responseText) {
20039
+ lines.push("### Final response", responseText, "");
20040
+ }
20041
+ if (terminalKind) {
20042
+ lines.push(`### Terminal kind`, terminalKind, "");
20043
+ }
20044
+ lines.push("### Action summary");
20045
+ if (actionSummary.length) {
20046
+ lines.push(...actionSummary.map((line) => `- ${line}`));
20047
+ } else {
20048
+ lines.push("- None");
20049
+ }
20050
+ lines.push("", "### User interactions");
20051
+ if (promptInteractions.length) {
20052
+ for (const interaction of promptInteractions) {
20053
+ lines.push(
20054
+ `- ${interaction.type} ${interaction.message || interaction.title} -> ${JSON.stringify(interaction.answer)}`
20055
+ );
20056
+ }
20057
+ } else {
20058
+ lines.push("- None");
20059
+ }
20060
+ lines.push("", "### Pending prompts");
20061
+ if (prompts.length) {
20062
+ for (const prompt of prompts) {
20063
+ lines.push(`- ${prompt.type} ${prompt.title || ""} ${prompt.message || ""}`);
20064
+ }
20065
+ } else {
20066
+ lines.push("- None");
20067
+ }
20068
+ if (result !== void 0) {
20069
+ lines.push("", "### Result payload", "", jsonBlock(result));
20070
+ }
20071
+ if (verification !== void 0) {
20072
+ lines.push("", "### Verification", "", jsonBlock(verification));
20073
+ }
20074
+ if (error) {
20075
+ lines.push("", `### Error`, "", error);
20076
+ }
20077
+ if (turnLog.error) {
20078
+ lines.push("", "### Turn log error", "", turnLog.error);
20079
+ }
20080
+ lines.push("");
20081
+ if (iterationLines.length === 0) {
20082
+ lines.splice(lines.indexOf("## Harness loop iterations") + 1, 0, "- _No iterations recorded._");
20083
+ }
20084
+ return `${lines.join("\n")}
20085
+ `;
20086
+ }
19891
20087
  function describeScenarioBehavior(result) {
19892
20088
  if (result.scenario.description?.trim()) {
19893
20089
  return result.scenario.description.trim();
@@ -20668,6 +20864,16 @@ function createAgentEvalHarness(options) {
20668
20864
  const chatTimeoutMs = options.chatTimeoutMs ?? 12e4;
20669
20865
  const jobTimeoutMs = options.jobTimeoutMs ?? 9e4;
20670
20866
  const pollIntervalMs = options.pollIntervalMs ?? 250;
20867
+ const isTruthyEnv = (value) => {
20868
+ return value?.trim().toLowerCase() === "1" || value?.trim().toLowerCase() === "true" || value?.trim().toLowerCase() === "yes" || value?.trim().toLowerCase() === "on";
20869
+ };
20870
+ const localTurnReportsEnabled = (() => {
20871
+ if (typeof options.local === "boolean") return options.local;
20872
+ return process.env.NODE_ENV === "development" || isTruthyEnv(process.env.GRANULAR_LOCAL) || isTruthyEnv(process.env.GRANULAR_USE_LOCAL_ENDPOINTS) || isTruthyEnv(process.env.GRANULAR_USE_LOCAL) || process.env.GRANULAR_ENDPOINT_MODE?.toLowerCase() === "local";
20873
+ })();
20874
+ const localTurnReportBaseDir = path__default.default.resolve(
20875
+ options.localTurnReportBaseDir || process.cwd()
20876
+ );
20671
20877
  const resolvedTemplate = resolveHarnessTemplate(
20672
20878
  options.harnessTemplateId || process.env.GRANULAR_AGENT_HARNESS_TEMPLATE || "stable");
20673
20879
  const promptRenderer = options.promptRenderer || resolvedTemplate.renderPrompt;
@@ -20716,6 +20922,41 @@ function createAgentEvalHarness(options) {
20716
20922
  } catch {
20717
20923
  }
20718
20924
  }
20925
+ async function writeLocalTurnReport(input) {
20926
+ if (!localTurnReportsEnabled) return;
20927
+ const { status, conversation, turnLog, request, requestTimestamp, error } = input;
20928
+ const requestId = safePathSegment(
20929
+ new Date(requestTimestamp).toISOString().replace(/[:.]/g, "-"),
20930
+ "turn"
20931
+ );
20932
+ const reportDir = path__default.default.join(
20933
+ localTurnReportBaseDir,
20934
+ safePathSegment(conversation.environment.ontologyId, "ontology"),
20935
+ safePathSegment(conversation.environment.subjectId, "subject"),
20936
+ safePathSegment(conversation.environment.sessionId, "session")
20937
+ );
20938
+ await ensureDir(reportDir);
20939
+ const completed = input.completed;
20940
+ const pending = input.pending;
20941
+ const report = buildTurnMdxReport({
20942
+ status,
20943
+ conversation,
20944
+ request,
20945
+ requestTimestamp,
20946
+ turnLog,
20947
+ responseText: completed?.responseText,
20948
+ terminalKind: completed?.terminalKind,
20949
+ actionSummary: completed?.actionSummary,
20950
+ promptInteractions: completed?.promptInteractions || pending?.promptInteractions,
20951
+ result: completed?.result,
20952
+ prompts: pending?.prompts,
20953
+ verification: completed?.verification,
20954
+ error
20955
+ });
20956
+ const reportPath = path__default.default.join(reportDir, `${requestId}.mdx`);
20957
+ await promises.writeFile(reportPath, report);
20958
+ console.log(`[agent][local] saved query report: ${reportPath}`);
20959
+ }
20719
20960
  async function runCheckJob(code, session) {
20720
20961
  const job = await session.submitJob(code);
20721
20962
  return withTimeout2(job.result, jobTimeoutMs, `check job ${job.id}`);
@@ -20975,99 +21216,96 @@ function createAgentEvalHarness(options) {
20975
21216
  const baselineClosureId = getCurrentClosureId(
20976
21217
  cloneJson(conversation.environment.document)
20977
21218
  );
20978
- while (iteration < maxIterations) {
20979
- const liveDoc = cloneJson(conversation.environment.document);
20980
- const promptHeap = normalizeHeapSnapshot2(asRecord6(liveDoc?.heap));
20981
- const pendingPrompts = filterPromptsByBoundary(
20982
- liveDoc,
20983
- getOpenPromptsFromDoc(liveDoc),
20984
- boundaryTimestamp
20985
- );
20986
- const workflowFocus = projectWorkflowFocus(liveDoc, pendingPrompts, {
20987
- boundaryTimestamp
21219
+ const writeTurnReport = async (status) => {
21220
+ await writeLocalTurnReport({
21221
+ status: status.type,
21222
+ conversation,
21223
+ turnLog,
21224
+ request: input.request,
21225
+ requestTimestamp: boundaryTimestamp,
21226
+ completed: status.completed,
21227
+ pending: status.pending,
21228
+ error: status.error
20988
21229
  });
20989
- const referentFocus = projectConversationReferentFocus(liveDoc);
20990
- const heapFocus = {
20991
- variableNames: [
20992
- ...workflowFocus.variableNames,
20993
- ...referentFocus.variableNames
20994
- ],
20995
- listNames: [...workflowFocus.listNames, ...referentFocus.listNames],
20996
- entryPaths: [
20997
- ...workflowFocus.entryPaths,
20998
- ...referentFocus.entryPaths,
20999
- ...focusedHeapEntryPathsFromUiContext(promptHeap, input.uiContext)
21000
- ]
21001
- };
21002
- const tools = conversation.environment.getEffects().map((tool) => ({
21003
- name: tool.name,
21004
- description: tool.description,
21005
- className: tool.className,
21006
- static: tool.static,
21007
- ready: tool.ready,
21008
- inputSchema: tool.inputSchema,
21009
- outputSchema: tool.outputSchema
21010
- }));
21011
- const renderedPrompt = promptRenderer({
21012
- domainDocumentation: await conversation.environment.getDomainDocumentation(),
21013
- sessionContext: {
21014
- sandboxId: conversation.environment.sandboxId,
21015
- environmentId: conversation.environment.environmentId,
21016
- domainRevision: conversation.environment.domainRevision,
21017
- uiContext: input.uiContext || null
21018
- },
21019
- heapSummary: projectHeapSummary(asRecord6(liveDoc?.heap), {
21020
- focus: heapFocus
21021
- }),
21022
- fileSummary: projectSessionFileSummary(liveDoc),
21023
- referentSummary: projectConversationReferentSummary(liveDoc),
21024
- loopSummary: projectLoopSummary(liveDoc, pendingPrompts, {
21230
+ };
21231
+ try {
21232
+ while (iteration < maxIterations) {
21233
+ const liveDoc = cloneJson(conversation.environment.document);
21234
+ const promptHeap = normalizeHeapSnapshot2(asRecord6(liveDoc?.heap));
21235
+ const pendingPrompts = filterPromptsByBoundary(
21236
+ liveDoc,
21237
+ getOpenPromptsFromDoc(liveDoc),
21025
21238
  boundaryTimestamp
21026
- }),
21027
- workflowSummary: projectWorkflowSummary(liveDoc, pendingPrompts, {
21239
+ );
21240
+ const workflowFocus = projectWorkflowFocus(liveDoc, pendingPrompts, {
21028
21241
  boundaryTimestamp
21029
- }),
21030
- tools,
21031
- checkpoint: latestCheckpoint
21032
- });
21033
- const systemPrompt = renderedPrompt.prompt;
21034
- await emitProgress(onProgress, {
21035
- phase: "prompt",
21036
- status: "passed",
21037
- scenarioId: conversation.label,
21038
- stepId: turnId,
21039
- iteration: iteration + 1,
21040
- templateId: renderedPrompt.templateId,
21041
- templateVersion: renderedPrompt.templateVersion,
21042
- title: "Rendered harness prompt",
21043
- message: `${systemPrompt.split("\n").length} lines`,
21044
- data: {
21242
+ });
21243
+ const referentFocus = projectConversationReferentFocus(liveDoc);
21244
+ const heapFocus = {
21245
+ variableNames: [
21246
+ ...workflowFocus.variableNames,
21247
+ ...referentFocus.variableNames
21248
+ ],
21249
+ listNames: [...workflowFocus.listNames, ...referentFocus.listNames],
21250
+ entryPaths: [
21251
+ ...workflowFocus.entryPaths,
21252
+ ...referentFocus.entryPaths,
21253
+ ...focusedHeapEntryPathsFromUiContext(promptHeap, input.uiContext)
21254
+ ]
21255
+ };
21256
+ const tools = conversation.environment.getEffects().map((tool) => ({
21257
+ name: tool.name,
21258
+ description: tool.description,
21259
+ className: tool.className,
21260
+ static: tool.static,
21261
+ ready: tool.ready,
21262
+ inputSchema: tool.inputSchema,
21263
+ outputSchema: tool.outputSchema
21264
+ }));
21265
+ const renderedPrompt = promptRenderer({
21266
+ domainDocumentation: await conversation.environment.getDomainDocumentation(),
21267
+ sessionContext: {
21268
+ sandboxId: conversation.environment.sandboxId,
21269
+ environmentId: conversation.environment.environmentId,
21270
+ domainRevision: conversation.environment.domainRevision,
21271
+ uiContext: input.uiContext || null
21272
+ },
21273
+ heapSummary: projectHeapSummary(asRecord6(liveDoc?.heap), {
21274
+ focus: heapFocus
21275
+ }),
21276
+ fileSummary: projectSessionFileSummary(liveDoc),
21277
+ referentSummary: projectConversationReferentSummary(liveDoc),
21278
+ loopSummary: projectLoopSummary(liveDoc, pendingPrompts, {
21279
+ boundaryTimestamp
21280
+ }),
21281
+ workflowSummary: projectWorkflowSummary(liveDoc, pendingPrompts, {
21282
+ boundaryTimestamp
21283
+ }),
21284
+ tools,
21285
+ checkpoint: latestCheckpoint
21286
+ });
21287
+ const systemPrompt = renderedPrompt.prompt;
21288
+ await emitProgress(onProgress, {
21289
+ phase: "prompt",
21290
+ status: "passed",
21291
+ scenarioId: conversation.label,
21292
+ stepId: turnId,
21293
+ iteration: iteration + 1,
21045
21294
  templateId: renderedPrompt.templateId,
21046
21295
  templateVersion: renderedPrompt.templateVersion,
21047
- templateHash: renderedPrompt.templateHash,
21048
- promptInstanceHash: renderedPrompt.promptInstanceHash,
21049
- prompt: systemPrompt
21050
- }
21051
- });
21052
- const request = iteration === 0 ? input.request : continuationRenderer(
21053
- buildContinuationPreview(latestCheckpoint, noProgressCount)
21054
- ).instruction;
21055
- await emitProgress(onProgress, {
21056
- phase: "generation",
21057
- status: "running",
21058
- scenarioId: conversation.label,
21059
- stepId: turnId,
21060
- iteration: iteration + 1,
21061
- templateId: renderedPrompt.templateId,
21062
- templateVersion: renderedPrompt.templateVersion,
21063
- title: iteration === 0 ? "Generating agent response" : "Generating continuation",
21064
- message: request
21065
- });
21066
- const emittedGeneratedReasoningLines = /* @__PURE__ */ new Set();
21067
- let generatedReasoningBuffer = "";
21068
- const emitGeneratedReasoningLine = async (line) => {
21069
- if (emittedGeneratedReasoningLines.has(line)) return;
21070
- emittedGeneratedReasoningLines.add(line);
21296
+ title: "Rendered harness prompt",
21297
+ message: `${systemPrompt.split("\n").length} lines`,
21298
+ data: {
21299
+ templateId: renderedPrompt.templateId,
21300
+ templateVersion: renderedPrompt.templateVersion,
21301
+ templateHash: renderedPrompt.templateHash,
21302
+ promptInstanceHash: renderedPrompt.promptInstanceHash,
21303
+ prompt: systemPrompt
21304
+ }
21305
+ });
21306
+ const request = iteration === 0 ? input.request : continuationRenderer(
21307
+ buildContinuationPreview(latestCheckpoint, noProgressCount)
21308
+ ).instruction;
21071
21309
  await emitProgress(onProgress, {
21072
21310
  phase: "generation",
21073
21311
  status: "running",
@@ -21076,190 +21314,231 @@ function createAgentEvalHarness(options) {
21076
21314
  iteration: iteration + 1,
21077
21315
  templateId: renderedPrompt.templateId,
21078
21316
  templateVersion: renderedPrompt.templateVersion,
21079
- title: "Generated reasoning comment",
21080
- message: line,
21081
- data: { source: "generated_code_comment" }
21317
+ title: iteration === 0 ? "Generating agent response" : "Generating continuation",
21318
+ message: request
21082
21319
  });
21083
- };
21084
- const generation = await withTimeout2(
21085
- generateTurnWithRepair(options.generator, {
21086
- systemPrompt,
21087
- history: buildHistory(conversation.history),
21088
- request,
21089
- attempt: 1,
21090
- tools,
21091
- onReplyDelta: async (delta) => {
21092
- await emitProgress(onProgress, {
21093
- phase: "generation",
21094
- status: "running",
21095
- scenarioId: conversation.label,
21096
- stepId: turnId,
21097
- iteration: iteration + 1,
21098
- templateId: renderedPrompt.templateId,
21099
- templateVersion: renderedPrompt.templateVersion,
21100
- title: "Generated text reply delta",
21101
- message: delta,
21102
- data: { delta }
21103
- });
21104
- },
21105
- onCodeDelta: async (delta) => {
21106
- const parsed = consumeGranularReasoningOnlyChunk(
21107
- generatedReasoningBuffer,
21108
- delta
21109
- );
21110
- generatedReasoningBuffer = parsed.buffer;
21111
- for (const line of parsed.reasoningLines) {
21112
- await emitGeneratedReasoningLine(line);
21113
- }
21114
- },
21115
- usageContext: {
21116
- sandboxId: conversation.environment.sandboxId,
21117
- environmentId: conversation.environment.environmentId,
21118
- sessionId: conversation.environment.sessionId,
21119
- subjectId: conversation.environment.subjectId,
21120
- permissionProfileId: conversation.environment.permissionProfileId
21121
- }
21122
- }),
21123
- chatTimeoutMs,
21124
- `chat generation for ${conversation.label} iteration ${iteration + 1}`
21125
- );
21126
- await emitProgress(onProgress, {
21127
- phase: "generation",
21128
- status: "passed",
21129
- scenarioId: conversation.label,
21130
- stepId: turnId,
21131
- iteration: iteration + 1,
21132
- templateId: renderedPrompt.templateId,
21133
- templateVersion: renderedPrompt.templateVersion,
21134
- title: generation.code ? "Generated job code" : "Generated text reply",
21135
- message: generation.code || generation.reply || "",
21136
- data: {
21137
- reply: generation.reply,
21138
- code: generation.code,
21139
- attempts: generation.generationAttempts,
21140
- usage: tokenUsageForGenerationOutput(generation)
21141
- }
21142
- });
21143
- const generatedReasoningLines = generation.code ? consumeGranularReasoningOnlyChunk("", generation.code, {
21144
- final: true
21145
- }).reasoningLines : [];
21146
- for (const line of generatedReasoningLines) {
21147
- await emitGeneratedReasoningLine(line);
21148
- }
21149
- const iterationLog = {
21150
- iteration: iteration + 1,
21151
- request,
21152
- systemPrompt,
21153
- templateId: renderedPrompt.templateId,
21154
- templateVersion: renderedPrompt.templateVersion,
21155
- templateHash: renderedPrompt.templateHash,
21156
- promptInstanceHash: renderedPrompt.promptInstanceHash,
21157
- generationReply: generation.reply,
21158
- generatedCode: generation.code,
21159
- rawGeneration: generation.raw,
21160
- generationAttempts: generation.generationAttempts,
21161
- tokenUsage: tokenUsageForGenerationOutput(generation)
21162
- };
21163
- turnLog.iterations.push(iterationLog);
21164
- await writeJson(
21165
- path__default.default.join(turnDir, `iteration-${iteration + 1}-generation.json`),
21166
- generation
21167
- );
21168
- if (!generation.code) {
21169
- const responseText2 = generation.reply?.trim() || "Done.";
21170
- conversation.history.push({ role: "assistant", content: responseText2 });
21171
- const completed = {
21172
- conversation,
21173
- request: input.request,
21174
- turnDir,
21175
- responseText: responseText2,
21176
- terminalKind: "reply",
21177
- actionSummary: [],
21178
- promptInteractions: [],
21179
- verification: null,
21180
- result: generation.reply?.trim() || responseText2
21181
- };
21182
- if (input.verification) {
21183
- completed.verification = await runInspection(
21184
- conversation,
21185
- input.verification,
21186
- completed,
21187
- turnDir
21188
- );
21189
- }
21190
- iterationLog.responseText = responseText2;
21191
- iterationLog.terminalKind = "reply";
21192
- iterationLog.actionSummary = [];
21193
- iterationLog.promptInteractions = [];
21194
- iterationLog.result = completed.result;
21195
- turnLog.completed = {
21196
- responseText: responseText2,
21197
- terminalKind: "reply",
21198
- actionSummary: [],
21199
- promptInteractions: [],
21200
- result: completed.result
21320
+ const emittedGeneratedReasoningLines = /* @__PURE__ */ new Set();
21321
+ let generatedReasoningBuffer = "";
21322
+ const emitGeneratedReasoningLine = async (line) => {
21323
+ if (emittedGeneratedReasoningLines.has(line)) return;
21324
+ emittedGeneratedReasoningLines.add(line);
21325
+ await emitProgress(onProgress, {
21326
+ phase: "generation",
21327
+ status: "running",
21328
+ scenarioId: conversation.label,
21329
+ stepId: turnId,
21330
+ iteration: iteration + 1,
21331
+ templateId: renderedPrompt.templateId,
21332
+ templateVersion: renderedPrompt.templateVersion,
21333
+ title: "Generated reasoning comment",
21334
+ message: line,
21335
+ data: { source: "generated_code_comment" }
21336
+ });
21201
21337
  };
21202
- await writeJson(path__default.default.join(turnDir, "result.json"), completed);
21338
+ const generationStartedAt = Date.now();
21339
+ const generation = await withTimeout2(
21340
+ generateTurnWithRepair(options.generator, {
21341
+ systemPrompt,
21342
+ history: buildHistory(conversation.history),
21343
+ request,
21344
+ attempt: 1,
21345
+ tools,
21346
+ onReplyDelta: async (delta) => {
21347
+ await emitProgress(onProgress, {
21348
+ phase: "generation",
21349
+ status: "running",
21350
+ scenarioId: conversation.label,
21351
+ stepId: turnId,
21352
+ iteration: iteration + 1,
21353
+ templateId: renderedPrompt.templateId,
21354
+ templateVersion: renderedPrompt.templateVersion,
21355
+ title: "Generated text reply delta",
21356
+ message: delta,
21357
+ data: { delta }
21358
+ });
21359
+ },
21360
+ onCodeDelta: async (delta) => {
21361
+ const parsed = consumeGranularReasoningOnlyChunk(
21362
+ generatedReasoningBuffer,
21363
+ delta
21364
+ );
21365
+ generatedReasoningBuffer = parsed.buffer;
21366
+ for (const line of parsed.reasoningLines) {
21367
+ await emitGeneratedReasoningLine(line);
21368
+ }
21369
+ },
21370
+ usageContext: {
21371
+ sandboxId: conversation.environment.sandboxId,
21372
+ environmentId: conversation.environment.environmentId,
21373
+ sessionId: conversation.environment.sessionId,
21374
+ subjectId: conversation.environment.subjectId,
21375
+ permissionProfileId: conversation.environment.permissionProfileId
21376
+ }
21377
+ }),
21378
+ chatTimeoutMs,
21379
+ `chat generation for ${conversation.label} iteration ${iteration + 1}`
21380
+ );
21381
+ const generationDurationMs = Date.now() - generationStartedAt;
21203
21382
  await emitProgress(onProgress, {
21204
- phase: "step",
21383
+ phase: "generation",
21205
21384
  status: "passed",
21206
21385
  scenarioId: conversation.label,
21207
21386
  stepId: turnId,
21208
- title: "Step completed with text reply",
21209
- message: responseText2,
21210
- data: completed
21387
+ iteration: iteration + 1,
21388
+ templateId: renderedPrompt.templateId,
21389
+ templateVersion: renderedPrompt.templateVersion,
21390
+ title: generation.code ? "Generated job code" : "Generated text reply",
21391
+ message: generation.code || generation.reply || "",
21392
+ data: {
21393
+ reply: generation.reply,
21394
+ code: generation.code,
21395
+ attempts: generation.generationAttempts,
21396
+ usage: tokenUsageForGenerationOutput(generation)
21397
+ }
21211
21398
  });
21212
- return completed;
21213
- }
21214
- const session = conversation.environment;
21215
- const job = await session.submitJob(generation.code, {
21216
- agent: {
21217
- userRequest: input.request,
21218
- generationRequest: request,
21219
- systemPrompt,
21220
- history: buildHistory(conversation.history),
21221
- scenarioLabel: conversation.label,
21222
- turnId,
21399
+ const generatedReasoningLines = generation.code ? consumeGranularReasoningOnlyChunk("", generation.code, {
21400
+ final: true
21401
+ }).reasoningLines : [];
21402
+ for (const line of generatedReasoningLines) {
21403
+ await emitGeneratedReasoningLine(line);
21404
+ }
21405
+ const iterationLog = {
21223
21406
  iteration: iteration + 1,
21224
- tools,
21407
+ request,
21408
+ generationDurationMs,
21409
+ systemPrompt,
21410
+ templateId: renderedPrompt.templateId,
21411
+ templateVersion: renderedPrompt.templateVersion,
21412
+ templateHash: renderedPrompt.templateHash,
21413
+ promptInstanceHash: renderedPrompt.promptInstanceHash,
21225
21414
  generationReply: generation.reply,
21415
+ generatedCode: generation.code,
21226
21416
  rawGeneration: generation.raw,
21227
- repairIssues: generation.generationAttempts?.flatMap(
21228
- (attempt) => attempt.repairIssues || []
21229
- )
21230
- }
21231
- });
21232
- await emitProgress(onProgress, {
21233
- phase: "job",
21234
- status: "running",
21235
- scenarioId: conversation.label,
21236
- stepId: turnId,
21237
- iteration: iteration + 1,
21238
- jobId: job.id,
21239
- templateId: renderedPrompt.templateId,
21240
- templateVersion: renderedPrompt.templateVersion,
21241
- title: "Submitted Granular job",
21242
- message: job.id,
21243
- data: { code: generation.code }
21244
- });
21245
- const outcome = await waitForJobOutcome({
21246
- environment: conversation.environment,
21247
- job,
21248
- boundaryTimestamp,
21249
- timeoutMs: jobTimeoutMs,
21250
- pollIntervalMs,
21251
- onProgress: (event) => void onProgress?.(event),
21252
- progressContext: {
21417
+ generationAttempts: generation.generationAttempts,
21418
+ tokenUsage: tokenUsageForGenerationOutput(generation)
21419
+ };
21420
+ turnLog.iterations.push(iterationLog);
21421
+ await writeJson(
21422
+ path__default.default.join(turnDir, `iteration-${iteration + 1}-generation.json`),
21423
+ generation
21424
+ );
21425
+ if (!generation.code) {
21426
+ const responseText2 = generation.reply?.trim() || "Done.";
21427
+ conversation.history.push({ role: "assistant", content: responseText2 });
21428
+ const completed = {
21429
+ conversation,
21430
+ request: input.request,
21431
+ turnDir,
21432
+ responseText: responseText2,
21433
+ terminalKind: "reply",
21434
+ actionSummary: [],
21435
+ promptInteractions: [],
21436
+ verification: null,
21437
+ result: generation.reply?.trim() || responseText2
21438
+ };
21439
+ if (input.verification) {
21440
+ completed.verification = await runInspection(
21441
+ conversation,
21442
+ input.verification,
21443
+ completed,
21444
+ turnDir
21445
+ );
21446
+ }
21447
+ iterationLog.responseText = responseText2;
21448
+ iterationLog.terminalKind = "reply";
21449
+ iterationLog.actionSummary = [];
21450
+ iterationLog.promptInteractions = [];
21451
+ iterationLog.result = completed.result;
21452
+ turnLog.completed = {
21453
+ responseText: responseText2,
21454
+ terminalKind: "reply",
21455
+ actionSummary: [],
21456
+ promptInteractions: [],
21457
+ result: completed.result
21458
+ };
21459
+ await writeJson(path__default.default.join(turnDir, "result.json"), completed);
21460
+ await writeTurnReport({ type: "completed", completed });
21461
+ await emitProgress(onProgress, {
21462
+ phase: "step",
21463
+ status: "passed",
21464
+ scenarioId: conversation.label,
21465
+ stepId: turnId,
21466
+ title: "Step completed with text reply",
21467
+ message: responseText2,
21468
+ data: completed
21469
+ });
21470
+ return completed;
21471
+ }
21472
+ const session = conversation.environment;
21473
+ const job = await session.submitJob(generation.code, {
21474
+ agent: {
21475
+ userRequest: input.request,
21476
+ generationRequest: request,
21477
+ systemPrompt,
21478
+ history: buildHistory(conversation.history),
21479
+ scenarioLabel: conversation.label,
21480
+ turnId,
21481
+ iteration: iteration + 1,
21482
+ tools,
21483
+ generationReply: generation.reply,
21484
+ rawGeneration: generation.raw,
21485
+ repairIssues: generation.generationAttempts?.flatMap(
21486
+ (attempt) => attempt.repairIssues || []
21487
+ )
21488
+ }
21489
+ });
21490
+ await emitProgress(onProgress, {
21491
+ phase: "job",
21492
+ status: "running",
21253
21493
  scenarioId: conversation.label,
21254
21494
  stepId: turnId,
21255
21495
  iteration: iteration + 1,
21496
+ jobId: job.id,
21256
21497
  templateId: renderedPrompt.templateId,
21257
- templateVersion: renderedPrompt.templateVersion
21258
- }
21259
- });
21260
- if (outcome.kind === "prompt") {
21261
- if (!autoAnswerPrompts) {
21262
- return {
21498
+ templateVersion: renderedPrompt.templateVersion,
21499
+ title: "Submitted Granular job",
21500
+ message: job.id,
21501
+ data: { code: generation.code }
21502
+ });
21503
+ const outcome = await waitForJobOutcome({
21504
+ environment: conversation.environment,
21505
+ job,
21506
+ boundaryTimestamp,
21507
+ timeoutMs: jobTimeoutMs,
21508
+ pollIntervalMs,
21509
+ onProgress: (event) => void onProgress?.(event),
21510
+ progressContext: {
21511
+ scenarioId: conversation.label,
21512
+ stepId: turnId,
21513
+ iteration: iteration + 1,
21514
+ templateId: renderedPrompt.templateId,
21515
+ templateVersion: renderedPrompt.templateVersion
21516
+ }
21517
+ });
21518
+ if (outcome.kind === "prompt") {
21519
+ if (!autoAnswerPrompts) {
21520
+ const pending2 = {
21521
+ conversation,
21522
+ request: input.request,
21523
+ turnDir,
21524
+ boundaryTimestamp,
21525
+ finalCode: generation.code,
21526
+ finalReply: generation.reply?.trim() || "",
21527
+ stdout: outcome.stdout,
21528
+ stderr: outcome.stderr,
21529
+ prompts: outcome.prompts,
21530
+ promptInteractions: [],
21531
+ job
21532
+ };
21533
+ await writeTurnReport({ type: "pending", pending: pending2 });
21534
+ return pending2;
21535
+ }
21536
+ if (!input.human) {
21537
+ throw new Error(
21538
+ "This turn reached a human prompt but no responder was provided"
21539
+ );
21540
+ }
21541
+ let pending = {
21263
21542
  conversation,
21264
21543
  request: input.request,
21265
21544
  turnDir,
@@ -21272,187 +21551,180 @@ function createAgentEvalHarness(options) {
21272
21551
  promptInteractions: [],
21273
21552
  job
21274
21553
  };
21554
+ while ("prompts" in pending) {
21555
+ const resumed = await resumePendingTurn(pending, input.human);
21556
+ if ("prompts" in resumed) {
21557
+ pending = resumed;
21558
+ continue;
21559
+ }
21560
+ if (input.verification) {
21561
+ resumed.verification = await runInspection(
21562
+ conversation,
21563
+ input.verification,
21564
+ resumed,
21565
+ turnDir
21566
+ );
21567
+ }
21568
+ turnLog.completed = {
21569
+ responseText: resumed.responseText,
21570
+ terminalKind: resumed.terminalKind,
21571
+ actionSummary: resumed.actionSummary,
21572
+ promptInteractions: resumed.promptInteractions,
21573
+ result: resumed.result
21574
+ };
21575
+ await writeTurnReport({ type: "completed", completed: resumed });
21576
+ return resumed;
21577
+ }
21275
21578
  }
21276
- if (!input.human) {
21579
+ if (outcome.kind !== "completed") {
21277
21580
  throw new Error(
21278
- "This turn reached a human prompt but no responder was provided"
21581
+ "Unexpected non-completed outcome after prompt handling"
21279
21582
  );
21280
21583
  }
21281
- let pending = {
21282
- conversation,
21283
- request: input.request,
21284
- turnDir,
21285
- boundaryTimestamp,
21286
- finalCode: generation.code,
21287
- finalReply: generation.reply?.trim() || "",
21584
+ await sleep2(350);
21585
+ const settledLiveDoc = cloneJson(
21586
+ conversation.environment.document
21587
+ );
21588
+ const sessionHeap = normalizeHeapSnapshot2(asRecord6(settledLiveDoc?.heap));
21589
+ const presentation = resolveJobPresentation({
21590
+ jobId: job.id,
21591
+ result: outcome.result,
21288
21592
  stdout: outcome.stdout,
21289
- stderr: outcome.stderr,
21290
- prompts: outcome.prompts,
21291
- promptInteractions: [],
21292
- job
21593
+ agentMessages: getJobAgentMessages(settledLiveDoc, job.id),
21594
+ sessionHeap
21595
+ });
21596
+ const responseText = presentation.responseText || generation.reply?.trim() || "Done.";
21597
+ const verifierSnapshot = createHarnessVerifierSnapshot({
21598
+ finalCode: generation.code,
21599
+ resultPreview: JSON.stringify(outcome.result, null, 2),
21600
+ liveDoc: settledLiveDoc,
21601
+ projectionOptions: { boundaryTimestamp }
21602
+ });
21603
+ const continuation = evaluateContinuation({
21604
+ iteration,
21605
+ budgets: controllerBudgets,
21606
+ baselineClosureId,
21607
+ currentClosureId: getCurrentClosureId(settledLiveDoc),
21608
+ liveDoc: settledLiveDoc,
21609
+ pendingPrompts: filterPromptsByBoundary(
21610
+ settledLiveDoc,
21611
+ getOpenPromptsFromDoc(settledLiveDoc),
21612
+ boundaryTimestamp
21613
+ ),
21614
+ projectionOptions: { boundaryTimestamp },
21615
+ latestResponseText: responseText,
21616
+ previousSnapshot,
21617
+ currentSnapshot: verifierSnapshot,
21618
+ previousNoProgressCount: noProgressCount
21619
+ });
21620
+ latestCheckpoint = {
21621
+ iteration: iteration + 1,
21622
+ latestJobStatus: "succeeded",
21623
+ latestJobResult: JSON.stringify(outcome.result, null, 2),
21624
+ latestActionSummary: getActionSummary(settledLiveDoc, job.id),
21625
+ controllerOutcome: continuation.outcome,
21626
+ controllerReason: continuation.reason,
21627
+ noProgressCount: continuation.nextNoProgressCount
21293
21628
  };
21294
- while ("prompts" in pending) {
21295
- const resumed = await resumePendingTurn(pending, input.human);
21296
- if ("prompts" in resumed) {
21297
- pending = resumed;
21298
- continue;
21629
+ await emitProgress(onProgress, {
21630
+ phase: "continuation",
21631
+ status: continuation.shouldContinue ? "running" : "passed",
21632
+ scenarioId: conversation.label,
21633
+ stepId: turnId,
21634
+ iteration: iteration + 1,
21635
+ jobId: job.id,
21636
+ templateId: renderedPrompt.templateId,
21637
+ templateVersion: renderedPrompt.templateVersion,
21638
+ title: continuation.shouldContinue ? "Harness requested another loop" : "Harness accepted completion",
21639
+ message: `${continuation.reason}; ${continuation.outcome}`,
21640
+ data: {
21641
+ continuation,
21642
+ checkpoint: latestCheckpoint,
21643
+ verifierSnapshot
21644
+ }
21645
+ });
21646
+ previousSnapshot = verifierSnapshot;
21647
+ noProgressCount = continuation.nextNoProgressCount;
21648
+ conversation.history.push({
21649
+ role: "assistant",
21650
+ content: responseText,
21651
+ code: generation.code,
21652
+ jobStatus: "succeeded",
21653
+ jobResultPreview: JSON.stringify(outcome.result, null, 2)
21654
+ });
21655
+ await writeJson(
21656
+ path__default.default.join(turnDir, `iteration-${iteration + 1}-result.json`),
21657
+ {
21658
+ responseText,
21659
+ continuation,
21660
+ actionSummary: latestCheckpoint.latestActionSummary,
21661
+ result: outcome.result
21299
21662
  }
21663
+ );
21664
+ iterationLog.responseText = responseText;
21665
+ iterationLog.terminalKind = getCurrentClosureId(settledLiveDoc) ? "closure" : "reply";
21666
+ iterationLog.actionSummary = latestCheckpoint.latestActionSummary || [];
21667
+ iterationLog.promptInteractions = [];
21668
+ iterationLog.continuation = continuation;
21669
+ iterationLog.result = outcome.result;
21670
+ if (!continuation.shouldContinue) {
21671
+ const completed = {
21672
+ conversation,
21673
+ request: input.request,
21674
+ turnDir,
21675
+ responseText,
21676
+ terminalKind: getCurrentClosureId(settledLiveDoc) ? "closure" : "reply",
21677
+ finalCode: generation.code,
21678
+ actionSummary: latestCheckpoint.latestActionSummary || [],
21679
+ promptInteractions: [],
21680
+ verification: null,
21681
+ result: outcome.result
21682
+ };
21300
21683
  if (input.verification) {
21301
- resumed.verification = await runInspection(
21684
+ completed.verification = await runInspection(
21302
21685
  conversation,
21303
21686
  input.verification,
21304
- resumed,
21687
+ completed,
21305
21688
  turnDir
21306
21689
  );
21307
21690
  }
21308
21691
  turnLog.completed = {
21309
- responseText: resumed.responseText,
21310
- terminalKind: resumed.terminalKind,
21311
- actionSummary: resumed.actionSummary,
21312
- promptInteractions: resumed.promptInteractions,
21313
- result: resumed.result
21692
+ responseText,
21693
+ terminalKind: completed.terminalKind,
21694
+ actionSummary: completed.actionSummary,
21695
+ promptInteractions: [],
21696
+ result: outcome.result
21314
21697
  };
21315
- return resumed;
21698
+ await writeJson(path__default.default.join(turnDir, "result.json"), completed);
21699
+ await writeTurnReport({ type: "completed", completed });
21700
+ await emitProgress(onProgress, {
21701
+ phase: "step",
21702
+ status: "passed",
21703
+ scenarioId: conversation.label,
21704
+ stepId: turnId,
21705
+ iteration: iteration + 1,
21706
+ jobId: job.id,
21707
+ title: "Step completed",
21708
+ message: responseText,
21709
+ data: completed
21710
+ });
21711
+ return completed;
21316
21712
  }
21713
+ iteration += 1;
21317
21714
  }
21318
- if (outcome.kind !== "completed") {
21319
- throw new Error(
21320
- "Unexpected non-completed outcome after prompt handling"
21321
- );
21322
- }
21323
- await sleep2(350);
21324
- const settledLiveDoc = cloneJson(
21325
- conversation.environment.document
21326
- );
21327
- const sessionHeap = normalizeHeapSnapshot2(asRecord6(settledLiveDoc?.heap));
21328
- const presentation = resolveJobPresentation({
21329
- jobId: job.id,
21330
- result: outcome.result,
21331
- stdout: outcome.stdout,
21332
- agentMessages: getJobAgentMessages(settledLiveDoc, job.id),
21333
- sessionHeap
21334
- });
21335
- const responseText = presentation.responseText || generation.reply?.trim() || "Done.";
21336
- const verifierSnapshot = createHarnessVerifierSnapshot({
21337
- finalCode: generation.code,
21338
- resultPreview: JSON.stringify(outcome.result, null, 2),
21339
- liveDoc: settledLiveDoc,
21340
- projectionOptions: { boundaryTimestamp }
21341
- });
21342
- const continuation = evaluateContinuation({
21343
- iteration,
21344
- budgets: controllerBudgets,
21345
- baselineClosureId,
21346
- currentClosureId: getCurrentClosureId(settledLiveDoc),
21347
- liveDoc: settledLiveDoc,
21348
- pendingPrompts: filterPromptsByBoundary(
21349
- settledLiveDoc,
21350
- getOpenPromptsFromDoc(settledLiveDoc),
21351
- boundaryTimestamp
21352
- ),
21353
- projectionOptions: { boundaryTimestamp },
21354
- latestResponseText: responseText,
21355
- previousSnapshot,
21356
- currentSnapshot: verifierSnapshot,
21357
- previousNoProgressCount: noProgressCount
21358
- });
21359
- latestCheckpoint = {
21360
- iteration: iteration + 1,
21361
- latestJobStatus: "succeeded",
21362
- latestJobResult: JSON.stringify(outcome.result, null, 2),
21363
- latestActionSummary: getActionSummary(settledLiveDoc, job.id),
21364
- controllerOutcome: continuation.outcome,
21365
- controllerReason: continuation.reason,
21366
- noProgressCount: continuation.nextNoProgressCount
21367
- };
21368
- await emitProgress(onProgress, {
21369
- phase: "continuation",
21370
- status: continuation.shouldContinue ? "running" : "passed",
21371
- scenarioId: conversation.label,
21372
- stepId: turnId,
21373
- iteration: iteration + 1,
21374
- jobId: job.id,
21375
- templateId: renderedPrompt.templateId,
21376
- templateVersion: renderedPrompt.templateVersion,
21377
- title: continuation.shouldContinue ? "Harness requested another loop" : "Harness accepted completion",
21378
- message: `${continuation.reason}; ${continuation.outcome}`,
21379
- data: {
21380
- continuation,
21381
- checkpoint: latestCheckpoint,
21382
- verifierSnapshot
21383
- }
21384
- });
21385
- previousSnapshot = verifierSnapshot;
21386
- noProgressCount = continuation.nextNoProgressCount;
21387
- conversation.history.push({
21388
- role: "assistant",
21389
- content: responseText,
21390
- code: generation.code,
21391
- jobStatus: "succeeded",
21392
- jobResultPreview: JSON.stringify(outcome.result, null, 2)
21393
- });
21394
- await writeJson(
21395
- path__default.default.join(turnDir, `iteration-${iteration + 1}-result.json`),
21396
- {
21397
- responseText,
21398
- continuation,
21399
- actionSummary: latestCheckpoint.latestActionSummary,
21400
- result: outcome.result
21401
- }
21715
+ throw new Error(
21716
+ `Turn exceeded iteration budget (${maxIterations}) for ${conversation.label}`
21402
21717
  );
21403
- iterationLog.responseText = responseText;
21404
- iterationLog.terminalKind = getCurrentClosureId(settledLiveDoc) ? "closure" : "reply";
21405
- iterationLog.actionSummary = latestCheckpoint.latestActionSummary || [];
21406
- iterationLog.promptInteractions = [];
21407
- iterationLog.continuation = continuation;
21408
- iterationLog.result = outcome.result;
21409
- if (!continuation.shouldContinue) {
21410
- const completed = {
21411
- conversation,
21412
- request: input.request,
21413
- turnDir,
21414
- responseText,
21415
- terminalKind: getCurrentClosureId(settledLiveDoc) ? "closure" : "reply",
21416
- finalCode: generation.code,
21417
- actionSummary: latestCheckpoint.latestActionSummary || [],
21418
- promptInteractions: [],
21419
- verification: null,
21420
- result: outcome.result
21421
- };
21422
- if (input.verification) {
21423
- completed.verification = await runInspection(
21424
- conversation,
21425
- input.verification,
21426
- completed,
21427
- turnDir
21428
- );
21429
- }
21430
- turnLog.completed = {
21431
- responseText,
21432
- terminalKind: completed.terminalKind,
21433
- actionSummary: completed.actionSummary,
21434
- promptInteractions: [],
21435
- result: outcome.result
21436
- };
21437
- await writeJson(path__default.default.join(turnDir, "result.json"), completed);
21438
- await emitProgress(onProgress, {
21439
- phase: "step",
21440
- status: "passed",
21441
- scenarioId: conversation.label,
21442
- stepId: turnId,
21443
- iteration: iteration + 1,
21444
- jobId: job.id,
21445
- title: "Step completed",
21446
- message: responseText,
21447
- data: completed
21448
- });
21449
- return completed;
21718
+ } catch (error) {
21719
+ const message = error instanceof Error ? error.message : String(error);
21720
+ const latestIteration = latestIterationLog(turnLog);
21721
+ if (latestIteration) {
21722
+ latestIteration.error = message;
21450
21723
  }
21451
- iteration += 1;
21724
+ turnLog.error = message;
21725
+ await writeTurnReport({ type: "failed", error: message });
21726
+ throw error;
21452
21727
  }
21453
- throw new Error(
21454
- `Turn exceeded iteration budget (${maxIterations}) for ${conversation.label}`
21455
- );
21456
21728
  }
21457
21729
  return {
21458
21730
  artifactDir,
@@ -21487,6 +21759,7 @@ function createAgentTester(options) {
21487
21759
  const harness = createAgentEvalHarness({
21488
21760
  granular,
21489
21761
  environmentId: resolvedEnvironmentId || void 0,
21762
+ local: options.local,
21490
21763
  openEnvironment: async ({ clientId }) => {
21491
21764
  if (resolvedEnvironmentId) {
21492
21765
  return granular.createSession({