@otto-code/protocol 0.7.3 → 0.7.5

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/messages.js CHANGED
@@ -7,7 +7,7 @@ import { CLIENT_CAPS } from "./client-capabilities.js";
7
7
  import { AGENT_LIFECYCLE_STATUSES } from "./agent-lifecycle.js";
8
8
  import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from "./agent-title-limits.js";
9
9
  import { AgentProviderSchema } from "./provider-manifest.js";
10
- import { McpServerConfigSchema, OTTO_TOOL_GROUPS } from "./provider-config.js";
10
+ import { ConnectorConfigSchema, McpServerConfigSchema, OTTO_TOOL_GROUPS, } from "./provider-config.js";
11
11
  import { TOOL_CALL_ICON_NAMES } from "./agent-types.js";
12
12
  import { ChatCreateRequestSchema, ChatListRequestSchema, ChatInspectRequestSchema, ChatDeleteRequestSchema, ChatPostRequestSchema, ChatReadRequestSchema, ChatWaitRequestSchema, ChatCreateResponseSchema, ChatListResponseSchema, ChatInspectResponseSchema, ChatDeleteResponseSchema, ChatPostResponseSchema, ChatReadResponseSchema, ChatWaitResponseSchema, } from "./chat/rpc-schemas.js";
13
13
  import { ScheduleCreateRequestSchema, ScheduleListRequestSchema, ScheduleInspectRequestSchema, ScheduleLogsRequestSchema, SchedulePauseRequestSchema, ScheduleResumeRequestSchema, ScheduleDeleteRequestSchema, ScheduleRunOnceRequestSchema, ScheduleUpdateRequestSchema, ScheduleCreateResponseSchema, ScheduleListResponseSchema, ScheduleInspectResponseSchema, ScheduleLogsResponseSchema, SchedulePauseResponseSchema, ScheduleResumeResponseSchema, ScheduleDeleteResponseSchema, ScheduleRunOnceResponseSchema, ScheduleUpdateResponseSchema, } from "./schedule/rpc-schemas.js";
@@ -68,6 +68,17 @@ const MutableAgentBehaviorsConfigSchema = z
68
68
  // Default value of an agent's notifyOnFinish when the spawn path leaves it
69
69
  // unspecified (the current implicit default).
70
70
  notifyOnFinishDefault: z.boolean().default(true),
71
+ // Provider-agnostic task-list reminders. Otto renders every provider's
72
+ // native todo list into one timeline UI; when an agent leaves that list with
73
+ // unfinished items, these keep it from going stale (the user shouldn't have
74
+ // to dismiss a half-checked list themselves).
75
+ // Passive: while a stale list is open, attach a reminder to the agent's next
76
+ // turn (mirrors the harness's own "your todo list looks stale" nudge).
77
+ todoNudge: z.boolean().default(true),
78
+ // Active: when the agent goes idle with a stale list, inject a one-shot
79
+ // reconcile pass so it marks done what's done (or states what's genuinely
80
+ // left) before the turn truly ends.
81
+ todoReconcileOnIdle: z.boolean().default(true),
71
82
  })
72
83
  .passthrough();
73
84
  export const TerminalProfileSchema = z
@@ -397,6 +408,153 @@ export const SavedProviderEndpointSchema = z
397
408
  savedAt: z.number().optional(),
398
409
  })
399
410
  .passthrough();
411
+ // The editable projection of @otto-code/brain's own config (the brain's
412
+ // config.json stays the source of truth on disk; the daemon writes changes
413
+ // through). Every field is defaulted so a new client parsing an old daemon's
414
+ // config sees a well-formed, OFF section.
415
+ export const MutableBrainTlsConfigSchema = z
416
+ .object({
417
+ mode: z.enum(["off", "files", "self-signed", "tailscale"]).default("off"),
418
+ certFile: z.string().nullable().default(null),
419
+ keyFile: z.string().nullable().default(null),
420
+ hostname: z.string().nullable().default(null),
421
+ certDir: z.string().nullable().default(null),
422
+ renewBeforeDays: z.number().int().min(1).default(21),
423
+ })
424
+ .passthrough();
425
+ // Where a remote brain lives, when brain.mode is "remote". Every field is
426
+ // defaulted so an old daemon's config parses as a well-formed, empty target.
427
+ export const MutableBrainRemoteConfigSchema = z
428
+ .object({
429
+ host: z.string().default(""),
430
+ port: z.number().int().default(1234),
431
+ secure: z.boolean().default(false),
432
+ // Secret: masked with DAEMON_CONFIG_SECRET_SENTINEL on the way out.
433
+ authToken: z.string().nullable().default(null),
434
+ })
435
+ .passthrough();
436
+ export const MutableBrainConfigSchema = z
437
+ .object({
438
+ enabled: z.boolean().default(false),
439
+ autoStart: z.boolean().default(false),
440
+ // "local": the daemon spawns and supervises the brain on this host.
441
+ // "remote": the daemon connects to a brain running on another Otto host
442
+ // (read-only: status/evals/config, no lifecycle). Gated by features.brainRemote.
443
+ mode: z.enum(["local", "remote"]).default("local"),
444
+ remote: MutableBrainRemoteConfigSchema.default({
445
+ host: "",
446
+ port: 1234,
447
+ secure: false,
448
+ authToken: null,
449
+ }),
450
+ listen: z
451
+ .object({
452
+ host: z.string().default("127.0.0.1"),
453
+ port: z.number().int().default(1234),
454
+ })
455
+ .passthrough()
456
+ .default({ host: "127.0.0.1", port: 1234 }),
457
+ defaultModel: z.string().nullable().default(null),
458
+ // Pin the host to one model: serve only the default/resident model and
459
+ // refuse completion requests that ask for a different one.
460
+ lockModel: z.boolean().default(false),
461
+ // Sharing gates (off by default). allowRemoteConfig: key holders may CHANGE
462
+ // config over the network (POST /__host/config), not just use it.
463
+ // allowInsecureBind: permit a non-loopback bind with no token (open share).
464
+ allowRemoteConfig: z.boolean().default(false),
465
+ allowInsecureBind: z.boolean().default(false),
466
+ authMode: z.enum(["none", "token"]).default("none"),
467
+ // Secret: masked with DAEMON_CONFIG_SECRET_SENTINEL on the way out; an
468
+ // unchanged sentinel is stripped from inbound patches.
469
+ authToken: z.string().nullable().default(null),
470
+ tls: MutableBrainTlsConfigSchema.default({
471
+ mode: "off",
472
+ certFile: null,
473
+ keyFile: null,
474
+ hostname: null,
475
+ certDir: null,
476
+ renewBeforeDays: 21,
477
+ }),
478
+ })
479
+ .passthrough();
480
+ // The brain PATCH schema — deliberately NOT `MutableBrainConfigSchema.partial()`.
481
+ // Every field of the full schema carries a `.default()` (so an old daemon's
482
+ // half-written config still parses as a well-formed OFF section), and Zod keeps
483
+ // those defaults through `.partial()`: `MutableBrainConfigSchema.partial().parse(
484
+ // { allowRemoteConfig: true })` expands to the FULL object with every other field
485
+ // defaulted. The daemon deep-merges the parsed patch over the stored config, so a
486
+ // single-field patch would silently reset the entire brain block to defaults —
487
+ // turning sharing off (host back to loopback), wiping the auth token, and
488
+ // disabling the server. Mirroring the shape WITHOUT defaults keeps an omitted
489
+ // field omitted, so the deep-merge preserves it. Every level is deep-partial so a
490
+ // nested patch (e.g. just `listen.host`) preserves its siblings too. Keep the
491
+ // field set in sync with MutableBrainConfigSchema; `.passthrough()` carries any
492
+ // field a newer daemon adds through untouched in the meantime.
493
+ const MutableBrainTlsPatchSchema = z
494
+ .object({
495
+ mode: z.enum(["off", "files", "self-signed", "tailscale"]),
496
+ certFile: z.string().nullable(),
497
+ keyFile: z.string().nullable(),
498
+ hostname: z.string().nullable(),
499
+ certDir: z.string().nullable(),
500
+ renewBeforeDays: z.number().int().min(1),
501
+ })
502
+ .partial()
503
+ .passthrough();
504
+ const MutableBrainRemotePatchSchema = z
505
+ .object({
506
+ host: z.string(),
507
+ port: z.number().int(),
508
+ secure: z.boolean(),
509
+ authToken: z.string().nullable(),
510
+ })
511
+ .partial()
512
+ .passthrough();
513
+ const MutableBrainListenPatchSchema = z
514
+ .object({
515
+ host: z.string(),
516
+ port: z.number().int(),
517
+ })
518
+ .partial()
519
+ .passthrough();
520
+ export const MutableBrainConfigPatchSchema = z
521
+ .object({
522
+ enabled: z.boolean(),
523
+ autoStart: z.boolean(),
524
+ mode: z.enum(["local", "remote"]),
525
+ remote: MutableBrainRemotePatchSchema,
526
+ listen: MutableBrainListenPatchSchema,
527
+ defaultModel: z.string().nullable(),
528
+ lockModel: z.boolean(),
529
+ allowRemoteConfig: z.boolean(),
530
+ allowInsecureBind: z.boolean(),
531
+ authMode: z.enum(["none", "token"]),
532
+ authToken: z.string().nullable(),
533
+ tls: MutableBrainTlsPatchSchema,
534
+ })
535
+ .partial()
536
+ .passthrough();
537
+ export const DEFAULT_MUTABLE_BRAIN_CONFIG = {
538
+ enabled: false,
539
+ autoStart: false,
540
+ mode: "local",
541
+ remote: { host: "", port: 1234, secure: false, authToken: null },
542
+ listen: { host: "127.0.0.1", port: 1234 },
543
+ defaultModel: null,
544
+ lockModel: false,
545
+ allowRemoteConfig: false,
546
+ allowInsecureBind: false,
547
+ authMode: "none",
548
+ authToken: null,
549
+ tls: {
550
+ mode: "off",
551
+ certFile: null,
552
+ keyFile: null,
553
+ hostname: null,
554
+ certDir: null,
555
+ renewBeforeDays: 21,
556
+ },
557
+ };
400
558
  export const MutableDaemonConfigSchema = z
