@odla-ai/harness 0.10.4 → 0.11.0

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.
@@ -7,7 +7,7 @@ import {
7
7
  createCodeRuntimeControlClient,
8
8
  createCodeRuntimeSessionSkillLoader,
9
9
  runCodeRuntimeHeartbeatLoop
10
- } from "./chunk-IV3VMKDV.js";
10
+ } from "./chunk-IXYKIPRA.js";
11
11
  import "./chunk-KXFI3WM2.js";
12
12
  import {
13
13
  assertPinnedImage,
package/dist/node.cjs CHANGED
@@ -27,6 +27,7 @@ __export(node_exports, {
27
27
  DecompositionError: () => DecompositionError,
28
28
  MAX_MEMORY_BODY: () => MAX_MEMORY_BODY,
29
29
  MEASURED_PREMIUM: () => MEASURED_PREMIUM,
30
+ REPOSITORY_RECIPES_FILE: () => REPOSITORY_RECIPES_FILE,
30
31
  SYSTEM_PROMPT_FOR: () => SYSTEM_PROMPT_FOR,
31
32
  TheseusRuntimeEngine: () => TheseusRuntimeEngine,
32
33
  V1_SYSTEM_PROMPT: () => V1_SYSTEM_PROMPT,
@@ -48,6 +49,7 @@ __export(node_exports, {
48
49
  createCodeToolBroker: () => createCodeToolBroker,
49
50
  createCodeWorkspaceCheckpoint: () => createCodeWorkspaceCheckpoint,
50
51
  createContainerRecipeExecutor: () => createContainerRecipeExecutor,
52
+ describeCodeRecipes: () => describeCodeRecipes,
51
53
  describePatchFailure: () => describePatchFailure,
52
54
  digestStagedWorkspace: () => digestStagedWorkspace,
53
55
  feedbackIsActionable: () => feedbackIsActionable,
@@ -62,16 +64,19 @@ __export(node_exports, {
62
64
  materializeGitTree: () => materializeGitTree,
63
65
  outcomeCloses: () => outcomeCloses,
64
66
  outcomeMemory: () => outcomeMemory,
67
+ parseRepositoryRecipes: () => parseRepositoryRecipes,
65
68
  patchPaths: () => patchPaths,
66
69
  planReachCollisions: () => planReachCollisions,
67
70
  prepareRuntimeCheckpoint: () => prepareRuntimeCheckpoint,
68
71
  racedAttempt: () => racedAttempt,
69
72
  readOnlyNotice: () => readOnlyNotice,
73
+ readRepositoryRecipes: () => readRepositoryRecipes,
70
74
  recallAbout: () => recallAbout,
71
75
  registeredFiles: () => registeredFiles,
72
76
  renderMemories: () => renderMemories,
73
77
  renderOutcome: () => renderOutcome,
74
78
  resolveCodePath: () => resolveCodePath,
79
+ resolveCodeRecipes: () => resolveCodeRecipes,
75
80
  restoreCodeWorkspaceCheckpoint: () => restoreCodeWorkspaceCheckpoint,
76
81
  runCodeAgent: () => runCodeAgent,
77
82
  runCodeAgentAttempt: () => runCodeAgentAttempt,
@@ -84,6 +89,7 @@ __export(node_exports, {
84
89
  safeWorkspaceLabel: () => safeWorkspaceLabel,
85
90
  selectContainerEngine: () => selectContainerEngine,
86
91
  selectWinner: () => selectWinner,
92
+ sessionRecipesFor: () => sessionRecipesFor,
87
93
  sessionSkillsFor: () => sessionSkillsFor,
88
94
  stageWorkspace: () => stageWorkspace,
89
95
  stageWorkspacePair: () => stageWorkspacePair,
@@ -2000,8 +2006,8 @@ var CodeRuntimeCheckpointManager = class {
2000
2006
  trustedBaseDigest: active.trustedBaseDigest,
2001
2007
  planningInputDigest: active.planningInputDigest,
2002
2008
  conversationRefs: active.conversationRefs,
2003
- fallbackPolicyDigest: this.options.fallbackPolicyDigest,
2004
- recipes: this.options.recipes,
2009
+ fallbackPolicyDigest: active.buildPolicyDigest ?? this.options.fallbackPolicyDigest,
2010
+ recipes: active.recipes ?? this.options.recipes,
2005
2011
  recipeExecutor: this.options.recipeExecutor,
2006
2012
  review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
2007
2013
  });
@@ -2942,13 +2948,19 @@ function createCodeRuntimeSessionSkillLoader(control) {
2942
2948
  ...tool.acceptsTaint === void 0 ? {} : { acceptsTaint: tool.acceptsTaint },
2943
2949
  handler: async (input, context) => {
2944
2950
  if (!context.toolCallId) throw new TypeError("collaboration tool call identity is required");
2945
- return execute2(command.sessionId, {
2946
- commandId: command.commandId,
2947
- toolCallId: context.toolCallId,
2948
- skill: manifest.name,
2949
- tool: tool.name,
2950
- input
2951
- }, context.signal);
2951
+ try {
2952
+ return await execute2(command.sessionId, {
2953
+ commandId: command.commandId,
2954
+ toolCallId: context.toolCallId,
2955
+ skill: manifest.name,
2956
+ tool: tool.name,
2957
+ input
2958
+ }, context.signal);
2959
+ } catch (cause) {
2960
+ if (context.signal?.aborted) throw cause;
2961
+ const detail = (cause instanceof Error ? cause.message : String(cause)).slice(0, 500);
2962
+ return { content: `Tool "${tool.name}" failed: ${detail}`, isError: true };
2963
+ }
2952
2964
  }
2953
2965
  }))
2954
2966
  }));
@@ -4417,6 +4429,96 @@ function observeCodeRuntimeSessionSkills(command, skills, emit) {
4417
4429
  }));
4418
4430
  }
