@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,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { DaemonConfig, TunnelConfig, FeaturesConfig, PairingConfig, WorktreesConfig, SpawnConfig, SessionsConfig, ProvenanceConfig, AgentsMdConfig, TitlerConfig, ProfileConfig, AcpAgentConfigEntry, TerminalPreset } from './config.js';
3
- import { S as SpawnDefaultsConfig } from './spawn-defaults-CZj39Znq.js';
3
+ import { S as SpawnDefaultsConfig } from './spawn-defaults-wQV9AeiG.js';
4
4
  import './context-continuity-Bn99UtVC.js';
5
5
  import '@agentproto/acp';
6
6
  import '@agentproto/command-sandbox';
@@ -2,10 +2,10 @@ import { createReadStream, promises } from 'fs';
2
2
  import 'crypto';
3
3
  import { homedir } from 'os';
4
4
  import { join, resolve } from 'path';
5
+ import '@agentproto/model-catalog/llm';
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';
@@ -184,6 +184,10 @@ var init_tool_call_record = __esm({
184
184
  "src/tool-call-record.ts"() {
185
185
  }
186
186
  });
187
+ var init_usage = __esm({
188
+ "src/usage.ts"() {
189
+ }
190
+ });
187
191
  function defaultTranscriptBaseDir() {
188
192
  return join(homedir(), ".agentproto", "sessions");
189
193
  }
@@ -197,6 +201,7 @@ var init_transcript_writer = __esm({
197
201
  "src/transcript-writer.ts"() {
198
202
  init_tool_call_record();
199
203
  init_pr_provenance();
204
+ init_usage();
200
205
  }
201
206
  });
202
207
 
@@ -593,11 +598,11 @@ async function discoverHermesSessions(cwd, since, expectedId) {
593
598
  }
594
599
  }
