@otto-code/protocol 0.6.5 → 0.6.7

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
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { TerminalActivitySchema } from "./terminal-activity.js";
3
- import { RunSchema } from "./orchestration.js";
3
+ import { OrchestrationGraphSchema, RunSchema } from "./orchestration.js";
4
4
  import { ArtifactMetadataSchema } from "./artifacts/types.js";
5
5
  import { ArtifactListRequestSchema, ArtifactCreateRequestSchema, ArtifactUpdateRequestSchema, ArtifactRegenerateRequestSchema, ArtifactCancelRequestSchema, ArtifactDeleteRequestSchema, ArtifactStarRequestSchema, ArtifactGetContentRequestSchema, ArtifactListResponseSchema, ArtifactCreateResponseSchema, ArtifactUpdateResponseSchema, ArtifactRegenerateResponseSchema, ArtifactCancelResponseSchema, ArtifactDeleteResponseSchema, ArtifactStarResponseSchema, ArtifactGetContentResponseSchema, ArtifactCreatedNotificationSchema, ArtifactUpdatedNotificationSchema, ArtifactDeletedNotificationSchema, } from "./artifacts/rpc-schemas.js";
6
6
  import { CLIENT_CAPS } from "./client-capabilities.js";
@@ -199,20 +199,25 @@ const AgentPersonalityVoiceSchema = z
199
199
  name: z.string().min(1),
200
200
  })
201
201
  .passthrough();
202
- // The three Visualizer lifecycle moments a personality voice-cue line can
203
- // belong to. Protocol owns this vocabulary — the daemon's cue generator, the
204
- // personality editor, and the Visualizer playback hook all import it from here.
205
- export const CUE_MOMENTS = ["join", "thinking", "done"];
202
+ // The Visualizer lifecycle moments a personality voice-cue line can belong to.
203
+ // Protocol owns this vocabulary — the daemon's cue generator, the personality
204
+ // editor, and the Visualizer playback hook all import it from here.
205
+ // "waiting" is the parent's turn ending while its observed sub-agents are still
206
+ // running; it DEFERS "done" rather than replacing it (see docs/visualizer.md).
207
+ export const CUE_MOMENTS = ["join", "thinking", "waiting", "done"];
206
208
  // Pre-generated (and user-editable) spoken "voice cue" lines for the personality
207
- // — a few short variations for each of three Visualizer moments (its node joins
208
- // the graph, first starts thinking, completes). Stored on the personality so
209
- // they're deterministic and hand-tunable in the editor; the Visualizer reads
210
- // them directly (no runtime generation). All groups optional/loose — a
211
- // personality may have none, or only some. See docs/visualizer.md "Voice cues".
209
+ // — a few short variations for each Visualizer moment (its node joins the graph,
210
+ // first starts thinking, finishes its turn but waits on sub-agents, completes).
211
+ // Stored on the personality so they're deterministic and hand-tunable in the
212
+ // editor; the Visualizer reads them directly (no runtime generation). All groups
213
+ // optional/loose — a personality may have none, or only some (personalities
214
+ // authored before "waiting" existed simply stay silent for that moment).
215
+ // See docs/visualizer.md "Voice cues".
212
216
  const AgentPersonalityVoiceCuesSchema = z
213
217
  .object({
214
218
  join: z.array(z.string()).optional(),
215
219
  thinking: z.array(z.string()).optional(),
220
+ waiting: z.array(z.string()).optional(),
216
221
  done: z.array(z.string()).optional(),
217
222
  })
218
223
  .passthrough();
@@ -367,6 +372,12 @@ export const MutableDaemonConfigSchema = z
367
372
  preferWriterPersonalities: false,
368
373
  }),
369
374
  autoArchiveAfterMerge: z.boolean().default(false),
375
+ // Drop the "Merge into <base>" action from the client's source-control menu
376
+ // (and stop promoting it to the primary CTA) for a pull-request-only
377
+ // workflow. Host-level so the whole team's clients share one policy.
378
+ // Defaults false so a new client parsing an old daemon's config keeps the
379
+ // action visible. Gated by server_info features.hideMergeIntoBaseSetting.
380
+ hideMergeIntoBaseAction: z.boolean().default(false),
370
381
  enableTerminalAgentHooks: z.boolean().default(false),
371
382
  appendSystemPrompt: z.string().default(""),
372
383
  terminalProfiles: z.array(TerminalProfileSchema).optional(),
@@ -407,6 +418,8 @@ export const MutableDaemonConfigPatchSchema = z
407
418
  .optional(),
408
419
  metadataGeneration: MutableMetadataGenerationConfigSchema.partial().optional(),
409
420
  autoArchiveAfterMerge: z.boolean().optional(),
421
+ // Gated by server_info features.hideMergeIntoBaseSetting.
422
+ hideMergeIntoBaseAction: z.boolean().optional(),
410
423
  enableTerminalAgentHooks: z.boolean().optional(),
411
424
  appendSystemPrompt: z.string().optional(),
