@odla-ai/harness 0.8.0 → 0.9.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.
@@ -5,7 +5,7 @@
5
5
  var import_node_os3 = require("os");
6
6
  var import_promises10 = require("fs/promises");
7
7
 
8
- // src/code-runtime-client.ts
8
+ // src/code-runtime-client-validation.ts
9
9
  var import_code = require("@odla-ai/camel/code");
10
10
  var CodeRuntimeControlError = class extends Error {
11
11
  constructor(message2, status, code = "control_error") {
@@ -17,101 +17,6 @@ var CodeRuntimeControlError = class extends Error {
17
17
  code;
18
18
  name = "CodeRuntimeControlError";
19
19
  };
20
- function createCodeRuntimeControlClient(options) {
21
- const endpoint = validatedEndpoint(options.endpoint);
22
- if (!/^odla_code_host_[0-9a-f]{64}$/.test(options.token)) throw new TypeError("invalid Code host credential");
23
- const requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
24
- if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
25
- throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
26
- }
27
- const modelRequestTimeoutMs = options.modelRequestTimeoutMs ?? 15 * 6e4;
28
- if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
29
- throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
30
- }
31
- const request = options.fetch ?? fetch;
32
- const call = async (path, body, timeoutMs = requestTimeoutMs) => {
33
- const timeout = AbortSignal.timeout(timeoutMs);
34
- const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
35
- let response2;
36
- try {
37
- response2 = await request(`${endpoint}${path}`, {
38
- method: "POST",
39
- headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
40
- body: JSON.stringify(body),
41
- redirect: "error",
42
- signal
43
- });
44
- } catch (cause) {
45
- if (options.signal?.aborted) throw cause;
46
- throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
47
- }
48
- const value = await response2.json().catch(() => null);
49
- if (!response2.ok) {
50
- const problem = record(record(value)?.error);
51
- throw new CodeRuntimeControlError(
52
- typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
53
- response2.status,
54
- typeof problem?.code === "string" ? problem.code : void 0
55
- );
56
- }
57
- return value;
58
- };
59
- return {
60
- heartbeat: async (version, capabilities) => {
61
- validateHeartbeat(version, capabilities);
62
- return parseSnapshot(await call("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
63
- },
64
- acknowledge: async (commandId, result) => {
65
- if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
66
- await call(`/registry/code/runtime/commands/${commandId}/ack`, result);
67
- },
68
- source: async (sessionId) => parseSource(
69
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
70
- ),
71
- infer: async (sessionId, inference) => {
72
- const value = record(await call(
73
- `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
74
- inference,
75
- modelRequestTimeoutMs
76
- ));
77
- if (!value || value.requestId !== inference.requestId || !record(value.response) || !record(value.receipt)) {
78
- throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
79
- }
80
- return value;
81
- },
82
- review: async (sessionId, review) => parseReview(
83
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
84
- ),
85
- submitCandidate: async (sessionId, checkpointId, verification) => {
86
- if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
87
- return parseCandidate(await call(
88
- `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
89
- { checkpointId, verification }
90
- ));
91
- },
92
- appendSessionEvent: async (sessionId, eventId, event) => {
93
- const serialized = JSON.stringify(event);
94
- if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
95
- throw new TypeError("invalid Code session event");
96
- }
97
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
98
- },
99
- recallMemories: async (sessionId, subjects, limit) => {
100
- const response2 = await call(
101
- `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
102
- { subjects: [...subjects], limit }
103
- );
104
- return Array.isArray(response2.memories) ? response2.memories : [];
105
- },
106
- rememberMemory: async (sessionId, memory) => {
107
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
108
- },
109
- reportSessionFailure: async (sessionId, message2) => {
110
- if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
111
- await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
112
- }
113
- };
114
- }
115
20
  function validatedEndpoint(value) {
116
21
  const endpoint = value.replace(/\/+$/, "");
117
22
  let url;
@@ -130,6 +35,10 @@ function validSessionId(value) {
130
35
  if (!/^csess_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code session id");
131
36
  return value;
132
37
  }
38
+ function validCommandId(value) {
39
+ if (!/^ccmd_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code runtime command id");
40
+ return value;
41
+ }
133
42
  function validateHeartbeat(version, capabilities) {
134
43
  if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
135
44
  if (capabilities.protocolVersion !== CODE_RUNTIME_PROTOCOL_VERSION) throw new TypeError("unsupported Code runtime protocol version");
@@ -211,9 +120,211 @@ function parseCandidate(value) {
211
120
  }
212
121
  return { candidateId: candidate.candidateId, status: candidate.status };
213
122
  }
123
+ function parseCollaborationSkills(value) {
124
+ const items = record(value)?.skills;
125
+ if (!Array.isArray(items) || items.length > 16) throw invalid("collaboration skills");
126
+ const skillNames = /* @__PURE__ */ new Set();
127
+ const toolNames = /* @__PURE__ */ new Set();
128
+ return items.map((item) => {
129
+ const skill = record(item);
130
+ if (!skill || !validManifestName(skill.name) || skillNames.has(skill.name) || skill.instructions !== void 0 && (typeof skill.instructions !== "string" || utf8Bytes(skill.instructions) > 32e3) || !Array.isArray(skill.tools) || !skill.tools.length || skill.tools.length > 128) {
131
+ throw invalid("collaboration skill");
132
+ }
133
+ skillNames.add(skill.name);
134
+ const tools = skill.tools.map((candidate) => {
135
+ const tool = record(candidate);
136
+ const inputSchema = record(tool?.inputSchema);
137
+ if (!tool || !validManifestName(tool.name) || toolNames.has(tool.name) || typeof tool.description !== "string" || utf8Bytes(tool.description) > 8e3 || !inputSchema || jsonBytes(inputSchema) > 64e3 || tool.concurrency !== void 0 && tool.concurrency !== "parallel") {
138
+ throw invalid("collaboration tool");
139
+ }
140
+ const outputTaint = parseTaintLabels(tool.outputTaint);
141
+ const acceptsTaint = parseTaintLabels(tool.acceptsTaint);
142
+ toolNames.add(tool.name);
143
+ return {
144
+ name: tool.name,
145
+ description: tool.description,
146
+ inputSchema,
147
+ ...tool.concurrency === "parallel" ? { concurrency: "parallel" } : {},
148
+ ...outputTaint ? { outputTaint } : {},
149
+ ...acceptsTaint ? { acceptsTaint } : {}
150
+ };
151
+ });
152
+ return {
153
+ name: skill.name,
154
+ ...typeof skill.instructions === "string" ? { instructions: skill.instructions } : {},
155
+ tools
156
+ };
157
+ });
158
+ }
159
+ function validateCollaborationToolRequest(value) {
160
+ validCommandId(value.commandId);
161
+ if (typeof value.toolCallId !== "string" || value.toolCallId.length > 256 || !/^[^\s\u0000-\u001f\u007f]+$/.test(value.toolCallId) || !validManifestName(value.skill) || !validManifestName(value.tool) || !record(value.input) || jsonBytes(value.input) > 128e3) {
162
+ throw new TypeError("invalid Code collaboration tool request");
163
+ }
164
+ }
165
+ function parseCollaborationToolOutput(value) {
166
+ const output = record(record(value)?.output);
167
+ if (!output || output.isError !== void 0 && typeof output.isError !== "boolean") {
168
+ throw invalid("collaboration tool");
169
+ }
170
+ if (typeof output.content === "string") {
171
+ if (utf8Bytes(output.content) > 1e6) throw invalid("collaboration tool");
172
+ return { content: output.content, ...output.isError === true ? { isError: true } : {} };
173
+ }
174
+ if (!Array.isArray(output.content) || output.content.length > 64 || jsonBytes(output.content) > 1e6 || !output.content.every((block) => {
175
+ const item = record(block);
176
+ return item && ["text", "image", "audio", "document", "tool_use", "tool_result", "thinking"].includes(String(item.type));
177
+ })) throw invalid("collaboration tool");
178
+ return {
179
+ content: output.content,
180
+ ...output.isError === true ? { isError: true } : {}
181
+ };
182
+ }
183
+ function parseTaintLabels(value) {
184
+ if (value === void 0) return void 0;
185
+ if (!Array.isArray(value) || value.length > 16) throw invalid("collaboration tool taint");
186
+ const labels = value.map((item) => {
187
+ if (item === "web_untrusted" || item === "operator_pasted_untrusted" || item === "llm_inherited") return item;
188
+ if (typeof item === "string" && /^tool_untrusted:[^\s\u0000-\u001f\u007f]{1,100}$/.test(item)) {
189
+ return item;
190
+ }
191
+ throw invalid("collaboration tool taint");
192
+ });
193
+ return [...new Set(labels)];
194
+ }
195
+ function validManifestName(value) {
196
+ return typeof value === "string" && /^[A-Za-z][A-Za-z0-9._:-]{0,127}$/.test(value);
197
+ }
198
+ function utf8Bytes(value) {
199
+ return new TextEncoder().encode(value).byteLength;
200
+ }
201
+ function jsonBytes(value) {
202
+ try {
203
+ return utf8Bytes(JSON.stringify(value));
204
+ } catch {
205
+ return Number.POSITIVE_INFINITY;
206
+ }
207
+ }
214
208
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
215
209
  var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
216
210
 
211
+ // src/code-runtime-client.ts
212
+ function createCodeRuntimeControlClient(options) {
213
+ const endpoint = validatedEndpoint(options.endpoint);
214
+ if (!/^odla_code_host_[0-9a-f]{64}$/.test(options.token)) throw new TypeError("invalid Code host credential");
215
+ const requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
216
+ if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
217
+ throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
218
+ }
219
+ const modelRequestTimeoutMs = options.modelRequestTimeoutMs ?? 15 * 6e4;
220
+ if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
221
+ throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
222
+ }
223
+ const request = options.fetch ?? fetch;
224
+ const call = async (path, body, timeoutMs = requestTimeoutMs, operationSignal) => {
225
+ const timeout = AbortSignal.timeout(timeoutMs);
226
+ const signals = [options.signal, operationSignal, timeout].filter((item) => Boolean(item));
227
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
228
+ let response2;
229
+ try {
230
+ response2 = await request(`${endpoint}${path}`, {
231
+ method: "POST",
232
+ headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
233
+ body: JSON.stringify(body),
234
+ redirect: "error",
235
+ signal
236
+ });
237
+ } catch (cause) {
238
+ if (options.signal?.aborted || operationSignal?.aborted) throw cause;
239
+ throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
240
+ }
241
+ const value = await response2.json().catch(() => null);
242
+ if (!response2.ok) {
243
+ const problem = record(record(value)?.error);
244
+ throw new CodeRuntimeControlError(
245
+ typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
246
+ response2.status,
247
+ typeof problem?.code === "string" ? problem.code : void 0
248
+ );
249
+ }
250
+ return value;
251
+ };
252
+ return {
253
+ heartbeat: async (version, capabilities) => {
254
+ validateHeartbeat(version, capabilities);
255
+ return parseSnapshot(await call("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
256
+ },
257
+ acknowledge: async (commandId, result) => {
258
+ await call(`/registry/code/runtime/commands/${validCommandId(commandId)}/ack`, result);
259
+ },
260
+ source: async (sessionId) => parseSource(
261
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
262
+ ),
263
+ infer: async (sessionId, inference) => {
264
+ const value = record(await call(
265
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
266
+ inference,
267
+ modelRequestTimeoutMs
268
+ ));
269
+ if (!value || value.requestId !== inference.requestId || !record(value.response) || !record(value.receipt)) {
270
+ throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
271
+ }
272
+ return value;
273
+ },
274
+ review: async (sessionId, review) => parseReview(
275
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
276
+ ),
277
+ submitCandidate: async (sessionId, checkpointId, verification) => {
278
+ if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
279
+ return parseCandidate(await call(
280
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
281
+ { checkpointId, verification }
282
+ ));
283
+ },
284
+ appendSessionEvent: async (sessionId, eventId, event) => {
285
+ const serialized = JSON.stringify(event);
286
+ if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
287
+ throw new TypeError("invalid Code session event");
288
+ }
289
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
290
+ },
291
+ recallMemories: async (sessionId, subjects, limit) => {
292
+ const response2 = await call(
293
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
294
+ { subjects: [...subjects], limit }
295
+ );
296
+ return Array.isArray(response2.memories) ? response2.memories : [];
297
+ },
298
+ rememberMemory: async (sessionId, memory) => {
299
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
300
+ },
301
+ collaborationSkills: async (sessionId, commandId) => {
302
+ try {
303
+ return parseCollaborationSkills(await call(
304
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/collaboration/skills`,
305
+ { commandId: validCommandId(commandId) }
306
+ ));
307
+ } catch (cause) {
308
+ if (cause instanceof CodeRuntimeControlError && cause.status === 404 && cause.code === "not_found") return [];
309
+ throw cause;
310
+ }
311
+ },
312
+ executeCollaborationTool: async (sessionId, collaboration, signal) => {
313
+ validateCollaborationToolRequest(collaboration);
314
+ return parseCollaborationToolOutput(await call(
315
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/collaboration/tools`,
316
+ collaboration,
317
+ requestTimeoutMs,
318
+ signal
319
+ ));
320
+ },
321
+ reportSessionFailure: async (sessionId, message2) => {
322
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
323
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
324
+ }
325
+ };
326
+ }
327
+
217
328
  // src/code-runtime.ts
218
329
  var CODE_RUNTIME_PROTOCOL_VERSION = 1;
219
330
  async function runCodeRuntimeHeartbeatLoop(options) {
@@ -1626,6 +1737,7 @@ async function runCodeAgentAttempt(options) {
1626
1737
  model: "brokered",
1627
1738
  surface,
1628
1739
  ...options.recipeIds ? { recipeIds: options.recipeIds } : {},
1740
+ ...options.extraSkills ? { extraSkills: options.extraSkills } : {},
1629
1741
  ...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
1630
1742
  ...options.budget ? { budget: options.budget } : {},
1631
1743
  ...options.signal ? { signal: options.signal } : {},
@@ -1657,6 +1769,48 @@ Finish with a concise, non-empty answer to the owner. Do not call tools or promi
1657
1769
  }
1658
1770
  }
1659
1771
 
1772
+ // src/code-runtime-session-skills.ts
1773
+ function createCodeRuntimeSessionSkillLoader(control) {
1774
+ const load = control.collaborationSkills?.bind(control);
1775
+ const execute2 = control.executeCollaborationTool?.bind(control);
1776
+ if (!load || !execute2) return async () => [];
1777
+ return async (command) => {
1778
+ const manifests = await load(command.sessionId, command.commandId);
1779
+ return manifests.map((manifest) => ({
1780
+ name: manifest.name,
1781
+ ...manifest.instructions === void 0 ? {} : { instructions: manifest.instructions },
1782
+ tools: manifest.tools.map((tool) => ({
1783
+ name: tool.name,
1784
+ description: tool.description,
1785
+ inputSchema: tool.inputSchema,
1786
+ ...tool.concurrency === void 0 ? {} : { concurrency: tool.concurrency },
1787
+ ...tool.outputTaint === void 0 ? {} : { outputTaint: tool.outputTaint },
1788
+ ...tool.acceptsTaint === void 0 ? {} : { acceptsTaint: tool.acceptsTaint },
1789
+ handler: async (input, context) => {
1790
+ if (!context.toolCallId) throw new TypeError("collaboration tool call identity is required");
1791
+ return execute2(command.sessionId, {
1792
+ commandId: command.commandId,
1793
+ toolCallId: context.toolCallId,
1794
+ skill: manifest.name,
1795
+ tool: tool.name,
1796
+ input
1797
+ }, context.signal);
1798
+ }
1799
+ }))
1800
+ }));
1801
+ };
1802
+ }
1803
+ async function sessionSkillsFor(options, command) {
1804
+ try {
1805
+ return await options.sessionSkills?.(command) ?? [];
1806
+ } catch (cause) {
1807
+ options.onDiagnostic?.(
1808
+ `session skills unavailable, continuing with code tools only: ${cause instanceof Error ? cause.message : String(cause)}`
1809
+ );
1810
+ return [];
1811
+ }
1812
+ }
1813
+
1660
1814
  // src/code-runtime-inference.ts
1661
1815
  async function handleCodeRuntimeInference(input) {
1662
1816
  const { command, request, state } = input;
@@ -2852,6 +3006,25 @@ function codeToolResultPresentation(request, response2) {
2852
3006
  };
2853
3007
  }
2854
3008
 
3009
+ // src/code-runtime-acknowledgement-gate.ts
3010
+ function codeRuntimeAcknowledgementGate(signal) {
3011
+ let settle;
3012
+ let settled = false;
3013
+ const ready = new Promise((resolve6) => {
3014
+ settle = resolve6;
3015
+ });
3016
+ const release = (run) => {
3017
+ if (settled) return;
3018
+ settled = true;
3019
+ signal.removeEventListener("abort", onAbort);
3020
+ settle(run);
3021
+ };
3022
+ const onAbort = () => release(false);
3023
+ if (signal.aborted) release(false);
3024
+ else signal.addEventListener("abort", onAbort, { once: true });
3025
+ return { ready, release };
3026
+ }
3027
+
2855
3028
  // src/code-runtime-engine.ts
2856
3029
  var TheseusRuntimeEngine = class {
2857
3030
  constructor(options) {
@@ -2882,6 +3055,8 @@ var TheseusRuntimeEngine = class {
2882
3055
  const active = this.#active.get(command.sessionId);
2883
3056
  if (!active || result.status !== "running") return;
2884
3057
  active.acknowledged = true;
3058
+ active.startGate?.release(true);
3059
+ active.startGate = void 0;
2885
3060
  if (active.failure) await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
2886
3061
  }
2887
3062
  async close() {
@@ -2901,13 +3076,14 @@ var TheseusRuntimeEngine = class {
2901
3076
  control: this.options.control,
2902
3077
  ...this.options.localSource ? { localSource: this.options.localSource } : {}
2903
3078
  });
2904
- const abort = new AbortController();
3079
+ const abort = new AbortController(), startGate = codeRuntimeAcknowledgementGate(abort.signal);
2905
3080
  const conversationRefs = [];
2906
3081
  const active = {
2907
3082
  workspace,
2908
3083
  abort,
2909
3084
  conversationRefs,
2910
3085
  acknowledged: false,
3086
+ startGate,
2911
3087
  role: metadata.role,
2912
3088
  title: metadata.title,
2913
3089
  maxTokensPerInteraction: metadata.maxTokensPerInteraction,
@@ -2931,7 +3107,7 @@ var TheseusRuntimeEngine = class {
2931
3107
  body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
2932
3108
  }, conversationRefs);
2933
3109
  }
2934
- active.done = this.#runAttempt(command, metadata, active).catch(async (cause) => {
3110
+ active.done = startGate.ready.then((run) => run ? this.#runAttempt(command, metadata, active) : null).catch(async (cause) => {
2935
3111
  const detail = runtimeErrorMessage(cause);
2936
3112
  await this.#event(command, { type: "message", actor: "system", body: detail }, conversationRefs).catch(() => void 0);
2937
3113
  await this.#diagnostic(command, active, detail);
@@ -3046,6 +3222,7 @@ var TheseusRuntimeEngine = class {
3046
3222
  event: (event) => this.#event(command, event, active.conversationRefs)
3047
3223
  });
3048
3224
  await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
3225
+ const extraSkills = await sessionSkillsFor(this.options, command);
3049
3226
  const result = await this.#attempt({
3050
3227
  inference,
3051
3228
  broker,
@@ -3053,7 +3230,8 @@ var TheseusRuntimeEngine = class {
3053
3230
  workspaceDir: active.workspace.workspaceDir,
3054
3231
  prompt: metadata.prompt,
3055
3232
  signal: active.abort.signal,
3056
- recipeIds: this.options.recipes.map((recipe2) => recipe2.id)
3233
+ recipeIds: this.options.recipes.map((recipe2) => recipe2.id),
3234
+ ...extraSkills.length ? { extraSkills } : {}
3057
3235
  });
3058
3236
  const closing = result.finalText.trim();
3059
3237
  const completed = result.status === "completed" && Boolean(closing);
@@ -3216,6 +3394,7 @@ async function main() {
3216
3394
  engine,
3217
3395
  recipes: policy.recipes,
3218
3396
  recipeAuthorization: policy.recipeAuthorization,
3397
+ sessionSkills: createCodeRuntimeSessionSkillLoader(control),
3219
3398
  onDiagnostic: (message2) => process.stderr.write(`[odla-code-runtime] agent failed \xB7 ${message2}
3220
3399
  `)
3221
3400
  });