@belonguniverseai/react-sdk 0.6.27 → 0.6.29

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 (143) hide show
  1. package/README.md +43 -4
  2. package/dist/api/account-scope.d.ts +75 -0
  3. package/dist/api/artifact-actions.d.ts +0 -1
  4. package/dist/api/auth-errors.d.ts +21 -0
  5. package/dist/api/client-identity.d.ts +3 -1
  6. package/dist/api/client.d.ts +1394 -162
  7. package/dist/api/goals.d.ts +19 -2
  8. package/dist/api/live-artifacts.d.ts +197 -0
  9. package/dist/api/my-usage.d.ts +28 -0
  10. package/dist/api/queries.d.ts +6 -4
  11. package/dist/api/query-keys.d.ts +44 -3
  12. package/dist/api/task-schedule-sharing.d.ts +18 -3
  13. package/dist/api/task-schedules.d.ts +76 -12
  14. package/dist/app/VoiceOverlay.d.ts +4 -3
  15. package/dist/app/dock-story-data.d.ts +19 -1
  16. package/dist/browser/control-channel.d.ts +9 -0
  17. package/dist/browser/dom-bridge.d.ts +2 -0
  18. package/dist/component/BelongWidget.d.ts +17 -4
  19. package/dist/component/{abnfDiagram-N423BO3Z-DrnjkZwk.js → abnfDiagram-N423BO3Z-BvL7baZ-.js} +4 -4
  20. package/dist/component/{arc-CLchGzOM.js → arc-DVUroP2Z.js} +2 -2
  21. package/dist/component/{architectureDiagram-T3A2C74G-Du8PLen3.js → architectureDiagram-T3A2C74G-CYt93mxs.js} +3 -3
  22. package/dist/component/{blockDiagram-VBNYF7ZC-BxmYbrlL.js → blockDiagram-VBNYF7ZC-yST7U8Pb.js} +4 -4
  23. package/dist/component/{c4Diagram-5PPSVZJV-DHiwlK7l.js → c4Diagram-5PPSVZJV-BHz6Efx3.js} +3 -3
  24. package/dist/component/{channel-C2eeNEMi.js → channel-CKTqNdN4.js} +2 -2
  25. package/dist/component/{chunk-2GRJ4B5K-D6I_U4nB.js → chunk-2GRJ4B5K-Bw2zHHAm.js} +2 -2
  26. package/dist/component/{chunk-2Q5K7J3B-BR3JSmNI.js → chunk-2Q5K7J3B-DpBuF73m.js} +2 -2
  27. package/dist/component/{chunk-5RXB4S5H-DcmzyncC.js → chunk-5RXB4S5H-dxBtwi1p.js} +5 -5
  28. package/dist/component/{chunk-5VM5RSS4-8ww1xEAz.js → chunk-5VM5RSS4-BOTqM1Rc.js} +2 -2
  29. package/dist/component/{chunk-6Q2QTUOP-Chlg3gnC.js → chunk-6Q2QTUOP-DjMXJhRD.js} +2 -2
  30. package/dist/component/{chunk-GF5L2VYU-CgKydjVC.js → chunk-GF5L2VYU-CGpnSfXM.js} +6 -6
  31. package/dist/component/{chunk-JWPE2WC7-13zVpFYT.js → chunk-JWPE2WC7-L5aYKPho.js} +2 -2
  32. package/dist/component/{chunk-KBJHAD2P-CP28c_dJ.js → chunk-KBJHAD2P-DH2RFBaF.js} +2 -2
  33. package/dist/component/{chunk-RYQCIY6F-CaHaa77d.js → chunk-RYQCIY6F-QPgRO0s7.js} +2 -2
  34. package/dist/component/{chunk-XXDRQBXY-Cd0yVkqx.js → chunk-XXDRQBXY-C1ezaFpA.js} +2 -2
  35. package/dist/component/{classDiagram-JCYQIIEL-Bn0740dK.js → classDiagram-JCYQIIEL-Dx7Qx836.js} +3 -3
  36. package/dist/component/{classDiagram-v2-OCEON4UE-Bn0740dK.js → classDiagram-v2-OCEON4UE-Dx7Qx836.js} +3 -3
  37. package/dist/component/{cose-bilkent-JH36ORCC-Y6CvxIe4.js → cose-bilkent-JH36ORCC-Bem1eaLJ.js} +2 -2
  38. package/dist/component/{cynefinDiagram-MW4NZA55-BJ_o5gyM.js → cynefinDiagram-MW4NZA55-DF15EWOu.js} +3 -3
  39. package/dist/component/{dagre-VZM6K2ZE-Ce_ZLKlj.js → dagre-VZM6K2ZE-BVzwev7l.js} +3 -3
  40. package/dist/component/{diagram-7IWD3JNH-K8E_XdII.js → diagram-7IWD3JNH-DzQhKX_v.js} +4 -4
  41. package/dist/component/{diagram-B4RE2ZJO-DZGs1RuF.js → diagram-B4RE2ZJO-DnHINfhE.js} +3 -3
  42. package/dist/component/{diagram-LBJQPF4R-CzKPOqJK.js → diagram-LBJQPF4R-CPqE_GwY.js} +3 -3
  43. package/dist/component/{diagram-Q27KOJAE-UItKj-dk.js → diagram-Q27KOJAE-BjpvfqhL.js} +4 -4
  44. package/dist/component/{diagram-UB23O5K3-ZxBLx0B9.js → diagram-UB23O5K3-BcYqFjqI.js} +3 -3
  45. package/dist/component/{ebnfDiagram-BXEA7PRR-BIS2pIB5.js → ebnfDiagram-BXEA7PRR-DoqGcWGx.js} +4 -4
  46. package/dist/component/{erDiagram-JOGREHBK-BNZB6-ZZ.js → erDiagram-JOGREHBK-DGrj5tIK.js} +5 -5
  47. package/dist/component/{flowDiagram-UKHOOZJN-B8H3ZNjd.js → flowDiagram-UKHOOZJN-B8OuOrVY.js} +7 -7
  48. package/dist/component/{ganttDiagram-PKOTCBZU-2JJJVAY-.js → ganttDiagram-PKOTCBZU-BaxSqTVY.js} +3 -3
  49. package/dist/component/{gitGraphDiagram-DS77QQ5N-BVWxsX1D.js → gitGraphDiagram-DS77QQ5N-W1Sqa3Hc.js} +4 -4
  50. package/dist/component/{highlighted-body-OFNGDK62-CmQzAJHd.js → highlighted-body-OFNGDK62-Cf1UqcGe.js} +2 -2
  51. package/dist/component/index.d.ts +1 -0
  52. package/dist/component/index.js +2 -2
  53. package/dist/component/{infoDiagram-6WML65LV-B-7B2PTM.js → infoDiagram-6WML65LV-BS0JlYPr.js} +2 -2
  54. package/dist/component/{ishikawaDiagram-WSZJBQD7-DgjY91jV.js → ishikawaDiagram-WSZJBQD7-Df_NzOQV.js} +2 -2
  55. package/dist/component/{journeyDiagram-NVQOT4AX-fkb_5H2H.js → journeyDiagram-NVQOT4AX-JWKvZ6rc.js} +5 -5
  56. package/dist/component/{kanban-definition-27J2QSJJ-ZRQERkli.js → kanban-definition-27J2QSJJ-DMvICKAB.js} +3 -3
  57. package/dist/component/{linear-DX0_iAiu.js → linear-D02U2fTS.js} +2 -2
  58. package/dist/component/{mermaid-GHXKKRXX-BhURR8Q_.js → mermaid-GHXKKRXX-B7CJJdoE.js} +51487 -47044
  59. package/dist/component/{mindmap-definition-FAOFIHXS-Duc36TEr.js → mindmap-definition-FAOFIHXS-AzTh8oju.js} +4 -4
  60. package/dist/component/{pegDiagram-VL7TDLO6-Egcz9GLB.js → pegDiagram-VL7TDLO6-DSCzZs_w.js} +4 -4
  61. package/dist/component/{pieDiagram-7S7Q4E2Y-DEe2GHp7.js → pieDiagram-7S7Q4E2Y-CEByr6BB.js} +4 -4
  62. package/dist/component/{quadrantDiagram-CIZ2JOQS-DNfio-6s.js → quadrantDiagram-CIZ2JOQS-BrWOf-GT.js} +3 -3
  63. package/dist/component/{railroadDiagram-AXF67PYL-B3lRYedY.js → railroadDiagram-AXF67PYL-DRtsV2gM.js} +4 -4
  64. package/dist/component/{requirementDiagram-LRYGKXZP-DYYxC0Cg.js → requirementDiagram-LRYGKXZP-_WwAo7AE.js} +4 -4
  65. package/dist/component/{sankeyDiagram-W5VNT64P-CKQNueMs.js → sankeyDiagram-W5VNT64P-DBxP3yyK.js} +2 -2
  66. package/dist/component/{sequenceDiagram-SI44F4Z6-ChF8pgI7.js → sequenceDiagram-SI44F4Z6-B3SNXrpC.js} +4 -4
  67. package/dist/component/{sizeCapture-X5ZJPWSS-BQyVznxb.js → sizeCapture-X5ZJPWSS-D5G8pyPC.js} +2 -2
  68. package/dist/component/{stateDiagram-OKZ733FA-CAVOTaJQ.js → stateDiagram-OKZ733FA-C4UZwsoc.js} +3 -3
  69. package/dist/component/{stateDiagram-v2-UEYNNEHI-rGNqHCbu.js → stateDiagram-v2-UEYNNEHI-DIYB5q15.js} +3 -3
  70. package/dist/component/{swimlanes-SLNWSIFB-j8tXsaid.js → swimlanes-SLNWSIFB-Cc9yJXTh.js} +4 -4
  71. package/dist/component/{swimlanesDiagram-ULZ7WXOC-CsbepRMA.js → swimlanesDiagram-ULZ7WXOC-DsGPiJ-s.js} +3 -3
  72. package/dist/component/{timeline-definition-Z64GVDOM-nCRE1AQh.js → timeline-definition-Z64GVDOM-Dr4-iAeD.js} +3 -3
  73. package/dist/component/{vennDiagram-T6HMQDX7-C3ubGm9c.js → vennDiagram-T6HMQDX7-ZiaZ9jYw.js} +2 -2
  74. package/dist/component/{wardleyDiagram-T6FBY63Y-mKT8yN1z.js → wardleyDiagram-T6FBY63Y-KdF37MT-.js} +3 -3
  75. package/dist/component/{xychartDiagram-ELKLHX3M-D5AF8jgv.js → xychartDiagram-ELKLHX3M-NH_bOKPL.js} +3 -3
  76. package/dist/components/artifacts/ArtifactDetailSurface.d.ts +37 -0
  77. package/dist/components/artifacts/ArtifactMeta.d.ts +10 -0
  78. package/dist/components/artifacts/ArtifactsListSurface.d.ts +11 -0
  79. package/dist/components/artifacts/BindScheduleDialog.d.ts +39 -0
  80. package/dist/components/artifacts/ConfirmDeleteArtifactDialog.d.ts +9 -0
  81. package/dist/components/artifacts/ConfirmReshareDialog.d.ts +24 -0
  82. package/dist/components/artifacts/artifact-format.d.ts +38 -0
  83. package/dist/components/artifacts/open-artifact-version.d.ts +16 -0
  84. package/dist/components/artifacts/use-artifact-actions.d.ts +32 -0
  85. package/dist/components/embed/ComposerContext.d.ts +11 -7
  86. package/dist/components/embed/EmbedComposer.d.ts +7 -1
  87. package/dist/components/embed/EmbedRail.d.ts +9 -2
  88. package/dist/components/embed/GoalRefusalCard.d.ts +5 -1
  89. package/dist/components/embed/MobileNavBar.d.ts +2 -1
  90. package/dist/components/embed/ResponseModeDial.d.ts +11 -0
  91. package/dist/components/embed/SessionStatsPanel.d.ts +3 -0
  92. package/dist/components/embed/UsageSettings.d.ts +11 -0
  93. package/dist/components/embed/format-compact-number.d.ts +8 -0
  94. package/dist/components/embed/use-artifact-operation-notices.d.ts +1 -0
  95. package/dist/components/scheduled/ConfirmRunPausedScheduleDialog.d.ts +18 -0
  96. package/dist/components/scheduled/EmailCopiesFields.d.ts +19 -0
  97. package/dist/components/scheduled/ScheduledDetailSurface.d.ts +2 -1
  98. package/dist/components/scheduled/use-run-now-with-confirmation.d.ts +17 -0
  99. package/dist/embed/artifact-action-bridge.d.ts +13 -3
  100. package/dist/embed/artifact-remint-budget.d.ts +5 -0
  101. package/dist/embed/copy.d.ts +13 -18
  102. package/dist/embed/dock-state.d.ts +1 -1
  103. package/dist/embed/global.d.ts +2 -0
  104. package/dist/embed/goal-edit-command.d.ts +6 -5
  105. package/dist/embed/plan-decision-text.d.ts +1 -0
  106. package/dist/embed/presets.d.ts +1 -0
  107. package/dist/embed/response-mode.d.ts +48 -0
  108. package/dist/embed/theme.d.ts +5 -0
  109. package/dist/embed/voice-config.d.ts +22 -31
  110. package/dist/embed/voice-prefs.d.ts +18 -34
  111. package/dist/i18n/messages/en.d.ts +146 -7
  112. package/dist/i18n/messages/surfaces/artifacts.d.ts +304 -0
  113. package/dist/i18n/messages/surfaces/feedback.d.ts +12 -0
  114. package/dist/i18n/messages/surfaces/header.d.ts +4 -0
  115. package/dist/i18n/messages/surfaces/myUsage.d.ts +75 -0
  116. package/dist/i18n/messages/surfaces/plan.d.ts +4 -0
  117. package/dist/i18n/messages/surfaces/scheduled.d.ts +53 -1
  118. package/dist/i18n/messages/surfaces/settings.d.ts +12 -0
  119. package/dist/i18n/messages/surfaces/stats.d.ts +4 -12
  120. package/dist/i18n/messages/surfaces/stream.d.ts +88 -0
  121. package/dist/i18n/messages/surfaces/usage.d.ts +1 -9
  122. package/dist/infrastructure/telemetry/first-response-timing.d.ts +51 -0
  123. package/dist/lib/tab-control-store.d.ts +14 -0
  124. package/dist/lib/tab-leadership.d.ts +9 -24
  125. package/dist/lib/use-now.d.ts +32 -0
  126. package/dist/lib/use-tab-leadership.d.ts +5 -7
  127. package/dist/routes/artifact-detail.d.ts +2 -0
  128. package/dist/routes/artifacts.d.ts +2 -0
  129. package/dist/routes/chat/history-merge.d.ts +53 -0
  130. package/dist/routes/chat/history-paging.d.ts +25 -6
  131. package/dist/routes/chat/transcript-row.d.ts +1 -0
  132. package/dist/routes/chat/use-catch-up-on-return.d.ts +70 -0
  133. package/dist/routes/chat/use-return-to-page.d.ts +7 -0
  134. package/dist/stream/belong-chat-transport.d.ts +38 -8
  135. package/dist/stream/belong-data-parts.d.ts +10 -0
  136. package/dist/stream/dispatch-once.d.ts +1 -1
  137. package/dist/stream/message-parts.d.ts +3 -1
  138. package/dist/stream/queued-message-outbox.d.ts +6 -0
  139. package/dist/stream/select-session-usage.d.ts +9 -0
  140. package/dist/stream/session-control-context.d.ts +7 -0
  141. package/dist/stream/session-stats-csv.d.ts +7 -9
  142. package/dist/types/public.d.ts +16 -9
  143. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ /** Live ownership shared by the composer and the browser-control channel. */
