@odla-ai/harness 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/chunk-ATKV6VTU.js +168 -0
  4. package/dist/chunk-ATKV6VTU.js.map +1 -0
  5. package/dist/chunk-GE6CCN7W.js +93 -0
  6. package/dist/chunk-GE6CCN7W.js.map +1 -0
  7. package/dist/chunk-GMVZ4LZH.js +1769 -0
  8. package/dist/chunk-GMVZ4LZH.js.map +1 -0
  9. package/dist/chunk-PHXQH4YM.js +550 -0
  10. package/dist/chunk-PHXQH4YM.js.map +1 -0
  11. package/dist/chunk-PTXZVYD4.js +81 -0
  12. package/dist/chunk-PTXZVYD4.js.map +1 -0
  13. package/dist/chunk-QTUEF2HZ.js +9 -0
  14. package/dist/chunk-QTUEF2HZ.js.map +1 -0
  15. package/dist/cli.cjs +795 -0
  16. package/dist/cli.cjs.map +1 -0
  17. package/dist/cli.d.cts +1 -0
  18. package/dist/cli.d.ts +1 -0
  19. package/dist/cli.js +98 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/code-runtime-cli.cjs +2341 -0
  22. package/dist/code-runtime-cli.cjs.map +1 -0
  23. package/dist/code-runtime-cli.d.cts +1 -0
  24. package/dist/code-runtime-cli.d.ts +1 -0
  25. package/dist/code-runtime-cli.js +133 -0
  26. package/dist/code-runtime-cli.js.map +1 -0
  27. package/dist/index.cjs +228 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +44 -0
  30. package/dist/index.d.ts +44 -0
  31. package/dist/index.js +46 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/node.cjs +2580 -0
  34. package/dist/node.cjs.map +1 -0
  35. package/dist/node.d.cts +544 -0
  36. package/dist/node.d.ts +544 -0
  37. package/dist/node.js +71 -0
  38. package/dist/node.js.map +1 -0
  39. package/dist/testing.cjs +106 -0
  40. package/dist/testing.cjs.map +1 -0
  41. package/dist/testing.d.cts +25 -0
  42. package/dist/testing.d.ts +25 -0
  43. package/dist/testing.js +79 -0
  44. package/dist/testing.js.map +1 -0
  45. package/dist/types-D12vK3K9.d.cts +249 -0
  46. package/dist/types-D12vK3K9.d.ts +249 -0
  47. package/package.json +84 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 odla ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @odla-ai/harness
