@otto-code/protocol 0.7.4 → 0.7.6

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,161 @@ 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
+ // SHA-256 fingerprint of the remote brain's TLS certificate (openssl's
435
+ // "AB:CD:..." form; colons optional). When set, the daemon pins HTTPS
436
+ // connections to exactly this certificate instead of the system trust
437
+ // store — required for a brain serving tls.mode=self-signed. When null,
438
+ // the certificate must validate against the system trust store.
439
+ certFingerprint: z.string().nullable().default(null),
440
+ })
441
+ .passthrough();
442
+ export const MutableBrainConfigSchema = z
443
+ .object({
444
+ enabled: z.boolean().default(false),
445
+ autoStart: z.boolean().default(false),
446
+ // "local": the daemon spawns and supervises the brain on this host.
447
+ // "remote": the daemon connects to a brain running on another Otto host
448
+ // (read-only: status/evals/config, no lifecycle). Gated by features.brainRemote.
449
+ mode: z.enum(["local", "remote"]).default("local"),
450
+ remote: MutableBrainRemoteConfigSchema.default({
451
+ host: "",
452
+ port: 1234,
453
+ secure: false,
454
+ authToken: null,
455
+ certFingerprint: null,
456
+ }),
457
+ listen: z
458
+ .object({
459
+ host: z.string().default("127.0.0.1"),
460
+ port: z.number().int().default(1234),
461
+ })
462
+ .passthrough()
463
+ .default({ host: "127.0.0.1", port: 1234 }),
464
+ defaultModel: z.string().nullable().default(null),
465
+ // Pin the host to one model: serve only the default/resident model and
466
+ // refuse completion requests that ask for a different one.
467
+ lockModel: z.boolean().default(false),
468
+ // Sharing gates (off by default). allowRemoteConfig: key holders may CHANGE
469
+ // config over the network (POST /__host/config), not just use it.
470
+ // allowInsecureBind: permit a non-loopback bind with no token (open share).
471
+ allowRemoteConfig: z.boolean().default(false),
472
+ allowInsecureBind: z.boolean().default(false),
473
+ authMode: z.enum(["none", "token"]).default("none"),
474
+ // Secret: masked with DAEMON_CONFIG_SECRET_SENTINEL on the way out; an
475
+ // unchanged sentinel is stripped from inbound patches.
476
+ authToken: z.string().nullable().default(null),
477
+ tls: MutableBrainTlsConfigSchema.default({
478
+ mode: "off",
479
+ certFile: null,
480
+ keyFile: null,
481
+ hostname: null,
482
+ certDir: null,
483
+ renewBeforeDays: 21,
484
+ }),
485
+ })
486
+ .passthrough();
487
+ // The brain PATCH schema — deliberately NOT `MutableBrainConfigSchema.partial()`.
488
+ // Every field of the full schema carries a `.default()` (so an old daemon's
489
+ // half-written config still parses as a well-formed OFF section), and Zod keeps
490
+ // those defaults through `.partial()`: `MutableBrainConfigSchema.partial().parse(
491
+ // { allowRemoteConfig: true })` expands to the FULL object with every other field
492
+ // defaulted. The daemon deep-merges the parsed patch over the stored config, so a
493
+ // single-field patch would silently reset the entire brain block to defaults —
494
+ // turning sharing off (host back to loopback), wiping the auth token, and
495
+ // disabling the server. Mirroring the shape WITHOUT defaults keeps an omitted
496
+ // field omitted, so the deep-merge preserves it. Every level is deep-partial so a
497
+ // nested patch (e.g. just `listen.host`) preserves its siblings too. Keep the
498
+ // field set in sync with MutableBrainConfigSchema; `.passthrough()` carries any
499
+ // field a newer daemon adds through untouched in the meantime.
500
+ const MutableBrainTlsPatchSchema = z
501
+ .object({
502
+ mode: z.enum(["off", "files", "self-signed", "tailscale"]),
503
+ certFile: z.string().nullable(),
504
+ keyFile: z.string().nullable(),
505
+ hostname: z.string().nullable(),
506
+ certDir: z.string().nullable(),
507
+ renewBeforeDays: z.number().int().min(1),
508
+ })
509
+ .partial()
510
+ .passthrough();
511
+ const MutableBrainRemotePatchSchema = z
512
+ .object({
513
+ host: z.string(),
514
+ port: z.number().int(),
515
+ secure: z.boolean(),
516
+ authToken: z.string().nullable(),
517
+ certFingerprint: z.string().nullable(),
518
+ })
519
+ .partial()
520
+ .passthrough();
521
+ const MutableBrainListenPatchSchema = z
522
+ .object({
523
+ host: z.string(),
524
+ port: z.number().int(),
525
+ })
526
+ .partial()
527
+ .passthrough();
528
+ export const MutableBrainConfigPatchSchema = z
529
+ .object({
530
+ enabled: z.boolean(),
531
+ autoStart: z.boolean(),
532
+ mode: z.enum(["local", "remote"]),
533
+ remote: MutableBrainRemotePatchSchema,
534
+ listen: MutableBrainListenPatchSchema,
535
+ defaultModel: z.string().nullable(),
536
+ lockModel: z.boolean(),
537
+ allowRemoteConfig: z.boolean(),
538
+ allowInsecureBind: z.boolean(),
539
+ authMode: z.enum(["none", "token"]),
540
+ authToken: z.string().nullable(),
541
+ tls: MutableBrainTlsPatchSchema,
542
+ })
543
+ .partial()
544
+ .passthrough();
545
+ export const DEFAULT_MUTABLE_BRAIN_CONFIG = {
546
+ enabled: false,
547
+ autoStart: false,
548
+ mode: "local",
549
+ remote: { host: "", port: 1234, secure: false, authToken: null, certFingerprint: null },
550
+ listen: { host: "127.0.0.1", port: 1234 },
551
+ defaultModel: null,
552
+ lockModel: false,
553
+ allowRemoteConfig: false,
554
+ allowInsecureBind: false,
555
+ authMode: "none",
556
+ authToken: null,
557
+ tls: {
558
+ mode: "off",
559
+ certFile: null,
560
+ keyFile: null,
561
+ hostname: null,
562
+ certDir: null,
563
+ renewBeforeDays: 21,
564
+ },
565
+ };
400
566
  export const MutableDaemonConfigSchema = z
401
567
  .object({
402
568
  mcp: z
@@ -418,6 +584,8 @@ export const MutableDaemonConfigSchema = z
418
584
  promptSuggestions: true,
419
585
  agentProgressSummaries: true,
420
586
  notifyOnFinishDefault: true,
587
+ todoNudge: true,
588
+ todoReconcileOnIdle: true,
421
589
  }),
422
590
  providers: z.record(z.string(), MutableDaemonProviderConfigSchema).default({}),
423
591
  metadataGeneration: MutableMetadataGenerationConfigSchema.default({
@@ -481,6 +649,15 @@ export const MutableDaemonConfigSchema = z
481
649
  maxRunningProbes: 2,
482
650
  idleMinutes: 10,
483
651
  }),
652
+ // Local AI host (otto-brain) management. Gated by server_info features.brainControl;
653
+ // defaults OFF and well-formed so a new client parsing an old daemon's config renders
654
+ // the row without ever implying the brain is running.
655
+ brain: MutableBrainConfigSchema.default(DEFAULT_MUTABLE_BRAIN_CONFIG),
656
+ // Host-wide connector registry (MCP servers surfaced as named, toggle-able
657
+ // integrations). Gated by server_info features.connectors; defaults to an
658
+ // empty roster so a new client parsing an old daemon's config still sees a
659
+ // well-formed section.
660
+ connectors: z.array(ConnectorConfigSchema).default([]),
484
661
  })
485
662
  .passthrough();
486
663
  export const MutableDaemonConfigPatchSchema = z
@@ -495,6 +672,7 @@ export const MutableDaemonConfigPatchSchema = z
495
672
  providers: z
496
673
  .record(z.string(), MutableDaemonProviderConfigSchema.partial().passthrough().nullable())
497
674
  .optional(),
675
+ removeProviders: z.array(z.string().min(1)).optional(),
498
676
  metadataGeneration: MutableMetadataGenerationConfigSchema.partial().optional(),
499
677
  autoArchiveAfterMerge: z.boolean().optional(),
500
678
  // Gated by server_info features.hideMergeIntoBaseSetting.
@@ -529,6 +707,15 @@ export const MutableDaemonConfigPatchSchema = z
529
707
  lsp: MutableLspConfigSchema.partial().optional(),
530
708
  // Gated by server_info features.solutionView; patches deep-merge.
531
709
  dotnetSolutionManagement: MutableDotnetSolutionConfigSchema.partial().optional(),
710
+ // Gated by server_info features.brainControl; patches deep-merge. Uses the
711
+ // dedicated no-default patch schema (see MutableBrainConfigPatchSchema): a
712
+ // plain `.partial()` here keeps every field's default and resets the whole
713
+ // block on a single-field patch.
714
+ brain: MutableBrainConfigPatchSchema.optional(),
715
+ // Gated by server_info features.connectors. Replaces the full array
716
+ // (read-modify-write), matching modelTierOverrides/savedProviderEndpoints, so
717
+ // enabling/disabling a connector or a tool is a whole-array rewrite.
718
+ connectors: z.array(ConnectorConfigSchema).optional(),
532
719
  })
533
720
  .partial()
534
721
  .passthrough();