2
+ export type TabOwnership = {
3
+ readonly term: number;
4
+ readonly tabId: string;
5
+ };
6
+ export type TabControlSnapshot = {
7
+ readonly role: "pending" | "leader" | "follower";
8
+ readonly owner: TabOwnership | null;
9
+ };
10
+ export declare const peekTabControl: (sessionId: string) => TabControlSnapshot;
11
+ export declare const subscribeTabControl: (listener: () => void) => (() => void);
12
+ export declare const publishTabControl: (sessionId: string, snapshot: TabControlSnapshot) => void;
13
+ /** A departing controller must not clear a newer controller's snapshot. */
14
+ export declare const clearTabControl: (sessionId: string, snapshot: TabControlSnapshot) => void;
@@ -1,26 +1,9 @@
1
- /**
2
- * Multi-tab same-session guard (plan 007 §C5).
3
- *
4
- * Two dock tabs open on ONE session can double-send and clobber the persisted
5
- * `{ runId, lastEventId }` stream state. This module elects a single LEADER
6
- * tab per session over a `BroadcastChannel` (`belong:session:<sessionId>`);
7
- * follower tabs banner "open in another tab" and disable the composer.
8
- *
9
- * Protocol (all messages Zod-parsed at the perimeter — §4):
10
- * - on start every tab broadcasts a `claim` carrying `{ tabId, claimedAtMs }`
11
- * - seniority = the EARLIER claim; equal timestamps tie-break on tabId
12
- * - the leader heartbeats every ~5 s and re-asserts when a junior claims
13
- * - a tab that hears a senior claim/heartbeat becomes follower
14
- * - a follower takes over when no senior beat lands for ~12 s, or
15
- * immediately on the leader's `release` (stop / pagehide)
16
- *
17
- * Feature detection: no `BroadcastChannel` (older WebKit, jsdom) → guard
18
- * disabled, the tab stays leader, zero behavior change.
19
- */
1
+ import { TabControlSnapshot } from './tab-control-store.ts';
20
2
  export type TabRole = "leader" | "follower";
