@odla-ai/harness 0.10.1 → 0.10.3

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.
Files changed (38) hide show
  1. package/dist/{chunk-U324RQ4N.js → chunk-CPV7ZVHH.js} +1 -1
  2. package/dist/{chunk-U324RQ4N.js.map → chunk-CPV7ZVHH.js.map} +1 -1
  3. package/dist/{chunk-OZBJNTML.js → chunk-ESLFS3FY.js} +4 -4
  4. package/dist/{chunk-5LRYJKUI.js → chunk-FUDTSAOZ.js} +3 -3
  5. package/dist/{chunk-VDY5V7ZG.js → chunk-JV45JAAW.js} +3 -3
  6. package/dist/chunk-JV45JAAW.js.map +1 -0
  7. package/dist/{chunk-FAN2R3GW.js → chunk-KXFI3WM2.js} +5 -1
  8. package/dist/chunk-KXFI3WM2.js.map +1 -0
  9. package/dist/{chunk-WM34GGTK.js → chunk-TMBYA5JX.js} +107 -13
  10. package/dist/chunk-TMBYA5JX.js.map +1 -0
  11. package/dist/cli.cjs +1 -1
  12. package/dist/cli.cjs.map +1 -1
  13. package/dist/cli.js +4 -4
  14. package/dist/code-runtime-cli.cjs +179 -79
  15. package/dist/code-runtime-cli.cjs.map +1 -1
  16. package/dist/code-runtime-cli.js +5 -5
  17. package/dist/index.cjs +5 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +2 -2
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +3 -3
  22. package/dist/node.cjs +183 -83
  23. package/dist/node.cjs.map +1 -1
  24. package/dist/node.d.cts +11 -2
  25. package/dist/node.d.ts +11 -2
  26. package/dist/node.js +6 -6
  27. package/dist/testing.cjs.map +1 -1
  28. package/dist/testing.d.cts +1 -1
  29. package/dist/testing.d.ts +1 -1
  30. package/dist/testing.js +1 -1
  31. package/dist/{types-_8y8vBDI.d.ts → types-ocy70g_p.d.cts} +1 -1
  32. package/dist/{types-_8y8vBDI.d.cts → types-ocy70g_p.d.ts} +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-FAN2R3GW.js.map +0 -1
  35. package/dist/chunk-VDY5V7ZG.js.map +0 -1
  36. package/dist/chunk-WM34GGTK.js.map +0 -1
  37. /package/dist/{chunk-OZBJNTML.js.map → chunk-ESLFS3FY.js.map} +0 -0
  38. /package/dist/{chunk-5LRYJKUI.js.map → chunk-FUDTSAOZ.js.map} +0 -0
@@ -6,4 +6,4 @@ export {
6
6
  HARNESS_PROTOCOL_VERSION,
7
7
  DEFAULT_AI_ROUTE
8
8
  };
