@vibefield/rosetta-protocol 0.0.0-stage → 0.1.0

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 (354) hide show
  1. package/README.md +73 -2
  2. package/dist/base64url.d.ts +4 -0
  3. package/dist/base64url.js +48 -0
  4. package/dist/client.d.ts +324 -0
  5. package/dist/client.js +981 -0
  6. package/dist/generated/ActivityResponse.d.ts +16 -0
  7. package/dist/generated/ActivityResponse.js +1 -0
  8. package/dist/generated/ActivitySession.d.ts +19 -0
  9. package/dist/generated/ActivitySession.js +1 -0
  10. package/dist/generated/ApiError.d.ts +5 -0
  11. package/dist/generated/ApiError.js +1 -0
  12. package/dist/generated/ArtifactKind.d.ts +1 -0
  13. package/dist/generated/ArtifactKind.js +2 -0
  14. package/dist/generated/ArtifactOp.d.ts +1 -0
  15. package/dist/generated/ArtifactOp.js +2 -0
  16. package/dist/generated/AssetRef.d.ts +14 -0
  17. package/dist/generated/AssetRef.js +1 -0
  18. package/dist/generated/AttachmentKind.d.ts +1 -0
  19. package/dist/generated/AttachmentKind.js +2 -0
  20. package/dist/generated/AttachmentRef.d.ts +21 -0
  21. package/dist/generated/AttachmentRef.js +1 -0
  22. package/dist/generated/AttachmentSource.d.ts +13 -0
  23. package/dist/generated/AttachmentSource.js +1 -0
  24. package/dist/generated/AttachmentUnavailableReason.d.ts +1 -0
  25. package/dist/generated/AttachmentUnavailableReason.js +2 -0
  26. package/dist/generated/BackfillState.d.ts +4 -0
  27. package/dist/generated/BackfillState.js +2 -0
  28. package/dist/generated/BoostRequest.d.ts +7 -0
  29. package/dist/generated/BoostRequest.js +2 -0
  30. package/dist/generated/BoostResponse.d.ts +6 -0
  31. package/dist/generated/BoostResponse.js +2 -0
  32. package/dist/generated/CanonicalDocument.d.ts +16 -0
  33. package/dist/generated/CanonicalDocument.js +1 -0
  34. package/dist/generated/CanonicalText.d.ts +10 -0
  35. package/dist/generated/CanonicalText.js +1 -0
  36. package/dist/generated/ChildTrace.d.ts +1 -0
  37. package/dist/generated/ChildTrace.js +2 -0
  38. package/dist/generated/CitationRef.d.ts +9 -0
  39. package/dist/generated/CitationRef.js +1 -0
  40. package/dist/generated/CodeLookupOp.d.ts +1 -0
  41. package/dist/generated/CodeLookupOp.js +2 -0
  42. package/dist/generated/CollaborationChannel.d.ts +1 -0
  43. package/dist/generated/CollaborationChannel.js +2 -0
  44. package/dist/generated/CollaborationEvent.d.ts +47 -0
  45. package/dist/generated/CollaborationEvent.js +1 -0
  46. package/dist/generated/CollaborationEventKind.d.ts +1 -0
  47. package/dist/generated/CollaborationEventKind.js +2 -0
  48. package/dist/generated/CollaborationOp.d.ts +1 -0
  49. package/dist/generated/CollaborationOp.js +2 -0
  50. package/dist/generated/CollaborationOutcome.d.ts +5 -0
  51. package/dist/generated/CollaborationOutcome.js +1 -0
  52. package/dist/generated/CollaborationStatus.d.ts +1 -0
  53. package/dist/generated/CollaborationStatus.js +2 -0
  54. package/dist/generated/CollaborationTimeline.d.ts +10 -0
  55. package/dist/generated/CollaborationTimeline.js +1 -0
  56. package/dist/generated/CollaboratorRef.d.ts +7 -0
  57. package/dist/generated/CollaboratorRef.js +1 -0
  58. package/dist/generated/CommandResponse.d.ts +7 -0
  59. package/dist/generated/CommandResponse.js +2 -0
  60. package/dist/generated/ContributionResponse.d.ts +4 -0
  61. package/dist/generated/ContributionResponse.js +1 -0
  62. package/dist/generated/ContributionRow.d.ts +22 -0
  63. package/dist/generated/ContributionRow.js +2 -0
  64. package/dist/generated/CorrelationDerivation.d.ts +1 -0
  65. package/dist/generated/CorrelationDerivation.js +2 -0
  66. package/dist/generated/DaemonStatus.d.ts +17 -0
  67. package/dist/generated/DaemonStatus.js +1 -0
  68. package/dist/generated/DayUsageResponse.d.ts +8 -0
  69. package/dist/generated/DayUsageResponse.js +1 -0
  70. package/dist/generated/Dimensions.d.ts +4 -0
  71. package/dist/generated/Dimensions.js +2 -0
  72. package/dist/generated/DiscoveriesResponse.d.ts +4 -0
  73. package/dist/generated/DiscoveriesResponse.js +1 -0
  74. package/dist/generated/DiscoveryRow.d.ts +23 -0
  75. package/dist/generated/DiscoveryRow.js +2 -0
  76. package/dist/generated/DisplayField.d.ts +7 -0
  77. package/dist/generated/DisplayField.js +1 -0
  78. package/dist/generated/DisplayScalar.d.ts +10 -0
  79. package/dist/generated/DisplayScalar.js +2 -0
  80. package/dist/generated/DisplayValue.d.ts +18 -0
  81. package/dist/generated/DisplayValue.js +1 -0
  82. package/dist/generated/DocumentBlock.d.ts +20 -0
  83. package/dist/generated/DocumentBlock.js +1 -0
  84. package/dist/generated/EnableRequest.d.ts +7 -0
  85. package/dist/generated/EnableRequest.js +2 -0
  86. package/dist/generated/EnableResponse.d.ts +3 -0
  87. package/dist/generated/EnableResponse.js +2 -0
  88. package/dist/generated/ErrorCode.d.ts +1 -0
  89. package/dist/generated/ErrorCode.js +2 -0
  90. package/dist/generated/ExecutionMode.d.ts +1 -0
  91. package/dist/generated/ExecutionMode.js +2 -0
  92. package/dist/generated/FailureKind.d.ts +1 -0
  93. package/dist/generated/FailureKind.js +2 -0
  94. package/dist/generated/FetchOutcome.d.ts +5 -0
  95. package/dist/generated/FetchOutcome.js +2 -0
  96. package/dist/generated/FieldRole.d.ts +1 -0
  97. package/dist/generated/FieldRole.js +2 -0
  98. package/dist/generated/FileChangeOp.d.ts +1 -0
  99. package/dist/generated/FileChangeOp.js +2 -0
  100. package/dist/generated/FileDelta.d.ts +11 -0
  101. package/dist/generated/FileDelta.js +1 -0
  102. package/dist/generated/FileSearchMode.d.ts +1 -0
  103. package/dist/generated/FileSearchMode.js +2 -0
  104. package/dist/generated/FindingItem.d.ts +9 -0
  105. package/dist/generated/FindingItem.js +1 -0
  106. package/dist/generated/FindingSeverity.d.ts +1 -0
  107. package/dist/generated/FindingSeverity.js +2 -0
  108. package/dist/generated/GroupDerivation.d.ts +1 -0
  109. package/dist/generated/GroupDerivation.js +2 -0
  110. package/dist/generated/GroupKind.d.ts +1 -0
  111. package/dist/generated/GroupKind.js +2 -0
  112. package/dist/generated/HeatmapDay.d.ts +16 -0
  113. package/dist/generated/HeatmapDay.js +2 -0
  114. package/dist/generated/HeatmapResponse.d.ts +4 -0
  115. package/dist/generated/HeatmapResponse.js +1 -0
  116. package/dist/generated/HourTotal.d.ts +16 -0
  117. package/dist/generated/HourTotal.js +2 -0
  118. package/dist/generated/HubStatus.d.ts +18 -0
  119. package/dist/generated/HubStatus.js +2 -0
  120. package/dist/generated/ImageVariant.d.ts +1 -0
  121. package/dist/generated/ImageVariant.js +2 -0
  122. package/dist/generated/IngestPhase.d.ts +9 -0
  123. package/dist/generated/IngestPhase.js +2 -0
  124. package/dist/generated/IngestProgress.d.ts +19 -0
  125. package/dist/generated/IngestProgress.js +1 -0
  126. package/dist/generated/ListOutcome.d.ts +3 -0
  127. package/dist/generated/ListOutcome.js +2 -0
  128. package/dist/generated/LocationOutcome.d.ts +5 -0
  129. package/dist/generated/LocationOutcome.js +1 -0
  130. package/dist/generated/MediaMetadata.d.ts +6 -0
  131. package/dist/generated/MediaMetadata.js +2 -0
  132. package/dist/generated/ModeOp.d.ts +1 -0
  133. package/dist/generated/ModeOp.js +2 -0
  134. package/dist/generated/ModelDay.d.ts +20 -0
  135. package/dist/generated/ModelDay.js +2 -0
  136. package/dist/generated/ModelHour.d.ts +8 -0
  137. package/dist/generated/ModelHour.js +2 -0
  138. package/dist/generated/Mutation.d.ts +45 -0
  139. package/dist/generated/Mutation.js +1 -0
  140. package/dist/generated/NativeToolRef.d.ts +6 -0
  141. package/dist/generated/NativeToolRef.js +2 -0
  142. package/dist/generated/Notice.d.ts +48 -0
  143. package/dist/generated/Notice.js +1 -0
  144. package/dist/generated/ParticipantColor.d.ts +1 -0
  145. package/dist/generated/ParticipantColor.js +2 -0
  146. package/dist/generated/ParticipantState.d.ts +1 -0
  147. package/dist/generated/ParticipantState.js +2 -0
  148. package/dist/generated/PermissionOutcome.d.ts +1 -0
  149. package/dist/generated/PermissionOutcome.js +2 -0
  150. package/dist/generated/PlanOp.d.ts +1 -0
  151. package/dist/generated/PlanOp.js +2 -0
  152. package/dist/generated/PricingResponse.d.ts +25 -0
  153. package/dist/generated/PricingResponse.js +1 -0
  154. package/dist/generated/PricingSource.d.ts +7 -0
  155. package/dist/generated/PricingSource.js +2 -0
  156. package/dist/generated/ProcessOp.d.ts +1 -0
  157. package/dist/generated/ProcessOp.js +2 -0
  158. package/dist/generated/ProcessOutcome.d.ts +4 -0
  159. package/dist/generated/ProcessOutcome.js +1 -0
  160. package/dist/generated/ProcessState.d.ts +1 -0
  161. package/dist/generated/ProcessState.js +2 -0
  162. package/dist/generated/ProjectBackfill.d.ts +27 -0
  163. package/dist/generated/ProjectBackfill.js +2 -0
  164. package/dist/generated/ProjectRow.d.ts +6 -0
  165. package/dist/generated/ProjectRow.js +2 -0
  166. package/dist/generated/ProjectsResponse.d.ts +4 -0
  167. package/dist/generated/ProjectsResponse.js +1 -0
  168. package/dist/generated/Provenance.d.ts +5 -0
  169. package/dist/generated/Provenance.js +2 -0
  170. package/dist/generated/QuestionOption.d.ts +5 -0
  171. package/dist/generated/QuestionOption.js +2 -0
  172. package/dist/generated/RangeUnit.d.ts +1 -0
  173. package/dist/generated/RangeUnit.js +2 -0
  174. package/dist/generated/ReadOutcome.d.ts +3 -0
  175. package/dist/generated/ReadOutcome.js +2 -0
  176. package/dist/generated/ReadTarget.d.ts +8 -0
  177. package/dist/generated/ReadTarget.js +1 -0
  178. package/dist/generated/ResourceRef.d.ts +25 -0
  179. package/dist/generated/ResourceRef.js +1 -0
  180. package/dist/generated/ResponseGroup.d.ts +5 -0
  181. package/dist/generated/ResponseGroup.js +1 -0
  182. package/dist/generated/ResponseRuntime.d.ts +9 -0
  183. package/dist/generated/ResponseRuntime.js +2 -0
  184. package/dist/generated/RuntimeBackfill.d.ts +50 -0
  185. package/dist/generated/RuntimeBackfill.js +1 -0
  186. package/dist/generated/RuntimeErrorKind.d.ts +1 -0
  187. package/dist/generated/RuntimeErrorKind.js +2 -0
  188. package/dist/generated/RuntimeIngestStatus.d.ts +30 -0
  189. package/dist/generated/RuntimeIngestStatus.js +2 -0
  190. package/dist/generated/RuntimeProvenance.d.ts +9 -0
  191. package/dist/generated/RuntimeProvenance.js +2 -0
  192. package/dist/generated/RuntimeRow.d.ts +25 -0
  193. package/dist/generated/RuntimeRow.js +1 -0
  194. package/dist/generated/RuntimesResponse.d.ts +4 -0
  195. package/dist/generated/RuntimesResponse.js +1 -0
  196. package/dist/generated/ScheduleOp.d.ts +1 -0
  197. package/dist/generated/ScheduleOp.js +2 -0
  198. package/dist/generated/SearchBuildProgress.d.ts +6 -0
  199. package/dist/generated/SearchBuildProgress.js +1 -0
  200. package/dist/generated/SearchBuildStage.d.ts +1 -0
  201. package/dist/generated/SearchBuildStage.js +2 -0
  202. package/dist/generated/SearchFilter.d.ts +5 -0
  203. package/dist/generated/SearchFilter.js +1 -0
  204. package/dist/generated/SearchFilterKind.d.ts +1 -0
  205. package/dist/generated/SearchFilterKind.js +2 -0
  206. package/dist/generated/SearchHit.d.ts +20 -0
  207. package/dist/generated/SearchHit.js +2 -0
  208. package/dist/generated/SearchOutcome.d.ts +7 -0
  209. package/dist/generated/SearchOutcome.js +1 -0
  210. package/dist/generated/SearchReadiness.d.ts +6 -0
  211. package/dist/generated/SearchReadiness.js +2 -0
  212. package/dist/generated/SearchRequest.d.ts +38 -0
  213. package/dist/generated/SearchRequest.js +2 -0
  214. package/dist/generated/SearchResponse.d.ts +6 -0
  215. package/dist/generated/SearchResponse.js +1 -0
  216. package/dist/generated/SearchState.d.ts +23 -0
  217. package/dist/generated/SearchState.js +1 -0
  218. package/dist/generated/ServerFrame.d.ts +56 -0
  219. package/dist/generated/ServerFrame.js +1 -0
  220. package/dist/generated/ServiceTier.d.ts +7 -0
  221. package/dist/generated/ServiceTier.js +2 -0
  222. package/dist/generated/SessionActivity.d.ts +9 -0
  223. package/dist/generated/SessionActivity.js +2 -0
  224. package/dist/generated/SessionDelegation.d.ts +30 -0
  225. package/dist/generated/SessionDelegation.js +1 -0
  226. package/dist/generated/SessionResponse.d.ts +4 -0
  227. package/dist/generated/SessionResponse.js +1 -0
  228. package/dist/generated/SessionRole.d.ts +1 -0
  229. package/dist/generated/SessionRole.js +2 -0
  230. package/dist/generated/SessionRow.d.ts +38 -0
  231. package/dist/generated/SessionRow.js +1 -0
  232. package/dist/generated/SessionsResponse.d.ts +4 -0
  233. package/dist/generated/SessionsResponse.js +1 -0
  234. package/dist/generated/ShellOutcome.d.ts +5 -0
  235. package/dist/generated/ShellOutcome.js +2 -0
  236. package/dist/generated/SourceLocation.d.ts +7 -0
  237. package/dist/generated/SourceLocation.js +1 -0
  238. package/dist/generated/SourceRange.d.ts +6 -0
  239. package/dist/generated/SourceRange.js +1 -0
  240. package/dist/generated/Stamp.d.ts +13 -0
  241. package/dist/generated/Stamp.js +2 -0
  242. package/dist/generated/StatusBasis.d.ts +1 -0
  243. package/dist/generated/StatusBasis.js +2 -0
  244. package/dist/generated/StatusResponse.d.ts +17 -0
  245. package/dist/generated/StatusResponse.js +1 -0
  246. package/dist/generated/StreamState.d.ts +1 -0
  247. package/dist/generated/StreamState.js +2 -0
  248. package/dist/generated/SummaryPurpose.d.ts +1 -0
  249. package/dist/generated/SummaryPurpose.js +2 -0
  250. package/dist/generated/TaskOp.d.ts +1 -0
  251. package/dist/generated/TaskOp.js +2 -0
  252. package/dist/generated/TaskSnapshot.d.ts +9 -0
  253. package/dist/generated/TaskSnapshot.js +1 -0
  254. package/dist/generated/TaskSnapshotItem.d.ts +13 -0
  255. package/dist/generated/TaskSnapshotItem.js +1 -0
  256. package/dist/generated/TaskStatus.d.ts +1 -0
  257. package/dist/generated/TaskStatus.js +2 -0
  258. package/dist/generated/TextRange.d.ts +4 -0
  259. package/dist/generated/TextRange.js +2 -0
  260. package/dist/generated/TimelineBody.d.ts +33 -0
  261. package/dist/generated/TimelineBody.js +1 -0
  262. package/dist/generated/TimelineEntry.d.ts +37 -0
  263. package/dist/generated/TimelineEntry.js +1 -0
  264. package/dist/generated/TimelineEntryResponse.d.ts +5 -0
  265. package/dist/generated/TimelineEntryResponse.js +1 -0
  266. package/dist/generated/TimelineGroup.d.ts +7 -0
  267. package/dist/generated/TimelineGroup.js +1 -0
  268. package/dist/generated/TimelineMeta.d.ts +10 -0
  269. package/dist/generated/TimelineMeta.js +1 -0
  270. package/dist/generated/TimelineMetaArtifact.d.ts +11 -0
  271. package/dist/generated/TimelineMetaArtifact.js +1 -0
  272. package/dist/generated/TimelineMetaArtifactSource.d.ts +10 -0
  273. package/dist/generated/TimelineMetaArtifactSource.js +1 -0
  274. package/dist/generated/TimelineMetaPlan.d.ts +8 -0
  275. package/dist/generated/TimelineMetaPlan.js +1 -0
  276. package/dist/generated/TimelineMetaPlanSource.d.ts +7 -0
  277. package/dist/generated/TimelineMetaPlanSource.js +2 -0
  278. package/dist/generated/TimelineMetaTask.d.ts +13 -0
  279. package/dist/generated/TimelineMetaTask.js +1 -0
  280. package/dist/generated/TimelineMetaWorkflow.d.ts +30 -0
  281. package/dist/generated/TimelineMetaWorkflow.js +1 -0
  282. package/dist/generated/TimelinePage.d.ts +17 -0
  283. package/dist/generated/TimelinePage.js +1 -0
  284. package/dist/generated/TokenSnapshot.d.ts +61 -0
  285. package/dist/generated/TokenSnapshot.js +1 -0
  286. package/dist/generated/ToolAction.d.ts +245 -0
  287. package/dist/generated/ToolAction.js +1 -0
  288. package/dist/generated/ToolCorrelation.d.ts +5 -0
  289. package/dist/generated/ToolCorrelation.js +1 -0
  290. package/dist/generated/ToolFailure.d.ts +6 -0
  291. package/dist/generated/ToolFailure.js +1 -0
  292. package/dist/generated/ToolInteraction.d.ts +19 -0
  293. package/dist/generated/ToolInteraction.js +1 -0
  294. package/dist/generated/ToolStatus.d.ts +1 -0
  295. package/dist/generated/ToolStatus.js +2 -0
  296. package/dist/generated/ToolStatusFact.d.ts +6 -0
  297. package/dist/generated/ToolStatusFact.js +1 -0
  298. package/dist/generated/ToolTiming.d.ts +5 -0
  299. package/dist/generated/ToolTiming.js +2 -0
  300. package/dist/generated/TruncationScope.d.ts +1 -0
  301. package/dist/generated/TruncationScope.js +2 -0
  302. package/dist/generated/TurnGap.d.ts +8 -0
  303. package/dist/generated/TurnGap.js +2 -0
  304. package/dist/generated/TurnSpan.d.ts +15 -0
  305. package/dist/generated/TurnSpan.js +1 -0
  306. package/dist/generated/UnpricedModel.d.ts +20 -0
  307. package/dist/generated/UnpricedModel.js +2 -0
  308. package/dist/generated/UsageCost.d.ts +18 -0
  309. package/dist/generated/UsageCost.js +2 -0
  310. package/dist/generated/UsageResponse.d.ts +11 -0
  311. package/dist/generated/UsageResponse.js +1 -0
  312. package/dist/generated/UsageScope.d.ts +6 -0
  313. package/dist/generated/UsageScope.js +2 -0
  314. package/dist/generated/UserAnswer.d.ts +5 -0
  315. package/dist/generated/UserAnswer.js +1 -0
  316. package/dist/generated/UserQuestion.d.ts +9 -0
  317. package/dist/generated/UserQuestion.js +1 -0
  318. package/dist/generated/WebNavigationOp.d.ts +1 -0
  319. package/dist/generated/WebNavigationOp.js +2 -0
  320. package/dist/generated/WebSearchOutcome.d.ts +5 -0
  321. package/dist/generated/WebSearchOutcome.js +1 -0
  322. package/dist/generated/WebSource.d.ts +4 -0
  323. package/dist/generated/WebSource.js +2 -0
  324. package/dist/generated/WorkflowAttempt.d.ts +16 -0
  325. package/dist/generated/WorkflowAttempt.js +1 -0
  326. package/dist/generated/WorkflowJournalKind.d.ts +1 -0
  327. package/dist/generated/WorkflowJournalKind.js +2 -0
  328. package/dist/generated/WorkflowJournalObservation.d.ts +21 -0
  329. package/dist/generated/WorkflowJournalObservation.js +1 -0
  330. package/dist/generated/WorkflowPhase.d.ts +5 -0
  331. package/dist/generated/WorkflowPhase.js +2 -0
  332. package/dist/generated/WorkflowResultKind.d.ts +1 -0
  333. package/dist/generated/WorkflowResultKind.js +2 -0
  334. package/dist/generated/WorkflowRun.d.ts +51 -0
  335. package/dist/generated/WorkflowRun.js +1 -0
  336. package/dist/generated/WorkflowRunStatus.d.ts +1 -0
  337. package/dist/generated/WorkflowRunStatus.js +2 -0
  338. package/dist/generated/WorkflowUsage.d.ts +10 -0
  339. package/dist/generated/WorkflowUsage.js +2 -0
  340. package/dist/generated/WorkflowWorker.d.ts +31 -0
  341. package/dist/generated/WorkflowWorker.js +1 -0
  342. package/dist/generated/WorkflowWorkerState.d.ts +1 -0
  343. package/dist/generated/WorkflowWorkerState.js +2 -0
  344. package/dist/generated/serde_json/JsonValue.d.ts +3 -0
  345. package/dist/generated/serde_json/JsonValue.js +2 -0
  346. package/dist/grants.d.ts +67 -0
  347. package/dist/grants.js +160 -0
  348. package/dist/index.d.ts +110 -0
  349. package/dist/index.js +1 -0
  350. package/dist/ordered-cache.d.ts +15 -0
  351. package/dist/ordered-cache.js +63 -0
  352. package/dist/protocol-version.d.mts +1 -0
  353. package/dist/protocol-version.mjs +2 -0
  354. package/package.json +47 -4