21
3
  export declare const HEARTBEAT_INTERVAL_MS = 5000;
22
4
  export declare const TAKEOVER_TIMEOUT_MS = 12000;
23
- /** The narrow `BroadcastChannel` surface the guard uses (injectable for tests). */
5
+ /** Allow the channel's asynchronous discovery replies to arrive before claiming. */
6
+ export declare const DISCOVERY_INTERVAL_MS = 150;
24
7
  export type LeadershipChannel = {
25
8
  postMessage: (message: unknown) => void;
26
9
  close: () => void;
@@ -30,17 +13,19 @@ export type LeadershipChannel = {
30
13
  };
31
14
  export type TabLeadershipOptions = {
32
15
  readonly sessionId: string;
33
- /** Fired on every role CHANGE (the caller assumes "leader" until told). */
34
- readonly onRoleChange: (role: TabRole) => void;
16
+ readonly onChange: (snapshot: TabControlSnapshot) => void;
35
17
  readonly heartbeatIntervalMs?: number;
36
18
  readonly takeoverTimeoutMs?: number;
37
- /** Channel seam; return `null` for "unsupported" (guard disabled). */
19
+ readonly discoveryIntervalMs?: number;
38
20
  readonly createChannel?: (name: string) => LeadershipChannel | null;
39
- /** Clock seam (§25). */
40
21
  readonly nowMs?: () => number;
22
+ /** Separate browser realms have separate page identities; injectable in tests. */
23
+ readonly tabId?: string;
41
24
  };
42
25
  export type TabLeadershipHandle = {
43
26
  readonly stop: () => void;
27
+ readonly requestControl: () => void;
28
+ readonly getSnapshot: () => TabControlSnapshot;
44
29
  };
45
30
  export declare const tabLeadershipChannelName: (sessionId: string) => string;
46
31
  export declare const startTabLeadership: (options: TabLeadershipOptions) => TabLeadershipHandle;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The dock's clock, as one moving value every surface can read.
3
+ *
4
+ * Surfaces that say something about time — "Link works until…", "refreshed 2h
5
+ * ago", "hasn't refreshed since…" — used to capture `new Date()` in a
6
+ * `useState` initializer. That freezes the comparison at MOUNT: a link that
7
+ * expired four minutes ago was still offered as live, and an overdue warning
8
+ * never appeared, even after a refetch brought fresh rows (P2, 2026-09-27
9
+ * PR #614 deep audit finding 8). Server-side expiry is enforced, so the damage
10
+ * was a screen that lied, not access — which is exactly the kind of thing a
11
+ * reader stops trusting the rest of the surface over.
12
+ *
13
+ * ONE STORE, not a hook-local interval, and that is the point: every consumer
14
+ * in a render reads the SAME `Date` object. Two independent clocks a few
15
+ * milliseconds apart can straddle an expiry and render "live" beside "expired"
16
+ * in the same paint (§25), which is the bug the frozen `useState` was
17
+ * originally protecting against — this keeps that guarantee and adds movement.
18
+ *
19
+ * One interval for the whole SDK, started by the first subscriber and cleared
20
+ * by the last: nothing ticks while no surface is on screen, and a store that
21
+ * has been idle re-reads the wall clock when it starts again rather than
22
+ * handing out the instant it stopped at.
23
+ */
24
+ /**
25
+ * How often the shared instant advances. Half a minute: everything the dock
26
+ * says about time is minute-scale or coarser ("works until 10:00", "2h ago"),
27
+ * so this is fine enough that no reader sees a stale answer, and coarse enough
28
+ * that an idle dock is not re-rendering its surfaces every second.
29
+ */
30
+ export declare const NOW_TICK_MS = 30000;
31
+ /** The current instant, shared with every other consumer of this render. */
32
+ export declare const useNow: () => Date;
@@ -1,8 +1,6 @@
1
1
  import { TabRole } from './tab-leadership.ts';
2
- /**
3
- * @param chatId The mount-time chat id — the persisted server session if one
4
- * was already known at mount, else a per-tab `belong-chat-<nanoid>`
5
- * placeholder. Used only as the channel key until the real server session id
6
- * is persisted.
7
- */
8
- export declare const useTabLeadership: (chatId: string) => TabRole;
2
+ /** The persisted server id replaces a new Session's per-tab placeholder. */
3
+ export declare const useTabLeadership: (chatId: string) => {
4
+ readonly role: TabRole;
5
+ readonly requestControl: () => void;
6
+ };
@@ -0,0 +1,2 @@
1
+ /** /artifacts/:liveArtifactId — one live report and every version it has had. */
2
+ export declare const ArtifactDetailRoute: () => import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ /** /artifacts — the live reports the assistant keeps up to date (rail tab). */
2
+ export declare const ArtifactsRoute: () => import("react").JSX.Element;
@@ -0,0 +1,53 @@
1
+ import { BelongUIMessage } from '../../stream/belong-data-parts.ts';
2
+ /**
3
+ * Merge freshly-fetched server history with whatever is CURRENTLY on screen
4
+ * (§41 — a history load must never make a message the user is looking at
5
+ * disappear). Server order is authoritative; any LOCAL-ONLY message — most
6
+ * often the optimistic turn a concurrent send appended after this fetch
7
+ * started — survives, appended AFTER the server-ordered messages rather than
8
+ * lost to the replace.
9
+ *
10
+ * What is on screen above the page stays above it: the page lines up on the
11
+ * first of its rows found on screen (`isRowOf`, so a turn this tab streamed
12
+ * live lines up too), then back over the earlier rows of the page.
13
+ *
14
+ * Dedupe prefers the row id, then the client id the row was sent with. A
15
+ * message with no id match yet falls back to `(role, flattened text)`. Only
16
+ * the suffix AFTER the last server-known on-screen message can be local-only.
17
+ * Within that suffix, an assistant at the same next position as a durable
18
+ * assistant is the same historical turn with stream-vs-folded whitespace/part
19
+ * drift, not a new reply. A genuinely live assistant is preserved when
20
+ * durable history has no assistant at that position yet.
21
+ */
22
+ export declare const mergeRestoredHistory: (loaded: readonly BelongUIMessage[], onScreen: readonly BelongUIMessage[]) => BelongUIMessage[];
23
+ /**
24
+ * A kept chat shows every turn it streamed. The server's newest page holds
25
+ * something it never saw only when it has a user turn that is not on screen:
26
+ * one sent from another tab or device (the reply to it comes with it).
27
+ *
28
+ * Turns are matched one to one, by their row id or the client id they were
29
+ * sent with, so a prompt repeated elsewhere ("continue", or two files-only
30
+ * turns with no text) is still a new turn. Text is only a fallback for a row
31
+ * that carries no client id, matched in order against the turns on screen
32
+ * that no id accounted for.
33
+ */
34
+ export declare const hasUnseenTurns: (loaded: readonly BelongUIMessage[], onScreen: readonly BelongUIMessage[]) => boolean;
35
+ /**
36
+ * #839 — what an IDLE chat shows once the reader comes back to it, given the
37
+ * newest page read just now: the conversation a reload would show (§41),
38
+ * without the reload. Null when the page brings nothing the screen lacks, so
39
+ * a return with nothing new leaves every message — and every card — as it is.
40
+ *
41
+ * - The page lines up with the screen (some message on screen is one of its
42
+ * rows): the turns added elsewhere are merged in after what is on screen,
43
+ * exactly as a mount merges its read (`mergeRestoredHistory`) — no
44
+ * duplicates, server order, older pages above it and anything only this
45
+ * tab holds kept.
46
+ * - Nothing on screen is on the page: the conversation moved past a whole
47
+ * page while this tab was away, or was cleared elsewhere and went on. What
48
+ * is on screen is from before the page, so the page replaces it, as a
49
+ * reload would; scrolling up loads what came before it.
50
+ * - An empty page never empties the screen: a chat cleared in another tab
51
+ * reads empty after the next reload, but a return alone wipes nothing.
52
+ */
53
+ export declare const catchUpTranscript: (page: readonly BelongUIMessage[], onScreen: readonly BelongUIMessage[]) => BelongUIMessage[] | null;
@@ -38,16 +38,35 @@ export declare const withoutOlderPages: <T extends {
38
38
  export declare const olderHistoryCursor: (paging: HistoryPaging, messages: readonly {
39
39
  readonly id: string;
40
40
  }[]) => string | undefined;
41
+ /** Where a walk back over a page stands: page row and on-screen message. */
42
+ export type PageRowPlace = {
43
+ readonly pageIndex: number;
44
+ readonly screenIndex: number;
45
+ };
46
+ /** How a page's rows are told apart on screen (see {@link splitOlderPrefix}). */
47
+ export type PageRowMatch<R, T> = {
48
+ /** `message` IS `row`. Anchors the page on screen. */
49
+ readonly isRow: (row: R, message: T) => boolean;
50
+ /** `message`, on screen right above where the page lines up so far, stands
51
+ * for `row`, an earlier page row; `at` says where both are. */
52
+ readonly isSameTurn: (row: R, message: T, at: PageRowPlace) => boolean;
53
+ };
41
54
  /**
42
- * What a newest-page re-read must leave alone: the older pages the reader
43
- * scrolled in ABOVE it. Found by the page's first row id; nothing when the
44
- * page does not line up with the screen (then it is merged as a whole).
55
+ * What a newest-page re-read must leave alone: whatever is on screen ABOVE
56
+ * the page (older pages the reader scrolled in, turns from before it). The
57
+ * page's first row found on screen anchors it; walking back from there, each
58
+ * earlier page row takes the message right above while that message stands
59
+ * for it, and the first one that does not ends the walk: what is above it
60
+ * stays where it is, even if that shows a message the page also holds. An
61
+ * extra message reads better than a wrong pairing. Nothing when no row of the
62
+ * page is on screen (it is merged as a whole). By default rows match by id
63
+ * only; the chat's merge passes its own rules.
45
64
  */
46
- export declare const splitOlderPrefix: <T extends {
65
+ export declare const splitOlderPrefix: <R extends {
47
66
  readonly id: string;
48
- }>(loaded: readonly {
67
+ }, T extends {
49
68
  readonly id: string;
50
- }[], current: readonly T[]) => {
69
+ }>(loaded: readonly R[], current: readonly T[], match?: PageRowMatch<R, T>) => {
51
70
  readonly older: readonly T[];
52
71
  readonly rest: readonly T[];
53
72
  };
@@ -29,6 +29,7 @@ export type TranscriptRowWiring = {
29
29
  readonly onGoalStartAnother: () => void;
30
30
  };
31
31
  export type TranscriptRowProps = {
32
+ readonly onFirstResponseRendered?: ComponentProps<typeof MessageParts>["onFirstResponseRendered"];
32
33
  readonly message: BelongUIMessage;
33
34
  /** This message's index in the FULL conversation (not the render window). */
34
35
  readonly index: number;
@@ -0,0 +1,70 @@
1
+ import { BelongUIMessage } from '../../stream/belong-data-parts.ts';
2
+ import { HistoryPageResult, LoadedHistoryPage } from './history-cache.ts';
3
+ import { useReturnToPage } from './use-return-to-page.ts';
4
+ /** The chat as the catch-up reads it: `@ai-sdk/react`'s `Chat`, live. Its
5
+ * `messages` array is replaced on every change, so identity says "changed". */
6
+ export type CatchUpChat = {
7
+ readonly status: "submitted" | "streaming" | "ready" | "error";
8
+ readonly messages: readonly BelongUIMessage[];
9
+ };
10
+ /** The server's answer about the session's active run. `failed` (network,
11
+ * non-2xx, a drifted body) is not `none`: only a confirmed `none` is
12
+ * remembered. */
13
+ export type ActiveRunLookup = {
14
+ readonly kind: "none";
15
+ } | {
16
+ readonly kind: "run";
17
+ readonly runId: string;
18
+ } | {
19
+ readonly kind: "failed";
20
+ };
21
+ /** The transport as the catch-up reads it. */
22
+ export type CatchUpTransport = {
23
+ readonly sessionId: string | null;
24
+ /** A stream, its handshake or its retry loop owns this session. */
25
+ readonly hasLiveStream: boolean;
26
+ };
27
+ /** What else may own the transcript right now; any of them blocks a step. */
28
+ export type CatchUpBlockers = {
29
+ /** The workspace does not resolve. */
30
+ readonly workspaceMissing: boolean;
31
+ /** The mount's first history read is out. */
32
+ readonly historyLoading: boolean;
33
+ /** A resume handshake is under way. */
34
+ readonly restoringLiveRun: boolean;
35
+ /** A queued-reply reconciliation is pending. It starts without a render,
36
+ * so it is asked for when needed rather than passed as a value. */
37
+ readonly queuedReconciliationPending: () => boolean;
38
+ };
39
+ export declare const useCatchUpOnReturn: (input: {
40
+ readonly chat: CatchUpChat;
41
+ readonly transport: CatchUpTransport;
42
+ /** Checked when a step starts and again after every await, against the
43
+ * latest render's values, never those of the render that started it. */
44
+ readonly blockers: CatchUpBlockers;
45
+ /** Read the newest page from the server now. */
46
+ readonly readNewestPage: (sessionId: string) => Promise<HistoryPageResult>;
47
+ /** The read landed after the transcript changed: the page it cached may
48
+ * predate the change (a clear), so no chat may be painted from it. */
49
+ readonly onReadOutdated: (sessionId: string) => void;
50
+ /** Show `caughtUp`, the conversation `page` brought the screen up to. */
51
+ readonly onCaughtUp: (caughtUp: BelongUIMessage[], page: LoadedHistoryPage) => void;
52
+ /** Ask the server for the session's active run. */
53
+ readonly findActiveRun: (sessionId: string) => Promise<ActiveRunLookup>;
54
+ /** Follow `runId`, the reply to the turn `page` ends on, from its first event. */
55
+ readonly onFollowRun: (sessionId: string, runId: string, page: LoadedHistoryPage) => void;
56
+ /** The session this tab has selected now (the stream-state pointer). */
57
+ readonly selectedSessionId: () => string | null;
58
+ /** Bumped when the transcript's work loses its owner (leaving the chat, a
59
+ * clear, a stop): a step started under an older value drops itself. A
60
+ * clear's POST runs before it empties the screen, so the transcript alone
61
+ * cannot tell a lookup that the clear is under way. */
62
+ readonly ownerGeneration: () => number;
63
+ /** A newly controlling tab catches up even when the page stayed visible. */
64
+ readonly controlRole?: "leader" | "follower";
65
+ /** Explicit retries of a control request, including a failed catch-up. */
66
+ readonly controlRequestId?: number;
67
+ readonly prepareControl?: (sessionId: string) => Promise<void>;
68
+ readonly onControlReady?: (ready: boolean) => void;
69
+ readonly returnOptions?: Parameters<typeof useReturnToPage>[1];
70
+ }) => void;
@@ -0,0 +1,7 @@
1
+ /** Two return signals closer together than this are one return. */
2
+ export declare const RETURN_MIN_GAP_MS = 2000;
3
+ export declare const useReturnToPage: (onReturn: () => void, options?: {
4
+ readonly minGapMs?: number;
5
+ /** Clock seam (§25). */
6
+ readonly nowMs?: () => number;
7
+ }) => void;
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { ChatTransport, UIMessageChunk } from 'ai';
3
+ import { FirstResponseObservation, FirstResponseSample } from '../infrastructure/telemetry/first-response-timing.ts';
4
+ import { AUTH_EXPIRED_ERROR_PREFIX } from '../api/auth-errors.ts';
3
5
  import { BelongUIMessage, WireStreamPart } from './belong-data-parts.ts';
4
6
  declare const cancelRunResponseSchema: z.ZodObject<{
5
7
  recalledMessages: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -22,9 +24,9 @@ export declare const choiceAnswerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
22
24
  export declare const choiceStateSchema: z.ZodEnum<{
23
25
  cancelled: "cancelled";
24
26
  pending: "pending";
25
- detached: "detached";
26
27
  dismissed: "dismissed";
27
28
  answered: "answered";
29
+ detached: "detached";
28
30
  }>;
29
31
  /**
30
32
  * The wire/stored part union. Exported so the history loader (chat.tsx) can
@@ -37,6 +39,14 @@ export declare const wireStreamPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
37
39
  type: z.ZodLiteral<"text">;
38
40
  id: z.ZodString;
39
41
  text: z.ZodString;
42
+ responsePhase: z.ZodCatch<z.ZodOptional<z.ZodObject<{
43
+ phase: z.ZodEnum<{
44
+ acknowledgment: "acknowledgment";
45
+ main: "main";
46
+ }>;
47
+ sourceMessageIds: z.ZodArray<z.ZodString>;
48
+ replyKey: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$strip>>>;
40
50
  }, z.core.$strip>, z.ZodObject<{
41
51
  type: z.ZodLiteral<"data-reasoning">;
42
52
  id: z.ZodString;
@@ -250,6 +260,7 @@ export declare const wireStreamPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
250
260
  count: z.ZodOptional<z.ZodNumber>;
251
261
  }, z.core.$strip>>;
252
262
  }, z.core.$strip>>;
263
+ weightedHundredths: z.ZodOptional<z.ZodNumber>;
253
264
  }, z.core.$strip>;
254
265
  }, z.core.$strip>, z.ZodObject<{
255
266
  type: z.ZodLiteral<"data-choice-request">;
@@ -259,9 +270,9 @@ export declare const wireStreamPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
259
270
  state: z.ZodEnum<{
260
271
  cancelled: "cancelled";
261
272
  pending: "pending";
262
- detached: "detached";
263
273
  dismissed: "dismissed";
264
274
  answered: "answered";
275
+ detached: "detached";
265
276
  }>;
266
277
  questions: z.ZodArray<z.ZodObject<{
267
278
  id: z.ZodString;
@@ -354,9 +365,9 @@ export declare const wireStreamPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
354
365
  at: z.ZodString;
355
366
  }, z.core.$strip>>>;
356
367
  runway: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
368
+ long: "long";
357
369
  quick: "quick";
358
370
  normal: "normal";
359
- long: "long";
360
371
  }>>>;
361
372
  }, z.core.$strip>;
362
373
  }, z.core.$strip>, z.ZodObject<{
@@ -367,15 +378,17 @@ export declare const wireStreamPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
367
378
  why: z.ZodString;
368
379
  criteria: z.ZodOptional<z.ZodArray<z.ZodString>>;
369
380
  runway: z.ZodOptional<z.ZodEnum<{
381
+ long: "long";
370
382
  quick: "quick";
371
383
  normal: "normal";
372
- long: "long";
373
384
  }>>;
374
385
  }, z.core.$strip>;
375
386
  }, z.core.$strip>], "type">;
376
- /** A request was 401'd twice (once with a freshly re-read token): the host
377
- * session is expired — the dock must surface re-auth, not retry forever. */
378
- export declare const AUTH_EXPIRED_ERROR_PREFIX = "auth_expired:";
387
+ /** A request was 401'd twice (once with a freshly re-read token), or no usable
388
+ * token could be had at all (`BelongAuthUnavailableError`): the host session is
389
+ * expired — the dock must surface re-auth, not retry forever. Defined by the
390
+ * request layer, which raises it too; re-exported here for chat.tsx. */
391
+ export { AUTH_EXPIRED_ERROR_PREFIX };
379
392
  /** The SSE reconnect loop exhausted its attempt budget without the backend
380
393
  * delivering a single event — the service is unreachable. */
381
394
  export declare const BACKEND_UNAVAILABLE_ERROR_PREFIX = "backend_unavailable:";
@@ -434,6 +447,10 @@ export declare const RESUME_GRACE_MS = 120000;
434
447
  * across tests in the same file otherwise). */
435
448
  export declare const __resetLiveStreamRegistryForTest: () => void;
436
449
  export type BelongChatTransportOptions = {
450
+ readonly firstResponseTiming?: {
451
+ readonly nowMs: () => number;
452
+ readonly report: (sample: FirstResponseSample) => void;
453
+ };
437
454
  /** Belong cloud base URL, e.g. `http://localhost:8787`. */
438
455
  readonly apiBaseUrl: string;
439
456
  /**
@@ -469,6 +486,10 @@ export type BelongChatTransportOptions = {
469
486
  readonly foregroundGeneration?: number;
470
487
  };
471
488
  export type StreamContext = {
489
+ textPhaseKey?: string;
490
+ firstResponseObservations?: (phase: NonNullable<Extract<WireStreamPart, {
491
+ type: "text";
492
+ }>["responsePhase"]>) => readonly FirstResponseObservation[];
472
493
  modelActivity?: {
473
494
  id: string;
474
495
  stepIndex: number;
@@ -572,8 +593,17 @@ export declare class BelongChatTransport implements ChatTransport<BelongUIMessag
572
593
  * unaccepted stop.
573
594
  */
574
595
  stopSubagent: (runId: string, subagentId: string) => Promise<void>;
596
+ beginFirstResponse: (clientMessageId: string) => void;
597
+ cancelFirstResponse: (clientMessageId: string) => void;
598
+ acceptFirstResponse: (input: {
599
+ sessionId: string;
600
+ runId: string;
601
+ clientMessageId: string;
602
+ messageId: string;
603
+ idempotent: boolean;
604
+ }) => void;
605
+ observeFirstResponseRendered: (observation: FirstResponseObservation) => void;
575
606
  sendMessages: ChatTransport<BelongUIMessage>["sendMessages"];
576
607
  reconnectToStream: ChatTransport<BelongUIMessage>["reconnectToStream"];
577
608
  }
578
609
  export declare const wireToUiChunks: (part: WireStreamPart, ctx: StreamContext) => readonly UIMessageChunk[];
579
- export {};
@@ -310,6 +310,11 @@ export type UsageData = {
310
310
  readonly autoCompactThresholdChars: number;
311
311
  /** The detailed context-window breakdown (own-loop runtimes only). */
312
312
  readonly contextWindow?: ContextWindowData | undefined;
313
+ /**
314
+ * Plan 018 — the turn in WEIGHTED tokens, in hundredths: the one unit the
315
+ * dock shows users. Absent on a turn from a cloud that predates it.
316
+ */
317
+ readonly weightedHundredths?: number | undefined;
313
318
  };
314
319
  /**
315
320
  * Goal Mode M6 — a goal the agent OFFERED. Nothing exists on the backend
@@ -367,6 +372,11 @@ export type BelongUIMessage = UIMessage<BelongMessageMetadata, BelongDataParts>;
367
372
  * chunks yet — the transport translates this into `UIMessageChunk`.
368
373
  */
369
374
  export type WireTextPart = {
375
+ readonly responsePhase?: {
376
+ readonly phase: "acknowledgment" | "main";
377
+ readonly sourceMessageIds: readonly string[];
378
+ readonly replyKey?: string | undefined;
379
+ } | undefined;
370
380
  readonly type: "text";
371
381
  readonly id: string;
372
382
  readonly text: string;
@@ -4,7 +4,7 @@ export declare const claimDispatch: (key: string) => boolean;
4
4
  * Run `effect` once for `key` on this page — or, without a key, once for the
5
5
  * component's lifetime (React 19 StrictMode double-invokes included).
6
6
  */
7
- export declare const useDispatchOnce: (key: string | undefined, effect: () => void, deps: readonly unknown[]) => void;
7
+ export declare const useDispatchOnce: (key: string | undefined, effect: () => void, deps: readonly unknown[], canDispatch?: () => boolean) => void;
8
8
  /** Where a dispatch card's one-shot call stands. */
9
9
  export type DispatchStatus = "dispatching" | "running" | "ok" | "no_handler" | "failed" | "done";
10
10
  /** What a dispatch card shows, remembered by the same key as its guard. */
@@ -1,4 +1,5 @@
1
1
  import { BelongMessagePart, PartRendererProps } from './ai-sdk-part-renderers.tsx';
2
+ import { FirstResponseObservation } from '../infrastructure/telemetry/first-response-timing.ts';
2
3
  /**
3
4
  * The parts a message is INDEXED from (spec deviation 16). File parts render as
4
5
  * chips above the message and never reach a render group, and dropping one
@@ -10,7 +11,7 @@ import { BelongMessagePart, PartRendererProps } from './ai-sdk-part-renderers.ts
10
11
  * is the same list.
11
12
  */
12
13
  export declare const indexableMessageParts: (parts: readonly BelongMessagePart[]) => readonly BelongMessagePart[];
13
- export declare const MessageParts: ({ parts, streaming, historical, onContinueConnector, onExecutePlan, onFollowContinuation, partProps, }: {
14
+ export declare const MessageParts: ({ parts, streaming, historical, onContinueConnector, onExecutePlan, onFollowContinuation, partProps, onFirstResponseRendered, }: {
14
15
  /** The message's parts, file parts already removed (they render as chips). */
15
16
  readonly parts: readonly BelongMessagePart[];
16
17
  /** This message is the one a run is streaming into right now. */
@@ -39,4 +40,5 @@ export declare const MessageParts: ({ parts, streaming, historical, onContinueCo
39
40
  * the named props still wins. Absent = no goal wiring (stories, tests).
40
41
  */
41
42
  readonly partProps?: ((part: BelongMessagePart) => Partial<PartRendererProps>) | undefined;
43
+ readonly onFirstResponseRendered?: ((observation: FirstResponseObservation) => void) | undefined;
42
44
  }) => import("react").JSX.Element;
@@ -15,6 +15,12 @@ declare const queuedMessageOutboxEntrySchema: z.ZodObject<{
15
15
  runtime: z.ZodOptional<z.ZodString>;
16
16
  llmProvider: z.ZodOptional<z.ZodString>;
17
17
  llmModel: z.ZodOptional<z.ZodString>;
18
+ responseMode: z.ZodOptional<z.ZodEnum<{
19
+ balanced: "balanced";
20
+ fast: "fast";
21
+ smart: "smart";
22
+ }>>;
23
+ pageLabel: z.ZodOptional<z.ZodString>;
18
24
  workspaceExternalId: z.ZodOptional<z.ZodString>;
19
25
  }, z.core.$strip>;
20
26
  export type QueuedMessageOutboxEntry = Readonly<z.infer<typeof queuedMessageOutboxEntrySchema>>;
@@ -30,6 +30,13 @@ export type SessionUsageSummary = {
30
30
  * panel then falls back to the session-usage view.
31
31
  */
32
32
  readonly contextWindow: ContextWindowData | null;
33
+ /**
34
+ * Plan 018 — the chat in WEIGHTED tokens, in hundredths: the one figure the
35
+ * panel shows users (round once with `Math.round(x / 100)`). `null` when any
36
+ * turn folded here, or the history baseline, predates the field — an honest
37
+ * "unknown" rather than a total that silently skips turns.
38
+ */
39
+ readonly totalWeightedHundredths: number | null;
33
40
  };
34
41
  /** The chat's usage total as of a history read (the ledger's sums). */
35
42
  export type SessionUsageBaseline = {
@@ -39,6 +46,8 @@ export type SessionUsageBaseline = {
39
46
  readonly totalCacheReadInputTokens: number;
40
47
  readonly totalCacheWriteInputTokens: number;
41
48
  readonly turns: number;
49
+ /** Plan 018 — absent from a cloud that predates weighted tokens. */
50
+ readonly totalWeightedHundredths?: number | undefined;
42
51
  };
43
52
  export declare const selectSessionUsage: (messages: readonly BelongUIMessage[],
44
53
  /**
@@ -0,0 +1,7 @@
1
+ export type SessionControl = {
2
+ readonly readOnly: boolean;
3
+ /** Read live ownership again at the moment a side effect starts. */
4
+ readonly canControl: () => boolean;
5
+ };
6
+ export declare const SessionControlContext: import('react').Context<SessionControl>;
7
+ export declare const useSessionControl: () => SessionControl;
@@ -6,6 +6,10 @@
6
6
  * DTOs. Kept as an explicit local mirror (not inferred from AppType) so the CSV
7
7
  * column contract is readable in one place.
8
8
  *
9
+ * Plan 018 — tokens appear in WEIGHTED tokens only, the one unit users see; the
10
+ * raw input / output split is no longer exported. The weighted fields are
11
+ * optional here because an older cloud does not send them (an empty cell).
12
+ *
9
13
  * RFC-4180: CRLF line breaks, header first, a field is quoted when it contains
10
14
  * `"`, `,`, CR or LF; interior quotes are doubled. CSV-injection guard: a text
11
15
  * field starting with = + - @ is prefixed with a single quote before quoting.
@@ -13,14 +17,10 @@
13
17
  export type SessionStatsTurn = {
14
18
  readonly turn: number;
15
19
  readonly at: string;
16
- readonly inputTokens: number;
17
- readonly outputTokens: number;
18
- readonly totalTokens: number;
20
+ readonly weightedTokens?: number;
19
21
  };
20
22
  export type SessionStatsDetail = {
21
- readonly totalInputTokens: number;
22
- readonly totalOutputTokens: number;
23
- readonly totalTokens: number;
23
+ readonly totalWeightedTokens?: number;
24
24
  readonly turns: number;
25
25
  readonly runningMs: number;
26
26
  readonly toolCalls: number;
@@ -32,9 +32,7 @@ export type SessionStatsSummaryRow = {
32
32
  readonly title: string | null;
33
33
  readonly createdAt: string;
34
34
  readonly updatedAt: string;
35
- readonly totalInputTokens: number;
36
- readonly totalOutputTokens: number;
37
- readonly totalTokens: number;
35
+ readonly totalWeightedTokens?: number;
38
36
  readonly turns: number;
39
37
  readonly runningMs: number;
40
38
  readonly toolCalls: number;
@@ -114,17 +114,17 @@ export type PartialBelongTheme = {
114
114
  /** Per-locale welcome-copy override (BEL-135). */
115
115
  readonly copy?: DockCopyOverride;
116
116
  };
117
- /** Built-in theme presets selectable via `belong.applyPreset(name)`. */
118
- export type PresetName = "belong" | "simetrik" | "custom";
117
+ /** Theme styles. `tenant` restores the latest published tenant default. */
118
+ export type PresetName = "tenant" | "belong" | "simetrik" | "custom";
119
119
  /** UI languages the dock ships. `setLocale` / `init({ locale })` accept these. */
120
120
  export type BelongLocale = "en" | "es" | "pt" | "it";
121
121
  export type BelongInitInput = {
122
122
  /** Belong cloud base URL, e.g. `https://api.belong.host` or `http://localhost:8787`. */
123
123
  readonly apiBaseUrl: string;
124
124
  /**
125
- * Public tenant slug (BEL-129, multi-tenant deployments). Used for
125
+ * Public tenant ID or slug (multi-tenant deployments). Used for
126
126
  * boot-time, unauthenticated surfaces only — the theme fetch adds
127
- * `?tenant=<slug>` and every request carries it as `X-Belong-Tenant`
127
+ * `?tenant=<id-or-slug>` and every request carries it as `X-Belong-Tenant`
128
128
  * (observability). Authenticated routes derive the tenant from the JWT;
129
129
  * this value can never escalate. Omit on single-tenant deployments.
130
130
  */
@@ -166,9 +166,11 @@ export type BelongInitInput = {
166
166
  };
167
167
  /**
168
168
  * What page the user is on, shown as "Looking at ‹label›" before the first
169
- * message. Omit `label` to use the browser tab title (`document.title`,
169
+ * message and in the composer's page chip, and SENT with every message so
170
+ * the agent can resolve "this page" (the label only, never the page's
171
+ * contents). Omit `label` to use the browser tab title (`document.title`,
170
172
  * tracked live so SPA navigation updates the line); pass
171
- * `fromDocumentTitle: false` to show nothing unless a label is given.
173
+ * `fromDocumentTitle: false` to show and send nothing unless a label is given.
172
174
  * A re-`init` replaces this config wholesale: omitting `pageContext` clears
173
175
  * a previously seeded `label` and restores the tab-title fallback.
174
176
  * `belong.setPageContext` overrides both at runtime and SURVIVES a later
@@ -182,7 +184,12 @@ export type BelongInitInput = {
182
184
  readonly theme?: PartialBelongTheme;
183
185
  /** Optional voice config (push-to-talk + read-aloud). */
184
186
  readonly voice?: {
185
- /** Hold-to-talk key as a `KeyboardEvent.code`. Default `"ControlLeft"`. */
187
+ /**
188
+ * Default hold-to-talk key as a `KeyboardEvent.code`: one of
189
+ * `"ControlRight"`, `"ControlLeft"`, `"AltRight"`, `"AltLeft"`, `"F2"`,
190
+ * `"F8"`, `"F9"`; any other code is ignored. Default: none — each user
191
+ * picks a key (or keeps it off) in Settings → Voice, and their choice wins.
192
+ */
186
193
  readonly pushToTalkKey?: string;
187
194
  /** Auto-read assistant replies aloud (ElevenLabs TTS). Default `true`. */
188
195
  readonly autoplayTts?: boolean;
@@ -324,8 +331,8 @@ export type BelongGlobal = {
324
331
  readonly displayName?: string | null;
325
332
  } | null) => void;
326
333
  /**
327
- * Set the page label shown before the first message (wins over
328
- * `init({ pageContext })` and the tab title). `null` drops the override.
334
+ * Set the page label shown in the dock and sent with each message (wins
335
+ * over `init({ pageContext })` and the tab title). `null` drops the override.
329
336
  */
330
337
  readonly setPageContext: (label: string | null) => void;
331
338
  readonly setTheme: (theme: PartialBelongTheme) => void;