9
- //# sourceMappingURL=chunk-U324RQ4N.js.map
9
+ //# sourceMappingURL=chunk-CPV7ZVHH.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { ChatInput, OracleResponse } from \"@odla-ai/ai\";\nimport type { CodeToolPresentation } from \"./code-session-event-types\";\nexport type { CodeToolLocationPreview, CodeToolPresentation } from \"./code-session-event-types\";\n\n/** Current JSONL protocol version exchanged between a runner and an agent container. */\nexport const HARNESS_PROTOCOL_VERSION = 1 as const;\n\n/** Default control-plane route used for model inference requested by coding agents. */\nexport const DEFAULT_AI_ROUTE = \"coding\" as const;\n\n/** Lifecycle state reported for a harness task and its active attempt. */\nexport type HarnessTaskStatus =\n | \"queued\"\n | \"running\"\n | \"cancel_requested\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\";\n\n/** Lifecycle state of one execution attempt for a task. */\nexport type HarnessAttemptStatus = HarnessTaskStatus;\n\n/** Trusted or untrusted participant that emitted a harness event. */\nexport type HarnessActor = \"operator\" | \"runner\" | \"agent\" | \"model\" | \"system\";\n\n/** Resource and isolation limits enforced while an untrusted task executes. */\nexport interface HarnessPolicy {\n network: \"none\";\n timeoutMs: number;\n maxOutputBytes: number;\n maxPatchBytes: number;\n}\n\n/** Immutable task instructions and execution policy delivered with a lease. */\nexport interface HarnessTaskSpec {\n taskId: string;\n attemptId: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n policy: HarnessPolicy;\n parentAttemptId?: string | null;\n checkpointSeq?: number | null;\n}\n\n/** Time-bound assignment authorizing a runner to execute one task attempt. */\nexport interface HarnessLease {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n leaseId: string;\n generation: number;\n expiresAt: number;\n task: HarnessTaskSpec;\n}\n\n/** Runner-supplied event before the control plane assigns sequence and task metadata. */\nexport interface HarnessEventInput {\n eventId: string;\n kind: string;\n actor: HarnessActor;\n payload: unknown;\n createdAt: number;\n}\n\n/** Persisted, ordered event associated with a specific task attempt. */\nexport interface HarnessEvent extends HarnessEventInput {\n seq: number;\n taskId: string;\n attemptId: string;\n}\n\n/** List-view metadata for a task and its current attempt. */\nexport interface HarnessTaskSummary {\n taskId: string;\n attemptId: string;\n appId: string;\n env: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n status: HarnessTaskStatus;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** List-view metadata for one attempt, including retry ancestry and runner ownership. */\nexport interface HarnessAttemptSummary {\n attemptId: string;\n taskId: string;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n status: HarnessAttemptStatus;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** Complete task view including attempts, events, result, and generated patch. */\nexport interface HarnessTaskDetail extends HarnessTaskSummary {\n attempts: HarnessAttemptSummary[];\n events: HarnessEvent[];\n patch: string | null;\n result: unknown;\n}\n\n/** Public control-plane view of a registered harness runner. */\nexport interface HarnessRunnerView {\n runnerId: string;\n appId: string;\n env: string;\n name: string;\n createdAt: number;\n lastSeenAt: number | null;\n revokedAt: number | null;\n}\n\n/** Credential-free normalized model request sent from an agent through the runner. */\nexport interface HarnessInferenceRequest {\n requestId: string;\n /** Exact initial, follow-up, or resume command whose budget this call consumes. */\n interactionId?: string;\n call: ChatInput;\n}\n\n/** Normalized model response plus auditable provider, policy, and token metadata. */\nexport interface HarnessInferenceResponse {\n requestId: string;\n response: OracleResponse;\n receipt: {\n provider: string;\n model: string;\n policyVersion: number;\n inputTokens: number;\n outputTokens: number;\n /**\n * USD charged for this call, priced against the model the control plane\n * actually resolved.\n *\n * ABSENT when the live catalog has no price for that model — never zero.\n * An unpriced call is unknown spend, and reporting it as free is what let\n * a goal's `maxUsd` look enforced while nothing enforced it. The runtime\n * cannot compute this itself: it asks for `brokered` and only the control\n * plane knows which model answered.\n */\n costUsd?: number;\n };\n}\n\n/** Bounded, content-minimized session activity emitted by a Code runtime.\n * Message bodies are projected separately into the app's owner-private\n * odla-db chat. `interactionId` is optional so stored v1 events remain valid. */\nexport type CodeSessionEventData = (\n | { type: \"message\"; actor: \"agent\" | \"system\"; body: string }\n | { type: \"diagnostic\"; level: \"error\"; message: string }\n | { type: \"thinking\"; available: true; durationMs: number }\n | {\n type: \"tool\";\n phase: \"started\";\n tool: HarnessToolName;\n operationId?: string;\n presentation?: CodeToolPresentation;\n }\n | {\n type: \"tool\";\n phase: \"completed\";\n tool: HarnessToolName;\n ok: boolean;\n durationMs: number;\n operationId?: string;\n /** Why the call failed, bounded. Absent when `ok`. Without this a watcher\n * saw that a tool failed and never why, which is what made an 84%\n * apply_patch failure rate impossible to diagnose (PM bug 515655ec). */\n failureReason?: string;\n presentation?: CodeToolPresentation;\n }\n | {\n type: \"collaboration\";\n phase: \"started\";\n skill: string;\n tool: string;\n operationId: string;\n }\n | {\n type: \"collaboration\";\n phase: \"completed\";\n skill: string;\n tool: string;\n ok: boolean;\n durationMs: number;\n operationId: string;\n }\n | {\n type: \"usage\"; provider: string; model: string;\n inputTokens: number; outputTokens: number; durationMs: number;\n interactionTokens?: number; interactionMaxTokens?: number;\n /** USD for this call; absent when the model is unpriced, never zero. */\n costUsd?: number;\n /** Cumulative USD for this owner interaction, when every call in it was\n * priced. Absent the moment one was not, so a partial total can never be\n * mistaken for the whole. */\n interactionCostUsd?: number;\n }\n | {\n type: \"status\"; status: \"running\" | \"idle\" | \"failed\" | \"checkpointed\";\n durationMs?: number;\n }\n) & { interactionId?: string };\n\n/** Registry-assigned cursor and timestamp for an owner-visible Code event. */\nexport type CodeSessionEvent = CodeSessionEventData & {\n eventId: string; sequence: number; createdAt: number;\n};\n\n/** Closed set of effects an agent container may request from its trusted broker. */\nexport type HarnessToolName =\n | \"sandbox.read\"\n | \"sandbox.list\"\n | \"sandbox.search\"\n | \"sandbox.overview\"\n | \"sandbox.where_is\"\n | \"sandbox.who_imports\"\n | \"sandbox.who_touches\"\n | \"sandbox.apply_patch\"\n | \"sandbox.run_recipe\";\n/** Correlated, structured tool request emitted by an untrusted agent container. */\nexport interface HarnessToolRequest {\n requestId: string;\n tool: HarnessToolName;\n input: Record<string, unknown>;\n}\n/** Bounded tool result returned to an agent after trusted policy evaluation. */\nexport interface HarnessToolResponse {\n requestId: string;\n ok: boolean;\n content: string;\n details?: Record<string, unknown>;\n}\n\n/** Validated JSONL message emitted by an untrusted agent container. */\nexport type HarnessAgentOutput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"event\";\n kind: string;\n payload?: unknown;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.request\";\n requestId: string;\n call: ChatInput;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.request\" } & HarnessToolRequest)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.complete\";\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n };\n\n/** JSONL command or inference result written by the trusted runner to an agent. */\nexport type HarnessAgentInput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"task.start\";\n task: HarnessTaskSpec;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.response\";\n requestId: string;\n response: OracleResponse;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.response\" } & HarnessToolResponse)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.cancel\";\n reason: string;\n };\n\n/** Terminal attempt report submitted by a runner to the control plane. */\nexport interface HarnessCompletion {\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n patch?: string;\n error?: string;\n}\n\n/** Operations a credentialed runner may perform against the harness control plane. */\nexport interface HarnessControlPlane {\n lease(workspaces: string[]): Promise<HarnessLease | null>;\n heartbeat(attemptId: string, leaseId: string): Promise<{ cancelRequested: boolean; expiresAt: number }>;\n appendEvents(attemptId: string, leaseId: string, events: HarnessEventInput[]): Promise<void>;\n infer(attemptId: string, leaseId: string, request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n complete(attemptId: string, leaseId: string, completion: HarnessCompletion): Promise<void>;\n}\n\n/** Trusted inference bridge used to keep model credentials outside agent containers. */\nexport interface HarnessAiConnection {\n infer(request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n}\n\n/** Trusted tool boundary. Implementations must evaluate CaMeL policy before effects. */\nexport interface HarnessToolBroker {\n execute(\n context: { lease: HarnessLease; workspaceDir: string; signal?: AbortSignal },\n request: HarnessToolRequest,\n ): Promise<HarnessToolResponse>;\n}\n"],"mappings":";AAKO,IAAM,2BAA2B;AAGjC,IAAM,mBAAmB;","names":[]}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { ChatInput, OracleResponse } from \"@odla-ai/ai\";\nimport type { CodeToolPresentation } from \"./code-session-event-types\";\nexport type { CodeToolLocationPreview, CodeToolPresentation } from \"./code-session-event-types\";\n\n/** Current JSONL protocol version exchanged between a runner and an agent container. */\nexport const HARNESS_PROTOCOL_VERSION = 1 as const;\n\n/** Default control-plane route used for model inference requested by coding agents. */\nexport const DEFAULT_AI_ROUTE = \"coding\" as const;\n\n/** Lifecycle state reported for a harness task and its active attempt. */\nexport type HarnessTaskStatus =\n | \"queued\"\n | \"running\"\n | \"cancel_requested\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\";\n\n/** Lifecycle state of one execution attempt for a task. */\nexport type HarnessAttemptStatus = HarnessTaskStatus;\n\n/** Trusted or untrusted participant that emitted a harness event. */\nexport type HarnessActor = \"operator\" | \"runner\" | \"agent\" | \"model\" | \"system\";\n\n/** Resource and isolation limits enforced while an untrusted task executes. */\nexport interface HarnessPolicy {\n network: \"none\";\n timeoutMs: number;\n maxOutputBytes: number;\n maxPatchBytes: number;\n}\n\n/** Immutable task instructions and execution policy delivered with a lease. */\nexport interface HarnessTaskSpec {\n taskId: string;\n attemptId: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n policy: HarnessPolicy;\n parentAttemptId?: string | null;\n checkpointSeq?: number | null;\n}\n\n/** Time-bound assignment authorizing a runner to execute one task attempt. */\nexport interface HarnessLease {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n leaseId: string;\n generation: number;\n expiresAt: number;\n task: HarnessTaskSpec;\n}\n\n/** Runner-supplied event before the control plane assigns sequence and task metadata. */\nexport interface HarnessEventInput {\n eventId: string;\n kind: string;\n actor: HarnessActor;\n payload: unknown;\n createdAt: number;\n}\n\n/** Persisted, ordered event associated with a specific task attempt. */\nexport interface HarnessEvent extends HarnessEventInput {\n seq: number;\n taskId: string;\n attemptId: string;\n}\n\n/** List-view metadata for a task and its current attempt. */\nexport interface HarnessTaskSummary {\n taskId: string;\n attemptId: string;\n appId: string;\n env: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n status: HarnessTaskStatus;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** List-view metadata for one attempt, including retry ancestry and runner ownership. */\nexport interface HarnessAttemptSummary {\n attemptId: string;\n taskId: string;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n status: HarnessAttemptStatus;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** Complete task view including attempts, events, result, and generated patch. */\nexport interface HarnessTaskDetail extends HarnessTaskSummary {\n attempts: HarnessAttemptSummary[];\n events: HarnessEvent[];\n patch: string | null;\n result: unknown;\n}\n\n/** Public control-plane view of a registered harness runner. */\nexport interface HarnessRunnerView {\n runnerId: string;\n appId: string;\n env: string;\n name: string;\n createdAt: number;\n lastSeenAt: number | null;\n revokedAt: number | null;\n}\n\n/** Credential-free normalized model request sent from an agent through the runner. */\nexport interface HarnessInferenceRequest {\n requestId: string;\n /** Exact initial, follow-up, or resume command whose budget this call consumes. */\n interactionId?: string;\n call: ChatInput;\n}\n\n/** Normalized model response plus auditable provider, policy, and token metadata. */\nexport interface HarnessInferenceResponse {\n requestId: string;\n response: OracleResponse;\n receipt: {\n provider: string;\n model: string;\n policyVersion: number;\n inputTokens: number;\n outputTokens: number;\n /**\n * USD charged for this call, priced against the model the control plane\n * actually resolved.\n *\n * ABSENT when the live catalog has no price for that model — never zero.\n * An unpriced call is unknown spend, and reporting it as free is what let\n * a goal's `maxUsd` look enforced while nothing enforced it. The runtime\n * cannot compute this itself: it asks for `brokered` and only the control\n * plane knows which model answered.\n */\n costUsd?: number;\n };\n}\n\n/** Bounded, content-minimized session activity emitted by a Code runtime.\n * Message bodies are projected separately into the app's owner-private\n * odla-db chat. `interactionId` is optional so stored v1 events remain valid. */\nexport type CodeSessionEventData = (\n | { type: \"message\"; actor: \"agent\" | \"system\"; body: string }\n | { type: \"diagnostic\"; level: \"error\"; message: string }\n | { type: \"thinking\"; available: true; durationMs: number }\n | {\n type: \"tool\";\n phase: \"started\";\n tool: HarnessToolName;\n operationId?: string;\n presentation?: CodeToolPresentation;\n }\n | {\n type: \"tool\";\n phase: \"completed\";\n tool: HarnessToolName;\n ok: boolean;\n durationMs: number;\n operationId?: string;\n /** Why the call failed, bounded. Absent when `ok`. Without this a watcher\n * saw that a tool failed and never why, which is what made an 84%\n * apply_patch failure rate impossible to diagnose (PM bug 515655ec). */\n failureReason?: string;\n presentation?: CodeToolPresentation;\n }\n | {\n type: \"collaboration\";\n phase: \"started\";\n skill: string;\n tool: string;\n operationId: string;\n }\n | {\n type: \"collaboration\";\n phase: \"completed\";\n skill: string;\n tool: string;\n ok: boolean;\n durationMs: number;\n operationId: string;\n }\n | {\n type: \"usage\"; provider: string; model: string;\n inputTokens: number; outputTokens: number; durationMs: number;\n interactionTokens?: number; interactionMaxTokens?: number;\n /** USD for this call; absent when the model is unpriced, never zero. */\n costUsd?: number;\n /** Cumulative USD for this owner interaction, when every call in it was\n * priced. Absent the moment one was not, so a partial total can never be\n * mistaken for the whole. */\n interactionCostUsd?: number;\n }\n | {\n type: \"status\"; status: \"running\" | \"idle\" | \"failed\" | \"checkpointed\";\n durationMs?: number;\n }\n) & { interactionId?: string };\n\n/** Registry-assigned cursor and timestamp for an owner-visible Code event. */\nexport type CodeSessionEvent = CodeSessionEventData & {\n eventId: string; sequence: number; createdAt: number;\n};\n\n/** Closed set of effects an agent container may request from its trusted broker. */\nexport type HarnessToolName =\n | \"sandbox.read\"\n | \"sandbox.list\"\n | \"sandbox.search\"\n | \"sandbox.overview\"\n | \"sandbox.where_is\"\n | \"sandbox.who_imports\"\n | \"sandbox.who_touches\"\n | \"sandbox.apply_patch\"\n | \"sandbox.edit\"\n | \"sandbox.run_recipe\";\n/** Correlated, structured tool request emitted by an untrusted agent container. */\nexport interface HarnessToolRequest {\n requestId: string;\n tool: HarnessToolName;\n input: Record<string, unknown>;\n}\n/** Bounded tool result returned to an agent after trusted policy evaluation. */\nexport interface HarnessToolResponse {\n requestId: string;\n ok: boolean;\n content: string;\n details?: Record<string, unknown>;\n}\n\n/** Validated JSONL message emitted by an untrusted agent container. */\nexport type HarnessAgentOutput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"event\";\n kind: string;\n payload?: unknown;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.request\";\n requestId: string;\n call: ChatInput;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.request\" } & HarnessToolRequest)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.complete\";\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n };\n\n/** JSONL command or inference result written by the trusted runner to an agent. */\nexport type HarnessAgentInput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"task.start\";\n task: HarnessTaskSpec;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.response\";\n requestId: string;\n response: OracleResponse;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.response\" } & HarnessToolResponse)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.cancel\";\n reason: string;\n };\n\n/** Terminal attempt report submitted by a runner to the control plane. */\nexport interface HarnessCompletion {\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n patch?: string;\n error?: string;\n}\n\n/** Operations a credentialed runner may perform against the harness control plane. */\nexport interface HarnessControlPlane {\n lease(workspaces: string[]): Promise<HarnessLease | null>;\n heartbeat(attemptId: string, leaseId: string): Promise<{ cancelRequested: boolean; expiresAt: number }>;\n appendEvents(attemptId: string, leaseId: string, events: HarnessEventInput[]): Promise<void>;\n infer(attemptId: string, leaseId: string, request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n complete(attemptId: string, leaseId: string, completion: HarnessCompletion): Promise<void>;\n}\n\n/** Trusted inference bridge used to keep model credentials outside agent containers. */\nexport interface HarnessAiConnection {\n infer(request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n}\n\n/** Trusted tool boundary. Implementations must evaluate CaMeL policy before effects. */\nexport interface HarnessToolBroker {\n execute(\n context: { lease: HarnessLease; workspaceDir: string; signal?: AbortSignal },\n request: HarnessToolRequest,\n ): Promise<HarnessToolResponse>;\n}\n"],"mappings":";AAKO,IAAM,2BAA2B;AAGjC,IAAM,mBAAmB;","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  runContainerAttempt,
3
3
  stageWorkspace