412
425
  terminalProfiles: z.array(TerminalProfileSchema).optional(),
@@ -1364,6 +1377,35 @@ export const SpeechTtsPreviewRequestSchema = z.object({
1364
1377
  .passthrough()
1365
1378
  .optional(),
1366
1379
  });
1380
+ // Read a full assistant message aloud on demand (the per-message playback
1381
+ // button). Unlike the preview RPC — which truncates to a short sample and
1382
+ // returns one buffered clip — this synthesizes the ENTIRE text and streams it
1383
+ // back as `audio_output` chunks (isVoiceMode: false), one group per sentence, so
1384
+ // playback starts after the first sentence instead of the whole message.
1385
+ // `voice` (optional) is the speaking agent's personality voice, resolved on the
1386
+ // client from the live personality (same soft-binding semantics as the preview
1387
+ // button); absent uses the host default. The correlated response lands once
1388
+ // playback finishes, is canceled, or errors.
1389
+ export const SpeechTtsSpeakRequestSchema = z.object({
1390
+ type: z.literal("speech.tts.speak.request"),
1391
+ requestId: z.string(),
1392
+ text: z.string(),
1393
+ voice: z
1394
+ .object({
1395
+ provider: z.string().optional(),
1396
+ model: z.string().optional(),
1397
+ name: z.string(),
1398
+ })
1399
+ .passthrough()
1400
+ .optional(),
1401
+ });
1402
+ // Stop the session's in-flight message playback (the button's stop press).
1403
+ // Aborts synthesis on the host; the pending speak response then resolves as
1404
+ // canceled and the client flushes its own audio queue.
1405
+ export const SpeechTtsSpeakCancelRequestSchema = z.object({
1406
+ type: z.literal("speech.tts.speak.cancel.request"),
1407
+ requestId: z.string(),
1408
+ });
1367
1409
  // COMPAT(visualizerVoiceCues): added in v0.6.3; gate lives in
1368
1410
  // features.visualizerVoiceCues. Author short spoken "cue" lines for a
1369
1411
  // personality — a handful of variations each for three Visualizer moments
@@ -2363,6 +2405,97 @@ export const RunsClearedNotificationSchema = z.object({
2363
2405
  runIds: z.array(z.string()),
2364
2406
  }),
2365
2407
  });
2408
+ // ── Orchestration graphs (user orchestrations) ──────────────────────────────
2409
+ // Host-level reusable graph templates + user-initiated orchestration start.
2410
+ // Gated by server_info.features.orchestrationGraphs. UI says "Orchestration"
2411
+ // and "Graph"; the wire keeps the short `runs.` namespace (see docs/glossary.md).
2412
+ // See projects/orchestration-graphs.
2413
+ export const RunsGraphsListRequestSchema = z.object({
2414
+ type: z.literal("runs.graphs.list.request"),
2415
+ requestId: z.string(),
2416
+ });
2417
+ export const RunsGraphsListResponseSchema = z.object({
2418
+ type: z.literal("runs.graphs.list.response"),
2419
+ payload: z.object({
2420
+ graphs: z.array(OrchestrationGraphSchema),
2421
+ requestId: z.string(),
2422
+ }),
2423
+ });
2424
+ // Upsert a graph template (create when the id is new). Built-in graphs are
2425
+ // copy-on-edit daemon-side: saving over a builtIn id persists a user copy.
2426
+ export const RunsGraphsSaveRequestSchema = z.object({
2427
+ type: z.literal("runs.graphs.save.request"),
2428
+ graph: OrchestrationGraphSchema,
2429
+ requestId: z.string(),
2430
+ });
2431
+ export const RunsGraphsSaveResponseSchema = z.object({
2432
+ type: z.literal("runs.graphs.save.response"),
2433
+ payload: z.object({
2434
+ graph: OrchestrationGraphSchema.optional(),
2435
+ error: z.string().optional(),
2436
+ requestId: z.string(),
2437
+ }),
2438
+ });
2439
+ export const RunsGraphsDeleteRequestSchema = z.object({
2440
+ type: z.literal("runs.graphs.delete.request"),
2441
+ graphId: z.string(),
2442
+ requestId: z.string(),
2443
+ });
2444
+ export const RunsGraphsDeleteResponseSchema = z.object({
2445
+ type: z.literal("runs.graphs.delete.response"),
2446
+ payload: z.object({
2447
+ deleted: z.boolean(),
2448
+ error: z.string().optional(),
2449
+ requestId: z.string(),
2450
+ }),
2451
+ });
2452
+ // Broadcast after any save/delete so every client's graph cache converges,
2453
+ // mirroring runs.updated.notification's role for runs.
2454
+ export const RunsGraphsChangedNotificationSchema = z.object({
2455
+ type: z.literal("runs.graphs.changed.notification"),
2456
+ payload: z.object({
2457
+ graphs: z.array(OrchestrationGraphSchema),
2458
+ }),
2459
+ });
2460
+ // Start (or draft) a user-initiated orchestration from the New Orchestration
2461
+ // dialog. `flavor` is an open vocabulary: "ai" (prompt-and-go — the daemon
2462
+ // spawns an orchestrator agent that declares its own plan via start_run) or
2463
+ // "graph" (deterministic — the daemon executes `graphId` with `graphInputs`).
2464
+ // `draft: true` creates the record without executing (the designer flow);
2465
+ // `runId` executes an existing draft in place.
2466
+ export const RunsStartRequestSchema = z.object({
2467
+ type: z.literal("runs.start.request"),
2468
+ flavor: z.string(),
2469
+ cwd: z.string(),
2470
+ workspaceId: z.string().optional(),
2471
+ title: z.string().optional(),
2472
+ description: z.string().optional(),
2473
+ // Orchestrator seat when the active team doesn't fill it: a personality, or
2474
+ // a bare provider/model pair.
2475
+ orchestratorPersonalityId: z.string().optional(),
2476
+ orchestratorProvider: z.string().optional(),
2477
+ orchestratorModel: z.string().optional(),
2478
+ orchestratorThinkingOptionId: z.string().optional(),
2479
+ prompt: z.string().optional(),
2480
+ graphId: z.string().optional(),
2481
+ graphInputs: z.record(z.string(), z.string()).optional(),
2482
+ draft: z.boolean().optional(),
2483
+ runId: z.string().optional(),
2484
+ requestId: z.string(),
2485
+ });
2486
+ export const RunsStartResponseSchema = z.object({
2487
+ type: z.literal("runs.start.response"),
2488
+ payload: z.object({
2489
+ runId: z.string().optional(),
2490
+ // The root/orchestrator agent whose chat the client navigates to, and the
2491
+ // workspace the daemon resolved it into (the dialog only knows a project
2492
+ // target's cwd).
2493
+ agentId: z.string().optional(),
2494
+ workspaceId: z.string().optional(),
2495
+ error: z.string().optional(),
2496
+ requestId: z.string(),
2497
+ }),
2498
+ });
2366
2499
  // Namespaced successor to checkout_commit_request: per-file selection and