package/dist/client.js ADDED
@@ -0,0 +1,981 @@
1
+ // Hand-written client over the generated protocol types. This is the ONE
2
+ // place the subscribe state machine lives outside Rust: snapshot at a
3
+ // stamp, apply mutations keyed by timeline_id (including patches into
4
+ // earlier rows), obey Resync barriers, and reconnect with a resume stamp.
5
+ // Consumers render; they never re-implement protocol semantics.
6
+ import { utf8Base64UrlEncode } from "./base64url.js";
7
+ import { OrderedCache } from "./ordered-cache.js";
8
+ import { PROTOCOL_VERSION } from "./protocol-version.mjs";
9
+ export { PROTOCOL_VERSION } from "./protocol-version.mjs";
10
+ export const DEFAULT_BASE_URL = "http://127.0.0.1:4640";
11
+ export const AUTH_REFUSALS = [
12
+ "missing",
13
+ "bad-token",
14
+ "malformed",
15
+ "bad-signature",
16
+ "expired",
17
+ "wrong-session",
18
+ "wrong-scope",
19
+ "wrong-route",
20
+ "grants-disabled",
21
+ ];
22
+ export const AUTH_REASON_HEADER = "x-rosetta-auth";
23
+ /** The subprotocol a daemon selects for a credentialed WebSocket upgrade. */
24
+ export const AUTH_WS_PROTOCOL = "rosetta";
25
+ /** The `Authorization` value that presents `credential`. */
26
+ export function authorizationHeader(credential) {
27
+ return credential.kind === "bearer"
28
+ ? `Bearer ${credential.token}`
29
+ : `Rosetta-Grant ${credential.grant}`;
30
+ }
31
+ /**
32
+ * A browser's WebSocket cannot set `Authorization`, so the credential rides
33
+ * the subprotocol list beside the marker the daemon selects. The daemon
34
+ * never echoes the credential entry.
35
+ */
36
+ export function webSocketProtocols(credential) {
37
+ return [
38
+ AUTH_WS_PROTOCOL,
39
+ credential.kind === "bearer"
40
+ ? `rosetta.bearer.${utf8Base64UrlEncode(credential.token)}`
41
+ : `rosetta.grant.${credential.grant}`,
42
+ ];
43
+ }
44
+ export class ApiRequestError extends Error {
45
+ status;
46
+ code;
47
+ constructor(status, code, message) {
48
+ super(message);
49
+ this.name = "ApiRequestError";
50
+ this.status = status;
51
+ this.code = code;
52
+ }
53
+ }
54
+ /** The bound watermark no longer matches: refetch from the head. */
55
+ export class CursorExpiredError extends ApiRequestError {
56
+ constructor(message) {
57
+ super(410, "cursor_expired", message);
58
+ this.name = "CursorExpiredError";
59
+ }
60
+ }
61
+ /** A 401: the daemon wants a credential this client did not present, or
62
+ * refused the one it did. `reason` is null only from a daemon that named
63
+ * none. */
64
+ export class UnauthorizedError extends ApiRequestError {
65
+ reason;
66
+ constructor(reason, message) {
67
+ super(401, null, message);
68
+ this.name = "UnauthorizedError";
69
+ this.reason = reason;
70
+ }
71
+ }
72
+ // `RequestInit["headers"]`, not `HeadersInit`: a Node consumer compiled without the DOM lib
73
+ // (VibeField's fieldd, `lib: ["ES2022"]` + `@types/node`) has `RequestInit` as a global but no
74
+ // `HeadersInit`; the indexed type is the same bag under both libs.
75
+ function withAuthorization(init, credential) {
76
+ const headers = new Headers(init);
77
+ headers.set("authorization", authorizationHeader(credential));
78
+ return headers;
79
+ }
80
+ function queryString(params) {
81
+ const search = new URLSearchParams();
82
+ for (const [key, value] of Object.entries(params)) {
83
+ if (value !== null && value !== undefined) {
84
+ search.set(key, String(value));
85
+ }
86
+ }
87
+ const text = search.toString();
88
+ return text === "" ? "" : `?${text}`;
89
+ }
90
+ /** `RequestInit.signal` is exactly optional: an absent signal omits the key
91
+ * rather than setting it to `undefined`. */
92
+ function withSignal(signal) {
93
+ return signal === undefined ? {} : { signal };
94
+ }
95
+ const RECONNECT_BASE_MS = 500;
96
+ const RECONNECT_MAX_MS = 10_000;
97
+ /** Keep additive timeline-meta fields safe across a daemon/client rolling
98
+ * restart. The protocol handshake still owns incompatible changes; a missing
99
+ * newly-added collection is the one backwards-compatible case we accept. */
100
+ function normalizeTimelineMeta(meta) {
101
+ return {
102
+ tasks: meta.tasks ?? [],
103
+ workflows: meta.workflows ?? [],
104
+ plans: meta.plans ?? [],
105
+ artifacts: meta.artifacts ?? [],
106
+ };
107
+ }
108
+ /** `run` is a query-time join and therefore absent from row-level mutations.
109
+ * Keep it only while both canonical actions still identify the same run. A
110
+ * replacement snapshot starts from an empty map and remains authoritative. */
111
+ function retainHydratedWorkflowRun(current, incoming) {
112
+ const before = current.action;
113
+ const after = incoming.action;
114
+ return before.type === "workflow" &&
115
+ after.type === "workflow" &&
116
+ before.run !== null &&
117
+ after.run === null &&
118
+ before.run_id !== null &&
119
+ after.run_id === before.run_id
120
+ ? { ...incoming, action: { ...after, run: before.run } }
121
+ : incoming;
122
+ }
123
+ export class RosettaClient {
124
+ origin;
125
+ get baseUrl() {
126
+ return this.origin;
127
+ }
128
+ connection = new AbortController();
129
+ connectionListeners = new Set();
130
+ subscriptions = new Set();
131
+ requestTimeoutMs;
132
+ notificationIntervalMs;
133
+ fetchFn;
134
+ WebSocketImpl;
135
+ credential;
136
+ constructor(options = {}) {
137
+ this.origin = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
138
+ this.fetchFn = options.fetchFn ?? fetch.bind(globalThis);
139
+ this.WebSocketImpl = options.WebSocketImpl ?? WebSocket;
140
+ this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
141
+ this.notificationIntervalMs = options.notificationIntervalMs ?? 16;
142
+ this.credential = options.credential ?? null;
143
+ }
144
+ /**
145
+ * Replace the credential (a refreshed grant, a rotated bearer), or drop
146
+ * it. Later requests and every later (re)connect present it. An open
147
+ * socket is left alone: the daemon checks a credential at the upgrade, so
148
+ * a grant that expires under a live subscription ends only with its
149
+ * socket, and the reconnect then needs a fresh one.
150
+ */
151
+ setCredential(credential) {
152
+ this.credential = credential;
153
+ }
154
+ async response(path, init) {
155
+ const signal = AbortSignal.any([
156
+ AbortSignal.timeout(this.requestTimeoutMs),
157
+ this.connection.signal,
158
+ ...(init?.signal ? [init.signal] : []),
159
+ ]);
160
+ const headers = this.credential === null ? init?.headers : withAuthorization(init?.headers, this.credential);
161
+ const response = await this.fetchFn(`${this.baseUrl}${path}`, {
162
+ ...init,
163
+ ...(headers === undefined ? {} : { headers }),
164
+ signal,
165
+ redirect: "error",
166
+ });
167
+ signal.throwIfAborted();
168
+ const version = response.headers.get("x-rosetta-protocol-version");
169
+ if (version !== String(PROTOCOL_VERSION)) {
170
+ throw new ApiRequestError(response.status, "incompatible_protocol", `Daemon protocol ${version ?? "unknown"} is incompatible with this app (expected ${PROTOCOL_VERSION}). Restart with the matching daemon.`);
171
+ }
172
+ if (response.status === 401) {
173
+ const reason = response.headers.get(AUTH_REASON_HEADER);
174
+ throw new UnauthorizedError(AUTH_REFUSALS.find((refusal) => refusal === reason) ?? null, `The daemon refused ${path} (${reason ?? "unauthorized"}).`);
175
+ }
176
+ if (!response.ok) {
177
+ let code = null;
178
+ let message = `${response.status} on ${path}`;
179
+ try {
180
+ const body = (await response.json());
181
+ if (body.error) {
182
+ code = body.error.code;
183
+ message = body.error.message;
184
+ }
185
+ }
186
+ catch {
187
+ // Non-JSON error body; keep the status-line message.
188
+ }
189
+ if (code === "cursor_expired") {
190
+ throw new CursorExpiredError(message);
191
+ }
192
+ throw new ApiRequestError(response.status, code, message);
193
+ }
194
+ return response;
195
+ }
196
+ async request(path, init) {
197
+ return (await (await this.response(path, init)).json());
198
+ }
199
+ status(signal) {
200
+ return this.request("/api/status", withSignal(signal));
201
+ }
202
+ /** The roll call's census — what discovery found, content-free. */
203
+ discoveries(signal) {
204
+ return this.request("/api/discoveries", withSignal(signal));
205
+ }
206
+ /** Consent: enroll the chosen agent kinds. Grantable exactly once, while
207
+ * the daemon is awaiting consent. */
208
+ enableRuntimes(kinds, signal) {
209
+ return this.request("/api/runtimes/enable", {
210
+ method: "POST",
211
+ ...withSignal(signal),
212
+ headers: { "content-type": "application/json" },
213
+ body: JSON.stringify({ kinds }),
214
+ });
215
+ }
216
+ /** Attention-directed ingestion: the reader opened a still-queued
217
+ * session — ask the daemon to read it next. Advisory; safe to fire on
218
+ * every selection of a pending session. */
219
+ boost(sessionId, signal) {
220
+ return this.request("/api/ingest/boost", {
221
+ method: "POST",
222
+ ...withSignal(signal),
223
+ headers: { "content-type": "application/json" },
224
+ body: JSON.stringify({ session_id: sessionId }),
225
+ });
226
+ }
227
+ runtimes(signal) {
228
+ return this.request("/api/runtimes", withSignal(signal));
229
+ }
230
+ projects(signal) {
231
+ return this.request("/api/projects", withSignal(signal));
232
+ }
233
+ sessions(options = {}, signal) {
234
+ return this.request(`/api/sessions${queryString(options)}`, withSignal(signal));
235
+ }
236
+ /** Resolve canonical rows for provider-native session ids; children come
237
+ * back too, and `root_session_id` names the navigable thread. */
238
+ sessionsByNativeId(nativeSessionIds, signal) {
239
+ return this.sessions({ native_session_id: nativeSessionIds.join(","), limit: 1000 }, signal);
240
+ }
241
+ session(id, signal) {
242
+ return this.request(`/api/sessions/${encodeURIComponent(id)}`, withSignal(signal));
243
+ }
244
+ /**
245
+ * One backward page. Pass the previous page's `cursor` to continue; a
246
+ * rewrite underneath throws {@link CursorExpiredError} — refetch from the
247
+ * head (no cursor).
248
+ */
249
+ timeline(sessionId, options = {}, signal) {
250
+ return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/timeline${queryString(options)}`, withSignal(signal));
251
+ }
252
+ /** Complete delivered collaboration history for one team member. The
253
+ * response deliberately excludes private prose and tool rows. */
254
+ collaborationTimeline(sessionId, signal) {
255
+ return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/collaboration`, withSignal(signal));
256
+ }
257
+ /** An exact row and the generation it belongs to. */
258
+ timelineEntryResponse(sessionId, timelineId, signal) {
259
+ return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/timeline/${encodeURIComponent(timelineId)}`, withSignal(signal));
260
+ }
261
+ async timelineEntry(sessionId, timelineId, signal) {
262
+ return (await this.timelineEntryResponse(sessionId, timelineId, signal)).entry;
263
+ }
264
+ reportWork(sessionId, timelineId, signal) {
265
+ return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/timeline/${encodeURIComponent(timelineId)}/work`, withSignal(signal));
266
+ }
267
+ /** Recover one deliberately omitted command body from its raw-ledger
268
+ * record. Nothing is fetched until the reader opens the disclosure. */
269
+ command(sessionId, timelineId, signal) {
270
+ return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/timeline/${encodeURIComponent(timelineId)}/command`, withSignal(signal));
271
+ }
272
+ /** Read the live file named by one canonical file-bearing target. The caller
273
+ * supplies identities and an index, never a path; the daemon resolves the
274
+ * exact path already stored on that timeline row. */
275
+ /** Direct image URLs let the browser share compressed and decoded resources.
276
+ * Recipes are finite, versioned and enforced by the daemon. */
277
+ imageAssetUrl(asset, variant) {
278
+ const base = new URL(`${this.baseUrl}/`);
279
+ const rendition = variant === "full" ? asset : asset.image_variants?.[variant];
280
+ const url = new URL((rendition ?? asset).url, base);
281
+ if (url.origin !== base.origin || !["http:", "https:"].includes(url.protocol)) {
282
+ throw new Error("the stored image has an unsupported address");
283
+ }
284
+ // Older projection fixtures have no rendition set. The versioned daemon
285
+ // route can prepare a legacy member without modifying its immutable URL.
286
+ if (!rendition) {
287
+ url.searchParams.set("image", variant);
288
+ url.searchParams.set("v", "1");
289
+ }
290
+ return url.toString();
291
+ }
292
+ imageFileTargetUrl(sessionId, timelineId, targetIndex, variant) {
293
+ if (!Number.isSafeInteger(targetIndex) || targetIndex < 0)
294
+ throw new Error("invalid image target");
295
+ return `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}/timeline/${encodeURIComponent(timelineId)}/read/${targetIndex}?image=${variant}&v=1`;
296
+ }
297
+ async readFileTarget(sessionId, timelineId, targetIndex, signal) {
298
+ const response = await this.response(`/api/sessions/${encodeURIComponent(sessionId)}/timeline/${encodeURIComponent(timelineId)}/read/${targetIndex}`, withSignal(signal));
299
+ return new Uint8Array(await response.arrayBuffer());
300
+ }
301
+ /** Resolve a canonical oversized-text member only when its disclosure is
302
+ * opened. Asset references are daemon-owned and may not redirect this
303
+ * authenticated/local client to another origin. */
304
+ async readTextAsset(asset, signal) {
305
+ const base = new URL(`${this.baseUrl}/`);
306
+ const url = new URL(asset.url, base);
307
+ if ((url.protocol !== "http:" && url.protocol !== "https:") || url.origin !== base.origin) {
308
+ throw new Error("the stored text has an unsupported address");
309
+ }
310
+ return (await this.response(`${url.pathname}${url.search}`, withSignal(signal))).text();
311
+ }
312
+ async readAsset(asset, signal) {
313
+ const base = new URL(`${this.baseUrl}/`);
314
+ const url = new URL(asset.url, base);
315
+ if (url.origin !== base.origin || !["http:", "https:"].includes(url.protocol)) {
316
+ throw new Error("the stored asset has an unsupported address");
317
+ }
318
+ return new Uint8Array(await (await this.response(`${url.pathname}${url.search}`, withSignal(signal))).arrayBuffer());
319
+ }
320
+ /** Read one exact plan document selected from timeline_meta. */
321
+ async readPlan(sessionId, source, signal) {
322
+ const path = source.type === "revision"
323
+ ? `/api/sessions/${encodeURIComponent(sessionId)}/plans/revisions/${source.revision}`
324
+ : `/api/sessions/${encodeURIComponent(sessionId)}/timeline/${encodeURIComponent(source.timeline_id)}/plan`;
325
+ const response = await this.response(path, withSignal(signal));
326
+ return new Uint8Array(await response.arrayBuffer());
327
+ }
328
+ search(request, signal) {
329
+ return this.request(`/api/search${queryString({
330
+ q: request.q,
331
+ limit: request.limit,
332
+ session: request.session,
333
+ sessions: request.sessions,
334
+ prefix: request.prefix,
335
+ runtime: request.runtime,
336
+ agent: request.agent,
337
+ project: request.project,
338
+ kind: request.kind,
339
+ is_error: request.is_error,
340
+ after_ts: request.after_ts,
341
+ before_ts: request.before_ts,
342
+ })}`, withSignal(signal));
343
+ }
344
+ heatmap(projectId, options = {}, signal) {
345
+ return this.request(`/api/projects/${encodeURIComponent(projectId)}/heatmap${queryString({
346
+ since_ms: options.sinceMs,
347
+ offset_minutes: options.offsetMinutes,
348
+ })}`, withSignal(signal));
349
+ }
350
+ contribution(projectId, signal) {
351
+ return this.request(`/api/projects/${encodeURIComponent(projectId)}/contribution`, withSignal(signal));
352
+ }
353
+ /** The heatmap figure's decomposition over the same window: per-model days
354
+ * (they sum back to the heatmap exactly) plus an hour-of-day profile. */
355
+ usage(projectId, options = {}, signal) {
356
+ return this.request(`/api/projects/${encodeURIComponent(projectId)}/usage${queryString({
357
+ since_ms: options.sinceMs,
358
+ offset_minutes: options.offsetMinutes,
359
+ })}`, withSignal(signal));
360
+ }
361
+ /** Hourly model tokens for one date, using the heatmap's timezone offset. */
362
+ dayUsage(projectId, day, offsetMinutes = 0, signal) {
363
+ return this.request(`/api/projects/${encodeURIComponent(projectId)}/usage/day${queryString({
364
+ day, offset_minutes: offsetMinutes,
365
+ })}`, withSignal(signal));
366
+ }
367
+ /** Which top-level sessions were active in a window of at most seven days,
368
+ * newest recorded activity first, each with its tree's turns and the gaps
369
+ * inside them longer than `gapMs` (15 minutes unless asked). It spans
370
+ * sessions, so it needs the bearer. */
371
+ activity(window, signal) {
372
+ return this.request(`/api/activity${queryString({
373
+ since_ms: window.sinceMs,
374
+ until_ms: window.untilMs,
375
+ sessions: window.sessions === undefined ? undefined : window.sessions.join(","),
376
+ gap_ms: window.gapMs,
377
+ limit: window.limit,
378
+ })}`, withSignal(signal));
379
+ }
380
+ /** The price catalog behind every cost figure: its basis (list-price
381
+ * equivalent), version, sources, and the catalog the projection's rollup
382
+ * was priced with. */
383
+ pricing(signal) {
384
+ return this.request("/api/pricing", withSignal(signal));
385
+ }
386
+ /** A restarted app-owned daemon may bind another port. Abort old requests
387
+ * and reconnect existing consumers without losing the reader's selection. */
388
+ setBaseUrl(baseUrl) {
389
+ const origin = baseUrl.replace(/\/$/, "");
390
+ if (origin === this.origin)
391
+ return;
392
+ this.origin = origin;
393
+ this.connection.abort();
394
+ this.connection = new AbortController();
395
+ for (const subscription of this.subscriptions)
396
+ subscription.restart();
397
+ for (const listener of this.connectionListeners)
398
+ listener();
399
+ }
400
+ onConnectionChange(listener) {
401
+ this.connectionListeners.add(listener);
402
+ return () => {
403
+ this.connectionListeners.delete(listener);
404
+ };
405
+ }
406
+ transport(path) {
407
+ return {
408
+ url: () => `${this.baseUrl.replace(/^http/, "ws")}${path}`,
409
+ protocols: () => (this.credential === null ? undefined : webSocketProtocols(this.credential)),
410
+ socket: this.WebSocketImpl,
411
+ interval: this.notificationIntervalMs,
412
+ onClose: (sub) => this.subscriptions.delete(sub),
413
+ };
414
+ }
415
+ subscribeSession(sessionId, handlers) {
416
+ const sub = new SessionSubscription(this.transport(`/ws/sessions/${encodeURIComponent(sessionId)}`), handlers, this, sessionId);
417
+ this.subscriptions.add(sub);
418
+ return sub;
419
+ }
420
+ subscribeMeta(handlers) {
421
+ const sub = new MetaSubscription(this.transport("/ws/meta"), handlers);
422
+ this.subscriptions.add(sub);
423
+ return sub;
424
+ }
425
+ subscribeCollaboration(sessionId, handlers) {
426
+ const sub = new CollaborationSubscription(this.transport(`/ws/collaboration/${encodeURIComponent(sessionId)}`), handlers);
427
+ this.subscriptions.add(sub);
428
+ return sub;
429
+ }
430
+ }
431
+ class BaseSubscription {
432
+ stamp = null;
433
+ closed = false;
434
+ transport;
435
+ socket = null;
436
+ attempts = 0;
437
+ status = null;
438
+ reconnectTimer = null;
439
+ deadline = null;
440
+ changeTimer = null;
441
+ constructor(transport) {
442
+ this.transport = transport;
443
+ }
444
+ start() {
445
+ this.connect();
446
+ }
447
+ connect() {
448
+ if (this.closed)
449
+ return;
450
+ this.reconnectTimer = null;
451
+ const resume = this.stamp === null ? "" : `?epoch=${this.stamp.epoch}&seq=${this.stamp.seq}`;
452
+ this.setStatus("connecting");
453
+ let socket;
454
+ try {
455
+ const url = `${this.transport.url()}${resume}`;
456
+ const protocols = this.transport.protocols();
457
+ socket =
458
+ protocols === undefined
459
+ ? new this.transport.socket(url)
460
+ : new this.transport.socket(url, protocols);
461
+ }
462
+ catch {
463
+ this.retry();
464
+ return;
465
+ }
466
+ this.socket = socket;
467
+ let greeted = false;
468
+ // Opening TCP is not successful synchronization. Keep backoff until live.
469
+ this.deadline = setTimeout(() => socket.close(), 15_000);
470
+ socket.onopen = () => {
471
+ if (this.socket === socket && !this.closed)
472
+ this.setStatus("syncing");
473
+ };
474
+ socket.onmessage = (event) => {
475
+ if (this.socket !== socket || this.closed)
476
+ return;
477
+ try {
478
+ const frame = JSON.parse(String(event.data));
479
+ if (frame.frame === "hello") {
480
+ if (frame.protocol_version !== PROTOCOL_VERSION) {
481
+ this.onError({
482
+ code: "incompatible_protocol",
483
+ message: `Daemon protocol ${frame.protocol_version} is incompatible with this app (expected ${PROTOCOL_VERSION}). Restart with the matching daemon.`,
484
+ });
485
+ this.close();
486
+ return;
487
+ }
488
+ greeted = true;
489
+ // Hello advertises a target. It never acknowledges data we received.
490
+ return;
491
+ }
492
+ if (!greeted)
493
+ throw new Error("The daemon sent data before its protocol handshake.");
494
+ if (frame.frame === "error")
495
+ this.onError(frame.error);
496
+ else
497
+ this.handleFrame(frame);
498
+ }
499
+ catch (error) {
500
+ this.onError({
501
+ code: "internal",
502
+ message: error instanceof Error ? error.message : "Invalid daemon frame",
503
+ });
504
+ this.stamp = null;
505
+ this.invalidate();
506
+ socket.close();
507
+ }
508
+ };
509
+ socket.onclose = () => {
510
+ if (this.socket !== socket)
511
+ return;
512
+ this.socket = null;
513
+ this.clearDeadline();
514
+ if (!this.closed)
515
+ this.retry();
516
+ };
517
+ socket.onerror = () => {
518
+ /* close owns reconnect */
519
+ };
520
+ }
521
+ retry() {
522
+ if (this.closed)
523
+ return;
524
+ this.setStatus("connecting");
525
+ const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** this.attempts++);
526
+ this.reconnectTimer = setTimeout(() => this.connect(), delay);
527
+ }
528
+ clearDeadline() {
529
+ if (this.deadline !== null)
530
+ clearTimeout(this.deadline);
531
+ this.deadline = null;
532
+ }
533
+ live() {
534
+ this.attempts = 0;
535
+ this.clearDeadline();
536
+ this.setStatus("live");
537
+ }
538
+ setStatus(status) {
539
+ if (this.status === status)
540
+ return;
541
+ this.status = status;
542
+ if (status === "resyncing" && this.socket !== null) {
543
+ this.clearDeadline();
544
+ const socket = this.socket;
545
+ this.deadline = setTimeout(() => socket.close(), 15_000);
546
+ }
547
+ this.onStatus(status);
548
+ }
549
+ changed(immediate = false) {
550
+ if (this.closed)
551
+ return;
552
+ if (immediate || this.transport.interval === 0) {
553
+ this.cancelChange();
554
+ this.flushChanges();
555
+ }
556
+ else if (this.changeTimer === null) {
557
+ this.changeTimer = setTimeout(() => {
558
+ this.changeTimer = null;
559
+ if (!this.closed)
560
+ this.flushChanges();
561
+ }, this.transport.interval);
562
+ }
563
+ }
564
+ cancelChange() {
565
+ if (this.changeTimer !== null)
566
+ clearTimeout(this.changeTimer);
567
+ this.changeTimer = null;
568
+ }
569
+ /** Drop a generation and request an authoritative snapshot. */
570
+ restart() {
571
+ if (this.closed)
572
+ return;
573
+ this.stamp = null;
574
+ this.invalidate();
575
+ const socket = this.socket;
576
+ this.socket = null;
577
+ this.clearDeadline();
578
+ if (this.reconnectTimer !== null)
579
+ clearTimeout(this.reconnectTimer);
580
+ this.reconnectTimer = null;
581
+ socket?.close();
582
+ this.connect();
583
+ }
584
+ close() {
585
+ if (this.closed)
586
+ return;
587
+ this.closed = true;
588
+ this.clearDeadline();
589
+ this.cancelChange();
590
+ if (this.reconnectTimer !== null)
591
+ clearTimeout(this.reconnectTimer);
592
+ this.reconnectTimer = null;
593
+ const socket = this.socket;
594
+ this.socket = null;
595
+ socket?.close();
596
+ this.dispose();
597
+ this.setStatus("closed");
598
+ this.transport.onClose(this);
599
+ }
600
+ dispose() { }
601
+ }
602
+ const timelineOrder = (a, b) => a.ordinal - b.ordinal || a.timeline_id.localeCompare(b.timeline_id);
603
+ /** Apply the same canonical patch to visible rows, held pages, and anchors. */
604
+ function patchRow(row, mutation) {
605
+ switch (mutation.type) {
606
+ case "message": {
607
+ const { type: _tag, ...incoming } = mutation;
608
+ return row?.body.type === "tool_use" && incoming.body.type === "tool_use"
609
+ ? {
610
+ ...incoming,
611
+ body: {
612
+ ...incoming.body,
613
+ tool: retainHydratedWorkflowRun(row.body.tool, incoming.body.tool),
614
+ },
615
+ }
616
+ : incoming;
617
+ }
618
+ case "tool_updated":
619
+ return row?.body.type === "tool_use"
620
+ ? {
621
+ ...row,
622
+ body: { ...row.body, tool: retainHydratedWorkflowRun(row.body.tool, mutation.tool) },
623
+ }
624
+ : row;
625
+ case "usage": {
626
+ if (!row)
627
+ return row;
628
+ // The read-time cost belongs to the replaced usage: never keep the old.
629
+ const { cost: _replaced, ...rest } = row;
630
+ const next = { ...rest, usage: mutation.tokens, model: mutation.model, at: mutation.ts };
631
+ return mutation.cost === null ? next : { ...next, cost: mutation.cost };
632
+ }
633
+ default:
634
+ return row;
635
+ }
636
+ }
637
+ export class SessionSubscription extends BaseSubscription {
638
+ handlers;
639
+ client;
640
+ sessionId;
641
+ rows = new Map();
642
+ visible = new OrderedCache((row) => row.timeline_id, timelineOrder);
643
+ timelineMeta = { tasks: [], workflows: [], plans: [], artifacts: [] };
644
+ generation = 0;
645
+ watermark = null;
646
+ pending = new Set();
647
+ rowsDirty = false;
648
+ metaDirty = false;
649
+ constructor(transport, handlers, client, sessionId) {
650
+ super(transport);
651
+ this.handlers = handlers;
652
+ this.client = client;
653
+ this.sessionId = sessionId;
654
+ this.start();
655
+ }
656
+ entries() {
657
+ return this.visible.values();
658
+ }
659
+ dispose() {
660
+ for (const read of this.pending)
661
+ read.controller.abort();
662
+ this.pending.clear();
663
+ }
664
+ invalidate() {
665
+ const hadSnapshot = this.watermark !== null;
666
+ this.cancelChange();
667
+ this.generation++;
668
+ this.watermark = null;
669
+ this.dispose();
670
+ this.rows.clear();
671
+ this.visible.replace([]);
672
+ this.timelineMeta = { tasks: [], workflows: [], plans: [], artifacts: [] };
673
+ this.rowsDirty = this.metaDirty = false;
674
+ if (hadSnapshot)
675
+ this.handlers.onReset?.();
676
+ }
677
+ handleFrame(frame) {
678
+ switch (frame.frame) {
679
+ case "timeline_snapshot":
680
+ this.invalidate();
681
+ this.watermark = frame.watermark;
682
+ for (const row of frame.entries)
683
+ this.rows.set(row.timeline_id, row);
684
+ this.visible.replace(frame.entries);
685
+ this.timelineMeta = normalizeTimelineMeta(frame.timeline_meta);
686
+ this.stamp = frame.stamp;
687
+ this.handlers.onSnapshot?.({
688
+ entries: frame.entries,
689
+ cursor: frame.cursor,
690
+ watermark: frame.watermark,
691
+ });
692
+ this.rowsDirty = this.metaDirty = true;
693
+ this.live();
694
+ this.changed(true);
695
+ break;
696
+ case "caught_up":
697
+ // A partial replay leaves stamp unchanged and is replayed again.
698
+ this.stamp = frame.stamp;
699
+ this.live();
700
+ this.changed(true);
701
+ break;
702
+ case "timeline_meta_snapshot":
703
+ this.timelineMeta = normalizeTimelineMeta(frame.timeline_meta);
704
+ this.metaDirty = true;
705
+ if (frame.stamp !== null) {
706
+ this.stamp = frame.stamp;
707
+ this.live();
708
+ }
709
+ this.changed();
710
+ break;
711
+ case "mutation":
712
+ if (frame.mutation.type === "session_swept") {
713
+ this.restart();
714
+ return;
715
+ }
716
+ this.apply(frame.mutation);
717
+ if (frame.stamp !== null) {
718
+ this.stamp = frame.stamp;
719
+ this.live();
720
+ }
721
+ this.changed();
722
+ break;
723
+ case "resync":
724
+ this.stamp = null;
725
+ this.invalidate();
726
+ this.setStatus("resyncing");
727
+ break;
728
+ default:
729
+ break;
730
+ }
731
+ }
732
+ apply(mutation) {
733
+ if (mutation.type !== "message" &&
734
+ mutation.type !== "tool_updated" &&
735
+ mutation.type !== "usage")
736
+ return;
737
+ for (const read of this.pending) {
738
+ // An overwhelmed read retries instead of silently dropping a late patch.
739
+ if (read.mutations.length >= 4096)
740
+ read.controller.abort();
741
+ else
742
+ read.mutations.push(mutation);
743
+ }
744
+ const current = this.rows.get(mutation.timeline_id);
745
+ const row = patchRow(current, mutation);
746
+ if (row === undefined || row === current)
747
+ return;
748
+ this.rows.set(row.timeline_id, row);
749
+ if (this.visible.get(row.timeline_id) !== undefined ||
750
+ (current === undefined && mutation.type === "message")) {
751
+ this.visible.set(row);
752
+ this.rowsDirty = true;
753
+ }
754
+ }
755
+ /** Hold a page in this cache without displaying it until the reader pulls. */
756
+ async fetchPage(cursor, limit = 200, signal) {
757
+ return this.read(async (signal) => this.client.timeline(this.sessionId, { cursor, limit }, signal), false, signal);
758
+ }
759
+ /** Reveal cached identities using their latest patched values. */
760
+ reveal(entries) {
761
+ for (const entry of entries) {
762
+ const row = this.rows.get(entry.timeline_id);
763
+ if (row !== undefined)
764
+ this.visible.set(row);
765
+ }
766
+ this.rowsDirty = true;
767
+ this.changed(true);
768
+ }
769
+ async loadEntry(timelineId, signal) {
770
+ await this.read(async (signal) => {
771
+ const result = await this.client.timelineEntryResponse(this.sessionId, timelineId, signal);
772
+ return { entries: [result.entry], watermark: result.watermark, cursor: null };
773
+ }, true, signal);
774
+ return true;
775
+ }
776
+ async read(load, show, signal) {
777
+ if (this.closed || this.watermark === null)
778
+ throw new DOMException("The session is synchronizing", "AbortError");
779
+ const generation = this.generation;
780
+ const read = { controller: new AbortController(), mutations: [] };
781
+ this.pending.add(read);
782
+ const combined = AbortSignal.any([read.controller.signal, ...(signal ? [signal] : [])]);
783
+ try {
784
+ const page = await load(combined);
785
+ combined.throwIfAborted();
786
+ if (generation !== this.generation || this.closed)
787
+ throw new DOMException("The session changed", "AbortError");
788
+ if (page.watermark !== this.watermark)
789
+ throw new CursorExpiredError("The session was rewritten");
790
+ for (const incoming of page.entries) {
791
+ let row = incoming;
792
+ for (const mutation of read.mutations) {
793
+ if ((mutation.type === "message" ||
794
+ mutation.type === "tool_updated" ||
795
+ mutation.type === "usage") &&
796
+ mutation.timeline_id === incoming.timeline_id)
797
+ row = patchRow(row, mutation);
798
+ }
799
+ if (row === undefined)
800
+ continue;
801
+ this.rows.set(row.timeline_id, row);
802
+ if (show || this.visible.get(row.timeline_id) !== undefined) {
803
+ this.visible.set(row);
804
+ this.rowsDirty = true;
805
+ }
806
+ }
807
+ this.changed(show);
808
+ return page;
809
+ }
810
+ catch (error) {
811
+ if (error instanceof CursorExpiredError && generation === this.generation) {
812
+ this.restart();
813
+ this.handlers.onError?.({ code: "cursor_expired", message: error.message });
814
+ }
815
+ throw error;
816
+ }
817
+ finally {
818
+ this.pending.delete(read);
819
+ }
820
+ }
821
+ flushChanges() {
822
+ if (this.rowsDirty) {
823
+ this.rowsDirty = false;
824
+ this.handlers.onChange?.(this.entries());
825
+ }
826
+ if (this.metaDirty) {
827
+ this.metaDirty = false;
828
+ this.handlers.onTimelineMetaChange?.(this.timelineMeta);
829
+ }
830
+ }
831
+ onStatus(status) {
832
+ this.handlers.onStatus?.(status);
833
+ }
834
+ onError(error) {
835
+ this.handlers.onError?.(error);
836
+ }
837
+ }
838
+ export class MetaSubscription extends BaseSubscription {
839
+ handlers;
840
+ runtimes = new OrderedCache((r) => r.id, (a, b) => a.id.localeCompare(b.id));
841
+ projects = new OrderedCache((r) => r.id, (a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
842
+ sessions = new OrderedCache((r) => r.id, (a, b) => b.updated_at - a.updated_at || a.id.localeCompare(b.id));
843
+ dirty = false;
844
+ source = "snapshot";
845
+ constructor(transport, handlers) {
846
+ super(transport);
847
+ this.handlers = handlers;
848
+ this.start();
849
+ }
850
+ state() {
851
+ return {
852
+ runtimes: this.runtimes.values(),
853
+ projects: this.projects.values(),
854
+ sessions: this.sessions.values(),
855
+ };
856
+ }
857
+ invalidate() {
858
+ this.cancelChange();
859
+ this.dirty = false;
860
+ }
861
+ handleFrame(frame) {
862
+ switch (frame.frame) {
863
+ case "meta_snapshot":
864
+ this.runtimes.replace(frame.runtimes);
865
+ this.projects.replace(frame.projects);
866
+ this.sessions.replace(frame.sessions);
867
+ this.dirty = true;
868
+ this.source = "snapshot";
869
+ this.live();
870
+ this.changed(true);
871
+ break;
872
+ case "mutation": {
873
+ const mutation = frame.mutation;
874
+ if (mutation.type === "runtime") {
875
+ const { type: _tag, ...row } = mutation;
876
+ this.runtimes.set(row);
877
+ }
878
+ else if (mutation.type === "project") {
879
+ const { type: _tag, ...row } = mutation;
880
+ this.projects.set(row);
881
+ }
882
+ else if (mutation.type === "session") {
883
+ const { type: _tag, ...row } = mutation;
884
+ this.sessions.set(row);
885
+ }
886
+ else
887
+ return;
888
+ this.dirty = true;
889
+ this.source = "mutation";
890
+ this.changed();
891
+ break;
892
+ }
893
+ case "resync":
894
+ this.invalidate();
895
+ this.setStatus("resyncing");
896
+ break;
897
+ default:
898
+ break;
899
+ }
900
+ }
901
+ flushChanges() {
902
+ if (this.dirty) {
903
+ this.dirty = false;
904
+ this.handlers.onChange?.(this.state(), this.source);
905
+ }
906
+ }
907
+ onStatus(status) {
908
+ this.handlers.onStatus?.(status);
909
+ }
910
+ onError(error) {
911
+ this.handlers.onError?.(error);
912
+ }
913
+ }
914
+ export class CollaborationSubscription extends BaseSubscription {
915
+ handlers;
916
+ rows = new OrderedCache((r) => r.timeline_id, timelineOrder);
917
+ observations = new Map();
918
+ dirty = false;
919
+ constructor(transport, handlers) {
920
+ super(transport);
921
+ this.handlers = handlers;
922
+ this.start();
923
+ }
924
+ observe(observation) {
925
+ this.observations.set(JSON.stringify([observation.response_id, observation.model, observation.effort]), observation);
926
+ }
927
+ invalidate() {
928
+ this.cancelChange();
929
+ this.rows.replace([]);
930
+ this.observations.clear();
931
+ this.dirty = false;
932
+ this.handlers.onReset?.();
933
+ }
934
+ handleFrame(frame) {
935
+ switch (frame.frame) {
936
+ case "collaboration_snapshot":
937
+ this.invalidate();
938
+ this.rows.replace(frame.entries);
939
+ for (const observation of frame.response_runtimes)
940
+ this.observe(observation);
941
+ this.dirty = true;
942
+ this.live();
943
+ this.changed(true);
944
+ break;
945
+ case "response_runtime":
946
+ this.observe(frame.observation);
947
+ this.dirty = true;
948
+ this.changed();
949
+ break;
950
+ case "mutation":
951
+ if (frame.mutation.type === "message" && frame.mutation.body.type === "collaboration") {
952
+ const { type: _tag, ...row } = frame.mutation;
953
+ this.rows.set(row);
954
+ this.dirty = true;
955
+ this.changed();
956
+ }
957
+ break;
958
+ case "resync":
959
+ this.invalidate();
960
+ this.setStatus("resyncing");
961
+ break;
962
+ default:
963
+ break;
964
+ }
965
+ }
966
+ flushChanges() {
967
+ if (this.dirty) {
968
+ this.dirty = false;
969
+ this.handlers.onChange?.({
970
+ entries: this.rows.values(),
971
+ response_runtimes: [...this.observations.values()],
972
+ });
973
+ }
974
+ }
975
+ onStatus(status) {
976
+ this.handlers.onStatus?.(status);
977
+ }
978
+ onError(error) {
979
+ this.handlers.onError?.(error);
980
+ }
981
+ }