4
- } from "./chunk-5LRYJKUI.js";
4
+ } from "./chunk-FUDTSAOZ.js";
5
5
  import {
6
6
  makeHarnessEvent
7
- } from "./chunk-VDY5V7ZG.js";
7
+ } from "./chunk-JV45JAAW.js";
8
8
  import {
9
9
  HARNESS_PROTOCOL_VERSION
10
- } from "./chunk-U324RQ4N.js";
10
+ } from "./chunk-CPV7ZVHH.js";
11
11
 
12
12
  // src/runner.ts
13
13
  import { setTimeout as delay } from "timers/promises";
@@ -165,4 +165,4 @@ export {
165
165
  runLeasedAttempt,
166
166
  runHarnessRunner
167
167
  };
168
- //# sourceMappingURL=chunk-OZBJNTML.js.map
168
+ //# sourceMappingURL=chunk-ESLFS3FY.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  encodeAgentInput,
3
3
  parseAgentOutput
4
- } from "./chunk-VDY5V7ZG.js";
4
+ } from "./chunk-JV45JAAW.js";
5
5
  import {
6
6
  HARNESS_PROTOCOL_VERSION
7
- } from "./chunk-U324RQ4N.js";
7
+ } from "./chunk-CPV7ZVHH.js";
8
8
 
