@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.
package/dist/config.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { join, resolve, dirname } 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';
@@ -179,6 +179,10 @@ var init_tool_call_record = __esm({
179
179
  "src/tool-call-record.ts"() {
180
180
  }
181
181
  });
182
+ var init_usage = __esm({
183
+ "src/usage.ts"() {
184
+ }
185
+ });
182
186
  function defaultTranscriptBaseDir() {
183
187
  return join(homedir(), ".agentproto", "sessions");
184
188
  }
@@ -192,6 +196,7 @@ var init_transcript_writer = __esm({
192
196
  "src/transcript-writer.ts"() {
193
197
  init_tool_call_record();
194
198
  init_pr_provenance();
199
+ init_usage();
195
200
  }
196
201
  });
197
202
 
@@ -588,11 +593,11 @@ async function discoverHermesSessions(cwd, since, expectedId) {
588
593
  }
589
594
  }
590
595
  async function defaultHermesRunner(adapterSessionId) {
591
- const { spawn: spawn4 } = await import('child_process');
596
+ const { spawn: spawn5 } = await import('child_process');
592
597
  return new Promise((resolve16, reject) => {
593
598
  const chunks = [];
594
599
  const errChunks = [];
595
- const proc = spawn4(
600
+ const proc = spawn5(
596
601
  "hermes",
597
602
  ["sessions", "export", "--session-id", adapterSessionId, "-"],
598
603
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -1939,6 +1944,29 @@ var init_worktree_identity = __esm({
1939
1944
  z.object({ worktreeId: z.string() });
1940
1945
  }
1941
1946
  });
1947
+ var init_mcp_imports = __esm({
1948
+ "src/mcp-imports.ts"() {
1949
+ }
1950
+ });
1951
+ var bundleIdSchema, bundleSchema;
1952
+ var init_bundles = __esm({
1953
+ "src/bundles.ts"() {
1954
+ init_mcp_imports();
1955
+ bundleIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "id must be lowercase kebab-case (letters, digits, hyphens)");
1956
+ bundleSchema = z.object({
1957
+ id: bundleIdSchema,
1958
+ label: z.string().min(1),
1959
+ description: z.string().min(1).optional(),
1960
+ mcpImports: z.array(z.string().min(1)),
1961
+ includeDaemon: z.boolean().optional(),
1962
+ skills: z.array(z.string().min(1))
1963
+ });
1964
+ z.object({
1965
+ version: z.literal(1),
1966
+ bundles: z.array(bundleSchema)
1967
+ });
1968
+ }
1969
+ });
1942
1970
  var init_catalog_models = __esm({
1943
1971
  "src/catalog-models.ts"() {
1944
1972
  init_spawn_defaults();
@@ -2142,10 +2170,13 @@ var init_sandbox_adapters = __esm({
2142
2170
  var init_session_spawn = __esm({
2143
2171
  "src/session-spawn.ts"() {
2144
2172
  init_sessions();
2173
+ init_transcript_writer();
2145
2174
  init_workspaces_config();
2146
2175
  init_config();
2147
2176
  init_worktree_identity();
2148
2177
  init_spawn_defaults();
2178
+ init_bundles();
2179
+ init_mcp_imports();
2149
2180
  init_launch_config();
2150
2181
  init_claude_code_oauth_source();
2151
2182
  init_providers_store();
@@ -2199,7 +2230,7 @@ var init_sandbox_spec_schema = __esm({
2199
2230
  }).strict();
2200
2231
  }
2201
2232
  });
2202
- var commandSandboxSchema, attachFieldSchema, contextContinuityInputSchema;
2233
+ var commandSandboxSchema, attachFieldSchema, contentBlockSchema, promptInputSchema, contextContinuityInputSchema;
2203
2234
  var init_spawn_field_schemas = __esm({
2204
2235
  "src/spawn-field-schemas.ts"() {
2205
2236
  commandSandboxSchema = z.enum(["off", "workspace", "strict"]);
@@ -2207,6 +2238,12 @@ var init_spawn_field_schemas = __esm({
2207
2238
  z.boolean(),
2208
2239
  z.object({ parent: z.string().min(1).optional() })
2209
2240
  ]);
2241
+ contentBlockSchema = z.record(z.string(), z.unknown());
2242
+ promptInputSchema = z.union([
2243
+ z.string().min(1),
2244
+ contentBlockSchema,
2245
+ z.array(contentBlockSchema).min(1)
2246
+ ]);
2210
2247
  contextContinuityInputSchema = z.object({
2211
2248
  mode: z.enum(["manual", "ask", "auto"]).optional(),
2212
2249
  warnAtPct: z.number().int().min(0).max(100).optional(),
@@ -2227,7 +2264,7 @@ var init_spawn_field_schemas = __esm({
2227
2264
  });
2228
2265
  }
2229
2266
  });
2230
- var mcpBool, mcpPositiveNumber, agentStartInputShape, agentStartInputSchema;
2267
+ var mcpBool, mcpPositiveNumber, help, agentStartInputShape, agentStartInputSchema;
2231
2268
  var init_agent_start_schema = __esm({
2232
2269
  "src/agent-start-schema.ts"() {
2233
2270
  init_json_tolerant();
@@ -2241,78 +2278,82 @@ var init_agent_start_schema = __esm({
2241
2278
  (v) => typeof v === "string" && v.trim() !== "" ? Number(v) : v,
2242
2279
  z.number().positive()
2243
2280
  );
2281
+ help = (field) => `Details: tool_help {name:"agent_start", topic:"${field}"}`;
2244
2282
  agentStartInputShape = {
2245
2283
  adapter: z.string().min(1).optional().describe(
2246
- "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."
2247
- ),
2248
- harness: z.string().min(1).optional().describe(
2249
- "Canonical harness slug \u2014 alias for `adapter`. Accepts the same values; use whichever field your caller produces."
2284
+ `Adapter slug (e.g. 'claude-code', 'hermes', 'aider'). Omit only when \`presetId\` supplies one. ${help("adapter")}`
2250
2285
  ),
2286
+ harness: z.string().min(1).optional().describe(`Alias for \`adapter\` \u2014 same values, whichever your caller produces.`),
2251
2287
  presetId: z.string().min(1).optional().describe(
2252
- "Saved user spawn preset id from `agentproto preset list`. Its adapter and decomposed axes are applied first; explicit fields on this call override it."
2288
+ "Saved spawn preset id from `agentproto preset list`. Explicit fields on this call override its values."
2253
2289
  ),
2254
2290
  workspaceSlug: z.string().optional().describe(
2255
- "Workspace slug from `agentproto workspace list`. The daemon resolves it to an absolute path. Omit to use the `cwd` field or the active workspace."
2291
+ "Workspace slug from `agentproto workspace list`, resolved to a path. Omit to use `cwd` or the active workspace."
2256
2292
  ),
2257
- cwd: z.string().optional().describe(
2258
- "Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."
2259
- ),
2260
- prompt: z.string().optional().describe(
2261
- "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."
2262
- ),
2263
- label: z.string().optional().describe(
2264
- "Free-text label that surfaces in `agent_sessions_list` and the UI \u2014 useful for tagging sessions with a conversation id or operator name."
2293
+ cwd: z.string().optional().describe("Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."),
2294
+ prompt: promptInputSchema.optional().describe(
2295
+ '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")
2265
2296
  ),
2297
+ label: z.string().optional().describe("Free-text label shown in `agent_sessions_list` and the UI."),
2266
2298
  mode: z.string().optional().describe(
2267
- "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."
2299
+ `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")}`
2268
2300
  ),
2269
2301
  origin: z.string().min(1).optional().describe(
2270
- "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."
2302
+ "Source label for this spawn (codex, cowork, vscode, cron, \u2026) \u2014 descriptor-only, groups the session under a source node in the tree."
2271
2303
  ),
2272
2304
  parentSessionId: z.string().min(1).optional().describe(
2273
- "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."
2305
+ `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")}`
2274
2306
  ),
2275
2307
  attach: jsonTolerant(attachFieldSchema).optional().describe(
2276
- '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.'
2308
+ `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")}`
2277
2309
  ),
2278
2310
  boardId: z.string().min(1).optional().describe(
2279
- "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."
2311
+ `Task-board pin for the child (\`meta.boardId\`), resolved before parent-lineage. An explicit board on a task verb still wins. ${help("boardId")}`
2280
2312
  ),
2281
2313
  idempotencyKey: z.string().min(1).optional().describe(
2282
- "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`."
2314
+ `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")}`
2283
2315
  ),
2284
2316
  dedupe: mcpBool.optional().describe(
2285
- '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.'
2317
+ `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")}`
2286
2318
  ),
2287
2319
  permissionHold: mcpBool.optional().describe(
2288
- "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."
2320
+ "Hold every ACP permission request in the cross-session inbox (`permissions_list`/`permissions_respond`) instead of auto-answering. ACP adapters only; default false."
2289
2321
  ),
2290
2322
  notifyParentOnCrash: mcpBool.optional().describe(
2291
- "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."
2323
+ `Notify this session's parent directly (in-band) if it later crashes. Independent of the global \`notifyUrl\` webhook, which fires either way. ${help("notifyParentOnCrash")}`
2324
+ ),
2325
+ sentinel: mcpBool.optional().describe(
2326
+ `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")}`
2292
2327
  ),
2293
2328
  allowSharedCwd: mcpBool.optional().describe(
2294
- "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."
2329
+ "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."
2295
2330
  ),
2296
2331
  keepAlive: mcpBool.optional().describe(
2297
- "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`."
2332
+ "Exempt this session from the idle-reaper \u2014 never auto-retired for sitting idle. Default false. Toggle later with `session_set_keepalive`."
2298
2333
  ),
2299
2334
  options: jsonTolerant(
2300
2335
  z.record(z.string(), z.union([z.boolean(), z.number(), z.string()]))
2301
2336
  ).optional().describe(
2302
- "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."
2337
+ "Manifest-declared option id \u2192 value map (AIP-45 `options`), validated against each option's declared type/enum/min/max. Unknown ids reject."
2303
2338
  ),
2304
2339
  skills: jsonTolerant(z.array(z.string())).optional().describe(
2305
- "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."
2340
+ `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")}`
2341
+ ),
2342
+ bundles: jsonTolerant(z.array(z.string())).optional().describe(
2343
+ `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")}`
2344
+ ),
2345
+ daemonMount: mcpBool.optional().describe(
2346
+ `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")}`
2306
2347
  ),
2307
2348
  model: z.string().optional().describe(
2308
- "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."
2349
+ "Model identifier to pass to the adapter. For claude-code, applied via session/set_config_option \u2014 NOT a CLI flag."
2309
2350
  ),
2310
2351
  effort: z.string().optional().describe(
2311
- "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."
2352
+ "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."
2312
2353
  ),
2313
2354
  route: jsonTolerant(
2314
2355
  z.object({ gateway: z.string().min(1), baseUrl: z.string().url().optional() })
2315
- ).optional().describe("Billing route/gateway. This is independent of model and named access profile."),
2356
+ ).optional().describe("Billing route/gateway. Independent of `model` and `access`."),
2316
2357
  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."),
2317
2358
  posture: z.string().optional().describe("Canonical agent posture (plan, bypass, accept-edits, read-only) or native harness mode."),
2318
2359
  contextProfile: z.string().optional().describe("Context intake profile (for example full or lean)."),
@@ -2324,7 +2365,7 @@ var init_agent_start_schema = __esm({
2324
2365
  apiKey: z.string().optional()
2325
2366
  })
2326
2367
  ).optional().describe(
2327
- "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."
2368
+ `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")}`
2328
2369
  ),
2329
2370
  mcpServers: jsonTolerant(
2330
2371
  z.array(
@@ -2332,42 +2373,34 @@ var init_agent_start_schema = __esm({
2332
2373
  name: z.string(),
2333
2374
  transport: z.enum(["stdio", "http", "sse"]),
2334
2375
  ref: z.string().optional(),
2335
- headers: z.record(z.string(), z.string()).optional().describe(
2336
- "Static HTTP headers sent with every request to an `http` or `sse` MCP server (e.g. a fixed auth token). Ignored for `stdio` transports."
2337
- ),
2376
+ headers: z.record(z.string(), z.string()).optional().describe("Static HTTP headers for `http`/`sse` transports. Ignored for `stdio`."),
2338
2377
  credentialRef: z.string().optional().describe(
2339
- "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`."
2378
+ "Brokered credential resolved into `headers` at spawn time (wins on collision with `headers`); the secret never lives in env/config."
2340
2379
  ),
2341
- args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command. Ignored for `http`/`sse`."),
2342
- env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server. Ignored for `http`/`sse`.")
2380
+ args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command."),
2381
+ env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server.")
2343
2382
  })
2344
2383
  )
2345
2384
  ).optional().describe(
2346
- "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."
2385
+ "MCP servers to mount into the spawned agent's session at spawn time. Adapters that don't model MCP mounting ignore it."
2347
2386
  ),
2348
2387
  orchestrator: jsonTolerant(
2349
2388
  z.union([
2350
2389
  z.boolean(),
2351
2390
  z.object({
2352
- tools: z.array(z.string()).optional().describe(
2353
- "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."
2354
- ),
2355
- maxDepth: z.number().int().min(1).max(8).optional().describe(
2356
- "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."
2357
- ),
2358
- maxChildren: z.number().int().min(1).optional().describe(
2359
- "Max concurrently-alive sub-agents this child may spawn (default 8). For a recursive spawn it can only lower the inherited quota."
2360
- )
2391
+ tools: z.array(z.string()).optional().describe("Explicit allowlist \u2286 the default subset \u2014 a child can never widen its own scope."),
2392
+ 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)."),
2393
+ maxChildren: z.number().int().min(1).optional().describe("Max concurrently-alive sub-agents this child may spawn (default 8, lower-only on recursion).")
2361
2394
  })
2362
2395
  ])
2363
2396
  ).optional().describe(
2364
- "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."
2397
+ `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")}`
2365
2398
  ),
2366
2399
  notifyUrl: z.string().url().optional().describe(
2367
2400
  "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."
2368
2401
  ),
2369
2402
  wait: mcpBool.optional().describe(
2370
- "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`."
2403
+ `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.`
2371
2404
  ),
2372
2405
  maxCostUsd: mcpPositiveNumber.optional().describe(
2373
2406
  "Hard ceiling on cumulative session cost (USD). The session is stopped at a turn-end once exceeded."
@@ -2379,45 +2412,39 @@ var init_agent_start_schema = __esm({
2379
2412
  scope: z.enum(["session", "profile"]).describe("Spend surface: this session, or every session on its auth profile.")
2380
2413
  })
2381
2414
  ).optional().describe(
2382
- "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."
2415
+ "Windowed cost-budget cap (DISTINCT from `maxCostUsd`) \u2014 trips `policy:failed` on overage, never kills the session."
2383
2416
  ),
2384
2417
  restartPolicy: jsonTolerant(
2385
2418
  z.object({
2386
- on: z.array(z.enum(["crashed", "error"])).min(1).describe(
2387
- '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.'
2388
- ),
2389
- maxRetries: z.number().int().positive().describe(
2390
- "Rolling-window crash-loop cap: give up (leave the session dead, no further auto-restart) once this many restarts have fired within `windowMs`."
2391
- ),
2419
+ 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."),
2420
+ maxRetries: z.number().int().positive().describe("Rolling-window crash-loop cap: give up after this many restarts within `windowMs`."),
2392
2421
  windowMs: z.number().int().positive().describe("Rolling window (ms) `maxRetries` counts restarts over."),
2393
2422
  baseDelayMs: z.number().int().positive().describe("First restart's backoff delay (ms), before `factor` compounds it."),
2394
2423
  factor: z.number().positive().describe("Exponential backoff multiplier applied per consecutive restart."),
2395
2424
  maxDelayMs: z.number().int().positive().describe("Backoff ceiling (ms) \u2014 the computed delay never grows past this."),
2396
- resume: z.boolean().optional().describe(
2397
- "Reserved for a future explicit resume-vs-fresh-spawn toggle; this PR always revives in place (same session id, same conversation)."
2398
- )
2425
+ resume: z.boolean().optional().describe("Reserved for a future resume-vs-fresh-spawn toggle; this always revives in place today.")
2399
2426
  })
2400
2427
  ).optional().describe(
2401
- "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."
2428
+ `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")}`
2402
2429
  ),
2403
2430
  contextContinuity: jsonTolerant(contextContinuityInputSchema).optional().describe(
2404
2431
  "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."
2405
2432
  ),
2406
2433
  role: z.string().optional().describe(
2407
- "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)."
2434
+ `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")}`
2408
2435
  ),
2409
2436
  promptAppend: z.string().optional().describe(
2410
- "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)."
2437
+ "One-off runtime text layered onto the resolved role's disposition and prepended to `prompt` \u2014 specializes it, cannot reopen a denied tool gate."
2411
2438
  ),
2412
2439
  deferredTools: z.boolean().optional().describe(
2413
- "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."
2440
+ `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")}`
2414
2441
  ),
2415
2442
  browser: z.preprocess(
2416
2443
  // `true` is sugar for the only mode; stringified booleans tolerated.
2417
2444
  (v) => v === true || v === "true" ? "headless" : v === "false" ? false : v,
2418
2445
  z.union([z.literal("headless"), z.literal(false)])
2419
2446
  ).optional().describe(
2420
- '`"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`.'
2447
+ `\`"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")}`
2421
2448
  ),
2422
2449
  trace: z.boolean().optional().describe(
2423
2450
  "Emit Langfuse observability traces for this session (prompt/completion + tool spans + tokens/cost). Off by default; requires langfuse eval-reporter creds configured."
@@ -2426,22 +2453,22 @@ var init_agent_start_schema = __esm({
2426
2453
  z.union([
2427
2454
  z.string().min(1).describe("Sandbox provider slug from `list_sandbox_providers` (e.g. 'local', 'e2b')."),
2428
2455
  sandboxSpecWithReuseSchema.describe(
2429
- "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."
2456
+ "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."
2430
2457
  )
2431
2458
  ])
2432
2459
  ).optional().describe(
2433
- "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)."
2460
+ `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")}`
2434
2461
  ),
2435
2462
  appServe: jsonTolerant(
2436
2463
  z.object({
2437
2464
  dir: z.string().min(1).describe("Absolute path to the app's directory INSIDE the sandbox box (e.g. '/home/user/apps/<slug>')."),
2438
- 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.")
2465
+ 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.")
2439
2466
  }).strict()
2440
2467
  ).optional().describe(
2441
- "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`)."
2468
+ `Serve an agentproto app's UI from INSIDE the sandbox box and return its public URL. Requires \`sandbox\` (rejected otherwise). ${help("appServe")}`
2442
2469
  ),
2443
2470
  commandSandbox: commandSandboxSchema.optional().describe(
2444
- '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.'
2471
+ `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")}`
2445
2472
  ),
2446
2473
  worktree: jsonTolerant(
2447
2474
  z.union([
@@ -2450,17 +2477,15 @@ var init_agent_start_schema = __esm({
2450
2477
  slug: z.string().regex(
2451
2478
  /^[a-z0-9][a-z0-9-]*$/,
2452
2479
  "slug must be lowercase kebab-case (letters, digits, hyphens)"
2453
- ).optional().describe(
2454
- "Pin the worktree's slug (names its branch `wt/<slug>` and its directory). Omit to auto-mint a collision-free one from the label."
2455
- ),
2480
+ ).optional().describe("Pin the worktree's slug (branch `wt/<slug>`). Omit to auto-mint one from the label."),
2456
2481
  base: z.string().min(1).optional().describe("Git ref the worktree branch is cut from. Default 'origin/main'."),
2457
2482
  async: z.boolean().optional().describe(
2458
- '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`.'
2483
+ `Return a registered session immediately (status "starting") instead of blocking on provisioning; incompatible with \`wait\`. Defaults true whenever a worktree is provisioned. ${help("worktree")}`
2459
2484
  )
2460
2485
  }).strict()
2461
2486
  ])
2462
2487
  ).optional().describe(
2463
- "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`."
2488
+ `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")}`
2464
2489
  )
2465
2490
  };
2466
2491
  agentStartInputSchema = z.object(agentStartInputShape);
@@ -2492,6 +2517,7 @@ var init_user_presets = __esm({
2492
2517
  contextProfile: z.string().min(1).optional(),
2493
2518
  cwd: z.string().min(1).optional(),
2494
2519
  skills: z.array(z.string().min(1)).optional(),
2520
+ bundles: z.array(z.string().min(1)).optional(),
2495
2521
  browser: z.union([z.literal("headless"), z.literal(false)]).optional()
2496
2522
  });
2497
2523
  z.object({
@@ -2504,8 +2530,20 @@ var init_catalog_provider_models = __esm({
2504
2530
  "src/catalog-provider-models.ts"() {
2505
2531
  }
2506
2532
  });
2533
+ var init_app_ui_build = __esm({
2534
+ "src/app-ui-build.ts"() {
2535
+ }
2536
+ });
2537
+
2538
+ // src/app-ui-placeholder.ts
2539
+ var init_app_ui_placeholder = __esm({
2540
+ "src/app-ui-placeholder.ts"() {
2541
+ }
2542
+ });
2507
2543
  var init_app_ui_apps = __esm({
2508
2544
  "src/app-ui-apps.ts"() {
2545
+ init_app_ui_build();
2546
+ init_app_ui_placeholder();
2509
2547
  }
2510
2548
  });
2511
2549
  var MESSAGE_KINDS, MESSAGE_URGENCIES;
@@ -2527,6 +2565,7 @@ var init_agent_tools = __esm({
2527
2565
  "src/agent-tools.ts"() {
2528
2566
  init_transcript_export();
2529
2567
  init_agent_start_schema();
2568
+ init_spawn_field_schemas();
2530
2569
  init_session_spawn();
2531
2570
  init_canonical_posture();
2532
2571
  init_user_presets();
@@ -2571,6 +2610,11 @@ var init_session_observer = __esm({
2571
2610
  "src/session-observer.ts"() {
2572
2611
  }
2573
2612
  });
2613
+ var init_session_artifacts = __esm({
2614
+ "src/session-artifacts.ts"() {
2615
+ init_transcript_writer();
2616
+ }
2617
+ });
2574
2618
  var DEFAULT_TITLER_MODEL;
2575
2619
  var init_session_titler = __esm({
2576
2620
  "src/session-titler.ts"() {
@@ -2597,10 +2641,6 @@ var init_terminal_transcript_writer = __esm({
2597
2641
  init_transcript_writer();
2598
2642
  }
2599
2643
  });
2600
- var init_usage = __esm({
2601
- "src/usage.ts"() {
2602
- }
2603
- });
2604
2644
  var init_context_window = __esm({
2605
2645
  "src/context-window.ts"() {
2606
2646
  }
@@ -2650,6 +2690,7 @@ var init_sessions = __esm({
2650
2690
  init_session_observer();
2651
2691
  init_tool_presenter();
2652
2692
  init_transcript_writer();
2693
+ init_session_artifacts();
2653
2694
  init_session_titler();
2654
2695
  init_resume_context_digest();
2655
2696
  init_conversation_index();
@@ -2733,7 +2774,9 @@ var init_config_schema = __esm({
2733
2774
  skills: z.array(z.string()).optional(),
2734
2775
  options: optionsMapSchema.optional(),
2735
2776
  auth: defaultsAdapterAuthConfigSchema.optional(),
2736
- contextContinuity: contextContinuityPolicySchema.optional()
2777
+ contextContinuity: contextContinuityPolicySchema.optional(),
2778
+ bundles: z.array(z.string()).optional(),
2779
+ daemonMount: z.boolean().optional()
2737
2780
  }).passthrough();
2738
2781
  mcpDefaultsConfigSchema = z.object({
2739
2782
  deferredTools: deferredToolsConfigSchema.optional()
@@ -2742,6 +2785,7 @@ var init_config_schema = __esm({
2742
2785
  skills: z.array(z.string()).optional(),
2743
2786
  options: optionsMapSchema.optional(),
2744
2787
  adapters: z.record(z.string(), defaultsAdapterConfigSchema).optional(),
2788
+ bundles: z.array(z.string()).optional(),
2745
2789
  contextContinuity: contextContinuityPolicySchema.optional(),
2746
2790
  defaultRoleDepthCutoff: z.number().optional(),
2747
2791
  maxGrantableDelegation: z.number().optional(),
@@ -2765,6 +2809,7 @@ var init_config_schema = __esm({
2765
2809
  label: z.string().optional(),
2766
2810
  authToken: z.string().optional(),
2767
2811
  resumeSessionsOnBoot: z.boolean().optional(),
2812
+ continueInterruptedOnBoot: z.boolean().optional(),
2768
2813
  idleReapAfterMs: z.number().optional(),
2769
2814
  crashDetectIntervalMs: z.number().optional(),
2770
2815
  restartSweepIntervalMs: z.number().optional(),
@@ -2782,7 +2827,8 @@ var init_config_schema = __esm({
2782
2827
  }).passthrough();
2783
2828
  featuresConfigSchema = z.object({
2784
2829
  pty: z.boolean().optional(),
2785
- llmEndpoint: z.boolean().optional()
2830
+ llmEndpoint: z.boolean().optional(),
2831
+ deviceInferenceShare: z.boolean().optional()
2786
2832
  }).passthrough();
2787
2833
  worktreesConfigSchema = z.object({
2788
2834
  root: z.string().optional(),
@@ -2939,6 +2985,16 @@ var init_config_schema = __esm({
2939
2985
  help: "Eagerly re-spawn eligible agent-cli sessions that died from a daemon restart.",
2940
2986
  default: false
2941
2987
  },
2988
+ {
2989
+ path: "daemon.continueInterruptedOnBoot",
2990
+ schema: bool,
2991
+ apply: "restart",
2992
+ writable: true,
2993
+ section: "daemon",
2994
+ label: "Continue interrupted sessions on boot",
2995
+ help: 'After the eager resume pass, send a one-shot "continue" prompt to sessions the last restart cut off mid-turn.',
2996
+ default: false
2997
+ },
2942
2998
  {
2943
2999
  path: "daemon.idleReapAfterMs",
2944
3000
  schema: num,
@@ -3112,6 +3168,15 @@ var init_config_schema = __esm({
3112
3168
  label: "Default options",
3113
3169
  help: "AIP-45 options auto-applied to every agent_start spawn."
3114
3170
  },
3171
+ {
3172
+ path: "defaults.bundles",
3173
+ schema: strArray,
3174
+ apply: "hot",
3175
+ writable: true,
3176
+ section: "defaults",
3177
+ label: "Default capability bundles",
3178
+ help: "Bundle ids (bundle_list) auto-attached to every agent_start spawn (unioned with per-adapter bundles)."
3179
+ },
3115
3180
  {
3116
3181
  path: "defaults.defaultRoleDepthCutoff",
3117
3182
  schema: num,
@@ -3338,6 +3403,25 @@ var init_config_schema = __esm({
3338
3403
  label: "Adapter default options",
3339
3404
  help: "AIP-45 options auto-applied to spawns of this adapter."
3340
3405
  },
3406
+ {
3407
+ path: "defaults.adapters.*.bundles",
3408
+ schema: strArray,
3409
+ apply: "hot",
3410
+ writable: true,
3411
+ section: "harnesses",
3412
+ label: "Adapter default capability bundles",
3413
+ help: "Bundle ids (bundle_list) auto-attached to spawns of this adapter (unioned with the global default)."
3414
+ },
3415
+ {
3416
+ path: "defaults.adapters.*.daemonMount",
3417
+ schema: z.boolean(),
3418
+ apply: "hot",
3419
+ writable: true,
3420
+ section: "harnesses",
3421
+ label: "Adapter daemon self-mount opt-in",
3422
+ 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).",
3423
+ default: false
3424
+ },
3341
3425
  {
3342
3426
  path: "defaults.adapters.*.contextContinuity",
3343
3427
  schema: contextContinuityPolicySchema,
@@ -3581,6 +3665,16 @@ var init_config_schema = __esm({
3581
3665
  label: "LLM Endpoint feature",
3582
3666
  help: "Enable the local LLM Endpoint proxy sidecar (route + MCP tools + child-process lifecycle).",
3583
3667
  default: false
3668
+ },
3669
+ {
3670
+ path: "features.deviceInferenceShare",
3671
+ schema: bool,
3672
+ apply: "restart",
3673
+ writable: true,
3674
+ section: "daemon",
3675
+ label: "Device inference sharing",
3676
+ help: "Expose this daemon's local inference endpoint(s) to a paired HOST-scoped controller. Toggle via `agentproto devices share-inference on|off`.",
3677
+ default: false
3584
3678
  }
3585
3679
  ];
3586
3680
  }