@granular-software/sdk 0.4.41 → 0.4.42

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.
@@ -1,4 +1,4 @@
1
- import { writeFile, mkdir } from 'fs/promises';
1
+ import { readFile, writeFile, mkdir } from 'fs/promises';
2
2
  import path from 'path';
3
3
  import OpenAI from 'openai';
4
4
  import * as Automerge from '@automerge/automerge';
@@ -16060,7 +16060,275 @@ var Granular = class _Granular {
16060
16060
  }
16061
16061
  };
16062
16062
 
16063
+ // src/agent-harness-templates/action-presentation/0.1.0/manifest.json
16064
+ var manifest_default = {
16065
+ id: "action-presentation",
16066
+ version: "0.1.0",
16067
+ status: "candidate",
16068
+ owner: "granular",
16069
+ createdAt: "2026-05-25T00:00:00.000Z",
16070
+ changelog: "Candidate harness template focused on action-request detection and mutation result presentation. It exists to harden cases where the model answers with a terse completion such as Done instead of using tools and reporting the target/action/result.",
16071
+ promptBuilder: "buildGranularAgentSystemPrompt:action-presentation",
16072
+ continuationBuilder: "buildContinuationInstruction:action-presentation",
16073
+ modelOutputInstruction: "agent-evals:modelOutputInstruction",
16074
+ codeReviewPolicy: "reviewGeneratedJobCode",
16075
+ defaultModel: "gpt-5.4",
16076
+ modelMatrix: ["gpt-5.4"],
16077
+ temperature: 0,
16078
+ compatibility: {
16079
+ minSdkVersion: "0.4.40",
16080
+ capabilities: [
16081
+ "executeCode",
16082
+ "readEntities",
16083
+ "workflowHelpers",
16084
+ "savedData",
16085
+ "showRecords"
16086
+ ]
16087
+ },
16088
+ evalGates: {
16089
+ requiredSuites: [
16090
+ "agent-harness-hardening",
16091
+ "agent-harness-runtime-e2e",
16092
+ "agent-production-readiness-e2e"
16093
+ ],
16094
+ criticalBuckets: [
16095
+ "relationship-traversal",
16096
+ "cross-turn-reference",
16097
+ "ambiguous-target-choice",
16098
+ "confirmation-gated-mutation",
16099
+ "denied-action-refusal",
16100
+ "mutation-presentation"
16101
+ ],
16102
+ maxRegressionPct: 0,
16103
+ minPassK: {
16104
+ "critical-mutation": 0.9,
16105
+ "permission-boundary": 1
16106
+ }
16107
+ }
16108
+ };
16109
+
16110
+ // src/agent-harness-templates/experimental-compact/0.1.0/manifest.json
16111
+ var manifest_default2 = {
16112
+ id: "experimental-compact",
16113
+ version: "0.1.0",
16114
+ status: "candidate",
16115
+ owner: "granular",
16116
+ createdAt: "2026-05-24T00:00:00.000Z",
16117
+ changelog: "Candidate harness template used for champion/challenger testing. It adds a compact decision discipline section on top of the stable renderer.",
16118
+ promptBuilder: "buildGranularAgentSystemPrompt:experimental-compact",
16119
+ continuationBuilder: "buildContinuationInstruction:experimental-compact",
16120
+ modelOutputInstruction: "agent-evals:modelOutputInstruction",
16121
+ codeReviewPolicy: "reviewGeneratedJobCode",
16122
+ defaultModel: "gpt-5.4",
16123
+ modelMatrix: ["gpt-5.4"],
16124
+ temperature: 0,
16125
+ compatibility: {
16126
+ minSdkVersion: "0.4.40",
16127
+ capabilities: [
16128
+ "executeCode",
16129
+ "readEntities",
16130
+ "workflowHelpers",
16131
+ "savedData",
16132
+ "showRecords"
16133
+ ]
16134
+ },
16135
+ evalGates: {
16136
+ requiredSuites: [
16137
+ "agent-harness-hardening",
16138
+ "agent-harness-runtime-e2e",
16139
+ "agent-production-readiness-e2e"
16140
+ ],
16141
+ criticalBuckets: [
16142
+ "relationship-traversal",
16143
+ "cross-turn-reference",
16144
+ "ambiguous-target-choice",
16145
+ "confirmation-gated-mutation",
16146
+ "denied-action-refusal",
16147
+ "mutation-presentation"
16148
+ ],
16149
+ maxRegressionPct: 0,
16150
+ minPassK: {
16151
+ "critical-mutation": 0.9,
16152
+ "permission-boundary": 1
16153
+ }
16154
+ }
16155
+ };
16156
+
16157
+ // src/agent-harness-templates/stable/1.0.0/manifest.json
16158
+ var manifest_default3 = {
16159
+ id: "stable",
16160
+ version: "1.0.0",
16161
+ status: "stable",
16162
+ owner: "granular",
16163
+ createdAt: "2026-05-24T00:00:00.000Z",
16164
+ changelog: "Baseline template wrapping the existing Granular agent harness prompt, continuation instruction, output contract, and generated-job review policy.",
16165
+ promptBuilder: "buildGranularAgentSystemPrompt",
16166
+ continuationBuilder: "buildContinuationInstruction",
16167
+ modelOutputInstruction: "agent-evals:modelOutputInstruction",
16168
+ codeReviewPolicy: "reviewGeneratedJobCode",
16169
+ defaultModel: "gpt-5.4",
16170
+ modelMatrix: ["gpt-5.4"],
16171
+ temperature: 0,
16172
+ compatibility: {
16173
+ minSdkVersion: "0.4.40",
16174
+ capabilities: [
16175
+ "executeCode",
16176
+ "readEntities",
16177
+ "workflowHelpers",
16178
+ "savedData",
16179
+ "showRecords"
16180
+ ]
16181
+ },
16182
+ evalGates: {
16183
+ requiredSuites: [
16184
+ "agent-harness-hardening",
16185
+ "agent-harness-runtime-e2e",
16186
+ "agent-production-readiness-e2e"
16187
+ ],
16188
+ criticalBuckets: [
16189
+ "relationship-traversal",
16190
+ "cross-turn-reference",
16191
+ "ambiguous-target-choice",
16192
+ "confirmation-gated-mutation",
16193
+ "denied-action-refusal",
16194
+ "mutation-presentation"
16195
+ ],
16196
+ maxRegressionPct: 0,
16197
+ minPassK: {
16198
+ "critical-mutation": 0.9,
16199
+ "permission-boundary": 1
16200
+ }
16201
+ }
16202
+ };
16203
+
16063
16204
  // src/agent-harness.ts