9
9
  // src/container.ts
10
10
  import { execFile, spawn } from "child_process";
@@ -548,4 +548,4 @@ export {
548
548
  stageWorkspacePair,
549
549
  safeWorkspaceLabel
550
550
  };
551
- //# sourceMappingURL=chunk-5LRYJKUI.js.map
551
+ //# sourceMappingURL=chunk-FUDTSAOZ.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HARNESS_PROTOCOL_VERSION
3
- } from "./chunk-U324RQ4N.js";
3
+ } from "./chunk-CPV7ZVHH.js";
4
4
 
5
5
  // src/protocol.ts
6
6
  var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
@@ -51,7 +51,7 @@ function parseAgentOutput(line) {
51
51
  if (message.type === "tool.request") {
52
52
  const input = record(message.input);
53
53
  const tool = String(message.tool);
54
- if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
54
+ if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.edit", "sandbox.run_recipe"].includes(tool)) {
55
55
  throw new HarnessProtocolError("tool.request requires a registered tool and object input");
56
56
  }
57
57
  return {
@@ -90,4 +90,4 @@ export {
90
90
  encodeAgentInput,
91
91
  makeHarnessEvent
92
92
  };
93
- //# sourceMappingURL=chunk-VDY5V7ZG.js.map
93
+ //# sourceMappingURL=chunk-JV45JAAW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["import type { ChatInput } from \"@odla-ai/ai\";\nimport {\n HARNESS_PROTOCOL_VERSION,\n type HarnessAgentInput,\n type HarnessAgentOutput,\n type HarnessEventInput,\n} from \"./types\";\n\nconst CONTROL = /[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/;\n\n/** Error raised when an agent emits malformed, oversized, or unsupported protocol data. */\nexport class HarnessProtocolError extends Error {\n override readonly name = \"HarnessProtocolError\";\n}\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n}\n\nfunction boundedText(value: unknown, label: string, max: number): string {\n if (typeof value !== \"string\" || !value || value.length > max || CONTROL.test(value)) {\n throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);\n }\n return value;\n}\n\n/** Parse and validate one newline-delimited message emitted by an agent container. */\nexport function parseAgentOutput(line: string): HarnessAgentOutput {\n if (Buffer.byteLength(line, \"utf8\") > 1_000_000) throw new HarnessProtocolError(\"agent message exceeds 1 MB\");\n let value: unknown;\n try { value = JSON.parse(line); } catch { throw new HarnessProtocolError(\"agent emitted invalid JSON\"); }\n const message = record(value);\n if (!message || message.protocolVersion !== HARNESS_PROTOCOL_VERSION) {\n throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);\n }\n if (message.type === \"event\") {\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n type: \"event\",\n kind: boundedText(message.kind, \"event.kind\", 120),\n ...(message.payload === undefined ? {} : { payload: message.payload }),\n };\n }\n if (message.type === \"inference.request\") {\n const call = record(message.call);\n if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {\n throw new HarnessProtocolError(\"inference.request.call requires messages and maxTokens\");\n }\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n type: \"inference.request\",\n requestId: boundedText(message.requestId, \"requestId\", 180),\n call: call as unknown as ChatInput,\n };\n }\n if (message.type === \"tool.request\") {\n const input = record(message.input);\n const tool = String(message.tool);\n if (!input || ![\"sandbox.read\", \"sandbox.apply_patch\", \"sandbox.edit\", \"sandbox.run_recipe\"].includes(tool)) {\n throw new HarnessProtocolError(\"tool.request requires a registered tool and object input\");\n }\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n type: \"tool.request\",\n requestId: boundedText(message.requestId, \"requestId\", 180),\n tool: tool as \"sandbox.read\" | \"sandbox.apply_patch\" | \"sandbox.edit\" | \"sandbox.run_recipe\",\n input,\n };\n }\n if (message.type === \"attempt.complete\") {\n if (!new Set([\"completed\", \"failed\", \"cancelled\"]).has(String(message.status))) {\n throw new HarnessProtocolError(\"attempt.complete.status is invalid\");\n }\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n type: \"attempt.complete\",\n status: message.status as \"completed\" | \"failed\" | \"cancelled\",\n ...(message.result === undefined ? {} : { result: message.result }),\n };\n }\n throw new HarnessProtocolError(\"agent message type is unsupported\");\n}\n\n/** Serialize one trusted runner message as a newline-terminated JSONL record. */\nexport function encodeAgentInput(message: HarnessAgentInput): string {\n return `${JSON.stringify(message)}\\n`;\n}\n\n/** Create a timestamped, uniquely identified event for control-plane submission. */\nexport function makeHarnessEvent(\n kind: string,\n actor: HarnessEventInput[\"actor\"],\n payload: unknown,\n now = Date.now(),\n id = crypto.randomUUID(),\n): HarnessEventInput {\n boundedText(kind, \"event.kind\", 120);\n return { eventId: id, kind, actor, payload, createdAt: now };\n}\n"],"mappings":"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","names":[]}
@@ -68,6 +68,10 @@ function codeToolRequestPresentation(request) {
68
68
  if (request.tool === "sandbox.apply_patch") {
69
69
  return { kind: "patch", ...patchStats(input.patch) };
70
70
  }
71
+ if (request.tool === "sandbox.edit") {
72
+ const lines = (value) => typeof value === "string" ? value.slice(0, 262144).split("\n").length : 0;
73
+ return { kind: "patch", additions: lines(input.newText), deletions: lines(input.oldText) };
74
+ }
71
75
  const recipeId = text(input.recipeId, 120);
72
76
  return recipeId ? { kind: "recipe", recipeId } : void 0;
73
77
  }
@@ -163,4 +167,4 @@ export {
163
167
  toolFailureReason,
164
168
  observedBroker
165
169
  };
166
- //# sourceMappingURL=chunk-FAN2R3GW.js.map
170
+ //# sourceMappingURL=chunk-KXFI3WM2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/code-tool-presentation.ts","../src/code-runtime-observer.ts"],"sourcesContent":["import type {\n CodeToolLocationPreview, CodeToolPresentation, HarnessToolRequest, HarnessToolResponse,\n} from \"./types\";\n\nconst text = (value: unknown, maximum: number): string | undefined => {\n if (typeof value !== \"string\") return undefined;\n const bounded = value.replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/g, \" \").trim();\n return bounded ? bounded.slice(0, maximum) : undefined;\n};\nconst integer = (value: unknown): number | undefined =>\n Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : undefined;\nconst record = (value: unknown): Record<string, unknown> | undefined =>\n value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown> : undefined;\nconst excerpt = (value: unknown, tail = false): string | undefined => {\n if (typeof value !== \"string\") return undefined;\n const safe = value.replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/g, \" \");\n const source = (tail ? safe.slice(-10_000) : safe.slice(0, 10_000)).trim();\n if (!source) return undefined;\n const lines = source.split(\"\\n\").filter((line) => line.trim()).map((line) => line.slice(0, 240));\n const selected = tail ? lines.slice(-10) : lines.slice(0, 10);\n return text(selected.join(\"\\n\"), 2_400);\n};\nconst paths = (value: unknown): string[] | undefined => {\n if (!Array.isArray(value)) return undefined;\n const items = value.flatMap((item) => {\n const path = text(item, 1_024);\n return path ? [path] : [];\n }).slice(0, 12);\n return items.length ? items : undefined;\n};\n\nfunction patchStats(value: unknown): { additions?: number; deletions?: number } {\n if (typeof value !== \"string\") return {};\n let additions = 0;\n let deletions = 0;\n for (const line of value.slice(0, 262_144).split(\"\\n\")) {\n if (line.startsWith(\"+++\") || line.startsWith(\"---\")) continue;\n if (line.startsWith(\"+\")) additions += 1;\n else if (line.startsWith(\"-\")) deletions += 1;\n }\n return { ...(additions ? { additions } : {}), ...(deletions ? { deletions } : {}) };\n}\n\nfunction searchResults(value: unknown): CodeToolLocationPreview[] | undefined {\n if (typeof value !== \"string\") return undefined;\n const results = value.split(\"\\n\").flatMap((line) => {\n const match = /^([^:\\n]{1,1024}):(\\d+):\\s?(.*)$/.exec(line);\n if (!match) return [];\n const lineNumber = Number(match[2]);\n const itemText = text(match[3], 240);\n if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];\n return [{ path: match[1]!, line: lineNumber, ...(itemText ? { text: itemText } : {}) }];\n }).slice(0, 5);\n return results.length ? results : undefined;\n}\n\n/** Produce the bounded presentation available as soon as a broker call starts. */\nexport function codeToolRequestPresentation(request: HarnessToolRequest): CodeToolPresentation | undefined {\n const input = request.input;\n if (request.tool === \"sandbox.read\") {\n const path = text(input.path, 1_024);\n if (!path) return undefined;\n const startLine = integer(input.startLine);\n const endLine = integer(input.endLine);\n return { kind: \"read\", path, ...(startLine ? { startLine } : {}), ...(endLine ? { endLine } : {}) };\n }\n if (request.tool === \"sandbox.list\") {\n const scope = text(input.prefix, 1_024);\n return { kind: \"list\", ...(scope ? { scope } : {}) };\n }\n if (request.tool === \"sandbox.search\" || request.tool === \"sandbox.overview\"\n || request.tool === \"sandbox.where_is\" || request.tool === \"sandbox.who_imports\"\n || request.tool === \"sandbox.who_touches\") {\n const query = text(input.query, 512);\n const scope = request.tool === \"sandbox.search\" ? text(input.prefix, 1_024) : undefined;\n if (request.tool === \"sandbox.search\" && !query) return undefined;\n return { kind: \"query\", ...(query ? { query } : {}), ...(scope ? { scope } : {}) };\n }\n if (request.tool === \"sandbox.apply_patch\") {\n return { kind: \"patch\", ...patchStats(input.patch) };\n }\n if (request.tool === \"sandbox.edit\") {\n const lines = (value: unknown) => (typeof value === \"string\" ? value.slice(0, 262_144).split(\"\\n\").length : 0);\n return { kind: \"patch\", additions: lines(input.newText), deletions: lines(input.oldText) };\n }\n const recipeId = text(input.recipeId, 120);\n return recipeId ? { kind: \"recipe\", recipeId } : undefined;\n}\n\n/** Enrich a started presentation with only the broker's bounded result shape. */\nexport function codeToolResultPresentation(\n request: HarnessToolRequest,\n response: HarnessToolResponse,\n): CodeToolPresentation | undefined {\n const started = codeToolRequestPresentation(request);\n if (!started || !response.ok) return started;\n const details = record(response.details);\n if (started.kind === \"read\") {\n return {\n ...started,\n ...(integer(details?.startLine) ? { startLine: integer(details?.startLine) } : {}),\n ...(integer(details?.endLine) ? { endLine: integer(details?.endLine) } : {}),\n ...(excerpt(response.content) ? { excerpt: excerpt(response.content) } : {}),\n };\n }\n if (started.kind === \"list\") {\n const listed = response.content.split(\"\\n\")\n .filter((line) => line && !line.startsWith(\"…\") && !line.startsWith(\"Workspace \"))\n .map((line) => text(line, 1_024)).filter((line): line is string => Boolean(line)).slice(0, 8);\n return {\n ...started,\n ...(integer(details?.count) !== undefined ? { count: integer(details?.count) } : {}),\n ...(listed.length ? { paths: listed } : {}),\n };\n }\n if (started.kind === \"query\") {\n const results = request.tool === \"sandbox.search\" ? searchResults(response.content) : undefined;\n const resultExcerpt = request.tool === \"sandbox.search\" ? undefined : excerpt(response.content);\n return {\n ...started,\n ...(integer(details?.count) !== undefined ? { count: integer(details?.count) } : {}),\n ...(results ? { results } : {}),\n ...(resultExcerpt ? { excerpt: resultExcerpt } : {}),\n };\n }\n if (started.kind === \"patch\") {\n return { ...started, ...(paths(details?.paths) ? { paths: paths(details?.paths) } : {}) };\n }\n const output = response.content.replace(/^Recipe [^\\n]*\\.?\\s*/u, \"\");\n return {\n ...started,\n ...(integer(details?.exitCode) !== undefined ? { exitCode: integer(details?.exitCode) } : {}),\n ...(typeof details?.timedOut === \"boolean\" ? { timedOut: details.timedOut } : {}),\n ...(typeof details?.outputLimitExceeded === \"boolean\"\n ? { outputLimitExceeded: details.outputLimitExceeded } : {}),\n ...(excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}),\n };\n}\n","// Reporting a brokered tool effect onto the session event stream.\n//\n// Split out of code-runtime-engine.ts, which was one line under the 250 LOC cap\n// and so could not absorb the failure reason below (PM bug c775485c: the same\n// two lines left PR #684 red and unlandable). The seam is real rather than\n// convenient — this is the only code in the engine that translates a broker\n// call into what a watcher sees, and it is the only code that needs to know how\n// a failure is described.\n\nimport { codeToolRequestPresentation, codeToolResultPresentation } from \"./code-tool-presentation\";\nimport type { CodeSessionEventData, HarnessToolBroker, HarnessToolResponse } from \"./types\";\n\n/** Longest failure reason forwarded onto a session event. A reason is a short\n * diagnostic for a human or an agent reading the stream, never a transcript. */\nexport const MAX_FAILURE_REASON = 240;\n\n/** The bounded reason a failed tool response carries, if it carries one.\n *\n * Only `response.ok` used to reach the stream, so a watcher saw that a call\n * failed and could never see why — which is what made an 84% apply_patch\n * failure rate undiagnosable (PM bugs 515655ec and 38d92f1d). */\nexport function toolFailureReason(response: HarnessToolResponse): string | undefined {\n if (response.ok) return undefined;\n // Guaranteed here rather than only where the response is built, because this\n // is the boundary that emits the event: a failure reaching the stream without\n // a reason is the defect, whoever constructed the response.\n const supplied = response.details?.failureReason;\n const reason = (typeof supplied === \"string\" && supplied)\n || DEFAULT_FAILURE_REASON[response.content]\n || response.content\n || \"tool request failed; inspect the tool input and workspace state\";\n return reason.slice(0, MAX_FAILURE_REASON);\n}\n\n/** Reasons for the two refusals the broker states as bare content. */\nconst DEFAULT_FAILURE_REASON: Record<string, string> = {\n \"tool denied by CaMeL policy\": \"tool denied by CaMeL policy\",\n \"review sessions are read-only\": \"review session is read-only; workspace changes are not permitted\",\n};\n\n/** Wrap `broker` so every effect reports its start and its finish.\n *\n * `emit` is the engine's own event sink; it already swallows its own errors, so\n * reporting can never fail the effect it is reporting on.\n */\nexport function observedBroker(input: {\n broker: HarnessToolBroker;\n operationIdFor: (requestId: string) => string;\n emit: (event: CodeSessionEventData) => Promise<void>;\n now?: () => number;\n}): HarnessToolBroker {\n const now = input.now ?? Date.now;\n return {\n execute: async (context, request) => {\n const startedAt = now();\n // Stable and opaque: an older container may choose any bounded request\n // id, while the owner stream admits only a closed printable id shape.\n const operationId = input.operationIdFor(request.requestId);\n const startedPresentation = codeToolRequestPresentation(request);\n await input.emit({\n type: \"tool\", phase: \"started\", tool: request.tool, operationId,\n ...(startedPresentation ? { presentation: startedPresentation } : {}),\n });\n const response = await input.broker.execute(context, request);\n const completedPresentation = codeToolResultPresentation(request, response);\n const failureReason = toolFailureReason(response);\n await input.emit({\n type: \"tool\", phase: \"completed\", tool: request.tool,\n ok: response.ok, durationMs: now() - startedAt, operationId,\n ...(failureReason ? { failureReason } : {}),\n ...(completedPresentation ? { presentation: completedPresentation } : {}),\n });\n return response;\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  observedBroker
3
- } from "./chunk-FAN2R3GW.js";
3
+ } from "./chunk-KXFI3WM2.js";
4
4
  import {
5
5
  SKIP_WORKSPACE_DIRS,
6
6
  assertPinnedImage,
7
7
  stageWorkspace,
8
8
  stageWorkspacePair,
9
9
  verifyContainerEngineBoundary
10
- } from "./chunk-5LRYJKUI.js";
10
+ } from "./chunk-FUDTSAOZ.js";
11
11
  import {
12
12
  HARNESS_PROTOCOL_VERSION
13
- } from "./chunk-U324RQ4N.js";
13
+ } from "./chunk-CPV7ZVHH.js";
14
14
 