401
559
  .object({
402
560
  mcp: z
@@ -418,6 +576,8 @@ export const MutableDaemonConfigSchema = z
418
576
  promptSuggestions: true,
419
577
  agentProgressSummaries: true,
420
578
  notifyOnFinishDefault: true,
579
+ todoNudge: true,
580
+ todoReconcileOnIdle: true,
421
581
  }),
422
582
  providers: z.record(z.string(), MutableDaemonProviderConfigSchema).default({}),
423
583
  metadataGeneration: MutableMetadataGenerationConfigSchema.default({
@@ -481,6 +641,15 @@ export const MutableDaemonConfigSchema = z
481
641
  maxRunningProbes: 2,
482
642
  idleMinutes: 10,
483
643
  }),
644
+ // Local AI host (otto-brain) management. Gated by server_info features.brainControl;
645
+ // defaults OFF and well-formed so a new client parsing an old daemon's config renders
646
+ // the row without ever implying the brain is running.
647
+ brain: MutableBrainConfigSchema.default(DEFAULT_MUTABLE_BRAIN_CONFIG),
648
+ // Host-wide connector registry (MCP servers surfaced as named, toggle-able
649
+ // integrations). Gated by server_info features.connectors; defaults to an
650
+ // empty roster so a new client parsing an old daemon's config still sees a
651
+ // well-formed section.
652
+ connectors: z.array(ConnectorConfigSchema).default([]),
484
653
  })
485
654
  .passthrough();
486
655
  export const MutableDaemonConfigPatchSchema = z
@@ -529,6 +698,15 @@ export const MutableDaemonConfigPatchSchema = z
529
698
  lsp: MutableLspConfigSchema.partial().optional(),
530
699
  // Gated by server_info features.solutionView; patches deep-merge.
531
700
  dotnetSolutionManagement: MutableDotnetSolutionConfigSchema.partial().optional(),
701
+ // Gated by server_info features.brainControl; patches deep-merge. Uses the
702
+ // dedicated no-default patch schema (see MutableBrainConfigPatchSchema): a
703
+ // plain `.partial()` here keeps every field's default and resets the whole
704
+ // block on a single-field patch.
705
+ brain: MutableBrainConfigPatchSchema.optional(),
706
+ // Gated by server_info features.connectors. Replaces the full array
707
+ // (read-modify-write), matching modelTierOverrides/savedProviderEndpoints, so
708
+ // enabling/disabling a connector or a tool is a whole-array rewrite.
709
+ connectors: z.array(ConnectorConfigSchema).optional(),
532
710
  })
533
711
  .partial()
534
712
  .passthrough();
@@ -1030,6 +1208,13 @@ export const QueuedAgentMessagePayloadSchema = z.object({
1030
1208
  preview: z.string(),
1031
1209
  enqueuedAt: z.string(),
1032
1210
  attachmentCount: z.number().int().nonnegative().optional(),
1211
+ /**
1212
+ * Who parked the message. Absent (from an older daemon) or "user" is a normal
1213
+ * user turn; "system" marks a system-injected entry (a chat mention, a
1214
+ * scheduled fire) that the daemon's drain never merges into a user turn — the
1215
+ * client must likewise exclude it from "Send all".
1216
+ */
1217
+ source: z.enum(["user", "system"]).optional(),
1033
1218
  });