16205
+ var HARNESS_TEMPLATE_STATUSES = [
16206
+ "draft",
16207
+ "candidate",
16208
+ "release-candidate",
16209
+ "stable",
16210
+ "deprecated"
16211
+ ];
16212
+ function requireRecord(value, context) {
16213
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
16214
+ throw new Error(`${context} must be an object.`);
16215
+ }
16216
+ return value;
16217
+ }
16218
+ function requiredString(record, key, context) {
16219
+ const value = record[key];
16220
+ if (typeof value !== "string" || !value.trim()) {
16221
+ throw new Error(`${context}.${key} must be a non-empty string.`);
16222
+ }
16223
+ return value;
16224
+ }
16225
+ function requiredNumber(record, key, context) {
16226
+ const value = record[key];
16227
+ if (typeof value !== "number" || !Number.isFinite(value)) {
16228
+ throw new Error(`${context}.${key} must be a finite number.`);
16229
+ }
16230
+ return value;
16231
+ }
16232
+ function requiredStringArray(record, key, context) {
16233
+ const value = record[key];
16234
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim())) {
16235
+ throw new Error(`${context}.${key} must be an array of non-empty strings.`);
16236
+ }
16237
+ return [...value];
16238
+ }
16239
+ function requiredNumberRecord(record, key, context) {
16240
+ const value = requireRecord(record[key], `${context}.${key}`);
16241
+ const output = {};
16242
+ for (const [entryKey, entryValue] of Object.entries(value)) {
16243
+ if (typeof entryValue !== "number" || !Number.isFinite(entryValue) || entryValue < 0 || entryValue > 1) {
16244
+ throw new Error(
16245
+ `${context}.${key}.${entryKey} must be a number between 0 and 1.`
16246
+ );
16247
+ }
16248
+ output[entryKey] = entryValue;
16249
+ }
16250
+ return output;
16251
+ }
16252
+ function validateHarnessTemplateManifest(value, context = "HarnessTemplateManifest") {
16253
+ const record = requireRecord(value, context);
16254
+ const status = requiredString(record, "status", context);
16255
+ if (!HARNESS_TEMPLATE_STATUSES.includes(status)) {
16256
+ throw new Error(
16257
+ `${context}.status must be one of ${HARNESS_TEMPLATE_STATUSES.join(", ")}.`
16258
+ );
16259
+ }
16260
+ const createdAt = requiredString(record, "createdAt", context);
16261
+ if (Number.isNaN(Date.parse(createdAt))) {
16262
+ throw new Error(`${context}.createdAt must be an ISO timestamp.`);
16263
+ }
16264
+ const compatibility = requireRecord(
16265
+ record.compatibility,
16266
+ `${context}.compatibility`
16267
+ );
16268
+ const evalGates = requireRecord(record.evalGates, `${context}.evalGates`);
16269
+ const maxRegressionPct = requiredNumber(
16270
+ evalGates,
16271
+ "maxRegressionPct",
16272
+ `${context}.evalGates`
16273
+ );
16274
+ if (maxRegressionPct < 0 || maxRegressionPct > 1) {
16275
+ throw new Error(
16276
+ `${context}.evalGates.maxRegressionPct must be between 0 and 1.`
16277
+ );
16278
+ }
16279
+ return {
16280
+ id: requiredString(record, "id", context),
16281
+ version: requiredString(record, "version", context),
16282
+ status,
16283
+ owner: requiredString(record, "owner", context),
16284
+ createdAt,
16285
+ changelog: requiredString(record, "changelog", context),
16286
+ promptBuilder: requiredString(record, "promptBuilder", context),
16287
+ continuationBuilder: requiredString(record, "continuationBuilder", context),
16288
+ modelOutputInstruction: requiredString(
16289
+ record,
16290
+ "modelOutputInstruction",
16291
+ context
16292
+ ),
16293
+ codeReviewPolicy: requiredString(record, "codeReviewPolicy", context),
16294
+ defaultModel: requiredString(record, "defaultModel", context),
16295
+ modelMatrix: requiredStringArray(record, "modelMatrix", context),
16296
+ temperature: requiredNumber(record, "temperature", context),
16297
+ compatibility: {
16298
+ minSdkVersion: requiredString(
16299
+ compatibility,
16300
+ "minSdkVersion",
16301
+ `${context}.compatibility`
16302
+ ),
16303
+ capabilities: requiredStringArray(
16304
+ compatibility,
16305
+ "capabilities",
16306
+ `${context}.compatibility`
16307
+ )
16308
+ },
16309
+ evalGates: {
16310
+ requiredSuites: requiredStringArray(
16311
+ evalGates,
16312
+ "requiredSuites",
16313
+ `${context}.evalGates`
16314
+ ),
16315
+ criticalBuckets: requiredStringArray(
16316
+ evalGates,
16317
+ "criticalBuckets",
16318
+ `${context}.evalGates`
16319
+ ),
16320
+ maxRegressionPct,
16321
+ minPassK: requiredNumberRecord(
16322
+ evalGates,
16323
+ "minPassK",
16324
+ `${context}.evalGates`
16325
+ )
16326
+ }
16327
+ };
16328
+ }
16329
+ function defineHarnessTemplateManifest(value, context) {
16330
+ return validateHarnessTemplateManifest(value, context);
16331
+ }
16064
16332
  function asRecord4(value) {
16065
16333
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
16066
16334
  return value;
@@ -16084,6 +16352,16 @@ function uniqueStrings(values, maxCount) {
16084
16352
  }
16085
16353
  return output;
16086
16354
  }
16355
+ function stableStringify(value) {
16356
+ if (value === null || typeof value !== "object") {
16357
+ return JSON.stringify(value);
16358
+ }
16359
+ if (Array.isArray(value)) {
16360
+ return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
16361
+ }
16362
+ const record = value;
16363
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
16364
+ }
16087
16365
  function renderConstBlock(name, value) {
16088
16366
  return `const ${name} = ${JSON.stringify(value, null, 2)} as const;`;
16089
16367
  }
@@ -18023,6 +18301,156 @@ ${knownFactsBlock}
18023
18301
  [Request]
18024
18302
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18025
18303
  }