4419
4431
 
4432
+ // src/code-repository-recipes.ts
4433
+ var import_promises15 = require("fs/promises");
4434
+ var import_node_path13 = require("path");
4435
+ var REPOSITORY_RECIPES_FILE = "odla.recipes.json";
4436
+ var MAX_RECIPES = 16;
4437
+ var DEFAULT_TIMEOUT_MS = 12e4;
4438
+ var MAX_TIMEOUT_MS = 15 * 6e4;
4439
+ var ID2 = /^[A-Za-z0-9._:-]{1,120}$/;
4440
+ var RECIPE_FIELDS = /* @__PURE__ */ new Set(["id", "command", "timeoutMs"]);
4441
+ var fault = (detail) => new TypeError(`${REPOSITORY_RECIPES_FILE} is malformed: ${detail}`);
4442
+ function parseRepositoryRecipes(text2, envelope) {
4443
+ let parsed;
4444
+ try {
4445
+ parsed = JSON.parse(text2);
4446
+ } catch {
4447
+ throw fault("not valid JSON");
4448
+ }
4449
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw fault("the document must be an object");
4450
+ const document = parsed;
4451
+ if (document.version !== 1) throw fault("version must be 1");
4452
+ const entries = document.recipes;
4453
+ if (!Array.isArray(entries) || entries.length < 1) throw fault("recipes must be a non-empty array");
4454
+ if (entries.length > MAX_RECIPES) throw fault(`at most ${MAX_RECIPES} recipes may be declared`);
4455
+ const recipes = [];
4456
+ const seen = /* @__PURE__ */ new Set();
4457
+ entries.forEach((entry, index) => {
4458
+ const label = `recipe ${index + 1}`;
4459
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw fault(`${label} must be an object`);
4460
+ const row = entry;
4461
+ const unknown = Object.keys(row).filter((key) => !RECIPE_FIELDS.has(key));
4462
+ if (unknown.length) throw fault(`${label} has unsupported field ${unknown[0]}; the host owns image and resource limits`);
4463
+ if (typeof row.id !== "string" || !ID2.test(row.id)) throw fault(`${label} needs an id of 1 to 120 letters, digits, . _ : or -`);
4464
+ if (seen.has(row.id)) throw fault(`${label} repeats id ${row.id}`);
4465
+ seen.add(row.id);
4466
+ if (!Array.isArray(row.command) || row.command.length < 1 || row.command.some((part) => typeof part !== "string" || !part)) {
4467
+ throw fault(`${label} (${row.id}) needs command as a non-empty array of non-empty strings`);
4468
+ }
4469
+ const timeoutMs = row.timeoutMs ?? DEFAULT_TIMEOUT_MS;
4470
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
4471
+ throw fault(`${label} (${row.id}) timeoutMs must be an integer from 1 to ${MAX_TIMEOUT_MS}`);
4472
+ }
4473
+ const recipe2 = {
4474
+ id: row.id,
4475
+ command: [...row.command],
4476
+ timeoutMs,
4477
+ image: envelope.image,
4478
+ maxOutputBytes: envelope.maxOutputBytes,
4479
+ cpus: envelope.cpus,
4480
+ memory: envelope.memory,
4481
+ pids: envelope.pids
4482
+ };
4483
+ try {
4484
+ assertCodeBuildRecipe(recipe2);
4485
+ } catch (cause) {
4486
+ throw fault(`${label} (${row.id}) is not a runnable recipe: ${cause instanceof Error ? cause.message : String(cause)}`);
4487
+ }
4488
+ recipes.push(recipe2);
4489
+ });
4490
+ return recipes;
4491
+ }
4492
+ async function readRepositoryRecipes(baselineDir, envelope) {
4493
+ let text2;
4494
+ try {
4495
+ text2 = await (0, import_promises15.readFile)((0, import_node_path13.join)(baselineDir, REPOSITORY_RECIPES_FILE), "utf8");
4496
+ } catch (cause) {
4497
+ if (cause.code === "ENOENT") return null;
4498
+ throw cause;
4499
+ }
4500
+ return parseRepositoryRecipes(text2, envelope);
4501
+ }
4502
+ async function resolveCodeRecipes(baselineDir, release, envelope) {
4503
+ const declared = envelope ? await readRepositoryRecipes(baselineDir, envelope) : null;
4504
+ return declared ? { recipes: declared, source: "repository" } : { recipes: release, source: "release" };
4505
+ }
4506
+ function describeCodeRecipes(resolved) {
4507
+ const ids = resolved.recipes.map((recipe2) => recipe2.id).join(", ");
4508
+ return resolved.source === "repository" ? `Verification recipes: declared by the repository in ${REPOSITORY_RECIPES_FILE} \xB7 ${ids}` : `Verification recipes: release-owned (the repository declares none in ${REPOSITORY_RECIPES_FILE}) \xB7 ${ids}`;
4509
+ }
4510
+ async function sessionRecipesFor(workspace, options, resume) {
4511
+ const resolved = await resolveCodeRecipes(workspace.baselineDir, options.recipes, options.repositoryRecipes ?? null).catch(async (cause) => {
4512
+ await workspace.cleanup();
4513
+ throw cause;
4514
+ });
4515
+ return {
4516
+ ...resolved,
4517
+ buildPolicyDigest: digestRuntimeValue(JSON.stringify(resolved.recipes)),
4518
+ note: !resume && resolved.source === "repository" ? describeCodeRecipes(resolved) : null
4519
+ };
4520
+ }
4521
+
4420
4522
  // src/code-runtime-engine.ts