2
+
3
+ @odla-ai/harness is the experimental coding-task boundary shared by odla
4
+ Studio, a trusted Mac/Linux runner, and an untrusted coding-agent container.
5
+ It owns the protocol and safe runner defaults; agent implementations such as Pi
6
+ remain replaceable adapters.
7
+
8
+ Harness can stage a bounded, secret-filtered copy of a local checkout. The
9
+ brokered Pi image receives no workspace mount at all: reads and mutations cross
10
+ the typed tool channel into that disposable snapshot. The original checkout,
11
+ terminal credential, provider credentials, container-engine socket, and
12
+ network are never available to Pi. Inference requests cross the same JSONL
13
+ channel and are fulfilled by the control plane through @odla-ai/ai.
14
+
15
+ ## Personal Code terminal
16
+
17
+ Open **Studio → Code → Terminal**, copy the command shown for the selected app
18
+ and environment, and run it from that app's Git checkout. For example:
19
+
20
+ ~~~sh
21
+ export ODLA_USER_EMAIL=owner@example.com
22
+ npx @odla-ai/cli@0.16.4 code connect \
23
+ --platform https://odla.ai \
24
+ --app-id my-app \
25
+ --env dev
26
+ ~~~
27
+
28
+ The terminal verifies the checkout and isolation engine, builds the Pi adapter
29
+ bundled with the exact CLI release into a content-addressed local image, prepares
30
+ each digest-pinned recipe image, and sends the connection request outbound to odla. The signed-in app
31
+ owner approves it; no bearer is copied from the browser. The process stays in
32
+ the foreground, opens no listener, and keeps its collected host credential in
33
+ memory only.
34
+
35
+ At connection time it freezes tracked and non-ignored untracked source into a
36
+ staged snapshot, omitting `.git`, `.odla`, `.wrangler`, `node_modules`, build
37
+ output, `.env` files, `.dev.vars`, credential JSON, unsafe paths, and symlinks.
38
+ Each session binds an explicitly selected primary repository/ref plus any named
39
+ read-only repository/ref references to exact commits and tree digests. A
40
+ matching local snapshot remains an optimization with explicit provenance;
41
+ otherwise Harness receives the immutable sources from the Registry. References
42
+ are materialized under `.odla-references/<alias>` and the broker rejects every
43
+ attempt to patch them. A checkpoint patch is computed against the selected
44
+ primary Git base, so local developer changes remain explicit provenance
45
+ alongside Pi's changes.
46
+
47
+ On Apple Silicon with macOS 26, install Apple's lightweight runtime once with
48
+ `brew install container`; `code connect` selects it and starts its user service.
49
+ On Linux, auto requires rootless Podman. Podman Machine is the macOS fallback;
50
+ Docker is an explicit compatibility adapter only. Recipe images must be
51
+ digest-pinned; the Pi adapter's local tag contains the SHA-256 of its CLI bundle
52
+ and requires no registry credential. All adapters run Pi networkless and
53
+ non-root with bounded CPU/memory; Pi has no
54
+ checkout or engine-socket mount.
55
+
56
+ ## Experimental task runner
57
+
58
+ The older generic task queue is separate from the personal Pi terminal flow.
59
+ It lives under **Code → Task runners · advanced**. To exercise it, register a
60
+ workspace and keep its runner token outside shell history:
61
+
62
+ ~~~sh
63
+ export ODLA_HARNESS_TOKEN=odla_hrn_...
64
+ npx @odla-ai/harness runner \
65
+ --endpoint https://odla.ai \
66
+ --workspace my-repo=/absolute/path/to/repo \
67
+ --image registry.example/coding-agent@sha256:<digest> \
68
+ --engine auto
69
+ ~~~
70
+
71
+ The runner adds network=none, a read-only root, dropped capabilities, non-root
72
+ uid/gid, and CPU/memory limits across adapters. Docker and Podman additionally
73
+ receive no-new-privileges plus PID and bounded-tmpfs limits. A legacy image's
74
+ only writable mount is the disposable workspace copy; a broker-only image has
75
+ no source mount.
76
+
77
+ The runner skips .git, .odla, .wrangler, node_modules, build output, .env files,
78
+ .dev.vars, credential JSON, and symlinks while staging. Completed attempts
79
+ return a bounded binary-capable Git patch to Studio.
80
+
81
+ Studio can branch the attempt ledger from an inspected event checkpoint. In
82
+ this first release the branch records immutable lineage and starts from a fresh
83
+ copy of the registered workspace; it does not replay the parent patch into the
84
+ new copy.
85
+
86
+ ## Build recipes
87
+
88
+ The Code terminal advertises a closed recipe catalog. The content-addressed,
89
+ CLI-embedded Pi image and every registry-digest-pinned recipe are prepared
90
+ before approval:
91
+
92
+ ~~~json
93
+ {
94
+ "recipeAuthorization": "registered_recipe",
95
+ "recipes": [{
96
+ "id": "test",
97
+ "image": "registry.example/app-build@sha256:<digest>",
98
+ "command": ["npm", "test"],
99
+ "timeoutMs": 600000,
100
+ "maxOutputBytes": 1048576
101
+ }]
102
+ }
103
+ ~~~
104
+
105
+ The runtime validates HTTPS, never places its credential in a request body,
106
+ never opens a listener, and rejects malformed or cross-host binding responses.
107
+ Pi reads, patches, and runs only registered build recipes through the typed
108
+ CaMeL broker. Checkpoint-stop captures a bounded combined patch; resume verifies
109
+ it against the same selected Git base and exact local source descriptor.
110
+
111
+ `--once` is diagnostic-only and refuses pending commands so a one-heartbeat
112
+ probe cannot acknowledge a session as running and then immediately tear it
113
+ down.
114
+
115
+ ## Agent protocol
116
+
117
+ The image reads one JSON object per line on stdin and writes protocol messages
118
+ on stdout. Version 1 supports task.start, typed event messages,
119
+ inference.request/inference.response, tool.request/tool.response,
120
+ attempt.cancel, and attempt.complete.
121
+
122
+ ## CaMeL tool and build boundary
123
+
124
+ `createCodeToolBroker` is the trusted implementation for the three Pi tools:
125
+ registered staged-file reads, confined unified patches, and app-registered
126
+ build recipes. Each request is labelled and evaluated by @odla-ai/camel before
127
+ an effect. Patch data remains Prompt-Unsafe payload; file and recipe selectors
128
+ must pass closed conversions; recipe execution is either explicitly
129
+ pre-authorized by app policy or bound to an exact approval digest.
130
+
131
+ Recipes contain a fixed command and digest-pinned image. They run without a
132
+ shell in a second networkless, non-root container over a newly staged copy.
133
+ Build output and mutations are discarded after the bounded result returns. The
134
+ decision binds a digest of the exact source copy, and the broker refuses to run
135
+ if that copy changes after authorization. No repository code executes in the
136
+ host runner process.
137
+
138
+ `verifyCodeCandidate` is the independent landing verifier. It reconstructs the
139
+ candidate from the registered trusted-base digest and patch, gives each fixed
140
+ recipe a fresh source copy, and emits a digest-authenticated closed receipt.
141
+ Raw stdout, stderr, and changed-test paths remain separate quarantined evidence
142
+ for coding, review, and owner display. Only the prose-free receipt of registered
143
+ recipe outcomes, digests, and changed-test counts can be promoted to planning
144
+ Safe through `createTrustedCodeVerificationReceipt`; changed tests always set a
145
+ separate review requirement.
146
+
147
+ ## Portable checkpoint and resume
148
+
149
+ `createCodeWorkspaceCheckpoint` captures the staged candidate as a bounded,
150
+ validated text patch plus closed resume state: plan and conversation cursors,
151
+ build-policy and dependency-layer digests, completed effect receipts,
152
+ unresolved approvals, and trust status. Patch, state, and total checkpoint have
153
+ independent SHA-256 digests. The Registry verifies the complete payload before
154
+ storing it and keeps only its digest in the command ledger.
155
+
156
+ `restoreCodeWorkspaceCheckpoint` verifies every digest, requires the exact base
157
+ commit, and applies the patch to a fresh staged copy on the currently bound
158
+ host. `isCheckpointEffectCompleted` skips an exact prior effect and rejects an
159
+ effect ID rebound to another action digest, preventing resume from replaying a
160
+ database write, publication, or deployment.
161
+
162
+ Use @odla-ai/harness for protocol types and validation,
163
+ @odla-ai/harness/node for runner/container primitives, and
164
+ @odla-ai/harness/testing for deterministic test doubles.
165
+
166
+ ## Local fixture image
167
+
168
+ The included fixture proves the transport without running an autonomous coding
169
+ agent:
170
+
171
+ ~~~sh
172
+ docker build -t odla-harness-fixture:local packages/harness/fixtures
173
+ ODLA_HARNESS_CONTAINER_TEST=1 npm test -w @odla-ai/harness
174
+ ~~~
175
+
176
+ Unpinned images are accepted only when both the explicit test flag and
177
+ ODLA_HARNESS_UNSAFE_TESTING=1 are present. That escape hatch is not intended
178
+ for a connected Studio runner.
@@ -0,0 +1,168 @@
1
+ import {
2
+ runContainerAttempt,
3
+ stageWorkspace
4
+ } from "./chunk-PHXQH4YM.js";
5
+ import {
6
+ makeHarnessEvent
7
+ } from "./chunk-GE6CCN7W.js";
8
+ import {
9
+ HARNESS_PROTOCOL_VERSION
10
+ } from "./chunk-QTUEF2HZ.js";
11
+
12
+ // src/runner.ts
13
+ import { setTimeout as delay } from "timers/promises";
14
+ async function append(control, lease, kind, actor, payload) {
15
+ await control.appendEvents(lease.task.attemptId, lease.leaseId, [makeHarnessEvent(kind, actor, payload)]);
16
+ }
17
+ async function runLeasedAttempt(lease, options) {
18
+ const source = options.workspaces[lease.task.workspace];
19
+ if (!source) {
20
+ await options.control.complete(lease.task.attemptId, lease.leaseId, {
21
+ status: "failed",
22
+ error: `runner does not expose workspace "${lease.task.workspace}"`
23
+ });
24
+ return;
25
+ }
26
+ let staged = null;
27
+ const controller = new AbortController();
28
+ const cancelFromParent = () => controller.abort(options.signal?.reason);
29
+ options.signal?.addEventListener("abort", cancelFromParent, { once: true });
30
+ if (options.signal?.aborted) controller.abort(options.signal.reason);
31
+ let heartbeat;
32
+ let heartbeatInFlight = false;
33
+ const pulse = async () => {
34
+ if (heartbeatInFlight || controller.signal.aborted) return;
35
+ heartbeatInFlight = true;
36
+ try {
37
+ const value = await options.control.heartbeat(lease.task.attemptId, lease.leaseId);
38
+ if (value.cancelRequested) controller.abort("cancel_requested");
39
+ } catch {
40
+ controller.abort("heartbeat_failed");
41
+ } finally {
42
+ heartbeatInFlight = false;
43
+ }
44
+ };
45
+ try {
46
+ await pulse();
47
+ heartbeat = setInterval(() => {
48
+ void pulse();
49
+ }, options.heartbeatMs ?? 15e3);
50
+ if (controller.signal.aborted) throw new Error("lease was cancelled before workspace staging");
51
+ staged = await stageWorkspace(source);
52
+ if (controller.signal.aborted) throw new Error("lease was cancelled during workspace staging");
53
+ const stagedWorkspace = staged;
54
+ await append(options.control, lease, "runner.workspace_staged", "runner", {
55
+ workspace: lease.task.workspace,
56
+ files: stagedWorkspace.fileCount,
57
+ bytes: stagedWorkspace.byteCount
58
+ });
59
+ const result = await runContainerAttempt({
60
+ engine: options.engine,
61
+ image: options.image,
62
+ workspaceDir: stagedWorkspace.workspaceDir,
63
+ task: lease.task,
64
+ limits: options.limits,
65
+ allowUnpinnedImage: options.allowUnpinnedImage,
66
+ workspaceAccess: options.workspaceAccess ?? (options.toolBroker ? "none" : "read-write"),
67
+ signal: controller.signal,
68
+ onStderr: async (text) => {
69
+ await append(options.control, lease, "agent.stderr", "agent", { text: text.slice(0, 64 * 1024) });
70
+ },
71
+ onMessage: async (message) => {
72
+ if (message.type === "event") {
73
+ await append(options.control, lease, message.kind, "agent", message.payload ?? null);
74
+ return;
75
+ }
76
+ if (message.type === "attempt.complete") {
77
+ await append(options.control, lease, "agent.completed", "agent", {
78
+ status: message.status,
79
+ result: message.result ?? null
80
+ });
81
+ return;
82
+ }
83
+ if (message.type === "tool.request") {
84
+ await append(options.control, lease, "tool.requested", "agent", {
85
+ requestId: message.requestId,
86
+ tool: message.tool
87
+ });
88
+ const response2 = options.toolBroker ? await options.toolBroker.execute({
89
+ lease,
90
+ workspaceDir: stagedWorkspace.workspaceDir,
91
+ signal: controller.signal
92
+ }, message) : { requestId: message.requestId, ok: false, content: "tool denied: no trusted broker configured" };
93
+ await append(options.control, lease, "tool.responded", "system", {
94
+ requestId: message.requestId,
95
+ tool: message.tool,
96
+ ok: response2.ok,
97
+ contentBytes: Buffer.byteLength(response2.content)
98
+ });
99
+ return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
100
+ }
101
+ await append(options.control, lease, "model.requested", "agent", {
102
+ requestId: message.requestId,
103
+ messages: message.call.messages.length,
104
+ maxTokens: message.call.maxTokens
105
+ });
106
+ const response = await options.control.infer(lease.task.attemptId, lease.leaseId, {
107
+ requestId: message.requestId,
108
+ call: message.call
109
+ });
110
+ await append(options.control, lease, "model.responded", "model", {
111
+ requestId: message.requestId,
112
+ response: response.response,
113
+ receipt: response.receipt
114
+ });
115
+ return {
116
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
117
+ type: "inference.response",
118
+ requestId: message.requestId,
119
+ response: response.response
120
+ };
121
+ }
122
+ });
123
+ const patch = await stagedWorkspace.patch(lease.task.policy.maxPatchBytes);
124
+ await options.control.complete(lease.task.attemptId, lease.leaseId, {
125
+ status: result.status,
126
+ result: result.result,
127
+ patch,
128
+ ...result.status === "failed" ? { error: result.stderr.slice(0, 4e3) || "container attempt failed" } : {}
129
+ });
130
+ } catch (reason) {
131
+ const message = reason instanceof Error ? reason.message : "runner failed";
132
+ try {
133
+ await append(options.control, lease, "runner.failed", "runner", { message });
134
+ await options.control.complete(lease.task.attemptId, lease.leaseId, {
135
+ status: controller.signal.aborted ? "cancelled" : "failed",
136
+ error: message
137
+ });
138
+ } catch {
139
+ }
140
+ } finally {
141
+ if (heartbeat) clearInterval(heartbeat);
142
+ options.signal?.removeEventListener("abort", cancelFromParent);
143
+ if (staged && !options.preserveWorkspace) await staged.cleanup();
144
+ }
145
+ }
146
+ async function runHarnessRunner(options) {
147
+ const workspaceNames = Object.keys(options.workspaces).sort();
148
+ if (!workspaceNames.length) throw new TypeError("at least one workspace mapping is required");
149
+ do {
150
+ if (options.signal?.aborted) return;
151
+ const lease = await options.control.lease(workspaceNames);
152
+ if (lease) {
153
+ options.log?.(`leased ${lease.task.taskId}/${lease.task.attemptId}`);
154
+ await runLeasedAttempt(lease, options);
155
+ if (options.once) return;
156
+ continue;
157
+ }
158
+ if (options.once) return;
159
+ await delay(options.pollMs ?? 2e3, void 0, { signal: options.signal }).catch(() => {
160
+ });
161
+ } while (!options.signal?.aborted);
162
+ }
163
+
164
+ export {
165
+ runLeasedAttempt,
166
+ runHarnessRunner
167
+ };
168
+ //# sourceMappingURL=chunk-ATKV6VTU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runner.ts"],"sourcesContent":["import { setTimeout as delay } from \"node:timers/promises\";\nimport { runContainerAttempt, type ContainerEngine, type ContainerLimits } from \"./container\";\nimport { makeHarnessEvent } from \"./protocol\";\nimport { stageWorkspace, type StagedWorkspace } from \"./workspace\";\nimport {\n HARNESS_PROTOCOL_VERSION,\n type HarnessAgentInput,\n type HarnessAgentOutput,\n type HarnessControlPlane,\n type HarnessLease,\n type HarnessToolBroker,\n} from \"./types\";\n\n/** Control-plane, workspace, isolation, polling, and lifecycle settings for a runner. */\nexport interface HarnessRunnerOptions {\n control: HarnessControlPlane;\n workspaces: Readonly<Record<string, string>>;\n engine: ContainerEngine;\n image: string;\n limits?: ContainerLimits;\n heartbeatMs?: number;\n pollMs?: number;\n once?: boolean;\n preserveWorkspace?: boolean;\n allowUnpinnedImage?: boolean;\n workspaceAccess?: \"read-write\" | \"read-only\" | \"none\";\n signal?: AbortSignal;\n log?: (message: string) => void;\n toolBroker?: HarnessToolBroker;\n}\n\nasync function append(\n control: HarnessControlPlane,\n lease: HarnessLease,\n kind: string,\n actor: \"runner\" | \"agent\" | \"model\" | \"system\",\n payload: unknown,\n): Promise<void> {\n await control.appendEvents(lease.task.attemptId, lease.leaseId, [makeHarnessEvent(kind, actor, payload)]);\n}\n\n/** Execute one leased task. The registered workspace is copied before the\n * container starts; neither the checkout nor the runner credential is mounted. */\nexport async function runLeasedAttempt(lease: HarnessLease, options: HarnessRunnerOptions): Promise<void> {\n const source = options.workspaces[lease.task.workspace];\n if (!source) {\n await options.control.complete(lease.task.attemptId, lease.leaseId, {\n status: \"failed\",\n error: `runner does not expose workspace \"${lease.task.workspace}\"`,\n });\n return;\n }\n\n let staged: StagedWorkspace | null = null;\n const controller = new AbortController();\n const cancelFromParent = () => controller.abort(options.signal?.reason);\n options.signal?.addEventListener(\"abort\", cancelFromParent, { once: true });\n if (options.signal?.aborted) controller.abort(options.signal.reason);\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n let heartbeatInFlight = false;\n const pulse = async () => {\n if (heartbeatInFlight || controller.signal.aborted) return;\n heartbeatInFlight = true;\n try {\n const value = await options.control.heartbeat(lease.task.attemptId, lease.leaseId);\n if (value.cancelRequested) controller.abort(\"cancel_requested\");\n } catch {\n controller.abort(\"heartbeat_failed\");\n } finally {\n heartbeatInFlight = false;\n }\n };\n try {\n await pulse();\n heartbeat = setInterval(() => { void pulse(); }, options.heartbeatMs ?? 15_000);\n if (controller.signal.aborted) throw new Error(\"lease was cancelled before workspace staging\");\n staged = await stageWorkspace(source);\n if (controller.signal.aborted) throw new Error(\"lease was cancelled during workspace staging\");\n const stagedWorkspace = staged;\n await append(options.control, lease, \"runner.workspace_staged\", \"runner\", {\n workspace: lease.task.workspace,\n files: stagedWorkspace.fileCount,\n bytes: stagedWorkspace.byteCount,\n });\n\n const result = await runContainerAttempt({\n engine: options.engine,\n image: options.image,\n workspaceDir: stagedWorkspace.workspaceDir,\n task: lease.task,\n limits: options.limits,\n allowUnpinnedImage: options.allowUnpinnedImage,\n workspaceAccess: options.workspaceAccess ?? (options.toolBroker ? \"none\" : \"read-write\"),\n signal: controller.signal,\n onStderr: async (text) => {\n await append(options.control, lease, \"agent.stderr\", \"agent\", { text: text.slice(0, 64 * 1024) });\n },\n onMessage: async (message: HarnessAgentOutput): Promise<HarnessAgentInput | void> => {\n if (message.type === \"event\") {\n await append(options.control, lease, message.kind, \"agent\", message.payload ?? null);\n return;\n }\n if (message.type === \"attempt.complete\") {\n await append(options.control, lease, \"agent.completed\", \"agent\", {\n status: message.status,\n result: message.result ?? null,\n });\n return;\n }\n if (message.type === \"tool.request\") {\n await append(options.control, lease, \"tool.requested\", \"agent\", {\n requestId: message.requestId, tool: message.tool,\n });\n const response = options.toolBroker\n ? await options.toolBroker.execute({\n lease, workspaceDir: stagedWorkspace.workspaceDir, signal: controller.signal,\n }, message)\n : { requestId: message.requestId, ok: false, content: \"tool denied: no trusted broker configured\" };\n await append(options.control, lease, \"tool.responded\", \"system\", {\n requestId: message.requestId, tool: message.tool, ok: response.ok,\n contentBytes: Buffer.byteLength(response.content),\n });\n return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: \"tool.response\", ...response };\n }\n await append(options.control, lease, \"model.requested\", \"agent\", {\n requestId: message.requestId,\n messages: message.call.messages.length,\n maxTokens: message.call.maxTokens,\n });\n const response = await options.control.infer(lease.task.attemptId, lease.leaseId, {\n requestId: message.requestId,\n call: message.call,\n });\n await append(options.control, lease, \"model.responded\", \"model\", {\n requestId: message.requestId,\n response: response.response,\n receipt: response.receipt,\n });\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n type: \"inference.response\",\n requestId: message.requestId,\n response: response.response,\n };\n },\n });\n\n const patch = await stagedWorkspace.patch(lease.task.policy.maxPatchBytes);\n await options.control.complete(lease.task.attemptId, lease.leaseId, {\n status: result.status,\n result: result.result,\n patch,\n ...(result.status === \"failed\" ? { error: result.stderr.slice(0, 4_000) || \"container attempt failed\" } : {}),\n });\n } catch (reason) {\n const message = reason instanceof Error ? reason.message : \"runner failed\";\n try {\n await append(options.control, lease, \"runner.failed\", \"runner\", { message });\n await options.control.complete(lease.task.attemptId, lease.leaseId, {\n status: controller.signal.aborted ? \"cancelled\" : \"failed\",\n error: message,\n });\n } catch {\n // A lost or revoked lease can also make the terminal report unavailable.\n }\n } finally {\n if (heartbeat) clearInterval(heartbeat);\n options.signal?.removeEventListener(\"abort\", cancelFromParent);\n if (staged && !options.preserveWorkspace) await staged.cleanup();\n }\n}\n\n/** One runner executes one attempt at a time. Concurrency comes from multiple\n * independently credentialed runner processes. */\nexport async function runHarnessRunner(options: HarnessRunnerOptions): Promise<void> {\n const workspaceNames = Object.keys(options.workspaces).sort();\n if (!workspaceNames.length) throw new TypeError(\"at least one workspace mapping is required\");\n do {\n if (options.signal?.aborted) return;\n const lease = await options.control.lease(workspaceNames);\n if (lease) {\n options.log?.(`leased ${lease.task.taskId}/${lease.task.attemptId}`);\n await runLeasedAttempt(lease, options);\n if (options.once) return;\n continue;\n }\n if (options.once) return;\n await delay(options.pollMs ?? 2_000, undefined, { signal: options.signal }).catch(() => {});\n } while (!options.signal?.aborted);\n}\n"],"mappings":"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","names":["response"]}
@@ -0,0 +1,93 @@
1
+ import {
2
+ HARNESS_PROTOCOL_VERSION
3
+ } from "./chunk-QTUEF2HZ.js";
4
+
5
+ // src/protocol.ts
6
+ var CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
7
+ var HarnessProtocolError = class extends Error {
8
+ name = "HarnessProtocolError";
9
+ };
10
+ function record(value) {
11
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
12
+ }
13
+ function boundedText(value, label, max) {
14
+ if (typeof value !== "string" || !value || value.length > max || CONTROL.test(value)) {
15
+ throw new HarnessProtocolError(`${label} must be a non-empty string of at most ${max} characters`);
16
+ }
17
+ return value;
18
+ }
19
+ function parseAgentOutput(line) {
20
+ if (Buffer.byteLength(line, "utf8") > 1e6) throw new HarnessProtocolError("agent message exceeds 1 MB");
21
+ let value;
22
+ try {
23
+ value = JSON.parse(line);
24
+ } catch {
25
+ throw new HarnessProtocolError("agent emitted invalid JSON");
26
+ }
27
+ const message = record(value);
28
+ if (!message || message.protocolVersion !== HARNESS_PROTOCOL_VERSION) {
29
+ throw new HarnessProtocolError(`agent protocolVersion must be ${HARNESS_PROTOCOL_VERSION}`);
30
+ }
31
+ if (message.type === "event") {
32
+ return {
33
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
34
+ type: "event",
35
+ kind: boundedText(message.kind, "event.kind", 120),
36
+ ...message.payload === void 0 ? {} : { payload: message.payload }
37
+ };
38
+ }
39
+ if (message.type === "inference.request") {
40
+ const call = record(message.call);
41
+ if (!call || !Array.isArray(call.messages) || !Number.isSafeInteger(call.maxTokens)) {
42
+ throw new HarnessProtocolError("inference.request.call requires messages and maxTokens");
43
+ }
44
+ return {
45
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
46
+ type: "inference.request",
47
+ requestId: boundedText(message.requestId, "requestId", 180),
48
+ call
49
+ };
50
+ }
51
+ if (message.type === "tool.request") {
52
+ const input = record(message.input);
53
+ const tool = String(message.tool);
54
+ if (!input || !["sandbox.read", "sandbox.apply_patch", "sandbox.run_recipe"].includes(tool)) {
55
+ throw new HarnessProtocolError("tool.request requires a registered tool and object input");
56
+ }
57
+ return {
58
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
59
+ type: "tool.request",
60
+ requestId: boundedText(message.requestId, "requestId", 180),
61
+ tool,
62
+ input
63
+ };
64
+ }
65
+ if (message.type === "attempt.complete") {
66
+ if (!(/* @__PURE__ */ new Set(["completed", "failed", "cancelled"])).has(String(message.status))) {
67
+ throw new HarnessProtocolError("attempt.complete.status is invalid");
68
+ }
69
+ return {
70
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
71
+ type: "attempt.complete",
72
+ status: message.status,
73
+ ...message.result === void 0 ? {} : { result: message.result }
74
+ };
75
+ }
76
+ throw new HarnessProtocolError("agent message type is unsupported");
77
+ }
78
+ function encodeAgentInput(message) {
79
+ return `${JSON.stringify(message)}
80
+ `;
81
+ }
82
+ function makeHarnessEvent(kind, actor, payload, now = Date.now(), id = crypto.randomUUID()) {
83
+ boundedText(kind, "event.kind", 120);
84
+ return { eventId: id, kind, actor, payload, createdAt: now };
85
+ }
86
+
87
+ export {
88
+ HarnessProtocolError,
89
+ parseAgentOutput,
90
+ encodeAgentInput,
91
+ makeHarnessEvent
92
+ };
93
+ //# sourceMappingURL=chunk-GE6CCN7W.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.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.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":[]}