@agentproto/runtime 4.0.0 → 5.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.
@@ -1,11 +1,11 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { join, resolve } from 'path';
3
+ import '@agentproto/model-catalog/llm';
3
4
  import { createReadStream, promises } from 'fs';
4
5
  import { homedir } from 'os';
5
6
  import { createInterface } from 'readline';
6
7
  import 'fs/promises';
7
8
  import * as providers_store_star from '@agentproto/providers-store';
8
- import '@agentproto/model-catalog/llm';
9
9
  import '@agentproto/model-catalog/route-identity';
10
10
  import '@agentproto/provider-presets';
11
11
  import '@agentproto/secrets/provision/recipe';
@@ -169,6 +169,10 @@ var init_tool_call_record = __esm({
169
169
  "src/tool-call-record.ts"() {
170
170
  }
171
171
  });
172
+ var init_usage = __esm({
173
+ "src/usage.ts"() {
174
+ }
175
+ });
172
176
  function defaultTranscriptBaseDir() {
173
177
  return join(homedir(), ".agentproto", "sessions");
174
178
  }
@@ -182,6 +186,7 @@ var init_transcript_writer = __esm({
182
186
  "src/transcript-writer.ts"() {
183
187
  init_tool_call_record();
184
188
  init_pr_provenance();
189
+ init_usage();
185
190
  }
186
191
  });
187
192
 
@@ -578,11 +583,11 @@ async function discoverHermesSessions(cwd, since, expectedId) {
578
583
  }
579
584
  }
