@otto-code/protocol 0.7.5 → 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
@@ -431,6 +431,12 @@ export const MutableBrainRemoteConfigSchema = z
431
431
  secure: z.boolean().default(false),
432
432
  // Secret: masked with DAEMON_CONFIG_SECRET_SENTINEL on the way out.
433
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),
434
440
  })
435
441
  .passthrough();
436
442
  export const MutableBrainConfigSchema = z
@@ -446,6 +452,7 @@ export const MutableBrainConfigSchema = z
446
452
  port: 1234,
447
453
  secure: false,
448
454
  authToken: null,
455
+ certFingerprint: null,
449
456
  }),
450
457
  listen: z
451
458
  .object({
@@ -507,6 +514,7 @@ const MutableBrainRemotePatchSchema = z
507
514
  port: z.number().int(),
508
515
  secure: z.boolean(),
509
516
  authToken: z.string().nullable(),
517
+ certFingerprint: z.string().nullable(),
510
518
  })
511
519
  .partial()
512
520
  .passthrough();
@@ -538,7 +546,7 @@ export const DEFAULT_MUTABLE_BRAIN_CONFIG = {
538
546
  enabled: false,
539
547
  autoStart: false,
540
548
  mode: "local",
541
- remote: { host: "", port: 1234, secure: false, authToken: null },
549
+ remote: { host: "", port: 1234, secure: false, authToken: null, certFingerprint: null },
542
550
  listen: { host: "127.0.0.1", port: 1234 },
543
551
  defaultModel: null,
544
552
  lockModel: false,
@@ -664,6 +672,7 @@ export const MutableDaemonConfigPatchSchema = z
664
672
  providers: z
665
673
  .record(z.string(), MutableDaemonProviderConfigSchema.partial().passthrough().nullable())
666
674
  .optional(),
675
+ removeProviders: z.array(z.string().min(1)).optional(),
667
676
  metadataGeneration: MutableMetadataGenerationConfigSchema.partial().optional(),
668
677
  autoArchiveAfterMerge: z.boolean().optional(),
669
678
  // Gated by server_info features.hideMergeIntoBaseSetting.
@@ -796,6 +805,7 @@ export const ProviderSnapshotEntrySchema = z.object({
796
805
  provider: AgentProviderSchema,
797
806
  status: ProviderStatusSchema,
798
807
  enabled: z.boolean().optional().default(true),
808
+ source: z.enum(["builtin", "custom"]).optional(),
799
809
  error: z.string().optional(),
800
810
  models: z.array(AgentModelDefinitionSchema).optional(),
801
811
  modes: z.array(AgentModeSchema).optional(),
@@ -1069,6 +1079,7 @@ export const AgentTimelineItemPayloadSchema = z.union([
1069
1079
  type: z.literal("user_message"),
1070
1080
  text: z.string(),
1071
1081
  messageId: z.string().optional(),
1082
+ clientMessageId: z.string().optional(),
1072
1083
  }),
1073
1084
  z.object({
1074
1085
  type: z.literal("assistant_message"),
@@ -1096,6 +1107,8 @@ export const AgentTimelineItemPayloadSchema = z.union([
1096
1107
  // COMPAT(compactionFailedStatus): "failed" added in v0.4.3. Clients older
1097
1108
  // than that drop the whole timeline event on parse and keep showing the
1098
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.
1099
1112
  status: z.enum(["loading", "completed", "failed"]),
1100
1113
  trigger: z.enum(["auto", "manual"]).optional(),
1101
1114
  preTokens: z.number().optional(),
@@ -1157,6 +1170,7 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
1157
1170
  body: z.string(),
1158
1171
  data: z.object({
1159
1172
  serverId: z.string(),
1173
+ workspaceId: z.string().optional(),
1160
1174
  agentId: z.string(),
1161
1175
  reason: z.enum(["finished", "error", "permission"]),
1162
1176
  }),
@@ -1291,7 +1305,9 @@ export const AgentSnapshotPayloadSchema = z.object({
1291
1305
  // a finished agent isn't "running Bash"). Both are purely additive optional
1292
1306
  // leaves: a provider that can't report them leaves them absent and the row
1293
1307
  // omits the readout rather than showing a wrong value.
1294
- // 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.
1295
1311
  // See docs/chat-lifecycle.md (the subagents track).
1296
1312
  toolUseCount: z.number().optional(),
1297
1313
  currentTool: z.string().optional(),
@@ -2002,6 +2018,22 @@ export const WorkspaceTitleSetRequestSchema = z.object({
2002
2018
  title: z.string().nullable(),
2003
2019
  requestId: z.string(),
2004
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
+ });
2005
2037
  export const SetVoiceModeMessageSchema = z.object({
2006
2038
  type: z.literal("set_voice_mode"),
2007
2039
  enabled: z.boolean(),
@@ -2018,6 +2050,18 @@ export const GitHubPrAttachmentSchema = z.object({
2018
2050
  baseRefName: z.string().nullable().optional(),
2019
2051
  headRefName: z.string().nullable().optional(),
2020
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
+ });
2021
2065
  export const GitHubIssueAttachmentSchema = z.object({
2022
2066
  type: z.literal("github_issue"),
2023
2067
  mimeType: z.literal("application/github-issue"),
@@ -2026,10 +2070,13 @@ export const GitHubIssueAttachmentSchema = z.object({
2026
2070
  url: z.string(),
2027
2071
  body: z.string().nullable().optional(),
2028
2072
  });
2029
- // Provider-neutral successors to github_pr/github_issue. New clients send
2030
- // these when server_info features.gitHostingProviders is set; the github_*
2031
- // kinds remain accepted forever (protocol contract) and are still what a new
2032
- // 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.
2033
2080
  export const HostingPrAttachmentSchema = z.object({
2034
2081
  type: z.literal("hosting_pr"),
2035
2082
  mimeType: z.literal("application/otto-hosting-pr"),
@@ -2050,6 +2097,16 @@ export const HostingIssueAttachmentSchema = z.object({
2050
2097
  url: z.string(),
2051
2098
  body: z.string().nullable().optional(),
2052
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
+ });
2053
2110
  export const TextAttachmentSchema = z
2054
2111
  .object({
2055
2112
  type: z.literal("text"),
@@ -2096,6 +2153,8 @@ export const UploadedFileAttachmentSchema = z.object({
2096
2153
  path: z.string(),
2097
2154
  });
2098
2155
  export const AgentAttachmentSchema = z.discriminatedUnion("type", [
2156
+ ForgeChangeRequestAttachmentSchema,
2157
+ ForgeIssueAttachmentSchema,
2099
2158
  GitHubPrAttachmentSchema,
2100
2159
  GitHubIssueAttachmentSchema,
2101
2160
  HostingPrAttachmentSchema,
@@ -2118,6 +2177,12 @@ function normalizeAgentAttachments(input) {
2118
2177
  return normalized;
2119
2178
  }
2120
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
+ });
2121
2186
  const ImageAttachmentSchema = z.object({
2122
2187
  data: z.string(), // base64 encoded image
2123
2188
  mimeType: z.string(), // e.g., "image/jpeg", "image/png"
@@ -2192,6 +2257,10 @@ export const FetchWorkspacesRequestMessageSchema = z.object({
2192
2257
  })
2193
2258
  .optional(),
2194
2259
  });
2260
+ export const ProjectListRequestMessageSchema = z.object({
2261
+ type: z.literal("project.list.request"),
2262
+ requestId: z.string(),
2263
+ });
2195
2264
  export const FetchAgentHistoryRequestMessageSchema = z.object({
2196
2265
  type: z.literal("fetch_agent_history_request"),
2197
2266
  requestId: z.string(),
@@ -2289,6 +2358,21 @@ export const DaemonGetPairingOfferRequestSchema = z.object({
2289
2358
  type: z.literal("daemon.get_pairing_offer.request"),
2290
2359
  requestId: z.string(),
2291
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
+ });
2292
2376
  export const DiagnosticsRequestSchema = z.object({
2293
2377
  type: z.literal("diagnostics.request"),
2294
2378
  requestId: z.string(),
@@ -2477,6 +2561,9 @@ const GitSetupOptionsSchema = z.object({
2477
2561
  worktreeSlug: z.string().optional(),
2478
2562
  refName: z.string().min(1).optional(),
2479
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.
2480
2567
  githubPrNumber: z.number().int().positive().optional(),
2481
2568
  });
2482
2569
  export const CreateAgentWorktreeTargetSchema = z.discriminatedUnion("mode", [
@@ -2505,6 +2592,9 @@ export const CreateAgentRequestMessageSchema = z.object({
2505
2592
  personality: z.string().optional(),
2506
2593
  env: z.record(z.string(), z.string()).optional(),
2507
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(),
2508
2598
  worktreeName: z.string().optional(),
2509
2599
  initialPrompt: z.string().optional(),
2510
2600
  clientMessageId: z.string().optional(),
@@ -2776,9 +2866,29 @@ export const FetchAgentTimelineRequestMessageSchema = z.object({
2776
2866
  // Default should be projected for app timeline loading.
2777
2867
  projection: z.enum(["projected", "canonical"]).optional(),
2778
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
+ });
2779
2888
  export const AgentForkContextRequestMessageSchema = z.object({
2780
2889
  type: z.literal("agent.fork_context.request"),
2781
2890
  agentId: z.string(),
2891
+ boundaryCursor: AgentTimelineCursorSchema.optional(),
2782
2892
  boundaryMessageId: z.string().optional(),
2783
2893
  requestId: z.string(),
2784
2894
  });
@@ -3498,6 +3608,48 @@ export const AgentWorkspaceTransferResponseMessageSchema = z.object({
3498
3608
  type: z.literal("agent.workspace.transfer.response"),
3499
3609
  payload: AgentWorkspaceTransferResponsePayloadSchema,
3500
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
+ });
3501
3653
  export const SetVoiceModeResponseMessageSchema = z.object({
3502
3654
  type: z.literal("set_voice_mode_response"),
3503
3655
  payload: z.object({
@@ -3958,6 +4110,15 @@ export const CheckoutPrMergeRequestSchema = z.object({
3958
4110
  mergeMethod: z.enum(["merge", "squash", "rebase"]),
3959
4111
  requestId: z.string(),
3960
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.*.
3961
4122
  export const CheckoutGithubSetAutoMergeRequestSchema = z.object({
3962
4123
  type: z.literal("checkout.github.set_auto_merge.request"),
3963
4124
  cwd: z.string(),
@@ -3965,16 +4126,63 @@ export const CheckoutGithubSetAutoMergeRequestSchema = z.object({
3965
4126
  mergeMethod: z.enum(["merge", "squash", "rebase"]).optional(),
3966
4127
  requestId: z.string(),
3967
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
+ });
3968
4158
  const GitHubRepoSegmentSchema = z.string().regex(/^[A-Za-z0-9._-]+$/);
3969
- export const CheckoutGithubGetCheckDetailsRequestSchema = z.object({
3970
- type: z.literal("checkout.github.get_check_details.request"),
4159
+ const CheckoutCheckDetailsRequestPayloadSchema = z.object({
3971
4160
  cwd: z.string(),
3972
- repoOwner: GitHubRepoSegmentSchema,
3973
- repoName: GitHubRepoSegmentSchema,
3974
- 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(),
3975
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(),
3976
4176
  requestId: z.string(),
3977
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
+ });
3978
4186
  export const CheckoutPrStatusRequestSchema = z.object({
3979
4187
  type: z.literal("checkout_pr_status_request"),
3980
4188
  cwd: z.string(),
@@ -4063,17 +4271,41 @@ export const BranchSuggestionsRequestSchema = z.object({
4063
4271
  });
4064
4272
  export const GitHubSearchItemSchema = z.object({
4065
4273
  kind: z.enum(["issue", "pr"]),
4274
+ forge: z.string().optional(),
4066
4275
  number: z.number(),
4067
4276
  title: z.string(),
4068
4277
  url: z.string(),
4069
4278
  state: z.string(),
4070
4279
  body: z.string().nullable(),
4071
4280
  labels: z.array(z.string()),
4281
+ projectPath: z.string().optional(),
4072
4282
  baseRefName: z.string().nullable().optional(),
4073
4283
  headRefName: z.string().nullable().optional(),
4074
4284
  updatedAt: z.string().optional(),
4075
4285
  });
4076
- 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.*.
4077
4309
  export const GitHubSearchRequestSchema = z.object({
4078
4310
  type: z.literal("github_search_request"),
4079
4311
  cwd: z.string(),
@@ -4154,6 +4386,9 @@ export const CreateOttoWorktreeRequestSchema = z.object({
4154
4386
  firstAgentContext: FirstAgentContextSchema.optional(),
4155
4387
  refName: z.string().min(1).optional(),
4156
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 }.
4157
4392
  githubPrNumber: z.number().int().positive().optional(),
4158
4393
  requestId: z.string(),
4159
4394
  });
@@ -4181,6 +4416,8 @@ export const OpenProjectRequestSchema = z.object({
4181
4416
  cwd: z.string(),
4182
4417
  requestId: z.string(),
4183
4418
  });
4419
+ // Smallest shorthand repo path is "a/b": owner, slash, repository.
4420
+ const MIN_REPOSITORY_PATH_LENGTH = 3;
4184
4421
  export const ProjectAddRequestSchema = z.object({
4185
4422
  type: z.literal("project.add.request"),
4186
4423
  cwd: z.string(),
@@ -4276,6 +4513,35 @@ export const HostingListOwnersRequestSchema = z.object({
4276
4513
  provider: GitHostingProviderIdWireSchema,
4277
4514
  requestId: z.string(),
4278
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
+ });
4279
4545
  export const ArchiveWorkspaceRequestSchema = z.object({
4280
4546
  type: z.literal("archive_workspace_request"),
4281
4547
  workspaceId: z.string(),
@@ -4350,6 +4616,11 @@ export const WorkspaceCreateRequestSchema = z.object({
4350
4616
  // the repository default.
4351
4617
  refName: z.string().min(1).optional(),
4352
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.
4353
4624
  githubPrNumber: z.number().int().positive().optional(),
4354
4625
  worktreeSlug: z.string().optional(),
4355
4626
  }),
@@ -4439,6 +4710,7 @@ const FileExplorerFileSchema = z.object({
4439
4710
  // inline JSON read path only); old daemons omit both fields.
4440
4711
  eol: FileEolSchema.optional(),
4441
4712
  hash: z.string().optional(),
4713
+ revision: z.string().optional(),
4442
4714
  });
4443
4715
  const FileExplorerDirectorySchema = z.object({
4444
4716
  path: z.string(),
@@ -4452,6 +4724,103 @@ export const FileExplorerRequestSchema = z.object({
4452
4724
  requestId: z.string(),
4453
4725
  acceptBinary: z.boolean().optional(),
4454
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
+ });
4455
4824
  export const ProjectIconRequestSchema = z.object({
4456
4825
  type: z.literal("project_icon_request"),
4457
4826
  cwd: z.string(),
@@ -4901,6 +5270,16 @@ export const CreateTerminalRequestSchema = z.object({
4901
5270
  agentId: z.string().optional(),
4902
5271
  command: z.string().optional(),
4903
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(),
4904
5283
  requestId: z.string(),
4905
5284
  });
4906
5285
  export const RenameTerminalRequestSchema = z.object({
@@ -4915,6 +5294,31 @@ export const StartWorkspaceScriptRequestSchema = z.object({
4915
5294
  scriptName: z.string(),
4916
5295
  requestId: z.string(),
4917
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
+ });
4918
5322
  export const SubscribeTerminalRequestSchema = z.object({
4919
5323
  type: z.literal("subscribe_terminal_request"),
4920
5324
  terminalId: z.string(),
@@ -4965,7 +5369,31 @@ export const CaptureTerminalRequestSchema = z.object({
4965
5369
  stripAnsi: z.boolean().default(true),
4966
5370
  requestId: z.string(),
4967
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
+ });
4968
5394
  export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5395
+ HubExecutionAgentCreateRequestSchema,
5396
+ HubExecutionControlRequestSchema,
4969
5397
  BrowserAutomationExecuteResponseSchema,
4970
5398
  VoiceAudioChunkMessageSchema,
4971
5399
  AbortRequestMessageSchema,
@@ -4974,6 +5402,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
4974
5402
  FetchAgentHistoryRequestMessageSchema,
4975
5403
  FetchRecentProviderSessionsRequestMessageSchema,
4976
5404
  FetchWorkspacesRequestMessageSchema,
5405
+ ProjectListRequestMessageSchema,
4977
5406
  FetchAgentRequestMessageSchema,
4978
5407
  DeleteAgentRequestMessageSchema,
4979
5408
  ArchiveAgentRequestMessageSchema,
@@ -5010,11 +5439,17 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5010
5439
  ProjectLinksSetRequestSchema,
5011
5440
  ProjectLinksUnsetRequestSchema,
5012
5441
  WorkspaceTitleSetRequestSchema,
5442
+ WorkspacePinSetRequestSchema,
5443
+ WorkspaceRecoveryInspectRequestSchema,
5444
+ WorkspaceRecoveryRestoreRequestSchema,
5013
5445
  SetVoiceModeMessageSchema,
5014
5446
  SendAgentMessageRequestSchema,
5015
5447
  WaitForFinishRequestSchema,
5016
5448
  DaemonGetStatusRequestSchema,
5017
5449
  DaemonGetPairingOfferRequestSchema,
5450
+ HubManagementDaemonConnectRequestSchema,
5451
+ HubManagementDaemonGetStatusRequestSchema,
5452
+ HubManagementDaemonDisconnectRequestSchema,
5018
5453
  DiagnosticsRequestSchema,
5019
5454
  GetDaemonConfigRequestMessageSchema,
5020
5455
  SetDaemonConfigRequestMessageSchema,
@@ -5061,6 +5496,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5061
5496
  RestartServerRequestMessageSchema,
5062
5497
  DaemonUpdateRequestMessageSchema,
5063
5498
  FetchAgentTimelineRequestMessageSchema,
5499
+ ProviderSubagentListRequestMessageSchema,
5500
+ ProviderSubagentTimelineRequestMessageSchema,
5501
+ SetAgentTimelineSubscriptionRequestMessageSchema,
5064
5502
  AgentForkContextRequestMessageSchema,
5065
5503
  SetAgentModeRequestMessageSchema,
5066
5504
  SetAgentModelRequestMessageSchema,
@@ -5110,7 +5548,11 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5110
5548
  CheckoutRefreshRequestSchema,
5111
5549
  CheckoutPrCreateRequestSchema,
5112
5550
  CheckoutPrMergeRequestSchema,
5551
+ CheckoutForgeSetAutoMergeRequestSchema,
5113
5552
  CheckoutGithubSetAutoMergeRequestSchema,
5553
+ CheckoutCommitsListRequestSchema,
5554
+ CheckoutCommitFileDiffRequestSchema,
5555
+ CheckoutForgeGetCheckDetailsRequestSchema,
5114
5556
  CheckoutGithubGetCheckDetailsRequestSchema,
5115
5557
  PreviewListConfigRequestSchema,
5116
5558
  PreviewStartRequestSchema,
@@ -5125,6 +5567,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5125
5567
  StashListRequestSchema,
5126
5568
  ValidateBranchRequestSchema,
5127
5569
  BranchSuggestionsRequestSchema,
5570
+ ForgeSearchRequestSchema,
5128
5571
  GitHubSearchRequestSchema,
5129
5572
  HostingSearchRequestSchema,
5130
5573
  HostingAuthStatusRequestSchema,
@@ -5140,6 +5583,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5140
5583
  ProjectScaffoldRequestSchema,
5141
5584
  HostingListRepositoriesRequestSchema,
5142
5585
  HostingListOwnersRequestSchema,
5586
+ ProjectCreateDirectoryRequestSchema,
5587
+ WorkspaceGithubSearchRepositoriesRequestSchema,
5588
+ ProjectGithubCloneRequestSchema,
5143
5589
  ArchiveWorkspaceRequestSchema,
5144
5590
  WorkspaceArchivePreflightRequestSchema,
5145
5591
  WorktreeBaseRefSetRequestSchema,
@@ -5148,6 +5594,10 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5148
5594
  WorkspaceCreateRequestSchema,
5149
5595
  WorkspaceClearAttentionRequestSchema,
5150
5596
  FileExplorerRequestSchema,
5597
+ FileSubscribeRequestSchema,
5598
+ FileUnsubscribeRequestSchema,
5599
+ FsFileWriteRequestSchema,
5600
+ FsFileWriteBinaryRequestSchema,
5151
5601
  ProjectIconRequestSchema,
5152
5602
  FileDownloadTokenRequestSchema,
5153
5603
  FileUploadRequestSchema,
@@ -5187,6 +5637,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
5187
5637
  CreateTerminalRequestSchema,
5188
5638
  RenameTerminalRequestSchema,
5189
5639
  StartWorkspaceScriptRequestSchema,
5640
+ WorkspaceScriptListRequestSchema,
5641
+ WorkspaceScriptStartRequestSchema,
5642
+ WorkspaceScriptStopRequestSchema,
5190
5643
  SubscribeTerminalRequestSchema,
5191
5644
  UnsubscribeTerminalRequestSchema,
5192
5645
  TerminalInputSchema,
@@ -5361,14 +5814,23 @@ export const ServerInfoStatusPayloadSchema = z
5361
5814
  serverId: z.string().trim().min(1),
5362
5815
  hostname: ServerInfoHostnameSchema.optional(),
5363
5816
  version: ServerInfoVersionSchema.optional(),
5817
+ // COMPAT(desktopManaged): added in v0.1.X, remove optional parsing after 2027-01-16.
5818
+ desktopManaged: z.boolean().optional(),
5364
5819
  capabilities: ServerCapabilitiesFromUnknownSchema.optional(),
5365
5820
  // COMPAT(providersSnapshot): added in v0.1.48, remove gating when all clients use snapshot
5366
5821
  features: z
5367
5822
  .object({
5368
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(),
5369
5827
  checkoutGithubSetAutoMerge: z.boolean().optional(),
5370
5828
  // COMPAT(githubCheckDetails): added in v0.1.92, remove gate after 2026-12-08.
5371
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(),
5372
5834
  // COMPAT(daemonStatusRpc): added in v0.1.76, remove gate after 2026-11-18.
5373
5835
  daemonStatusRpc: z.boolean().optional(),
5374
5836
  // COMPAT(terminalRestoreModes): added in v0.1.81, remove gate after 2026-11-23.
@@ -5391,10 +5853,16 @@ export const ServerInfoStatusPayloadSchema = z
5391
5853
  projectScaffold: z.boolean().optional(),
5392
5854
  // COMPAT(worktreeRestore): added in v0.1.97, drop the gate when floor >= v0.1.97
5393
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(),
5394
5860
  // COMPAT(providerUsageList): added in v0.1.98, drop the gate when daemon floor >= v0.1.98.
5395
5861
  providerUsageList: z.boolean().optional(),
5396
5862
  // COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
5397
5863
  agentDetach: z.boolean().optional(),
5864
+ // COMPAT(agentThinkingUpdate): added in v0.2.4, remove gate after 2027-01-28.
5865
+ agentThinkingUpdate: z.boolean().optional(),
5398
5866
  // COMPAT(daemonDiagnostics): added in v0.1.100, remove gate after 2026-12-25 once daemon floor >= v0.1.100.
5399
5867
  daemonDiagnostics: z.boolean().optional(),
5400
5868
  // COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
@@ -5494,6 +5962,16 @@ export const ServerInfoStatusPayloadSchema = z
5494
5962
  // client-side fallback could read.
5495
5963
  // COMPAT(personalityMemory): added in v0.7.0, drop the gate when daemon floor >= v0.7.0.
5496
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(),
5497
5975
  // COMPAT(projectSearch): added in v0.4.4, drop the gate when daemon floor >= v0.4.4.
5498
5976
  projectSearch: z.boolean().optional(),
5499
5977
  // COMPAT(codeIndex): added in v0.4.4, drop the gate when daemon floor >= v0.4.4.
@@ -5685,6 +6163,13 @@ export const ServerInfoStatusPayloadSchema = z
5685
6163
  // There is no client-side substitute (the client never touches the
5686
6164
  // filesystem), so an old daemon simply does not get the menu items.
5687
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(),
5688
6173
  // COMPAT(attachmentStorage): added in v0.7.1, drop the gate when daemon floor >= v0.7.1.
5689
6174
  // Set when the daemon serves `attachments.images.get_stats` and
5690
6175
  // `attachments.images.clear` — the readout and reclaim for the images it
@@ -5699,6 +6184,40 @@ export const ServerInfoStatusPayloadSchema = z
5699
6184
  // the same directory. The client cannot restamp ownership itself (it is
5700
6185
  // daemon state), so an old daemon simply does not get the menu item.
5701
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(),
5702
6221
  })
5703
6222
  .optional(),
5704
6223
  })
@@ -5918,8 +6437,50 @@ export const ProjectPlacementPayloadSchema = z.object({
5918
6437
  });
5919
6438
  export const WorkspaceScriptLifecycleSchema = z.enum(["running", "stopped"]);
5920
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
+ });
5921
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
+ */
5922
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(),
5923
6484
  type: z.enum(["script", "service"]).optional().default("service"),
5924
6485
  hostname: z.string(),
5925
6486
  port: z.number().int().positive().nullable(),
@@ -6003,7 +6564,9 @@ export const WorkspaceDescriptorPayloadSchema = z
6003
6564
  projectRootPath: z.string(),
6004
6565
  workspaceDirectory: z.string().optional(),
6005
6566
  projectKind: z.enum(["git", "non_git", "directory"]),
6006
- // 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.
6007
6570
  workspaceKind: z.enum(["directory", "local_checkout", "checkout", "worktree"]),
6008
6571
  name: z.string(),
6009
6572
  // COMPAT(workspaceTitles): added in v0.1.97, drop the optional gate when floor >= v0.1.97.
@@ -6012,6 +6575,8 @@ export const WorkspaceDescriptorPayloadSchema = z
6012
6575
  // its input and offer a "reset to branch name" action. Null means the name
6013
6576
  // is derived from the branch/directory.
6014
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(),
6015
6580
  archivingAt: z.string().nullable().optional().default(null),
6016
6581
  status: WorkspaceStateBucketSchema,
6017
6582
  // Best-effort workspace status entry timestamp. Old daemons omit the
@@ -6033,6 +6598,10 @@ export const WorkspaceDescriptorPayloadSchema = z
6033
6598
  scripts: z.array(WorkspaceScriptPayloadSchema).default([]),
6034
6599
  gitRuntime: WorkspaceGitRuntimePayloadSchema,
6035
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(),
6036
6605
  project: ProjectPlacementPayloadSchema.optional(),
6037
6606
  })
6038
6607
  .transform((workspace) => ({
@@ -6124,6 +6693,8 @@ export const FetchRecentProviderSessionsResponseMessageSchema = z.object({
6124
6693
  // workspace is archived.
6125
6694
  export const WorkspaceProjectDescriptorPayloadSchema = z.object({
6126
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(),
6127
6698
  projectDisplayName: z.string(),
6128
6699
  projectCustomName: z.string().nullable().optional(),
6129
6700
  projectRootPath: z.string(),
@@ -6185,6 +6756,20 @@ export const ProjectUpdatedNotificationSchema = z.object({
6185
6756
  hasActiveWorkspaces: z.boolean(),
6186
6757
  }),
6187
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
+ });
6188
6773
  export const ScriptStatusUpdateMessageSchema = z.object({
6189
6774
  type: z.literal("script_status_update"),
6190
6775
  payload: z.object({
@@ -6316,6 +6901,70 @@ export const HostingListOwnersResponseSchema = z.object({
6316
6901
  error: z.string().nullable(),
6317
6902
  }),
6318
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
+ });
6319
6968
  export const StartWorkspaceScriptResponseMessageSchema = z.object({
6320
6969
  type: z.literal("start_workspace_script_response"),
6321
6970
  payload: z.object({
@@ -6326,6 +6975,26 @@ export const StartWorkspaceScriptResponseMessageSchema = z.object({
6326
6975
  error: z.string().nullable(),
6327
6976
  }),
6328
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
+ });
6329
6998
  // COMPAT(desktopEditorBridge): added in v0.1.88, remove after 2026-12-03 once old clients no longer parse daemon editor RPC responses.
6330
6999
  export const LegacyListAvailableEditorsResponseMessageSchema = z.object({
6331
7000
  type: z.literal("list_available_editors_response"),
@@ -6490,6 +7159,106 @@ export const FetchAgentTimelineResponseMessageSchema = z.object({
6490
7159
  error: z.string().nullable(),
6491
7160
  }),
6492
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
+ });
6493
7262
  export const AgentForkContextResponseMessageSchema = z.object({
6494
7263
  type: z.literal("agent.fork_context.response"),
6495
7264
  payload: z.object({
@@ -6498,6 +7267,7 @@ export const AgentForkContextResponseMessageSchema = z.object({
6498
7267
  attachment: TextAttachmentSchema.nullable(),
6499
7268
  itemCount: z.number().int().nonnegative(),
6500
7269
  boundaryMessageId: z.string().nullable(),
7270
+ boundaryCursor: AgentTimelineCursorSchema.nullable().optional(),
6501
7271
  error: z.string().nullable(),
6502
7272
  }),
6503
7273
  });
@@ -6558,6 +7328,7 @@ export const CancelAgentResponseMessageSchema = z.object({
6558
7328
  // can say "nothing to stop" instead of silently no-oping. Purely additive;
6559
7329
  // absent ⇒ unknown (old daemon). See docs/agent-lifecycle.md (Item 2).
6560
7330
  cancelled: z.boolean().optional(),
7331
+ error: z.string().nullable().optional(),
6561
7332
  }),
6562
7333
  });
6563
7334
  export const ClearAgentAttentionResponseMessageSchema = z.object({
@@ -6766,6 +7537,37 @@ export const DaemonGetStatusResponseSchema = z.object({
6766
7537
  })
6767
7538
  .passthrough(),
6768
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
+ });
6769
7571
  export const DaemonGetPairingOfferResponseSchema = z.object({
6770
7572
  type: z.literal("daemon.get_pairing_offer.response"),
6771
7573
  payload: z
@@ -6979,8 +7781,7 @@ const CheckoutPrGithubRepositoryPolicySchema = z
6979
7781
  rebaseMergeAllowed: false,
6980
7782
  viewerDefaultMergeMethod: null,
6981
7783
  });
6982
- const CheckoutPrGithubStatusSchema = z
6983
- .object({
7784
+ const CheckoutPrGithubStatusObjectSchema = z.object({
6984
7785
  mergeStateStatus: z.string().nullable().optional().default(null),
6985
7786
  autoMergeRequest: CheckoutPrGithubAutoMergeRequestSchema,
6986
7787
  viewerCanEnableAutoMerge: z.boolean().optional().default(false),
@@ -6990,9 +7791,23 @@ const CheckoutPrGithubStatusSchema = z
6990
7791
  repository: CheckoutPrGithubRepositoryPolicySchema,
6991
7792
  isMergeQueueEnabled: z.boolean().optional().default(false),
6992
7793
  isInMergeQueue: z.boolean().optional().default(false),
6993
- })
6994
- .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();
6995
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(),
6996
7811
  number: z.number().optional(),
6997
7812
  url: z.string(),
6998
7813
  title: z.string(),
@@ -7025,6 +7840,14 @@ export const CheckoutPrStatusSchema = z.object({
7025
7840
  github: CheckoutPrGithubStatusSchema,
7026
7841
  // Provider-neutral per-PR hosting facts. Absent from old daemons; for
7027
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.
7028
7851
  hosting: z
7029
7852
  .object({
7030
7853
  provider: GitHostingProviderIdWireSchema,
@@ -7038,7 +7861,9 @@ export const CheckoutPrStatusSchema = z.object({
7038
7861
  .optional(),
7039
7862
  })
7040
7863
  .optional(),
7864
+ forgeSpecific: CheckoutPrForgeSpecificSchema,
7041
7865
  });
7866
+ export const ForgeAuthStateSchema = z.unknown().optional();
7042
7867
  const CheckoutPrStatusPayloadSchema = z.object({
7043
7868
  cwd: z.string(),
7044
7869
  status: CheckoutPrStatusSchema.nullable(),
@@ -7048,6 +7873,9 @@ const CheckoutPrStatusPayloadSchema = z.object({
7048
7873
  githubFeaturesEnabled: z.boolean(),
7049
7874
  // Provider-neutral enablement. Present even when status is null so clients
7050
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".
7051
7879
  hosting: z
7052
7880
  .object({
7053
7881
  provider: GitHostingProviderIdWireSchema,
@@ -7055,6 +7883,13 @@ const CheckoutPrStatusPayloadSchema = z.object({
7055
7883
  capabilities: GitHostingCapabilitiesSchema.optional(),
7056
7884
  })
7057
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"),
7058
7893
  error: CheckoutErrorSchema.nullable(),
7059
7894
  requestId: z.string(),
7060
7895
  });
@@ -7082,6 +7917,8 @@ const CheckoutDiffSubscriptionPayloadSchema = z.object({
7082
7917
  cwd: z.string(),
7083
7918
  files: z.array(ParsedDiffFileSchema),
7084
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(),
7085
7922
  });
7086
7923
  export const SubscribeCheckoutDiffResponseSchema = z.object({
7087
7924
  type: z.literal("subscribe_checkout_diff_response"),
@@ -7378,6 +8215,18 @@ export const CheckoutPrMergeResponseSchema = z.object({
7378
8215
  requestId: z.string(),
7379
8216
  }),
7380
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.*.
7381
8230
  export const CheckoutGithubSetAutoMergeResponseSchema = z.object({
7382
8231
  type: z.literal("checkout.github.set_auto_merge.response"),
7383
8232
  payload: z.object({
@@ -7454,6 +8303,29 @@ export const PreviewStopResponseSchema = z.object({
7454
8303
  requestId: z.string(),
7455
8304
  }),
7456
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
+ });
7457
8329
  const CheckoutGithubCheckAnnotationSchema = z.object({
7458
8330
  path: z.string().optional(),
7459
8331
  startLine: z.number().optional(),
@@ -7472,6 +8344,31 @@ const CheckoutGithubCheckJobSchema = z.object({
7472
8344
  logTail: z.string().optional(),
7473
8345
  logTruncated: z.boolean().optional(),
7474
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
+ });
7475
8372
  export const CheckoutGithubCheckDetailsSchema = z.object({
7476
8373
  checkRunId: z.number(),
7477
8374
  workflowRunId: z.number().nullable().optional(),
@@ -7491,13 +8388,28 @@ export const CheckoutGithubCheckDetailsSchema = z.object({
7491
8388
  annotations: z.array(CheckoutGithubCheckAnnotationSchema).optional().default([]),
7492
8389
  failedJobs: z.array(CheckoutGithubCheckJobSchema).optional().default([]),
7493
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(),
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
+ }),
7494
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.*.
7495
8407
  export const CheckoutGithubGetCheckDetailsResponseSchema = z.object({
7496
8408
  type: z.literal("checkout.github.get_check_details.response"),
7497
8409
  payload: z.object({
7498
8410
  cwd: z.string(),
7499
8411
  success: z.boolean(),
7500
- details: CheckoutGithubCheckDetailsSchema.nullable().optional().default(null),
8412
+ details: CheckoutCheckDetailsSchema.nullable().optional().default(null),
7501
8413
  error: CheckoutErrorSchema.nullable(),
7502
8414
  requestId: z.string(),
7503
8415
  }),
@@ -7557,6 +8469,16 @@ const PullRequestTimelineCommentItemSchema = z.object({
7557
8469
  // threads under their parent review. Absent on issue comments and on
7558
8470
  // timelines from daemons that predate the field.
7559
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(),
7560
8482
  location: z
7561
8483
  .object({
7562
8484
  path: z.string(),
@@ -7592,6 +8514,10 @@ export const PullRequestTimelineResponseSchema = z.object({
7592
8514
  error: PullRequestTimelineErrorSchema.nullable().optional().default(null),
7593
8515
  requestId: z.string().optional().default(""),
7594
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,
7595
8521
  })
7596
8522
  .optional()
7597
8523
  .prefault({}),
@@ -7681,14 +8607,29 @@ export const BranchSuggestionsResponseSchema = z.object({
7681
8607
  requestId: z.string(),
7682
8608
  }),
7683
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.*.
7684
8630
  export const GitHubSearchResponseSchema = z.object({
7685
8631
  type: z.literal("github_search_response"),
7686
- payload: z.object({
7687
- items: z.array(GitHubSearchItemSchema),
7688
- githubFeaturesEnabled: z.boolean(),
7689
- error: z.string().nullable(),
7690
- requestId: z.string(),
7691
- }),
8632
+ payload: GitHubSearchResponsePayloadSchema,
7692
8633
  });
7693
8634
  export const HostingSearchResponseSchema = z.object({
7694
8635
  type: z.literal("hosting.search.response"),
@@ -7769,6 +8710,64 @@ export const FileExplorerResponseSchema = z.object({
7769
8710
  requestId: z.string(),
7770
8711
  }),
7771
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
+ });
7772
8771
  const ProjectIconSchema = z.object({
7773
8772
  data: z.string(),
7774
8773
  mimeType: z.string(),
@@ -8759,7 +9758,72 @@ export const DaemonUpdateProgressMessageSchema = z.object({
8759
9758
  phase: z.enum(["starting", "downloading", "installing", "complete"]),
8760
9759
  }),
8761
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
+ }
8762
9822
  export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
9823
+ HubExecutionAgentCreateResponseSchema,
9824
+ HubExecutionControlResponseSchema,
9825
+ HubExecutionAgentUpdateSchema,
9826
+ HubExecutionAgentStreamSchema,
8763
9827
  BrowserAutomationExecuteRequestSchema,
8764
9828
  ActivityLogMessageSchema,
8765
9829
  AssistantChunkMessageSchema,
@@ -8778,6 +9842,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8778
9842
  ArtifactUpdateMessageSchema,
8779
9843
  AgentUpdateMessageSchema,
8780
9844
  WorkspaceUpdateMessageSchema,
9845
+ ProjectUpdateMessageSchema,
9846
+ ProjectListResponseMessageSchema,
8781
9847
  ScriptStatusUpdateMessageSchema,
8782
9848
  WorkspaceSetupProgressMessageSchema,
8783
9849
  WorkspaceSetupStatusResponseMessageSchema,
@@ -8790,8 +9856,14 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8790
9856
  ProjectAddResponseSchema,
8791
9857
  ProjectScaffoldResponseSchema,
8792
9858
  ProjectScaffoldProgressSchema,
9859
+ ProjectCreateDirectoryResponseSchema,
8793
9860
  OpenProjectResponseMessageSchema,
9861
+ WorkspaceGithubSearchRepositoriesResponseSchema,
9862
+ ProjectGithubCloneResponseSchema,
8794
9863
  StartWorkspaceScriptResponseMessageSchema,
9864
+ WorkspaceScriptListResponseMessageSchema,
9865
+ WorkspaceScriptStartResponseMessageSchema,
9866
+ WorkspaceScriptStopResponseMessageSchema,
8795
9867
  LegacyListAvailableEditorsResponseMessageSchema,
8796
9868
  LegacyOpenInEditorResponseMessageSchema,
8797
9869
  ArchiveWorkspaceResponseMessageSchema,
@@ -8801,6 +9873,11 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8801
9873
  WorktreeReattachResponseSchema,
8802
9874
  FetchAgentResponseMessageSchema,
8803
9875
  FetchAgentTimelineResponseMessageSchema,
9876
+ ProviderSubagentListResponseMessageSchema,
9877
+ ProviderSubagentTimelineResponseMessageSchema,
9878
+ ProviderSubagentUpdateMessageSchema,
9879
+ SetAgentTimelineSubscriptionResponseMessageSchema,
9880
+ AgentAttentionRequiredMessageSchema,
8804
9881
  AgentForkContextResponseMessageSchema,
8805
9882
  CancelAgentResponseMessageSchema,
8806
9883
  ClearAgentAttentionResponseMessageSchema,
@@ -8810,6 +9887,9 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8810
9887
  SetVoiceModeResponseMessageSchema,
8811
9888
  DaemonGetStatusResponseSchema,
8812
9889
  DaemonGetPairingOfferResponseSchema,
9890
+ HubManagementDaemonConnectResponseSchema,
9891
+ HubManagementDaemonGetStatusResponseSchema,
9892
+ HubManagementDaemonDisconnectResponseSchema,
8813
9893
  DiagnosticsResponseSchema,
8814
9894
  GetDaemonConfigResponseMessageSchema,
8815
9895
  SetDaemonConfigResponseMessageSchema,
@@ -8851,6 +9931,9 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8851
9931
  ProjectLinksUnsetResponseSchema,
8852
9932
  ProjectLinksChangedSchema,
8853
9933
  WorkspaceTitleSetResponseSchema,
9934
+ WorkspacePinSetResponseSchema,
9935
+ WorkspaceRecoveryInspectResponseSchema,
9936
+ WorkspaceRecoveryRestoreResponseSchema,
8854
9937
  WaitForFinishResponseMessageSchema,
8855
9938
  AgentPermissionRequestMessageSchema,
8856
9939
  AgentPermissionResolvedMessageSchema,
@@ -8919,7 +10002,11 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8919
10002
  CheckoutRefreshResponseSchema,
8920
10003
  CheckoutPrCreateResponseSchema,
8921
10004
  CheckoutPrMergeResponseSchema,
10005
+ CheckoutForgeSetAutoMergeResponseSchema,
8922
10006
  CheckoutGithubSetAutoMergeResponseSchema,
10007
+ CheckoutCommitsListResponseSchema,
10008
+ CheckoutCommitFileDiffResponseSchema,
10009
+ CheckoutForgeGetCheckDetailsResponseSchema,
8923
10010
  CheckoutGithubGetCheckDetailsResponseSchema,
8924
10011
  PreviewListConfigResponseSchema,
8925
10012
  PreviewStartResponseSchema,
@@ -8934,6 +10021,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8934
10021
  StashListResponseSchema,
8935
10022
  ValidateBranchResponseSchema,
8936
10023
  BranchSuggestionsResponseSchema,
10024
+ ForgeSearchResponseSchema,
8937
10025
  GitHubSearchResponseSchema,
8938
10026
  HostingSearchResponseSchema,
8939
10027
  HostingAuthStatusResponseSchema,
@@ -8944,6 +10032,11 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
8944
10032
  OttoWorktreeArchiveResponseSchema,
8945
10033
  CreateOttoWorktreeResponseSchema,
8946
10034
  FileExplorerResponseSchema,
10035
+ FileSubscribeResponseSchema,
10036
+ FileUnsubscribeResponseSchema,
10037
+ FsFileWriteResponseSchema,
10038
+ FsFileWriteBinaryResponseSchema,
10039
+ FileUpdateSchema,
8947
10040
  ProjectIconResponseSchema,
8948
10041
  FileDownloadTokenResponseSchema,
8949
10042
  FileUploadResponseSchema,
@@ -9063,8 +10156,11 @@ export const WSHelloMessageSchema = z.object({
9063
10156
  voice: z.boolean().optional(),
9064
10157
  pushNotifications: z.boolean().optional(),
9065
10158
  [CLIENT_CAPS.reasoningMergeEnum]: z.boolean().optional(),
10159
+ [CLIENT_CAPS.selectiveAgentTimeline]: z.boolean().optional(),
9066
10160
  [CLIENT_CAPS.customModeIcons]: z.boolean().optional(),
9067
10161
  [CLIENT_CAPS.terminalReflowableSnapshot]: z.boolean().optional(),
10162
+ [CLIENT_CAPS.providerSubagents]: z.boolean().optional(),
10163
+ [CLIENT_CAPS.projectUpdates]: z.boolean().optional(),
9068
10164
  [CLIENT_CAPS.browserHost]: BrowserAutomationHostCapabilitySchema.optional(),
9069
10165
  })
9070
10166
  .passthrough()