1034
1219
  /**
1035
1220
  * An agent's LIFETIME SPEND, kept as the real token split plus the provider's
@@ -1129,6 +1314,14 @@ export const AgentSnapshotPayloadSchema = z.object({
1129
1314
  // COMPAT(observedSubagents): added in v0.4.3; absent ⇒ "attended". Drop the
1130
1315
  // gate when daemon floor >= v0.4.3. See projects/observed-subagents/observed-subagents.md.
1131
1316
  attend: z.enum(["attended", "observed"]).optional(),
1317
+ // True when an observed sub-agent run outlives an interrupt of the parent's
1318
+ // turn: a backgrounded Task/Agent (its tool_result was only a launch ack) or
1319
+ // a Workflow orchestration run. The client uses it to stop claiming that
1320
+ // interrupting the parent stops work it will not actually stop.
1321
+ // COMPAT(backgroundedObservedSubagents): added in v0.7.5; absent ⇒ treated as
1322
+ // foreground, which is the pre-existing behavior. Drop the gate when daemon
1323
+ // floor >= v0.7.5. See docs/chat-lifecycle.md.
1324
+ backgrounded: z.boolean().optional(),
1132
1325
  // Spinner colors from the Agent Personality this agent was spawned from, so
1133
1326
  // its live thinking indicator renders in the personality's identity. Absent ⇒
1134
1327
  // the client falls back to the theme's default spinner colors. Purely additive
@@ -1313,6 +1506,459 @@ export const AttachmentsImagesClearResponseSchema = z.object({
1313
1506
  requestId: z.string(),
1314
1507
  }),
1315
1508
  });
1509
+ // --- Local AI host (otto-brain) management -------------------------------
1510
+ // Lifecycle + evals are correlated request/response RPCs. Gated by
1511
+ // server_info.features.brainControl (lifecycle) and features.brainStatus
1512
+ // (evals). Live status streaming (subscribe_brain_status + brain_status_changed)
1513
+ // is added alongside its client/daemon consumers.
1514
+ // The brain's host status, as the daemon derives it: liveness plus the fields
1515
+ // proxied from the brain's own `/__host/status`. Passthrough on the opaque
1516
+ // sub-objects so the brain can evolve them without a protocol bump.
1517
+ export const BrainHostStatusSchema = z
1518
+ .object({
1519
+ running: z.boolean(),
1520
+ pid: z.number().nullable().optional(),
1521
+ version: z.string().nullable().optional(),
1522
+ host: z.string().nullable().optional(),
1523
+ port: z.number().nullable().optional(),
1524
+ displayHost: z.string().nullable().optional(),
1525
+ secure: z.boolean().optional(),
1526
+ state: z.string().nullable().optional(),
1527
+ model: z.string().nullable().optional(),
1528
+ modelId: z.string().nullable().optional(),
1529
+ vramBytes: z.number().nullable().optional(),
1530
+ loadSeconds: z.number().nullable().optional(),
1531
+ startedAt: z.string().nullable().optional(),
1532
+ lastError: z.string().nullable().optional(),
1533
+ telemetry: z.record(z.string(), z.unknown()).nullable().optional(),
1534
+ scheduler: z.record(z.string(), z.unknown()).nullable().optional(),
1535
+ recent: z.array(z.record(z.string(), z.unknown())).optional(),
1536
+ })
1537
+ .passthrough();
1538
+ const BrainHostStatusResultSchema = z.object({
1539
+ status: BrainHostStatusSchema,
1540
+ error: z.string().nullable(),
1541
+ requestId: z.string(),
1542
+ });
1543
+ export const BrainHostStatusRequestSchema = z.object({
1544
+ type: z.literal("brain.host.status.request"),
1545
+ requestId: z.string(),
1546
+ });
1547
+ export const BrainHostStatusResponseSchema = z.object({
1548
+ type: z.literal("brain.host.status.response"),
1549
+ payload: BrainHostStatusResultSchema,
1550
+ });
1551
+ export const BrainHostStartRequestSchema = z.object({
1552
+ type: z.literal("brain.host.start.request"),
1553
+ // Optional model fragment/id to load on start; null = the brain's default.
1554
+ model: z.string().nullable().default(null),
1555
+ requestId: z.string(),
1556
+ });
1557
+ export const BrainHostStartResponseSchema = z.object({
1558
+ type: z.literal("brain.host.start.response"),
1559
+ payload: BrainHostStatusResultSchema,
1560
+ });
1561
+ export const BrainHostStopRequestSchema = z.object({
1562
+ type: z.literal("brain.host.stop.request"),
1563
+ requestId: z.string(),
1564
+ });
1565
+ export const BrainHostStopResponseSchema = z.object({
1566
+ type: z.literal("brain.host.stop.response"),
1567
+ payload: BrainHostStatusResultSchema,
1568
+ });
1569
+ export const BrainHostRestartRequestSchema = z.object({
1570
+ type: z.literal("brain.host.restart.request"),
1571
+ model: z.string().nullable().default(null),
1572
+ requestId: z.string(),
1573
+ });
1574
+ export const BrainHostRestartResponseSchema = z.object({
1575
+ type: z.literal("brain.host.restart.response"),
1576
+ payload: BrainHostStatusResultSchema,
1577
+ });
1578
+ // Benchmark rankings/variance/latest, proxied from the brain's `/__host/evals`.
1579
+ export const BrainEvalsSchema = z
1580
+ .object({
1581
+ rankings: z.array(z.record(z.string(), z.unknown())).default([]),
1582
+ latest: z.array(z.record(z.string(), z.unknown())).default([]),
1583
+ variance: z.array(z.record(z.string(), z.unknown())).default([]),
1584
+ runCount: z.number().default(0),
1585
+ })
1586
+ .passthrough();
1587
+ export const BrainEvalsGetRequestSchema = z.object({
1588
+ type: z.literal("brain.evals.get.request"),
1589
+ requestId: z.string(),
1590
+ });
1591
+ export const BrainEvalsGetResponseSchema = z.object({
1592
+ type: z.literal("brain.evals.get.response"),
1593
+ payload: z.object({
1594
+ evals: BrainEvalsSchema.nullable(),
1595
+ error: z.string().nullable(),
1596
+ requestId: z.string(),
1597
+ }),
1598
+ });
1599
+ // Brain network auto-discovery: the daemon enumerates this host's bind
1600
+ // addresses and probes the local `tailscale` CLI so the client can offer the
1601
+ // operator a pick-list of likely listen hosts (and pre-fill the tailscale TLS
1602
+ // mode) instead of asking them to hunt for IPs by hand.
1603
+ // Gated by server_info.features.brainNetworkDiscovery.
1604
+ export const BrainTailscaleInfoSchema = z
1605
+ .object({
1606
+ // Whether the tailscale CLI is present and its daemon answers.
1607
+ available: z.boolean(),
1608
+ // This machine's MagicDNS name, e.g. greyskull.tail279562.ts.net.
1609
+ hostname: z.string().nullable().optional(),
1610
+ // The tailnet IPv4 address, for a tailnet-only bind.
1611
+ ipv4: z.string().nullable().optional(),
1612
+ // The default directory the brain writes issued certificates to.
1613
+ certDir: z.string().nullable().optional(),
1614
+ })
1615
+ .passthrough();
1616
+ // One candidate value for `listen.host`, with a human label for the pick-list.
1617
+ export const BrainBindAddressSchema = z
1618
+ .object({
1619
+ // The literal value written to listen.host (an IP, 0.0.0.0, or "tailscale").
1620
+ value: z.string(),
1621
+ // Display label, e.g. "Local only", "All interfaces", "192.168.1.42 (en0)".
1622
+ label: z.string(),
1623
+ kind: z.enum(["loopback", "all", "lan", "tailscale"]),
1624
+ })
1625
+ .passthrough();
1626
+ export const BrainNetworkInfoSchema = z
1627
+ .object({
1628
+ addresses: z.array(BrainBindAddressSchema).default([]),
1629
+ tailscale: BrainTailscaleInfoSchema.nullable().optional(),
1630
+ })
1631
+ .passthrough();
1632
+ // Detected model names for the settings pickers. Read from the brain's
1633
+ // /v1/models when it is reachable (local child up, or remote); empty otherwise,
1634
+ // which the client renders as a disabled picker. Gated by features.brainStatus.
1635
+ export const BrainModelsListRequestSchema = z.object({
1636
+ type: z.literal("brain.models.list.request"),
1637
+ requestId: z.string(),
1638
+ });
1639
+ export const BrainModelsListResponseSchema = z.object({
1640
+ type: z.literal("brain.models.list.response"),
1641
+ payload: z.object({
1642
+ models: z.array(z.string()).default([]),
1643
+ error: z.string().nullable(),
1644
+ requestId: z.string(),
1645
+ }),
1646
+ });
1647
+ // Read/write a *remote* brain's own config (its /__host/config). Only valid in
1648
+ // brain.mode "remote"; the config is the remote brain's effective config with
1649
+ // secrets redacted. Editable fields are model-related (defaultModel, lockModel);
1650
+ // network/TLS/auth stay host-owned. Gated by features.brainRemote.
1651
+ export const BrainRemoteConfigSchema = z.record(z.string(), z.unknown());
1652
+ export const BrainRemoteConfigGetRequestSchema = z.object({
1653
+ type: z.literal("brain.remote.config.get.request"),
1654
+ requestId: z.string(),
1655
+ });
1656
+ export const BrainRemoteConfigGetResponseSchema = z.object({
1657
+ type: z.literal("brain.remote.config.get.response"),
1658
+ payload: z.object({
1659
+ config: BrainRemoteConfigSchema.nullable(),
1660
+ error: z.string().nullable(),
1661
+ requestId: z.string(),
1662
+ }),
1663
+ });
1664
+ export const BrainRemoteConfigPatchRequestSchema = z.object({
1665
+ type: z.literal("brain.remote.config.patch.request"),
1666
+ patch: BrainRemoteConfigSchema,
1667
+ requestId: z.string(),
1668
+ });
1669
+ export const BrainRemoteConfigPatchResponseSchema = z.object({
1670
+ type: z.literal("brain.remote.config.patch.response"),
1671
+ payload: z.object({
1672
+ config: BrainRemoteConfigSchema.nullable(),
1673
+ error: z.string().nullable(),
1674
+ requestId: z.string(),
1675
+ }),
1676
+ });
1677
+ export const BrainNetworkDiscoverRequestSchema = z.object({
1678
+ type: z.literal("brain.network.discover.request"),
1679
+ requestId: z.string(),
1680
+ });
1681
+ export const BrainNetworkDiscoverResponseSchema = z.object({
1682
+ type: z.literal("brain.network.discover.response"),
1683
+ payload: z.object({
1684
+ info: BrainNetworkInfoSchema.nullable(),
1685
+ error: z.string().nullable(),
1686
+ requestId: z.string(),
1687
+ }),
1688
+ });
1689
+ // --- Brain model management (runtimes, catalog, downloads, ops) ----------
1690
+ // The daemon drives these by shelling out to `otto-brain <verb> --json` (it
1691
+ // never imports the brain's runtime modules in-process). Reads (scan, catalog,
1692
+ // runtime list) are correlated request/response; long operations (pull, runtime
1693
+ // install, calibrate, sweep, bench) run as tracked JOBS the client polls via
1694
+ // brain.jobs.list. All gated by server_info.features.brainManage.
1695
+ // An installed local model, from `otto-brain scan`. Passthrough so the brain's
1696
+ // scan row can grow fields without a protocol bump.
1697
+ export const BrainInstalledModelSchema = z
1698
+ .object({
1699
+ model: z.string().default(""),
1700
+ arch: z.string().default(""),
1701
+ quant: z.string().default(""),
1702
+ size: z.string().default(""),
1703
+ ctx: z.string().default(""),
1704
+ vision: z.string().default(""),
1705
+ calibrated: z.string().default(""),
1706
+ features: z.string().default(""),
1707
+ source: z.string().default(""),
1708
+ })
1709
+ .passthrough();
1710
+ // A downloadable catalog model, annotated with whether it is already installed
1711
+ // (the daemon reuses the brain's authoritative catalog↔model join). Passthrough
1712
+ // over the catalog entry's optional metadata.
1713
+ export const BrainCatalogModelSchema = z
1714
+ .object({
1715
+ id: z.string(),
1716
+ name: z.string().default(""),
1717
+ installed: z.boolean().default(false),
1718
+ publisher: z.string().default(""),
1719
+ repo: z.string().default(""),
1720
+ quant: z.string().default(""),
1721
+ params: z.string().default(""),
1722
+ sizeBytes: z.number().nullable().optional(),
1723
+ size: z.string().default(""),
1724
+ vision: z.boolean().default(false),
1725
+ thinking: z.boolean().default(false),
1726
+ contextMax: z.number().nullable().optional(),
1727
+ tier: z.string().default(""),
1728
+ useCases: z.array(z.string()).default([]),
1729
+ why: z.string().default(""),
1730
+ })
1731
+ .passthrough();
1732
+ // An installed llama.cpp runtime, from `otto-brain runtime list`.
1733
+ export const BrainRuntimeSchema = z
1734
+ .object({
1735
+ label: z.string().default(""),
1736
+ version: z.string().default(""),
1737
+ source: z.string().default(""),
1738
+ dir: z.string().default(""),
1739
+ })
1740
+ .passthrough();
1741
+ // A tracked long-running brain operation. The client polls brain.jobs.list and
1742
+ // renders progress. `percent` is null when the job reports no measurable
1743
+ // progress (indeterminate). Terminal jobs linger briefly so the UI can show
1744
+ // the outcome before they are pruned.
1745
+ export const BrainJobKindSchema = z.enum([
1746
+ "pull",
1747
+ "runtime-install",
1748
+ "calibrate",
1749
+ "sweep",
1750
+ "bench",
1751
+ ]);
1752
+ export const BrainJobStatusSchema = z.enum(["running", "succeeded", "failed", "canceled"]);
1753
+ export const BrainJobSchema = z
1754
+ .object({
1755
+ id: z.string(),
1756
+ kind: BrainJobKindSchema,
1757
+ // A short human label, e.g. "Download Phi-4 (14B)".
1758
+ label: z.string().default(""),
1759
+ // The subject id (catalog id, model name, or build tag) this job acts on.
1760
+ target: z.string().nullable().default(null),
1761
+ status: BrainJobStatusSchema.default("running"),
1762
+ percent: z.number().nullable().default(null),
1763
+ // The latest progress line (e.g. "extracting…", "budget 512: done").
1764
+ message: z.string().nullable().default(null),
1765
+ error: z.string().nullable().default(null),
1766
+ startedAt: z.string().default(""),
1767
+ finishedAt: z.string().nullable().default(null),
1768
+ })
1769
+ .passthrough();
1770
+ // Every job-starting RPC returns the created (or refused) job under this shape.
1771
+ const BrainJobResultSchema = z.object({
1772
+ job: BrainJobSchema.nullable(),
1773
+ error: z.string().nullable(),
1774
+ requestId: z.string(),
1775
+ });
1776
+ // Every job-listing RPC returns the active + recently-finished jobs.
1777
+ const BrainJobsResultSchema = z.object({
1778
+ jobs: z.array(BrainJobSchema).default([]),
1779
+ error: z.string().nullable(),
1780
+ requestId: z.string(),
1781
+ });
1782
+ // Installed models — `otto-brain scan`.
1783
+ export const BrainModelsScanRequestSchema = z.object({
1784
+ type: z.literal("brain.models.scan.request"),
1785
+ requestId: z.string(),
1786
+ });
1787
+ export const BrainModelsScanResponseSchema = z.object({
1788
+ type: z.literal("brain.models.scan.response"),
1789
+ payload: z.object({
1790
+ models: z.array(BrainInstalledModelSchema).default([]),
1791
+ error: z.string().nullable(),
1792
+ requestId: z.string(),
1793
+ }),
1794
+ });
1795
+ // Downloadable catalog — `otto-brain catalog`.
1796
+ export const BrainCatalogListRequestSchema = z.object({
1797
+ type: z.literal("brain.catalog.list.request"),
1798
+ requestId: z.string(),
1799
+ });
1800
+ export const BrainCatalogListResponseSchema = z.object({
1801
+ type: z.literal("brain.catalog.list.response"),
1802
+ payload: z.object({
1803
+ models: z.array(BrainCatalogModelSchema).default([]),
1804
+ error: z.string().nullable(),
1805
+ requestId: z.string(),
1806
+ }),
1807
+ });
1808
+ // Installed runtimes — `otto-brain runtime list`.
1809
+ export const BrainRuntimeListRequestSchema = z.object({
1810
+ type: z.literal("brain.runtime.list.request"),
1811
+ requestId: z.string(),
1812
+ });
1813
+ export const BrainRuntimeListResponseSchema = z.object({
1814
+ type: z.literal("brain.runtime.list.response"),
1815
+ payload: z.object({
1816
+ runtimes: z.array(BrainRuntimeSchema).default([]),
1817
+ error: z.string().nullable(),
1818
+ requestId: z.string(),
1819
+ }),
1820
+ });
1821
+ // Download a catalog model — starts a `pull` job.
1822
+ export const BrainModelsPullRequestSchema = z.object({
1823
+ type: z.literal("brain.models.pull.request"),
1824
+ // Catalog id or name fragment.
1825
+ model: z.string(),
1826
+ requestId: z.string(),
1827
+ });
1828
+ export const BrainModelsPullResponseSchema = z.object({
1829
+ type: z.literal("brain.models.pull.response"),
1830
+ payload: BrainJobResultSchema,
1831
+ });
1832
+ // Install a llama.cpp runtime — starts a `runtime-install` job.
1833
+ export const BrainRuntimeInstallRequestSchema = z.object({
1834
+ type: z.literal("brain.runtime.install.request"),
1835
+ // Optional llama.cpp release build tag; null = the brain's default.
1836
+ build: z.string().nullable().default(null),
1837
+ requestId: z.string(),
1838
+ });
1839
+ export const BrainRuntimeInstallResponseSchema = z.object({
1840
+ type: z.literal("brain.runtime.install.response"),
1841
+ payload: BrainJobResultSchema,
1842
+ });
1843
+ // Measure real KV bytes/token for a model — starts a `calibrate` job. Needs a
1844
+ // runtime + GPU; refused with a helpful error otherwise.
1845
+ export const BrainCalibrateRequestSchema = z.object({
1846
+ type: z.literal("brain.calibrate.request"),
1847
+ model: z.string(),
1848
+ requestId: z.string(),
1849
+ });
1850
+ export const BrainCalibrateResponseSchema = z.object({
1851
+ type: z.literal("brain.calibrate.response"),
1852
+ payload: BrainJobResultSchema,
1853
+ });
1854
+ // Find the best reasoning budget for a model — starts a `sweep` job.
1855
+ export const BrainSweepRequestSchema = z.object({
1856
+ type: z.literal("brain.sweep.request"),
1857
+ model: z.string(),
1858
+ requestId: z.string(),
1859
+ });
1860
+ export const BrainSweepResponseSchema = z.object({
1861
+ type: z.literal("brain.sweep.response"),
1862
+ payload: BrainJobResultSchema,
1863
+ });
1864
+ // Run the agentic-coding benchmark — starts a `bench` job. `model` is an
1865
+ // optional comma list of name fragments; null lets the brain pick.
1866
+ export const BrainBenchRequestSchema = z.object({
1867
+ type: z.literal("brain.bench.request"),
1868
+ model: z.string().nullable().default(null),
1869
+ requestId: z.string(),
1870
+ });
1871
+ export const BrainBenchResponseSchema = z.object({
1872
+ type: z.literal("brain.bench.response"),
1873
+ payload: BrainJobResultSchema,
1874
+ });
1875
+ // Poll the active + recently-finished jobs.
1876
+ export const BrainJobsListRequestSchema = z.object({
1877
+ type: z.literal("brain.jobs.list.request"),
1878
+ requestId: z.string(),
1879
+ });
1880
+ export const BrainJobsListResponseSchema = z.object({
1881
+ type: z.literal("brain.jobs.list.response"),
1882
+ payload: BrainJobsResultSchema,
1883
+ });
1884
+ // Cancel a running job; returns the refreshed job list.
1885
+ export const BrainJobsCancelRequestSchema = z.object({
1886
+ type: z.literal("brain.jobs.cancel.request"),
1887
+ jobId: z.string(),
1888
+ requestId: z.string(),
1889
+ });
1890
+ export const BrainJobsCancelResponseSchema = z.object({
1891
+ type: z.literal("brain.jobs.cancel.response"),
1892
+ payload: BrainJobsResultSchema,
1893
+ });
1894
+ // --- Hugging Face discovery (search + add arbitrary repos) ---------------
1895
+ // The daemon shells out to `otto-brain search`/`add --json`. Reads are
1896
+ // correlated request/response; the download runs as a `pull` job. Gated by
1897
+ // server_info.features.brainHfSearch. The brain resolves its own HF token (env
1898
+ // HF_TOKEN or its config), so no secret crosses this boundary.
1899
+ // One GGUF repo from a Hugging Face search. Passthrough so the brain's search
1900
+ // row can grow fields without a protocol bump.
1901
+ export const BrainHfSearchResultSchema = z
1902
+ .object({
1903
+ repo: z.string().default(""),
1904
+ downloads: z.number().default(0),
1905
+ likes: z.number().default(0),
1906
+ gated: z.boolean().default(false),
1907
+ // True when any quant of this repo is already on disk.
1908
+ installed: z.boolean().default(false),
1909
+ })
1910
+ .passthrough();
1911
+ // One downloadable quantization of a repo — `otto-brain add <repo> --list-quants`.
1912
+ export const BrainRepoQuantSchema = z
1913
+ .object({
1914
+ quant: z.string().default(""),
1915
+ size: z.string().default(""),
1916
+ sizeBytes: z.number().default(0),
1917
+ files: z.number().default(0),
1918
+ // True when this specific quant is already on disk.
1919
+ installed: z.boolean().default(false),
1920
+ })
1921
+ .passthrough();
1922
+ // Search Hugging Face for GGUF models — `otto-brain search <query>`.
1923
+ export const BrainHfSearchRequestSchema = z.object({
1924
+ type: z.literal("brain.hf.search.request"),
1925
+ query: z.string(),
1926
+ limit: z.number().nullable().default(null),
1927
+ requestId: z.string(),
1928
+ });
1929
+ export const BrainHfSearchResponseSchema = z.object({
1930
+ type: z.literal("brain.hf.search.response"),
1931
+ payload: z.object({
1932
+ results: z.array(BrainHfSearchResultSchema).default([]),
1933
+ error: z.string().nullable(),
1934
+ requestId: z.string(),
1935
+ }),
1936
+ });
1937
+ // List the quantizations a repo offers — `otto-brain add <repo> --list-quants`.
1938
+ export const BrainHfQuantsRequestSchema = z.object({
1939
+ type: z.literal("brain.hf.quants.request"),
1940
+ repo: z.string(),
1941
+ requestId: z.string(),
1942
+ });
1943
+ export const BrainHfQuantsResponseSchema = z.object({
1944
+ type: z.literal("brain.hf.quants.response"),
1945
+ payload: z.object({
1946
+ quants: z.array(BrainRepoQuantSchema).default([]),
1947
+ error: z.string().nullable(),
1948
+ requestId: z.string(),
1949
+ }),
1950
+ });
1951
+ // Download a chosen quant of an arbitrary HF repo — starts a `pull` job.
1952
+ export const BrainModelsAddRequestSchema = z.object({
1953
+ type: z.literal("brain.models.add.request"),
1954
+ repo: z.string(),
1955
+ quant: z.string(),
1956
+ requestId: z.string(),
1957
+ });
1958
+ export const BrainModelsAddResponseSchema = z.object({
1959
+ type: z.literal("brain.models.add.response"),
1960
+ payload: BrainJobResultSchema,
1961
+ });
1316
1962
  export const ProjectRenameRequestSchema = z.object({
1317
1963
  type: z.literal("project.rename.request"),
1318
1964
  projectId: z.string(),
@@ -1656,6 +2302,33 @@ export const SetDaemonConfigRequestMessageSchema = z.object({
1656
2302
  requestId: z.string(),
1657
2303
  config: MutableDaemonConfigPatchSchema,
1658
2304
  });
2305
+ // Connectors — MCP servers surfaced as named, toggle-able integrations. The
2306
+ // registry itself (add/remove/enable/disable a connector or an individual tool)
2307
+ // lives in daemon config and is edited via set_daemon_config's `connectors`
2308
+ // patch. The one thing config can't answer is what tools a connector actually
2309
+ // exposes, which needs a live connect + listTools — that is this RPC. Gated by
2310
+ // features.connectors.
2311
+ export const ConnectorsListToolsRequestSchema = z.object({
2312
+ type: z.literal("connectors.list_tools.request"),
2313
+ requestId: z.string(),
2314
+ connectorId: z.string(),
2315
+ });
2316
+ export const ConnectorsListToolsResponseSchema = z.object({
2317
+ type: z.literal("connectors.list_tools.response"),
2318
+ payload: z.object({
2319
+ connectorId: z.string(),
2320
+ tools: z
2321
+ .array(z.object({
2322
+ name: z.string(),
2323
+ description: z.string().nullable().default(null),
2324
+ disabled: z.boolean().default(false),
2325
+ }))
2326
+ .default([]),
2327
+ // Non-null when the connector could not be reached / enumerated.
2328
+ error: z.string().nullable(),
2329
+ requestId: z.string(),
2330
+ }),
2331
+ });
1659
2332
  export const SpeechSettingsGetOptionsRequestSchema = z.object({
1660
2333
  type: z.literal("speech.settings.get_options.request"),
1661
2334
  requestId: z.string(),
@@ -1737,6 +2410,29 @@ export const AgentPersonalitiesGetStatsRequestSchema = z.object({
1737
2410
  type: z.literal("agentPersonalities.get_stats.request"),
1738
2411
  requestId: z.string(),
1739
2412
  });
2413
+ // COMPAT(personalityProfile): added in v0.7.5; gate lives in
2414
+ // features.personalityProfile. Author a personality PROFILE (the prose
2415
+ // `personalityPrompt` that shapes how an agent behaves) from the only things
2416
+ // the editor knows before one exists: the handle, the roles it will be spawned
2417
+ // for, and its two spinner colors. Like the voice-cue RPC this is described
2418
+ // inline (not a stored id) so the editor can generate for an unsaved draft, and
2419
+ // is an editor-time action: the result lands in the prompt field for the user to
2420
+ // edit, and is stored on the personality by the ordinary save.
2421
+ export const AgentPersonalitiesGenerateProfileRequestSchema = z.object({
2422
+ type: z.literal("agentPersonalities.generate_profile.request"),
2423
+ requestId: z.string(),
2424
+ name: z.string(),
2425
+ // Permissive strings to match the stored personality shape (forward-compatible
2426
+ // with roles this daemon predates); the daemon filters to its known set.
2427
+ roles: z.array(z.string().min(1)).optional(),
2428
+ // The spinner glow pair, read as a palette (temperature, energy) and never
2429
+ // quoted literally in the profile.
2430
+ glowA: z.string().optional(),
2431
+ glowB: z.string().optional(),
2432
+ // Scopes provider resolution to a workspace; omitted falls back to any
2433
+ // resolvable one.
2434
+ cwd: z.string().optional(),
2435
+ });
1740
2436
  export const ReadProjectConfigRequestMessageSchema = z.object({
1741
2437
  type: z.literal("read_project_config_request"),
1742
2438
  requestId: z.string(),
@@ -2143,6 +2839,27 @@ export const AgentDetachResponseMessageSchema = z.object({
2143
2839
  type: z.literal("agent.detach.response"),
2144
2840
  payload: AgentActionResponsePayloadSchema,
2145
2841
  });
2842
+ // Move a chat into another workspace, in the same project or a different one.
2843
+ //
2844
+ // Ownership is one field (see the agent snapshot's `workspaceId`), independent of
2845
+ // cwd, so re-stamping it is all a move takes: nothing on disk is keyed by
2846
+ // workspace, and clients derive which workspace shows a chat from that field
2847
+ // alone.
2848
+ //
2849
+ // The chat's `cwd` does not change, and does not have to match the target
2850
+ // workspace's directory. The daemon has never required those to agree (an
2851
+ // agent's cwd can already be a subdirectory of its workspace, and nothing
2852
+ // validates one against the other), and a session already rooted on disk cannot
2853
+ // honestly be re-rooted. So a moved chat keeps running where it was started.
2854
+ //
2855
+ // Gated by server_info.features.agentWorkspaceTransfer.
2856
+ export const AgentWorkspaceTransferRequestMessageSchema = z.object({
2857
+ type: z.literal("agent.workspace.transfer.request"),
2858
+ agentId: z.string(),
2859
+ // The workspace that should own the chat from now on.
2860
+ workspaceId: z.string(),
2861
+ requestId: z.string(),
2862
+ });
2146
2863
  // Stop a running observed subagent (Claude Task / ultracode fan-out). The
2147
2864
  // agentId is the observed subagent's id; the daemon resolves it to the owning
2148
2865
  // provider session's task and calls stopTask. Only observed subagents accept
@@ -2767,6 +3484,20 @@ export const WorkspaceTitleSetResponseSchema = z.object({
2767
3484
  type: z.literal("workspace.title.set.response"),
2768
3485
  payload: WorkspaceTitleSetResponsePayloadSchema,
2769
3486
  });
3487
+ export const AgentWorkspaceTransferResponsePayloadSchema = z.object({
3488
+ requestId: z.string(),
3489
+ agentId: z.string(),
3490
+ // Which workspace owns the chat now: the requested one when accepted, the one
3491
+ // it never left when refused. Lets a client correct its optimistic state from
3492
+ // the response alone.
3493
+ workspaceId: z.string().nullable(),
3494
+ accepted: z.boolean(),
3495
+ error: z.string().nullable(),
3496
+ });
3497
+ export const AgentWorkspaceTransferResponseMessageSchema = z.object({
3498
+ type: z.literal("agent.workspace.transfer.response"),
3499
+ payload: AgentWorkspaceTransferResponsePayloadSchema,
3500
+ });
2770
3501
  export const SetVoiceModeResponseMessageSchema = z.object({
2771
3502
  type: z.literal("set_voice_mode_response"),
2772
3503
  payload: z.object({
@@ -3567,6 +4298,11 @@ export const WorkspaceArchivePreflightRequestSchema = z.object({
3567
4298
  requestId: z.string(),
3568
4299
  workspaceId: z.string(),
3569
4300
  });
4301
+ // Where the Changes view's base branch came from. Surfaced so the chip can say *why* it is
4302
+ // comparing against this branch: an inferred parent is a heuristic over a graph that does not
4303
+ // record the answer, and it has to look like one or a wrong guess reads as a bug in the diff.
4304
+ // COMPAT(checkoutDiffBaseAnyRepo): added in v0.7.4.
4305
+ export const CheckoutBaseSourceSchema = z.enum(["user", "inferred", "worktree", "default"]);
3570
4306
  // Repoint a worktree-backed workspace's base branch — what the Changes view diffs
3571
4307
  // against, and what merge-into-base and PR creation target. On a stacked branch the
3572
4308
  // useful base is the parent branch, not the repo default, the same way a forge PR
@@ -3576,8 +4312,14 @@ export const WorktreeBaseRefSetRequestSchema = z.object({
3576
4312
  type: z.literal("worktree.baseRef.set.request"),
3577
4313
  requestId: z.string(),
3578
4314
  workspaceId: z.string(),
3579
- // Branch name, with or without an `origin/` prefix; null resets to the default branch.
4315
+ // Branch name; null resets to the default branch. An `origin/` prefix is meaningful and is
4316
+ // kept — `main` and `origin/main` are different comparisons whenever the two have drifted.
3580
4317
  baseRef: z.string().nullable(),
4318
+ // Forget the remembered base and detect the branch's parent again, ignoring `baseRef`.
4319
+ // The escape hatch for a wrong guess: parent detection is a heuristic over a graph that does
4320
+ // not record the answer, and the result is sticky, so it has to be re-runnable on demand.
4321
+ // COMPAT(checkoutDiffBaseAnyRepo): added in v0.7.4.
4322
+ redetect: z.boolean().optional(),
3581
4323
  });
3582
4324
  // Create a new workspace record. Unlike open_project, this never deduplicates by
3583
4325
  // directory: it always produces a fresh workspace. The source discriminates
@@ -4239,6 +4981,28 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4239
4981
  HistoryAgentsClearArchivedRequestSchema,
4240
4982
  AttachmentsImagesStatsRequestSchema,
4241
4983
  AttachmentsImagesClearRequestSchema,
4984
+ BrainHostStatusRequestSchema,
4985
+ BrainHostStartRequestSchema,
4986
+ BrainHostStopRequestSchema,
4987
+ BrainHostRestartRequestSchema,
4988
+ BrainEvalsGetRequestSchema,
4989
+ BrainNetworkDiscoverRequestSchema,
4990
+ BrainModelsListRequestSchema,
4991
+ BrainRemoteConfigGetRequestSchema,
4992
+ BrainRemoteConfigPatchRequestSchema,
4993
+ BrainModelsScanRequestSchema,
4994
+ BrainCatalogListRequestSchema,
4995
+ BrainRuntimeListRequestSchema,
4996
+ BrainModelsPullRequestSchema,
4997
+ BrainRuntimeInstallRequestSchema,
4998
+ BrainCalibrateRequestSchema,
4999
+ BrainSweepRequestSchema,
5000
+ BrainBenchRequestSchema,
5001
+ BrainJobsListRequestSchema,
5002
+ BrainJobsCancelRequestSchema,
5003
+ BrainHfSearchRequestSchema,
5004
+ BrainHfQuantsRequestSchema,
5005
+ BrainModelsAddRequestSchema,
4242
5006
  UpdateAgentRequestMessageSchema,
4243
5007
  ProjectRenameRequestSchema,
4244
5008
  ProjectRemoveRequestSchema,
@@ -4254,12 +5018,14 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4254
5018
  DiagnosticsRequestSchema,
4255
5019
  GetDaemonConfigRequestMessageSchema,
4256
5020
  SetDaemonConfigRequestMessageSchema,
5021
+ ConnectorsListToolsRequestSchema,
4257
5022
  SpeechSettingsGetOptionsRequestSchema,
4258
5023
  SpeechTtsPreviewRequestSchema,
4259
5024
  SpeechTtsSpeakRequestSchema,
4260
5025
  SpeechTtsSpeakCancelRequestSchema,
4261
5026
  VisualizerVoiceCuesGenerateRequestSchema,
4262
5027
  AgentPersonalitiesGetStatsRequestSchema,
5028
+ AgentPersonalitiesGenerateProfileRequestSchema,
4263
5029
  ReadProjectConfigRequestMessageSchema,
4264
5030
  WriteProjectConfigRequestMessageSchema,
4265
5031
  DictationStreamStartMessageSchema,
@@ -4301,6 +5067,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4301
5067
  SetAgentThinkingRequestMessageSchema,
4302
5068
  SetAgentFeatureRequestMessageSchema,
4303
5069
  AgentDetachRequestMessageSchema,
5070
+ AgentWorkspaceTransferRequestMessageSchema,
4304
5071
  AgentSubagentStopRequestMessageSchema,
4305
5072
  AgentBackgroundTaskStopRequestMessageSchema,
4306
5073
  AgentBackgroundTaskClearRequestMessageSchema,
@@ -4632,6 +5399,41 @@ export const ServerInfoStatusPayloadSchema = z
4632
5399
  daemonDiagnostics: z.boolean().optional(),
4633
5400
  // COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
4634
5401
  daemonSelfUpdate: z.boolean().optional(),
5402
+ // Daemon manages the local AI host (otto-brain) as a child: reports
5403
+ // brain.host.status, serves brain.host.start/stop/restart, exposes the
5404
+ // editable `brain` config block, and honors kill-on-shutdown. Without it
5405
+ // the Local brain host UI is hidden ("update the host").
5406
+ // COMPAT(brainControl): added in v0.7.5, remove gate after 2026-01-30 once daemon floor >= v0.7.5.
5407
+ brainControl: z.boolean().optional(),
5408
+ // Daemon streams the brain's live status/telemetry via
5409
+ // subscribe_brain_status + brain_status_changed, and serves brain.evals.get.
5410
+ // Separate from brainControl because status/eval watching can ship after
5411
+ // lifecycle control. Without it the Brain dashboard falls back to a
5412
+ // periodic brain.host.status poll (no live feed, no eval charts).
5413
+ // COMPAT(brainStatus): added in v0.7.5, remove gate after 2026-01-30 once daemon floor >= v0.7.5.
5414
+ brainStatus: z.boolean().optional(),
5415
+ // Daemon serves brain.network.discover: enumerates this host's bind
5416
+ // addresses and probes the local `tailscale` CLI, so the client can
5417
+ // offer a listen-host pick-list and auto-fill the tailscale TLS mode.
5418
+ // COMPAT(brainNetworkDiscovery): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5419
+ brainNetworkDiscovery: z.boolean().optional(),
5420
+ // Daemon can point the brain at a remote host (brain.mode "remote"):
5421
+ // status/evals/config proxied from another Otto's brain, no local spawn.
5422
+ // COMPAT(brainRemote): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5423
+ brainRemote: z.boolean().optional(),
5424
+ // Daemon manages the brain's models and runtimes by shelling out to the
5425
+ // otto-brain CLI: serves brain.models.scan / brain.catalog.list /
5426
+ // brain.runtime.list (reads) and starts brain.models.pull /
5427
+ // brain.runtime.install / brain.calibrate / brain.sweep / brain.bench as
5428
+ // tracked jobs polled via brain.jobs.list. Without it the Brain "Models"
5429
+ // and "Operations" sections are hidden ("update the host").
5430
+ // COMPAT(brainManage): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5431
+ brainManage: z.boolean().optional(),
5432
+ // Daemon serves brain.hf.search / brain.hf.quants (reads) and starts
5433
+ // brain.models.add (download an arbitrary HF repo's quant) as a pull job.
5434
+ // Without it the Brain "Models" section hides the Hugging Face search box.
5435
+ // COMPAT(brainHfSearch): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5436
+ brainHfSearch: z.boolean().optional(),
4635
5437
  // COMPAT(agentForkContext): added in v0.1.102, remove gate after 2026-12-28.
4636
5438
  agentForkContext: z.boolean().optional(),
4637
5439
  // COMPAT(providerRemove): added in v0.1.105, drop the gate when daemon floor >= v0.1.105.
@@ -4732,6 +5534,10 @@ export const ServerInfoStatusPayloadSchema = z
4732
5534
  ttsSpeak: z.boolean().optional(),
4733
5535
  // COMPAT(visualizerVoiceCues): added in v0.6.3, drop the gate when daemon floor >= v0.6.3.
4734
5536
  visualizerVoiceCues: z.boolean().optional(),
5537
+ // COMPAT(personalityProfile): added in v0.7.5, drop the gate when daemon floor >= v0.7.5.
5538
+ // Host can author a personality profile (the prompt prose) from a name,
5539
+ // roles, and spinner colors.
5540
+ personalityProfile: z.boolean().optional(),
4735
5541
  // COMPAT(setAgentPersonality): added in v0.5.0, drop the gate when daemon floor >= v0.5.0.
4736
5542
  setAgentPersonality: z.boolean().optional(),
4737
5543
  // COMPAT(checkoutGitCommit): added in v0.5.1, drop the gate when daemon floor >= v0.5.1.
@@ -4763,6 +5569,13 @@ export const ServerInfoStatusPayloadSchema = z
4763
5569
  // the daemon can write the worktree's metadata.
4764
5570
  // COMPAT(worktreeDiffBase): added in v0.6.8, drop the gate when daemon floor >= v0.6.8.
4765
5571
  worktreeDiffBase: z.boolean().optional(),
5572
+ // Set when the daemon stores the diff base *per branch*, which is what lets any git
5573
+ // checkout repoint it rather than only an Otto worktree — a plain checkout's gitdir is
5574
+ // shared by every branch in it, so a single stored base would bleed across branch
5575
+ // switches. Also gates parent-branch detection, the `origin/`-qualified pin, and the
5576
+ // re-detect action. Without it the client keeps the worktree-only picker.
5577
+ // COMPAT(checkoutDiffBaseAnyRepo): added in v0.7.4, drop the gate when daemon floor >= v0.7.4.
5578
+ checkoutDiffBaseAnyRepo: z.boolean().optional(),
4766
5579
  // Set when the daemon persists the host-level hideMergeIntoBaseAction
4767
5580
  // workspace policy (read/written via the daemon config RPCs). Without
4768
5581
  // it the client hides the Workspaces toggle, since patching the field
@@ -4818,11 +5631,23 @@ export const ServerInfoStatusPayloadSchema = z
4818
5631
  // group regardless, so the client hides the categorized section instead
4819
5632
  // of showing category switches that do nothing.
4820
5633
  mcpToolGroups: z.boolean().optional(),
5634
+ // COMPAT(connectors): added in v0.7.5, drop the gate when daemon floor >= v0.7.5.
5635
+ // Set when the daemon persists and honors `connectors` — MCP servers
5636
+ // surfaced as named, toggle-able integrations with per-tool disable,
5637
+ // enforced today on the openai-compat path. Old daemons ignore the
5638
+ // section, so the client hides the Connectors settings entirely.
5639
+ connectors: z.boolean().optional(),
4821
5640
  // COMPAT(agentBehaviorToggles): added in v0.6.4, drop the gate when daemon floor >= v0.6.4.
4822
5641
  // Set when the daemon persists `agentBehaviors.*` (promptSuggestions,
4823
5642
  // agentProgressSummaries, notifyOnFinishDefault). The reads are wired by
4824
5643
  // Claude-tier providers (WP-E); the client gates the toggle cards on this.
4825
5644
  agentBehaviorToggles: z.boolean().optional(),
5645
+ // COMPAT(todoReminders): added in v0.7.5, drop the gate when daemon floor >= v0.7.5.
5646
+ // Set when the daemon acts on `agentBehaviors.{todoNudge,todoReconcileOnIdle}` —
5647
+ // the provider-agnostic stale-todo nudge (next turn) and idle reconcile pass.
5648
+ // The client gates the task-list toggle cards on this so an old daemon never
5649
+ // shows switches that do nothing.
5650
+ todoReminders: z.boolean().optional(),
4826
5651
  // COMPAT(metadataGenerationEnabled): added in v0.6.4, drop the gate when daemon floor >= v0.6.4.
4827
5652
  // Set when the daemon persists `metadataGeneration.{enabled,preferWriterPersonalities}`.
4828
5653
  // The generation path (WP-B) reads them; the client gates the toggle cards on this.
@@ -4868,6 +5693,12 @@ export const ServerInfoStatusPayloadSchema = z
4868
5693
  // the daemon half of the Storage section; the app-side preview cache row
4869
5694
  // is local and always shown.
4870
5695
  attachmentStorage: z.boolean().optional(),
5696
+ // COMPAT(agentWorkspaceTransfer): added in v0.7.4, drop the gate when
5697
+ // daemon floor >= v0.7.4. Set when the daemon serves
5698
+ // `agent.workspace.transfer` — moving a chat to another workspace over
5699
+ // the same directory. The client cannot restamp ownership itself (it is
5700
+ // daemon state), so an old daemon simply does not get the menu item.
5701
+ agentWorkspaceTransfer: z.boolean().optional(),
4871
5702
  })
4872
5703
  .optional(),
4873
5704
  })
@@ -5572,6 +6403,9 @@ export const WorktreeBaseRefSetResponseSchema = z.object({
5572
6403
  baseRef: z.string().nullable(),
5573
6404
  // The stored base is the repository default branch (no stacked-branch override).
5574
6405
  isDefault: z.boolean(),
6406
+ // Where the resulting base came from, so the client can label it without a refetch.
6407
+ // COMPAT(checkoutDiffBaseAnyRepo): added in v0.7.4.
6408
+ baseSource: CheckoutBaseSourceSchema.optional(),
5575
6409
  error: z.string().nullable(),
5576
6410
  }),
5577
6411
  });
@@ -5890,6 +6724,19 @@ export const AgentPersonalitiesGetStatsResponseSchema = z.object({
5890
6724
  })
5891
6725
  .passthrough(),
5892
6726
  });
6727
+ export const AgentPersonalitiesGenerateProfileResponseSchema = z.object({
6728
+ type: z.literal("agentPersonalities.generate_profile.response"),
6729
+ payload: z
6730
+ .object({
6731
+ requestId: z.string(),
6732
+ // The authored personality prompt, ready to drop into the editor's prompt
6733
+ // field. Absent when generation failed (see error) or no writer/provider
6734
+ // resolves on this host.
6735
+ profile: z.string().optional(),
6736
+ error: z.string().optional(),
6737
+ })
6738
+ .passthrough(),
6739
+ });
5893
6740
  export const DaemonGetStatusResponseSchema = z.object({
5894
6741
  type: z.literal("daemon.get_status.response"),
5895
6742
  payload: z
@@ -6048,6 +6895,15 @@ const CheckoutStatusCommonSchema = z.object({
6048
6895
  // COMPAT(checkoutStatusGitStateAt): added in v0.6.8; absent from older daemons.
6049
6896
  // Drop the optional marker when floor >= v0.6.8 (target 2027-01-24).
6050
6897
  gitStateAt: z.number().optional(),
6898
+ // Where `baseRef` came from, so the base chip can label an inferred parent as a guess rather
6899
+ // than presenting it with the same authority as an explicit pick.
6900
+ // COMPAT(checkoutDiffBaseAnyRepo): added in v0.7.4.
6901
+ baseSource: CheckoutBaseSourceSchema.optional(),
6902
+ // Whether this checkout can have its base repointed. True for any git checkout on a daemon
6903
+ // that stores the base per branch; older daemons only supported Otto worktrees, which the
6904
+ // client inferred from isOttoOwnedWorktree.
6905
+ // COMPAT(checkoutDiffBaseAnyRepo): added in v0.7.4.
6906
+ isBaseEditable: z.boolean().optional(),
6051
6907
  });
6052
6908
  const CheckoutStatusNotGitSchema = CheckoutStatusCommonSchema.extend({
6053
6909
  isGit: z.literal(false),
@@ -7957,12 +8813,14 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
7957
8813
  DiagnosticsResponseSchema,
7958
8814
  GetDaemonConfigResponseMessageSchema,
7959
8815
  SetDaemonConfigResponseMessageSchema,
8816
+ ConnectorsListToolsResponseSchema,
7960
8817
  SpeechSettingsGetOptionsResponseSchema,
7961
8818
  SpeechTtsPreviewResponseSchema,
7962
8819
  SpeechTtsSpeakResponseSchema,
7963
8820
  SpeechTtsSpeakCancelResponseSchema,
7964
8821
  VisualizerVoiceCuesGenerateResponseSchema,
7965
8822
  AgentPersonalitiesGetStatsResponseSchema,
8823
+ AgentPersonalitiesGenerateProfileResponseSchema,
7966
8824
  ReadProjectConfigResponseMessageSchema,
7967
8825
  WriteProjectConfigResponseMessageSchema,
7968
8826
  SetAgentModeResponseMessageSchema,
@@ -7970,6 +8828,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
7970
8828
  SetAgentThinkingResponseMessageSchema,
7971
8829
  SetAgentFeatureResponseMessageSchema,
7972
8830
  AgentDetachResponseMessageSchema,
8831
+ AgentWorkspaceTransferResponseMessageSchema,
7973
8832
  AgentQueueRemoveResponseMessageSchema,
7974
8833
  AgentQueueReorderResponseMessageSchema,
7975
8834
  AgentQueueClearResponseMessageSchema,
@@ -7999,6 +8858,28 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
7999
8858
  HistoryAgentsClearArchivedResponseSchema,
8000
8859
  AttachmentsImagesStatsResponseSchema,
8001
8860
  AttachmentsImagesClearResponseSchema,
8861
+ BrainHostStatusResponseSchema,
8862
+ BrainHostStartResponseSchema,
8863
+ BrainHostStopResponseSchema,
8864
+ BrainHostRestartResponseSchema,
8865
+ BrainEvalsGetResponseSchema,
8866
+ BrainNetworkDiscoverResponseSchema,
8867
+ BrainModelsListResponseSchema,
8868
+ BrainRemoteConfigGetResponseSchema,
8869
+ BrainRemoteConfigPatchResponseSchema,
8870
+ BrainModelsScanResponseSchema,
8871
+ BrainCatalogListResponseSchema,
8872
+ BrainRuntimeListResponseSchema,
8873
+ BrainModelsPullResponseSchema,
8874
+ BrainRuntimeInstallResponseSchema,
8875
+ BrainCalibrateResponseSchema,
8876
+ BrainSweepResponseSchema,
8877
+ BrainBenchResponseSchema,
8878
+ BrainJobsListResponseSchema,
8879
+ BrainJobsCancelResponseSchema,
8880
+ BrainHfSearchResponseSchema,
8881
+ BrainHfQuantsResponseSchema,
8882
+ BrainModelsAddResponseSchema,
8002
8883
  AgentArchivedMessageSchema,
8003
8884
  CloseItemsResponseSchema,
8004
8885
  CheckoutStatusResponseSchema,