2367
2500
  // structured errors. Gated by server_info.features.checkoutGitCommit; the flat
2368
2501
  // RPC stays accepted for old clients.
@@ -2401,6 +2534,57 @@ export const CheckoutGitRollbackRequestSchema = z.object({
2401
2534
  allowWithRunningAgents: z.boolean().optional(),
2402
2535
  requestId: z.string(),
2403
2536
  });
2537
+ // ── Git file investigation (local git, no hosting provider) ─────────────────
2538
+ // History / per-commit diff / blame / origin commit for one file or one line
2539
+ // range within it. Everything below is plain local git: it works in a repo with
2540
+ // no remote and no forge connection, and it is provider-neutral by construction
2541
+ // (it inspects the repo, not an agent), so there is no per-provider rollout to
2542
+ // look for. Gated by server_info.features.checkoutGitFileHistory.
2543
+ // Commits that touched a file, newest first. Whole-file mode follows renames;
2544
+ // passing startLine/endLine switches to `git log -L` for that range instead.
2545
+ export const CheckoutGitFileHistoryRequestSchema = z.object({
2546
+ type: z.literal("checkout.git.get_file_history.request"),
2547
+ cwd: z.string(),
2548
+ // Repo-relative path, as the file is named today.
2549
+ path: z.string(),
2550
+ limit: z.number().int().positive().optional(),
2551
+ offset: z.number().int().nonnegative().optional(),
2552
+ // 1-based inclusive line range. Both or neither.
2553
+ startLine: z.number().int().positive().optional(),
2554
+ endLine: z.number().int().positive().optional(),
2555
+ requestId: z.string(),
2556
+ });
2557
+ // What a single commit did to a single file, as a unified diff. `path` must be
2558
+ // the file's name *at that commit* (history entries carry it) or the pathspec
2559
+ // misses across a rename.
2560
+ export const CheckoutGitFileCommitDiffRequestSchema = z.object({
2561
+ type: z.literal("checkout.git.get_file_commit_diff.request"),
2562
+ cwd: z.string(),
2563
+ path: z.string(),
2564
+ sha: z.string(),
2565
+ ignoreWhitespace: z.boolean().optional(),
2566
+ requestId: z.string(),
2567
+ });
2568
+ // One page of blame. Always paged — blaming a large file whole would block the
2569
+ // daemon — so the client walks the file a page at a time.
2570
+ export const CheckoutGitFileBlameRequestSchema = z.object({
2571
+ type: z.literal("checkout.git.get_file_blame.request"),
2572
+ cwd: z.string(),
2573
+ path: z.string(),
2574
+ startLine: z.number().int().positive().optional(),
2575
+ lineCount: z.number().int().positive().optional(),
2576
+ // Blame at a specific commit instead of the working tree.
2577
+ sha: z.string().optional(),
2578
+ requestId: z.string(),
2579
+ });
2580
+ // The commit that first added the file ("who originally wrote this"), following
2581
+ // renames so a moved file reports its true origin.
2582
+ export const CheckoutGitFileOriginRequestSchema = z.object({
2583
+ type: z.literal("checkout.git.get_file_origin.request"),
2584
+ cwd: z.string(),
2585
+ path: z.string(),
2586
+ requestId: z.string(),
2587
+ });
2404
2588
  export const CheckoutMergeRequestSchema = z.object({
2405
2589
  type: z.literal("checkout_merge_request"),
2406
2590
  cwd: z.string(),
@@ -2677,6 +2861,23 @@ export const ArchiveWorkspaceRequestSchema = z.object({
2677
2861
  type: z.literal("archive_workspace_request"),
2678
2862
  workspaceId: z.string(),
2679
2863
  requestId: z.string(),
2864
+ // COMPAT(worktreeArchiveBranchCleanup): added in v0.6.7, drop the optional
2865
+ // gate when daemon floor >= v0.6.7. Absent means "keep" — the leftover local
2866
+ // branch is never touched (old-client behavior). "delete" asks the daemon to
2867
+ // remove the worktree's local branch after the backing directory is torn down
2868
+ // (only when this was the last reference to it and the branch is not checked
2869
+ // out elsewhere). Gated by server_info.features.worktreeArchiveBranchCleanup.
2870
+ branchDisposition: z.enum(["keep", "delete"]).optional(),
2871
+ });
2872
+ // Read-only pre-archive inspection for a worktree-backed workspace: what branch
2873
+ // it is on, whether that branch is merged into its base, and whether archiving
2874
+ // will actually free the branch (last reference, not checked out elsewhere). The
2875
+ // client uses this to render the "delete the leftover branch?" confirmation.
2876
+ // COMPAT(worktreeArchiveBranchCleanup): added in v0.6.7.
2877
+ export const WorkspaceArchivePreflightRequestSchema = z.object({
2878
+ type: z.literal("workspace.archive.preflight.request"),
2879
+ requestId: z.string(),
2880
+ workspaceId: z.string(),
2680
2881
  });
2681
2882
  // Create a new workspace record. Unlike open_project, this never deduplicates by
2682
2883
  // directory: it always produces a fresh workspace. The source discriminates
@@ -2709,6 +2910,36 @@ export const WorkspaceCreateRequestSchema = z.object({
2709
2910
  }),
2710
2911
  ]),