4421
4523
  var TheseusRuntimeEngine = class {
4422
4524
  constructor(options) {
@@ -4468,6 +4570,7 @@ var TheseusRuntimeEngine = class {
4468
4570
  control: this.options.control,
4469
4571
  ...this.options.localSource ? { localSource: this.options.localSource } : {}
4470
4572
  });
4573
+ const resolvedRecipes = await sessionRecipesFor(workspace, this.options, resume);
4471
4574
  const abort = new AbortController(), startGate = codeRuntimeAcknowledgementGate(abort.signal);
4472
4575
  const conversationRefs = [];
4473
4576
  const active = {
@@ -4476,6 +4579,8 @@ var TheseusRuntimeEngine = class {
4476
4579
  conversationRefs,
4477
4580
  acknowledged: false,
4478
4581
  startGate,
4582
+ recipes: resolvedRecipes.recipes,
4583
+ buildPolicyDigest: resolvedRecipes.buildPolicyDigest,
4479
4584
  role: metadata.role,
4480
4585
  readOnly: metadata.readOnly,
4481
4586
  title: metadata.title,
@@ -4493,6 +4598,7 @@ var TheseusRuntimeEngine = class {
4493
4598
  done: Promise.resolve(null)
4494
4599
  };
4495
4600
  this.#active.set(command.sessionId, active);
4601
+ if (resolvedRecipes.note) await this.#event(command, { type: "message", actor: "system", body: resolvedRecipes.note }, conversationRefs);
4496
4602
  if (requestedLocal) {
4497
4603
  await this.#event(command, {
4498
4604
  type: "message",
@@ -4528,7 +4634,7 @@ var TheseusRuntimeEngine = class {
4528
4634
  const active = await this.#takeOver(command, "pursue requires an active Code session");
4529
4635
  active.done = startGoalPursuit({
4530
4636
  spec,
4531
- recipes: this.options.recipes,
4637
+ recipes: active.recipes,
4532
4638
  recipeExecutor: this.options.recipeExecutor ?? createContainerRecipeExecutor(this.options.engine),
4533
4639
  workspace: active.workspace,
4534
4640
  baseCommitSha: active.baseCommitSha,
@@ -4607,7 +4713,7 @@ var TheseusRuntimeEngine = class {
4607
4713
  async #runAttempt(command, metadata, active) {
4608
4714
  const lease = fakeCodeLease(command, metadata);
4609
4715
  const broker = this.#observed(command, active, createCodeRuntimeToolBroker({
4610
- recipes: this.options.recipes,
4716
+ recipes: active.recipes,
4611
4717
  engine: this.options.engine,
4612
4718
  recipeAuthorization: this.options.recipeAuthorization
4613
4719
  }, lease, metadata.role));
@@ -4633,7 +4739,7 @@ var TheseusRuntimeEngine = class {
4633
4739
  // A review-role session is read-only and still runs the proof; a planner
4634
4740
  // marked read-only by its payload runs nothing.
4635
4741
  recipes: metadata.role === "review" || !metadata.readOnly,
4636
- recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
4742
+ recipeIds: active.recipes.map((recipe2) => recipe2.id),
4637
4743
  ...extraSkills.length ? { extraSkills } : {}
4638
4744
  });
4639
4745
  const closing = result.finalText.trim();
@@ -4964,12 +5070,12 @@ function chooseStrategy(signals = {}) {
4964
5070
  var feedbackIsActionable = actionable;
4965
5071
 
4966
5072
  // src/code-recipe-dependencies.ts
4967
- var import_promises15 = require("fs/promises");
4968
- var import_node_path13 = require("path");
5073
+ var import_promises16 = require("fs/promises");
5074
+ var import_node_path14 = require("path");
4969
5075
  var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
4970
5076
  function withRecipeDependencies(executor, dependencies) {
4971
5077
  const mountAs = dependencies.mountAs ?? "node_modules";
4972
- if (!(0, import_node_path13.isAbsolute)(dependencies.source)) {
5078
+ if (!(0, import_node_path14.isAbsolute)(dependencies.source)) {
4973
5079
  throw new TypeError("recipe dependency source must be an absolute path");
4974
5080
  }
4975
5081
  if (!RESERVED_MOUNTS.has(mountAs)) {
@@ -4977,24 +5083,24 @@ function withRecipeDependencies(executor, dependencies) {
4977
5083
  }
4978
5084
  return {
4979
5085
  run: async (input) => {
4980
- const target = (0, import_node_path13.join)(input.workspaceDir, mountAs);
5086
+ const target = (0, import_node_path14.join)(input.workspaceDir, mountAs);
4981
5087
  let linked = false;
4982
5088
  try {
4983
- const existing = await (0, import_promises15.lstat)(target).catch(() => null);
5089
+ const existing = await (0, import_promises16.lstat)(target).catch(() => null);
4984
5090
  if (!existing) {
4985
- await (0, import_promises15.symlink)(dependencies.source, target, "dir");
5091
+ await (0, import_promises16.symlink)(dependencies.source, target, "dir");
4986
5092
  linked = true;
4987
5093
  }
4988
5094
  return await executor.run(input);
4989
5095
  } finally {
4990
- if (linked) await (0, import_promises15.rm)(target, { force: true, recursive: false }).catch(() => void 0);
5096
+ if (linked) await (0, import_promises16.rm)(target, { force: true, recursive: false }).catch(() => void 0);
4991
5097
  }
4992
5098
  }
4993
5099
  };
4994
5100
  }
4995
5101
  async function installedDependencies(repoRoot) {
4996
- const source = (0, import_node_path13.join)(repoRoot, "node_modules");
4997
- const info = await (0, import_promises15.lstat)(source).catch(() => null);
5102
+ const source = (0, import_node_path14.join)(repoRoot, "node_modules");
5103
+ const info = await (0, import_promises16.lstat)(source).catch(() => null);
4998
5104
  return info?.isDirectory() ? { source } : null;
4999
5105
  }
5000
5106
  // Annotate the CommonJS export names for ESM import in node:
@@ -5006,6 +5112,7 @@ async function installedDependencies(repoRoot) {
5006
5112
  DecompositionError,
5007
5113
  MAX_MEMORY_BODY,
5008
5114
  MEASURED_PREMIUM,
5115
+ REPOSITORY_RECIPES_FILE,
5009
5116
  SYSTEM_PROMPT_FOR,
5010
5117
  TheseusRuntimeEngine,
5011
5118
  V1_SYSTEM_PROMPT,
@@ -5027,6 +5134,7 @@ async function installedDependencies(repoRoot) {
5027
5134
  createCodeToolBroker,
5028
5135
  createCodeWorkspaceCheckpoint,
5029
5136
  createContainerRecipeExecutor,
5137
+ describeCodeRecipes,
5030
5138
  describePatchFailure,
5031
5139
  digestStagedWorkspace,
5032
5140
  feedbackIsActionable,
@@ -5041,16 +5149,19 @@ async function installedDependencies(repoRoot) {
5041
5149
  materializeGitTree,
5042
5150
  outcomeCloses,
5043
5151
  outcomeMemory,
5152
+ parseRepositoryRecipes,
5044
5153
  patchPaths,
5045
5154
  planReachCollisions,
5046
5155
  prepareRuntimeCheckpoint,
5047
5156
  racedAttempt,
5048
5157
  readOnlyNotice,
5158
+ readRepositoryRecipes,
5049
5159
  recallAbout,
5050
5160
  registeredFiles,
5051
5161
  renderMemories,
5052
5162
  renderOutcome,
5053
5163
  resolveCodePath,
5164
+ resolveCodeRecipes,
5054
5165
  restoreCodeWorkspaceCheckpoint,
5055
5166
  runCodeAgent,
5056
5167
  runCodeAgentAttempt,
@@ -5063,6 +5174,7 @@ async function installedDependencies(repoRoot) {
5063
5174
  safeWorkspaceLabel,
5064
5175
  selectContainerEngine,
5065
5176
  selectWinner,
5177
+ sessionRecipesFor,
5066
5178
  sessionSkillsFor,
5067
5179
  stageWorkspace,
5068
5180
  stageWorkspacePair,