15
15
  // src/workspace-digest.ts
16
16
  import { createHash } from "crypto";
@@ -1801,10 +1801,12 @@ Prefer these over listing the tree \u2014 a full listing of a real repository is
1801
1801
  of thousands of tokens and you will carry it for the rest of the session.
1802
1802
 
1803
1803
  Then odla_search for a literal string, odla_read for a bounded range, and
1804
- odla_apply_git_diff to change something. A patch must start with
1805
- "diff --git a/<path> b/<path>", include matching "---" and "+++" headers and
1806
- numbered "@@" hunks with at least one line of surrounding context, and must never
1807
- use "*** Begin Patch" wrappers.
1804
+ odla_edit_file to change an existing file: give it the exact current text
1805
+ (copied verbatim from odla_read) and the text that replaces it; it must match
1806
+ once. Use odla_apply_git_diff only to create or delete whole files. A patch must
1807
+ start with "diff --git a/<path> b/<path>", include matching "---" and "+++"
1808
+ headers and numbered "@@" hunks with at least one line of surrounding context,
1809
+ and must never use "*** Begin Patch" wrappers.
1808
1810
 
1809
1811
  The workspace, model, and tool effects are controlled by the host broker.
1810
1812
  Never claim a build or test passed unless odla_run_recipe returned that result.`;
@@ -1864,6 +1866,21 @@ function codeSkill(opts) {
1864
1866
  },
1865
1867
  handler: (input, ctx) => call("sandbox.apply_patch", input, ctx.signal)
1866
1868
  };
1869
+ const editFile = {
1870
+ name: "odla_edit_file",
1871
+ description: "Change an existing file by replacing one exact stretch of its current text with new text. oldText must match the file exactly once (copy it verbatim from odla_read, including indentation and blank lines; widen it if it would match more than once). Prefer this over odla_apply_git_diff for every change to an existing file; use odla_apply_git_diff only to create or delete whole files.",
1872
+ inputSchema: {
1873
+ type: "object",
1874
+ required: ["path", "oldText", "newText"],
1875
+ properties: {
1876
+ path: { type: "string", minLength: 1, maxLength: 1024 },
1877
+ oldText: { type: "string", minLength: 1, maxLength: 262144 },
1878
+ newText: { type: "string", maxLength: 262144 }
1879
+ },
1880
+ additionalProperties: false
1881
+ },
1882
+ handler: (input, ctx) => call("sandbox.edit", input, ctx.signal)
1883
+ };
1867
1884
  const runRecipe = {
1868
1885
  name: "odla_run_recipe",
1869
1886
  description: `Run one app-registered build or test recipe through CaMeL policy.${opts.recipeIds?.length ? ` Available recipes: ${opts.recipeIds.join(", ")}.` : ""}`,
@@ -1950,8 +1967,9 @@ function codeSkill(opts) {
1950
1967
  true
1951
1968
  )
1952
1969
  ];
1953
- const effects = opts.readOnly ? [] : [applyPatch, runRecipe];
1954
- const tools = opts.surface === "v3" ? [...orientation, searchFiles, read2, ...effects] : opts.surface === "v2" ? [listFiles, searchFiles, read2, ...effects] : [read2, ...effects];
1970
+ const runsRecipes = opts.recipes ?? !opts.readOnly;
1971
+ const effects = [...opts.readOnly ? [] : [applyPatch], ...runsRecipes ? [runRecipe] : []];
1972
+ const tools = opts.surface === "v3" ? [...orientation, searchFiles, read2, ...opts.readOnly ? [] : [editFile], ...effects] : opts.surface === "v2" ? [listFiles, searchFiles, read2, ...effects] : [read2, ...effects];
1955
1973
  return { name: "code", tools };
1956
1974
  }
1957
1975
 
@@ -2013,6 +2031,7 @@ async function runCodeAgentAttempt(options) {
2013
2031
  model: "brokered",
2014
2032
  surface,
2015
2033
  ...options.readOnly === void 0 ? {} : { readOnly: options.readOnly },
2034
+ ...options.recipes === void 0 ? {} : { recipes: options.recipes },
2016
2035
  ...options.recipeIds ? { recipeIds: options.recipeIds } : {},
2017
2036
  ...options.extraSkills ? { extraSkills: options.extraSkills } : {},
2018
2037
  ...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
@@ -2397,6 +2416,11 @@ var PATCH = descriptor("sandbox.apply_patch", "reversible_mutation", {
2397
2416
  authority: "authority",
2398
2417
  patch: "payload"
2399
2418
  });
2419
+ var EDIT = descriptor("sandbox.edit", "reversible_mutation", {
2420
+ workspace: "destination",
2421
+ authority: "authority",
2422
+ patch: "payload"
2423
+ });
2400
2424
  var RECIPE = descriptor("sandbox.run_recipe", "code_execution", {
2401
2425
  workspace: "destination",
2402
2426
  authority: "authority",
@@ -2464,6 +2488,14 @@ function createCodePolicyGate(options) {
2464
2488
  patch: { role: "payload", value: patch2 }
2465
2489
  }, []);
2466
2490
  },
2491
+ edit: async (input) => {
2492
+ const base = await environment(input, options, "sandbox.edit");
2493
+ const patch2 = unsafe(base, input.patch, "patch");
2494
+ return authorize(input, options, base, EDIT, {
2495
+ ...base.fixedArgs,
2496
+ patch: { role: "payload", value: patch2 }
2497
+ }, []);
2498
+ },
2467
2499
  recipe: async (input) => {
2468
2500
  const base = await environment(input, options, "sandbox.run_recipe");
2469
2501
  const conversions = await conversionRegistry([
@@ -2608,8 +2640,8 @@ function response(request, ok, content, details) {
2608
2640
  return { requestId: request.requestId, ok, content, details: { ...details, failureReason } };
2609
2641
  }
2610
2642
 
2611
- // src/code-tool-reads.ts
2612
- import { readFile as readFile4, stat } from "fs/promises";
2643
+ // src/code-tool-edit.ts
2644
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
2613
2645
 
2614
2646
  // src/code-tool-graph.ts
2615
2647
  import { readFile as readFile3 } from "fs/promises";
@@ -2679,7 +2711,65 @@ function renderWhoTouches(graphs, query) {
2679
2711
  }).join("\n\n");
2680
2712
  }
2681
2713
 
2714
+ // src/code-tool-edit.ts
2715
+ var MAX_TEXT_BYTES = 256 * 1024;
2716
+ function editAsDiff(path, oldText, newText) {
2717
+ const removed = oldText.split("\n").map((line) => `-${line}`);
2718
+ const added = newText.split("\n").map((line) => `+${line}`);
2719
+ return `diff --git a/${path} b/${path}
2720
+ --- a/${path}
2721
+ +++ b/${path}
2722
+ @@ -1,${removed.length} +1,${added.length} @@
2723
+ ${[...removed, ...added].join("\n")}
2724
+ `;
2725
+ }
2726
+ function occurrences(haystack, needle) {
2727
+ let count = 0;
2728
+ for (let at = haystack.indexOf(needle); at !== -1; at = haystack.indexOf(needle, at + needle.length)) count += 1;
2729
+ return count;
2730
+ }
2731
+ async function editCodeFile(workspaceDir, path, oldText, newText) {
2732
+ if (!oldText) throw new TypeError("oldText must not be empty; to create a file use sandbox.apply_patch with a new-file diff");
2733
+ if (Buffer.byteLength(oldText) > MAX_TEXT_BYTES || Buffer.byteLength(newText) > MAX_TEXT_BYTES) {
2734
+ throw new TypeError(`oldText and newText must each stay under ${MAX_TEXT_BYTES} bytes; split the change`);
2735
+ }
2736
+ if (oldText.includes("\0") || newText.includes("\0")) throw new TypeError("edit text contains NUL bytes; use plain text");
2737
+ const target = resolveCodePath(workspaceDir, path);
2738
+ const current = await readFile4(target, "utf8");
2739
+ const found = occurrences(current, oldText);
2740
+ if (found === 0) {
2741
+ throw new TypeError(`oldText was not found in "${path}"; sandbox.read the current lines and copy them exactly, including indentation and blank lines`);
2742
+ }
2743
+ if (found > 1) {
2744
+ throw new TypeError(`oldText occurs ${found} times in "${path}"; include more of the surrounding lines so it matches exactly once`);
2745
+ }
2746
+ const at = current.indexOf(oldText);
2747
+ await writeFile3(target, `${current.slice(0, at)}${newText}${current.slice(at + oldText.length)}`, "utf8");
2748
+ return { deletions: oldText.split("\n").length, additions: newText.split("\n").length };
2749
+ }
2750
+ async function edit(context, request, options, policy, registry) {
2751
+ exactKeys(request.input, ["path", "oldText", "newText"]);
2752
+ const path = stringField(request.input, "path");
2753
+ const oldText = stringField(request.input, "oldText");
2754
+ const newText = typeof request.input.newText === "string" ? request.input.newText : stringField(request.input, "newText");
2755
+ const paths = await registry.files(context.workspaceDir);
2756
+ if (!paths.includes(path)) {
2757
+ throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
2758
+ }
2759
+ if (options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`))) {
2760
+ throw new TypeError("edit targets a read-only reference source");
2761
+ }
2762
+ const patch2 = editAsDiff(path, oldText, newText);
2763
+ const allowed = await policy.edit(policyContext(context, request, options, { patch: patch2 }));
2764
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
2765
+ const changed = await editCodeFile(context.workspaceDir, path, oldText, newText);
2766
+ registry.invalidate(context.workspaceDir);
2767
+ forgetWorkspaceGraphs(context.workspaceDir);
2768
+ return response(request, true, `Edited ${path}: -${changed.deletions} +${changed.additions} line(s).`, { paths: [path], ...changed });
2769
+ }
2770
+
2682
2771
  // src/code-tool-reads.ts