2711
2912
  });
2913
+ // Re-attach a "left" Otto worktree as a live workspace. Two targets: revive an
2914
+ // archived worktree workspace record in place (recreating its backing directory
2915
+ // from the kept branch when it is gone), or bind a fresh workspace to an orphaned
2916
+ // on-disk Otto worktree that no live workspace references.
2917
+ // COMPAT(worktreeReattach): added in v0.6.7. Gated by server_info.features.worktreeReattach.
2918
+ export const WorktreeReattachTargetSchema = z.discriminatedUnion("kind", [
2919
+ z.object({
2920
+ kind: z.literal("workspace"),
2921
+ workspaceId: z.string(),
2922
+ }),
2923
+ z.object({
2924
+ kind: z.literal("orphan"),
2925
+ worktreePath: z.string(),
2926
+ projectId: z.string().optional(),
2927
+ }),
2928
+ ]);
2929
+ export const WorktreeReattachRequestSchema = z.object({
2930
+ type: z.literal("worktree.reattach.request"),
2931
+ requestId: z.string(),
2932
+ target: WorktreeReattachTargetSchema,
2933
+ });
2934
+ // List re-attachable Otto worktrees for a project/repo: archived worktree
2935
+ // workspace records whose branch is kept, plus orphaned on-disk worktrees with no
2936
+ // live workspace. Either projectId or a cwd inside the repo is required.
2937
+ export const WorktreeReattachListRequestSchema = z.object({
2938
+ type: z.literal("worktree.reattach.list.request"),
2939
+ requestId: z.string(),
2940
+ projectId: z.string().optional(),
2941
+ cwd: z.string().optional(),
2942
+ });
2712
2943
  export const WorkspaceClearAttentionRequestSchema = z.object({
2713
2944
  type: z.literal("workspace.clear_attention.request"),
2714
2945
  workspaceId: z.union([z.string(), z.array(z.string())]),
@@ -3052,6 +3283,8 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
3052
3283
  SetDaemonConfigRequestMessageSchema,
3053
3284
  SpeechSettingsGetOptionsRequestSchema,
3054
3285
  SpeechTtsPreviewRequestSchema,
3286
+ SpeechTtsSpeakRequestSchema,
3287
+ SpeechTtsSpeakCancelRequestSchema,
3055
3288
  VisualizerVoiceCuesGenerateRequestSchema,
3056
3289
  AgentPersonalitiesGetStatsRequestSchema,
3057
3290
  ReadProjectConfigRequestMessageSchema,
@@ -3105,10 +3338,18 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
3105
3338
  CheckoutGitCommitAgentRequestSchema,
3106
3339
  CheckoutGitRollbackRequestSchema,
3107
3340
  CheckoutGitGetOperationLogRequestSchema,
3341
+ CheckoutGitFileHistoryRequestSchema,
3342
+ CheckoutGitFileCommitDiffRequestSchema,
3343
+ CheckoutGitFileBlameRequestSchema,
3344
+ CheckoutGitFileOriginRequestSchema,
3108
3345
  RunsGetSnapshotRequestSchema,
3109
3346
  RunsGateRespondRequestSchema,
3110
3347
  RunsCancelRequestSchema,
3111
3348
  RunsClearRequestSchema,
3349
+ RunsGraphsListRequestSchema,
3350
+ RunsGraphsSaveRequestSchema,
3351
+ RunsGraphsDeleteRequestSchema,
3352
+ RunsStartRequestSchema,
3112
3353
  CheckoutMergeRequestSchema,
3113
3354
  CheckoutMergeFromBaseRequestSchema,
3114
3355
  CheckoutPullRequestSchema,
@@ -3144,6 +3385,9 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
3144
3385
  OpenProjectRequestSchema,
3145
3386
  ProjectAddRequestSchema,
3146
3387
  ArchiveWorkspaceRequestSchema,
3388
+ WorkspaceArchivePreflightRequestSchema,
3389
+ WorktreeReattachListRequestSchema,
3390
+ WorktreeReattachRequestSchema,
3147
3391
  WorkspaceCreateRequestSchema,
3148
3392
  WorkspaceClearAttentionRequestSchema,
3149
3393
  FileExplorerRequestSchema,
@@ -3416,6 +3660,9 @@ export const ServerInfoStatusPayloadSchema = z
3416
3660
  agentPersonalities: z.boolean().optional(),
3417
3661
  // COMPAT(ttsPreview): added in v0.4.7, drop the gate when daemon floor >= v0.4.7.
3418
3662
  ttsPreview: z.boolean().optional(),
3663
+ // COMPAT(ttsSpeak): added in v0.6.7, drop the gate when daemon floor >= v0.6.7.
3664
+ // Host can stream a full message aloud on demand (per-message playback).
3665
+ ttsSpeak: z.boolean().optional(),
3419
3666
  // COMPAT(visualizerVoiceCues): added in v0.6.3, drop the gate when daemon floor >= v0.6.3.
3420
3667
  visualizerVoiceCues: z.boolean().optional(),
3421
3668
  // COMPAT(setAgentPersonality): added in v0.5.0, drop the gate when daemon floor >= v0.5.0.
@@ -3428,6 +3675,27 @@ export const ServerInfoStatusPayloadSchema = z
3428
3675
  checkoutGitRollback: z.boolean().optional(),
3429
3676
  // COMPAT(checkoutGitLog): added in v0.5.1, drop the gate when daemon floor >= v0.5.1.
3430
3677
  checkoutGitLog: z.boolean().optional(),
3678
+ // Local-git file investigation: history, per-commit diff, blame, origin
3679
+ // commit — for a whole file or a line range. No forge connection needed
3680
+ // and no per-provider rollout; it is git, so every provider gets it at
3681
+ // once.
3682
+ // COMPAT(checkoutGitFileHistory): added in v0.6.6, drop the gate when daemon floor >= v0.6.6.
3683
+ checkoutGitFileHistory: z.boolean().optional(),
3684
+ // Set when the daemon can inspect a worktree's leftover branch before
3685
+ // archiving (workspace.archive.preflight.*) and delete it as part of the
3686
+ // archive (archive_workspace_request.branchDisposition). Without it the
3687
+ // client archives the worktree exactly as before and never offers to
3688
+ // remove the branch — no degraded client-side branch detection exists.
3689
+ // COMPAT(worktreeArchiveBranchCleanup): added in v0.6.7, drop the gate when daemon floor >= v0.6.7.
3690
+ worktreeArchiveBranchCleanup: z.boolean().optional(),
3691
+ // COMPAT(worktreeReattach): added in v0.6.7, drop the gate when daemon floor >= v0.6.7.
3692
+ worktreeReattach: z.boolean().optional(),
3693
+ // Set when the daemon persists the host-level hideMergeIntoBaseAction
3694
+ // workspace policy (read/written via the daemon config RPCs). Without
3695
+ // it the client hides the Workspaces toggle, since patching the field
3696
+ // on an old daemon would silently fail to stick.
3697
+ // COMPAT(hideMergeIntoBaseSetting): added in v0.6.7, drop the gate when daemon floor >= v0.6.7.
3698
+ hideMergeIntoBaseSetting: z.boolean().optional(),
3431
3699
  // COMPAT(agentTeams): added in v0.5.2, drop the gate when daemon floor >= v0.5.2.
3432
3700
  agentTeams: z.boolean().optional(),
3433
3701
  // COMPAT(modelTierOverrides): added in v0.5.2, drop the gate when daemon floor >= v0.5.2.
@@ -3440,6 +3708,8 @@ export const ServerInfoStatusPayloadSchema = z
3440
3708
  activityStats: z.boolean().optional(),
3441
3709
  // COMPAT(runsClear): added in v0.5.3, drop the gate when daemon floor >= v0.5.3.
3442
3710
  runsClear: z.boolean().optional(),
3711
+ // COMPAT(orchestrationGraphs): added in v0.6.7, drop the gate when daemon floor >= v0.6.7.
3712
+ orchestrationGraphs: z.boolean().optional(),
3443
3713
  // COMPAT(projectLinks): added in v0.5.6, drop the gate when daemon floor >= v0.5.6.
3444
3714
  projectLinks: z.boolean().optional(),
3445
3715
  // COMPAT(fileOutsideWorkspace): added in v0.5.8, drop the gate when daemon floor >= v0.5.8.
@@ -3986,6 +4256,79 @@ export const ArchiveWorkspaceResponseMessageSchema = z.object({
3986
4256
  workspaceId: z.string(),
3987
4257
  archivedAt: z.string().nullable(),
3988
4258
  error: z.string().nullable(),
4259
+ // COMPAT(worktreeArchiveBranchCleanup): added in v0.6.7. The name of the
4260
+ // local branch the daemon deleted as part of this archive (when the request
4261
+ // asked for branchDisposition: "delete" and the branch was actually
4262
+ // removed), else null/absent. Old daemons omit it.
4263
+ deletedBranch: z.string().nullable().optional(),
4264
+ }),
4265
+ });
4266
+ // Whether/how a worktree-backed workspace's local branch can be cleaned up when
4267
+ // the workspace is archived. See WorkspaceArchivePreflightRequestSchema.
4268
+ export const WorktreeArchiveBranchDetectionSchema = z.object({
4269
+ // True only for Otto-owned worktrees whose branch we can offer to delete.
4270
+ // False for local checkouts, plain directories, and non-owned worktrees — the
4271
+ // client then skips the branch-cleanup UI entirely.
4272
+ isOttoWorktree: z.boolean(),
4273
+ // The local branch checked out in the worktree, or null when detached/unknown.
4274
+ branchName: z.string().nullable(),
4275
+ // The base ref the branch was created from (origin/ stripped), or null.
4276
+ baseBranch: z.string().nullable(),
4277
+ mergeState: z.enum(["merged", "unmerged", "unknown"]),
4278
+ // Commits on the branch not contained in the base ref; null when unknown.
4279
+ unmergedCommitCount: z.number().int().nonnegative().nullable(),
4280
+ // A matching origin/<branch> exists — deleting the local branch keeps the
4281
+ // remote copy. Purely informational for the confirmation copy.
4282
+ hasRemoteBranch: z.boolean(),
4283
+ // The branch is checked out in another worktree too, so git will refuse to
4284
+ // delete it even after this worktree is removed. The client hides the option.
4285
+ branchCheckedOutElsewhere: z.boolean(),
4286
+ // Archiving will actually remove the backing directory (this is the last
4287
+ // active workspace referencing it). Branch cleanup is only offered when true.
4288
+ directoryWillBeRemoved: z.boolean(),
4289
+ });
4290
+ export const WorkspaceArchivePreflightResponseSchema = z.object({
4291
+ type: z.literal("workspace.archive.preflight.response"),
4292
+ payload: z.object({
4293
+ requestId: z.string(),
4294
+ workspaceId: z.string(),
4295
+ // Null when detection failed (see error) or the workspace is gone.
4296
+ detection: WorktreeArchiveBranchDetectionSchema.nullable(),
4297
+ error: z.string().nullable(),
4298
+ }),
4299
+ });
4300
+ // A re-attachable Otto worktree surfaced by worktree.reattach.list.
4301
+ // COMPAT(worktreeReattach): added in v0.6.7.
4302
+ export const WorktreeReattachCandidateSchema = z.object({
4303
+ // Present when an archived workspace record still backs this worktree; null for
4304
+ // an orphaned on-disk worktree with no record. The reattach request keys off
4305
+ // whichever identity is available.
4306
+ workspaceId: z.string().nullable(),
4307
+ worktreePath: z.string(),
4308
+ branchName: z.string().nullable(),
4309
+ baseBranch: z.string().nullable(),
4310
+ // The worktree directory currently exists on disk. False means the record was
4311
+ // archived away and the directory must be recreated from the branch on reattach.
4312
+ directoryOnDisk: z.boolean(),
4313
+ // The workspace's human name when we have a record, else null.
4314
+ displayName: z.string().nullable(),
4315
+ archivedAt: z.string().nullable(),
4316
+ });
4317
+ export const WorktreeReattachListResponseSchema = z.object({
4318
+ type: z.literal("worktree.reattach.list.response"),
4319
+ payload: z.object({
4320
+ requestId: z.string(),
4321
+ candidates: z.array(WorktreeReattachCandidateSchema),
4322
+ error: z.string().nullable(),
4323
+ }),
4324
+ });
4325
+ export const WorktreeReattachResponseSchema = z.object({
4326
+ type: z.literal("worktree.reattach.response"),
4327
+ payload: z.object({
4328
+ requestId: z.string(),
4329
+ // The revived/created workspace descriptor, or null on error.
4330
+ workspace: WorkspaceDescriptorPayloadSchema.nullable(),
4331
+ error: z.string().nullable(),
3989
4332
  }),
3990
4333
  });