18304
+ var STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST = defineHarnessTemplateManifest(
18305
+ manifest_default3,
18306
+ "stable@1.0.0 manifest"
18307
+ );
18308
+ function hashHarnessTemplateValue(value) {
18309
+ return hashString(stableStringify(value)) || "00000000";
18310
+ }
18311
+ function renderStableHarnessPrompt(input) {
18312
+ const prompt = buildGranularAgentSystemPrompt(input);
18313
+ const templateHash = hashHarnessTemplateValue(
18314
+ STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST
18315
+ );
18316
+ return {
18317
+ templateId: STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST.id,
18318
+ templateVersion: STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST.version,
18319
+ templateHash,
18320
+ promptInstanceHash: hashHarnessTemplateValue({
18321
+ templateHash,
18322
+ input,
18323
+ prompt
18324
+ }),
18325
+ prompt
18326
+ };
18327
+ }
18328
+ function renderStableHarnessContinuation(resultPreview) {
18329
+ const instruction = buildContinuationInstruction(resultPreview);
18330
+ const templateHash = hashHarnessTemplateValue(
18331
+ STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST
18332
+ );
18333
+ return {
18334
+ templateId: STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST.id,
18335
+ templateVersion: STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST.version,
18336
+ templateHash,
18337
+ instruction
18338
+ };
18339
+ }
18340
+ var STABLE_AGENT_HARNESS_TEMPLATE = {
18341
+ manifest: STABLE_AGENT_HARNESS_TEMPLATE_MANIFEST,
18342
+ renderPrompt: renderStableHarnessPrompt,
18343
+ renderContinuation: renderStableHarnessContinuation
18344
+ };
18345
+ var EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST = defineHarnessTemplateManifest(
18346
+ manifest_default2,
18347
+ "experimental-compact@0.1.0 manifest"
18348
+ );
18349
+ function renderExperimentalCompactHarnessPrompt(input) {
18350
+ const prompt = `${buildGranularAgentSystemPrompt(input)}
18351
+
18352
+ [Candidate Harness Delta: Compact Decision Discipline]
18353
+ - Prefer the smallest action that satisfies the current request.
18354
+ - When several records could match, ask one structured choice question before mutating.
18355
+ - Before any irreversible or outbound mutation, obtain explicit confirmation unless the policy surface already requires it.
18356
+ - Do not compensate for missing tools with raw network calls, synthetic records, or hidden side channels.
18357
+ - Keep the final user-facing reply focused on what was done, what was not done, and any remaining blocker.`;
18358
+ const templateHash = hashHarnessTemplateValue(
18359
+ EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST
18360
+ );
18361
+ return {
18362
+ templateId: EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST.id,
18363
+ templateVersion: EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST.version,
18364
+ templateHash,
18365
+ promptInstanceHash: hashHarnessTemplateValue({
18366
+ templateHash,
18367
+ input,
18368
+ prompt
18369
+ }),
18370
+ prompt
18371
+ };
18372
+ }
18373
+ function renderExperimentalCompactHarnessContinuation(resultPreview) {
18374
+ const instruction = `${buildContinuationInstruction(resultPreview)}
18375
+
18376
+ Keep the continuation compact: either finish, ask the one blocking question, or run the next smallest safe action.`;
18377
+ const templateHash = hashHarnessTemplateValue(
18378
+ EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST
18379
+ );
18380
+ return {
18381
+ templateId: EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST.id,
18382
+ templateVersion: EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST.version,
18383
+ templateHash,
18384
+ instruction
18385
+ };
18386
+ }
18387
+ var EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE = {
18388
+ manifest: EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE_MANIFEST,
18389
+ renderPrompt: renderExperimentalCompactHarnessPrompt,
18390
+ renderContinuation: renderExperimentalCompactHarnessContinuation
18391
+ };
18392
+ var ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST = defineHarnessTemplateManifest(
18393
+ manifest_default,
18394
+ "action-presentation@0.1.0 manifest"
18395
+ );
18396
+ function renderActionPresentationHarnessPrompt(input) {
18397
+ const prompt = `${buildGranularAgentSystemPrompt(input)}
18398
+
18399
+ [Candidate Harness Delta: Action Request And Result Presentation]
18400
+ - Treat requests like "handle it", "take care of it", "send it", "update it", "fix it", "process it", or "do it" as action requests when the message names a business object, target, workflow, or mutation verb.
18401
+ - For action requests involving session data, generated files, records, effects, or workflows, do not use text-only completion. Generate and run code, ask the blocking human question, or clearly refuse if policy/tooling prevents the action.
18402
+ - Never answer only "Done", "OK", "Handled", or similar terse completion text for a mutation request. The final user-facing reply must name the action attempted, the grounded target or blocker, and the actual result.
18403
+ - After a mutation/effect call, base the reply on the returned action result and include a visible target label or identifier when one exists.
18404
+ - If no target can be grounded, say what was searched and what exact identifier or choice is needed; do not pretend the action completed.`;
18405
+ const templateHash = hashHarnessTemplateValue(
18406
+ ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST
18407
+ );
18408
+ return {
18409
+ templateId: ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST.id,
18410
+ templateVersion: ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST.version,
18411
+ templateHash,
18412
+ promptInstanceHash: hashHarnessTemplateValue({
18413
+ templateHash,
18414
+ input,
18415
+ prompt
18416
+ }),
18417
+ prompt
18418
+ };
18419
+ }
18420
+ function renderActionPresentationHarnessContinuation(resultPreview) {
18421
+ const instruction = `${buildContinuationInstruction(resultPreview)}
18422
+
18423
+ Before finishing, check whether the latest user request asked for an action. If it did, do not finish with a bare completion token; either continue with the needed tool/code step, ask the blocking question, refuse with the policy reason, or report the grounded action result with the target label.`;
18424
+ const templateHash = hashHarnessTemplateValue(
18425
+ ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST
18426
+ );
18427
+ return {
18428
+ templateId: ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST.id,
18429
+ templateVersion: ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST.version,
18430
+ templateHash,
18431
+ instruction
18432
+ };
18433
+ }
18434
+ var ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE = {
18435
+ manifest: ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE_MANIFEST,
18436
+ renderPrompt: renderActionPresentationHarnessPrompt,
18437
+ renderContinuation: renderActionPresentationHarnessContinuation
18438
+ };
18439
+ var AGENT_HARNESS_TEMPLATES = {
18440
+ [STABLE_AGENT_HARNESS_TEMPLATE.manifest.id]: STABLE_AGENT_HARNESS_TEMPLATE,
18441
+ [EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE.manifest.id]: EXPERIMENTAL_COMPACT_AGENT_HARNESS_TEMPLATE,
18442
+ [ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE.manifest.id]: ACTION_PRESENTATION_AGENT_HARNESS_TEMPLATE
18443
+ };
18444
+ function resolveHarnessTemplate(templateId = "stable", options) {
18445
+ const resolved = AGENT_HARNESS_TEMPLATES[templateId];
18446
+ if (resolved) return resolved;
18447
+ {
18448
+ const known = Object.keys(AGENT_HARNESS_TEMPLATES).join(", ");
18449
+ throw new Error(
18450
+ `Unknown harness template "${templateId}". Known templates: ${known}`
18451
+ );
18452
+ }
18453
+ }
18026
18454
 
18027
18455
  // src/openai-usage.ts
18028
18456
  var OPENAI_PRICING_SOURCE_URL = "https://developers.openai.com/api/docs/models/gpt-5.4/";
@@ -18144,6 +18572,98 @@ function matchesPattern(text, matcher) {
18144
18572
  if (typeof matcher === "string") return text.includes(matcher);
18145
18573
  return matcher.test(text);
18146
18574
  }
