@agent-compose/sdk 0.8.1 → 0.8.3

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.
Files changed (48) hide show
  1. package/dist/agent/__tests__/perf-sampler.test.d.ts +10 -0
  2. package/dist/agent/agent-context.d.ts +1 -1
  3. package/dist/agent/agent-loop.d.ts +5 -1
  4. package/dist/agent/desktop-open.d.ts +184 -0
  5. package/dist/agent/perf-sampler.d.ts +99 -0
  6. package/dist/agent/services-manifest.d.ts +88 -0
  7. package/dist/agent/services-restore.d.ts +58 -0
  8. package/dist/client.d.ts +189 -15
  9. package/dist/display.d.ts +17 -0
  10. package/dist/index.d.ts +14 -5
  11. package/dist/index.js +1625 -120
  12. package/dist/runtimes/_cli-agent.d.ts +372 -2
  13. package/dist/runtimes/claude-code.d.ts +12 -0
  14. package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
  15. package/dist/runtimes/codex.d.ts +8 -0
  16. package/dist/runtimes/openai-desktop.js +1555 -120
  17. package/dist/runtimes/session-env.test.d.ts +14 -0
  18. package/dist/sandbox/sizes.d.ts +120 -30
  19. package/dist/sandbox.d.ts +1 -1
  20. package/dist/types/api-conversations.d.ts +476 -1
  21. package/dist/types/api-factory.d.ts +164 -7
  22. package/dist/types/api-runs.d.ts +23 -1
  23. package/dist/types/protocol.d.ts +32 -1
  24. package/dist/types/runtime.d.ts +120 -0
  25. package/dist/types/workflow-metadata.d.ts +6 -5
  26. package/package.json +1 -1
  27. package/src/agent/agent-context.ts +128 -28
  28. package/src/agent/agent-loop.ts +10 -3
  29. package/src/agent/desktop-open.ts +418 -0
  30. package/src/agent/perf-sampler.ts +202 -0
  31. package/src/agent/services-manifest.ts +356 -0
  32. package/src/agent/services-restore.ts +195 -0
  33. package/src/client.ts +384 -32
  34. package/src/display.ts +44 -1
  35. package/src/index.ts +74 -7
  36. package/src/runtimes/_cli-agent.ts +1160 -67
  37. package/src/runtimes/claude-code.ts +187 -12
  38. package/src/runtimes/codex.ts +65 -2
  39. package/src/sandbox/providers/e2b.ts +8 -4
  40. package/src/sandbox/providers/local.ts +16 -4
  41. package/src/sandbox/sizes.ts +127 -44
  42. package/src/sandbox.ts +8 -0
  43. package/src/types/api-conversations.ts +461 -2
  44. package/src/types/api-factory.ts +165 -7
  45. package/src/types/api-runs.ts +25 -1
  46. package/src/types/protocol.ts +30 -1
  47. package/src/types/runtime.ts +122 -0
  48. package/src/types/workflow-metadata.ts +6 -5
@@ -92,15 +92,43 @@ export interface ConversationRow {
92
92
  * callers; key callers see 0. */
93
93
  unreadCount?: number;
94
94
  }