3991
4334
  export const FetchAgentResponseMessageSchema = z.object({
@@ -4177,6 +4520,23 @@ export const SpeechTtsPreviewResponseSchema = z.object({
4177
4520
  })
4178
4521
  .passthrough(),
4179
4522
  });
4523
+ export const SpeechTtsSpeakResponseSchema = z.object({
4524
+ type: z.literal("speech.tts.speak.response"),
4525
+ payload: z
4526
+ .object({
4527
+ requestId: z.string(),
4528
+ // True when the full message played to completion; absent/false when it was
4529
+ // canceled or produced no audio. `error` carries a human-readable failure.
4530
+ ok: z.boolean().optional(),
4531
+ canceled: z.boolean().optional(),
4532
+ error: z.string().optional(),
4533
+ })
4534
+ .passthrough(),
4535
+ });
4536
+ export const SpeechTtsSpeakCancelResponseSchema = z.object({
4537
+ type: z.literal("speech.tts.speak.cancel.response"),
4538
+ payload: z.object({ requestId: z.string() }).passthrough(),
4539
+ });
4180
4540
  export const VisualizerVoiceCuesGenerateResponseSchema = z.object({
4181
4541
  type: z.literal("visualizer.voiceCues.generate.response"),
4182
4542
  payload: z
@@ -4645,6 +5005,116 @@ export const CheckoutGitRollbackResponseSchema = z.object({
4645
5005
  requestId: z.string(),
4646
5006
  }),
4647
5007
  });
