@oh-my-ai-sdk/omakase 0.1.2 → 0.1.4

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 (56) hide show
  1. package/README.md +157 -10
  2. package/dist/agent-catalog.d.ts +202 -0
  3. package/dist/agent-catalog.js +427 -0
  4. package/dist/agent-runtime/manifest.json +1 -1
  5. package/dist/agent-runtime/slice-worker.mjs +112 -24
  6. package/dist/agent-runtime/versions.json +2 -2
  7. package/dist/agent-runtime/worker.mjs +18 -7
  8. package/dist/agent-slice-client.d.ts +54 -6
  9. package/dist/agent-slice-client.js +151 -22
  10. package/dist/agent-slice-contract.d.ts +4 -0
  11. package/dist/agent-slice.d.ts +15 -1
  12. package/dist/agent-slice.js +12 -5
  13. package/dist/agent-tasks.d.ts +112 -0
  14. package/dist/agent-tasks.js +398 -0
  15. package/dist/agents.d.ts +8 -1
  16. package/dist/agents.js +138 -70
  17. package/dist/code-mode.js +12 -2
  18. package/dist/durable-engine.js +23 -2
  19. package/dist/durable-host.d.ts +7 -4
  20. package/dist/durable-host.js +279 -13
  21. package/dist/durable-protocol.d.ts +11 -1
  22. package/dist/durable-protocol.js +3 -2
  23. package/dist/durable-worker/steps.mjs +27 -0
  24. package/dist/durable-worker/versions.json +1 -1
  25. package/dist/durable-worker/worker.mjs +100 -12
  26. package/dist/durable-worker/workflows.mjs +11 -6
  27. package/dist/durable-worker-client.d.ts +1 -0
  28. package/dist/durable-worker-client.js +5 -2
  29. package/dist/frontend.d.ts +2 -2
  30. package/dist/frontend.js +2 -2
  31. package/dist/guest-agent-error.d.ts +11 -0
  32. package/dist/guest-agent-error.js +50 -0
  33. package/dist/skills-cli.js +16 -3
  34. package/dist/skills-server.js +1 -1
  35. package/dist/workflow-host.js +9 -1
  36. package/dist/workflow.d.ts +4 -0
  37. package/examples/agent-tasks.config.mjs +63 -0
  38. package/package.json +5 -2
  39. package/skill/omakase-codemode/SKILL.md +86 -0
  40. package/skill/omakase-codemode/examples/0-parallel-agents.ts +76 -0
  41. package/skill/omakase-codemode/examples/1-decidable-probe.ts +62 -0
  42. package/skill/omakase-codemode/examples/2-review-branch.ts +95 -0
  43. package/skill/omakase-codemode/examples/3-fix-loop.ts +163 -0
  44. package/skill/omakase-codemode/examples/4-unit-pipeline.ts +171 -0
  45. package/skill/omakase-codemode/examples/5-inline-run.ts +48 -0
  46. package/skill/omakase-codemode/examples/6-judge-host.ts +144 -0
  47. package/skill/omakase-codemode/examples/7-discover-and-steer.ts +93 -0
  48. package/skill/omakase-codemode/examples/README.md +38 -0
  49. package/skill/omakase-codemode/references/briefs.md +82 -0
  50. package/skill/omakase-codemode/references/evidence.md +62 -0
  51. package/skill/omakase-codemode/references/facts.md +68 -0
  52. package/skill/omakase-codemode/references/gates.md +73 -0
  53. package/skill/omakase-codemode/references/preflight.md +48 -0
  54. package/skill/omakase-codemode/references/review.md +75 -0
  55. package/skill/omakase-codemode/references/runbook.md +82 -0
  56. package/skill/omakase-codemode/references/tool-surface.md +66 -0