95
+ /** Agent-author identity decoration (mirrors the server's `agentAuthor`
96
+ * on message rows and stream payloads): rows authored by a user's agent
97
+ * that is not the conversation's resident agent — the assistant's
98
+ * delegated sends, review briefs, and PEER-SESSION messages
99
+ * (`sourceChannel: 'session'`). The persona `name` is the author label;
100
+ * `avatarSeed` is the agent's own generated orb; the owner names WHOSE
101
+ * agent it is. */
102
+ export interface ConversationAgentAuthor {
103
+ agentId: string;
104
+ name: string;
105
+ avatarSeed: string;
106
+ ownerUserId: string | null;
107
+ ownerName: string | null;
108
+ }
95
109
  export interface ConversationMessageRow {
96
110
  id: string;
97
111
  authorKind: string;
98
112
  authorId: string | null;
113
+ /** The author's display name, resolved server-side — a HUMAN author's
114
+ * display name, or an attributable AGENT author's persona name (see
115
+ * `agentAuthor`). Null for resident-agent/system rows; absent on older
116
+ * servers. */
117
+ authorLabel?: string | null;
118
+ /** Sender identity for foreign-agent and peer-session rows — render the
119
+ * persona name + its avatarSeed FIRST, before any session/runtime
120
+ * fallback (the one resolution order; streamed and settled renders are
121
+ * identical). Null/absent on resident-agent, session channel-post, and
122
+ * older-server rows. */
123
+ agentAuthor?: ConversationAgentAuthor | null;
99
124
  parts: ConversationMessagePart[];
100
125
  threadRootId: string | null;
101
126
  reactions: Record<string, string[]>;
102
127
  replyCount: number;
103
128
  lastReplyAt: string | null;
129
+ /** When the author last edited the content; null/absent = never edited
130
+ * (absent on older servers). */
131
+ editedAt?: string | null;
104
132
  createdAt: string;
105
133
  /** Thread-root facepile (≤3) — present only on roots with replies. */
106
134
  replyAuthors?: Array<{
@@ -112,6 +140,12 @@ export interface ConversationMessageRow {
112
140
  * rows to the session (title + runtime mark), never a resident agent.
113
141
  * Null once the session is deleted; absent on older servers. */
114
142
  sourceSessionId?: string | null;
143
+ /** THE ORIGIN SURFACE (ADR-0073 §6.5): the ingress the row arrived over —
144
+ * 'sms' / 'phone' / 'voice' on inbound user rows, 'assistant' on
145
+ * delegated sends, 'session' on peer session→session messages (whose
146
+ * author identity rides `agentAuthor`). Null/absent = an ordinary
147
+ * in-app send or an older server. */
148
+ sourceChannel?: "sms" | "phone" | "voice" | "assistant" | "session" | null;
115
149
  /** Set on a session-transcript row RELAYED from a channel mention
116
150
  * (ADR-0057 Seam 3) — the channel it arrived from. Null once the
117
151
  * channel is deleted; absent on older servers. */
@@ -128,13 +162,49 @@ export interface ConversationsPage {
128
162
  /** Sessions page — each row additionally carries its resident
129
163
  * executor substrate (ADR-0037 §8): `"cloud"` = persistent server-side
130
164
  * sandbox (pickers render the ☁ marker), `"local"` = bridge daemon,
131
- * null = no durable session record yet. */
165
+ * null = no durable session record yet — and, on diff-review spawns,
166
+ * `reviewOf`: the session this row was born to REVIEW (id + its current
167
+ * alias/title; a deleted reviewed conversation degrades to a bare id
168
+ * ref). Null/absent for ordinary sessions and older servers. */
132
169
  export interface SessionsPage {
133
170
  conversations: Array<ConversationRow & {
134
171
  sessionExecutor: "local" | "cloud" | null;
172
+ reviewOf?: {
173
+ conversationId: string;
174
+ alias: string | null;
175
+ title: string | null;
176
+ head?: string | null;
177
+ } | null;
135
178
  }>;
136
179
  next_cursor: string | null;
137
180
  }
181
+ /** One 15-minute sandbox perf rollup bucket from
182
+ * GET /conversations/:id/perf-history (session_perf_rollups). Null
183
+ * utilization fields = the guest never reported that resource in the
184
+ * bucket — never zero-filled. */
185
+ export interface SessionPerfBucket {
186
+ /** Start of the bucket, ISO 8601. */
187
+ bucket_start: string;
188
+ /** Guest samples folded into the bucket (turn + sweep lanes). */
189
+ samples: number;
190
+ cpu_busy_p50: number | null;
191
+ cpu_busy_p95: number | null;
192
+ mem_used_p50: number | null;
193
+ mem_used_p95: number | null;
194
+ disk_used_p95: number | null;
195
+ load1_max: number | null;
196
+ /** Seconds within the bucket any resource sat ≥90% (capped at 900). */
197
+ saturated_seconds: number;
198
+ vcpu: number | null;
199
+ provider: string;
200
+ }
201
+ /** Newest-first, keyset-paginated (house list envelope). */
202
+ export interface SessionPerfHistoryPage {
203
+ object: "list";
204
+ data: SessionPerfBucket[];
205
+ has_more: boolean;
206
+ next_cursor: string | null;
207
+ }
138
208
  export interface ConversationDetail {
139
209
  conversation: ConversationRow;
140
210
  /** Resident executor substrate (ADR-0037 §8): `"local"` = a developer's
@@ -167,16 +237,35 @@ export interface ConversationDetail {
167
237
  factorySlug: string;
168
238
  branch: string;
169
239
  } | null;
240
+ /** THE REVIEWER MARKER (diff-review spawns): non-null ⇒ this session was
241
+ * born to review that conversation's diff. Null for ordinary sessions;
242
+ * absent on older servers (read loosely). */
243
+ sessionReviewOf?: string | null;
170
244
  viewerLastReadAt: string | null;
171
245
  /** The caller's role in this conversation (ADR-0045) — drives client
172
246
  * affordances only; the server remains the authority on every action.
173
247
  * Public channels report implicit `write` for non-member teammates. */
174
248
  viewerRole: ConversationMemberRole;
249
+ /** Provenance when `viewerRole` is DERIVED rather than held: the project
250
+ * (ADR-0045 read floor) or attached channel (ADR-0057) the viewer
251
+ * follows this conversation through. Null/absent for real members —
252
+ * clients use it to explain read-only honestly ("You follow this
253
+ * session through <project>…"). Absent on older servers. */
254
+ viewerRoleVia?: {
255
+ kind: "project" | "channel";
256
+ id: string;
257
+ name: string | null;
258
+ } | null;
175
259
  /** SSE replay watermark: the conversation's highest durable stream-event
176
260
  * id at hydrate time — everything at or below it is already folded into
177
261
  * `messages`. Seed the stream's first `Last-Event-ID` from it instead of
178
262
  * 0 (0 replays the entire history). Absent on older servers. */
179
263
  streamCursor?: number;
264
+ /** Windowed hydrate (2026-08-17): `messages` is the NEWEST window
265
+ * (default 50; `limit` caps at 200). Non-null = older transcript
266
+ * exists — page it with `GET /conversations/:id/messages?before=` +
267
+ * this opaque cursor. Absent on older servers. */
268
+ olderCursor?: string | null;
180
269
  messages: ConversationMessageRow[];
181
270
  }
182
271
  /** Input for `createCloudSession` (ADR-0037 Phase 3b / ADR-0055 §9). The
@@ -257,6 +346,15 @@ export interface CreateCloudSessionInput {
257
346
  contentB64: string;
258
347
  mode: "write" | "append";
259
348
  }>;
349
+ /** DIFF REVIEW spawn: the conversation whose proposed changes this
350
+ * session is born to review. Server-resolved: on a graph-plane drive
351
+ * the new session's branch is minted as a FORK of the reviewed branch
352
+ * (its working dir IS the proposal, `.review/` comparison materials
353
+ * included); on the legacy plane the spawn degrades to an ordinary
354
+ * session. Requires a human caller and a chat session on the reviewed
355
+ * session's factory; 409 `review_of_review` when the target is itself
356
+ * a review session. */
357
+ reviewOfConversationId?: string;
260
358
  }
261
359
  export interface CloudSessionCreated {
262
360
  conversation: ConversationRow;
@@ -326,6 +424,14 @@ export interface SessionForked {
326
424
  /** The new child conversation to switch to. */
327
425
  conversationId: string;
328
426
  }
427
+ /** Result of holding a session's background-work busy lease
428
+ * (`POST /conversations/:id/background-work`). While the lease is live the
429
+ * between-turns park/suspend leaves the session's VM running; it lapses on
430
+ * its own — re-hold to extend. */
431
+ export interface BackgroundWorkHeld {
432
+ /** ISO timestamp the lease now runs to. */
433
+ leaseUntil: string;
434
+ }
329
435
  /** One changed file on a session's drive branch relative to `main`. */
330
436
  export interface SessionFileChange {
331
437
  /** Factory-relative path. */
@@ -357,6 +463,224 @@ export interface SessionChangeSet {
357
463
  truncated: boolean;
358
464
  /** Open `factory_file_conflicts` rows from this session's prior merges. */
359
465
  openConflicts: number;
466
+ /** Graph plane only (additive — absent elsewhere): the opaque cursor for
467
+ * the NEXT page of `changes` — pass it back as `after` on
468
+ * `getSessionChanges`. Null = last page. */
469
+ nextAfter?: string | null;
470
+ /** The serving plane (additive): `"graph"` when the change set is served
471
+ * from the commit graph (paginated, head-anchored); absent = legacy. */
472
+ plane?: "graph";
473
+ /** MERGE GATE (merge-gate spec, additive — absent on older servers):
474
+ * null = ungated. Present: whether THIS caller's merge lands directly
475
+ * (`canMerge` — an owner or named approver) or stages a kind='merge'
476
+ * approval instead (`mergeSessionChanges` answers 202
477
+ * `SessionMergeGated`), plus the resolved approver set. */
478
+ mergeGate?: {
479
+ enabled: true;
480
+ canMerge: boolean;
481
+ approvers: Array<{
482
+ userId: string;
483
+ label: string | null;
484
+ }>;
485
+ } | null;
486
+ /** The OPEN kind='merge' approval already waiting on this session's
487
+ * branch, if any (additive — absent on older servers). */
488
+ pendingApprovalId?: string | null;
489
+ /** DIFF REVIEW (additive — absent on older servers): notes written back
490
+ * by a spawned review session, whether they predate the served diff
491
+ * (`reviewStale` — the changed-file signature no longer matches), the
492
+ * bound review session's conversation + whether it is still live, and
493
+ * whether the deployment offers reviews at all (`reviewEnabled`; false
494
+ * hides the affordance). Reviews are USER-TRIGGERED only — binding
495
+ * rides `POST /conversations/:id/changes/review`, and notes arrive
496
+ * through the review session's own gated write-back. */
497
+ reviewNotes?: string | null;
498
+ reviewStale?: boolean;
499
+ reviewEnabled?: boolean;
500
+ reviewSessionConversationId?: string | null;
501
+ reviewSessionActive?: boolean;
502
+ /** BOUND (additive): the bound review session — the FOLLOW-UP LOOP's
503
+ * driver — exists and is not ended (a parked reviewer between turns is
504
+ * bound but not active). Not publish rights: every reviewer of the
505
+ * diff owns its own document; concurrent reviewers are first-class,
506
+ * bounded at spawn (`reviewer_cap`). */
507
+ reviewSessionBound?: boolean;
508
+ /** The AUTOMATIC follow-up loop (additive): armed (default true) + how
509
+ * many automatic rounds ran since the last human-triggered review
510
+ * (hard cap 2; a manual re-review resets it). */
511
+ reviewAutoFollowup?: boolean;
512
+ reviewAutoRounds?: number;
513
+ /** The OPT-IN main-advance auto-rebase reflex (additive — absent on
514
+ * older servers; default OFF): armed ⇒ when main moves and a preflight
515
+ * proves the fold clean, the platform rebases this session's branch
516
+ * from main headlessly. Toggled via `setSessionAutoRebase`. */
517
+ autoRebaseFromMain?: boolean;
518
+ /** STRUCTURED review suggestions beside the notes (additive — absent on
519
+ * older servers): individually actionable {id, path, title, rationale,
520
+ * patch} entries the review session wrote back, status-stamped
521
+ * `"proposed"` by the server (`"accepted"`/`"rejected"` are reserved for
522
+ * the human decision pass). */
523
+ reviewSuggestions?: ReviewSuggestion[];
524
+ /** THE REVIEWER MARKER (additive — absent on older servers): non-null ⇒
525
+ * THIS session was born to review that conversation's diff. Its own
526
+ * branch is a fork of the reviewed branch and can never merge
527
+ * (`mergeable` false, merge 409s `fork_branch_unmergeable`), and it is
528
+ * refused as a review target (409 `review_of_review`). */
529
+ reviewOfConversationId?: string | null;
530
+ /** PER-REVIEWER review documents (additive — absent on older servers;
531
+ * owner ask 2026-08-22): EVERY reviewer's published document for this
532
+ * diff, newest publish first — parallel reviewers each own one. The
533
+ * flat `reviewNotes`/`reviewSuggestions` above are derived aggregates
534
+ * (the primary document's notes; the reviewer-stamped union); this is
535
+ * the grouped truth the review panel renders. Page 1 only — cursor
536
+ * pages serve an empty array. */
537
+ reviewDocuments?: SessionReviewDocument[];
538
+ }
539
+ /** One reviewer's published review document
540
+ * (`SessionChangeSet.reviewDocuments`). `stale` compares the head this
541
+ * document was published against with the served diff. */
542
+ export interface SessionReviewDocument {
543
+ reviewerConversationId: string;
544
+ /** The reviewer session's @alias / title, when its conversation still
545
+ * exists (a deleted reviewer degrades to the bare id). */
546
+ reviewerAlias: string | null;
547
+ reviewerTitle: string | null;
548
+ notes: string;
549
+ suggestions: ReviewSuggestion[];
550
+ /** ISO timestamp of this document's last publish. */
551
+ publishedAt: string;
552
+ /** The diff head published against (null = pre-stamp reviewer). */
553
+ signature: string | null;
554
+ /** Display form of `signature` ("head cd741f27"). */
555
+ headShort: string | null;
556
+ stale: boolean;
557
+ }
558
+ /** One STRUCTURED, individually actionable suggestion a review session
559
+ * wrote back beside its notes (`SessionChangeSet.reviewSuggestions`). */
560
+ export interface ReviewSuggestion {
561
+ /** Reviewer-minted stable id — survives full-replace re-posts. */
562
+ id: string;
563
+ /** The changed file the suggestion targets (always in the change set —
564
+ * the writeback refuses paths outside it). */
565
+ path: string;
566
+ title: string;
567
+ rationale: string;
568
+ /** Unified-diff hunk targeting the PROPOSED side of `path`. */
569
+ patch: string;
570
+ /** Server-stamped `"proposed"` at writeback; the HUMAN decision routes
571
+ * (`POST …/changes/suggestions/:suggestionId/decide` / `…/decide-all`)
572
+ * are the only writers of the rest — `"accepted"` (the patch landed on
573
+ * the session branch), `"rejected"`, or `"stale"` (an accept found the
574
+ * file drifted since review; nothing was written). */
575
+ status: "proposed" | "accepted" | "rejected" | "stale";
576
+ /** REVIEWER IDENTITY (additive — present on the flat
577
+ * `SessionChangeSet.reviewSuggestions` union; owner ask 2026-08-22):
578
+ * which reviewer's document this entry came from. Ids are unique only
579
+ * PER REVIEWER — the decide-one route takes `reviewerConversationId`
580
+ * when two reviewers reused an id (409 `ambiguous_suggestion`
581
+ * otherwise). */
582
+ reviewerConversationId?: string;
583
+ reviewerAlias?: string | null;
584
+ }
585
+ /** One suggestion in the review write-back (`publishReviewNotes` /
586
+ * `agentc review publish`): `ReviewSuggestion` minus `status` — the server
587
+ * stamps every stored entry `"proposed"`, so a reviewer can never claim
588
+ * acceptance. `path` must name a file in the change set (refused
589
+ * otherwise). */
590
+ export interface ReviewSuggestionInput {
591
+ id: string;
592
+ path: string;
593
+ title: string;
594
+ rationale: string;
595
+ patch: string;
596
+ }
597
+ /** `POST /conversations/:id/review-notes` — the review session publishing
598
+ * its whole review document (full replace: notes AND suggestions
599
+ * together). Gated to the bound review session's own toolbelt key. */
600
+ export interface ReviewNotesPublished {
601
+ ok: true;
602
+ }
603
+ /** `POST /conversations/:id/review-git-credential` — one fresh short-lived
604
+ * credential for the review git remote, minted per git operation by the
605
+ * sandbox's credential helper (`agentc review git-credential`). Gated to
606
+ * the review session's own toolbelt key; never stored, never reused. */
607
+ export interface ReviewGitCredential {
608
+ /** The stable, credential-free remote URL (`https://host/git/<disk>`). */
609
+ url: string;
610
+ username: string;
611
+ password: string;
612
+ /** ISO timestamp the credential expires — informational (the helper
613
+ * mints anew per operation). */
614
+ expiresAt: string;
615
+ }
616
+ /** `POST /conversations/:id/changes/suggestions/:suggestionId/decide` —
617
+ * the post-call truth for that suggestion (an accept that found drift
618
+ * answers `"stale"`; repeats on a settled row are no-ops). */
619
+ export interface ReviewSuggestionDecision {
620
+ object: "review_suggestion_decision";
621
+ conversationId: string;
622
+ id: string;
623
+ status: ReviewSuggestion["status"];
624
+ /** PER-REVIEWER documents (additive): whose document the decided
625
+ * suggestion lives in — null on older servers / legacy ids. */
626
+ reviewerConversationId?: string | null;
627
+ }
628
+ /** `POST /conversations/:id/changes/suggestions/decide-all` — every stored
629
+ * suggestion's post-call status (only `"proposed"` rows flip). */
630
+ export interface ReviewSuggestionDecisions {
631
+ object: "review_suggestion_decisions";
632
+ conversationId: string;
633
+ results: Array<{
634
+ id: string;
635
+ status: ReviewSuggestion["status"];
636
+ reviewerConversationId?: string | null;
637
+ }>;
638
+ }
639
+ /** `GET /conversations/:id/changes/stats` — the changes chip's aggregate
640
+ * +added/−removed line counts, cached server-side per (base, theirsHead).
641
+ * `unavailable` = no cheap head anchors (legacy plane, unbranched) — the
642
+ * chip degrades to its file count, never fake zeros. */
643
+ export type SessionChangeStats = {
644
+ object: "session_change_stats";
645
+ conversationId: string;
646
+ state: "unavailable";
647
+ } | {
648
+ object: "session_change_stats";
649
+ conversationId: string;
650
+ state: "ok";
651
+ additions: number;
652
+ deletions: number;
653
+ files: number;
654
+ /** True when the count is partial (list truncated / file cap). */
655
+ truncated: boolean;
656
+ base: string;
657
+ theirsHead: string;
658
+ };
659
+ /** `POST /conversations/:id/changes/review` — binds a just-spawned review
660
+ * session to the reviewed session and stamps the diff signature the
661
+ * review covers. */
662
+ export interface SessionDiffReviewBound {
663
+ object: "session_diff_review";
664
+ conversationId: string;
665
+ reviewSessionConversationId: string;
666
+ reviewDiffSignature: string;
667
+ }
668
+ /** 202 from `POST /conversations/:id/changes/merge` on a merge-GATED
669
+ * session when the caller is not an approver: nothing merged — the ask
670
+ * froze into (`approval_required`) or converged on (`approval_pending`) a
671
+ * kind='merge' approval routed to the approvers. */
672
+ export interface SessionMergeGated {
673
+ object: "session_merge_gated";
674
+ conversationId: string;
675
+ code: "approval_required" | "approval_pending";
676
+ approvalId: string;
677
+ branch?: string;
678
+ changeCount?: number;
679
+ openConflicts?: number;
680
+ approvers: Array<{
681
+ userId: string;
682
+ label: string | null;
683
+ }>;
360
684
  }
361
685
  /** Per-file accounting of one session branch merge — the merge core's
362
686
  * honest report, returned verbatim. */
@@ -401,6 +725,89 @@ export interface SessionDiscardReport {
401
725
  newBranch: string;
402
726
  discarded: true;
403
727
  }
728
+ /** One row of the conflict preflight (`GET /conversations/:id/changes/preflight`)
729
+ * — the branch-side change decorated with the REAL merge's own per-file
730
+ * disposition, computed report-only: `fastForward` lands as-is (main
731
+ * untouched at the path), `skip` is already-equal on both sides,
732
+ * `conflict` would 409 the merge today. `autoMerge` is the legacy plane's
733
+ * clean-diff3 class — never emitted on the graph plane. */
734
+ export interface SessionPreflightFileRow {
735
+ path: string;
736
+ kind: "add" | "modify" | "delete";
737
+ disposition: "fastForward" | "autoMerge" | "conflict" | "skip";
738
+ /** Set on `conflict` rows only. */
739
+ conflictReason: "same_region" | "binary" | "delete_vs_edit" | null;
740
+ }
741
+ /** `GET /conversations/:id/changes/preflight` — see conflicts AHEAD of the
742
+ * merge: report-only, no writes, no locks. `unavailable` = no cheap head
743
+ * anchors (legacy plane, unbranched) or the classification faulted — the
744
+ * caller degrades honestly, never fake zeros. `pending` = a cold compute
745
+ * outlived the first-response deadline; poll again to collect it. */
746
+ export type SessionChangePreflight = {
747
+ object: "session_change_preflight";
748
+ conversationId: string;
749
+ state: "unavailable";
750
+ } | {
751
+ object: "session_change_preflight";
752
+ conversationId: string;
753
+ state: "pending";
754
+ base: string;
755
+ oursHead: string;
756
+ theirsHead: string;
757
+ } | {
758
+ object: "session_change_preflight";
759
+ conversationId: string;
760
+ state: "ok";
761
+ branch: string;
762
+ base: string;
763
+ /** main's head — the exact commit the dispositions were computed against. */
764
+ oursHead: string;
765
+ /** the session branch's head. */
766
+ theirsHead: string;
767
+ files: SessionPreflightFileRow[];
768
+ conflictCount: number;
769
+ /** Page caps hit — dispositions cover a floor, honestly. */
770
+ truncated: boolean;
771
+ };
772
+ /** Result of `POST /conversations/:id/changes/rebase` — current main
773
+ * folded INTO the session branch (the reverse merge; main untouched).
774
+ * `rebased` advanced the branch by one merge commit (`rebaseCommit`)
775
+ * and future diffs/merges use the new base. `rebased_with_conflicts`
776
+ * folded the clean remainder (`foldCommit`, null when nothing folded) and
777
+ * KEPT the branch's version for `conflictPaths` — each is recorded as a
778
+ * conflict row and the session was told in-conversation to reconcile. */
779
+ export interface SessionRebaseReport {
780
+ object: "session_rebase_report";
781
+ conversationId: string;
782
+ branch: string;
783
+ outcome: "noop" | "rebased" | "rebased_with_conflicts";
784
+ /** main's head at rebase — the branch's new effective base on a clean
785
+ * rebase. */
786
+ mainHead: string;
787
+ rebaseCommit?: string;
788
+ foldCommit?: string | null;
789
+ folded?: {
790
+ added: number;
791
+ modified: number;
792
+ deleted: number;
793
+ skipped: number;
794
+ };
795
+ baselineAdvanced?: boolean;
796
+ /** Capped at 100 on the wire; `conflictCount` is the true total. */
797
+ conflictPaths?: string[];
798
+ conflictCount?: number;
799
+ conflictsTruncated?: boolean;
800
+ /** True when the gateway truncated its conflict list — the fold was
801
+ * skipped whole (an unlisted conflicted path must never be overwritten). */
802
+ foldSkipped?: boolean;
803
+ }
804
+ /** Result of `POST /conversations/:id/changes/autorebase` — the opt-in
805
+ * main-advance auto-rebase reflex's new state. */
806
+ export interface SessionAutoRebaseState {
807
+ object: "session_autorebase";
808
+ conversationId: string;
809
+ autoRebaseFromMain: boolean;
810
+ }
404
811
  /** The sender's page stamp (HUD bar sends) — persisted server-side, never
405
812
  * echoed back on the wire. Mirrors the server's `PageContext` schema. */
406
813
  export interface ConversationPageContext {
@@ -425,6 +832,14 @@ export interface SendConversationMessageInput {
425
832
  * streaming; `queued` = a turn is already in flight — the message is owed
426
833
  * work and a coalesced follow-up turn will answer it. NOT an error. */
427
834
  export type ConversationTurnState = "none" | "started" | "queued";
835
+ /** One @-mentioned user the server DROPPED as a non-member — the mention
836
+ * ping went to nobody, and the response says so instead of staying
837
+ * silent. `name` is the label the sender's own text carried for the
838
+ * mention (picked in the typeahead — no new information). */
839
+ export interface UnnotifiedMention {
840
+ userId: string;
841
+ name: string;
842
+ }
428
843
  export interface SendConversationMessageResult {
429
844
  /** The persisted user message's id. */
430
845
  messageId: string;
@@ -437,6 +852,10 @@ export interface SendConversationMessageResult {
437
852
  * each runs its own turn in its own conversation. Absent on older
438
853
  * servers and non-channel sends. */
439
854
  relayedSessionIds?: string[];
855
+ /** Mentioned users whose ping was dropped by the member filter (a
856
+ * private channel pings members only; a DM pings only the pair) —
857
+ * present only when non-empty. Absent on older servers. */
858
+ unnotifiedMentions?: UnnotifiedMention[];
440
859
  }
441
860
  /** Response of the presence heartbeat (ADR-0037 §6): a fresh agent-liveness
442
861
  * snapshot + the roster TTL. The attach roster itself is NOT returned —
@@ -494,6 +913,62 @@ export interface SessionChannelMessagePosted {
494
913
  * null for a room post. Absent on older servers. */
495
914
  threadRootId?: string | null;
496
915
  }
916
+ /** Result of `POST /session-messages` — one session's agent messaging
917
+ * ANOTHER session's conversation (`agentc session message @alias`). Unlike
918
+ * a channel post, this DOES wake the target's turn machinery: `turn` is
919
+ * the server's verdict ('started' | 'queued' | 'parked' — the agent-to-
920
+ * agent exchange damper landed the message without waking anyone). */
921
+ export interface SessionDirectMessageSent {
922
+ object: "session_message";
923
+ messageId: string;
924
+ target: {
925
+ conversationId: string;
926
+ alias: string | null;
927
+ title: string | null;
928
+ };
929
+ turn: "started" | "queued" | "parked";
930
+ /** Honest posture line — delivery is not completion. */
931
+ note: string;
932
+ }
933
+ /** One branch claim, as the claim/release routes serialize it. */
934
+ export interface BranchClaimInfo {
935
+ id: string;
936
+ branch: string;
937
+ targetConversationId: string;
938
+ holderConversationId: string;
939
+ targetLabel: string;
940
+ holderLabel: string;
941
+ reason: string | null;
942
+ claimedAt: string;
943
+ }
944
+ /** Result of `POST /session-branch-claims` — write authority over the
945
+ * target session's drive branch transferred to the calling session, plus
946
+ * the mount material the CLI uses to FUSE-mount the branch in-guest. */
947
+ export interface BranchClaimGranted {
948
+ object: "branch_claim";
949
+ claim: BranchClaimInfo;
950
+ /** True = the caller already held this claim (re-mint / re-mount path). */
951
+ reclaimedOwn: boolean;
952
+ branch: string;
953
+ token: string;
954
+ gatewayWsUrl: string;
955
+ expiresAtS: number;
956
+ diskId: string;
957
+ }
958
+ /** Result of `POST /session-branch-claims/release` (and the human reclaim
959
+ * door) — the claims this call closed. */
960
+ export interface BranchClaimsReleased {
961
+ object: "branch_claim.release";
962
+ released: BranchClaimInfo[];
963
+ }
964
+ /** `GET /conversations/:id/branch-claim` — one session's claim state: the
965
+ * live claim ON its branch (null = writable there), and the claims it
966
+ * HOLDS on other sessions' branches. */
967
+ export interface BranchClaimState {
968
+ object: "branch_claim.state";
969
+ claimedBy: BranchClaimInfo | null;
970
+ holds: BranchClaimInfo[];
971
+ }
497
972
  /** One agent on the team, as listed by `GET /api/v1/agents`. Bridge-runtime
498
973
  * rows additionally carry live presence (`online`, from the daemon's
499
974
  * heartbeat within the server's TTL) and the hosting machine's label. */