595
600
  async function defaultHermesRunner(adapterSessionId) {
596
- const { spawn: spawn4 } = await import('child_process');
601
+ const { spawn: spawn5 } = await import('child_process');
597
602
  return new Promise((resolve16, reject) => {
598
603
  const chunks = [];
599
604
  const errChunks = [];
600
- const proc = spawn4(
605
+ const proc = spawn5(
601
606
  "hermes",
602
607
  ["sessions", "export", "--session-id", adapterSessionId, "-"],
603
608
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -1944,6 +1949,29 @@ var init_worktree_identity = __esm({
1944
1949
  z.object({ worktreeId: z.string() });
1945
1950
  }
1946
1951
  });
1952
+ var init_mcp_imports = __esm({
1953
+ "src/mcp-imports.ts"() {
1954
+ }
1955
+ });
1956
+ var bundleIdSchema, bundleSchema;
1957
+ var init_bundles = __esm({
1958
+ "src/bundles.ts"() {
1959
+ init_mcp_imports();
1960
+ bundleIdSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "id must be lowercase kebab-case (letters, digits, hyphens)");
1961
+ bundleSchema = z.object({
1962
+ id: bundleIdSchema,
1963
+ label: z.string().min(1),
1964
+ description: z.string().min(1).optional(),
1965
+ mcpImports: z.array(z.string().min(1)),
1966
+ includeDaemon: z.boolean().optional(),
1967
+ skills: z.array(z.string().min(1))
1968
+ });
1969
+ z.object({
1970
+ version: z.literal(1),
1971
+ bundles: z.array(bundleSchema)
1972
+ });
1973
+ }
1974
+ });
1947
1975
  var init_catalog_models = __esm({
1948
1976
  "src/catalog-models.ts"() {
1949
1977
  init_spawn_defaults();
@@ -2147,10 +2175,13 @@ var init_sandbox_adapters = __esm({
2147
2175
  var init_session_spawn = __esm({
2148
2176
  "src/session-spawn.ts"() {
2149
2177
  init_sessions();
2178
+ init_transcript_writer();
2150
2179
  init_workspaces_config();
2151
2180
  init_config();
2152
2181
  init_worktree_identity();
2153
2182
  init_spawn_defaults();
2183
+ init_bundles();
2184
+ init_mcp_imports();
2154
2185
  init_launch_config();
2155
2186
  init_claude_code_oauth_source();
2156
2187
  init_providers_store();
@@ -2204,7 +2235,7 @@ var init_sandbox_spec_schema = __esm({
2204
2235
  }).strict();
2205
2236
  }
2206
2237
  });
2207
- var commandSandboxSchema, attachFieldSchema, contextContinuityInputSchema;
2238
+ var commandSandboxSchema, attachFieldSchema, contentBlockSchema, promptInputSchema, contextContinuityInputSchema;
2208
2239
  var init_spawn_field_schemas = __esm({
2209
2240
  "src/spawn-field-schemas.ts"() {
2210
2241
  commandSandboxSchema = z.enum(["off", "workspace", "strict"]);
@@ -2212,6 +2243,12 @@ var init_spawn_field_schemas = __esm({
2212
2243
  z.boolean(),
2213
2244
  z.object({ parent: z.string().min(1).optional() })
2214
2245
  ]);
2246
+ contentBlockSchema = z.record(z.string(), z.unknown());
2247
+ promptInputSchema = z.union([
2248
+ z.string().min(1),
2249
+ contentBlockSchema,
2250
+ z.array(contentBlockSchema).min(1)
2251
+ ]);
2215
2252
  contextContinuityInputSchema = z.object({
2216
2253
  mode: z.enum(["manual", "ask", "auto"]).optional(),
2217
2254
  warnAtPct: z.number().int().min(0).max(100).optional(),
@@ -2232,7 +2269,7 @@ var init_spawn_field_schemas = __esm({
2232
2269
  });
2233
2270
  }
2234
2271
  });
2235
- var mcpBool, mcpPositiveNumber, agentStartInputShape, agentStartInputSchema;
2272
+ var mcpBool, mcpPositiveNumber, help, agentStartInputShape, agentStartInputSchema;
2236
2273
  var init_agent_start_schema = __esm({
2237
2274
  "src/agent-start-schema.ts"() {
2238
2275
  init_json_tolerant();
@@ -2246,78 +2283,82 @@ var init_agent_start_schema = __esm({
2246
2283
  (v) => typeof v === "string" && v.trim() !== "" ? Number(v) : v,
2247
2284
  z.number().positive()
2248
2285
  );
2286
+ help = (field) => `Details: tool_help {name:"agent_start", topic:"${field}"}`;
2249
2287
  agentStartInputShape = {
2250
2288
  adapter: z.string().min(1).optional().describe(
2251
- "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."
2252
- ),
2253
- harness: z.string().min(1).optional().describe(
2254
- "Canonical harness slug \u2014 alias for `adapter`. Accepts the same values; use whichever field your caller produces."
2289
+ `Adapter slug (e.g. 'claude-code', 'hermes', 'aider'). Omit only when \`presetId\` supplies one. ${help("adapter")}`
2255
2290
  ),
2291
+ harness: z.string().min(1).optional().describe(`Alias for \`adapter\` \u2014 same values, whichever your caller produces.`),
2256
2292
  presetId: z.string().min(1).optional().describe(
2257
- "Saved user spawn preset id from `agentproto preset list`. Its adapter and decomposed axes are applied first; explicit fields on this call override it."
2293
+ "Saved spawn preset id from `agentproto preset list`. Explicit fields on this call override its values."
2258
2294
  ),
2259
2295
  workspaceSlug: z.string().optional().describe(
2260
- "Workspace slug from `agentproto workspace list`. The daemon resolves it to an absolute path. Omit to use the `cwd` field or the active workspace."
2296
+ "Workspace slug from `agentproto workspace list`, resolved to a path. Omit to use `cwd` or the active workspace."
2261
2297
  ),
2262
- cwd: z.string().optional().describe(
2263
- "Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."
2264
- ),
2265
- prompt: z.string().optional().describe(
2266
- "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."
2267
- ),
2268
- label: z.string().optional().describe(
2269
- "Free-text label that surfaces in `agent_sessions_list` and the UI \u2014 useful for tagging sessions with a conversation id or operator name."
2298
+ cwd: z.string().optional().describe("Absolute path to spawn the agent in. Wins over `workspaceSlug` when both are set."),
2299
+ prompt: promptInputSchema.optional().describe(
2300
+ '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")
2270
2301
  ),
2302
+ label: z.string().optional().describe("Free-text label shown in `agent_sessions_list` and the UI."),
2271
2303
  mode: z.string().optional().describe(
2272
- "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."
2304
+ `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")}`
2273
2305
  ),
2274
2306
  origin: z.string().min(1).optional().describe(
2275
- "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."
2307
+ "Source label for this spawn (codex, cowork, vscode, cron, \u2026) \u2014 descriptor-only, groups the session under a source node in the tree."
2276
2308
  ),
2277
2309
  parentSessionId: z.string().min(1).optional().describe(
2278
- "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."
2310
+ `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")}`
2279
2311
  ),
2280
2312
  attach: jsonTolerant(attachFieldSchema).optional().describe(
2281
- '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.'
2313
+ `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")}`
2282
2314
  ),
2283
2315
  boardId: z.string().min(1).optional().describe(
2284
- "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."
2316
+ `Task-board pin for the child (\`meta.boardId\`), resolved before parent-lineage. An explicit board on a task verb still wins. ${help("boardId")}`
2285
2317
  ),
2286
2318
  idempotencyKey: z.string().min(1).optional().describe(
2287
- "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`."
2319
+ `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")}`
2288
2320
  ),
2289
2321
  dedupe: mcpBool.optional().describe(
2290
- '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.'
2322
+ `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")}`
2291
2323
  ),
2292
2324
  permissionHold: mcpBool.optional().describe(
2293
- "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."
2325
+ "Hold every ACP permission request in the cross-session inbox (`permissions_list`/`permissions_respond`) instead of auto-answering. ACP adapters only; default false."
2294
2326
  ),
2295
2327
  notifyParentOnCrash: mcpBool.optional().describe(
2296
- "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."
2328
+ `Notify this session's parent directly (in-band) if it later crashes. Independent of the global \`notifyUrl\` webhook, which fires either way. ${help("notifyParentOnCrash")}`
2329
+ ),
2330
+ sentinel: mcpBool.optional().describe(
2331
+ `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")}`
2297
2332
  ),
2298
2333
  allowSharedCwd: mcpBool.optional().describe(
2299
- "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."
2334
+ "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."
2300
2335
  ),
2301
2336
  keepAlive: mcpBool.optional().describe(
2302
- "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`."
2337
+ "Exempt this session from the idle-reaper \u2014 never auto-retired for sitting idle. Default false. Toggle later with `session_set_keepalive`."
2303
2338
  ),
2304
2339
  options: jsonTolerant(
2305
2340
  z.record(z.string(), z.union([z.boolean(), z.number(), z.string()]))
2306
2341
  ).optional().describe(
2307
- "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."
2342
+ "Manifest-declared option id \u2192 value map (AIP-45 `options`), validated against each option's declared type/enum/min/max. Unknown ids reject."
2308
2343
  ),
2309
2344
  skills: jsonTolerant(z.array(z.string())).optional().describe(
2310
- "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."
2345
+ `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")}`
2346
+ ),
2347
+ bundles: jsonTolerant(z.array(z.string())).optional().describe(
2348
+ `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")}`
2349
+ ),
2350
+ daemonMount: mcpBool.optional().describe(
2351
+ `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")}`
2311
2352
  ),
2312
2353
  model: z.string().optional().describe(
2313
- "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."
2354
+ "Model identifier to pass to the adapter. For claude-code, applied via session/set_config_option \u2014 NOT a CLI flag."
2314
2355
  ),
2315
2356
  effort: z.string().optional().describe(
2316
- "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."
2357
+ "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."
2317
2358
  ),
2318
2359
  route: jsonTolerant(
2319
2360
  z.object({ gateway: z.string().min(1), baseUrl: z.string().url().optional() })
2320
- ).optional().describe("Billing route/gateway. This is independent of model and named access profile."),
2361
+ ).optional().describe("Billing route/gateway. Independent of `model` and `access`."),
2321
2362
  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."),
2322
2363
  posture: z.string().optional().describe("Canonical agent posture (plan, bypass, accept-edits, read-only) or native harness mode."),
2323
2364
  contextProfile: z.string().optional().describe("Context intake profile (for example full or lean)."),
@@ -2329,7 +2370,7 @@ var init_agent_start_schema = __esm({
2329
2370
  apiKey: z.string().optional()
2330
2371
  })
2331
2372
  ).optional().describe(
2332
- "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."
2373
+ `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")}`
2333
2374
  ),
2334
2375
  mcpServers: jsonTolerant(
2335
2376
  z.array(
@@ -2337,42 +2378,34 @@ var init_agent_start_schema = __esm({
2337
2378
  name: z.string(),
2338
2379
  transport: z.enum(["stdio", "http", "sse"]),
2339
2380
  ref: z.string().optional(),
2340
- headers: z.record(z.string(), z.string()).optional().describe(
2341
- "Static HTTP headers sent with every request to an `http` or `sse` MCP server (e.g. a fixed auth token). Ignored for `stdio` transports."
2342
- ),
2381
+ headers: z.record(z.string(), z.string()).optional().describe("Static HTTP headers for `http`/`sse` transports. Ignored for `stdio`."),
2343
2382
  credentialRef: z.string().optional().describe(
2344
- "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`."
2383
+ "Brokered credential resolved into `headers` at spawn time (wins on collision with `headers`); the secret never lives in env/config."
2345
2384
  ),
2346
- args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command. Ignored for `http`/`sse`."),
2347
- env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server. Ignored for `http`/`sse`.")
2385
+ args: z.array(z.string()).optional().describe("`stdio` only: argv passed to the `ref` command."),
2386
+ env: z.record(z.string(), z.string()).optional().describe("`stdio` only: extra environment for the launched server.")
2348
2387
  })
2349
2388
  )
2350
2389
  ).optional().describe(
2351
- "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."
2390
+ "MCP servers to mount into the spawned agent's session at spawn time. Adapters that don't model MCP mounting ignore it."
2352
2391
  ),
2353
2392
  orchestrator: jsonTolerant(
2354
2393
  z.union([
2355
2394
  z.boolean(),
2356
2395
  z.object({
2357
- tools: z.array(z.string()).optional().describe(
2358
- "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."
2359
- ),
2360
- maxDepth: z.number().int().min(1).max(8).optional().describe(
2361
- "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."
2362
- ),
2363
- maxChildren: z.number().int().min(1).optional().describe(
2364
- "Max concurrently-alive sub-agents this child may spawn (default 8). For a recursive spawn it can only lower the inherited quota."
2365
- )
2396
+ tools: z.array(z.string()).optional().describe("Explicit allowlist \u2286 the default subset \u2014 a child can never widen its own scope."),
2397
+ 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)."),
2398
+ maxChildren: z.number().int().min(1).optional().describe("Max concurrently-alive sub-agents this child may spawn (default 8, lower-only on recursion).")
2366
2399
  })
2367
2400
  ])
2368
2401
  ).optional().describe(
2369
- "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."
2402
+ `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")}`
2370
2403
  ),
2371
2404
  notifyUrl: z.string().url().optional().describe(
2372
2405
  "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."
2373
2406
  ),
2374
2407
  wait: mcpBool.optional().describe(
2375
- "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`."
2408
+ `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.`
2376
2409
  ),
2377
2410
  maxCostUsd: mcpPositiveNumber.optional().describe(
2378
2411
  "Hard ceiling on cumulative session cost (USD). The session is stopped at a turn-end once exceeded."
@@ -2384,45 +2417,39 @@ var init_agent_start_schema = __esm({
2384
2417
  scope: z.enum(["session", "profile"]).describe("Spend surface: this session, or every session on its auth profile.")
2385
2418
  })
2386
2419
  ).optional().describe(
2387
- "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."
2420
+ "Windowed cost-budget cap (DISTINCT from `maxCostUsd`) \u2014 trips `policy:failed` on overage, never kills the session."
2388
2421
  ),
2389
2422
  restartPolicy: jsonTolerant(
2390
2423
  z.object({
2391
- on: z.array(z.enum(["crashed", "error"])).min(1).describe(
2392
- '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.'
2393
- ),
2394
- maxRetries: z.number().int().positive().describe(
2395
- "Rolling-window crash-loop cap: give up (leave the session dead, no further auto-restart) once this many restarts have fired within `windowMs`."
2396
- ),
2424
+ 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."),
2425
+ maxRetries: z.number().int().positive().describe("Rolling-window crash-loop cap: give up after this many restarts within `windowMs`."),
2397
2426
  windowMs: z.number().int().positive().describe("Rolling window (ms) `maxRetries` counts restarts over."),
2398
2427
  baseDelayMs: z.number().int().positive().describe("First restart's backoff delay (ms), before `factor` compounds it."),
2399
2428
  factor: z.number().positive().describe("Exponential backoff multiplier applied per consecutive restart."),
2400
2429
  maxDelayMs: z.number().int().positive().describe("Backoff ceiling (ms) \u2014 the computed delay never grows past this."),
2401
- resume: z.boolean().optional().describe(
2402
- "Reserved for a future explicit resume-vs-fresh-spawn toggle; this PR always revives in place (same session id, same conversation)."
2403
- )
2430
+ resume: z.boolean().optional().describe("Reserved for a future resume-vs-fresh-spawn toggle; this always revives in place today.")
2404
2431
  })
2405
2432
  ).optional().describe(
2406
- "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."
2433
+ `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")}`
2407
2434
  ),
2408
2435
  contextContinuity: jsonTolerant(contextContinuityInputSchema).optional().describe(
2409
2436
  "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."
2410
2437
  ),
2411
2438
  role: z.string().optional().describe(
2412
- "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)."
2439
+ `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")}`
2413
2440
  ),
2414
2441
  promptAppend: z.string().optional().describe(
2415
- "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)."
2442
+ "One-off runtime text layered onto the resolved role's disposition and prepended to `prompt` \u2014 specializes it, cannot reopen a denied tool gate."
2416
2443
  ),
2417
2444
  deferredTools: z.boolean().optional().describe(
2418
- "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."
2445
+ `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")}`
2419
2446
  ),
2420
2447
  browser: z.preprocess(
2421
2448
  // `true` is sugar for the only mode; stringified booleans tolerated.
2422
2449
  (v) => v === true || v === "true" ? "headless" : v === "false" ? false : v,
2423
2450
  z.union([z.literal("headless"), z.literal(false)])
2424
2451
  ).optional().describe(
2425
- '`"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`.'
2452
+ `\`"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")}`
2426
2453
  ),
2427
2454
  trace: z.boolean().optional().describe(
2428
2455
  "Emit Langfuse observability traces for this session (prompt/completion + tool spans + tokens/cost). Off by default; requires langfuse eval-reporter creds configured."
@@ -2431,22 +2458,22 @@ var init_agent_start_schema = __esm({
2431
2458
  z.union([
2432
2459
  z.string().min(1).describe("Sandbox provider slug from `list_sandbox_providers` (e.g. 'local', 'e2b')."),
2433
2460
  sandboxSpecWithReuseSchema.describe(
2434
- "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."
2461
+ "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."
2435
2462
  )
2436
2463
  ])
2437
2464
  ).optional().describe(
2438
- "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)."
2465
+ `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")}`
2439
2466
  ),
2440
2467
  appServe: jsonTolerant(
2441
2468
  z.object({
2442
2469
  dir: z.string().min(1).describe("Absolute path to the app's directory INSIDE the sandbox box (e.g. '/home/user/apps/<slug>')."),
2443
- 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.")
2470
+ 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.")
2444
2471
  }).strict()
2445
2472
  ).optional().describe(
2446
- "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`)."
2473
+ `Serve an agentproto app's UI from INSIDE the sandbox box and return its public URL. Requires \`sandbox\` (rejected otherwise). ${help("appServe")}`
2447
2474
  ),
2448
2475
  commandSandbox: commandSandboxSchema.optional().describe(
2449
- '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.'
2476
+ `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")}`
2450
2477
  ),
2451
2478
  worktree: jsonTolerant(
2452
2479
  z.union([
@@ -2455,17 +2482,15 @@ var init_agent_start_schema = __esm({
2455
2482
  slug: z.string().regex(
2456
2483
  /^[a-z0-9][a-z0-9-]*$/,
2457
2484
  "slug must be lowercase kebab-case (letters, digits, hyphens)"
2458
- ).optional().describe(
2459
- "Pin the worktree's slug (names its branch `wt/<slug>` and its directory). Omit to auto-mint a collision-free one from the label."
2460
- ),
2485
+ ).optional().describe("Pin the worktree's slug (branch `wt/<slug>`). Omit to auto-mint one from the label."),
2461
2486
  base: z.string().min(1).optional().describe("Git ref the worktree branch is cut from. Default 'origin/main'."),
2462
2487
  async: z.boolean().optional().describe(
2463
- '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`.'
2488
+ `Return a registered session immediately (status "starting") instead of blocking on provisioning; incompatible with \`wait\`. Defaults true whenever a worktree is provisioned. ${help("worktree")}`
2464
2489
  )
2465
2490
  }).strict()
2466
2491
  ])
2467
2492
  ).optional().describe(
2468
- "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`."
2493
+ `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")}`
2469
2494
  )
2470
2495
  };
2471
2496
  agentStartInputSchema = z.object(agentStartInputShape);
@@ -2497,6 +2522,7 @@ var init_user_presets = __esm({
2497
2522
  contextProfile: z.string().min(1).optional(),
2498
2523
  cwd: z.string().min(1).optional(),
2499
2524
  skills: z.array(z.string().min(1)).optional(),
2525
+ bundles: z.array(z.string().min(1)).optional(),
2500
2526
  browser: z.union([z.literal("headless"), z.literal(false)]).optional()
2501
2527
  });
2502
2528
  z.object({
@@ -2509,8 +2535,20 @@ var init_catalog_provider_models = __esm({
2509
2535
  "src/catalog-provider-models.ts"() {
2510
2536
  }
2511
2537
  });
2538
+ var init_app_ui_build = __esm({
2539
+ "src/app-ui-build.ts"() {
2540
+ }
2541
+ });
2542
+
2543
+ // src/app-ui-placeholder.ts
2544
+ var init_app_ui_placeholder = __esm({
2545
+ "src/app-ui-placeholder.ts"() {
2546
+ }
2547
+ });
2512
2548
  var init_app_ui_apps = __esm({
2513
2549
  "src/app-ui-apps.ts"() {
2550
+ init_app_ui_build();
2551
+ init_app_ui_placeholder();
2514
2552
  }
2515
2553
  });
2516
2554
  var MESSAGE_KINDS, MESSAGE_URGENCIES;
@@ -2532,6 +2570,7 @@ var init_agent_tools = __esm({
2532
2570
  "src/agent-tools.ts"() {
2533
2571
  init_transcript_export();
2534
2572
  init_agent_start_schema();
2573
+ init_spawn_field_schemas();
2535
2574
  init_session_spawn();
2536
2575
  init_canonical_posture();
2537
2576
  init_user_presets();
@@ -2576,6 +2615,11 @@ var init_session_observer = __esm({
2576
2615
  "src/session-observer.ts"() {
2577
2616
  }
2578
2617
  });
2618
+ var init_session_artifacts = __esm({
2619
+ "src/session-artifacts.ts"() {
2620
+ init_transcript_writer();
2621
+ }
2622
+ });
2579
2623
  var DEFAULT_TITLER_MODEL;
2580
2624
  var init_session_titler = __esm({
2581
2625
  "src/session-titler.ts"() {
@@ -2602,10 +2646,6 @@ var init_terminal_transcript_writer = __esm({
2602
2646
  init_transcript_writer();
2603
2647
  }
2604
2648
  });
2605
- var init_usage = __esm({
2606
- "src/usage.ts"() {
2607
- }
2608
- });
2609
2649
  var init_context_window = __esm({
2610
2650
  "src/context-window.ts"() {
2611
2651
  }
@@ -2655,6 +2695,7 @@ var init_sessions = __esm({
2655
2695
  init_session_observer();
2656
2696
  init_tool_presenter();
2657
2697
  init_transcript_writer();
2698
+ init_session_artifacts();
2658
2699
  init_session_titler();
2659
2700
  init_resume_context_digest();
2660
2701
  init_conversation_index();
@@ -2822,7 +2863,9 @@ var init_config_schema = __esm({
2822
2863
  skills: z.array(z.string()).optional(),
2823
2864
  options: optionsMapSchema.optional(),
2824
2865
  auth: defaultsAdapterAuthConfigSchema.optional(),
2825
- contextContinuity: contextContinuityPolicySchema.optional()
2866
+ contextContinuity: contextContinuityPolicySchema.optional(),
2867
+ bundles: z.array(z.string()).optional(),
2868
+ daemonMount: z.boolean().optional()
2826
2869
  }).passthrough();
2827
2870
  mcpDefaultsConfigSchema = z.object({
2828
2871
  deferredTools: deferredToolsConfigSchema.optional()
@@ -2831,6 +2874,7 @@ var init_config_schema = __esm({
2831
2874
  skills: z.array(z.string()).optional(),
2832
2875
  options: optionsMapSchema.optional(),
2833
2876
  adapters: z.record(z.string(), defaultsAdapterConfigSchema).optional(),
2877
+ bundles: z.array(z.string()).optional(),
2834
2878
  contextContinuity: contextContinuityPolicySchema.optional(),
2835
2879
  defaultRoleDepthCutoff: z.number().optional(),
2836
2880
  maxGrantableDelegation: z.number().optional(),
@@ -2854,6 +2898,7 @@ var init_config_schema = __esm({
2854
2898
  label: z.string().optional(),
2855
2899
  authToken: z.string().optional(),
2856
2900
  resumeSessionsOnBoot: z.boolean().optional(),
2901
+ continueInterruptedOnBoot: z.boolean().optional(),
2857
2902
  idleReapAfterMs: z.number().optional(),
2858
2903
  crashDetectIntervalMs: z.number().optional(),
2859
2904
  restartSweepIntervalMs: z.number().optional(),
@@ -2871,7 +2916,8 @@ var init_config_schema = __esm({
2871
2916
  }).passthrough();
2872
2917
  featuresConfigSchema = z.object({
2873
2918
  pty: z.boolean().optional(),
2874
- llmEndpoint: z.boolean().optional()
2919
+ llmEndpoint: z.boolean().optional(),
2920
+ deviceInferenceShare: z.boolean().optional()
2875
2921
  }).passthrough();
2876
2922
  worktreesConfigSchema = z.object({
2877
2923
  root: z.string().optional(),
@@ -3028,6 +3074,16 @@ var init_config_schema = __esm({
3028
3074
  help: "Eagerly re-spawn eligible agent-cli sessions that died from a daemon restart.",
3029
3075
  default: false
3030
3076
  },
3077
+ {
3078
+ path: "daemon.continueInterruptedOnBoot",
3079
+ schema: bool,
3080
+ apply: "restart",
3081
+ writable: true,
3082
+ section: "daemon",
3083
+ label: "Continue interrupted sessions on boot",
3084
+ help: 'After the eager resume pass, send a one-shot "continue" prompt to sessions the last restart cut off mid-turn.',
3085
+ default: false
3086
+ },
3031
3087
  {
3032
3088
  path: "daemon.idleReapAfterMs",
3033
3089
  schema: num,
@@ -3201,6 +3257,15 @@ var init_config_schema = __esm({
3201
3257
  label: "Default options",
3202
3258
  help: "AIP-45 options auto-applied to every agent_start spawn."
3203
3259
  },
3260
+ {
3261
+ path: "defaults.bundles",
3262
+ schema: strArray,
3263
+ apply: "hot",
3264
+ writable: true,
3265
+ section: "defaults",
3266
+ label: "Default capability bundles",
3267
+ help: "Bundle ids (bundle_list) auto-attached to every agent_start spawn (unioned with per-adapter bundles)."
3268
+ },
3204
3269
  {
3205
3270
  path: "defaults.defaultRoleDepthCutoff",
3206
3271
  schema: num,
@@ -3427,6 +3492,25 @@ var init_config_schema = __esm({
3427
3492
  label: "Adapter default options",
3428
3493
  help: "AIP-45 options auto-applied to spawns of this adapter."
3429
3494
  },
3495
+ {
3496
+ path: "defaults.adapters.*.bundles",
3497
+ schema: strArray,
3498
+ apply: "hot",
3499
+ writable: true,
3500
+ section: "harnesses",
3501
+ label: "Adapter default capability bundles",
3502
+ help: "Bundle ids (bundle_list) auto-attached to spawns of this adapter (unioned with the global default)."
3503
+ },
3504
+ {
3505
+ path: "defaults.adapters.*.daemonMount",
3506
+ schema: z.boolean(),
3507
+ apply: "hot",
3508
+ writable: true,
3509
+ section: "harnesses",
3510
+ label: "Adapter daemon self-mount opt-in",
3511
+ 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).",
3512
+ default: false
3513
+ },
3430
3514
  {
3431
3515
  path: "defaults.adapters.*.contextContinuity",
3432
3516
  schema: contextContinuityPolicySchema,
@@ -3670,6 +3754,16 @@ var init_config_schema = __esm({
3670
3754
  label: "LLM Endpoint feature",
3671
3755
  help: "Enable the local LLM Endpoint proxy sidecar (route + MCP tools + child-process lifecycle).",
3672
3756
  default: false
3757
+ },
3758
+ {
3759
+ path: "features.deviceInferenceShare",
3760
+ schema: bool,
3761
+ apply: "restart",
3762
+ writable: true,
3763
+ section: "daemon",
3764
+ label: "Device inference sharing",
3765
+ help: "Expose this daemon's local inference endpoint(s) to a paired HOST-scoped controller. Toggle via `agentproto devices share-inference on|off`.",
3766
+ default: false
3673
3767
  }
3674
3768
  ];
3675
3769
  CONFIG_KEYS_NOT_EXPOSED = [