@@ -0,0 +1,427 @@
1
+ /**
2
+ * The host-owned agent catalog and the generic `agent` namespace it backs.
3
+ *
4
+ * `mountAgents` (agents.ts) turns trusted Flow backend references into named
5
+ * `agents.NAME` tools. This module adds the second, generic surface on the
6
+ * SAME execution machinery: `agent.run`, discriminated between a named
7
+ * registration (`{ agent, prompt, workspaceDir? }` — byte-identical result
8
+ * contract to `agents.NAME`) and an inline selection (`{ harness, provider,
9
+ * model, settings?, prompt, workspaceDir? }` → `{ sessionId, text }`), plus
10
+ * `agent.steer`, routed to the task-control port owned by agent-tasks.ts.
11
+ *
12
+ * Authority model: the host registers harnesses, the providers under each
13
+ * harness, the models authorized on each provider and named settings presets.
14
+ * Every credential pointer (`${file:…}`, `${VAR}`), sandbox env declaration,
15
+ * permission mode and option fragment lives in that host-owned registration
16
+ * and is composed into a trusted inline backend spec — the object
17
+ * presentation the flow provider already supports (inlineManifest). Guests
18
+ * pass only registered names, an authorized model id and a settings key;
19
+ * they can never supply executables, environment, paths or credentials, and
20
+ * no second backend loader exists.
21
+ *
22
+ * Identity protocol (agent-slice.ts): every agent tool registers a
23
+ * whole-catalog inspector (admission drift rejection — any change to the
24
+ * mounted authority, including unrelated additions, blocks recovery) and a
25
+ * selected-identity inspector resolving the per-invocation backend identity
26
+ * the durable host pins before native execution. The catalog fingerprint
27
+ * incorporates each named profile's resolved inspect identity, so content
28
+ * drift behind an unchanged ref string rejects recovery too. Inline specs
29
+ * never appear verbatim in identity, evidence or errors — only their stable
30
+ * content id does.
31
+ */
32
+ import { createHash } from 'node:crypto';
33
+ import { tool, jsonSchema } from 'ai';
34
+ import { AgentHostError } from './agents.js';
35
+ import { agentSteerTool, invokeAgentSteer } from './agent-tasks.js';
36
+ import { registerAgentRecovery } from './agent-slice.js';
37
+ import { assertJson } from './schema.js';
38
+ const NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
39
+ const PERMISSION_MODES = ['allow-reads', 'allow-edits', 'allow-all'];
40
+ function isPlainObject(value) {
41
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
42
+ && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
43
+ }
44
+ function plainObject(value, where) {
45
+ if (!isPlainObject(value))
46
+ throw new AgentHostError('INVALID_INPUT', `${where} must be a plain object`);
47
+ return value;
48
+ }
49
+ function rejectUnknownKeys(record, allowed, where) {
50
+ for (const key of Object.keys(record)) {
51
+ if (!allowed.includes(key))
52
+ throw new AgentHostError('INVALID_INPUT', `Unknown ${where} field: ${JSON.stringify(key)}; allowed: ${[...allowed].sort().join(', ')}`);
53
+ }
54
+ }
55
+ function optionalNonblankString(value, where) {
56
+ if (value === undefined)
57
+ return undefined;
58
+ if (typeof value !== 'string' || !value.trim())
59
+ throw new AgentHostError('INVALID_INPUT', `${where} must be a nonblank string when present`);
60
+ return value;
61
+ }
62
+ /** Own-key lookup into a registration dictionary: names inherited from
63
+ * Object.prototype (`constructor`, `toString`, `__proto__`, …) never
64
+ * resolve — only explicitly registered own keys grant authority. */
65
+ function ownEntry(record, key) {
66
+ return record !== undefined && Object.hasOwn(record, key) ? record[key] : undefined;
67
+ }
68
+ /** A trusted, host-owned option fragment. JSON-only, cloned into the
69
+ * snapshot, and never allowed to declare `model`: model authority belongs to
70
+ * the registered model key the caller selects. */
71
+ function trustedOptions(value, where) {
72
+ if (value === undefined)
73
+ return undefined;
74
+ const record = plainObject(value, where);
75
+ try {
76
+ assertJson(record);
77
+ }
78
+ catch (error) {
79
+ throw new AgentHostError('INVALID_INPUT', `${where} must be JSON-compatible (${error instanceof Error ? error.message : String(error)})`);
80
+ }
81
+ if (Object.hasOwn(record, 'model')) {
82
+ throw new AgentHostError('INVALID_INPUT', `${where} must not declare "model" — model authority belongs to the registered model key selected by the caller`);
83
+ }
84
+ // assertJson above established JSON compatibility; the cast states it.
85
+ return structuredClone(record);
86
+ }
87
+ function stringRecord(value, where) {
88
+ if (value === undefined)
89
+ return undefined;
90
+ const record = plainObject(value, where);
91
+ // Null prototype: keys are freeform, so a trusted-JSON own "__proto__" key
92
+ // registers as the declared entry instead of rewiring this record's
93
+ // prototype.
94
+ const out = Object.create(null);
95
+ for (const [key, entry] of Object.entries(record)) {
96
+ if (!key)
97
+ throw new AgentHostError('INVALID_INPUT', `${where} keys must be nonempty`);
98
+ if (typeof entry !== 'string' || !entry)
99
+ throw new AgentHostError('INVALID_INPUT', `${where}.${key} must be a nonempty string (credential pointers like \${file:…} are resolved against the host environment only)`);
100
+ out[key] = entry;
101
+ }
102
+ return out;
103
+ }
104
+ function settingsLike(value, where) {
105
+ if (value === undefined)
106
+ return undefined;
107
+ const record = plainObject(value, where);
108
+ const out = {};
109
+ for (const [key, entry] of Object.entries(record)) {
110
+ if (!NAME_PATTERN.test(key))
111
+ throw new AgentHostError('INVALID_INPUT', `${where} key ${JSON.stringify(key)} must match ${NAME_PATTERN.source}`);
112
+ const fields = plainObject(entry, `${where}.${key}`);
113
+ rejectUnknownKeys(fields, ['options'], `${where}.${key}`);
114
+ const options = trustedOptions(fields.options, `${where}.${key}.options`);
115
+ out[key] = options === undefined ? {} : { options };
116
+ }
117
+ return out;
118
+ }
119
+ /** Validates and snapshots a catalog before any host effect (namespace
120
+ * registration, filesystem, workers). Throws typed INVALID_INPUT naming the
121
+ * offending field; forbidden overrides (a `model` key in any option
122
+ * fragment) are rejected here, before effects, per the authority contract. */
123
+ export function validateAgentCatalog(catalog) {
124
+ const root = plainObject(catalog, 'catalog');
125
+ rejectUnknownKeys(root, ['harnesses'], 'catalog');
126
+ const harnessesRaw = plainObject(root.harnesses, 'catalog.harnesses');
127
+ if (Object.keys(harnessesRaw).length === 0)
128
+ throw new AgentHostError('INVALID_INPUT', 'catalog.harnesses must register at least one harness');
129
+ const harnesses = {};
130
+ for (const [harnessKey, harnessRaw] of Object.entries(harnessesRaw)) {
131
+ if (!NAME_PATTERN.test(harnessKey))
132
+ throw new AgentHostError('INVALID_INPUT', `catalog.harnesses key ${JSON.stringify(harnessKey)} must match ${NAME_PATTERN.source}`);
133
+ const where = `catalog.harnesses.${harnessKey}`;
134
+ const h = plainObject(harnessRaw, where);
135
+ rejectUnknownKeys(h, ['harness', 'description', 'instructions', 'permissionMode', 'toolApproval', 'activeTools', 'sandbox', 'options', 'providers'], where);
136
+ if (typeof h.harness !== 'string' || !h.harness.trim())
137
+ throw new AgentHostError('INVALID_INPUT', `${where}.harness must be a nonblank adapter id (e.g. 'claude-code')`);
138
+ if (h.permissionMode !== undefined && !PERMISSION_MODES.includes(h.permissionMode)) {
139
+ throw new AgentHostError('INVALID_INPUT', `${where}.permissionMode must be one of ${PERMISSION_MODES.join(', ')}`);
140
+ }
141
+ let activeTools;
142
+ if (h.activeTools !== undefined) {
143
+ if (!Array.isArray(h.activeTools))
144
+ throw new AgentHostError('INVALID_INPUT', `${where}.activeTools must be an array of tool names`);
145
+ activeTools = h.activeTools.map(name => {
146
+ if (typeof name !== 'string' || !name)
147
+ throw new AgentHostError('INVALID_INPUT', `${where}.activeTools entries must be nonempty strings`);
148
+ return name;
149
+ });
150
+ }
151
+ let sandbox = {};
152
+ if (h.sandbox !== undefined) {
153
+ const raw = plainObject(h.sandbox, `${where}.sandbox`);
154
+ rejectUnknownKeys(raw, ['type', 'inheritEnv', 'warm'], `${where}.sandbox`);
155
+ if (raw.type !== undefined && raw.type !== 'local' && raw.type !== 'local-persistent')
156
+ throw new AgentHostError('INVALID_INPUT', `${where}.sandbox.type must be 'local' or 'local-persistent'`);
157
+ if (raw.inheritEnv !== undefined && typeof raw.inheritEnv !== 'boolean')
158
+ throw new AgentHostError('INVALID_INPUT', `${where}.sandbox.inheritEnv must be a boolean`);
159
+ if (raw.warm !== undefined && typeof raw.warm !== 'boolean')
160
+ throw new AgentHostError('INVALID_INPUT', `${where}.sandbox.warm must be a boolean`);
161
+ sandbox = {
162
+ ...(raw.type !== undefined ? { type: raw.type } : {}),
163
+ ...(raw.inheritEnv !== undefined ? { inheritEnv: raw.inheritEnv } : {}),
164
+ ...(raw.warm !== undefined ? { warm: raw.warm } : {}),
165
+ };
166
+ }
167
+ const providersRaw = plainObject(h.providers, `${where}.providers`);
168
+ if (Object.keys(providersRaw).length === 0)
169
+ throw new AgentHostError('INVALID_INPUT', `${where}.providers must register at least one provider`);
170
+ const providers = {};
171
+ for (const [providerKey, providerRaw] of Object.entries(providersRaw)) {
172
+ if (!NAME_PATTERN.test(providerKey))
173
+ throw new AgentHostError('INVALID_INPUT', `${where}.providers key ${JSON.stringify(providerKey)} must match ${NAME_PATTERN.source}`);
174
+ const pWhere = `${where}.providers.${providerKey}`;
175
+ const p = plainObject(providerRaw, pWhere);
176
+ rejectUnknownKeys(p, ['description', 'models', 'options', 'sandboxEnv', 'processEnv', 'settings'], pWhere);
177
+ const description = optionalNonblankString(p.description, `${pWhere}.description`);
178
+ const providerOptions = trustedOptions(p.options, `${pWhere}.options`);
179
+ const sandboxEnv = stringRecord(p.sandboxEnv, `${pWhere}.sandboxEnv`);
180
+ const processEnv = stringRecord(p.processEnv, `${pWhere}.processEnv`);
181
+ const settings = settingsLike(p.settings, `${pWhere}.settings`);
182
+ const modelsRaw = plainObject(p.models, `${pWhere}.models`);
183
+ if (Object.keys(modelsRaw).length === 0)
184
+ throw new AgentHostError('INVALID_INPUT', `${pWhere}.models must authorize at least one model`);
185
+ // Null prototype: model ids are freeform, so a trusted-JSON own
186
+ // "__proto__" key registers as the declared entry instead of rewiring
187
+ // this record's prototype.
188
+ const models = Object.create(null);
189
+ for (const [modelKey, modelRaw] of Object.entries(modelsRaw)) {
190
+ if (!modelKey.trim())
191
+ throw new AgentHostError('INVALID_INPUT', `${pWhere}.models keys must be nonempty model ids`);
192
+ const mWhere = `${pWhere}.models.${JSON.stringify(modelKey)}`;
193
+ const m = plainObject(modelRaw, mWhere);
194
+ rejectUnknownKeys(m, ['options'], mWhere);
195
+ const modelOptions = trustedOptions(m.options, `${mWhere}.options`);
196
+ models[modelKey] = modelOptions === undefined ? {} : { options: modelOptions };
197
+ }
198
+ providers[providerKey] = {
199
+ key: providerKey,
200
+ ...(description !== undefined ? { description } : {}),
201
+ models,
202
+ ...(providerOptions !== undefined ? { options: providerOptions } : {}),
203
+ ...(sandboxEnv !== undefined ? { sandboxEnv } : {}),
204
+ ...(processEnv !== undefined ? { processEnv } : {}),
205
+ ...(settings !== undefined ? { settings } : {}),
206
+ };
207
+ }
208
+ const harnessDescription = optionalNonblankString(h.description, `${where}.description`);
209
+ const instructions = optionalNonblankString(h.instructions, `${where}.instructions`);
210
+ const toolApproval = trustedOptions(h.toolApproval, `${where}.toolApproval`);
211
+ const harnessOptions = trustedOptions(h.options, `${where}.options`);
212
+ harnesses[harnessKey] = {
213
+ key: harnessKey,
214
+ harness: h.harness,
215
+ ...(harnessDescription !== undefined ? { description: harnessDescription } : {}),
216
+ ...(instructions !== undefined ? { instructions } : {}),
217
+ ...(h.permissionMode !== undefined ? { permissionMode: h.permissionMode } : {}),
218
+ ...(toolApproval !== undefined ? { toolApproval } : {}),
219
+ ...(activeTools !== undefined ? { activeTools } : {}),
220
+ sandbox,
221
+ ...(harnessOptions !== undefined ? { options: harnessOptions } : {}),
222
+ providers,
223
+ };
224
+ }
225
+ return { harnesses };
226
+ }
227
+ /** Deterministic JSON with recursively sorted object keys: equal
228
+ * configurations hash equal regardless of authoring key order. */
229
+ export function canonicalJson(value) {
230
+ if (Array.isArray(value))
231
+ return `[${value.map(canonicalJson).join(',')}]`;
232
+ if (value !== null && typeof value === 'object') {
233
+ const keys = Object.keys(value).sort();
234
+ return `{${keys.map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
235
+ }
236
+ return JSON.stringify(value) ?? 'null';
237
+ }
238
+ /** Stable, secret-free fingerprint of the whole mounted agent authority.
239
+ * Inputs are the validated catalog snapshot, the trusted slice budget and —
240
+ * critically — each named profile's RESOLVED inspect identity, so content
241
+ * drift behind an unchanged ref string (an agent.yaml edit on the host)
242
+ * rejects admission recovery even for tools a pending cell never invoked.
243
+ * Inline config drift is covered the same way: the selected identity embeds
244
+ * the resolved manifest fingerprint of the composed spec. The result is a
245
+ * hex hash — no configuration object, pointer or secret is serialized. */
246
+ export function agentCatalogFingerprint(input) {
247
+ const material = {
248
+ v: 1,
249
+ kind: 'omakase-agent-catalog',
250
+ named: input.named.map(({ name, identity }) => ({ name, identity })),
251
+ catalog: input.catalog ?? null,
252
+ sliceSeconds: input.sliceSeconds,
253
+ };
254
+ return `omakase-agent-catalog-v1:${createHash('sha256').update(canonicalJson(material)).digest('hex')}`;
255
+ }
256
+ /** In-memory memo key for one backend ref: the ref itself when named, a hash
257
+ * of the canonical spec otherwise. Never persisted, logged or sent. */
258
+ export function agentBackendRefKey(ref) {
259
+ return typeof ref === 'string' ? ref : `inline:${createHash('sha256').update(canonicalJson(ref)).digest('hex')}`;
260
+ }
261
+ /** Deep merge of trusted option fragments: plain objects merge recursively,
262
+ * everything else (arrays, scalars) replaces. Later fragments win; the
263
+ * caller-selected model id is placed last under `options.model`. Own keys
264
+ * only: inherited names never merge, and a JSON-declared own `__proto__` key
265
+ * lands as a data property instead of rewiring the prototype. */
266
+ function mergeOptions(fragments) {
267
+ const out = {};
268
+ for (const fragment of fragments) {
269
+ if (fragment === undefined)
270
+ continue;
271
+ for (const [key, value] of Object.entries(fragment)) {
272
+ const prior = ownEntry(out, key);
273
+ const merged = isPlainObject(prior) && isPlainObject(value)
274
+ ? mergeOptions([prior, value])
275
+ : value;
276
+ // Trusted JSON can declare an own "__proto__" data property; plain
277
+ // assignment would rewire out's prototype instead of merging it.
278
+ if (key === '__proto__')
279
+ Object.defineProperty(out, key, { value: merged, writable: true, enumerable: true, configurable: true });
280
+ else
281
+ out[key] = merged;
282
+ }
283
+ }
284
+ return out;
285
+ }
286
+ /** Validates one agent.run input against the mounted authority and resolves
287
+ * the backend selection. Pure: no worker, no filesystem, no effects — every
288
+ * unregistered or mixed selection fails here, typed, before anything runs.
289
+ * Registration lookups are own-key: names inherited from Object.prototype
290
+ * (`constructor`, `toString`, `__proto__`, …) never grant authority. */
291
+ export function resolveAgentRunSelection(input, named, catalog) {
292
+ if (!isPlainObject(input))
293
+ throw new AgentHostError('INVALID_INPUT', 'agent.run input must be an object');
294
+ if (input.agent !== undefined && typeof input.agent !== 'string')
295
+ throw new AgentHostError('INVALID_INPUT', 'agent.run agent must be a string when present');
296
+ if (input.harness !== undefined && typeof input.harness !== 'string')
297
+ throw new AgentHostError('INVALID_INPUT', 'agent.run harness must be a string when present');
298
+ if (input.provider !== undefined && typeof input.provider !== 'string')
299
+ throw new AgentHostError('INVALID_INPUT', 'agent.run provider must be a string when present');
300
+ if (input.model !== undefined && typeof input.model !== 'string')
301
+ throw new AgentHostError('INVALID_INPUT', 'agent.run model must be a string when present');
302
+ if (input.settings !== undefined && typeof input.settings !== 'string')
303
+ throw new AgentHostError('INVALID_INPUT', 'agent.run settings must be a string when present');
304
+ const { agent, harness, provider, model, settings, prompt, workspaceDir } = input;
305
+ if (prompt === undefined || !prompt.trim())
306
+ throw new AgentHostError('INVALID_INPUT', 'agent.run requires a nonblank prompt');
307
+ if (workspaceDir !== undefined && !workspaceDir)
308
+ throw new AgentHostError('INVALID_INPUT', 'agent.run workspaceDir must be a nonempty string when present');
309
+ const namedChosen = agent !== undefined;
310
+ const inlineChosen = harness !== undefined || provider !== undefined || model !== undefined || settings !== undefined;
311
+ if (namedChosen && inlineChosen)
312
+ throw new AgentHostError('INVALID_INPUT', 'agent.run takes either { agent } or { harness, provider, model, settings? }, never both');
313
+ if (!namedChosen && !inlineChosen)
314
+ throw new AgentHostError('INVALID_INPUT', 'agent.run requires either a registered agent name or a harness/provider/model selection');
315
+ if (namedChosen) {
316
+ if (!NAME_PATTERN.test(agent))
317
+ throw new AgentHostError('INVALID_INPUT', `agent.run agent name is invalid: ${JSON.stringify(agent)}`);
318
+ const registration = named.get(agent);
319
+ if (registration === undefined) {
320
+ throw new AgentHostError('INVALID_INPUT', `agent.run has no registered agent named ${JSON.stringify(agent)}; registered: ${[...named.keys()].sort().join(', ') || '(none)'}`);
321
+ }
322
+ return { kind: 'named', name: agent, ref: registration.ref, ...(registration.outputSchema !== undefined ? { outputSchema: registration.outputSchema } : {}) };
323
+ }
324
+ if (catalog === undefined)
325
+ throw new AgentHostError('INVALID_INPUT', 'agent.run inline selection requires a mounted harness catalog; this host registered none');
326
+ if (harness === undefined || provider === undefined || model === undefined) {
327
+ throw new AgentHostError('INVALID_INPUT', 'agent.run inline selection requires harness, provider and model');
328
+ }
329
+ const harnessRegistration = ownEntry(catalog.harnesses, harness);
330
+ if (harnessRegistration === undefined) {
331
+ throw new AgentHostError('INVALID_INPUT', `agent.run has no registered harness named ${JSON.stringify(harness)}; registered: ${Object.keys(catalog.harnesses).sort().join(', ')}`);
332
+ }
333
+ const providerRegistration = ownEntry(harnessRegistration.providers, provider);
334
+ if (providerRegistration === undefined) {
335
+ throw new AgentHostError('INVALID_INPUT', `agent.run harness ${JSON.stringify(harness)} has no provider named ${JSON.stringify(provider)}; registered: ${Object.keys(harnessRegistration.providers).sort().join(', ')}`);
336
+ }
337
+ const modelRegistration = ownEntry(providerRegistration.models, model);
338
+ if (modelRegistration === undefined) {
339
+ throw new AgentHostError('INVALID_INPUT', `agent.run model ${JSON.stringify(model)} is not authorized on ${harness}/${provider}; authorized: ${Object.keys(providerRegistration.models).sort().join(', ')}`);
340
+ }
341
+ let settingsOptions;
342
+ if (settings !== undefined) {
343
+ const preset = ownEntry(providerRegistration.settings, settings);
344
+ if (preset === undefined) {
345
+ throw new AgentHostError('INVALID_INPUT', `agent.run settings ${JSON.stringify(settings)} is not registered on ${harness}/${provider}; registered: ${Object.keys(providerRegistration.settings ?? {}).sort().join(', ') || '(none)'}`);
346
+ }
347
+ settingsOptions = preset.options;
348
+ }
349
+ const sandbox = {
350
+ ...(harnessRegistration.sandbox.type !== undefined ? { type: harnessRegistration.sandbox.type } : {}),
351
+ ...(providerRegistration.sandboxEnv !== undefined ? { env: providerRegistration.sandboxEnv } : {}),
352
+ ...(harnessRegistration.sandbox.inheritEnv !== undefined ? { inheritEnv: harnessRegistration.sandbox.inheritEnv } : {}),
353
+ ...(harnessRegistration.sandbox.warm !== undefined ? { warm: harnessRegistration.sandbox.warm } : {}),
354
+ };
355
+ const spec = {
356
+ harness: harnessRegistration.harness,
357
+ ...(harnessRegistration.instructions !== undefined ? { instructions: harnessRegistration.instructions } : {}),
358
+ options: mergeOptions([harnessRegistration.options, providerRegistration.options, modelRegistration.options, settingsOptions, { model }]),
359
+ ...(Object.keys(sandbox).length > 0 ? { sandbox } : {}),
360
+ ...(providerRegistration.processEnv !== undefined ? { processEnv: providerRegistration.processEnv } : {}),
361
+ ...(harnessRegistration.permissionMode !== undefined ? { permissionMode: harnessRegistration.permissionMode } : {}),
362
+ ...(harnessRegistration.toolApproval !== undefined ? { toolApproval: harnessRegistration.toolApproval } : {}),
363
+ ...(harnessRegistration.activeTools !== undefined ? { activeTools: harnessRegistration.activeTools } : {}),
364
+ };
365
+ return { kind: 'inline', spec, label: `inline ${harness}/${provider}/${model}${settings !== undefined ? ` settings=${settings}` : ''}` };
366
+ }
367
+ /** Nonsecret, host-derived selection summary for the tool description. */
368
+ function describeCatalog(mount) {
369
+ const namedNames = [...mount.named.keys()].sort();
370
+ const harnessLines = Object.values(mount.catalog?.harnesses ?? {}).sort((a, b) => a.key.localeCompare(b.key)).map(harness => {
371
+ const providers = Object.values(harness.providers).sort((a, b) => a.key.localeCompare(b.key)).map(provider => `${provider.key} (models: ${Object.keys(provider.models).sort().join(', ')}${provider.settings !== undefined ? `; settings: ${Object.keys(provider.settings).sort().join(', ')}` : ''})`);
372
+ return `${harness.key} [${harness.harness}]: ${providers.join(' | ')}`;
373
+ });
374
+ return [
375
+ `Named agents: ${namedNames.join(', ') || '(none registered)'}.`,
376
+ harnessLines.length > 0 ? `Inline harnesses: ${harnessLines.join(' ')}` : 'No inline harness catalog is registered on this host.',
377
+ ].join(' ');
378
+ }
379
+ /** Registers the `agent` namespace — `run` (named or inline selection) and
380
+ * `steer` (routed to the task-control port) — and the recovery inspectors
381
+ * of `agent.run`. Called once from mountAgents after the named tools. */
382
+ export async function mountAgentCatalog(mount) {
383
+ const { host } = mount;
384
+ const runTool = tool({
385
+ description: `Run a registered agent. Named: { agent, prompt, workspaceDir? } returns the agent's declared structured result (identical to its agents.NAME tool). Inline: { harness, provider, model, settings?, prompt, workspaceDir? } composes the registered harness/provider configuration around the authorized model and returns { sessionId, text }. ${describeCatalog(mount)}`,
386
+ inputSchema: jsonSchema({
387
+ type: 'object',
388
+ properties: {
389
+ agent: { type: 'string', pattern: '^[a-zA-Z0-9][a-zA-Z0-9_-]*$', description: 'Named mode: a registered agent name. Exclusive with harness/provider/model/settings.' },
390
+ harness: { type: 'string', pattern: '^[a-zA-Z0-9][a-zA-Z0-9_-]*$', description: 'Inline mode: a registered harness key.' },
391
+ provider: { type: 'string', pattern: '^[a-zA-Z0-9][a-zA-Z0-9_-]*$', description: 'Inline mode: a provider key registered under the harness.' },
392
+ model: { type: 'string', minLength: 1, description: 'Inline mode: a model id authorized on the provider.' },
393
+ settings: { type: 'string', pattern: '^[a-zA-Z0-9][a-zA-Z0-9_-]*$', description: 'Inline mode: optional settings key registered on the provider.' },
394
+ prompt: { type: 'string', pattern: '\\S', description: 'The task for the agent.' },
395
+ workspaceDir: { type: 'string', minLength: 1, description: 'Omit to use the registered agent workspace. Optional subdirectory must stay inside that root; the calling harness current directory may be elsewhere.' },
396
+ },
397
+ required: ['prompt'],
398
+ additionalProperties: false,
399
+ }),
400
+ // Safe only because same-intent invocations are deduplicated in the
401
+ // shared run machinery, selection included.
402
+ metadata: { omakase: { replay: 'safe' } },
403
+ execute: async (input, execution) => {
404
+ if (typeof execution.toolCallId !== 'string' || !execution.toolCallId)
405
+ throw new AgentHostError('INVALID_INPUT', 'Agent tools require a tool call id');
406
+ const selection = resolveAgentRunSelection(input, mount.named, mount.catalog);
407
+ return await mount.runSelected(selection, input, { toolCallId: execution.toolCallId, abortSignal: execution.abortSignal });
408
+ },
409
+ });
410
+ await host.register('agent', { run: runTool, steer: agentSteerTool }, {
411
+ resolveBinding: invocation => invocation.toolName === 'agent.steer'
412
+ ? { id: 'agent.steer', target: agentSteerTool }
413
+ : { id: 'agent.run', target: runTool },
414
+ executor: {
415
+ replays: false,
416
+ invoke: async (invocation, binding, options) => {
417
+ if (invocation.toolName === 'agent.steer')
418
+ return await invokeAgentSteer(host, invocation);
419
+ return await binding.target.execute(invocation.input, {
420
+ ...options,
421
+ toolCallId: JSON.stringify([invocation.harnessId ?? null, invocation.sessionId ?? null, invocation.toolCallId]),
422
+ });
423
+ },
424
+ },
425
+ });
426
+ registerAgentRecovery(host, 'agent.run', mount.catalogFingerprint, input => mount.identityForSelection(resolveAgentRunSelection(input, mount.named, mount.catalog)));
427
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-11T16:56:28.358Z",
2
+ "generatedAt": "2026-10-12T07:09:16.138Z",
3
3
  "entry": "worker.mjs",
4
4
  "runtime": "bun >=1.3.14 (launched per run by the host; stdin/stdout JSON wire)",
5
5
  "adapters": {