5008
+ // ── Git file investigation responses ────────────────────────────────────────
5009
+ // A structured failure any of the four file-investigation RPCs can report. They
5010
+ // are pure reads, so the failure modes are narrow: not a repo, path/revision
5011
+ // rejected, or git itself refused.
5012
+ export const CheckoutGitFileErrorSchema = z.discriminatedUnion("kind", [
5013
+ z.object({
5014
+ kind: z.literal("not_git_repo"),
5015
+ }),
5016
+ z.object({
5017
+ kind: z.literal("invalid_path"),
5018
+ detail: z.string(),
5019
+ }),
5020
+ z.object({
5021
+ kind: z.literal("git_failed"),
5022
+ detail: z.string(),
5023
+ }),
5024
+ ]);
5025
+ export const GitFileHistoryEntrySchema = z.object({
5026
+ sha: z.string(),
5027
+ shortSha: z.string(),
5028
+ subject: z.string(),
5029
+ body: z.string(),
5030
+ authorName: z.string(),
5031
+ authorEmail: z.string(),
5032
+ // Unix seconds.
5033
+ authoredAt: z.number(),
5034
+ committerName: z.string(),
5035
+ committedAt: z.number(),
5036
+ // The file's name at this commit — differs from the requested path across a
5037
+ // rename. Diff requests must echo this one back, not the current name.
5038
+ path: z.string(),
5039
+ previousPath: z.string().optional(),
5040
+ // Single-letter git status (A/M/D/R/C).
5041
+ changeKind: z.string().optional(),
5042
+ isMerge: z.boolean(),
5043
+ // Parent object names, so a diff view can name the revision it is comparing
5044
+ // against instead of writing "<sha>^". Empty for a root commit.
5045
+ parentShas: z.array(z.string()).optional(),
5046
+ });
5047
+ export const CheckoutGitFileHistoryResponseSchema = z.object({
5048
+ type: z.literal("checkout.git.get_file_history.response"),
5049
+ payload: z.object({
5050
+ cwd: z.string(),
5051
+ path: z.string(),
5052
+ entries: z.array(GitFileHistoryEntrySchema),
5053
+ hasMore: z.boolean(),
5054
+ error: CheckoutGitFileErrorSchema.nullable(),
5055
+ requestId: z.string(),
5056
+ }),
5057
+ });
5058
+ export const CheckoutGitFileCommitDiffResponseSchema = z.object({
5059
+ type: z.literal("checkout.git.get_file_commit_diff.response"),
5060
+ payload: z.object({
5061
+ cwd: z.string(),
5062
+ path: z.string(),
5063
+ sha: z.string(),
5064
+ diff: z.string(),
5065
+ // Highlighted/parsed form of the same diff, when it parsed cleanly.
5066
+ structured: z.array(ParsedDiffFileSchema).optional(),
5067
+ // The file's previous revision — the diff's left-hand side, and the honest
5068
+ // label for it. Absent when this revision created the file.
5069
+ previousSha: z.string().optional(),
5070
+ previousPath: z.string().optional(),
5071
+ truncated: z.boolean(),
5072
+ error: CheckoutGitFileErrorSchema.nullable(),
5073
+ requestId: z.string(),
5074
+ }),
5075
+ });
5076
+ export const GitBlameLineSchema = z.object({
5077
+ line: z.number(),
5078
+ sha: z.string(),
5079
+ originalLine: z.number(),
5080
+ });
5081
+ // Blame commit metadata is deduped by sha rather than inlined per line: a
5082
+ // thousand-line page usually references a handful of commits.
5083
+ export const GitBlameCommitSchema = z.object({
5084
+ sha: z.string(),
5085
+ shortSha: z.string(),
5086
+ summary: z.string(),
5087
+ authorName: z.string(),
5088
+ authorEmail: z.string(),
5089
+ authoredAt: z.number(),
5090
+ path: z.string().optional(),
5091
+ });
5092
+ export const CheckoutGitFileBlameResponseSchema = z.object({
5093
+ type: z.literal("checkout.git.get_file_blame.response"),
5094
+ payload: z.object({
5095
+ cwd: z.string(),
5096
+ path: z.string(),
5097
+ lines: z.array(GitBlameLineSchema),
5098
+ commits: z.array(GitBlameCommitSchema),
5099
+ startLine: z.number(),
5100
+ endLine: z.number(),
5101
+ reachedEndOfFile: z.boolean(),
5102
+ error: CheckoutGitFileErrorSchema.nullable(),
5103
+ requestId: z.string(),
5104
+ }),
5105
+ });
5106
+ export const CheckoutGitFileOriginResponseSchema = z.object({
5107
+ type: z.literal("checkout.git.get_file_origin.response"),
5108
+ payload: z.object({
5109
+ cwd: z.string(),
5110
+ path: z.string(),
5111
+ // Null when the file has no commit history (never committed, or a shallow
5112
+ // clone that does not reach its creation).
5113
+ entry: GitFileHistoryEntrySchema.nullable(),
5114
+ error: CheckoutGitFileErrorSchema.nullable(),
5115
+ requestId: z.string(),
5116
+ }),
5117
+ });
4648
5118
  export const CheckoutMergeResponseSchema = z.object({
4649
5119
  type: z.literal("checkout_merge_response"),
4650
5120
  payload: z.object({
@@ -5648,6 +6118,9 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
5648
6118
  LegacyListAvailableEditorsResponseMessageSchema,
5649
6119
  LegacyOpenInEditorResponseMessageSchema,
5650
6120
  ArchiveWorkspaceResponseMessageSchema,
6121
+ WorkspaceArchivePreflightResponseSchema,
6122
+ WorktreeReattachListResponseSchema,
6123
+ WorktreeReattachResponseSchema,
5651
6124
  FetchAgentResponseMessageSchema,
5652
6125
  FetchAgentTimelineResponseMessageSchema,
5653
6126
  AgentForkContextResponseMessageSchema,
@@ -5664,6 +6137,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
5664
6137
  SetDaemonConfigResponseMessageSchema,
5665
6138
  SpeechSettingsGetOptionsResponseSchema,
5666
6139
  SpeechTtsPreviewResponseSchema,
6140
+ SpeechTtsSpeakResponseSchema,
6141
+ SpeechTtsSpeakCancelResponseSchema,
5667
6142
  VisualizerVoiceCuesGenerateResponseSchema,
5668
6143
  AgentPersonalitiesGetStatsResponseSchema,
5669
6144
  ReadProjectConfigResponseMessageSchema,
@@ -5707,12 +6182,21 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
5707
6182
  CheckoutGitRollbackResponseSchema,
5708
6183
  CheckoutGitGetOperationLogResponseSchema,
5709
6184
  CheckoutGitLogAppendedNotificationSchema,
6185
+ CheckoutGitFileHistoryResponseSchema,
6186
+ CheckoutGitFileCommitDiffResponseSchema,
6187
+ CheckoutGitFileBlameResponseSchema,
6188
+ CheckoutGitFileOriginResponseSchema,
5710
6189
  RunsGetSnapshotResponseSchema,
5711
6190
  RunsUpdatedNotificationSchema,
5712
6191
  RunsGateRespondResponseSchema,
5713
6192
  RunsCancelResponseSchema,
5714
6193
  RunsClearResponseSchema,
5715
6194
  RunsClearedNotificationSchema,
6195
+ RunsGraphsListResponseSchema,
6196
+ RunsGraphsSaveResponseSchema,
6197
+ RunsGraphsDeleteResponseSchema,
6198
+ RunsGraphsChangedNotificationSchema,
6199
+ RunsStartResponseSchema,
5716
6200
  CheckoutMergeResponseSchema,
5717
6201
  CheckoutMergeFromBaseResponseSchema,
5718
6202
  CheckoutPullResponseSchema,