18575
+ function matcherFromConfig(value) {
18576
+ const match = value.match(/^\/([\s\S]*)\/([dgimsuvy]*)$/);
18577
+ if (!match) return value;
18578
+ return new RegExp(match[1] || "", match[2] || "");
18579
+ }
18580
+ function matchersFromConfig(values) {
18581
+ if (!values?.length) return void 0;
18582
+ return values.map((value) => matcherFromConfig(value));
18583
+ }
18584
+ function expectationsFromConfig(input) {
18585
+ if (!input) return void 0;
18586
+ return {
18587
+ replyIncludes: matchersFromConfig(input.replyIncludes),
18588
+ replyExcludes: matchersFromConfig(input.replyExcludes),
18589
+ actionIncludes: matchersFromConfig(input.actionIncludes),
18590
+ actionExcludes: matchersFromConfig(input.actionExcludes),
18591
+ codeIncludes: matchersFromConfig(input.codeIncludes),
18592
+ codeExcludes: matchersFromConfig(input.codeExcludes),
18593
+ behaviorBuckets: input.behaviorBuckets,
18594
+ actions: input.actions ? {
18595
+ required: matchersFromConfig(input.actions.required),
18596
+ forbidden: matchersFromConfig(input.actions.forbidden)
18597
+ } : void 0,
18598
+ prompts: input.prompts ? {
18599
+ ...input.prompts,
18600
+ requiredConfirmationBefore: matchersFromConfig(
18601
+ input.prompts.requiredConfirmationBefore
18602
+ )
18603
+ } : void 0,
18604
+ presentation: input.presentation ? {
18605
+ mustMention: matchersFromConfig(input.presentation.mustMention),
18606
+ mustNotMention: matchersFromConfig(input.presentation.mustNotMention),
18607
+ mustDisplayOrSave: matchersFromConfig(
18608
+ input.presentation.mustDisplayOrSave
18609
+ )
18610
+ } : void 0
18611
+ };
18612
+ }
18613
+ function promptResponderFromConfig(rules) {
18614
+ if (!rules?.length) return void 0;
18615
+ return createHumanResponder(
18616
+ rules.map((rule) => ({
18617
+ type: rule.type,
18618
+ when: Array.isArray(rule.when) ? rule.when.map((matcher) => matcherFromConfig(matcher)) : rule.when ? matcherFromConfig(rule.when) : void 0,
18619
+ answer: rule.answer
18620
+ }))
18621
+ );
18622
+ }
18623
+ function inspectionFromConfig(inspection) {
18624
+ if (!inspection) return void 0;
18625
+ return {
18626
+ code: inspection.code,
18627
+ includes: matchersFromConfig(inspection.includes),
18628
+ excludes: matchersFromConfig(inspection.excludes)
18629
+ };
18630
+ }
18631
+ function inspectionsFromConfig(inspections) {
18632
+ if (!inspections) return void 0;
18633
+ if (Array.isArray(inspections)) {
18634
+ return inspections.map((inspection) => inspectionFromConfig(inspection)).filter(
18635
+ (inspection) => Boolean(inspection)
18636
+ );
18637
+ }
18638
+ return inspectionFromConfig(inspections);
18639
+ }
18640
+ function scenariosFromAgentEvalFile(file) {
18641
+ return file.scenarios.map((scenario) => ({
18642
+ id: scenario.id,
18643
+ description: scenario.description,
18644
+ request: scenario.request,
18645
+ behaviorBuckets: scenario.behaviorBuckets,
18646
+ human: promptResponderFromConfig(scenario.human),
18647
+ prepareRecords: scenario.prepareRecords,
18648
+ expect: expectationsFromConfig(scenario.expect),
18649
+ verify: inspectionFromConfig(scenario.verify),
18650
+ inspect: inspectionsFromConfig(scenario.inspect),
18651
+ steps: scenario.steps?.map((step) => ({
18652
+ id: step.id,
18653
+ request: step.request,
18654
+ behaviorBuckets: step.behaviorBuckets || scenario.behaviorBuckets,
18655
+ human: promptResponderFromConfig(step.human || scenario.human),
18656
+ expect: expectationsFromConfig(step.expect || scenario.expect),
18657
+ inspect: inspectionsFromConfig(step.inspect)
18658
+ }))
18659
+ }));
18660
+ }
18661
+ async function loadAgentEvalScenarioFile(filePath) {
18662
+ const raw = JSON.parse(
18663
+ await readFile(filePath, "utf8")
18664
+ );
18665
+ return scenariosFromAgentEvalFile(raw);
18666
+ }
18147
18667
  function assertMatches(label, text, includes = [], excludes = []) {
18148
18668
  for (const matcher of includes) {
18149
18669
  if (!matchesPattern(text, matcher)) {
@@ -18176,6 +18696,23 @@ function asArray3(value) {
18176
18696
  if (!value) return [];
18177
18697
  return Array.isArray(value) ? value : [value];
18178
18698
  }
18699
+ function progressEvent(input) {
18700
+ return {
18701
+ ...input,
18702
+ id: input.id || [
18703
+ input.scenarioId || "suite",
18704
+ input.stepId || input.phase,
18705
+ input.iteration ? `iteration-${input.iteration}` : "",
18706
+ input.jobId || "",
18707
+ input.title,
18708
+ Date.now()
18709
+ ].filter(Boolean).join(":"),
18710
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
18711
+ };
18712
+ }
18713
+ async function emitProgress(handler, event) {
18714
+ await handler?.(progressEvent(event));
18715
+ }
18179
18716
  var GPT_54_TOKEN_PRICING_USD_PER_MILLION = {
18180
18717
  input: 2.5,
18181
18718
  cachedInput: 0.25,
@@ -18418,7 +18955,7 @@ function filterPromptsByBoundary(liveDoc, prompts, boundaryTimestamp) {
18418
18955
  });
18419
18956
  }
18420
18957
  function createScriptedPromptResponder(rules, fallback) {
18421
- return async ({ prompt, history }) => {
18958
+ const responder = async ({ prompt, history }) => {
18422
18959
  const promptText = `${prompt.title || ""}
18423
18960
  ${prompt.message || ""}`;
18424
18961
  for (const rule of rules) {
@@ -18435,6 +18972,11 @@ ${prompt.message || ""}`;
18435
18972
  `No scripted prompt responder matched prompt ${prompt.id}: ${promptText}`
18436
18973
  );
18437
18974
  };
18975
+ Object.defineProperty(responder, "__granularScriptedPromptRules", {
18976
+ enumerable: false,
18977
+ value: rules
18978
+ });
18979
+ return responder;
18438
18980
  }
18439
18981
  function extractJsonObject(text) {
18440
18982
  const start = text.indexOf("{");
@@ -18721,6 +19263,22 @@ ${checkpoint.latestJobResult}` : null
18721
19263
  ];
18722
19264
  return lines.filter(Boolean).join("\n\n");
18723
19265
  }
19266
+ function readableAgentMessage(message) {
19267
+ const record = asRecord6(message);
19268
+ if (!record) return JSON.stringify(message);
19269
+ if (typeof record.reply === "string" && record.reply.trim()) {
19270
+ return record.reply;
19271
+ }
19272
+ const show = asRecord6(record.show);
19273
+ const variableNames = asArray3(show?.variableNames).map((value) => String(value)).filter(Boolean);
19274
+ if (variableNames.length) {
19275
+ return `Displayed ${variableNames.join(", ")}`;
19276
+ }
19277
+ if (typeof record.kind === "string") {
19278
+ return `Agent ${record.kind} message`;
19279
+ }
19280
+ return JSON.stringify(message);
19281
+ }
18724
19282
  async function waitForJobOutcome(input) {
18725
19283
  const stdout = [];
18726
19284
  const stderr = [];
@@ -18728,6 +19286,11 @@ async function waitForJobOutcome(input) {
18728
19286
  let lastPromptCount = 0;
18729
19287
  let lastMessageCount = 0;
18730
19288
  let lastJobSummary = null;
19289
+ let lastActionSummaryKey = "";
19290
+ let lastActionSummaryLength = 0;
19291
+ let lastAgentMessageKey = "";
19292
+ let lastAgentMessageCount = 0;
19293
+ let lastJobStatus = "";
18731
19294
  input.job.on("stdout", (line) => stdout.push(String(line)));
18732
19295
  input.job.on("stderr", (line) => stderr.push(String(line)));
18733
19296
  const startedAt = Date.now();
@@ -18743,7 +19306,71 @@ async function waitForJobOutcome(input) {
18743
19306
  const messages = asArray3(asRecord6(liveDoc.conversation)?.messages);
18744
19307
  lastMessageCount = messages.length;
18745
19308
  lastJobSummary = asRecord6(asRecord6(liveDoc.jobs)?.byId)?.[input.job.id] || null;
19309
+ const jobRecord = asRecord6(lastJobSummary);
19310
+ const jobStatus = typeof jobRecord?.status === "string" ? jobRecord.status : "";
19311
+ if (jobStatus && jobStatus !== lastJobStatus) {
19312
+ lastJobStatus = jobStatus;
19313
+ input.onProgress?.({
19314
+ id: `${input.job.id}:status:${jobStatus}:${Date.now()}`,
19315
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
19316
+ phase: "job",
19317
+ status: "running",
19318
+ jobId: input.job.id,
19319
+ title: `Job ${jobStatus}`,
19320
+ message: `Granular job ${input.job.id}`,
19321
+ data: jobRecord,
19322
+ ...input.progressContext
19323
+ });
19324
+ }
19325
+ const actionSummary = getActionSummary(liveDoc, input.job.id);
19326
+ const actionSummaryKey = JSON.stringify(actionSummary);
19327
+ if (actionSummary.length && actionSummaryKey !== lastActionSummaryKey) {
19328
+ const newActions = actionSummary.slice(lastActionSummaryLength);
19329
+ lastActionSummaryLength = actionSummary.length;
19330
+ lastActionSummaryKey = actionSummaryKey;
19331
+ input.onProgress?.({
19332
+ id: `${input.job.id}:actions:${Date.now()}`,
19333
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
19334
+ phase: "job",
19335
+ status: "running",
19336
+ jobId: input.job.id,
19337
+ title: newActions.length === 1 ? "Action observed" : "Actions observed",
19338
+ message: (newActions.length ? newActions : actionSummary).join("\n"),
19339
+ data: { actionSummary, newActions },
19340
+ ...input.progressContext
19341
+ });
19342
+ }
19343
+ const agentMessages = getJobAgentMessages(liveDoc, input.job.id);
19344
+ const agentMessageKey = JSON.stringify(agentMessages);
19345
+ if (agentMessages.length && agentMessageKey !== lastAgentMessageKey) {
19346
+ const newMessages = agentMessages.slice(lastAgentMessageCount);
19347
+ lastAgentMessageCount = agentMessages.length;
19348
+ lastAgentMessageKey = agentMessageKey;
19349
+ const latestMessage = newMessages.at(-1) || agentMessages.at(-1);
19350
+ input.onProgress?.({
19351
+ id: `${input.job.id}:messages:${Date.now()}`,
19352
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
19353
+ phase: "job",
19354
+ status: "running",
19355
+ jobId: input.job.id,
19356
+ title: "Agent message",
19357
+ message: readableAgentMessage(latestMessage),
19358
+ data: { agentMessages, newMessages },
19359
+ ...input.progressContext
19360
+ });
19361
+ }
18746
19362
  if (prompts.length > 0) {
19363
+ input.onProgress?.({
19364
+ id: `${input.job.id}:prompt:${prompts[0]?.id || Date.now()}`,
19365
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
19366
+ phase: "prompt",
19367
+ status: "running",
19368
+ jobId: input.job.id,
19369
+ title: "Waiting for prompt answer",
19370
+ message: prompts[0]?.message || prompts[0]?.title,
19371
+ data: { prompts },
19372
+ ...input.progressContext
19373
+ });
18747
19374
  return { kind: "prompt", prompts, liveDoc, stdout, stderr };
18748
19375
  }
18749
19376
  try {
@@ -18752,6 +19379,17 @@ async function waitForJobOutcome(input) {
18752
19379
  input.pollIntervalMs,
18753
19380
  `job ${input.job.id} tick`
18754
19381
  );
19382
+ input.onProgress?.({
19383
+ id: `${input.job.id}:completed:${Date.now()}`,
19384
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
19385
+ phase: "job",
19386
+ status: "passed",
19387
+ jobId: input.job.id,
19388
+ title: "Job completed",
19389
+ message: JSON.stringify(result)?.slice(0, 800),
19390
+ data: { result, stdout, stderr },
19391
+ ...input.progressContext
19392
+ });
18755
19393
  return { kind: "completed", result, liveDoc, stdout, stderr };
18756
19394
  } catch (error) {
18757
19395
  const message = error instanceof Error ? error.message : String(error);
@@ -18983,6 +19621,10 @@ function buildSessionLogReport(input) {
18983
19621
  `- Environment id: \`${conversation.environment.environmentId}\``,
18984
19622
  `- Sandbox id: \`${conversation.environment.sandboxId}\``,
18985
19623
  `- Status: ${result?.status || (error ? "failed" : "unknown")}`,
19624
+ ...systemPrompts[0]?.iteration.templateId ? [
19625
+ `- Harness template: \`${systemPrompts[0].iteration.templateId}@${systemPrompts[0].iteration.templateVersion || "unknown"}\``,
19626
+ `- Template hash: \`${systemPrompts[0].iteration.templateHash || "unknown"}\``
19627
+ ] : [],
18986
19628
  ...result?.error || error ? [`- Error: ${result?.error || error}`] : [],
18987
19629
  "",
18988
19630
  "## Conversation"
@@ -19090,6 +19732,12 @@ function buildSessionLogReport(input) {
19090
19732
  lines.push(
19091
19733
  `### Turn ${turn.turnNumber}, Generation ${iteration.iteration}`,
19092
19734
  "",
19735
+ ...iteration.templateId ? [
19736
+ `- Template: \`${iteration.templateId}@${iteration.templateVersion || "unknown"}\``,
19737
+ `- Template hash: \`${iteration.templateHash || "unknown"}\``,
19738
+ `- Prompt hash: \`${iteration.promptInstanceHash || "unknown"}\``,
19739
+ ""
19740
+ ] : [],
19093
19741
  fenced(iteration.systemPrompt, "text"),
19094
19742
  ""
19095
19743
  );
@@ -19131,9 +19779,167 @@ function isTransientEvalError(error) {
19131
19779
  const message = error instanceof Error ? error.message : String(error);
19132
19780
  return /socket connection was closed unexpectedly/i.test(message) || /timed out after/i.test(message) || /ECONNRESET/i.test(message) || /network/i.test(message) || /429/.test(message);
19133
19781
  }
19782
+ function assertionPassed(id, run) {
19783
+ try {
19784
+ run(id);
19785
+ return { id, label: id, status: "passed" };
19786
+ } catch (error) {
19787
+ return {
19788
+ id,
19789
+ label: id,
19790
+ status: "failed",
19791
+ message: error instanceof Error ? error.message : String(error)
19792
+ };
19793
+ }
19794
+ }
19795
+ function hasMatchers(matchers) {
19796
+ return Boolean(matchers?.length);
19797
+ }
19798
+ function evaluateExpectationAssertions(input) {
19799
+ const expect = input.expect;
19800
+ if (!expect) return [];
19801
+ const prefix = `${input.scenarioId} step ${input.stepIndex + 1}`;
19802
+ const assertions = [];
19803
+ const actionText = input.completed.actionSummary.join("\n");
19804
+ const codeText = input.completed.finalCode || "";
19805
+ const displaySurface = [
19806
+ input.completed.responseText,
19807
+ actionText,
19808
+ codeText,
19809
+ JSON.stringify(input.completed.result)
19810
+ ].join("\n");
19811
+ const promptSurface = input.completed.promptInteractions.map(
19812
+ (interaction) => [interaction.type, interaction.title, interaction.message].join(" ")
19813
+ ).join("\n");
19814
+ if (hasMatchers(expect.replyIncludes) || hasMatchers(expect.replyExcludes)) {
19815
+ assertions.push(
19816
+ assertionPassed(
19817
+ "reply",
19818
+ () => assertMatches(
19819
+ `Reply for ${prefix}`,
19820
+ input.completed.responseText,
19821
+ expect.replyIncludes,
19822
+ expect.replyExcludes
19823
+ )
19824
+ )
19825
+ );
19826
+ }
19827
+ if (hasMatchers(expect.actionIncludes) || hasMatchers(expect.actionExcludes)) {
19828
+ assertions.push(
19829
+ assertionPassed(
19830
+ "actions",
19831
+ () => assertMatches(
19832
+ `Action summary for ${prefix}`,
19833
+ actionText,
19834
+ expect.actionIncludes,
19835
+ expect.actionExcludes
19836
+ )
19837
+ )
19838
+ );
19839
+ }
19840
+ if (hasMatchers(expect.codeIncludes) || hasMatchers(expect.codeExcludes)) {
19841
+ assertions.push(
19842
+ assertionPassed(
19843
+ "code",
19844
+ () => assertMatches(
19845
+ `Generated code for ${prefix}`,
19846
+ codeText,
19847
+ expect.codeIncludes,
19848
+ expect.codeExcludes
19849
+ )
19850
+ )
19851
+ );
19852
+ }
19853
+ if (hasMatchers(expect.actions?.required) || hasMatchers(expect.actions?.forbidden)) {
19854
+ assertions.push(
19855
+ assertionPassed(
19856
+ "required-actions",
19857
+ () => assertMatches(
19858
+ `Required actions for ${prefix}`,
19859
+ actionText,
19860
+ expect.actions?.required,
19861
+ expect.actions?.forbidden
19862
+ )
19863
+ )
19864
+ );
19865
+ }
19866
+ if (hasMatchers(expect.presentation?.mustMention) || hasMatchers(expect.presentation?.mustNotMention)) {
19867
+ assertions.push(
19868
+ assertionPassed(
19869
+ "presentation",
19870
+ () => assertMatches(
19871
+ `Presentation for ${prefix}`,
19872
+ input.completed.responseText,
19873
+ expect.presentation?.mustMention,
19874
+ expect.presentation?.mustNotMention
19875
+ )
19876
+ )
19877
+ );
19878
+ }
19879
+ if (hasMatchers(expect.presentation?.mustDisplayOrSave)) {
19880
+ assertions.push(
19881
+ assertionPassed(
19882
+ "display",
19883
+ () => assertMatches(
19884
+ `Displayed or saved records for ${prefix}`,
19885
+ displaySurface,
19886
+ expect.presentation?.mustDisplayOrSave
19887
+ )
19888
+ )
19889
+ );
19890
+ }
19891
+ if (expect.prompts?.requiredChoiceWhenAmbiguous) {
19892
+ assertions.push(
19893
+ assertionPassed("choice", () => {
19894
+ if (!input.completed.promptInteractions.some(
19895
+ (interaction) => interaction.type === "choice"
19896
+ )) {
19897
+ throw new Error(`Expected ${prefix} to use a choice prompt.`);
19898
+ }
19899
+ })
19900
+ );
19901
+ }
19902
+ for (const forbiddenType of expect.prompts?.forbiddenPromptTypes || []) {
19903
+ assertions.push(
19904
+ assertionPassed(`forbidden-prompt:${forbiddenType}`, () => {
19905
+ if (input.completed.promptInteractions.some(
19906
+ (interaction) => interaction.type === forbiddenType
19907
+ )) {
19908
+ throw new Error(
19909
+ `Prompt interactions for ${prefix} used forbidden prompt type ${forbiddenType}.`
19910
+ );
19911
+ }
19912
+ })
19913
+ );
19914
+ }
19915
+ if (expect.prompts?.requiredConfirmationBefore?.length) {
19916
+ assertions.push(
19917
+ assertionPassed(
19918
+ "confirmation",
19919
+ () => assertMatches(
19920
+ `Confirmation prompts for ${prefix}`,
19921
+ [actionText, promptSurface].join("\n"),
19922
+ expect.prompts?.requiredConfirmationBefore
19923
+ )
19924
+ )
19925
+ );
19926
+ }
19927
+ return assertions;
19928
+ }
19134
19929
  async function runAgentEvalSuite(options) {
19135
19930
  const results = [];
19136
19931
  for (const scenario of options.scenarios) {
19932
+ await options.harness.emitProgress?.({
19933
+ phase: "scenario",
19934
+ status: "running",
19935
+ scenarioId: scenario.id,
19936
+ title: "Scenario started",
19937
+ message: scenario.description || scenario.request || scenario.id,
19938
+ data: {
19939
+ scenarioId: scenario.id,
19940
+ behaviorBuckets: scenario.behaviorBuckets
19941
+ }
19942
+ });
19137
19943
  let attempt = 0;
19138
19944
  let finalResult = null;
19139
19945
  while (attempt < 2 && !finalResult) {
@@ -19204,27 +20010,38 @@ async function runAgentEvalSuite(options) {
19204
20010
  },
19205
20011
  assertMatches
19206
20012
  };
19207
- if (step.expect) {
19208
- assertMatches(
19209
- `Reply for ${scenario.id} step ${index + 1}`,
19210
- completed.responseText,
19211
- step.expect.replyIncludes,
19212
- step.expect.replyExcludes
19213
- );
19214
- assertMatches(
19215
- `Action summary for ${scenario.id} step ${index + 1}`,
19216
- completed.actionSummary.join("\n"),
19217
- step.expect.actionIncludes,
19218
- step.expect.actionExcludes
19219
- );
19220
- assertMatches(
19221
- `Generated code for ${scenario.id} step ${index + 1}`,
19222
- completed.finalCode || "",
19223
- step.expect.codeIncludes,
19224
- step.expect.codeExcludes
19225
- );
20013
+ const assertions = evaluateExpectationAssertions({
20014
+ scenarioId: scenario.id,
20015
+ stepIndex: index,
20016
+ expect: step.expect,
20017
+ completed
20018
+ });
20019
+ for (const assertion of assertions) {
20020
+ await options.harness.emitProgress?.({
20021
+ phase: "assertion",
20022
+ status: assertion.status,
20023
+ scenarioId: scenario.id,
20024
+ stepId: step.id || `step-${index + 1}`,
20025
+ title: assertion.label,
20026
+ message: assertion.message,
20027
+ data: assertion
20028
+ });
20029
+ }
20030
+ const failedAssertion = assertions.find(
20031
+ (assertion) => assertion.status === "failed"
20032
+ );
20033
+ if (failedAssertion) {
20034
+ throw new Error(failedAssertion.message || failedAssertion.label);
19226
20035
  }
19227
20036
  for (const inspection of stepInspections) {
20037
+ await options.harness.emitProgress?.({
20038
+ phase: "inspection",
20039
+ status: "running",
20040
+ scenarioId: scenario.id,
20041
+ stepId: step.id || `step-${index + 1}`,
20042
+ title: "Running step inspection",
20043
+ message: inspection.code.slice(0, 500)
20044
+ });
19228
20045
  const inspectionResult = await context.inspect(inspection.code);
19229
20046
  const inspectionText = JSON.stringify(inspectionResult, null, 2);
19230
20047
  assertMatches(
@@ -19240,9 +20057,32 @@ async function runAgentEvalSuite(options) {
19240
20057
  });
19241
20058
  }
19242
20059
  inspectionResults.push(inspectionResult);
20060
+ await options.harness.emitProgress?.({
20061
+ phase: "inspection",
20062
+ status: "passed",
20063
+ scenarioId: scenario.id,
20064
+ stepId: step.id || `step-${index + 1}`,
20065
+ title: "Step inspection passed",
20066
+ message: inspectionText.slice(0, 800),
20067
+ data: inspectionResult
20068
+ });
19243
20069
  }
19244
20070
  for (const check of stepChecks) {
20071
+ await options.harness.emitProgress?.({
20072
+ phase: "check",
20073
+ status: "running",
20074
+ scenarioId: scenario.id,
20075
+ stepId: step.id || `step-${index + 1}`,
20076
+ title: "Running custom check"
20077
+ });
19245
20078
  await check(context);
20079
+ await options.harness.emitProgress?.({
20080
+ phase: "check",
20081
+ status: "passed",
20082
+ scenarioId: scenario.id,
20083
+ stepId: step.id || `step-${index + 1}`,
20084
+ title: "Custom check passed"
20085
+ });
19246
20086
  }
19247
20087
  stepResults.push({
19248
20088
  id: step.id || `step-${index + 1}`,
@@ -19253,6 +20093,7 @@ async function runAgentEvalSuite(options) {
19253
20093
  actionSummary: completed.actionSummary,
19254
20094
  promptInteractions: completed.promptInteractions,
19255
20095
  inspectionResults,
20096
+ assertions,
19256
20097
  turnDir: completed.turnDir
19257
20098
  });
19258
20099
  }
@@ -19271,6 +20112,7 @@ async function runAgentEvalSuite(options) {
19271
20112
  actionSummary: lastStep.actionSummary,
19272
20113
  promptInteractions: lastStep.promptInteractions,
19273
20114
  verification: lastStep.inspectionResults.length <= 1 ? lastStep.inspectionResults[0] ?? null : lastStep.inspectionResults,
20115
+ assertions: stepResults.flatMap((step) => step.assertions || []),
19274
20116
  tokenUsage: aggregateConversationTokenUsage(conversation),
19275
20117
  steps: stepResults,
19276
20118
  turnDir: conversation.artifactDir
@@ -19292,6 +20134,14 @@ async function runAgentEvalSuite(options) {
19292
20134
  conversation,
19293
20135
  result
19294
20136
  });
20137
+ await options.harness.emitProgress?.({
20138
+ phase: "scenario",
20139
+ status: "passed",
20140
+ scenarioId: scenario.id,
20141
+ title: "Scenario passed",
20142
+ message: `${result.assertions?.filter((assertion) => assertion.status === "passed").length || 0}/${result.assertions?.length || 0} assertions`,
20143
+ data: result
20144
+ });
19295
20145
  finalResult = result;
19296
20146
  } catch (error) {
19297
20147
  const failureMessage = error instanceof Error ? error.message : String(error);
@@ -19332,6 +20182,14 @@ async function runAgentEvalSuite(options) {
19332
20182
  result: failed,
19333
20183
  error: failureMessage
19334
20184
  });
20185
+ await options.harness.emitProgress?.({
20186
+ phase: "scenario",
20187
+ status: "failed",
20188
+ scenarioId: scenario.id,
20189
+ title: "Scenario failed",
20190
+ message: failureMessage,
20191
+ data: failed
20192
+ });
19335
20193
  finalResult = failed;
19336
20194
  } finally {
19337
20195
  await options.harness.closeConversation(conversation);
@@ -19380,6 +20238,11 @@ function createAgentEvalHarness(options) {
19380
20238
  const chatTimeoutMs = options.chatTimeoutMs ?? 12e4;
19381
20239
  const jobTimeoutMs = options.jobTimeoutMs ?? 9e4;
19382
20240
  const pollIntervalMs = options.pollIntervalMs ?? 250;
20241
+ const resolvedTemplate = resolveHarnessTemplate(
20242
+ options.harnessTemplateId || process.env.GRANULAR_AGENT_HARNESS_TEMPLATE || "stable");
20243
+ const promptRenderer = options.promptRenderer || resolvedTemplate.renderPrompt;
20244
+ const continuationRenderer = options.continuationRenderer || resolvedTemplate.renderContinuation;
20245
+ const onProgress = options.onProgress;
19383
20246
  async function openConversation(label) {
19384
20247
  await ensureDir(artifactDir);
19385
20248
  const clientId = `${slugify(label)}-${Date.now()}`;
@@ -19448,6 +20311,13 @@ function createAgentEvalHarness(options) {
19448
20311
  };
19449
20312
  }
19450
20313
  async function runInspection(conversation, inspection, completed, turnDir) {
20314
+ await emitProgress(onProgress, {
20315
+ phase: "inspection",
20316
+ status: "running",
20317
+ scenarioId: conversation.label,
20318
+ title: "Running inspection",
20319
+ message: inspection.code.slice(0, 500)
20320
+ });
19451
20321
  let result = null;
19452
20322
  let lastError = null;
19453
20323
  for (let attempt = 0; attempt < 10; attempt += 1) {
@@ -19475,6 +20345,14 @@ function createAgentEvalHarness(options) {
19475
20345
  });
19476
20346
  }
19477
20347
  await writeJson(path.join(turnDir, "verification.json"), result);
20348
+ await emitProgress(onProgress, {
20349
+ phase: "inspection",
20350
+ status: "passed",
20351
+ scenarioId: conversation.label,
20352
+ title: "Inspection passed",
20353
+ message: JSON.stringify(result)?.slice(0, 800),
20354
+ data: result
20355
+ });
19478
20356
  return result;
19479
20357
  }
19480
20358
  async function resumePendingTurn(pending, responder) {
@@ -19484,6 +20362,16 @@ function createAgentEvalHarness(options) {
19484
20362
  prompt,
19485
20363
  history: pending.promptInteractions
19486
20364
  });
20365
+ await emitProgress(onProgress, {
20366
+ phase: "interaction",
20367
+ status: "running",
20368
+ scenarioId: pending.conversation.label,
20369
+ stepId: path.basename(pending.turnDir),
20370
+ jobId: pending.job.id,
20371
+ title: "Prompt answered",
20372
+ message: `${prompt.type}: ${prompt.message || prompt.title} -> ${JSON.stringify(answer)}`,
20373
+ data: { prompt, answer }
20374
+ });
19487
20375
  const session = pending.conversation.environment;
19488
20376
  await session.answerPrompt(prompt.id, answer);
19489
20377
  pending.promptInteractions.push({
@@ -19503,7 +20391,12 @@ function createAgentEvalHarness(options) {
19503
20391
  job: pending.job,
19504
20392
  boundaryTimestamp: pending.boundaryTimestamp,
19505
20393
  timeoutMs: jobTimeoutMs,
19506
- pollIntervalMs
20394
+ pollIntervalMs,
20395
+ onProgress: (event) => void onProgress?.(event),
20396
+ progressContext: {
20397
+ scenarioId: pending.conversation.label,
20398
+ stepId: path.basename(pending.turnDir)
20399
+ }
19507
20400
  });
19508
20401
  if (resumed.kind === "prompt") {
19509
20402
  return {
@@ -19584,12 +20477,44 @@ function createAgentEvalHarness(options) {
19584
20477
  };
19585
20478
  conversation.logTurns.push(turnLog);
19586
20479
  if (input.prepareRecords?.length) {
20480
+ await emitProgress(onProgress, {
20481
+ phase: "setup",
20482
+ status: "running",
20483
+ scenarioId: conversation.label,
20484
+ stepId: turnId,
20485
+ title: "Recording setup records",
20486
+ message: `${input.prepareRecords.length} records`,
20487
+ data: input.prepareRecords
20488
+ });
19587
20489
  await conversation.environment.recordObjects(input.prepareRecords);
20490
+ await emitProgress(onProgress, {
20491
+ phase: "setup",
20492
+ status: "passed",
20493
+ scenarioId: conversation.label,
20494
+ stepId: turnId,
20495
+ title: "Setup records recorded",
20496
+ message: `${input.prepareRecords.length} records`
20497
+ });
19588
20498
  }
19589
20499
  if (input.prepareTools?.length) {
20500
+ await emitProgress(onProgress, {
20501
+ phase: "setup",
20502
+ status: "running",
20503
+ scenarioId: conversation.label,
20504
+ stepId: turnId,
20505
+ title: "Registering effect handlers",
20506
+ message: `${input.prepareTools.length} handlers`
20507
+ });
19590
20508
  await options.granular.ontology(conversation.environment.sandboxId).effects.registerMany(input.prepareTools);
19591
20509
  }
19592
20510
  if (input.prepare) {
20511
+ await emitProgress(onProgress, {
20512
+ phase: "setup",
20513
+ status: "running",
20514
+ scenarioId: conversation.label,
20515
+ stepId: turnId,
20516
+ title: "Running custom setup"
20517
+ });
19593
20518
  await input.prepare({
19594
20519
  conversation,
19595
20520
  environment: conversation.environment,
@@ -19598,6 +20523,14 @@ function createAgentEvalHarness(options) {
19598
20523
  }
19599
20524
  const boundaryTimestamp = Date.now();
19600
20525
  conversation.history.push({ role: "user", content: input.request });
20526
+ await emitProgress(onProgress, {
20527
+ phase: "step",
20528
+ status: "running",
20529
+ scenarioId: conversation.label,
20530
+ stepId: turnId,
20531
+ title: "User request",
20532
+ message: input.request
20533
+ });
19601
20534
  await writeJson(path.join(turnDir, "request.json"), {
19602
20535
  request: input.request,
19603
20536
  boundaryTimestamp
@@ -19639,7 +20572,7 @@ function createAgentEvalHarness(options) {
19639
20572
  inputSchema: tool.inputSchema,
19640
20573
  outputSchema: tool.outputSchema
19641
20574
  }));
19642
- const systemPrompt = buildGranularAgentSystemPrompt({
20575
+ const renderedPrompt = promptRenderer({
19643
20576
  domainDocumentation: await conversation.environment.getDomainDocumentation(),
19644
20577
  sessionContext: {
19645
20578
  sandboxId: conversation.environment.sandboxId,
@@ -19660,9 +20593,39 @@ function createAgentEvalHarness(options) {
19660
20593
  tools,
19661
20594
  checkpoint: latestCheckpoint
19662
20595
  });
19663
- const request = iteration === 0 ? input.request : buildContinuationInstruction(
20596
+ const systemPrompt = renderedPrompt.prompt;
20597
+ await emitProgress(onProgress, {
20598
+ phase: "prompt",
20599
+ status: "passed",
20600
+ scenarioId: conversation.label,
20601
+ stepId: turnId,
20602
+ iteration: iteration + 1,
20603
+ templateId: renderedPrompt.templateId,
20604
+ templateVersion: renderedPrompt.templateVersion,
20605
+ title: "Rendered harness prompt",
20606
+ message: `${systemPrompt.split("\n").length} lines`,
20607
+ data: {
20608
+ templateId: renderedPrompt.templateId,
20609
+ templateVersion: renderedPrompt.templateVersion,
20610
+ templateHash: renderedPrompt.templateHash,
20611
+ promptInstanceHash: renderedPrompt.promptInstanceHash,
20612
+ prompt: systemPrompt
20613
+ }
20614
+ });
20615
+ const request = iteration === 0 ? input.request : continuationRenderer(
19664
20616
  buildContinuationPreview(latestCheckpoint, noProgressCount)
19665
- );
20617
+ ).instruction;
20618
+ await emitProgress(onProgress, {
20619
+ phase: "generation",
20620
+ status: "running",
20621
+ scenarioId: conversation.label,
20622
+ stepId: turnId,
20623
+ iteration: iteration + 1,
20624
+ templateId: renderedPrompt.templateId,
20625
+ templateVersion: renderedPrompt.templateVersion,
20626
+ title: iteration === 0 ? "Generating agent response" : "Generating continuation",
20627
+ message: request
20628
+ });
19666
20629
  const generation = await withTimeout2(
19667
20630
  generateTurnWithRepair(options.generator, {
19668
20631
  systemPrompt,
@@ -19681,10 +20644,31 @@ function createAgentEvalHarness(options) {
19681
20644
  chatTimeoutMs,
19682
20645
  `chat generation for ${conversation.label} iteration ${iteration + 1}`
19683
20646
  );
20647
+ await emitProgress(onProgress, {
20648
+ phase: "generation",
20649
+ status: "passed",
20650
+ scenarioId: conversation.label,
20651
+ stepId: turnId,
20652
+ iteration: iteration + 1,
20653
+ templateId: renderedPrompt.templateId,
20654
+ templateVersion: renderedPrompt.templateVersion,
20655
+ title: generation.code ? "Generated job code" : "Generated text reply",
20656
+ message: generation.code || generation.reply || "",
20657
+ data: {
20658
+ reply: generation.reply,
20659
+ code: generation.code,
20660
+ attempts: generation.generationAttempts,
20661
+ usage: tokenUsageForGenerationOutput(generation)
20662
+ }
20663
+ });
19684
20664
  const iterationLog = {
19685
20665
  iteration: iteration + 1,
19686
20666
  request,
19687
20667
  systemPrompt,
20668
+ templateId: renderedPrompt.templateId,
20669
+ templateVersion: renderedPrompt.templateVersion,
20670
+ templateHash: renderedPrompt.templateHash,
20671
+ promptInstanceHash: renderedPrompt.promptInstanceHash,
19688
20672
  generationReply: generation.reply,
19689
20673
  generatedCode: generation.code,
19690
20674
  rawGeneration: generation.raw,
@@ -19731,6 +20715,15 @@ function createAgentEvalHarness(options) {
19731
20715
  result: completed.result
19732
20716
  };
19733
20717
  await writeJson(path.join(turnDir, "result.json"), completed);
20718
+ await emitProgress(onProgress, {
20719
+ phase: "step",
20720
+ status: "passed",
20721
+ scenarioId: conversation.label,
20722
+ stepId: turnId,
20723
+ title: "Step completed with text reply",
20724
+ message: responseText2,
20725
+ data: completed
20726
+ });
19734
20727
  return completed;
19735
20728
  }
19736
20729
  const session = conversation.environment;
@@ -19751,12 +20744,33 @@ function createAgentEvalHarness(options) {
19751
20744
  )
19752
20745
  }
19753
20746
  });
20747
+ await emitProgress(onProgress, {
20748
+ phase: "job",
20749
+ status: "running",
20750
+ scenarioId: conversation.label,
20751
+ stepId: turnId,
20752
+ iteration: iteration + 1,
20753
+ jobId: job.id,
20754
+ templateId: renderedPrompt.templateId,
20755
+ templateVersion: renderedPrompt.templateVersion,
20756
+ title: "Submitted Granular job",
20757
+ message: job.id,
20758
+ data: { code: generation.code }
20759
+ });
19754
20760
  const outcome = await waitForJobOutcome({
19755
20761
  environment: conversation.environment,
19756
20762
  job,
19757
20763
  boundaryTimestamp,
19758
20764
  timeoutMs: jobTimeoutMs,
19759
- pollIntervalMs
20765
+ pollIntervalMs,
20766
+ onProgress: (event) => void onProgress?.(event),
20767
+ progressContext: {
20768
+ scenarioId: conversation.label,
20769
+ stepId: turnId,
20770
+ iteration: iteration + 1,
20771
+ templateId: renderedPrompt.templateId,
20772
+ templateVersion: renderedPrompt.templateVersion
20773
+ }
19760
20774
  });
19761
20775
  if (outcome.kind === "prompt") {
19762
20776
  if (!autoAnswerPrompts) {
@@ -19866,6 +20880,23 @@ function createAgentEvalHarness(options) {
19866
20880
  controllerReason: continuation.reason,
19867
20881
  noProgressCount: continuation.nextNoProgressCount
19868
20882
  };
20883
+ await emitProgress(onProgress, {
20884
+ phase: "continuation",
20885
+ status: continuation.shouldContinue ? "running" : "passed",
20886
+ scenarioId: conversation.label,
20887
+ stepId: turnId,
20888
+ iteration: iteration + 1,
20889
+ jobId: job.id,
20890
+ templateId: renderedPrompt.templateId,
20891
+ templateVersion: renderedPrompt.templateVersion,
20892
+ title: continuation.shouldContinue ? "Harness requested another loop" : "Harness accepted completion",
20893
+ message: `${continuation.reason}; ${continuation.outcome}`,
20894
+ data: {
20895
+ continuation,
20896
+ checkpoint: latestCheckpoint,
20897
+ verifierSnapshot
20898
+ }
20899
+ });
19869
20900
  previousSnapshot = verifierSnapshot;
19870
20901
  noProgressCount = continuation.nextNoProgressCount;
19871
20902
  conversation.history.push({
@@ -19919,6 +20950,17 @@ function createAgentEvalHarness(options) {
19919
20950
  result: outcome.result
19920
20951
  };
19921
20952
  await writeJson(path.join(turnDir, "result.json"), completed);
20953
+ await emitProgress(onProgress, {
20954
+ phase: "step",
20955
+ status: "passed",
20956
+ scenarioId: conversation.label,
20957
+ stepId: turnId,
20958
+ iteration: iteration + 1,
20959
+ jobId: job.id,
20960
+ title: "Step completed",
20961
+ message: responseText,
20962
+ data: completed
20963
+ });
19922
20964
  return completed;
19923
20965
  }
19924
20966
  iteration += 1;
@@ -19930,6 +20972,7 @@ function createAgentEvalHarness(options) {
19930
20972
  return {
19931
20973
  artifactDir,
19932
20974
  granular: options.granular,
20975
+ emitProgress: (event) => emitProgress(onProgress, event),
19933
20976
  openConversation,
19934
20977
  closeConversation,
19935
20978
  runTurn,
@@ -19995,7 +21038,11 @@ function createAgentTester(options) {
19995
21038
  controllerBudgets: options.controllerBudgets,
19996
21039
  chatTimeoutMs: options.chatTimeoutMs,
19997
21040
  jobTimeoutMs: options.jobTimeoutMs,
19998
- pollIntervalMs: options.pollIntervalMs
21041
+ pollIntervalMs: options.pollIntervalMs,
21042
+ harnessTemplateId: options.harnessTemplateId,
21043
+ promptRenderer: options.promptRenderer,
21044
+ continuationRenderer: options.continuationRenderer,
21045
+ onProgress: options.onProgress
19999
21046
  });
20000
21047
  return {
20001
21048
  ...harness,
@@ -20021,6 +21068,6 @@ var createHumanResponder = createScriptedPromptResponder;
20021
21068
  var createOpenAIGenerator = createOpenAIChatTurnGenerator;
20022
21069
  var createTestArtifactsDirectory = createTimestampedArtifactDirectory;
20023
21070
 
20024
- export { createAgentEvalHarness, createAgentTester, createHumanResponder, createOpenAIChatTurnGenerator, createOpenAIGenerator, createScriptedPromptResponder, createTestArtifactsDirectory, createTimestampedArtifactDirectory, generateTurnWithRepair, runAgentEvalSuite, runAgentTests };
21071
+ export { createAgentEvalHarness, createAgentTester, createHumanResponder, createOpenAIChatTurnGenerator, createOpenAIGenerator, createScriptedPromptResponder, createTestArtifactsDirectory, createTimestampedArtifactDirectory, generateTurnWithRepair, loadAgentEvalScenarioFile, runAgentEvalSuite, runAgentTests, scenariosFromAgentEvalFile };
20025
21072
  //# sourceMappingURL=agent-evals.mjs.map
20026
21073
  //# sourceMappingURL=agent-evals.mjs.map