2772
+ import { readFile as readFile5, stat } from "fs/promises";
2683
2773
  var GRAPH_TOOLS = /* @__PURE__ */ new Set([
2684
2774
  "sandbox.overview",
2685
2775
  "sandbox.where_is",
@@ -2705,7 +2795,7 @@ async function read(context, request, options, policy, registry) {
2705
2795
  if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
2706
2796
  throw new TypeError("file is not a bounded regular source file");
2707
2797
  }
2708
- const source = await readFile4(target);
2798
+ const source = await readFile5(target);
2709
2799
  if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
2710
2800
  const lines = source.toString("utf8").split("\n");
2711
2801
  const content = lines.slice(startLine - 1, endLine).join("\n");
@@ -2820,6 +2910,7 @@ async function route(context, request, options, recipes, policy, registry) {
2820
2910
  if (request.tool === "sandbox.search") return await search(context, request, options, policy, registry);
2821
2911
  if (GRAPH_TOOLS.has(request.tool)) return await graphQuery(context, request, options, policy, registry);
2822
2912
  if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy, registry);
2913
+ if (request.tool === "sandbox.edit") return await edit(context, request, options, policy, registry);
2823
2914
  return await recipe(context, request, options, recipes, policy);
2824
2915
  } catch (reason) {
2825
2916
  return response(request, false, toolFailureMessage(reason));
@@ -3529,6 +3620,9 @@ var TheseusRuntimeEngine = class {
3529
3620
  prompt: metadata.prompt,
3530
3621
  signal: active.abort.signal,
3531
3622
  readOnly: metadata.readOnly,
3623
+ // A review-role session is read-only and still runs the proof; a planner
3624
+ // marked read-only by its payload runs nothing.
3625
+ recipes: metadata.role === "review" || !metadata.readOnly,
3532
3626
  recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
3533
3627
  ...extraSkills.length ? { extraSkills } : {}
3534
3628
  });
@@ -3641,4 +3735,4 @@ export {
3641
3735
  runGoal,
3642
3736
  TheseusRuntimeEngine
3643
3737
  };
3644
- //# sourceMappingURL=chunk-WM34GGTK.js.map
3738
+ //# sourceMappingURL=chunk-TMBYA5JX.js.map