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