@@ -618,6 +805,7 @@ export const ProviderSnapshotEntrySchema = z.object({
618
805
  provider: AgentProviderSchema,
619
806
  status: ProviderStatusSchema,
620
807
  enabled: z.boolean().optional().default(true),
808
+ source: z.enum(["builtin", "custom"]).optional(),
621
809
  error: z.string().optional(),
622
810
  models: z.array(AgentModelDefinitionSchema).optional(),
623
811
  modes: z.array(AgentModeSchema).optional(),
@@ -891,6 +1079,7 @@ export const AgentTimelineItemPayloadSchema = z.union([
891
1079
  type: z.literal("user_message"),
892
1080
  text: z.string(),
893
1081
  messageId: z.string().optional(),
1082
+ clientMessageId: z.string().optional(),
894
1083
  }),
895
1084
  z.object({
896
1085
  type: z.literal("assistant_message"),
@@ -918,6 +1107,8 @@ export const AgentTimelineItemPayloadSchema = z.union([
918
1107
  // COMPAT(compactionFailedStatus): "failed" added in v0.4.3. Clients older
919
1108
  // than that drop the whole timeline event on parse and keep showing the
920
1109
  // loading row — exactly their pre-"failed" behavior, so no gate is needed.
1110
+ // Nothing to remove: this tag records why the enum could be widened without
1111
+ // a shim, so it has no cleanup date by design.
921
1112
  status: z.enum(["loading", "completed", "failed"]),
922
1113
  trigger: z.enum(["auto", "manual"]).optional(),
923
1114
  preTokens: z.number().optional(),
@@ -979,6 +1170,7 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
979
1170
  body: z.string(),
980
1171
  data: z.object({
981
1172
  serverId: z.string(),
1173
+ workspaceId: z.string().optional(),
982
1174
  agentId: z.string(),
983
1175
  reason: z.enum(["finished", "error", "permission"]),
984
1176
  }),
@@ -1030,6 +1222,13 @@ export const QueuedAgentMessagePayloadSchema = z.object({
1030
1222
  preview: z.string(),
1031
1223
  enqueuedAt: z.string(),
1032
1224
  attachmentCount: z.number().int().nonnegative().optional(),
1225
+ /**
1226
+ * Who parked the message. Absent (from an older daemon) or "user" is a normal
1227
+ * user turn; "system" marks a system-injected entry (a chat mention, a
1228
+ * scheduled fire) that the daemon's drain never merges into a user turn — the
1229
+ * client must likewise exclude it from "Send all".
1230
+ */
1231
+ source: z.enum(["user", "system"]).optional(),
1033
1232
  });
1034
1233
  /**
1035
1234
  * An agent's LIFETIME SPEND, kept as the real token split plus the provider's
@@ -1106,7 +1305,9 @@ export const AgentSnapshotPayloadSchema = z.object({
1106
1305
  // a finished agent isn't "running Bash"). Both are purely additive optional
1107
1306
  // leaves: a provider that can't report them leaves them absent and the row
1108
1307
  // omits the readout rather than showing a wrong value.
1109
- // COMPAT(subagentLiveness): added in v0.6.7; old clients ignore both.
1308
+ // COMPAT(subagentLiveness): added in v0.6.7, drop the optional gate when the
1309
+ // floor is >= v0.6.7. Old clients ignore both fields and show the row without
1310
+ // its liveness readouts, which is their pre-v0.6.7 behaviour.
1110
1311
  // See docs/chat-lifecycle.md (the subagents track).
1111
1312
  toolUseCount: z.number().optional(),
1112
1313
  currentTool: z.string().optional(),
@@ -1129,6 +1330,14 @@ export const AgentSnapshotPayloadSchema = z.object({
1129
1330
  // COMPAT(observedSubagents): added in v0.4.3; absent ⇒ "attended". Drop the
1130
1331
  // gate when daemon floor >= v0.4.3. See projects/observed-subagents/observed-subagents.md.
1131
1332
  attend: z.enum(["attended", "observed"]).optional(),
1333
+ // True when an observed sub-agent run outlives an interrupt of the parent's
1334
+ // turn: a backgrounded Task/Agent (its tool_result was only a launch ack) or
1335
+ // a Workflow orchestration run. The client uses it to stop claiming that
1336
+ // interrupting the parent stops work it will not actually stop.
1337
+ // COMPAT(backgroundedObservedSubagents): added in v0.7.5; absent ⇒ treated as
1338
+ // foreground, which is the pre-existing behavior. Drop the gate when daemon
1339
+ // floor >= v0.7.5. See docs/chat-lifecycle.md.
1340
+ backgrounded: z.boolean().optional(),
1132
1341
  // Spinner colors from the Agent Personality this agent was spawned from, so
1133
1342
  // its live thinking indicator renders in the personality's identity. Absent ⇒
1134
1343
  // the client falls back to the theme's default spinner colors. Purely additive
@@ -1313,6 +1522,459 @@ export const AttachmentsImagesClearResponseSchema = z.object({
1313
1522
  requestId: z.string(),
1314
1523
  }),
1315
1524
  });
1525
+ // --- Local AI host (otto-brain) management -------------------------------
1526
+ // Lifecycle + evals are correlated request/response RPCs. Gated by
1527
+ // server_info.features.brainControl (lifecycle) and features.brainStatus
1528
+ // (evals). Live status streaming (subscribe_brain_status + brain_status_changed)
1529
+ // is added alongside its client/daemon consumers.
1530
+ // The brain's host status, as the daemon derives it: liveness plus the fields
1531
+ // proxied from the brain's own `/__host/status`. Passthrough on the opaque
1532
+ // sub-objects so the brain can evolve them without a protocol bump.
1533
+ export const BrainHostStatusSchema = z
1534
+ .object({
1535
+ running: z.boolean(),
1536
+ pid: z.number().nullable().optional(),
1537
+ version: z.string().nullable().optional(),
1538
+ host: z.string().nullable().optional(),
1539
+ port: z.number().nullable().optional(),
1540
+ displayHost: z.string().nullable().optional(),
1541
+ secure: z.boolean().optional(),
1542
+ state: z.string().nullable().optional(),
1543
+ model: z.string().nullable().optional(),
1544
+ modelId: z.string().nullable().optional(),
1545
+ vramBytes: z.number().nullable().optional(),
1546
+ loadSeconds: z.number().nullable().optional(),
1547
+ startedAt: z.string().nullable().optional(),
1548
+ lastError: z.string().nullable().optional(),
1549
+ telemetry: z.record(z.string(), z.unknown()).nullable().optional(),
1550
+ scheduler: z.record(z.string(), z.unknown()).nullable().optional(),
1551
+ recent: z.array(z.record(z.string(), z.unknown())).optional(),
1552
+ })
1553
+ .passthrough();
1554
+ const BrainHostStatusResultSchema = z.object({
1555
+ status: BrainHostStatusSchema,
1556
+ error: z.string().nullable(),
1557
+ requestId: z.string(),
1558
+ });
1559
+ export const BrainHostStatusRequestSchema = z.object({
1560
+ type: z.literal("brain.host.status.request"),
1561
+ requestId: z.string(),
1562
+ });
1563
+ export const BrainHostStatusResponseSchema = z.object({
1564
+ type: z.literal("brain.host.status.response"),
1565
+ payload: BrainHostStatusResultSchema,
1566
+ });
1567
+ export const BrainHostStartRequestSchema = z.object({
1568
+ type: z.literal("brain.host.start.request"),
1569
+ // Optional model fragment/id to load on start; null = the brain's default.
1570
+ model: z.string().nullable().default(null),
1571
+ requestId: z.string(),
1572
+ });
1573
+ export const BrainHostStartResponseSchema = z.object({
1574
+ type: z.literal("brain.host.start.response"),
1575
+ payload: BrainHostStatusResultSchema,
1576
+ });
1577
+ export const BrainHostStopRequestSchema = z.object({
1578
+ type: z.literal("brain.host.stop.request"),
1579
+ requestId: z.string(),
1580
+ });
1581
+ export const BrainHostStopResponseSchema = z.object({
1582
+ type: z.literal("brain.host.stop.response"),
1583
+ payload: BrainHostStatusResultSchema,
1584
+ });
1585
+ export const BrainHostRestartRequestSchema = z.object({
1586
+ type: z.literal("brain.host.restart.request"),
1587
+ model: z.string().nullable().default(null),
1588
+ requestId: z.string(),
1589
+ });
1590
+ export const BrainHostRestartResponseSchema = z.object({
1591
+ type: z.literal("brain.host.restart.response"),
1592
+ payload: BrainHostStatusResultSchema,
1593
+ });
1594
+ // Benchmark rankings/variance/latest, proxied from the brain's `/__host/evals`.
1595
+ export const BrainEvalsSchema = z
1596
+ .object({
1597
+ rankings: z.array(z.record(z.string(), z.unknown())).default([]),
1598
+ latest: z.array(z.record(z.string(), z.unknown())).default([]),
1599
+ variance: z.array(z.record(z.string(), z.unknown())).default([]),
1600
+ runCount: z.number().default(0),
1601
+ })
1602
+ .passthrough();
1603
+ export const BrainEvalsGetRequestSchema = z.object({
1604
+ type: z.literal("brain.evals.get.request"),
1605
+ requestId: z.string(),
1606
+ });
1607
+ export const BrainEvalsGetResponseSchema = z.object({
1608
+ type: z.literal("brain.evals.get.response"),
1609
+ payload: z.object({
1610
+ evals: BrainEvalsSchema.nullable(),
1611
+ error: z.string().nullable(),
1612
+ requestId: z.string(),
1613
+ }),
1614
+ });
1615
+ // Brain network auto-discovery: the daemon enumerates this host's bind
1616
+ // addresses and probes the local `tailscale` CLI so the client can offer the
1617
+ // operator a pick-list of likely listen hosts (and pre-fill the tailscale TLS
1618
+ // mode) instead of asking them to hunt for IPs by hand.
1619
+ // Gated by server_info.features.brainNetworkDiscovery.
1620
+ export const BrainTailscaleInfoSchema = z
1621
+ .object({
1622
+ // Whether the tailscale CLI is present and its daemon answers.
1623
+ available: z.boolean(),
1624
+ // This machine's MagicDNS name, e.g. greyskull.tail279562.ts.net.
1625
+ hostname: z.string().nullable().optional(),
1626
+ // The tailnet IPv4 address, for a tailnet-only bind.
1627
+ ipv4: z.string().nullable().optional(),
1628
+ // The default directory the brain writes issued certificates to.
1629
+ certDir: z.string().nullable().optional(),
1630
+ })
1631
+ .passthrough();
1632
+ // One candidate value for `listen.host`, with a human label for the pick-list.
1633
+ export const BrainBindAddressSchema = z
1634
+ .object({
1635
+ // The literal value written to listen.host (an IP, 0.0.0.0, or "tailscale").
1636
+ value: z.string(),
1637
+ // Display label, e.g. "Local only", "All interfaces", "192.168.1.42 (en0)".
1638
+ label: z.string(),
1639
+ kind: z.enum(["loopback", "all", "lan", "tailscale"]),
1640
+ })
1641
+ .passthrough();
1642
+ export const BrainNetworkInfoSchema = z
1643
+ .object({
1644
+ addresses: z.array(BrainBindAddressSchema).default([]),
1645
+ tailscale: BrainTailscaleInfoSchema.nullable().optional(),
1646
+ })
1647
+ .passthrough();
1648
+ // Detected model names for the settings pickers. Read from the brain's
1649
+ // /v1/models when it is reachable (local child up, or remote); empty otherwise,
1650
+ // which the client renders as a disabled picker. Gated by features.brainStatus.
1651
+ export const BrainModelsListRequestSchema = z.object({
1652
+ type: z.literal("brain.models.list.request"),
1653
+ requestId: z.string(),
1654
+ });
1655
+ export const BrainModelsListResponseSchema = z.object({
1656
+ type: z.literal("brain.models.list.response"),
1657
+ payload: z.object({
1658
+ models: z.array(z.string()).default([]),
1659
+ error: z.string().nullable(),
1660
+ requestId: z.string(),
1661
+ }),
1662
+ });
1663
+ // Read/write a *remote* brain's own config (its /__host/config). Only valid in
1664
+ // brain.mode "remote"; the config is the remote brain's effective config with
1665
+ // secrets redacted. Editable fields are model-related (defaultModel, lockModel);
1666
+ // network/TLS/auth stay host-owned. Gated by features.brainRemote.
1667
+ export const BrainRemoteConfigSchema = z.record(z.string(), z.unknown());
1668
+ export const BrainRemoteConfigGetRequestSchema = z.object({
1669
+ type: z.literal("brain.remote.config.get.request"),
1670
+ requestId: z.string(),
1671
+ });
1672
+ export const BrainRemoteConfigGetResponseSchema = z.object({
1673
+ type: z.literal("brain.remote.config.get.response"),
1674
+ payload: z.object({
1675
+ config: BrainRemoteConfigSchema.nullable(),
1676
+ error: z.string().nullable(),
1677
+ requestId: z.string(),
1678
+ }),
1679
+ });
1680
+ export const BrainRemoteConfigPatchRequestSchema = z.object({
1681
+ type: z.literal("brain.remote.config.patch.request"),
1682
+ patch: BrainRemoteConfigSchema,
1683
+ requestId: z.string(),
1684
+ });
1685
+ export const BrainRemoteConfigPatchResponseSchema = z.object({
1686
+ type: z.literal("brain.remote.config.patch.response"),
1687
+ payload: z.object({
1688
+ config: BrainRemoteConfigSchema.nullable(),
1689
+ error: z.string().nullable(),
1690
+ requestId: z.string(),
1691
+ }),
1692
+ });
1693
+ export const BrainNetworkDiscoverRequestSchema = z.object({
1694
+ type: z.literal("brain.network.discover.request"),
1695
+ requestId: z.string(),
1696
+ });
1697
+ export const BrainNetworkDiscoverResponseSchema = z.object({
1698
+ type: z.literal("brain.network.discover.response"),
1699
+ payload: z.object({
1700
+ info: BrainNetworkInfoSchema.nullable(),
1701
+ error: z.string().nullable(),
1702
+ requestId: z.string(),
1703
+ }),
1704
+ });
1705
+ // --- Brain model management (runtimes, catalog, downloads, ops) ----------
1706
+ // The daemon drives these by shelling out to `otto-brain <verb> --json` (it
1707
+ // never imports the brain's runtime modules in-process). Reads (scan, catalog,
1708
+ // runtime list) are correlated request/response; long operations (pull, runtime
1709
+ // install, calibrate, sweep, bench) run as tracked JOBS the client polls via
1710
+ // brain.jobs.list. All gated by server_info.features.brainManage.
1711
+ // An installed local model, from `otto-brain scan`. Passthrough so the brain's
1712
+ // scan row can grow fields without a protocol bump.
1713
+ export const BrainInstalledModelSchema = z
1714
+ .object({
1715
+ model: z.string().default(""),
1716
+ arch: z.string().default(""),
1717
+ quant: z.string().default(""),
1718
+ size: z.string().default(""),
1719
+ ctx: z.string().default(""),
1720
+ vision: z.string().default(""),
1721
+ calibrated: z.string().default(""),
1722
+ features: z.string().default(""),
1723
+ source: z.string().default(""),
1724
+ })
1725
+ .passthrough();
1726
+ // A downloadable catalog model, annotated with whether it is already installed
1727
+ // (the daemon reuses the brain's authoritative catalog↔model join). Passthrough
1728
+ // over the catalog entry's optional metadata.
1729
+ export const BrainCatalogModelSchema = z
1730
+ .object({
1731
+ id: z.string(),
1732
+ name: z.string().default(""),
1733
+ installed: z.boolean().default(false),
1734
+ publisher: z.string().default(""),
1735
+ repo: z.string().default(""),
1736
+ quant: z.string().default(""),
1737
+ params: z.string().default(""),
1738
+ sizeBytes: z.number().nullable().optional(),
1739
+ size: z.string().default(""),
1740
+ vision: z.boolean().default(false),
1741
+ thinking: z.boolean().default(false),
1742
+ contextMax: z.number().nullable().optional(),
1743
+ tier: z.string().default(""),
1744
+ useCases: z.array(z.string()).default([]),
1745
+ why: z.string().default(""),
1746
+ })
1747
+ .passthrough();
1748
+ // An installed llama.cpp runtime, from `otto-brain runtime list`.
1749
+ export const BrainRuntimeSchema = z
1750
+ .object({
1751
+ label: z.string().default(""),
1752
+ version: z.string().default(""),
1753
+ source: z.string().default(""),
1754
+ dir: z.string().default(""),
1755
+ })
1756
+ .passthrough();
1757
+ // A tracked long-running brain operation. The client polls brain.jobs.list and
1758
+ // renders progress. `percent` is null when the job reports no measurable
1759
+ // progress (indeterminate). Terminal jobs linger briefly so the UI can show
1760
+ // the outcome before they are pruned.
1761
+ export const BrainJobKindSchema = z.enum([
1762
+ "pull",
1763
+ "runtime-install",
1764
+ "calibrate",
1765
+ "sweep",
1766
+ "bench",
1767
+ ]);
1768
+ export const BrainJobStatusSchema = z.enum(["running", "succeeded", "failed", "canceled"]);
1769
+ export const BrainJobSchema = z
1770
+ .object({
1771
+ id: z.string(),
1772
+ kind: BrainJobKindSchema,
1773
+ // A short human label, e.g. "Download Phi-4 (14B)".
1774
+ label: z.string().default(""),
1775
+ // The subject id (catalog id, model name, or build tag) this job acts on.
1776
+ target: z.string().nullable().default(null),
1777
+ status: BrainJobStatusSchema.default("running"),
1778
+ percent: z.number().nullable().default(null),
1779
+ // The latest progress line (e.g. "extracting…", "budget 512: done").
1780
+ message: z.string().nullable().default(null),
1781
+ error: z.string().nullable().default(null),
1782
+ startedAt: z.string().default(""),
1783
+ finishedAt: z.string().nullable().default(null),
1784
+ })
1785
+ .passthrough();
1786
+ // Every job-starting RPC returns the created (or refused) job under this shape.
1787
+ const BrainJobResultSchema = z.object({
1788
+ job: BrainJobSchema.nullable(),
1789
+ error: z.string().nullable(),
1790
+ requestId: z.string(),
1791
+ });
1792
+ // Every job-listing RPC returns the active + recently-finished jobs.
1793
+ const BrainJobsResultSchema = z.object({
1794
+ jobs: z.array(BrainJobSchema).default([]),
1795
+ error: z.string().nullable(),
1796
+ requestId: z.string(),
1797
+ });
1798
+ // Installed models — `otto-brain scan`.
1799
+ export const BrainModelsScanRequestSchema = z.object({
1800
+ type: z.literal("brain.models.scan.request"),
1801
+ requestId: z.string(),
1802
+ });
1803
+ export const BrainModelsScanResponseSchema = z.object({
1804
+ type: z.literal("brain.models.scan.response"),
1805
+ payload: z.object({
1806
+ models: z.array(BrainInstalledModelSchema).default([]),
1807
+ error: z.string().nullable(),
1808
+ requestId: z.string(),
1809
+ }),
1810
+ });
1811
+ // Downloadable catalog — `otto-brain catalog`.
1812
+ export const BrainCatalogListRequestSchema = z.object({
1813
+ type: z.literal("brain.catalog.list.request"),
1814
+ requestId: z.string(),
1815
+ });
1816
+ export const BrainCatalogListResponseSchema = z.object({
1817
+ type: z.literal("brain.catalog.list.response"),
1818
+ payload: z.object({
1819
+ models: z.array(BrainCatalogModelSchema).default([]),
1820
+ error: z.string().nullable(),
1821
+ requestId: z.string(),
1822
+ }),
1823
+ });
1824
+ // Installed runtimes — `otto-brain runtime list`.
1825
+ export const BrainRuntimeListRequestSchema = z.object({
1826
+ type: z.literal("brain.runtime.list.request"),
1827
+ requestId: z.string(),
1828
+ });
1829
+ export const BrainRuntimeListResponseSchema = z.object({
1830
+ type: z.literal("brain.runtime.list.response"),
1831
+ payload: z.object({
1832
+ runtimes: z.array(BrainRuntimeSchema).default([]),
1833
+ error: z.string().nullable(),
1834
+ requestId: z.string(),
1835
+ }),
1836
+ });
1837
+ // Download a catalog model — starts a `pull` job.
1838
+ export const BrainModelsPullRequestSchema = z.object({
1839
+ type: z.literal("brain.models.pull.request"),
1840
+ // Catalog id or name fragment.
1841
+ model: z.string(),
1842
+ requestId: z.string(),
1843
+ });
1844
+ export const BrainModelsPullResponseSchema = z.object({
1845
+ type: z.literal("brain.models.pull.response"),
1846
+ payload: BrainJobResultSchema,
1847
+ });
1848
+ // Install a llama.cpp runtime — starts a `runtime-install` job.
1849
+ export const BrainRuntimeInstallRequestSchema = z.object({
1850
+ type: z.literal("brain.runtime.install.request"),
1851
+ // Optional llama.cpp release build tag; null = the brain's default.
1852
+ build: z.string().nullable().default(null),
1853
+ requestId: z.string(),
1854
+ });
1855
+ export const BrainRuntimeInstallResponseSchema = z.object({
1856
+ type: z.literal("brain.runtime.install.response"),
1857
+ payload: BrainJobResultSchema,
1858
+ });
1859
+ // Measure real KV bytes/token for a model — starts a `calibrate` job. Needs a
1860
+ // runtime + GPU; refused with a helpful error otherwise.
1861
+ export const BrainCalibrateRequestSchema = z.object({
1862
+ type: z.literal("brain.calibrate.request"),
1863
+ model: z.string(),
1864
+ requestId: z.string(),
1865
+ });
1866
+ export const BrainCalibrateResponseSchema = z.object({
1867
+ type: z.literal("brain.calibrate.response"),
1868
+ payload: BrainJobResultSchema,
1869
+ });
1870
+ // Find the best reasoning budget for a model — starts a `sweep` job.
1871
+ export const BrainSweepRequestSchema = z.object({
1872
+ type: z.literal("brain.sweep.request"),
1873
+ model: z.string(),
1874
+ requestId: z.string(),
1875
+ });
1876
+ export const BrainSweepResponseSchema = z.object({
1877
+ type: z.literal("brain.sweep.response"),
1878
+ payload: BrainJobResultSchema,
1879
+ });
1880
+ // Run the agentic-coding benchmark — starts a `bench` job. `model` is an
1881
+ // optional comma list of name fragments; null lets the brain pick.
1882
+ export const BrainBenchRequestSchema = z.object({
1883
+ type: z.literal("brain.bench.request"),
1884
+ model: z.string().nullable().default(null),
1885
+ requestId: z.string(),
1886
+ });
1887
+ export const BrainBenchResponseSchema = z.object({
1888
+ type: z.literal("brain.bench.response"),
1889
+ payload: BrainJobResultSchema,
1890
+ });
1891
+ // Poll the active + recently-finished jobs.
1892
+ export const BrainJobsListRequestSchema = z.object({
1893
+ type: z.literal("brain.jobs.list.request"),
1894
+ requestId: z.string(),
1895
+ });
1896
+ export const BrainJobsListResponseSchema = z.object({
1897
+ type: z.literal("brain.jobs.list.response"),
1898
+ payload: BrainJobsResultSchema,
1899
+ });
1900
+ // Cancel a running job; returns the refreshed job list.
1901
+ export const BrainJobsCancelRequestSchema = z.object({
1902
+ type: z.literal("brain.jobs.cancel.request"),
1903
+ jobId: z.string(),
1904
+ requestId: z.string(),
1905
+ });
1906
+ export const BrainJobsCancelResponseSchema = z.object({
1907
+ type: z.literal("brain.jobs.cancel.response"),
1908
+ payload: BrainJobsResultSchema,
1909
+ });
1910
+ // --- Hugging Face discovery (search + add arbitrary repos) ---------------
1911
+ // The daemon shells out to `otto-brain search`/`add --json`. Reads are
1912
+ // correlated request/response; the download runs as a `pull` job. Gated by
1913
+ // server_info.features.brainHfSearch. The brain resolves its own HF token (env
1914
+ // HF_TOKEN or its config), so no secret crosses this boundary.
1915
+ // One GGUF repo from a Hugging Face search. Passthrough so the brain's search
1916
+ // row can grow fields without a protocol bump.
1917
+ export const BrainHfSearchResultSchema = z
1918
+ .object({
1919
+ repo: z.string().default(""),
1920
+ downloads: z.number().default(0),
1921
+ likes: z.number().default(0),
1922
+ gated: z.boolean().default(false),
1923
+ // True when any quant of this repo is already on disk.
1924
+ installed: z.boolean().default(false),
1925
+ })
1926
+ .passthrough();
1927
+ // One downloadable quantization of a repo — `otto-brain add <repo> --list-quants`.
1928
+ export const BrainRepoQuantSchema = z
1929
+ .object({
1930
+ quant: z.string().default(""),
1931
+ size: z.string().default(""),
1932
+ sizeBytes: z.number().default(0),
1933
+ files: z.number().default(0),
1934
+ // True when this specific quant is already on disk.
1935
+ installed: z.boolean().default(false),
1936
+ })
1937
+ .passthrough();
1938
+ // Search Hugging Face for GGUF models — `otto-brain search <query>`.
1939
+ export const BrainHfSearchRequestSchema = z.object({
1940
+ type: z.literal("brain.hf.search.request"),
1941
+ query: z.string(),
1942
+ limit: z.number().nullable().default(null),
1943
+ requestId: z.string(),
1944
+ });
1945
+ export const BrainHfSearchResponseSchema = z.object({
1946
+ type: z.literal("brain.hf.search.response"),
1947
+ payload: z.object({
1948
+ results: z.array(BrainHfSearchResultSchema).default([]),
1949
+ error: z.string().nullable(),
1950
+ requestId: z.string(),
1951
+ }),
1952
+ });
1953
+ // List the quantizations a repo offers — `otto-brain add <repo> --list-quants`.
1954
+ export const BrainHfQuantsRequestSchema = z.object({
1955
+ type: z.literal("brain.hf.quants.request"),
1956
+ repo: z.string(),
1957
+ requestId: z.string(),
1958
+ });
1959
+ export const BrainHfQuantsResponseSchema = z.object({
1960
+ type: z.literal("brain.hf.quants.response"),
1961
+ payload: z.object({
1962
+ quants: z.array(BrainRepoQuantSchema).default([]),
1963
+ error: z.string().nullable(),
1964
+ requestId: z.string(),
1965
+ }),
1966
+ });
1967
+ // Download a chosen quant of an arbitrary HF repo — starts a `pull` job.
1968
+ export const BrainModelsAddRequestSchema = z.object({
1969
+ type: z.literal("brain.models.add.request"),
1970
+ repo: z.string(),
1971
+ quant: z.string(),
1972
+ requestId: z.string(),
1973
+ });
1974
+ export const BrainModelsAddResponseSchema = z.object({
1975
+ type: z.literal("brain.models.add.response"),
1976
+ payload: BrainJobResultSchema,
1977
+ });
1316
1978
  export const ProjectRenameRequestSchema = z.object({
1317
1979
  type: z.literal("project.rename.request"),
1318
1980
  projectId: z.string(),
@@ -1356,6 +2018,22 @@ export const WorkspaceTitleSetRequestSchema = z.object({
1356
2018
  title: z.string().nullable(),
1357
2019
  requestId: z.string(),
1358
2020
  });
2021
+ export const WorkspacePinSetRequestSchema = z.object({
2022
+ type: z.literal("workspace.pin.set.request"),
2023
+ workspaceId: z.string(),
2024
+ pinned: z.boolean(),
2025
+ requestId: z.string(),
2026
+ });
2027
+ export const WorkspaceRecoveryInspectRequestSchema = z.object({
2028
+ type: z.literal("workspace.recovery.inspect.request"),
2029
+ workspaceId: z.string(),
2030
+ requestId: z.string(),
2031
+ });
2032
+ export const WorkspaceRecoveryRestoreRequestSchema = z.object({
2033
+ type: z.literal("workspace.recovery.restore.request"),
2034
+ workspaceId: z.string(),
2035
+ requestId: z.string(),
2036
+ });
1359
2037
  export const SetVoiceModeMessageSchema = z.object({
1360
2038
  type: z.literal("set_voice_mode"),
1361
2039
  enabled: z.boolean(),
@@ -1372,6 +2050,18 @@ export const GitHubPrAttachmentSchema = z.object({
1372
2050
  baseRefName: z.string().nullable().optional(),
1373
2051
  headRefName: z.string().nullable().optional(),
1374
2052
  });
2053
+ export const ForgeChangeRequestAttachmentSchema = z.object({
2054
+ type: z.literal("forge_change_request"),
2055
+ mimeType: z.literal("application/otto-forge-change-request"),
2056
+ forge: z.string().optional().default("github"),
2057
+ number: z.number().int().positive(),
2058
+ title: z.string(),
2059
+ url: z.string(),
2060
+ body: z.string().nullable().optional(),
2061
+ projectPath: z.string().optional(),
2062
+ baseRefName: z.string().nullable().optional(),
2063
+ headRefName: z.string().nullable().optional(),
2064
+ });
1375
2065
  export const GitHubIssueAttachmentSchema = z.object({
1376
2066
  type: z.literal("github_issue"),
1377
2067
  mimeType: z.literal("application/github-issue"),
@@ -1380,10 +2070,13 @@ export const GitHubIssueAttachmentSchema = z.object({
1380
2070
  url: z.string(),
1381
2071
  body: z.string().nullable().optional(),
1382
2072
  });
1383
- // Provider-neutral successors to github_pr/github_issue. New clients send
1384
- // these when server_info features.gitHostingProviders is set; the github_*
1385
- // kinds remain accepted forever (protocol contract) and are still what a new
1386
- // client sends to an old daemon for GitHub projects.
2073
+ // COMPAT(hostingAttachments): added in v0.7.6, remove after 2027-02-01.
2074
+ // These were the provider-neutral successors to github_pr/github_issue. The
2075
+ // forge merge replaced them with forge_change_request/forge_issue, so no
2076
+ // current client sends them they stay accepted (protocol contract) purely so
2077
+ // a client from before that merge can still attach a PR or an issue. The
2078
+ // daemon renders them at
2079
+ // server/src/server/agent/prompt-attachments.ts; retire both halves together.
1387
2080
  export const HostingPrAttachmentSchema = z.object({
1388
2081
  type: z.literal("hosting_pr"),
1389
2082
  mimeType: z.literal("application/otto-hosting-pr"),
@@ -1404,6 +2097,16 @@ export const HostingIssueAttachmentSchema = z.object({
1404
2097
  url: z.string(),
1405
2098
  body: z.string().nullable().optional(),
1406
2099
  });
2100
+ export const ForgeIssueAttachmentSchema = z.object({
2101
+ type: z.literal("forge_issue"),
2102
+ mimeType: z.literal("application/otto-forge-issue"),
2103
+ forge: z.string().optional().default("github"),
2104
+ number: z.number().int().positive(),
2105
+ title: z.string(),
2106
+ url: z.string(),
2107
+ body: z.string().nullable().optional(),
2108
+ projectPath: z.string().optional(),
2109
+ });
1407
2110
  export const TextAttachmentSchema = z
1408
2111
  .object({
1409
2112
  type: z.literal("text"),
@@ -1450,6 +2153,8 @@ export const UploadedFileAttachmentSchema = z.object({
1450
2153
  path: z.string(),
1451
2154
  });
1452
2155
  export const AgentAttachmentSchema = z.discriminatedUnion("type", [
2156
+ ForgeChangeRequestAttachmentSchema,
2157
+ ForgeIssueAttachmentSchema,
1453
2158
  GitHubPrAttachmentSchema,
1454
2159
  GitHubIssueAttachmentSchema,
1455
2160
  HostingPrAttachmentSchema,
@@ -1472,6 +2177,12 @@ function normalizeAgentAttachments(input) {
1472
2177
  return normalized;
1473
2178
  }
1474
2179
  const AgentAttachmentsSchema = z.unknown().transform(normalizeAgentAttachments).optional();
2180
+ export const ChangeRequestCheckoutSourceSchema = z.object({
2181
+ kind: z.literal("change_request"),
2182
+ forge: z.string().optional(),
2183
+ number: z.number().int().positive(),
2184
+ projectPath: z.string().optional(),
2185
+ });
1475
2186
  const ImageAttachmentSchema = z.object({
1476
2187
  data: z.string(), // base64 encoded image
1477
2188
  mimeType: z.string(), // e.g., "image/jpeg", "image/png"
@@ -1546,6 +2257,10 @@ export const FetchWorkspacesRequestMessageSchema = z.object({
1546
2257
  })
1547
2258
  .optional(),
1548
2259
  });
2260
+ export const ProjectListRequestMessageSchema = z.object({
2261
+ type: z.literal("project.list.request"),
2262
+ requestId: z.string(),
2263
+ });
1549
2264
  export const FetchAgentHistoryRequestMessageSchema = z.object({
1550
2265
  type: z.literal("fetch_agent_history_request"),
1551
2266
  requestId: z.string(),
@@ -1643,6 +2358,21 @@ export const DaemonGetPairingOfferRequestSchema = z.object({
1643
2358
  type: z.literal("daemon.get_pairing_offer.request"),
1644
2359
  requestId: z.string(),
1645
2360
  });
2361
+ export const HubManagementDaemonConnectRequestSchema = z.object({
2362
+ type: z.literal("hub.management.daemon.connect.request"),
2363
+ requestId: z.string(),
2364
+ hubUrl: z.string(),
2365
+ token: z.string(),
2366
+ });
2367
+ export const HubManagementDaemonGetStatusRequestSchema = z.object({
2368
+ type: z.literal("hub.management.daemon.get_status.request"),
2369
+ requestId: z.string(),
2370
+ });
2371
+ export const HubManagementDaemonDisconnectRequestSchema = z.object({
2372
+ type: z.literal("hub.management.daemon.disconnect.request"),
2373
+ requestId: z.string(),
2374
+ force: z.boolean().optional(),
2375
+ });
1646
2376
  export const DiagnosticsRequestSchema = z.object({
1647
2377
  type: z.literal("diagnostics.request"),
1648
2378
  requestId: z.string(),
@@ -1656,6 +2386,33 @@ export const SetDaemonConfigRequestMessageSchema = z.object({
1656
2386
  requestId: z.string(),
1657
2387
  config: MutableDaemonConfigPatchSchema,
1658
2388
  });
2389
+ // Connectors — MCP servers surfaced as named, toggle-able integrations. The
2390
+ // registry itself (add/remove/enable/disable a connector or an individual tool)
2391
+ // lives in daemon config and is edited via set_daemon_config's `connectors`
2392
+ // patch. The one thing config can't answer is what tools a connector actually
2393
+ // exposes, which needs a live connect + listTools — that is this RPC. Gated by
2394
+ // features.connectors.
2395
+ export const ConnectorsListToolsRequestSchema = z.object({
2396
+ type: z.literal("connectors.list_tools.request"),
2397
+ requestId: z.string(),
2398
+ connectorId: z.string(),
2399
+ });
2400
+ export const ConnectorsListToolsResponseSchema = z.object({
2401
+ type: z.literal("connectors.list_tools.response"),
2402
+ payload: z.object({
2403
+ connectorId: z.string(),
2404
+ tools: z
2405
+ .array(z.object({
2406
+ name: z.string(),
2407
+ description: z.string().nullable().default(null),
2408
+ disabled: z.boolean().default(false),
2409
+ }))
2410
+ .default([]),
2411
+ // Non-null when the connector could not be reached / enumerated.
2412
+ error: z.string().nullable(),
2413
+ requestId: z.string(),
2414
+ }),
2415
+ });
1659
2416
  export const SpeechSettingsGetOptionsRequestSchema = z.object({
1660
2417
  type: z.literal("speech.settings.get_options.request"),
1661
2418
  requestId: z.string(),
@@ -1737,6 +2494,29 @@ export const AgentPersonalitiesGetStatsRequestSchema = z.object({
1737
2494
  type: z.literal("agentPersonalities.get_stats.request"),
1738
2495
  requestId: z.string(),
1739
2496
  });
2497
+ // COMPAT(personalityProfile): added in v0.7.5; gate lives in
2498
+ // features.personalityProfile. Author a personality PROFILE (the prose
2499
+ // `personalityPrompt` that shapes how an agent behaves) from the only things
2500
+ // the editor knows before one exists: the handle, the roles it will be spawned
2501
+ // for, and its two spinner colors. Like the voice-cue RPC this is described
2502
+ // inline (not a stored id) so the editor can generate for an unsaved draft, and
2503
+ // is an editor-time action: the result lands in the prompt field for the user to
2504
+ // edit, and is stored on the personality by the ordinary save.
2505
+ export const AgentPersonalitiesGenerateProfileRequestSchema = z.object({
2506
+ type: z.literal("agentPersonalities.generate_profile.request"),
2507
+ requestId: z.string(),
2508
+ name: z.string(),
2509
+ // Permissive strings to match the stored personality shape (forward-compatible
2510
+ // with roles this daemon predates); the daemon filters to its known set.
2511
+ roles: z.array(z.string().min(1)).optional(),
2512
+ // The spinner glow pair, read as a palette (temperature, energy) and never
2513
+ // quoted literally in the profile.
2514
+ glowA: z.string().optional(),
2515
+ glowB: z.string().optional(),
2516
+ // Scopes provider resolution to a workspace; omitted falls back to any
2517
+ // resolvable one.
2518
+ cwd: z.string().optional(),
2519
+ });
1740
2520
  export const ReadProjectConfigRequestMessageSchema = z.object({
1741
2521
  type: z.literal("read_project_config_request"),
1742
2522
  requestId: z.string(),
@@ -1781,6 +2561,9 @@ const GitSetupOptionsSchema = z.object({
1781
2561
  worktreeSlug: z.string().optional(),
1782
2562
  refName: z.string().min(1).optional(),
1783
2563
  action: z.enum(["branch-off", "checkout"]).optional(),
2564
+ checkoutSource: ChangeRequestCheckoutSourceSchema.optional(),
2565
+ // COMPAT(githubPrNumber): added in v0.1.106, remove after 2026-12-28 once
2566
+ // clients send checkoutSource.
1784
2567
  githubPrNumber: z.number().int().positive().optional(),
1785
2568
  });
1786
2569
  export const CreateAgentWorktreeTargetSchema = z.discriminatedUnion("mode", [
@@ -1809,6 +2592,9 @@ export const CreateAgentRequestMessageSchema = z.object({
1809
2592
  personality: z.string().optional(),
1810
2593
  env: z.record(z.string(), z.string()).optional(),
1811
2594
  workspaceId: z.string().optional(),
2595
+ // Optional caller context lets managed CLI invocations use the same daemon-owned
2596
+ // workspace and parentage policy as agent-scoped MCP creation.
2597
+ callerAgentId: z.string().optional(),
1812
2598
  worktreeName: z.string().optional(),
1813
2599
  initialPrompt: z.string().optional(),
1814
2600
  clientMessageId: z.string().optional(),
@@ -2080,9 +2866,29 @@ export const FetchAgentTimelineRequestMessageSchema = z.object({
2080
2866
  // Default should be projected for app timeline loading.
2081
2867
  projection: z.enum(["projected", "canonical"]).optional(),
2082
2868
  });
2869
+ export const ProviderSubagentListRequestMessageSchema = z.object({
2870
+ type: z.literal("agent.provider_subagents.list.request"),
2871
+ parentAgentId: z.string(),
2872
+ requestId: z.string(),
2873
+ });
2874
+ export const ProviderSubagentTimelineRequestMessageSchema = z.object({
2875
+ type: z.literal("agent.provider_subagents.timeline.get.request"),
2876
+ parentAgentId: z.string(),
2877
+ subagentId: z.string(),
2878
+ requestId: z.string(),
2879
+ direction: z.enum(["tail", "before", "after"]).optional(),
2880
+ cursor: AgentTimelineCursorSchema.optional(),
2881
+ limit: z.number().int().nonnegative().optional(),
2882
+ });
2883
+ export const SetAgentTimelineSubscriptionRequestMessageSchema = z.object({
2884
+ type: z.literal("agent.timeline.set_subscription.request"),
2885
+ agentIds: z.array(z.string()),
2886
+ requestId: z.string(),
2887
+ });
2083
2888
  export const AgentForkContextRequestMessageSchema = z.object({
2084
2889
  type: z.literal("agent.fork_context.request"),
2085
2890
  agentId: z.string(),
2891
+ boundaryCursor: AgentTimelineCursorSchema.optional(),
2086
2892
  boundaryMessageId: z.string().optional(),
2087
2893
  requestId: z.string(),
2088
2894
  });
@@ -2802,6 +3608,48 @@ export const AgentWorkspaceTransferResponseMessageSchema = z.object({
2802
3608
  type: z.literal("agent.workspace.transfer.response"),
2803
3609
  payload: AgentWorkspaceTransferResponsePayloadSchema,
2804
3610
  });
3611
+ export const WorkspacePinSetResponsePayloadSchema = z.object({
3612
+ requestId: z.string(),
3613
+ workspaceId: z.string(),
3614
+ accepted: z.boolean(),
3615
+ pinnedAt: z.string().nullable(),
3616
+ error: z.string().nullable(),
3617
+ });
3618
+ export const WorkspacePinSetResponseSchema = z.object({
3619
+ type: z.literal("workspace.pin.set.response"),
3620
+ payload: WorkspacePinSetResponsePayloadSchema,
3621
+ });
3622
+ export const WorkspaceRecoveryStateSchema = z.discriminatedUnion("kind", [
3623
+ z.object({
3624
+ kind: z.literal("recoverable"),
3625
+ workspaceId: z.string(),
3626
+ workspaceName: z.string(),
3627
+ action: z.string(),
3628
+ branch: z.string().nullable(),
3629
+ }),
3630
+ z.object({
3631
+ kind: z.literal("unavailable"),
3632
+ workspaceId: z.string(),
3633
+ reason: z.string(),
3634
+ message: z.string(),
3635
+ }),
3636
+ ]);
3637
+ export const WorkspaceRecoveryInspectResponseSchema = z.object({
3638
+ type: z.literal("workspace.recovery.inspect.response"),
3639
+ payload: z.object({
3640
+ requestId: z.string(),
3641
+ state: WorkspaceRecoveryStateSchema,
3642
+ }),
3643
+ });
3644
+ export const WorkspaceRecoveryRestoreResponseSchema = z.object({
3645
+ type: z.literal("workspace.recovery.restore.response"),
3646
+ payload: z.object({
3647
+ requestId: z.string(),
3648
+ workspaceId: z.string(),
3649
+ accepted: z.boolean(),
3650
+ error: z.string().nullable(),
3651
+ }),
3652
+ });
2805
3653
  export const SetVoiceModeResponseMessageSchema = z.object({
2806
3654
  type: z.literal("set_voice_mode_response"),
2807
3655
  payload: z.object({
@@ -3262,6 +4110,15 @@ export const CheckoutPrMergeRequestSchema = z.object({
3262
4110
  mergeMethod: z.enum(["merge", "squash", "rebase"]),
3263
4111
  requestId: z.string(),
3264
4112
  });
4113
+ export const CheckoutForgeSetAutoMergeRequestSchema = z.object({
4114
+ type: z.literal("checkout.forge.set_auto_merge.request"),
4115
+ cwd: z.string(),
4116
+ enabled: z.boolean(),
4117
+ mergeMethod: z.enum(["merge", "squash", "rebase"]).optional(),
4118
+ requestId: z.string(),
4119
+ });
4120
+ // COMPAT(githubAutoMergeRpc): added in v0.1.106, remove after 2026-12-28 once
4121
+ // all supported clients use checkout.forge.set_auto_merge.*.
3265
4122
  export const CheckoutGithubSetAutoMergeRequestSchema = z.object({
3266
4123
  type: z.literal("checkout.github.set_auto_merge.request"),
3267
4124
  cwd: z.string(),
@@ -3269,16 +4126,63 @@ export const CheckoutGithubSetAutoMergeRequestSchema = z.object({
3269
4126
  mergeMethod: z.enum(["merge", "squash", "rebase"]).optional(),
3270
4127
  requestId: z.string(),
3271
4128
  });
4129
+ const CheckoutCommitFileSchema = z.object({
4130
+ path: z.string(),
4131
+ additions: z.number(),
4132
+ deletions: z.number(),
4133
+ status: z.enum(["added", "modified", "deleted", "renamed"]).optional(),
4134
+ });
4135
+ const CheckoutCommitSchema = z.object({
4136
+ sha: z.string(),
4137
+ shortSha: z.string(),
4138
+ subject: z.string(),
4139
+ authorName: z.string(),
4140
+ authorDate: z.string(), // ISO 8601
4141
+ isOnRemote: z.boolean(), // false = local-only (unpushed)
4142
+ // COMPAT(commitBaseClassification): added in v0.2.0, remove optional after 2027-01-23.
4143
+ isOnBase: z.boolean().optional(),
4144
+ files: z.array(CheckoutCommitFileSchema),
4145
+ });
4146
+ export const CheckoutCommitsListRequestSchema = z.object({
4147
+ type: z.literal("checkout.commits.list.request"),
4148
+ cwd: z.string(),
4149
+ requestId: z.string(),
4150
+ });
4151
+ export const CheckoutCommitFileDiffRequestSchema = z.object({
4152
+ type: z.literal("checkout.commits.file_diff.request"),
4153
+ cwd: z.string(),
4154
+ sha: z.string(),
4155
+ path: z.string(),
4156
+ requestId: z.string(),
4157
+ });
3272
4158
  const GitHubRepoSegmentSchema = z.string().regex(/^[A-Za-z0-9._-]+$/);
3273
- export const CheckoutGithubGetCheckDetailsRequestSchema = z.object({
3274
- type: z.literal("checkout.github.get_check_details.request"),
4159
+ const CheckoutCheckDetailsRequestPayloadSchema = z.object({
3275
4160
  cwd: z.string(),
3276
- repoOwner: GitHubRepoSegmentSchema,
3277
- repoName: GitHubRepoSegmentSchema,
3278
- checkRunId: z.number().int().positive(),
4161
+ // GitHub addresses check runs by owner/name. GitLab resolves the project from
4162
+ // cwd and omits these GitHub-only single-segment fields.
4163
+ repoOwner: GitHubRepoSegmentSchema.optional(),
4164
+ repoName: GitHubRepoSegmentSchema.optional(),
4165
+ // Permanently optional: a check addressed only by workflowRunId (Gitea
4166
+ // Actions runs carry no check-run id) is fetchable. Callers send at least one
4167
+ // of checkRunId/workflowRunId; the gated forge RPC only reaches daemons that
4168
+ // understand this.
4169
+ checkRunId: z.number().int().positive().optional(),
3279
4170
  workflowRunId: z.number().int().positive().optional(),
4171
+ // Permanent forge-routing field, optional because only some forges need it:
4172
+ // GitLab routes check details to the MR's head pipeline; Gitea-family adapters
4173
+ // resolve the PR head SHA by number, including after merge/close. GitHub
4174
+ // ignores it.
4175
+ changeRequestNumber: z.number().int().positive().optional(),
3280
4176
  requestId: z.string(),
3281
4177
  });
4178
+ export const CheckoutForgeGetCheckDetailsRequestSchema = CheckoutCheckDetailsRequestPayloadSchema.extend({
4179
+ type: z.literal("checkout.forge.get_check_details.request"),
4180
+ });
4181
+ // COMPAT(githubCheckDetailsRpc): added in v0.1.106, remove after 2026-12-28 once
4182
+ // all supported clients use checkout.forge.get_check_details.*.
4183
+ export const CheckoutGithubGetCheckDetailsRequestSchema = CheckoutCheckDetailsRequestPayloadSchema.extend({
4184
+ type: z.literal("checkout.github.get_check_details.request"),
4185
+ });
3282
4186
  export const CheckoutPrStatusRequestSchema = z.object({
3283
4187
  type: z.literal("checkout_pr_status_request"),
3284
4188
  cwd: z.string(),
@@ -3367,17 +4271,41 @@ export const BranchSuggestionsRequestSchema = z.object({
3367
4271
  });
3368
4272
  export const GitHubSearchItemSchema = z.object({
3369
4273
  kind: z.enum(["issue", "pr"]),
4274
+ forge: z.string().optional(),
3370
4275
  number: z.number(),
3371
4276
  title: z.string(),
3372
4277
  url: z.string(),
3373
4278
  state: z.string(),
3374
4279
  body: z.string().nullable(),
3375
4280
  labels: z.array(z.string()),
4281
+ projectPath: z.string().optional(),
3376
4282
  baseRefName: z.string().nullable().optional(),
3377
4283
  headRefName: z.string().nullable().optional(),
3378
4284
  updatedAt: z.string().optional(),
3379
4285
  });
3380
- export const GitHubSearchKindSchema = z.enum(["github-issue", "github-pr"]);
4286
+ export const ForgeSearchItemSchema = GitHubSearchItemSchema.extend({
4287
+ kind: z.enum(["issue", "change_request"]),
4288
+ });
4289
+ // COMPAT(githubSearchKind): added in v0.1.106, remove with the legacy
4290
+ // github_search_request RPC after 2026-12-28.
4291
+ export const ForgeSearchKindSchema = z.enum([
4292
+ "issue",
4293
+ "change_request",
4294
+ "github-issue",
4295
+ "github-pr",
4296
+ "pr",
4297
+ ]);
4298
+ export const GitHubSearchKindSchema = ForgeSearchKindSchema;
4299
+ export const ForgeSearchRequestSchema = z.object({
4300
+ type: z.literal("forge.search.request"),
4301
+ cwd: z.string(),
4302
+ query: z.string(),
4303
+ limit: z.number().int().min(1).max(50).optional(),
4304
+ kinds: z.array(ForgeSearchKindSchema).optional(),
4305
+ requestId: z.string(),
4306
+ });
4307
+ // COMPAT(githubSearchRpc): added in v0.1.106, remove after 2026-12-28 once
4308
+ // clients use forge.search.*.
3381
4309
  export const GitHubSearchRequestSchema = z.object({
3382
4310
  type: z.literal("github_search_request"),
3383
4311
  cwd: z.string(),
@@ -3458,6 +4386,9 @@ export const CreateOttoWorktreeRequestSchema = z.object({
3458
4386
  firstAgentContext: FirstAgentContextSchema.optional(),
3459
4387
  refName: z.string().min(1).optional(),
3460
4388
  action: z.enum(["branch-off", "checkout"]).optional(),
4389
+ checkoutSource: ChangeRequestCheckoutSourceSchema.optional(),
4390
+ // COMPAT(githubPrNumber): added in v0.1.106, remove after 2026-12-28 once
4391
+ // clients send checkoutSource: { kind: "change_request", forge, number }.
3461
4392
  githubPrNumber: z.number().int().positive().optional(),
3462
4393
  requestId: z.string(),
3463
4394
  });
@@ -3485,6 +4416,8 @@ export const OpenProjectRequestSchema = z.object({
3485
4416
  cwd: z.string(),
3486
4417
  requestId: z.string(),
3487
4418
  });
4419
+ // Smallest shorthand repo path is "a/b": owner, slash, repository.
4420
+ const MIN_REPOSITORY_PATH_LENGTH = 3;
3488
4421
  export const ProjectAddRequestSchema = z.object({
3489
4422
  type: z.literal("project.add.request"),
3490
4423
  cwd: z.string(),
@@ -3580,6 +4513,35 @@ export const HostingListOwnersRequestSchema = z.object({
3580
4513
  provider: GitHostingProviderIdWireSchema,
3581
4514
  requestId: z.string(),
3582
4515
  });
4516
+ export const ProjectCreateDirectoryRequestSchema = z.object({
4517
+ type: z.literal("project.create_directory.request"),
4518
+ parentPath: z.string(),
4519
+ name: z.string(),
4520
+ requestId: z.string(),
4521
+ });
4522
+ export const GithubRepositorySchema = z.object({
4523
+ id: z.string().min(1),
4524
+ name: z.string().min(1),
4525
+ nameWithOwner: z.string().min(MIN_REPOSITORY_PATH_LENGTH),
4526
+ description: z.string().nullable(),
4527
+ visibility: z.enum(["public", "private", "internal"]),
4528
+ updatedAt: z.string(),
4529
+ cloneUrl: z.string().min(MIN_REPOSITORY_PATH_LENGTH),
4530
+ });
4531
+ export const WorkspaceGithubSearchRepositoriesRequestSchema = z.object({
4532
+ type: z.literal("workspace.github.search_repositories.request"),
4533
+ query: z.string(),
4534
+ limit: z.number().int().min(1).max(50).optional(),
4535
+ requestId: z.string(),
4536
+ });
4537
+ export const ProjectGithubCloneProtocolSchema = z.enum(["https", "ssh"]);
4538
+ export const ProjectGithubCloneRequestSchema = z.object({
4539
+ type: z.literal("project.github.clone.request"),
4540
+ repo: z.string().trim().min(MIN_REPOSITORY_PATH_LENGTH),
4541
+ cloneProtocol: ProjectGithubCloneProtocolSchema.optional(),
4542
+ targetDirectory: z.string().trim().min(1),
4543
+ requestId: z.string(),
4544
+ });
3583
4545
  export const ArchiveWorkspaceRequestSchema = z.object({
3584
4546
  type: z.literal("archive_workspace_request"),
3585
4547
  workspaceId: z.string(),
@@ -3654,6 +4616,11 @@ export const WorkspaceCreateRequestSchema = z.object({
3654
4616
  // the repository default.
3655
4617
  refName: z.string().min(1).optional(),
3656
4618
  baseBranch: z.string().optional(),
4619
+ // New branch name for branch-off. The worktree path may use a different slug.
4620
+ branchName: z.string().min(1).optional(),
4621
+ checkoutSource: ChangeRequestCheckoutSourceSchema.optional(),
4622
+ // COMPAT(githubPrNumber): added in v0.1.106, remove after 2026-12-28 once
4623
+ // clients send checkoutSource.
3657
4624
  githubPrNumber: z.number().int().positive().optional(),
3658
4625
  worktreeSlug: z.string().optional(),
3659
4626
  }),
@@ -3743,6 +4710,7 @@ const FileExplorerFileSchema = z.object({
3743
4710
  // inline JSON read path only); old daemons omit both fields.
3744
4711
  eol: FileEolSchema.optional(),
3745
4712
  hash: z.string().optional(),
4713
+ revision: z.string().optional(),
3746
4714
  });
3747
4715
  const FileExplorerDirectorySchema = z.object({
3748
4716
  path: z.string(),
@@ -3756,6 +4724,103 @@ export const FileExplorerRequestSchema = z.object({
3756
4724
  requestId: z.string(),
3757
4725
  acceptBinary: z.boolean().optional(),
3758
4726
  });
4727
+ export const FileVersionSchema = z.discriminatedUnion("status", [
4728
+ z.object({
4729
+ status: z.literal("ready"),
4730
+ cwd: z.string(),
4731
+ path: z.string(),
4732
+ size: z.number().int().nonnegative(),
4733
+ modifiedAt: z.string(),
4734
+ revision: z.string().optional(),
4735
+ }),
4736
+ z.object({
4737
+ status: z.literal("missing"),
4738
+ cwd: z.string(),
4739
+ path: z.string(),
4740
+ }),
4741
+ z.object({
4742
+ status: z.literal("error"),
4743
+ cwd: z.string(),
4744
+ path: z.string(),
4745
+ error: z.string(),
4746
+ }),
4747
+ ]);
4748
+ export const FileSubscribeRequestSchema = z.object({
4749
+ type: z.literal("fs.file.subscribe.request"),
4750
+ cwd: z.string(),
4751
+ path: z.string(),
4752
+ subscriptionId: z.string(),
4753
+ requestId: z.string(),
4754
+ });
4755
+ export const FileUnsubscribeRequestSchema = z.object({
4756
+ type: z.literal("fs.file.unsubscribe.request"),
4757
+ subscriptionId: z.string(),
4758
+ requestId: z.string(),
4759
+ });
4760
+ export const FsFileWriteRequestSchema = z.object({
4761
+ type: z.literal("fs.file.write.request"),
4762
+ cwd: z.string(),
4763
+ path: z.string(),
4764
+ content: z.string(),
4765
+ expectedModifiedAt: z.string(),
4766
+ expectedRevision: z.string().optional(),
4767
+ requestId: z.string(),
4768
+ });
4769
+ /**
4770
+ * Write bytes to a workspace file.
4771
+ *
4772
+ * The counterpart to `fs.file.write`, which is text only: it LF-normalizes,
4773
+ * re-applies the file's detected EOL, and outright refuses to overwrite a file
4774
+ * whose current bytes look binary. None of that can carry a PDF, an image or
4775
+ * any other generated artifact, so those go through here instead — the bytes
4776
+ * land verbatim.
4777
+ *
4778
+ * Deliberately not a conditional write. Callers are producing a generated file
4779
+ * from a source they already hold, so there is no "the file changed under you"
4780
+ * to reconcile: either the caller means to replace what is there or it does
4781
+ * not, and `overwrite` says which. Keeping that explicit is what stops this
4782
+ * from being a clobber-any-path primitive.
4783
+ *
4784
+ * Workspace-bounded, like the create/delete/rename surface and unlike
4785
+ * `file.write`. `file.write` is unbounded because a tab may edit a file the
4786
+ * user opened from anywhere; putting new bytes at an arbitrary path on the host
4787
+ * is a different power and does not need to be that wide.
4788
+ *
4789
+ * The bytes themselves do not ride in this message. They follow it as
4790
+ * `FileTransfer` binary frames correlated on `requestId` — FileBegin, then
4791
+ * FileChunk, then FileEnd — the same transport `file.upload` uses. This request
4792
+ * is the metadata half: where the bytes go and how many of them to expect.
4793
+ * Everything here writes multi-megabyte files (a printed PDF, a dropped image),
4794
+ * and base64 in a JSON message costs a third again on the wire plus the whole
4795
+ * encoded string allocated on both sides and walked by the validator.
4796
+ */
4797
+ export const FsFileWriteBinaryRequestSchema = z.object({
4798
+ type: z.literal("fs.file.write_binary.request"),
4799
+ cwd: z.string(),
4800
+ path: z.string(),
4801
+ /**
4802
+ * Byte length of the payload to follow. The daemon refuses a transfer that
4803
+ * overruns it and refuses one that ends short, so a truncated stream fails
4804
+ * loudly instead of landing a half file. Optional only because the base64
4805
+ * form below predates it and carries its own length.
4806
+ */
4807
+ size: z.number().int().nonnegative().optional(),
4808
+ /**
4809
+ * base64. Decoded and written as-is: no EOL translation, no re-encoding.
4810
+ *
4811
+ * COMPAT(binaryWriteBase64): added in v0.7.6, drop this field and its daemon
4812
+ * branch on 2027-02-02. Superseded by `size` plus file-transfer frames. The
4813
+ * daemon still reads it — a field we stopped sending is not a field we stop
4814
+ * accepting — and picks the branch from which of the two is present.
4815
+ */
4816
+ contentBase64: z.string().optional(),
4817
+ /**
4818
+ * Replace an existing file. Absent (the default) an existing target comes
4819
+ * back as `exists` and nothing is written.
4820
+ */
4821
+ overwrite: z.boolean().optional(),
4822
+ requestId: z.string(),
4823
+ });
3759
4824
  export const ProjectIconRequestSchema = z.object({
3760
4825
  type: z.literal("project_icon_request"),
3761
4826
  cwd: z.string(),
@@ -4205,6 +5270,16 @@ export const CreateTerminalRequestSchema = z.object({
4205
5270
  agentId: z.string().optional(),
4206
5271
  command: z.string().optional(),
4207
5272
  args: z.array(z.string()).optional(),
5273
+ // Initial PTY size. Added in v0.1.107; the app no longer sends it (the estimate cache that fed
5274
+ // it was removed — the pane-focus resize claim sizes the PTY instead). Kept and honored
5275
+ // permanently: released v0.1.107 clients still send it, and programmatic callers may pass an
5276
+ // exact size. Daemons without it start at 80x24 and the first resize corrects that.
5277
+ size: z
5278
+ .object({
5279
+ rows: z.number().int().positive(),
5280
+ cols: z.number().int().positive(),
5281
+ })
5282
+ .optional(),
4208
5283
  requestId: z.string(),
4209
5284
  });
4210
5285
  export const RenameTerminalRequestSchema = z.object({
@@ -4219,6 +5294,31 @@ export const StartWorkspaceScriptRequestSchema = z.object({
4219
5294
  scriptName: z.string(),
4220
5295
  requestId: z.string(),
4221
5296
  });
5297
+ export const WorkspaceScriptListRequestSchema = z.object({
5298
+ type: z.literal("workspace.script.list.request"),
5299
+ workspaceId: z.string(),
5300
+ requestId: z.string(),
5301
+ /**
5302
+ * Also return the Scripts the workspace's own project files declare
5303
+ * (`package.json` scripts, and later Makefile targets, .NET launch profiles),
5304
+ * each tagged with the `source` it came from. Off by default so a client that
5305
+ * predates discovery gets exactly the otto.json list it asked for.
5306
+ * COMPAT(workspaceScriptDiscovery): added in v0.7.6.
5307
+ */
5308
+ includeDiscovered: z.boolean().optional().default(false),
5309
+ });
5310
+ export const WorkspaceScriptStartRequestSchema = z.object({
5311
+ type: z.literal("workspace.script.start.request"),
5312
+ workspaceId: z.string(),
5313
+ scriptName: z.string(),
5314
+ requestId: z.string(),
5315
+ });
5316
+ export const WorkspaceScriptStopRequestSchema = z.object({
5317
+ type: z.literal("workspace.script.stop.request"),
5318
+ workspaceId: z.string(),
5319
+ scriptName: z.string(),
5320
+ requestId: z.string(),
5321
+ });
4222
5322
  export const SubscribeTerminalRequestSchema = z.object({
4223
5323
  type: z.literal("subscribe_terminal_request"),
4224
5324
  terminalId: z.string(),
@@ -4269,7 +5369,31 @@ export const CaptureTerminalRequestSchema = z.object({
4269
5369
  stripAnsi: z.boolean().default(true),
4270
5370
  requestId: z.string(),
4271
5371
  });
5372
+ export const HubExecutionAgentCreateRequestSchema = z.object({
5373
+ type: z.literal("hub.execution.agent.create.request"),
5374
+ requestId: z.string(),
5375
+ executionId: z.string(),
5376
+ provider: z.string(),
5377
+ cwd: z.string(),
5378
+ prompt: z.string(),
5379
+ workspaceId: z.string().optional(),
5380
+ model: z.string().optional(),
5381
+ modeId: z.string().optional(),
5382
+ thinkingOptionId: z.string().optional(),
5383
+ featureValues: z.record(z.string(), z.unknown()).optional(),
5384
+ env: z.record(z.string(), z.string()).optional(),
5385
+ worktree: CreateAgentWorktreeTargetSchema.optional(),
5386
+ });
5387
+ export const HubExecutionControlActionSchema = z.enum(["interrupt", "archive"]);
5388
+ export const HubExecutionControlRequestSchema = z.object({
5389
+ type: z.literal("hub.execution.control.request"),
5390
+ requestId: z.string(),
5391
+ executionId: z.string(),
5392
+ action: HubExecutionControlActionSchema,
5393
+ });
4272
5394
  export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5395
+ HubExecutionAgentCreateRequestSchema,
5396
+ HubExecutionControlRequestSchema,
4273
5397
  BrowserAutomationExecuteResponseSchema,
4274
5398
  VoiceAudioChunkMessageSchema,
4275
5399
  AbortRequestMessageSchema,
@@ -4278,6 +5402,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4278
5402
  FetchAgentHistoryRequestMessageSchema,
4279
5403
  FetchRecentProviderSessionsRequestMessageSchema,
4280
5404
  FetchWorkspacesRequestMessageSchema,
5405
+ ProjectListRequestMessageSchema,
4281
5406
  FetchAgentRequestMessageSchema,
4282
5407
  DeleteAgentRequestMessageSchema,
4283
5408
  ArchiveAgentRequestMessageSchema,
@@ -4285,6 +5410,28 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4285
5410
  HistoryAgentsClearArchivedRequestSchema,
4286
5411
  AttachmentsImagesStatsRequestSchema,
4287
5412
  AttachmentsImagesClearRequestSchema,
5413
+ BrainHostStatusRequestSchema,
5414
+ BrainHostStartRequestSchema,
5415
+ BrainHostStopRequestSchema,
5416
+ BrainHostRestartRequestSchema,
5417
+ BrainEvalsGetRequestSchema,
5418
+ BrainNetworkDiscoverRequestSchema,
5419
+ BrainModelsListRequestSchema,
5420
+ BrainRemoteConfigGetRequestSchema,
5421
+ BrainRemoteConfigPatchRequestSchema,
5422
+ BrainModelsScanRequestSchema,
5423
+ BrainCatalogListRequestSchema,
5424
+ BrainRuntimeListRequestSchema,
5425
+ BrainModelsPullRequestSchema,
5426
+ BrainRuntimeInstallRequestSchema,
5427
+ BrainCalibrateRequestSchema,
5428
+ BrainSweepRequestSchema,
5429
+ BrainBenchRequestSchema,
5430
+ BrainJobsListRequestSchema,
5431
+ BrainJobsCancelRequestSchema,
5432
+ BrainHfSearchRequestSchema,
5433
+ BrainHfQuantsRequestSchema,
5434
+ BrainModelsAddRequestSchema,
4288
5435
  UpdateAgentRequestMessageSchema,
4289
5436
  ProjectRenameRequestSchema,
4290
5437
  ProjectRemoveRequestSchema,
@@ -4292,20 +5439,28 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4292
5439
  ProjectLinksSetRequestSchema,
4293
5440
  ProjectLinksUnsetRequestSchema,
4294
5441
  WorkspaceTitleSetRequestSchema,
5442
+ WorkspacePinSetRequestSchema,
5443
+ WorkspaceRecoveryInspectRequestSchema,
5444
+ WorkspaceRecoveryRestoreRequestSchema,
4295
5445
  SetVoiceModeMessageSchema,
4296
5446
  SendAgentMessageRequestSchema,
4297
5447
  WaitForFinishRequestSchema,
4298
5448
  DaemonGetStatusRequestSchema,
4299
5449
  DaemonGetPairingOfferRequestSchema,
5450
+ HubManagementDaemonConnectRequestSchema,
5451
+ HubManagementDaemonGetStatusRequestSchema,
5452
+ HubManagementDaemonDisconnectRequestSchema,
4300
5453
  DiagnosticsRequestSchema,
4301
5454
  GetDaemonConfigRequestMessageSchema,
4302
5455
  SetDaemonConfigRequestMessageSchema,
5456
+ ConnectorsListToolsRequestSchema,
4303
5457
  SpeechSettingsGetOptionsRequestSchema,
4304
5458
  SpeechTtsPreviewRequestSchema,
4305
5459
  SpeechTtsSpeakRequestSchema,
4306
5460
  SpeechTtsSpeakCancelRequestSchema,
4307
5461
  VisualizerVoiceCuesGenerateRequestSchema,
4308
5462
  AgentPersonalitiesGetStatsRequestSchema,
5463
+ AgentPersonalitiesGenerateProfileRequestSchema,
4309
5464
  ReadProjectConfigRequestMessageSchema,
4310
5465
  WriteProjectConfigRequestMessageSchema,
4311
5466
  DictationStreamStartMessageSchema,
@@ -4341,6 +5496,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4341
5496
  RestartServerRequestMessageSchema,
4342
5497
  DaemonUpdateRequestMessageSchema,
4343
5498
  FetchAgentTimelineRequestMessageSchema,
5499
+ ProviderSubagentListRequestMessageSchema,
5500
+ ProviderSubagentTimelineRequestMessageSchema,
5501
+ SetAgentTimelineSubscriptionRequestMessageSchema,
4344
5502
  AgentForkContextRequestMessageSchema,
4345
5503
  SetAgentModeRequestMessageSchema,
4346
5504
  SetAgentModelRequestMessageSchema,
@@ -4390,7 +5548,11 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4390
5548
  CheckoutRefreshRequestSchema,
4391
5549
  CheckoutPrCreateRequestSchema,
4392
5550
  CheckoutPrMergeRequestSchema,
5551
+ CheckoutForgeSetAutoMergeRequestSchema,
4393
5552
  CheckoutGithubSetAutoMergeRequestSchema,
5553
+ CheckoutCommitsListRequestSchema,
5554
+ CheckoutCommitFileDiffRequestSchema,
5555
+ CheckoutForgeGetCheckDetailsRequestSchema,
4394
5556
  CheckoutGithubGetCheckDetailsRequestSchema,
4395
5557
  PreviewListConfigRequestSchema,
4396
5558
  PreviewStartRequestSchema,
@@ -4405,6 +5567,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4405
5567
  StashListRequestSchema,
4406
5568
  ValidateBranchRequestSchema,
4407
5569
  BranchSuggestionsRequestSchema,
5570
+ ForgeSearchRequestSchema,
4408
5571
  GitHubSearchRequestSchema,
4409
5572
  HostingSearchRequestSchema,
4410
5573
  HostingAuthStatusRequestSchema,
@@ -4420,6 +5583,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4420
5583
  ProjectScaffoldRequestSchema,
4421
5584
  HostingListRepositoriesRequestSchema,
4422
5585
  HostingListOwnersRequestSchema,
5586
+ ProjectCreateDirectoryRequestSchema,
5587
+ WorkspaceGithubSearchRepositoriesRequestSchema,
5588
+ ProjectGithubCloneRequestSchema,
4423
5589
  ArchiveWorkspaceRequestSchema,
4424
5590
  WorkspaceArchivePreflightRequestSchema,
4425
5591
  WorktreeBaseRefSetRequestSchema,
@@ -4428,6 +5594,10 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4428
5594
  WorkspaceCreateRequestSchema,
4429
5595
  WorkspaceClearAttentionRequestSchema,
4430
5596
  FileExplorerRequestSchema,
5597
+ FileSubscribeRequestSchema,
5598
+ FileUnsubscribeRequestSchema,
5599
+ FsFileWriteRequestSchema,
5600
+ FsFileWriteBinaryRequestSchema,
4431
5601
  ProjectIconRequestSchema,
4432
5602
  FileDownloadTokenRequestSchema,
4433
5603
  FileUploadRequestSchema,
@@ -4467,6 +5637,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4467
5637
  CreateTerminalRequestSchema,
4468
5638
  RenameTerminalRequestSchema,
4469
5639
  StartWorkspaceScriptRequestSchema,
5640
+ WorkspaceScriptListRequestSchema,
5641
+ WorkspaceScriptStartRequestSchema,
5642
+ WorkspaceScriptStopRequestSchema,
4470
5643
  SubscribeTerminalRequestSchema,
4471
5644
  UnsubscribeTerminalRequestSchema,
4472
5645
  TerminalInputSchema,
@@ -4641,14 +5814,23 @@ export const ServerInfoStatusPayloadSchema = z
4641
5814
  serverId: z.string().trim().min(1),
4642
5815
  hostname: ServerInfoHostnameSchema.optional(),
4643
5816
  version: ServerInfoVersionSchema.optional(),
5817
+ // COMPAT(desktopManaged): added in v0.1.X, remove optional parsing after 2027-01-16.
5818
+ desktopManaged: z.boolean().optional(),
4644
5819
  capabilities: ServerCapabilitiesFromUnknownSchema.optional(),
4645
5820
  // COMPAT(providersSnapshot): added in v0.1.48, remove gating when all clients use snapshot
4646
5821
  features: z
4647
5822
  .object({
4648
5823
  providersSnapshot: z.boolean().optional(),
5824
+ // COMPAT(checkoutForgeSetAutoMerge): added in v0.1.106, remove old
5825
+ // checkoutGithubSetAutoMerge fallback after 2026-12-28.
5826
+ checkoutForgeSetAutoMerge: z.boolean().optional(),
4649
5827
  checkoutGithubSetAutoMerge: z.boolean().optional(),
4650
5828
  // COMPAT(githubCheckDetails): added in v0.1.92, remove gate after 2026-12-08.
4651
5829
  githubCheckDetails: z.boolean().optional(),
5830
+ // COMPAT(forgeCheckDetails): added in v0.1.106, remove githubCheckDetails fallback after 2026-12-28.
5831
+ forgeCheckDetails: z.boolean().optional(),
5832
+ // COMPAT(forgeSearch): added in v0.1.106, remove github_search fallback after 2026-12-28.
5833
+ forgeSearch: z.boolean().optional(),
4652
5834
  // COMPAT(daemonStatusRpc): added in v0.1.76, remove gate after 2026-11-18.
4653
5835
  daemonStatusRpc: z.boolean().optional(),
4654
5836
  // COMPAT(terminalRestoreModes): added in v0.1.81, remove gate after 2026-11-23.
@@ -4671,14 +5853,55 @@ export const ServerInfoStatusPayloadSchema = z
4671
5853
  projectScaffold: z.boolean().optional(),
4672
5854
  // COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
4673
5855
  worktreeRestore: z.boolean().optional(),
5856
+ // COMPAT(workspaceRecovery): added in v0.1.105, remove after 2027-01-11 once daemon floor >= v0.1.105.
5857
+ workspaceRecovery: z.boolean().optional(),
5858
+ // COMPAT(workspaceFileEditing): added in v0.2.0, remove after 2027-01-18 once daemon floor >= v0.2.0.
5859
+ workspaceFileEditing: z.boolean().optional(),
4674
5860
  // COMPAT(providerUsageList): added in v0.1.98, drop the gate when daemon floor >= v0.1.98.
4675
5861
  providerUsageList: z.boolean().optional(),
4676
5862
  // COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
4677
5863
  agentDetach: z.boolean().optional(),
5864
+ // COMPAT(agentThinkingUpdate): added in v0.2.4, remove gate after 2027-01-28.
5865
+ agentThinkingUpdate: z.boolean().optional(),
4678
5866
  // COMPAT(daemonDiagnostics): added in v0.1.100, remove gate after 2026-12-25 once daemon floor >= v0.1.100.
4679
5867
  daemonDiagnostics: z.boolean().optional(),
4680
5868
  // COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
4681
5869
  daemonSelfUpdate: z.boolean().optional(),
5870
+ // Daemon manages the local AI host (otto-brain) as a child: reports
5871
+ // brain.host.status, serves brain.host.start/stop/restart, exposes the
5872
+ // editable `brain` config block, and honors kill-on-shutdown. Without it
5873
+ // the Local brain host UI is hidden ("update the host").
5874
+ // COMPAT(brainControl): added in v0.7.5, remove gate after 2026-01-30 once daemon floor >= v0.7.5.
5875
+ brainControl: z.boolean().optional(),
5876
+ // Daemon streams the brain's live status/telemetry via
5877
+ // subscribe_brain_status + brain_status_changed, and serves brain.evals.get.
5878
+ // Separate from brainControl because status/eval watching can ship after
5879
+ // lifecycle control. Without it the Brain dashboard falls back to a
5880
+ // periodic brain.host.status poll (no live feed, no eval charts).
5881
+ // COMPAT(brainStatus): added in v0.7.5, remove gate after 2026-01-30 once daemon floor >= v0.7.5.
5882
+ brainStatus: z.boolean().optional(),
5883
+ // Daemon serves brain.network.discover: enumerates this host's bind
5884
+ // addresses and probes the local `tailscale` CLI, so the client can
5885
+ // offer a listen-host pick-list and auto-fill the tailscale TLS mode.
5886
+ // COMPAT(brainNetworkDiscovery): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5887
+ brainNetworkDiscovery: z.boolean().optional(),
5888
+ // Daemon can point the brain at a remote host (brain.mode "remote"):
5889
+ // status/evals/config proxied from another Otto's brain, no local spawn.
5890
+ // COMPAT(brainRemote): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5891
+ brainRemote: z.boolean().optional(),
5892
+ // Daemon manages the brain's models and runtimes by shelling out to the
5893
+ // otto-brain CLI: serves brain.models.scan / brain.catalog.list /
5894
+ // brain.runtime.list (reads) and starts brain.models.pull /
5895
+ // brain.runtime.install / brain.calibrate / brain.sweep / brain.bench as
5896
+ // tracked jobs polled via brain.jobs.list. Without it the Brain "Models"
5897
+ // and "Operations" sections are hidden ("update the host").
5898
+ // COMPAT(brainManage): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5899
+ brainManage: z.boolean().optional(),
5900
+ // Daemon serves brain.hf.search / brain.hf.quants (reads) and starts
5901
+ // brain.models.add (download an arbitrary HF repo's quant) as a pull job.
5902
+ // Without it the Brain "Models" section hides the Hugging Face search box.
5903
+ // COMPAT(brainHfSearch): added in v0.7.5, remove gate after 2026-07-30 once daemon floor >= v0.7.5.
5904
+ brainHfSearch: z.boolean().optional(),
4682
5905
  // COMPAT(agentForkContext): added in v0.1.102, remove gate after 2026-12-28.
4683
5906
  agentForkContext: z.boolean().optional(),
4684
5907
  // COMPAT(providerRemove): added in v0.1.105, drop the gate when daemon floor >= v0.1.105.
@@ -4739,6 +5962,16 @@ export const ServerInfoStatusPayloadSchema = z
4739
5962
  // client-side fallback could read.
4740
5963
  // COMPAT(personalityMemory): added in v0.7.0, drop the gate when daemon floor >= v0.7.0.
4741
5964
  personalityMemory: z.boolean().optional(),
5965
+ // Script discovery — the daemon scans a workspace for the Scripts its
5966
+ // project files already declare (package.json scripts, and later
5967
+ // Makefile targets, .NET launch profiles) and serves them from
5968
+ // `workspace.script.list` with `includeDiscovered`. Without it the
5969
+ // Scripts dropdown shows only what otto.json declares, which is the
5970
+ // pre-existing behavior and not a degraded build of this feature: only
5971
+ // the daemon can read the workspace's files, so there is no client-side
5972
+ // scan to fall back to.
5973
+ // COMPAT(workspaceScriptDiscovery): added in v0.7.6, drop the gate when daemon floor >= v0.7.6.
5974
+ workspaceScriptDiscovery: z.boolean().optional(),
4742
5975
  // COMPAT(projectSearch): added in v0.4.4, drop the gate when daemon floor >= v0.4.4.
4743
5976
  projectSearch: z.boolean().optional(),
4744
5977
  // COMPAT(codeIndex): added in v0.4.4, drop the gate when daemon floor >= v0.4.4.
@@ -4779,6 +6012,10 @@ export const ServerInfoStatusPayloadSchema = z
4779
6012
  ttsSpeak: z.boolean().optional(),
4780
6013
  // COMPAT(visualizerVoiceCues): added in v0.6.3, drop the gate when daemon floor >= v0.6.3.
4781
6014
  visualizerVoiceCues: z.boolean().optional(),
6015
+ // COMPAT(personalityProfile): added in v0.7.5, drop the gate when daemon floor >= v0.7.5.
6016
+ // Host can author a personality profile (the prompt prose) from a name,
6017
+ // roles, and spinner colors.
6018
+ personalityProfile: z.boolean().optional(),
4782
6019
  // COMPAT(setAgentPersonality): added in v0.5.0, drop the gate when daemon floor >= v0.5.0.
4783
6020
  setAgentPersonality: z.boolean().optional(),
4784
6021
  // COMPAT(checkoutGitCommit): added in v0.5.1, drop the gate when daemon floor >= v0.5.1.
@@ -4872,11 +6109,23 @@ export const ServerInfoStatusPayloadSchema = z
4872
6109
  // group regardless, so the client hides the categorized section instead
4873
6110
  // of showing category switches that do nothing.
4874
6111
  mcpToolGroups: z.boolean().optional(),
6112
+ // COMPAT(connectors): added in v0.7.5, drop the gate when daemon floor >= v0.7.5.
6113
+ // Set when the daemon persists and honors `connectors` — MCP servers
6114
+ // surfaced as named, toggle-able integrations with per-tool disable,
6115
+ // enforced today on the openai-compat path. Old daemons ignore the
6116
+ // section, so the client hides the Connectors settings entirely.
6117
+ connectors: z.boolean().optional(),
4875
6118
  // COMPAT(agentBehaviorToggles): added in v0.6.4, drop the gate when daemon floor >= v0.6.4.
4876
6119
  // Set when the daemon persists `agentBehaviors.*` (promptSuggestions,
4877
6120
  // agentProgressSummaries, notifyOnFinishDefault). The reads are wired by
4878
6121
  // Claude-tier providers (WP-E); the client gates the toggle cards on this.
4879
6122
  agentBehaviorToggles: z.boolean().optional(),
6123
+ // COMPAT(todoReminders): added in v0.7.5, drop the gate when daemon floor >= v0.7.5.
6124
+ // Set when the daemon acts on `agentBehaviors.{todoNudge,todoReconcileOnIdle}` —
6125
+ // the provider-agnostic stale-todo nudge (next turn) and idle reconcile pass.
6126
+ // The client gates the task-list toggle cards on this so an old daemon never
6127
+ // shows switches that do nothing.
6128
+ todoReminders: z.boolean().optional(),
4880
6129
  // COMPAT(metadataGenerationEnabled): added in v0.6.4, drop the gate when daemon floor >= v0.6.4.
4881
6130
  // Set when the daemon persists `metadataGeneration.{enabled,preferWriterPersonalities}`.
4882
6131
  // The generation path (WP-B) reads them; the client gates the toggle cards on this.
@@ -4914,6 +6163,13 @@ export const ServerInfoStatusPayloadSchema = z
4914
6163
  // There is no client-side substitute (the client never touches the
4915
6164
  // filesystem), so an old daemon simply does not get the menu items.
4916
6165
  fileMutations: z.boolean().optional(),
6166
+ // COMPAT(binaryFileWrite): added in v0.7.6, drop the gate when daemon
6167
+ // floor >= v0.7.6. Set when the daemon serves
6168
+ // `fs.file.write_binary` — bytes to a workspace path, as opposed to
6169
+ // `fs.file.write`, which is text and refuses binary targets outright.
6170
+ // The client cannot write a workspace file itself on any platform, so
6171
+ // an old daemon simply does not offer the exports that produce bytes.
6172
+ binaryFileWrite: z.boolean().optional(),
4917
6173
  // COMPAT(attachmentStorage): added in v0.7.1, drop the gate when daemon floor >= v0.7.1.
4918
6174
  // Set when the daemon serves `attachments.images.get_stats` and
4919
6175
  // `attachments.images.clear` — the readout and reclaim for the images it
@@ -4928,6 +6184,40 @@ export const ServerInfoStatusPayloadSchema = z
4928
6184
  // the same directory. The client cannot restamp ownership itself (it is
4929
6185
  // daemon state), so an old daemon simply does not get the menu item.
4930
6186
  agentWorkspaceTransfer: z.boolean().optional(),
6187
+ // COMPAT(agentForkContextCursor): added in v0.1.108, remove gate after 2027-01-14.
6188
+ agentForkContextCursor: z.boolean().optional(),
6189
+ // COMPAT(providerSubagents): added in v0.1.107, remove gate after 2027-01-12.
6190
+ providerSubagents: z.boolean().optional(),
6191
+ // COMPAT(workspacePinning): added in v0.1.107, remove gate after 2027-01-12.
6192
+ workspacePinning: z.boolean().optional(),
6193
+ // COMPAT(hubRelationship): added in v0.1.X, drop the gate when floor >= v0.1.X.
6194
+ hubRelationship: z.boolean().optional(),
6195
+ // COMPAT(projectGithubClone): added in v0.1.108, remove gate after 2027-01-15.
6196
+ projectGithubClone: z.boolean().optional(),
6197
+ // COMPAT(workspaceGithubRepositorySearch): added in v0.1.108, remove gate after 2027-01-15.
6198
+ workspaceGithubRepositorySearch: z.boolean().optional(),
6199
+ // COMPAT(projectCreateDirectory): added in v0.1.108, remove gate after 2027-01-15.
6200
+ projectCreateDirectory: z.boolean().optional(),
6201
+ // COMPAT(projectList): added in v0.2.4, drop the gate when floor >= v0.2.4.
6202
+ projectList: z.boolean().optional(),
6203
+ // COMPAT(commitsList): added in v0.1.110, remove gate after 2027-01-16.
6204
+ commitsList: z.boolean().optional(),
6205
+ // COMPAT(commitBaseClassification): added in v0.2.0, remove gate after 2027-01-23.
6206
+ commitBaseClassification: z.boolean().optional(),
6207
+ // COMPAT(providerRemoval): added in v0.1.105, drop the gate when floor >= v0.1.105.
6208
+ providerRemoval: z.boolean().optional(),
6209
+ // COMPAT(importSessionWorkspaceTarget): added in v0.1.110, remove gate after 2027-01-16.
6210
+ importSessionWorkspaceTarget: z.boolean().optional(),
6211
+ // COMPAT(forgeProviders): added in v0.1.106, drop the gate when daemon floor >= v0.1.106.
6212
+ // Daemon advertises pluggable non-GitHub forge support (the forge registry);
6213
+ // the client gates non-GitHub setup UI on it.
6214
+ forgeProviders: z.boolean().optional(),
6215
+ // COMPAT(selectiveAgentTimeline): added in v0.1.106, remove after 2027-01-12.
6216
+ selectiveAgentTimeline: z.boolean().optional(),
6217
+ // COMPAT(stableProjectIdentity): added in v0.1.109, remove gate after 2027-01-15.
6218
+ stableProjectIdentity: z.boolean().optional(),
6219
+ // COMPAT(workspaceScriptManagement): added in v0.1.105, remove gate after 2027-01-10.
6220
+ workspaceScriptManagement: z.boolean().optional(),
4931
6221
  })
4932
6222
  .optional(),
4933
6223
  })
@@ -5147,8 +6437,50 @@ export const ProjectPlacementPayloadSchema = z.object({
5147
6437
  });
5148
6438
  export const WorkspaceScriptLifecycleSchema = z.enum(["running", "stopped"]);
5149
6439
  export const WorkspaceScriptHealthSchema = z.enum(["healthy", "unhealthy"]);
6440
+ export const WorkspaceScriptSourcePayloadSchema = z.object({
6441
+ /** Stable provider id; also the prefix of every `scriptName` it produces. */
6442
+ id: z.string(),
6443
+ /** The tool half of the dropdown's group header, e.g. "npm" or "pnpm". */
6444
+ label: z.string(),
6445
+ /**
6446
+ * Repo-relative file the script was read from, e.g. "package.json".
6447
+ *
6448
+ * Plain-optional rather than defaulted, matching `label` and `command` on the
6449
+ * payload: absent means unknown. A discovery run knows the file, but the
6450
+ * descriptor's orphan path recovers a source from a qualified runtime name
6451
+ * alone (`npm:dev`) and has no discovery behind it to ask. A `.default(null)`
6452
+ * here would make the field required on the output type and force that path
6453
+ * to invent an answer it does not have.
6454
+ */
6455
+ file: z.string().nullable().optional(),
6456
+ });
5150
6457
  export const WorkspaceScriptPayloadSchema = z.object({
6458
+ /**
6459
+ * The launch/stop key, unique within a workspace. Otto's own otto.json
6460
+ * scripts use their bare name; a discovered one is qualified by its source
6461
+ * ("npm:build") so two sources offering "build" cannot collide in the
6462
+ * runtime store or the service-proxy hostname.
6463
+ */
5151
6464
  scriptName: z.string(),
6465
+ /**
6466
+ * What to show instead of `scriptName` — the name the project itself uses.
6467
+ * COMPAT(workspaceScriptDiscovery): added in v0.7.6; absent ⇒ show `scriptName`.
6468
+ */
6469
+ label: z.string().optional(),
6470
+ /**
6471
+ * Where this Script came from. Absent ⇒ declared in otto.json, which is the
6472
+ * only kind that existed before discovery and the only kind that may own a
6473
+ * service-proxy route.
6474
+ * COMPAT(workspaceScriptDiscovery): added in v0.7.6.
6475
+ */
6476
+ source: WorkspaceScriptSourcePayloadSchema.optional(),
6477
+ /**
6478
+ * The command the Script runs, for the row's subtitle. Left plain-optional
6479
+ * rather than defaulted so an existing payload that never carried a command
6480
+ * stays valid without gaining a field it has no answer for.
6481
+ * COMPAT(workspaceScriptDiscovery): added in v0.7.6; absent ⇒ no subtitle.
6482
+ */
6483
+ command: z.string().nullable().optional(),
5152
6484
  type: z.enum(["script", "service"]).optional().default("service"),
5153
6485
  hostname: z.string(),
5154
6486
  port: z.number().int().positive().nullable(),
@@ -5232,7 +6564,9 @@ export const WorkspaceDescriptorPayloadSchema = z
5232
6564
  projectRootPath: z.string(),
5233
6565
  workspaceDirectory: z.string().optional(),
5234
6566
  projectKind: z.enum(["git", "non_git", "directory"]),
5235
- // COMPAT(workspaces): keep legacy directory workspace kind parseable.
6567
+ // COMPAT(workspaces): keep the legacy "directory" workspace kind parseable.
6568
+ // Persisted registries still carry it and there is no migration, so this
6569
+ // stays until a migration retires the kind rather than expiring on a date.
5236
6570
  workspaceKind: z.enum(["directory", "local_checkout", "checkout", "worktree"]),
5237
6571
  name: z.string(),
5238
6572
  // COMPAT(workspaceTitles): added in v0.1.97, drop the optional gate when floor >= v0.1.97.
@@ -5241,6 +6575,8 @@ export const WorkspaceDescriptorPayloadSchema = z
5241
6575
  // its input and offer a "reset to branch name" action. Null means the name
5242
6576
  // is derived from the branch/directory.
5243
6577
  title: z.string().nullable().optional(),
6578
+ // COMPAT(workspacePinning): added in v0.1.107, remove optional after 2027-01-12.
6579
+ pinnedAt: z.string().nullable().optional(),
5244
6580
  archivingAt: z.string().nullable().optional().default(null),
5245
6581
  status: WorkspaceStateBucketSchema,
5246
6582
  // Best-effort workspace status entry timestamp. Old daemons omit the
@@ -5262,6 +6598,10 @@ export const WorkspaceDescriptorPayloadSchema = z
5262
6598
  scripts: z.array(WorkspaceScriptPayloadSchema).default([]),
5263
6599
  gitRuntime: WorkspaceGitRuntimePayloadSchema,
5264
6600
  githubRuntime: WorkspaceGitHubRuntimePayloadSchema,
6601
+ // COMPAT(forge): added in v0.1.106, remove after 2026-12-27. The forge resolved
6602
+ // for this workspace, so the sidebar/hover-card render the right brand mark.
6603
+ // Old daemons omit it; absent means the client falls back to GitHub.
6604
+ forge: z.string().optional(),
5265
6605
  project: ProjectPlacementPayloadSchema.optional(),
5266
6606
  })
5267
6607
  .transform((workspace) => ({
@@ -5353,6 +6693,8 @@ export const FetchRecentProviderSessionsResponseMessageSchema = z.object({
5353
6693
  // workspace is archived.
5354
6694
  export const WorkspaceProjectDescriptorPayloadSchema = z.object({
5355
6695
  projectId: z.string(),
6696
+ // COMPAT(projectKey): added in v0.2.4 on 2026-07-28; remove optional after 2027-01-28.
6697
+ projectKey: z.string().optional(),
5356
6698
  projectDisplayName: z.string(),
5357
6699
  projectCustomName: z.string().nullable().optional(),
5358
6700
  projectRootPath: z.string(),
@@ -5414,6 +6756,20 @@ export const ProjectUpdatedNotificationSchema = z.object({
5414
6756
  hasActiveWorkspaces: z.boolean(),
5415
6757
  }),
5416
6758
  });
6759
+ export const ProjectUpdateMessageSchema = z.object({
6760
+ type: z.literal("project.update"),
6761
+ payload: z.discriminatedUnion("kind", [
6762
+ z.object({ kind: z.literal("upsert"), project: WorkspaceProjectDescriptorPayloadSchema }),
6763
+ z.object({ kind: z.literal("remove"), projectId: z.string() }),
6764
+ ]),
6765
+ });
6766
+ export const ProjectListResponseMessageSchema = z.object({
6767
+ type: z.literal("project.list.response"),
6768
+ payload: z.object({
6769
+ requestId: z.string(),
6770
+ projects: z.array(WorkspaceProjectDescriptorPayloadSchema),
6771
+ }),
6772
+ });
5417
6773
  export const ScriptStatusUpdateMessageSchema = z.object({
5418
6774
  type: z.literal("script_status_update"),
5419
6775
  payload: z.object({
@@ -5545,6 +6901,70 @@ export const HostingListOwnersResponseSchema = z.object({
5545
6901
  error: z.string().nullable(),
5546
6902
  }),
5547
6903
  });
6904
+ export const ProjectCreateDirectoryErrorCodeSchema = z.enum([
6905
+ "invalid_name",
6906
+ "parent_directory_not_found",
6907
+ "directory_exists",
6908
+ "permission_denied",
6909
+ "registration_failed",
6910
+ "filesystem_error",
6911
+ ]);
6912
+ export const ProjectCreateDirectoryResponseSchema = z.object({
6913
+ type: z.literal("project.create_directory.response"),
6914
+ payload: z.object({
6915
+ requestId: z.string(),
6916
+ directoryPath: z.string().nullable(),
6917
+ project: WorkspaceProjectDescriptorPayloadSchema.nullable(),
6918
+ error: z.string().nullable(),
6919
+ // Error codes are open-ended on the wire so older clients can still parse
6920
+ // responses after a newer daemon learns another failure reason.
6921
+ errorCode: z.string().nullable(),
6922
+ }),
6923
+ });
6924
+ export const WorkspaceGithubSearchRepositoriesResponseSchema = z.object({
6925
+ type: z.literal("workspace.github.search_repositories.response"),
6926
+ payload: z.discriminatedUnion("status", [
6927
+ z.object({
6928
+ status: z.literal("success"),
6929
+ requestId: z.string(),
6930
+ repositories: z.array(GithubRepositorySchema),
6931
+ available: z.literal(true),
6932
+ error: z.null(),
6933
+ }),
6934
+ z.object({
6935
+ status: z.literal("unavailable"),
6936
+ requestId: z.string(),
6937
+ repositories: z.array(GithubRepositorySchema),
6938
+ reason: z.literal("gh_missing"),
6939
+ available: z.literal(false),
6940
+ error: z.string(),
6941
+ }),
6942
+ z.object({
6943
+ status: z.literal("unauthenticated"),
6944
+ requestId: z.string(),
6945
+ repositories: z.array(GithubRepositorySchema),
6946
+ available: z.literal(false),
6947
+ error: z.string(),
6948
+ }),
6949
+ z.object({
6950
+ status: z.literal("error"),
6951
+ requestId: z.string(),
6952
+ repositories: z.array(GithubRepositorySchema),
6953
+ available: z.literal(true),
6954
+ error: z.string(),
6955
+ }),
6956
+ ]),
6957
+ });
6958
+ export const ProjectGithubCloneResponseSchema = z.object({
6959
+ type: z.literal("project.github.clone.response"),
6960
+ payload: z.object({
6961
+ requestId: z.string(),
6962
+ repo: z.string().trim().min(MIN_REPOSITORY_PATH_LENGTH),
6963
+ checkoutPath: z.string().nullable(),
6964
+ project: WorkspaceProjectDescriptorPayloadSchema.nullable(),
6965
+ error: z.string().nullable(),
6966
+ }),
6967
+ });
5548
6968
  export const StartWorkspaceScriptResponseMessageSchema = z.object({
5549
6969
  type: z.literal("start_workspace_script_response"),
5550
6970
  payload: z.object({
@@ -5555,6 +6975,26 @@ export const StartWorkspaceScriptResponseMessageSchema = z.object({
5555
6975
  error: z.string().nullable(),
5556
6976
  }),
5557
6977
  });
6978
+ const WorkspaceScriptOperationPayloadSchema = z.object({
6979
+ requestId: z.string(),
6980
+ workspaceId: z.string(),
6981
+ scriptName: z.string().optional(),
6982
+ script: WorkspaceScriptPayloadSchema.nullable().optional(),
6983
+ scripts: z.array(WorkspaceScriptPayloadSchema).optional(),
6984
+ error: z.string().nullable(),
6985
+ });
6986
+ export const WorkspaceScriptListResponseMessageSchema = z.object({
6987
+ type: z.literal("workspace.script.list.response"),
6988
+ payload: WorkspaceScriptOperationPayloadSchema,
6989
+ });
6990
+ export const WorkspaceScriptStartResponseMessageSchema = z.object({
6991
+ type: z.literal("workspace.script.start.response"),
6992
+ payload: WorkspaceScriptOperationPayloadSchema,
6993
+ });
6994
+ export const WorkspaceScriptStopResponseMessageSchema = z.object({
6995
+ type: z.literal("workspace.script.stop.response"),
6996
+ payload: WorkspaceScriptOperationPayloadSchema,
6997
+ });
5558
6998
  // COMPAT(desktopEditorBridge): added in v0.1.88, remove after 2026-12-03 once old clients no longer parse daemon editor RPC responses.
5559
6999
  export const LegacyListAvailableEditorsResponseMessageSchema = z.object({
5560
7000
  type: z.literal("list_available_editors_response"),
@@ -5719,6 +7159,106 @@ export const FetchAgentTimelineResponseMessageSchema = z.object({
5719
7159
  error: z.string().nullable(),
5720
7160
  }),
5721
7161
  });
7162
+ export const ProviderSubagentDescriptorPayloadSchema = z.object({
7163
+ id: z.string(),
7164
+ parentAgentId: z.string(),
7165
+ provider: AgentProviderSchema,
7166
+ title: z.string().nullable(),
7167
+ description: z.string().nullable(),
7168
+ status: z.enum(["running", "completed", "failed", "canceled"]),
7169
+ createdAt: z.string(),
7170
+ updatedAt: z.string(),
7171
+ toolCallId: z.string().nullable(),
7172
+ cwd: z.string().nullable().optional(),
7173
+ });
7174
+ export const ProviderSubagentListResponseMessageSchema = z.object({
7175
+ type: z.literal("agent.provider_subagents.list.response"),
7176
+ payload: z.object({
7177
+ requestId: z.string(),
7178
+ parentAgentId: z.string(),
7179
+ subagents: z.array(ProviderSubagentDescriptorPayloadSchema),
7180
+ error: z.string().nullable(),
7181
+ }),
7182
+ });
7183
+ export const ProviderSubagentTimelineResponseMessageSchema = z.object({
7184
+ type: z.literal("agent.provider_subagents.timeline.get.response"),
7185
+ payload: z.object({
7186
+ requestId: z.string(),
7187
+ parentAgentId: z.string(),
7188
+ subagentId: z.string(),
7189
+ provider: AgentProviderSchema.nullable(),
7190
+ direction: z.enum(["tail", "before", "after"]),
7191
+ epoch: z.string(),
7192
+ reset: z.boolean(),
7193
+ staleCursor: z.boolean(),
7194
+ gap: z.boolean(),
7195
+ window: z.object({
7196
+ minSeq: z.number().int().nonnegative(),
7197
+ maxSeq: z.number().int().nonnegative(),
7198
+ nextSeq: z.number().int().nonnegative(),
7199
+ }),
7200
+ hasOlder: z.boolean(),
7201
+ hasNewer: z.boolean(),
7202
+ rows: z.array(z.object({
7203
+ item: AgentTimelineItemPayloadSchema,
7204
+ timestamp: z.string(),
7205
+ seq: z.number().int().nonnegative(),
7206
+ })),
7207
+ error: z.string().nullable(),
7208
+ }),
7209
+ });
7210
+ export const ProviderSubagentUpdateMessageSchema = z.object({
7211
+ type: z.literal("agent.provider_subagents.update"),
7212
+ payload: z.discriminatedUnion("kind", [
7213
+ z.object({
7214
+ kind: z.literal("upsert"),
7215
+ subagent: ProviderSubagentDescriptorPayloadSchema,
7216
+ }),
7217
+ z.object({
7218
+ kind: z.literal("timeline"),
7219
+ parentAgentId: z.string(),
7220
+ subagentId: z.string(),
7221
+ provider: AgentProviderSchema,
7222
+ item: AgentTimelineItemPayloadSchema,
7223
+ timestamp: z.string(),
7224
+ seq: z.number().int().nonnegative(),
7225
+ epoch: z.string(),
7226
+ }),
7227
+ z.object({
7228
+ kind: z.literal("remove"),
7229
+ parentAgentId: z.string(),
7230
+ subagentId: z.string(),
7231
+ }),
7232
+ ]),
7233
+ });
7234
+ export const SetAgentTimelineSubscriptionResponseMessageSchema = z.object({
7235
+ type: z.literal("agent.timeline.set_subscription.response"),
7236
+ payload: z.object({
7237
+ agentIds: z.array(z.string()),
7238
+ requestId: z.string(),
7239
+ }),
7240
+ });
7241
+ export const AgentAttentionRequiredMessageSchema = z.object({
7242
+ type: z.literal("agent_attention_required"),
7243
+ payload: z.object({
7244
+ agentId: z.string(),
7245
+ reason: z.enum(["finished", "error", "permission"]),
7246
+ timestamp: z.string(),
7247
+ shouldNotify: z.boolean(),
7248
+ notification: z
7249
+ .object({
7250
+ title: z.string(),
7251
+ body: z.string(),
7252
+ data: z.object({
7253
+ serverId: z.string(),
7254
+ workspaceId: z.string().optional(),
7255
+ agentId: z.string(),
7256
+ reason: z.enum(["finished", "error", "permission"]),
7257
+ }),
7258
+ })
7259
+ .optional(),
7260
+ }),
7261
+ });
5722
7262
  export const AgentForkContextResponseMessageSchema = z.object({
5723
7263
  type: z.literal("agent.fork_context.response"),
5724
7264
  payload: z.object({
@@ -5727,6 +7267,7 @@ export const AgentForkContextResponseMessageSchema = z.object({
5727
7267
  attachment: TextAttachmentSchema.nullable(),
5728
7268
  itemCount: z.number().int().nonnegative(),
5729
7269
  boundaryMessageId: z.string().nullable(),
7270
+ boundaryCursor: AgentTimelineCursorSchema.nullable().optional(),
5730
7271
  error: z.string().nullable(),
5731
7272
  }),
5732
7273
  });
@@ -5787,6 +7328,7 @@ export const CancelAgentResponseMessageSchema = z.object({
5787
7328
  // can say "nothing to stop" instead of silently no-oping. Purely additive;
5788
7329
  // absent ⇒ unknown (old daemon). See docs/agent-lifecycle.md (Item 2).
5789
7330
  cancelled: z.boolean().optional(),
7331
+ error: z.string().nullable().optional(),
5790
7332
  }),
5791
7333
  });
5792
7334
  export const ClearAgentAttentionResponseMessageSchema = z.object({
@@ -5953,6 +7495,19 @@ export const AgentPersonalitiesGetStatsResponseSchema = z.object({
5953
7495
  })
5954
7496
  .passthrough(),
5955
7497
  });
7498
+ export const AgentPersonalitiesGenerateProfileResponseSchema = z.object({
7499
+ type: z.literal("agentPersonalities.generate_profile.response"),
7500
+ payload: z
7501
+ .object({
7502
+ requestId: z.string(),
7503
+ // The authored personality prompt, ready to drop into the editor's prompt
7504
+ // field. Absent when generation failed (see error) or no writer/provider
7505
+ // resolves on this host.
7506
+ profile: z.string().optional(),
7507
+ error: z.string().optional(),
7508
+ })
7509
+ .passthrough(),
7510
+ });
5956
7511
  export const DaemonGetStatusResponseSchema = z.object({
5957
7512
  type: z.literal("daemon.get_status.response"),
5958
7513
  payload: z
@@ -5982,6 +7537,37 @@ export const DaemonGetStatusResponseSchema = z.object({
5982
7537
  })
5983
7538
  .passthrough(),
5984
7539
  });
7540
+ export const HubRelationshipStatusSchema = z.object({
7541
+ state: z.enum([
7542
+ "not_connected",
7543
+ "connecting",
7544
+ "connected",
7545
+ "reconnecting",
7546
+ "disconnecting",
7547
+ "revoked",
7548
+ ]),
7549
+ daemonId: z.string().nullable(),
7550
+ hubOrigin: z.string().nullable(),
7551
+ scopes: z.array(z.string()),
7552
+ connectedAt: z.string().nullable(),
7553
+ lastError: z.string().nullable(),
7554
+ });
7555
+ export const HubManagementDaemonConnectResponseSchema = z.object({
7556
+ type: z.literal("hub.management.daemon.connect.response"),
7557
+ payload: z.object({ requestId: z.string(), status: HubRelationshipStatusSchema }),
7558
+ });
7559
+ export const HubManagementDaemonGetStatusResponseSchema = z.object({
7560
+ type: z.literal("hub.management.daemon.get_status.response"),
7561
+ payload: z.object({ requestId: z.string(), status: HubRelationshipStatusSchema }),
7562
+ });
7563
+ export const HubManagementDaemonDisconnectResponseSchema = z.object({
7564
+ type: z.literal("hub.management.daemon.disconnect.response"),
7565
+ payload: z.object({
7566
+ requestId: z.string(),
7567
+ status: HubRelationshipStatusSchema,
7568
+ warning: z.string().optional(),
7569
+ }),
7570
+ });
5985
7571
  export const DaemonGetPairingOfferResponseSchema = z.object({
5986
7572
  type: z.literal("daemon.get_pairing_offer.response"),
5987
7573
  payload: z
@@ -6195,8 +7781,7 @@ const CheckoutPrGithubRepositoryPolicySchema = z
6195
7781
  rebaseMergeAllowed: false,
6196
7782
  viewerDefaultMergeMethod: null,
6197
7783
  });
6198
- const CheckoutPrGithubStatusSchema = z
6199
- .object({
7784
+ const CheckoutPrGithubStatusObjectSchema = z.object({
6200
7785
  mergeStateStatus: z.string().nullable().optional().default(null),
6201
7786
  autoMergeRequest: CheckoutPrGithubAutoMergeRequestSchema,
6202
7787
  viewerCanEnableAutoMerge: z.boolean().optional().default(false),
@@ -6206,9 +7791,23 @@ const CheckoutPrGithubStatusSchema = z
6206
7791
  repository: CheckoutPrGithubRepositoryPolicySchema,
6207
7792
  isMergeQueueEnabled: z.boolean().optional().default(false),
6208
7793
  isInMergeQueue: z.boolean().optional().default(false),
6209
- })
6210
- .optional();
7794
+ });
7795
+ const CheckoutPrGithubStatusSchema = CheckoutPrGithubStatusObjectSchema.optional();
7796
+ // The open facts envelope for forge-specific PR facts. Permanent — non-GitHub
7797
+ // forges deliver their native facts through it. The transitional piece is the
7798
+ // `github` mirror above, which stays populated for clients predating this
7799
+ // envelope; see COMPAT(forgeSpecific) in status-projection.ts for the shim.
7800
+ //
7801
+ // NOTE: `forgeSpecific.forge` is a FACTS-FAMILY tag, not the workspace brand id.
7802
+ // The whole Gitea family (gitea, forgejo, codeberg) emits `forge: "gitea"` here
7803
+ // because they share one facts shape, while the top-level `forge` above carries
7804
+ // the specific brand. Validation of family-specific payloads happens at runtime
7805
+ // in the consumer that knows that forge family.
7806
+ const CheckoutPrForgeSpecificSchema = z.unknown().optional();
6211
7807
  export const CheckoutPrStatusSchema = z.object({
7808
+ // COMPAT(forge): added in v0.1.106, remove the default after 2026-12-27 once daemon floor >= v0.1.106.
7809
+ forge: z.string().optional().default("github"),
7810
+ projectPath: z.string().optional(),
6212
7811
  number: z.number().optional(),
6213
7812
  url: z.string(),
6214
7813
  title: z.string(),
@@ -6241,6 +7840,14 @@ export const CheckoutPrStatusSchema = z.object({
6241
7840
  github: CheckoutPrGithubStatusSchema,
6242
7841
  // Provider-neutral per-PR hosting facts. Absent from old daemons; for
6243
7842
  // GitHub projects both this and the legacy `github` field are populated.
7843
+ //
7844
+ // NOT a shim, and deliberately untagged: this does not collapse into `forge`.
7845
+ // Otto registers every hosting provider into the forge registry under the
7846
+ // forge id `github`, which is the provider-routing facade rather than the gh
7847
+ // CLI (bootstrap.ts, `createGitHostingResolver`). So `forge` reads "github"
7848
+ // for a Bitbucket workspace and only `hosting.provider` carries the truth.
7849
+ // The two disagree by design; a reader wanting the real provider must use
7850
+ // this field.
6244
7851
  hosting: z
6245
7852
  .object({
6246
7853
  provider: GitHostingProviderIdWireSchema,
@@ -6254,7 +7861,9 @@ export const CheckoutPrStatusSchema = z.object({
6254
7861
  .optional(),
6255
7862
  })
6256
7863
  .optional(),
7864
+ forgeSpecific: CheckoutPrForgeSpecificSchema,
6257
7865
  });
7866
+ export const ForgeAuthStateSchema = z.unknown().optional();
6258
7867
  const CheckoutPrStatusPayloadSchema = z.object({
6259
7868
  cwd: z.string(),
6260
7869
  status: CheckoutPrStatusSchema.nullable(),
@@ -6264,6 +7873,9 @@ const CheckoutPrStatusPayloadSchema = z.object({
6264
7873
  githubFeaturesEnabled: z.boolean(),
6265
7874
  // Provider-neutral enablement. Present even when status is null so clients
6266
7875
  // can drive search/create-PR affordances for the workspace's provider.
7876
+ // Permanent for the same reason as the `hosting` block on the PR status
7877
+ // schema above: `forge` is a routing-facade id and cannot answer "which
7878
+ // provider is this really".
6267
7879
  hosting: z
6268
7880
  .object({
6269
7881
  provider: GitHostingProviderIdWireSchema,
@@ -6271,6 +7883,13 @@ const CheckoutPrStatusPayloadSchema = z.object({
6271
7883
  capabilities: GitHostingCapabilitiesSchema.optional(),
6272
7884
  })
6273
7885
  .optional(),
7886
+ // COMPAT(forgeAuthState): added in v0.1.106, remove after 2026-12-27. Optional richer
7887
+ // signal that supersedes githubFeaturesEnabled. The legacy boolean stays for old clients
7888
+ // and may remain true for non-auth error payloads so old clients still show the error.
7889
+ // Drop the boolean once the daemon floor >= v0.1.106.
7890
+ authState: ForgeAuthStateSchema,
7891
+ // COMPAT(forge): added in v0.1.106, remove the default after 2026-12-27 once daemon floor >= v0.1.106.
7892
+ forge: z.string().optional().default("github"),
6274
7893
  error: CheckoutErrorSchema.nullable(),
6275
7894
  requestId: z.string(),
6276
7895
  });
@@ -6298,6 +7917,8 @@ const CheckoutDiffSubscriptionPayloadSchema = z.object({
6298
7917
  cwd: z.string(),
6299
7918
  files: z.array(ParsedDiffFileSchema),
6300
7919
  error: CheckoutErrorSchema.nullable(),
7920
+ // COMPAT(diffTooLarge): added in v0.2.4, keep optional until the daemon floor is v0.2.4.
7921
+ diffTooLarge: z.boolean().optional(),
6301
7922
  });
6302
7923
  export const SubscribeCheckoutDiffResponseSchema = z.object({
6303
7924
  type: z.literal("subscribe_checkout_diff_response"),
@@ -6594,6 +8215,18 @@ export const CheckoutPrMergeResponseSchema = z.object({
6594
8215
  requestId: z.string(),
6595
8216
  }),
6596
8217
  });
8218
+ export const CheckoutForgeSetAutoMergeResponseSchema = z.object({
8219
+ type: z.literal("checkout.forge.set_auto_merge.response"),
8220
+ payload: z.object({
8221
+ cwd: z.string(),
8222
+ enabled: z.boolean(),
8223
+ success: z.boolean(),
8224
+ error: CheckoutErrorSchema.nullable(),
8225
+ requestId: z.string(),
8226
+ }),
8227
+ });
8228
+ // COMPAT(githubAutoMergeRpc): added in v0.1.106, remove after 2026-12-28 once
8229
+ // all supported clients use checkout.forge.set_auto_merge.*.
6597
8230
  export const CheckoutGithubSetAutoMergeResponseSchema = z.object({
6598
8231
  type: z.literal("checkout.github.set_auto_merge.response"),
6599
8232
  payload: z.object({
@@ -6670,6 +8303,29 @@ export const PreviewStopResponseSchema = z.object({
6670
8303
  requestId: z.string(),
6671
8304
  }),
6672
8305
  });
8306
+ export const CheckoutCommitsListResponseSchema = z.object({
8307
+ type: z.literal("checkout.commits.list.response"),
8308
+ payload: z.object({
8309
+ cwd: z.string(),
8310
+ baseRef: z.string().nullable(),
8311
+ commits: z.array(CheckoutCommitSchema),
8312
+ error: CheckoutErrorSchema.nullable(),
8313
+ requestId: z.string(),
8314
+ }),
8315
+ });
8316
+ export const CheckoutCommitFileDiffResponseSchema = z.object({
8317
+ type: z.literal("checkout.commits.file_diff.response"),
8318
+ payload: z.object({
8319
+ cwd: z.string(),
8320
+ sha: z.string(),
8321
+ path: z.string(),
8322
+ // null when the file is absent from the commit or carries no textual diff
8323
+ // (e.g. binary-only changes).
8324
+ file: ParsedDiffFileSchema.nullable(),
8325
+ error: CheckoutErrorSchema.nullable(),
8326
+ requestId: z.string(),
8327
+ }),
8328
+ });
6673
8329
  const CheckoutGithubCheckAnnotationSchema = z.object({
6674
8330
  path: z.string().optional(),
6675
8331
  startLine: z.number().optional(),
@@ -6688,6 +8344,31 @@ const CheckoutGithubCheckJobSchema = z.object({
6688
8344
  logTail: z.string().optional(),
6689
8345
  logTruncated: z.boolean().optional(),
6690
8346
  });
8347
+ // Statuses stay open strings so future forge values cannot break parsing.
8348
+ const CheckoutPipelineJobSchema = z.object({
8349
+ id: z.number(),
8350
+ name: z.string(),
8351
+ stage: z.string(),
8352
+ status: z.string(),
8353
+ rawStatus: z.string(),
8354
+ url: z.string().nullable().optional().default(null),
8355
+ allowFailure: z.boolean().optional().default(false),
8356
+ durationSeconds: z.number().nullable().optional().default(null),
8357
+ });
8358
+ const CheckoutPipelineStageSchema = z.object({
8359
+ name: z.string(),
8360
+ status: z.string(),
8361
+ jobs: z.array(CheckoutPipelineJobSchema).optional().default([]),
8362
+ });
8363
+ const CheckoutPipelineSchema = z.object({
8364
+ id: z.number(),
8365
+ status: z.string(),
8366
+ rawStatus: z.string(),
8367
+ url: z.string().nullable().optional().default(null),
8368
+ ref: z.string().nullable().optional().default(null),
8369
+ sha: z.string().nullable().optional().default(null),
8370
+ stages: z.array(CheckoutPipelineStageSchema).optional().default([]),
8371
+ });
6691
8372
  export const CheckoutGithubCheckDetailsSchema = z.object({
6692
8373
  checkRunId: z.number(),
6693
8374
  workflowRunId: z.number().nullable().optional(),
@@ -6707,13 +8388,28 @@ export const CheckoutGithubCheckDetailsSchema = z.object({
6707
8388
  annotations: z.array(CheckoutGithubCheckAnnotationSchema).optional().default([]),
6708
8389
  failedJobs: z.array(CheckoutGithubCheckJobSchema).optional().default([]),
6709
8390
  truncated: z.boolean().optional().default(false),
8391
+ // No default: server CheckDetails keeps this optional and GitHub leaves it absent.
8392
+ pipeline: CheckoutPipelineSchema.nullable().optional(),
6710
8393
  });
8394
+ export const CheckoutCheckDetailsSchema = CheckoutGithubCheckDetailsSchema;
8395
+ export const CheckoutForgeGetCheckDetailsResponseSchema = z.object({
8396
+ type: z.literal("checkout.forge.get_check_details.response"),
8397
+ payload: z.object({
8398
+ cwd: z.string(),
8399
+ success: z.boolean(),
8400
+ details: CheckoutCheckDetailsSchema.nullable().optional().default(null),
8401
+ error: CheckoutErrorSchema.nullable(),
8402
+ requestId: z.string(),
8403
+ }),
8404
+ });
8405
+ // COMPAT(githubCheckDetailsRpc): added in v0.1.106, remove after 2026-12-28 once
8406
+ // all supported clients use checkout.forge.get_check_details.*.
6711
8407
  export const CheckoutGithubGetCheckDetailsResponseSchema = z.object({
6712
8408
  type: z.literal("checkout.github.get_check_details.response"),
6713
8409
  payload: z.object({
6714
8410
  cwd: z.string(),
6715
8411
  success: z.boolean(),
6716
- details: CheckoutGithubCheckDetailsSchema.nullable().optional().default(null),
8412
+ details: CheckoutCheckDetailsSchema.nullable().optional().default(null),
6717
8413
  error: CheckoutErrorSchema.nullable(),
6718
8414
  requestId: z.string(),
6719
8415
  }),
@@ -6773,6 +8469,16 @@ const PullRequestTimelineCommentItemSchema = z.object({
6773
8469
  // threads under their parent review. Absent on issue comments and on
6774
8470
  // timelines from daemons that predate the field.
6775
8471
  reviewId: z.string().optional(),
8472
+ // Forge-neutral discussion/thread id this comment belongs to, independent of a
8473
+ // file position. GitLab maps its discussion id here so general (non-file)
8474
+ // reply chains group into one thread; file-position threads also carry it.
8475
+ // Absent on standalone comments and on timelines from daemons that predate it.
8476
+ threadId: z.string().optional(),
8477
+ // Forge-neutral resolution state for a thread that has no file position, e.g. a
8478
+ // GitLab general (non-file) discussion that is resolvable. File-position threads
8479
+ // carry their resolution under `location.isResolved` instead. Absent on ordinary
8480
+ // comments, on forges that expose no thread resolution, and on older timelines.
8481
+ threadIsResolved: z.boolean().optional(),
6776
8482
  location: z
6777
8483
  .object({
6778
8484
  path: z.string(),
@@ -6808,6 +8514,10 @@ export const PullRequestTimelineResponseSchema = z.object({
6808
8514
  error: PullRequestTimelineErrorSchema.nullable().optional().default(null),
6809
8515
  requestId: z.string().optional().default(""),
6810
8516
  githubFeaturesEnabled: z.boolean().optional().default(true),
8517
+ // COMPAT(forgeAuthState): added in v0.1.106, remove after 2026-12-27. Optional richer
8518
+ // signal that supersedes githubFeaturesEnabled, mirroring CheckoutPrStatusPayloadSchema.
8519
+ // Drop the boolean once the daemon floor >= v0.1.106.
8520
+ authState: ForgeAuthStateSchema,
6811
8521
  })
6812
8522
  .optional()
6813
8523
  .prefault({}),
@@ -6897,14 +8607,29 @@ export const BranchSuggestionsResponseSchema = z.object({
6897
8607
  requestId: z.string(),
6898
8608
  }),
6899
8609
  });
8610
+ const ForgeSearchResponsePayloadSchema = z.object({
8611
+ items: z.array(z.unknown()),
8612
+ authState: z.unknown().optional(),
8613
+ error: z.string().nullable(),
8614
+ requestId: z.string(),
8615
+ });
8616
+ const GitHubSearchResponsePayloadSchema = z.object({
8617
+ items: z.array(z.unknown()),
8618
+ featuresEnabled: z.boolean().optional(),
8619
+ authState: z.unknown().optional(),
8620
+ githubFeaturesEnabled: z.boolean().optional(),
8621
+ error: z.string().nullable(),
8622
+ requestId: z.string(),
8623
+ });
8624
+ export const ForgeSearchResponseSchema = z.object({
8625
+ type: z.literal("forge.search.response"),
8626
+ payload: ForgeSearchResponsePayloadSchema,
8627
+ });
8628
+ // COMPAT(githubSearchRpc): added in v0.1.106, remove after 2026-12-28 once
8629
+ // clients use forge.search.*.
6900
8630
  export const GitHubSearchResponseSchema = z.object({
6901
8631
  type: z.literal("github_search_response"),
6902
- payload: z.object({
6903
- items: z.array(GitHubSearchItemSchema),
6904
- githubFeaturesEnabled: z.boolean(),
6905
- error: z.string().nullable(),
6906
- requestId: z.string(),
6907
- }),
8632
+ payload: GitHubSearchResponsePayloadSchema,
6908
8633
  });
6909
8634
  export const HostingSearchResponseSchema = z.object({
6910
8635
  type: z.literal("hosting.search.response"),
@@ -6985,6 +8710,64 @@ export const FileExplorerResponseSchema = z.object({
6985
8710
  requestId: z.string(),
6986
8711
  }),
6987
8712
  });
8713
+ export const FileSubscribeResponseSchema = z.object({
8714
+ type: z.literal("fs.file.subscribe.response"),
8715
+ payload: z.object({
8716
+ subscriptionId: z.string(),
8717
+ initial: FileVersionSchema,
8718
+ requestId: z.string(),
8719
+ }),
8720
+ });
8721
+ export const FileUnsubscribeResponseSchema = z.object({
8722
+ type: z.literal("fs.file.unsubscribe.response"),
8723
+ payload: z.object({
8724
+ subscriptionId: z.string(),
8725
+ requestId: z.string(),
8726
+ }),
8727
+ });
8728
+ export const FsFileWriteResultSchema = z.discriminatedUnion("status", [
8729
+ z.object({
8730
+ status: z.literal("written"),
8731
+ modifiedAt: z.string(),
8732
+ size: z.number(),
8733
+ revision: z.string().optional(),
8734
+ }),
8735
+ z.object({ status: z.literal("conflict"), version: FileVersionSchema }),
8736
+ z.object({ status: z.literal("error"), error: z.string() }),
8737
+ ]);
8738
+ export const FsFileWriteResponseSchema = z.object({
8739
+ type: z.literal("fs.file.write.response"),
8740
+ payload: z.object({
8741
+ result: FsFileWriteResultSchema,
8742
+ requestId: z.string(),
8743
+ }),
8744
+ });
8745
+ export const FsFileWriteBinaryResultSchema = z.discriminatedUnion("status", [
8746
+ z.object({
8747
+ status: z.literal("written"),
8748
+ modifiedAt: z.string(),
8749
+ size: z.number(),
8750
+ }),
8751
+ // The target is already there and the request did not ask to replace it.
8752
+ z.object({ status: z.literal("exists") }),
8753
+ z.object({ status: z.literal("error"), error: z.string() }),
8754
+ ]);
8755
+ export const FsFileWriteBinaryResponseSchema = z.object({
8756
+ type: z.literal("fs.file.write_binary.response"),
8757
+ payload: z.object({
8758
+ cwd: z.string(),
8759
+ path: z.string(),
8760
+ result: FsFileWriteBinaryResultSchema,
8761
+ requestId: z.string(),
8762
+ }),
8763
+ });
8764
+ export const FileUpdateSchema = z.object({
8765
+ type: z.literal("fs.file.update"),
8766
+ payload: z.object({
8767
+ subscriptionId: z.string(),
8768
+ version: FileVersionSchema,
8769
+ }),
8770
+ });
6988
8771
  const ProjectIconSchema = z.object({
6989
8772
  data: z.string(),
6990
8773
  mimeType: z.string(),
@@ -7975,7 +9758,72 @@ export const DaemonUpdateProgressMessageSchema = z.object({
7975
9758
  phase: z.enum(["starting", "downloading", "installing", "complete"]),
7976
9759
  }),
7977
9760
  });
9761
+ export const HubExecutionAgentCreateResponseSchema = z.object({
9762
+ type: z.literal("hub.execution.agent.create.response"),
9763
+ payload: z.object({
9764
+ requestId: z.string(),
9765
+ executionId: z.string(),
9766
+ agentId: z.string().nullable(),
9767
+ agent: AgentSnapshotPayloadSchema.nullable(),
9768
+ success: z.boolean(),
9769
+ error: z.string().nullable(),
9770
+ }),
9771
+ });
9772
+ export const HubExecutionControlResponseSchema = z.object({
9773
+ type: z.literal("hub.execution.control.response"),
9774
+ payload: z.object({
9775
+ requestId: z.string(),
9776
+ executionId: z.string(),
9777
+ action: HubExecutionControlActionSchema,
9778
+ success: z.boolean(),
9779
+ error: z.string().nullable(),
9780
+ }),
9781
+ });
9782
+ export const HubExecutionAgentUpdateSchema = z.object({
9783
+ type: z.literal("hub.execution.agent.update"),
9784
+ payload: z.object({
9785
+ executionId: z.string(),
9786
+ agentId: z.string(),
9787
+ agent: AgentSnapshotPayloadSchema,
9788
+ }),
9789
+ });
9790
+ export const HubExecutionAgentStreamSchema = z.object({
9791
+ type: z.literal("hub.execution.agent.stream"),
9792
+ payload: z.object({
9793
+ executionId: z.string(),
9794
+ agentId: z.string(),
9795
+ event: AgentStreamEventPayloadSchema,
9796
+ }),
9797
+ });
9798
+ export const HubExecutionOutboundMessageSchema = z.discriminatedUnion("type", [
9799
+ HubExecutionAgentCreateResponseSchema,
9800
+ HubExecutionControlResponseSchema,
9801
+ HubExecutionAgentUpdateSchema,
9802
+ HubExecutionAgentStreamSchema,
9803
+ ]);
9804
+ export class HubMessageCorrelationError extends Error {
9805
+ constructor(messageType) {
9806
+ super(`Hub message ${messageType} has mismatched agent correlation`);
9807
+ this.name = "HubMessageCorrelationError";
9808
+ }
9809
+ }
9810
+ export function parseHubExecutionOutboundMessage(value) {
9811
+ const message = HubExecutionOutboundMessageSchema.parse(value);
9812
+ const payload = message.payload;
9813
+ if ("agent" in payload &&
9814
+ payload.agent !== null &&
9815
+ "agentId" in payload &&
9816
+ payload.agentId !== null &&
9817
+ payload.agent.id !== payload.agentId) {
9818
+ throw new HubMessageCorrelationError(message.type);
9819
+ }
9820
+ return message;
9821
+ }
7978
9822
  export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
9823
+ HubExecutionAgentCreateResponseSchema,
9824
+ HubExecutionControlResponseSchema,
9825
+ HubExecutionAgentUpdateSchema,
9826
+ HubExecutionAgentStreamSchema,
7979
9827
  BrowserAutomationExecuteRequestSchema,
7980
9828
  ActivityLogMessageSchema,
7981
9829
  AssistantChunkMessageSchema,
@@ -7994,6 +9842,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
7994
9842
  ArtifactUpdateMessageSchema,
7995
9843
  AgentUpdateMessageSchema,
7996
9844
  WorkspaceUpdateMessageSchema,
9845
+ ProjectUpdateMessageSchema,
9846
+ ProjectListResponseMessageSchema,
7997
9847
  ScriptStatusUpdateMessageSchema,
7998
9848
  WorkspaceSetupProgressMessageSchema,
7999
9849
  WorkspaceSetupStatusResponseMessageSchema,
@@ -8006,8 +9856,14 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8006
9856
  ProjectAddResponseSchema,
8007
9857
  ProjectScaffoldResponseSchema,
8008
9858
  ProjectScaffoldProgressSchema,
9859
+ ProjectCreateDirectoryResponseSchema,
8009
9860
  OpenProjectResponseMessageSchema,
9861
+ WorkspaceGithubSearchRepositoriesResponseSchema,
9862
+ ProjectGithubCloneResponseSchema,
8010
9863
  StartWorkspaceScriptResponseMessageSchema,
9864
+ WorkspaceScriptListResponseMessageSchema,
9865
+ WorkspaceScriptStartResponseMessageSchema,
9866
+ WorkspaceScriptStopResponseMessageSchema,
8011
9867
  LegacyListAvailableEditorsResponseMessageSchema,
8012
9868
  LegacyOpenInEditorResponseMessageSchema,
8013
9869
  ArchiveWorkspaceResponseMessageSchema,
@@ -8017,6 +9873,11 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8017
9873
  WorktreeReattachResponseSchema,
8018
9874
  FetchAgentResponseMessageSchema,
8019
9875
  FetchAgentTimelineResponseMessageSchema,
9876
+ ProviderSubagentListResponseMessageSchema,
9877
+ ProviderSubagentTimelineResponseMessageSchema,
9878
+ ProviderSubagentUpdateMessageSchema,
9879
+ SetAgentTimelineSubscriptionResponseMessageSchema,
9880
+ AgentAttentionRequiredMessageSchema,
8020
9881
  AgentForkContextResponseMessageSchema,
8021
9882
  CancelAgentResponseMessageSchema,
8022
9883
  ClearAgentAttentionResponseMessageSchema,
@@ -8026,15 +9887,20 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8026
9887
  SetVoiceModeResponseMessageSchema,
8027
9888
  DaemonGetStatusResponseSchema,
8028
9889
  DaemonGetPairingOfferResponseSchema,
9890
+ HubManagementDaemonConnectResponseSchema,
9891
+ HubManagementDaemonGetStatusResponseSchema,
9892
+ HubManagementDaemonDisconnectResponseSchema,
8029
9893
  DiagnosticsResponseSchema,
8030
9894
  GetDaemonConfigResponseMessageSchema,
8031
9895
  SetDaemonConfigResponseMessageSchema,
9896
+ ConnectorsListToolsResponseSchema,
8032
9897
  SpeechSettingsGetOptionsResponseSchema,
8033
9898
  SpeechTtsPreviewResponseSchema,
8034
9899
  SpeechTtsSpeakResponseSchema,
8035
9900
  SpeechTtsSpeakCancelResponseSchema,
8036
9901
  VisualizerVoiceCuesGenerateResponseSchema,
8037
9902
  AgentPersonalitiesGetStatsResponseSchema,
9903
+ AgentPersonalitiesGenerateProfileResponseSchema,
8038
9904
  ReadProjectConfigResponseMessageSchema,
8039
9905
  WriteProjectConfigResponseMessageSchema,
8040
9906
  SetAgentModeResponseMessageSchema,
@@ -8065,6 +9931,9 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8065
9931
  ProjectLinksUnsetResponseSchema,
8066
9932
  ProjectLinksChangedSchema,
8067
9933
  WorkspaceTitleSetResponseSchema,
9934
+ WorkspacePinSetResponseSchema,
9935
+ WorkspaceRecoveryInspectResponseSchema,
9936
+ WorkspaceRecoveryRestoreResponseSchema,
8068
9937
  WaitForFinishResponseMessageSchema,
8069
9938
  AgentPermissionRequestMessageSchema,
8070
9939
  AgentPermissionResolvedMessageSchema,
@@ -8072,6 +9941,28 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8072
9941
  HistoryAgentsClearArchivedResponseSchema,
8073
9942
  AttachmentsImagesStatsResponseSchema,
8074
9943
  AttachmentsImagesClearResponseSchema,
9944
+ BrainHostStatusResponseSchema,
9945
+ BrainHostStartResponseSchema,
9946
+ BrainHostStopResponseSchema,
9947
+ BrainHostRestartResponseSchema,
9948
+ BrainEvalsGetResponseSchema,
9949
+ BrainNetworkDiscoverResponseSchema,
9950
+ BrainModelsListResponseSchema,
9951
+ BrainRemoteConfigGetResponseSchema,
9952
+ BrainRemoteConfigPatchResponseSchema,
9953
+ BrainModelsScanResponseSchema,
9954
+ BrainCatalogListResponseSchema,
9955
+ BrainRuntimeListResponseSchema,
9956
+ BrainModelsPullResponseSchema,
9957
+ BrainRuntimeInstallResponseSchema,
9958
+ BrainCalibrateResponseSchema,
9959
+ BrainSweepResponseSchema,
9960
+ BrainBenchResponseSchema,
9961
+ BrainJobsListResponseSchema,
9962
+ BrainJobsCancelResponseSchema,
9963
+ BrainHfSearchResponseSchema,
9964
+ BrainHfQuantsResponseSchema,
9965
+ BrainModelsAddResponseSchema,
8075
9966
  AgentArchivedMessageSchema,
8076
9967
  CloseItemsResponseSchema,
8077
9968
  CheckoutStatusResponseSchema,
@@ -8111,7 +10002,11 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8111
10002
  CheckoutRefreshResponseSchema,
8112
10003
  CheckoutPrCreateResponseSchema,
8113
10004
  CheckoutPrMergeResponseSchema,
10005
+ CheckoutForgeSetAutoMergeResponseSchema,
8114
10006
  CheckoutGithubSetAutoMergeResponseSchema,
10007
+ CheckoutCommitsListResponseSchema,
10008
+ CheckoutCommitFileDiffResponseSchema,
10009
+ CheckoutForgeGetCheckDetailsResponseSchema,
8115
10010
  CheckoutGithubGetCheckDetailsResponseSchema,
8116
10011
  PreviewListConfigResponseSchema,
8117
10012
  PreviewStartResponseSchema,
@@ -8126,6 +10021,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8126
10021
  StashListResponseSchema,
8127
10022
  ValidateBranchResponseSchema,
8128
10023
  BranchSuggestionsResponseSchema,
10024
+ ForgeSearchResponseSchema,
8129
10025
  GitHubSearchResponseSchema,
8130
10026
  HostingSearchResponseSchema,
8131
10027
  HostingAuthStatusResponseSchema,
@@ -8136,6 +10032,11 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8136
10032
  OttoWorktreeArchiveResponseSchema,
8137
10033
  CreateOttoWorktreeResponseSchema,
8138
10034
  FileExplorerResponseSchema,
10035
+ FileSubscribeResponseSchema,
10036
+ FileUnsubscribeResponseSchema,
10037
+ FsFileWriteResponseSchema,
10038
+ FsFileWriteBinaryResponseSchema,
10039
+ FileUpdateSchema,
8139
10040
  ProjectIconResponseSchema,
8140
10041
  FileDownloadTokenResponseSchema,
8141
10042
  FileUploadResponseSchema,
@@ -8255,8 +10156,11 @@ export const WSHelloMessageSchema = z.object({
8255
10156
  voice: z.boolean().optional(),
8256
10157
  pushNotifications: z.boolean().optional(),
8257
10158
  [CLIENT_CAPS.reasoningMergeEnum]: z.boolean().optional(),
10159
+ [CLIENT_CAPS.selectiveAgentTimeline]: z.boolean().optional(),
8258
10160
  [CLIENT_CAPS.customModeIcons]: z.boolean().optional(),
8259
10161
  [CLIENT_CAPS.terminalReflowableSnapshot]: z.boolean().optional(),
10162
+ [CLIENT_CAPS.providerSubagents]: z.boolean().optional(),
10163
+ [CLIENT_CAPS.projectUpdates]: z.boolean().optional(),
8260
10164
  [CLIENT_CAPS.browserHost]: BrowserAutomationHostCapabilitySchema.optional(),
8261
10165
  })
8262
10166
  .passthrough()