580
585
  async function defaultHermesRunner(adapterSessionId) {
581
- const { spawn: spawn4 } = await import('child_process');
586
+ const { spawn: spawn5 } = await import('child_process');
582
587
  return new Promise((resolve16, reject) => {
583
588
  const chunks = [];
584
589
  const errChunks = [];
585
- const proc = spawn4(
590
+ const proc = spawn5(
586
591
  "hermes",
587
592
  ["sessions", "export", "--session-id", adapterSessionId, "-"],
588
593
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -1929,6 +1934,29 @@ var init_worktree_identity = __esm({
1929
1934
  z.object({ worktreeId: z.string() });
1930
1935
  }
1931
1936
  });
1937
+ var init_mcp_imports = __esm({
1938
+ "src/mcp-imports.ts"() {
1939
+ }
1940
+ });
1941
+ var bundleIdSchema, bundleSchema;
1942
+ var init_bundles = __esm({
1943
+ "src/bundles.ts"() {
1944
+ init_mcp_imports();
1945
+ bundleIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "id must be lowercase kebab-case (letters, digits, hyphens)");
1946
+ bundleSchema = z.object({
1947
+ id: bundleIdSchema,
1948
+ label: z.string().min(1),
1949
+ description: z.string().min(1).optional(),
1950
+ mcpImports: z.array(z.string().min(1)),
1951
+ includeDaemon: z.boolean().optional(),
1952
+ skills: z.array(z.string().min(1))
1953
+ });
1954
+ z.object({
1955
+ version: z.literal(1),
1956
+ bundles: z.array(bundleSchema)
1957
+ });
1958
+ }
1959
+ });
1932
1960
  var init_catalog_models = __esm({
1933
1961
  "src/catalog-models.ts"() {
1934
1962
  init_spawn_defaults();
@@ -2132,10 +2160,13 @@ var init_sandbox_adapters = __esm({
2132
2160
  var init_session_spawn = __esm({
2133
2161
  "src/session-spawn.ts"() {
2134
2162
  init_sessions();
2163
+ init_transcript_writer();
2135
2164
  init_workspaces_config();
2136
2165
  init_config();
2137
2166
  init_worktree_identity();
2138
2167
  init_spawn_defaults();
2168
+ init_bundles();
2169
+ init_mcp_imports();
2139
2170
  init_launch_config();
2140
2171
  init_claude_code_oauth_source();
2141
2172
  init_providers_store();
@@ -2189,7 +2220,7 @@ var init_sandbox_spec_schema = __esm({
2189
2220
  }).strict();
2190
2221
  }
2191
2222
  });
2192
- var commandSandboxSchema, attachFieldSchema, contextContinuityInputSchema;
2223
+ var commandSandboxSchema, attachFieldSchema, contentBlockSchema, promptInputSchema, contextContinuityInputSchema;
2193
2224
  var init_spawn_field_schemas = __esm({
2194
2225
  "src/spawn-field-schemas.ts"() {
2195
2226
  commandSandboxSchema = z.enum(["off", "workspace", "strict"]);
@@ -2197,6 +2228,12 @@ var init_spawn_field_schemas = __esm({
2197
2228
  z.boolean(),
2198
2229
  z.object({ parent: z.string().min(1).optional() })
2199
2230
  ]);
2231
+ contentBlockSchema = z.record(z.string(), z.unknown());
2232
+ promptInputSchema = z.union([
2233
+ z.string().min(1),
2234
+ contentBlockSchema,
2235
+ z.array(contentBlockSchema).min(1)
2236
+ ]);
2200
2237
  contextContinuityInputSchema = z.object({
2201
2238
  mode: z.enum(["manual", "ask", "auto"]).optional(),
2202
2239
  warnAtPct: z.number().int().min(0).max(100).optional(),
@@ -2217,7 +2254,7 @@ var init_spawn_field_schemas = __esm({
2217
2254
  });
2218
2255
  }
2219
2256
  });
2220
- var mcpBool, mcpPositiveNumber, agentStartInputShape, agentStartInputSchema;
2257
+ var mcpBool, mcpPositiveNumber, help, agentStartInputShape, agentStartInputSchema;
2221
2258
  var init_agent_start_schema = __esm({
2222
2259
  "src/agent-start-schema.ts"() {
2223
2260
  init_json_tolerant();
@@ -2231,78 +2268,82 @@ var init_agent_start_schema = __esm({
2231
2268
  (v) => typeof v === "string" && v.trim() !== "" ? Number(v) : v,
2232
2269
  z.number().positive()
2233
2270
  );
2271
+ help = (field) => `Details: tool_help {name:"agent_start", topic:"${field}"}`;
2234
2272
  agentStartInputShape = {
2235
2273
  adapter: z.string().min(1).optional().describe(
2236
- "Adapter slug \u2014 one of the installed `@agentproto/adapter-*` packages (e.g. 'claude-code', 'hermes', 'aider'). Omit only when `presetId` names a saved preset with an adapter."
2237
- ),
2238
- harness: z.string().min(1).optional().describe(
2239
- "Canonical harness slug \u2014 alias for `adapter`. Accepts the same values; use whichever field your caller produces."
2274
+ `Adapter slug (e.g. 'claude-code', 'hermes', 'aider'). Omit only when \`presetId\` supplies one. ${help("adapter")}`
2240
2275
  ),
2276
+ harness: z.string().min(1).optional().describe(`Alias for \`adapter\` \u2014 same values, whichever your caller produces.`),
2241
2277
  presetId: z.string().min(1).optional().describe(
2242
- "Saved user spawn preset id from `agentproto preset list`. Its adapter and decomposed axes are applied first; explicit fields on this call override it."
2278
+ "Saved spawn preset id from `agentproto preset list`. Explicit fields on this call override its values."
2243
2279
  ),
2244
2280
  workspaceSlug: z.string().optional().describe(
2245
- "Workspace slug from `agentproto workspace list`. The daemon resolves it to an absolute path. Omit to use the `cwd` field or the active workspace."
2281
+ "Workspace slug from `agentproto workspace list`, resolved to a path. Omit to use `cwd` or the active workspace."
2246
2282
  ),
2247
- cwd: z.string().optional().describe(
2248
- "Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."
2249
- ),
2250
- prompt: z.string().optional().describe(
2251
- "Optional initial prompt. The session is spawned and the prompt dispatched in one shot \u2014 equivalent to `start` then `prompt` back-to-back. Skip to spawn idle."
2252
- ),
2253
- label: z.string().optional().describe(
2254
- "Free-text label that surfaces in `agent_sessions_list` and the UI \u2014 useful for tagging sessions with a conversation id or operator name."
2283
+ cwd: z.string().optional().describe("Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."),
2284
+ prompt: promptInputSchema.optional().describe(
2285
+ 'Optional initial prompt \u2014 spawns and dispatches it in one call (spawn + `agent_prompt` combined). Omit to spawn idle. A plain string is composed with the role/AGENTS.md disposition preamble as usual; a content block or block array (e.g. a pasted image, `{type:"image", data, mimeType}`) SKIPS that composition and is sent to the adapter verbatim \u2014 the adapter negotiates its own multimodal support. ' + help("prompt")
2255
2286
  ),
2287
+ label: z.string().optional().describe("Free-text label shown in `agent_sessions_list` and the UI."),
2256
2288
  mode: z.string().optional().describe(
2257
- "Manifest-declared mode id (AIP-45 `modes`) applied at spawn time, BEFORE the child process starts \u2014 e.g. claude-code's 'plan' (read-only: reasons and proposes but does not edit or run commands), 'accept-edits', 'bypass-permissions'; codex's 'read-only' / 'full-access'; mastracode/opencode's 'plan' / 'build'. Adapters that don't declare `modes` (e.g. hermes) reject ANY value here \u2014 only pass this for adapters known to support it. Omit for the adapter's normal interactive mode."
2289
+ `Manifest-declared mode id (AIP-45 \`modes\`), applied before the child starts (e.g. claude-code's 'plan'/'accept-edits'/'bypass-permissions'). Adapters with no declared \`modes\` REJECT any value here. ${help("mode")}`
2258
2290
  ),
2259
2291
  origin: z.string().min(1).optional().describe(
2260
- "Source label for this spawn \u2014 the calling channel/harness (codex, cowork, vscode, cron, \u2026). Descriptor-only: groups the session under a source node in the tree. In-repo callers set it; the mcp-bridge can auto-stamp it from the host clientInfo."
2292
+ "Source label for this spawn (codex, cowork, vscode, cron, \u2026) \u2014 descriptor-only, groups the session under a source node in the tree."
2261
2293
  ),
2262
2294
  parentSessionId: z.string().min(1).optional().describe(
2263
- "Parent-lineage hint: attribute this spawn to a logical parent session so it nests under that node in the sessions tree instead of appearing as a depth-0 root. Pass the `id` of the session doing the spawning (e.g. an agent-to-agent `agent_start`). The child's `depth` is derived from the parent (parent depth + 1); you don't set it. Ignored when this call arrives through the scoped orchestrator gateway \u2014 that path derives the parent from its own token, which always wins over this hint."
2295
+ `Parent-lineage hint: nests this spawn under that session in the tree instead of a depth-0 root. Ignored through the scoped orchestrator gateway (its own token wins). ${help("parentSessionId")}`
2264
2296
  ),
2265
2297
  attach: jsonTolerant(attachFieldSchema).optional().describe(
2266
- 'Parent-attach control, mirroring `worktree`. By DEFAULT (omitted) a spawn attaches under the session that made it \u2014 the daemon derives that parent from the trusted caller id, so a supervisor\'s executors nest instead of appearing as depth-0 roots, no `parentSessionId` needed. Pass `false` to launch an INDEPENDENT root (no parent) \u2014 the deliberate detached spawn. `true` forces attach even under an `on-request` daemon policy; `{ parent: "<id>" }` pins an explicit parent. Ignored when this call arrives through the scoped orchestrator gateway (the scope token wins). Descriptor-only lineage: never relaxes a depth-gated worktree/role guard.'
2298
+ `Parent-attach override, mirrors \`worktree\`. Default attaches under the calling session; \`false\` = independent root; \`true\` forces attach; \`{ parent: "<id>" }\` pins an explicit parent. ${help("attach")}`
2267
2299
  ),
2268
2300
  boardId: z.string().min(1).optional().describe(
2269
- "Task-board pin for the spawned child, stamped onto its descriptor as `meta.boardId`. The Task ledger resolves the child's default board from this BEFORE walking `parentSessionId` lineage \u2014 so a client spawning several depth-0 root sessions (no shared lineage) can join them all onto ONE shared board. An explicit `boardId` passed on a task verb still wins over this pin. Omit for the lineage-derived `tree:<root>` default."
2301
+ `Task-board pin for the child (\`meta.boardId\`), resolved before parent-lineage. An explicit board on a task verb still wins. ${help("boardId")}`
2270
2302
  ),
2271
2303
  idempotencyKey: z.string().min(1).optional().describe(
2272
- "Caller-declared 'this is the same logical spawn' token \u2014 a PROMISE, not a guess. A retried agent_start call (e.g. after a slow/lost response) that repeats the same `idempotencyKey` for the same `adapter`+`cwd` within ~10min of a successful spawn gets that SAME session's descriptor back instead of forking a second process \u2014 the response carries `deduped: true` and `dedupeSource: \"explicit\"` so you can tell. Always wins over the daemon's own derived key (see `dedupe` below) when both would apply. Omitting this does NOT mean 'spawn unconditionally' \u2014 see `dedupe`."
2304
+ `Caller-declared dedup token: a retried call with the SAME key for the same \`adapter\`+\`cwd\` within ~10min of a successful spawn returns that SAME session (\`deduped: true\`) instead of forking a new one. Always wins over the implicit key \`dedupe\` derives. ${help("idempotencyKey")}`
2273
2305
  ),
2274
2306
  dedupe: mcpBool.optional().describe(
2275
- 'Per-call override for the daemon\'s `spawn.dedupe` policy \u2014 what happens when NO `idempotencyKey` is supplied. By DEFAULT (`spawn.dedupe: "always"`) a spawn that carries a `label` gets an IMPLICIT key derived from that label plus a hash of `prompt`, and dedupes against it exactly like an explicit key \u2014 set `dedupeSource: "implicit"` on the response (alongside `deduped: true`) so you can tell it wasn\'t your own promise that matched. A spawn with no `label` is never touched by this \u2014 deliberate parallel fan-out into one cwd (a real, exercised pattern here) needs no label and stays exactly as many sessions as you asked for. Pass `dedupe: false` to opt this ONE spawn out of implicit derivation regardless of policy \u2014 the escape hatch, mirroring `attach: false` / `worktree: false`. `dedupe: true` forces derivation even under an `"on-request"` daemon policy, mirroring `attach: true`. Unrelated and NOT covered by this flag: a `worktree` spawn that lands in a worktree another LIVE session already occupies under the same `label` is always refused (`dedupeSource: "worktree-cwd"`) \u2014 a shared worktree, unlike a shared plain cwd, is never a legitimate fan-out.'
2307
+ `Override the implicit dedup-by-\`label\` default (on when no \`idempotencyKey\` is given): \`false\` opts this spawn out, \`true\` forces it even under an "on-request" daemon policy. ${help("dedupe")}`
2276
2308
  ),
2277
2309
  permissionHold: mcpBool.optional().describe(
2278
- "Start the session in permission-hold mode: every ACP permission request the agent raises (Write, Bash, \u2026) is SURFACED and HELD in the cross-session inbox (`permissions_list` / `permissions_respond`) instead of auto-answered, and the agent blocks until a human/orchestrator approves or denies it. Default false = today's auto-answer behaviour. ACP adapters only; others ignore it."
2310
+ "Hold every ACP permission request in the cross-session inbox (`permissions_list`/`permissions_respond`) instead of auto-answering. ACP adapters only; default false."
2279
2311
  ),
2280
2312
  notifyParentOnCrash: mcpBool.optional().describe(
2281
- "Opt this spawn into a direct in-band crash notice to its parent: if THIS session later crashes (adapter process gone between turns), the parent (`parentSessionId`, direct or inherited) is told via `[child-crashed] <label/id>: <reason> \u2014 <lastError>` \u2014 enqueued immediately if the parent is alive and idle, or queued for its next turn (never interrupting an in-flight one) if it's busy. Default false. The free external webhook (`notifyUrl`) already fires on any crash regardless of this flag \u2014 this only adds the direct signal into the parent's OWN session, for a delegating supervisor that wants to react to a child's death without polling."
2313
+ `Notify this session's parent directly (in-band) if it later crashes. Independent of the global \`notifyUrl\` webhook, which fires either way. ${help("notifyParentOnCrash")}`
2314
+ ),
2315
+ sentinel: mcpBool.optional().describe(
2316
+ `Set false to opt this spawn OUT of sentinel auto-link: a PR this session opens will never get an automatic AIP-60 sentinel watching it, regardless of the daemon's \`sentinel.autoWatchPrs\` config. ${help("sentinel")}`
2282
2317
  ),
2283
2318
  allowSharedCwd: mcpBool.optional().describe(
2284
- "Acknowledge that this nested spawn WILL run in place inside its parent's working tree even when that tree has uncommitted changes and isn't an isolated worktree \u2014 silencing the shared-dirty-cwd warning agent_start otherwise returns in `warnings`. Only relevant for a delegated (depth > 0) spawn with no `worktree` and no `sandbox`; ignored otherwise. Default false = warn."
2319
+ "Acknowledge running a nested spawn in the parent's dirty working tree, silencing the shared-dirty-cwd warning. Only relevant with no `worktree`/`sandbox`; default false = warn."
2285
2320
  ),
2286
2321
  keepAlive: mcpBool.optional().describe(
2287
- "Exempt this session from the idle-reaper: it is never auto-retired for sitting idle, no matter how long. For a supervisor that legitimately parks \u2014 waiting on a child, waiting on a scheduled wake \u2014 idle looks identical to finished, and the reaper would otherwise pull it out from under you. Default false = today's behaviour. Toggle later with `session_set_keepalive`."
2322
+ "Exempt this session from the idle-reaper \u2014 never auto-retired for sitting idle. Default false. Toggle later with `session_set_keepalive`."
2288
2323
  ),
2289
2324
  options: jsonTolerant(
2290
2325
  z.record(z.string(), z.union([z.boolean(), z.number(), z.string()]))
2291
2326
  ).optional().describe(
2292
- "Manifest-declared option id \u2192 value map (AIP-45 `options`), applied at spawn time alongside `mode` \u2014 e.g. hermes' `skills` (string, prepended before the subcommand) or a boolean flag appended when true. Each value is validated against the option's declared `type`/`enum`/`min`/`max`; unknown ids reject. Adapters that don't declare a given option id reject it."
2327
+ "Manifest-declared option id \u2192 value map (AIP-45 `options`), validated against each option's declared type/enum/min/max. Unknown ids reject."
2293
2328
  ),
2294
2329
  skills: jsonTolerant(z.array(z.string())).optional().describe(
2295
- "Normalized, adapter-agnostic skill ids for this session (e.g. ['agentproto']). Merges with `~/.agentproto/config.json`'s `defaults.skills` / `defaults.adapters.<slug>.skills` (global < per-adapter < this field, which REPLACES rather than unions the config defaults when provided \u2014 a deliberate exact set). Folded into `options.skills` using the resolved adapter's declared shape (e.g. hermes' comma-joined `--skills a,b`); adapters with no declared `skills` option (e.g. claude-code, which auto-discovers from `~/.claude/skills`) ignore this \u2014 no-op."
2330
+ `Normalized skill ids for this session (e.g. ['agentproto']). REPLACES (not unions) config defaults when set. Adapters with no \`skills\` option (e.g. claude-code) ignore it. ${help("skills")}`
2331
+ ),
2332
+ bundles: jsonTolerant(z.array(z.string())).optional().describe(
2333
+ `Capability bundle ids (\`bundle_list\`) to attach \u2014 each expands to its imported MCPs (mounted as native MCP servers, never via the mcp_imported_* indirection) + skills, and optionally the daemon's own /mcp. REPLACES (not unions) config defaults when set. ${help("bundles")}`
2334
+ ),
2335
+ daemonMount: mcpBool.optional().describe(
2336
+ `Explicitly mount the daemon's own scoped /mcp gateway for this spawn \u2014 the only way an adapter outside the default self-mount set (opencode, codex, gemini, \u2026) gets it. Default false. ${help("daemonMount")}`
2296
2337
  ),
2297
2338
  model: z.string().optional().describe(
2298
- "Model identifier to pass to the adapter (e.g. 'claude-opus-4-8'). For ACP adapters (claude-code) applied via session/set_config_option after newSession \u2014 NOT via a CLI flag. Others may ignore it."
2339
+ "Model identifier to pass to the adapter. For claude-code, applied via session/set_config_option \u2014 NOT a CLI flag."
2299
2340
  ),
2300
2341
  effort: z.string().optional().describe(
2301
- "Reasoning effort level (e.g. 'low', 'medium', 'high', 'xhigh', 'max', 'ultracode'). IMPORTANT: effort is calibrated per model \u2014 the same label maps to different compute budgets across models, and defaults differ by model (Sonnet 4.6 / Opus 4.8 default 'high'; Opus 4.7 default 'xhigh'). 'max' and 'ultracode' are session-only. Omit to keep the model's own default."
2342
+ "Reasoning effort label ('low'/'medium'/'high'/'xhigh'/'max'/'ultracode'). Calibrated PER MODEL \u2014 the same label means different compute budgets across models. Omit to keep the model's own default."
2302
2343
  ),
2303
2344
  route: jsonTolerant(
2304
2345
  z.object({ gateway: z.string().min(1), baseUrl: z.string().url().optional() })
2305
- ).optional().describe("Billing route/gateway. This is independent of model and named access profile."),
2346
+ ).optional().describe("Billing route/gateway. Independent of `model` and `access`."),
2306
2347
  access: jsonTolerant(z.object({ profileRef: z.string().min(1).optional() })).optional().describe("Named auth profile to bill at initial spawn; resolved from the local keychain."),
2307
2348
  posture: z.string().optional().describe("Canonical agent posture (plan, bypass, accept-edits, read-only) or native harness mode."),
2308
2349
  contextProfile: z.string().optional().describe("Context intake profile (for example full or lean)."),
@@ -2314,7 +2355,7 @@ var init_agent_start_schema = __esm({
2314
2355
  apiKey: z.string().optional()
2315
2356
  })
2316
2357
  ).optional().describe(
2317
- "Deterministic billing-auth mode + EXPLICIT credential for adapters that declare it (today: claude-code). EXPLICIT credential selection, not scrub-by-absence: `mode` picks 'subscription' (default) or 'api-key'; `token`/`apiKey` (matching the resolved mode) is the secret VALUE, merged against `~/.agentproto/config.json`'s `defaults.adapters.claude-code.auth` (this field's `mode` wins; the credential for the resolved mode wins over the matching config field). For claude-code, 'subscription' SETS CLAUDE_CODE_OAUTH_TOKEN to `token` (a bearer token minted via `claude setup-token` \u2014 bills the Max/Pro subscription, not API credits) and DELETES ANTHROPIC_API_KEY + the cloud-provider redirect toggles + ANTHROPIC_BASE_URL. 'api-key' SETS ANTHROPIC_API_KEY to `apiKey` and DELETES ANTHROPIC_AUTH_TOKEN \u2014 the deliberate 'bill the API' choice. FAILS FAST (refuses the spawn, no fallback) when the resolved mode has no credential configured anywhere. The secret is never logged or echoed back \u2014 only a fingerprint appears on the session descriptor / `agent_sessions_list`. Adapters that don't declare this vocabulary ignore this field entirely. `source: \"claude-code-oauth\"` (subscription mode, opt-in) instead reads the bearer FRESH on every spawn from the local Claude Code login (Keychain / ~/.claude/.credentials.json) \u2014 effectively self-refreshing; an explicit `token` still wins over it."
2358
+ `Explicit billing-auth mode + credential for adapters that declare it (today: claude-code) \u2014 'subscription' (default) bills the Max/Pro plan, 'api-key' bills API credits. FAILS FAST with no fallback if the resolved mode has no credential configured anywhere. ${help("auth")}`
2318
2359
  ),
2319
2360
  mcpServers: jsonTolerant(
2320
2361
  z.array(
@@ -2322,42 +2363,34 @@ var init_agent_start_schema = __esm({
2322
2363
  name: z.string(),
2323
2364
  transport: z.enum(["stdio", "http", "sse"]),
2324
2365
  ref: z.string().optional(),
2325
- headers: z.record(z.string(), z.string()).optional().describe(
2326
- "Static HTTP headers sent with every request to an `http` or `sse` MCP server (e.g. a fixed auth token). Ignored for `stdio` transports."
2327
- ),
2366
+ headers: z.record(z.string(), z.string()).optional().describe("Static HTTP headers for `http`/`sse` transports. Ignored for `stdio`."),
2328
2367
  credentialRef: z.string().optional().describe(
2329
- "Brokered credential path resolved at spawn time into additional `headers` (typically `Authorization`). The actual secret never lives in env or config; brokered headers win on collision with `headers`."
2368
+ "Brokered credential resolved into `headers` at spawn time (wins on collision with `headers`); the secret never lives in env/config."
2330
2369
  ),
2331
- args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command. Ignored for `http`/`sse`."),
2332
- env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server. Ignored for `http`/`sse`.")
2370
+ args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command."),
2371
+ env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server.")
2333
2372
  })
2334
2373
  )
2335
2374
  ).optional().describe(
2336
- "MCP servers to mount into the spawned agent's session at spawn time. Forwarded verbatim to `session/new.mcpServers` on the ACP arm \u2014 gives the child agent a host-chosen scoped toolset (e.g. the daemon's own orchestration gateway so it can spawn + supervise sub-agents). Adapters that don't model MCP mounting ignore it."
2375
+ "MCP servers to mount into the spawned agent's session at spawn time. Adapters that don't model MCP mounting ignore it."
2337
2376
  ),
2338
2377
  orchestrator: jsonTolerant(
2339
2378
  z.union([
2340
2379
  z.boolean(),
2341
2380
  z.object({
2342
- tools: z.array(z.string()).optional().describe(
2343
- "Explicit allowlist \u2014 narrows the orchestration toolset to \u2286 the default subset. Names outside the default are dropped (a child can never widen its own scope). Omit for the full default subset."
2344
- ),
2345
- maxDepth: z.number().int().min(1).max(8).optional().describe(
2346
- "Max recursion depth reachable through this child (default 3, hard ceiling 8). A spawn that would exceed it is rejected. For a recursive spawn it can only LOWER the inherited cap, never raise it."
2347
- ),
2348
- maxChildren: z.number().int().min(1).optional().describe(
2349
- "Max concurrently-alive sub-agents this child may spawn (default 8). For a recursive spawn it can only lower the inherited quota."
2350
- )
2381
+ tools: z.array(z.string()).optional().describe("Explicit allowlist \u2286 the default subset \u2014 a child can never widen its own scope."),
2382
+ maxDepth: z.number().int().min(1).max(8).optional().describe("Max recursion depth reachable through this child (default 3, ceiling 8, lower-only on recursion)."),
2383
+ maxChildren: z.number().int().min(1).optional().describe("Max concurrently-alive sub-agents this child may spawn (default 8, lower-only on recursion).")
2351
2384
  })
2352
2385
  ])
2353
2386
  ).optional().describe(
2354
- "Make this child a SCOPED orchestrator \u2014 auto-mount the daemon's own orchestration MCP tools (start/prompt/wait/poll/output + subtree list/kill) so it can spawn and supervise its OWN sub-agents. `true` = the default curated subset; `{ tools: [...] }` narrows it. The daemon mints a per-child scope-token, injects the scoped sub-gateway URL into the child's session (alongside any `mcpServers` you pass), and revokes the token when the session exits. Shell/fs/remote/import/terminal tools are NEVER exposed this way."
2387
+ `Make this child a SCOPED orchestrator \u2014 auto-mounts the daemon's own spawn/supervise tools, scoped to its own sub-agents. \`true\` = default subset; \`{ tools }\` narrows it. Shell/fs/remote tools are NEVER exposed. ${help("orchestrator")}`
2355
2388
  ),
2356
2389
  notifyUrl: z.string().url().optional().describe(
2357
2390
  "Optional per-session webhook URL. POSTed (fire-and-forget) on this session's turn-end / awaiting-input / exited events, in addition to any global notify URL."
2358
2391
  ),
2359
2392
  wait: mcpBool.optional().describe(
2360
- "Block until the spawned session's first turn completes and include the cleaned output in the response. Default false = return the descriptor immediately. Note this blocks for the child's ENTIRE first turn (~40-90s+), not just the spawn. Batching several `wait: true` calls in one turn does NOT run them in parallel: harnesses that execute tool calls sequentially serialize them, each wait blocking its slot until its child's turn ends. For parallel fan-out spawn with `wait: false` (all spawns return in seconds), then wait on completion separately via `agentproto sessions wait <id> --until turn-end` (detached/background) or a completion policy via `policy_attach`."
2393
+ `Block until the spawned session's first turn completes (~40-90s+) and include its output. Default false = return immediately. Batched \`wait:true\` calls do NOT run in parallel \u2014 see \`tool_help {name:"agent_start", topic:"wait"}\` for the fan-out pattern.`
2361
2394
  ),
2362
2395
  maxCostUsd: mcpPositiveNumber.optional().describe(
2363
2396
  "Hard ceiling on cumulative session cost (USD). The session is stopped at a turn-end once exceeded."
@@ -2369,45 +2402,39 @@ var init_agent_start_schema = __esm({
2369
2402
  scope: z.enum(["session", "profile"]).describe("Spend surface: this session, or every session on its auth profile.")
2370
2403
  })
2371
2404
  ).optional().describe(
2372
- "Windowed cost-budget cap (DISTINCT from `maxCostUsd`). Auto-attaches a governance policy that trips `policy:failed` when the rolling windowed spend for `scope` crosses `maxCostUsd`. Never kills the session \u2014 it trips a policy for a supervisor to act on."
2405
+ "Windowed cost-budget cap (DISTINCT from `maxCostUsd`) \u2014 trips `policy:failed` on overage, never kills the session."
2373
2406
  ),
2374
2407
  restartPolicy: jsonTolerant(
2375
2408
  z.object({
2376
- on: z.array(z.enum(["crashed", "error"])).min(1).describe(
2377
- 'Which automatic death reasons trigger a restart: "crashed" (the crash-detect sweep found the adapter process gone) and/or "error" (an unexpected turn error with no other intentional reason). Never restarts a clean exit, an operator kill, an idle-reap, a daemon-restart death, or a cost-budget kill, regardless of this list.'
2378
- ),
2379
- maxRetries: z.number().int().positive().describe(
2380
- "Rolling-window crash-loop cap: give up (leave the session dead, no further auto-restart) once this many restarts have fired within `windowMs`."
2381
- ),
2409
+ on: z.array(z.enum(["crashed", "error"])).min(1).describe("Which automatic death reasons trigger a restart. Never a clean exit, kill, idle-reap, or cost-budget kill."),
2410
+ maxRetries: z.number().int().positive().describe("Rolling-window crash-loop cap: give up after this many restarts within `windowMs`."),
2382
2411
  windowMs: z.number().int().positive().describe("Rolling window (ms) `maxRetries` counts restarts over."),
2383
2412
  baseDelayMs: z.number().int().positive().describe("First restart's backoff delay (ms), before `factor` compounds it."),
2384
2413
  factor: z.number().positive().describe("Exponential backoff multiplier applied per consecutive restart."),
2385
2414
  maxDelayMs: z.number().int().positive().describe("Backoff ceiling (ms) \u2014 the computed delay never grows past this."),
2386
- resume: z.boolean().optional().describe(
2387
- "Reserved for a future explicit resume-vs-fresh-spawn toggle; this PR always revives in place (same session id, same conversation)."
2388
- )
2415
+ resume: z.boolean().optional().describe("Reserved for a future resume-vs-fresh-spawn toggle; this always revives in place today.")
2389
2416
  })
2390
2417
  ).optional().describe(
2391
- "Opt-in auto-restart policy (restart-scheduler PR-2). When set, an unexpected death (`crashed` and/or `error`, per `on`) is automatically revived IN PLACE (reusing the resume machinery \u2014 same session id, same conversation) after an exponential backoff, up to a rolling-window crash-loop cap. Omit for today's behaviour: a dead session stays dead until a human/orchestrator prompts or restarts it."
2418
+ `Opt-in auto-restart policy: an unexpected death (\`crashed\`/\`error\`, per \`on\`) is revived IN PLACE after exponential backoff, up to a rolling-window crash-loop cap. Omit for today's stay-dead behaviour. ${help("restartPolicy")}`
2392
2419
  ),
2393
2420
  contextContinuity: jsonTolerant(contextContinuityInputSchema).optional().describe(
2394
2421
  "Context-continuity policy for this session \u2014 controls warning, opportunistic compaction, fresh-continuation, and hard-stop thresholds. Resolved from global \u2192 per-adapter \u2192 explicit override."
2395
2422
  ),
2396
2423
  role: z.string().optional().describe(
2397
- "Spawn-time role gating whether this child may itself delegate (spawn/drive further children) and, if it can, which roles IT may in turn spawn. Built-ins: 'executor' = leaf, cannot delegate \u2014 `orchestrator` is ignored and `agent_start`/`agent_prompt` are stripped from its default toolset, regardless of `promptAppend`. 'supervisor' = may delegate (today's default behaviour). Custom roles installed as role packs (see `role_list`) resolve the same way. A spawn made THROUGH an orchestrator is additionally gated by the privilege lattice: the calling role may only spawn a role allowlisted in its `spawnableRoles`, or \u2014 open mode, the default \u2014 at or below its own `level` (never something MORE privileged than itself). Omit `role` to derive from spawn depth (root spawns default to supervisor; spawns made through an orchestrator default to executor \u2014 see `defaultRoleDepthCutoff` in config.json's `defaults` block)."
2424
+ `Spawn-time delegation gate. 'executor' = leaf, cannot delegate (\`agent_start\`/\`agent_prompt\` stripped). 'supervisor' = may delegate (default). Custom role packs resolve the same way. Omit to derive from spawn depth. ${help("role")}`
2398
2425
  ),
2399
2426
  promptAppend: z.string().optional().describe(
2400
- "One-off runtime text layered ON TOP of the resolved role's disposition and prepended to `prompt` \u2014 it specializes the disposition, it cannot replace it, and it cannot re-open the tool gate (an executor asked to 'delegate anyway' via this field still has no delegation tools)."
2427
+ "One-off runtime text layered onto the resolved role's disposition and prepended to `prompt` \u2014 specializes it, cannot reopen a denied tool gate."
2401
2428
  ),
2402
2429
  deferredTools: z.boolean().optional().describe(
2403
- "Override deferred/lazy MCP tool loading for this spawn's daemon self-mount: `true` hides every tool outside a small always-on set from `tools/list` (still fully callable \u2014 use `tool_search` to look up a hidden tool's schema by keyword before calling it), `false` keeps the full eager surface. Omit to use the resolved role's own default ('executor' defaults ON, since it can't delegate anyway and rarely needs the full ~190-tool surface); omit AND spawn a role with no opinion to fall through to the daemon's own boot-time `defaults.mcp.deferredTools` config."
2430
+ `Override lazy MCP tool loading for this spawn: \`true\` hides most tools from \`tools/list\` (still callable via \`tool_search\`), \`false\` keeps the full eager surface. Omit to use the role/daemon default. ${help("deferredTools")}`
2404
2431
  ),
2405
2432
  browser: z.preprocess(
2406
2433
  // `true` is sugar for the only mode; stringified booleans tolerated.
2407
2434
  (v) => v === true || v === "true" ? "headless" : v === "false" ? false : v,
2408
2435
  z.union([z.literal("headless"), z.literal(false)])
2409
2436
  ).optional().describe(
2410
- '`"headless"` gives the spawned agent its own isolated headless Chrome (1440x900, temporary profile) as a per-session `browser` MCP server (chrome-devtools-mcp: navigate_page, take_screenshot, evaluate_script, click, list_console_messages, \u2026), torn down with the session (`true` = `"headless"`). Works for any adapter that mounts stdio MCP servers; runs inside the session\'s `commandSandbox` (`strict` \u21D2 file:// only). `false` = none. Omit to use the role / preset / `defaults.spawn.browser` default (off). Not supported with `sandbox`.'
2437
+ `\`"headless"\` gives the spawned agent its own isolated headless Chrome as a per-session \`browser\` MCP server, torn down with the session. \`false\` = none (default). Not supported with \`sandbox\`. ${help("browser")}`
2411
2438
  ),
2412
2439
  trace: z.boolean().optional().describe(
2413
2440
  "Emit Langfuse observability traces for this session (prompt/completion + tool spans + tokens/cost). Off by default; requires langfuse eval-reporter creds configured."
@@ -2416,22 +2443,22 @@ var init_agent_start_schema = __esm({
2416
2443
  z.union([
2417
2444
  z.string().min(1).describe("Sandbox provider slug from `list_sandbox_providers` (e.g. 'local', 'e2b')."),
2418
2445
  sandboxSpecWithReuseSchema.describe(
2419
- "Inline AIP-36 SandboxDefinition \u2014 boots this exact spec instead of a catalog slug. Set `reuse` (sandboxId, ledger label, or unique sandboxId prefix \u2014 see `agentproto sandbox list`) to reconnect to an existing sandbox instead of booting fresh."
2446
+ "Inline AIP-36 SandboxDefinition \u2014 boots this exact spec instead of a catalog slug. Set `reuse` to reconnect to an existing sandbox instead of booting fresh."
2420
2447
  )
2421
2448
  ])
2422
2449
  ).optional().describe(
2423
- "Run this session inside a sandbox instead of on the host \u2014 pass a provider slug (see `list_sandbox_providers`) or an inline AIP-36 SandboxDefinition object. The daemon boots the sandbox, spawns `adapter` on the box's OWN agentproto daemon, and proxies the conversation back onto this session \u2014 `agent_prompt`/`agent_output`/`agent_kill` behave exactly as they do for a local spawn, and the transcript stays readable here even after the box is torn down. Omit to run locally (default). Pass an inline spec with `reuse: \"<sandboxId>\"` (from a prior session's `sandboxId`) to reconnect to an existing box instead \u2014 by default such a box is PAUSED (not killed) on session close so it stays reusable; set `lifecycle.destroy_on` to always kill it. DO NOT CONFUSE with `commandSandbox` below \u2014 this field boots a WHOLE SEPARATE machine/box; `commandSandbox` confines THIS host's own spawn argv in place. The two are independent and combine (or not) freely; `commandSandbox` is ignored for a `sandbox` spawn (the box's own daemon would need to apply it)."
2450
+ `Run this session inside a sandbox instead of on the host (provider slug or inline AIP-36 spec) \u2014 the daemon boots the box, spawns \`adapter\` on its own agentproto daemon, and proxies the conversation back here. DISTINCT from \`commandSandbox\` (whole separate machine vs. confining this host's own process). Omit to run locally. ${help("sandbox")}`
2424
2451
  ),
2425
2452
  appServe: jsonTolerant(
2426
2453
  z.object({
2427
2454
  dir: z.string().min(1).describe("Absolute path to the app's directory INSIDE the sandbox box (e.g. '/home/user/apps/<slug>')."),
2428
- port: z.number().int().min(1).max(65535).optional().describe("Port the UI binds inside the box (default 3210). The port is exposed by the sandbox provider and its public URL returned.")
2455
+ port: z.number().int().min(1).max(65535).optional().describe("Port the UI binds inside the box (default 3210). Exposed by the sandbox provider as a public URL.")
2429
2456
  }).strict()
2430
2457
  ).optional().describe(
2431
- "WP3 \u2014 serve an agentproto app's UI from INSIDE the sandbox box and return its public URL. Requires `sandbox` (rejected otherwise). The box daemon installs the app (`app_install` on the in-box `dir`), launches `agentproto app serve --host 0.0.0.0 --port <port>` detached through the box's `command_execute`, and the spawn result + descriptor carry `appServe: { appId, dir, port, url, ready }` \u2014 `url` is the provider-resolved public URL for the served UI (the port is also added to the spec's `extraPorts` and echoed in `sandboxPorts`)."
2458
+ `Serve an agentproto app's UI from INSIDE the sandbox box and return its public URL. Requires \`sandbox\` (rejected otherwise). ${help("appServe")}`
2432
2459
  ),
2433
2460
  commandSandbox: commandSandboxSchema.optional().describe(
2434
- 'OS-level process confinement (macOS Seatbelt / Linux bubblewrap) for the adapter\'s OWN spawned process on THIS host \u2014 NOT the `sandbox` field above, which boots an entirely separate remote box. This wraps the exact argv `adapter` spawns as (e.g. `claude`, `npx @agentclientprotocol/claude-agent-acp`) so its process tree is denied filesystem access outside the session\'s `cwd` \u2014 confinement an ACP permission seam can never provide, since it only sees tool calls the adapter chooses to report, not what an in-process Bash actually touches. `"off"` (default when omitted AND no `.agentproto/command-sandbox.json` sets an `adapterSpawn.mode`) = unconfined, unchanged behaviour. `"workspace"` = deny reads/writes to $HOME outside the workspace (protects ~/.ssh, ~/.aws, credentials, \u2026); network stays allowed. `"strict"` = `"workspace"` + deny all network. A workspace can set this same axis persistently via the `adapterSpawn` key of `.agentproto/command-sandbox.json` (a DISTINCT key from the top-level `mode` that key file also carries for `command_execute` \u2014 the two are never shared; misconfiguring the whole-session adapter jail is a bigger blast radius than misconfiguring one shell command) \u2014 this param, when set, overrides that file. Ignored for a `sandbox` spawn. `"workspace"`/`"strict"` with no backend installed for this platform (macOS needs `sandbox-exec`, Linux needs `bwrap`) FAILS the spawn rather than silently running unconfined.'
2461
+ `OS-level confinement (macOS Seatbelt / Linux bubblewrap) of the adapter's OWN process on THIS host \u2014 NOT \`sandbox\` above, which boots a separate machine. \`"off"\` (default) = unconfined; \`"workspace"\` = deny reads/writes outside cwd; \`"strict"\` = + deny network. Fails the spawn if the OS backend isn't installed. ${help("commandSandbox")}`
2435
2462
  ),
2436
2463
  worktree: jsonTolerant(
2437
2464
  z.union([
@@ -2440,17 +2467,15 @@ var init_agent_start_schema = __esm({
2440
2467
  slug: z.string().regex(
2441
2468
  /^[a-z0-9][a-z0-9-]*$/,
2442
2469
  "slug must be lowercase kebab-case (letters, digits, hyphens)"
2443
- ).optional().describe(
2444
- "Pin the worktree's slug (names its branch `wt/<slug>` and its directory). Omit to auto-mint a collision-free one from the label."
2445
- ),
2470
+ ).optional().describe("Pin the worktree's slug (branch `wt/<slug>`). Omit to auto-mint one from the label."),
2446
2471
  base: z.string().min(1).optional().describe("Git ref the worktree branch is cut from. Default 'origin/main'."),
2447
2472
  async: z.boolean().optional().describe(
2448
- 'Return a real, registered session as soon as it\'s minted (status "starting") instead of blocking `agent_start`\'s response on `git worktree add` + the repo\'s setup hooks, which can run minutes. Provisioning + the driver spawn continue in the background; poll the session\'s `status` (flips to "running" on success, "error" with a readable `lastError` on failure \u2014 it never sits in "starting" forever). Any `prompt` is held and dispatched only once the tree and the driver session both exist. Incompatible with `wait` (there is no first-turn output to block on yet) \u2014 combining the two is rejected. Defaults to true for any spawn that provisions a worktree, UNLESS this call also sets `wait` (which falls back to the old synchronous path instead of conflicting). Pass `false` explicitly to force the old blocking ok/fail contract even without `wait`.'
2473
+ `Return a registered session immediately (status "starting") instead of blocking on provisioning; incompatible with \`wait\`. Defaults true whenever a worktree is provisioned. ${help("worktree")}`
2449
2474
  )
2450
2475
  }).strict()
2451
2476
  ])
2452
2477
  ).optional().describe(
2453
- "Isolate this session in its OWN git worktree instead of spawning directly in `cwd` \u2014 so a parallel agent can't collide on the working tree. `true` provisions a worktree on a fresh branch `wt/<slug>` cut from origin/main (slug auto-minted from `label`); pass `{ slug, base, async }` to pin either, or opt into an early return (`async`, see that field's own description). The daemon boots the worktree (git worktree add + the repo's agentproto.json setup hooks) and spawns `adapter` THERE; the session's cwd, and every path it edits, live inside the worktree. Honoured only for a ROOT spawn (a spawn made THROUGH an orchestrator inherits its parent's tree \u2014 no second worktree; an EXPLICIT `worktree` on such a nested spawn is REJECTED, not silently ignored \u2014 use `sandbox` to isolate a child) and only when `cwd` is inside a git repo (nothing to isolate otherwise \u21D2 spawns plain, no error). The daemon's `worktrees.isolation` policy may force this ON for every root spawn (`always`) or OFF (`never`, which REJECTS an explicit `worktree`). Ignored for a `sandbox` spawn (the box already isolates). The worktree is NOT auto-removed on session close \u2014 it holds the agent's work; tear it down with `agentproto worktree rm|archive|gc`."
2478
+ `Isolate this session in its OWN git worktree instead of \`cwd\`. \`true\` provisions one on a fresh \`wt/<slug>\` branch; \`{ slug, base, async }\` pins details. ROOT spawns only (rejected on a nested spawn \u2014 use \`sandbox\` there); ignored when \`cwd\` isn't a git repo. NOT auto-removed on session close \u2014 tear down with \`agentproto worktree rm\`. ${help("worktree")}`
2454
2479
  )
2455
2480
  };
2456
2481
  agentStartInputSchema = z.object(agentStartInputShape);
@@ -2482,6 +2507,7 @@ var init_user_presets = __esm({
2482
2507
  contextProfile: z.string().min(1).optional(),
2483
2508
  cwd: z.string().min(1).optional(),
2484
2509
  skills: z.array(z.string().min(1)).optional(),
2510
+ bundles: z.array(z.string().min(1)).optional(),
2485
2511
  browser: z.union([z.literal("headless"), z.literal(false)]).optional()
2486
2512
  });
2487
2513
  z.object({
@@ -2494,8 +2520,20 @@ var init_catalog_provider_models = __esm({
2494
2520
  "src/catalog-provider-models.ts"() {
2495
2521
  }
2496
2522
  });
2523
+ var init_app_ui_build = __esm({
2524
+ "src/app-ui-build.ts"() {
2525
+ }
2526
+ });
2527
+
2528
+ // src/app-ui-placeholder.ts
2529
+ var init_app_ui_placeholder = __esm({
2530
+ "src/app-ui-placeholder.ts"() {
2531
+ }
2532
+ });
2497
2533
  var init_app_ui_apps = __esm({
2498
2534
  "src/app-ui-apps.ts"() {
2535
+ init_app_ui_build();
2536
+ init_app_ui_placeholder();
2499
2537
  }
2500
2538
  });
2501
2539
  var MESSAGE_KINDS, MESSAGE_URGENCIES;
@@ -2517,6 +2555,7 @@ var init_agent_tools = __esm({
2517
2555
  "src/agent-tools.ts"() {
2518
2556
  init_transcript_export();
2519
2557
  init_agent_start_schema();
2558
+ init_spawn_field_schemas();
2520
2559
  init_session_spawn();
2521
2560
  init_canonical_posture();
2522
2561
  init_user_presets();
@@ -2561,6 +2600,11 @@ var init_session_observer = __esm({
2561
2600
  "src/session-observer.ts"() {
2562
2601
  }
2563
2602
  });
2603
+ var init_session_artifacts = __esm({
2604
+ "src/session-artifacts.ts"() {
2605
+ init_transcript_writer();
2606
+ }
2607
+ });
2564
2608
  var DEFAULT_TITLER_MODEL;
2565
2609
  var init_session_titler = __esm({
2566
2610
  "src/session-titler.ts"() {
@@ -2587,10 +2631,6 @@ var init_terminal_transcript_writer = __esm({
2587
2631
  init_transcript_writer();
2588
2632
  }
2589
2633
  });
2590
- var init_usage = __esm({
2591
- "src/usage.ts"() {
2592
- }
2593
- });
2594
2634
  var init_context_window = __esm({
2595
2635
  "src/context-window.ts"() {
2596
2636
  }
@@ -2640,6 +2680,7 @@ var init_sessions = __esm({
2640
2680
  init_session_observer();
2641
2681
  init_tool_presenter();
2642
2682
  init_transcript_writer();
2683
+ init_session_artifacts();
2643
2684
  init_session_titler();
2644
2685
  init_resume_context_digest();
2645
2686
  init_conversation_index();
@@ -2723,7 +2764,9 @@ var init_config_schema = __esm({
2723
2764
  skills: z.array(z.string()).optional(),
2724
2765
  options: optionsMapSchema.optional(),
2725
2766
  auth: defaultsAdapterAuthConfigSchema.optional(),
2726
- contextContinuity: contextContinuityPolicySchema.optional()
2767
+ contextContinuity: contextContinuityPolicySchema.optional(),
2768
+ bundles: z.array(z.string()).optional(),
2769
+ daemonMount: z.boolean().optional()
2727
2770
  }).passthrough();
2728
2771
  mcpDefaultsConfigSchema = z.object({
2729
2772
  deferredTools: deferredToolsConfigSchema.optional()
@@ -2732,6 +2775,7 @@ var init_config_schema = __esm({
2732
2775
  skills: z.array(z.string()).optional(),
2733
2776
  options: optionsMapSchema.optional(),
2734
2777
  adapters: z.record(z.string(), defaultsAdapterConfigSchema).optional(),
2778
+ bundles: z.array(z.string()).optional(),
2735
2779
  contextContinuity: contextContinuityPolicySchema.optional(),
2736
2780
  defaultRoleDepthCutoff: z.number().optional(),
2737
2781
  maxGrantableDelegation: z.number().optional(),
@@ -2755,6 +2799,7 @@ var init_config_schema = __esm({
2755
2799
  label: z.string().optional(),
2756
2800
  authToken: z.string().optional(),
2757
2801
  resumeSessionsOnBoot: z.boolean().optional(),
2802
+ continueInterruptedOnBoot: z.boolean().optional(),
2758
2803
  idleReapAfterMs: z.number().optional(),
2759
2804
  crashDetectIntervalMs: z.number().optional(),
2760
2805
  restartSweepIntervalMs: z.number().optional(),
@@ -2772,7 +2817,8 @@ var init_config_schema = __esm({
2772
2817
  }).passthrough();
2773
2818
  featuresConfigSchema = z.object({
2774
2819
  pty: z.boolean().optional(),
2775
- llmEndpoint: z.boolean().optional()
2820
+ llmEndpoint: z.boolean().optional(),
2821
+ deviceInferenceShare: z.boolean().optional()
2776
2822
  }).passthrough();
2777
2823
  worktreesConfigSchema = z.object({
2778
2824
  root: z.string().optional(),
@@ -2929,6 +2975,16 @@ var init_config_schema = __esm({
2929
2975
  help: "Eagerly re-spawn eligible agent-cli sessions that died from a daemon restart.",
2930
2976
  default: false
2931
2977
  },
2978
+ {
2979
+ path: "daemon.continueInterruptedOnBoot",
2980
+ schema: bool,
2981
+ apply: "restart",
2982
+ writable: true,
2983
+ section: "daemon",
2984
+ label: "Continue interrupted sessions on boot",
2985
+ help: 'After the eager resume pass, send a one-shot "continue" prompt to sessions the last restart cut off mid-turn.',
2986
+ default: false
2987
+ },
2932
2988
  {
2933
2989
  path: "daemon.idleReapAfterMs",
2934
2990
  schema: num,
@@ -3102,6 +3158,15 @@ var init_config_schema = __esm({
3102
3158
  label: "Default options",
3103
3159
  help: "AIP-45 options auto-applied to every agent_start spawn."
3104
3160
  },
3161
+ {
3162
+ path: "defaults.bundles",
3163
+ schema: strArray,
3164
+ apply: "hot",
3165
+ writable: true,
3166
+ section: "defaults",
3167
+ label: "Default capability bundles",
3168
+ help: "Bundle ids (bundle_list) auto-attached to every agent_start spawn (unioned with per-adapter bundles)."
3169
+ },
3105
3170
  {
3106
3171
  path: "defaults.defaultRoleDepthCutoff",
3107
3172
  schema: num,
@@ -3328,6 +3393,25 @@ var init_config_schema = __esm({
3328
3393
  label: "Adapter default options",
3329
3394
  help: "AIP-45 options auto-applied to spawns of this adapter."
3330
3395
  },
3396
+ {
3397
+ path: "defaults.adapters.*.bundles",
3398
+ schema: strArray,
3399
+ apply: "hot",
3400
+ writable: true,
3401
+ section: "harnesses",
3402
+ label: "Adapter default capability bundles",
3403
+ help: "Bundle ids (bundle_list) auto-attached to spawns of this adapter (unioned with the global default)."
3404
+ },
3405
+ {
3406
+ path: "defaults.adapters.*.daemonMount",
3407
+ schema: z.boolean(),
3408
+ apply: "hot",
3409
+ writable: true,
3410
+ section: "harnesses",
3411
+ label: "Adapter daemon self-mount opt-in",
3412
+ help: "Explicitly mount the daemon's own scoped /mcp for spawns of this adapter, for harnesses outside the default self-mount allowlist (opencode, codex, gemini, \u2026).",
3413
+ default: false
3414
+ },
3331
3415
  {
3332
3416
  path: "defaults.adapters.*.contextContinuity",
3333
3417
  schema: contextContinuityPolicySchema,
@@ -3571,6 +3655,16 @@ var init_config_schema = __esm({
3571
3655
  label: "LLM Endpoint feature",
3572
3656
  help: "Enable the local LLM Endpoint proxy sidecar (route + MCP tools + child-process lifecycle).",
3573
3657
  default: false
3658
+ },
3659
+ {
3660
+ path: "features.deviceInferenceShare",
3661
+ schema: bool,
3662
+ apply: "restart",
3663
+ writable: true,
3664
+ section: "daemon",
3665
+ label: "Device inference sharing",
3666
+ help: "Expose this daemon's local inference endpoint(s) to a paired HOST-scoped controller. Toggle via `agentproto devices share-inference on|off`.",
3667
+ default: false
3574
3668
  }
3575
3669
  ];
3576
3670
  }