@abloatai/ablo 0.3.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 (278) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +12 -0
  4. package/README.md +230 -0
  5. package/dist/BaseSyncedStore.d.ts +709 -0
  6. package/dist/BaseSyncedStore.js +1843 -0
  7. package/dist/Database.d.ts +344 -0
  8. package/dist/Database.js +1259 -0
  9. package/dist/LazyReferenceCollection.d.ts +181 -0
  10. package/dist/LazyReferenceCollection.js +460 -0
  11. package/dist/Model.d.ts +339 -0
  12. package/dist/Model.js +715 -0
  13. package/dist/ModelRegistry.d.ts +200 -0
  14. package/dist/ModelRegistry.js +535 -0
  15. package/dist/NetworkMonitor.d.ts +27 -0
  16. package/dist/NetworkMonitor.js +73 -0
  17. package/dist/ObjectPool.d.ts +202 -0
  18. package/dist/ObjectPool.js +1106 -0
  19. package/dist/SyncClient.d.ts +489 -0
  20. package/dist/SyncClient.js +1555 -0
  21. package/dist/SyncEngineContext.d.ts +46 -0
  22. package/dist/SyncEngineContext.js +74 -0
  23. package/dist/adapters/alwaysOnline.d.ts +16 -0
  24. package/dist/adapters/alwaysOnline.js +19 -0
  25. package/dist/adapters/inMemoryStorage.d.ts +30 -0
  26. package/dist/adapters/inMemoryStorage.js +94 -0
  27. package/dist/agent/Agent.d.ts +358 -0
  28. package/dist/agent/Agent.js +500 -0
  29. package/dist/agent/index.d.ts +115 -0
  30. package/dist/agent/index.js +128 -0
  31. package/dist/agent/session.d.ts +90 -0
  32. package/dist/agent/session.js +156 -0
  33. package/dist/agent/types.d.ts +73 -0
  34. package/dist/agent/types.js +10 -0
  35. package/dist/ai-sdk/coordination-context.d.ts +51 -0
  36. package/dist/ai-sdk/coordination-context.js +107 -0
  37. package/dist/ai-sdk/index.d.ts +68 -0
  38. package/dist/ai-sdk/index.js +68 -0
  39. package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
  40. package/dist/ai-sdk/intent-broadcast.js +72 -0
  41. package/dist/ai-sdk/wrap.d.ts +67 -0
  42. package/dist/ai-sdk/wrap.js +45 -0
  43. package/dist/api/index.d.ts +10 -0
  44. package/dist/api/index.js +9 -0
  45. package/dist/auth/index.d.ts +137 -0
  46. package/dist/auth/index.js +246 -0
  47. package/dist/client/Ablo.d.ts +835 -0
  48. package/dist/client/Ablo.js +1440 -0
  49. package/dist/client/ApiClient.d.ts +200 -0
  50. package/dist/client/ApiClient.js +659 -0
  51. package/dist/client/auth.d.ts +79 -0
  52. package/dist/client/auth.js +81 -0
  53. package/dist/client/createInternalComponents.d.ts +44 -0
  54. package/dist/client/createInternalComponents.js +88 -0
  55. package/dist/client/createModelProxy.d.ts +152 -0
  56. package/dist/client/createModelProxy.js +199 -0
  57. package/dist/client/identity.d.ts +63 -0
  58. package/dist/client/identity.js +156 -0
  59. package/dist/client/index.d.ts +36 -0
  60. package/dist/client/index.js +33 -0
  61. package/dist/client/persistence.d.ts +7 -0
  62. package/dist/client/persistence.js +11 -0
  63. package/dist/client/validateAbloOptions.d.ts +42 -0
  64. package/dist/client/validateAbloOptions.js +43 -0
  65. package/dist/config/index.d.ts +10 -0
  66. package/dist/config/index.js +12 -0
  67. package/dist/context.d.ts +27 -0
  68. package/dist/context.js +58 -0
  69. package/dist/core/DatabaseManager.d.ts +108 -0
  70. package/dist/core/DatabaseManager.js +361 -0
  71. package/dist/core/QueryProcessor.d.ts +77 -0
  72. package/dist/core/QueryProcessor.js +262 -0
  73. package/dist/core/QueryView.d.ts +64 -0
  74. package/dist/core/QueryView.js +219 -0
  75. package/dist/core/StoreManager.d.ts +131 -0
  76. package/dist/core/StoreManager.js +334 -0
  77. package/dist/core/ViewRegistry.d.ts +20 -0
  78. package/dist/core/ViewRegistry.js +55 -0
  79. package/dist/core/index.d.ts +34 -0
  80. package/dist/core/index.js +59 -0
  81. package/dist/core/openIDBWithTimeout.d.ts +27 -0
  82. package/dist/core/openIDBWithTimeout.js +63 -0
  83. package/dist/core/query-utils.d.ts +37 -0
  84. package/dist/core/query-utils.js +60 -0
  85. package/dist/errors.d.ts +235 -0
  86. package/dist/errors.js +243 -0
  87. package/dist/index.d.ts +41 -0
  88. package/dist/index.js +82 -0
  89. package/dist/interfaces/headless.d.ts +95 -0
  90. package/dist/interfaces/headless.js +41 -0
  91. package/dist/interfaces/index.d.ts +321 -0
  92. package/dist/interfaces/index.js +8 -0
  93. package/dist/mutators/RecordingTransaction.d.ts +36 -0
  94. package/dist/mutators/RecordingTransaction.js +216 -0
  95. package/dist/mutators/Transaction.d.ts +48 -0
  96. package/dist/mutators/Transaction.js +64 -0
  97. package/dist/mutators/UndoManager.d.ts +114 -0
  98. package/dist/mutators/UndoManager.js +143 -0
  99. package/dist/mutators/defineMutators.d.ts +55 -0
  100. package/dist/mutators/defineMutators.js +28 -0
  101. package/dist/policy/index.d.ts +19 -0
  102. package/dist/policy/index.js +18 -0
  103. package/dist/policy/types.d.ts +74 -0
  104. package/dist/policy/types.js +17 -0
  105. package/dist/principal.d.ts +44 -0
  106. package/dist/principal.js +49 -0
  107. package/dist/query/client.d.ts +43 -0
  108. package/dist/query/client.js +84 -0
  109. package/dist/query/index.d.ts +6 -0
  110. package/dist/query/index.js +5 -0
  111. package/dist/query/types.d.ts +143 -0
  112. package/dist/query/types.js +36 -0
  113. package/dist/react/AbloProvider.d.ts +205 -0
  114. package/dist/react/AbloProvider.js +398 -0
  115. package/dist/react/ClientSideSuspense.d.ts +36 -0
  116. package/dist/react/ClientSideSuspense.js +17 -0
  117. package/dist/react/DefaultFallback.d.ts +24 -0
  118. package/dist/react/DefaultFallback.js +43 -0
  119. package/dist/react/SyncGroupProvider.d.ts +19 -0
  120. package/dist/react/SyncGroupProvider.js +44 -0
  121. package/dist/react/context.d.ts +161 -0
  122. package/dist/react/context.js +35 -0
  123. package/dist/react/index.d.ts +64 -0
  124. package/dist/react/index.js +73 -0
  125. package/dist/react/internalContext.d.ts +35 -0
  126. package/dist/react/internalContext.js +3 -0
  127. package/dist/react/useAblo.d.ts +72 -0
  128. package/dist/react/useAblo.js +63 -0
  129. package/dist/react/useCurrentUserId.d.ts +21 -0
  130. package/dist/react/useCurrentUserId.js +33 -0
  131. package/dist/react/useErrorListener.d.ts +20 -0
  132. package/dist/react/useErrorListener.js +39 -0
  133. package/dist/react/useIntent.d.ts +29 -0
  134. package/dist/react/useIntent.js +42 -0
  135. package/dist/react/useMutate.d.ts +83 -0
  136. package/dist/react/useMutate.js +122 -0
  137. package/dist/react/useMutationFailureListener.d.ts +26 -0
  138. package/dist/react/useMutationFailureListener.js +38 -0
  139. package/dist/react/useMutators.d.ts +56 -0
  140. package/dist/react/useMutators.js +66 -0
  141. package/dist/react/usePresence.d.ts +32 -0
  142. package/dist/react/usePresence.js +41 -0
  143. package/dist/react/useQuery.d.ts +123 -0
  144. package/dist/react/useQuery.js +145 -0
  145. package/dist/react/useReactive.d.ts +35 -0
  146. package/dist/react/useReactive.js +111 -0
  147. package/dist/react/useReader.d.ts +69 -0
  148. package/dist/react/useReader.js +73 -0
  149. package/dist/react/useSyncStatus.d.ts +61 -0
  150. package/dist/react/useSyncStatus.js +76 -0
  151. package/dist/react/useUndoScope.d.ts +36 -0
  152. package/dist/react/useUndoScope.js +73 -0
  153. package/dist/realtime/index.d.ts +10 -0
  154. package/dist/realtime/index.js +9 -0
  155. package/dist/schema/field.d.ts +134 -0
  156. package/dist/schema/field.js +264 -0
  157. package/dist/schema/index.d.ts +29 -0
  158. package/dist/schema/index.js +38 -0
  159. package/dist/schema/model.d.ts +326 -0
  160. package/dist/schema/model.js +89 -0
  161. package/dist/schema/queries.d.ts +203 -0
  162. package/dist/schema/queries.js +145 -0
  163. package/dist/schema/relation.d.ts +172 -0
  164. package/dist/schema/relation.js +104 -0
  165. package/dist/schema/schema.d.ts +259 -0
  166. package/dist/schema/schema.js +188 -0
  167. package/dist/schema/sugar.d.ts +129 -0
  168. package/dist/schema/sugar.js +94 -0
  169. package/dist/source/index.d.ts +423 -0
  170. package/dist/source/index.js +320 -0
  171. package/dist/source/pushQueue.d.ts +112 -0
  172. package/dist/source/pushQueue.js +249 -0
  173. package/dist/stores/ObjectStore.d.ts +103 -0
  174. package/dist/stores/ObjectStore.js +371 -0
  175. package/dist/stores/ObjectStoreContract.d.ts +39 -0
  176. package/dist/stores/ObjectStoreContract.js +1 -0
  177. package/dist/stores/SyncActionStore.d.ts +101 -0
  178. package/dist/stores/SyncActionStore.js +481 -0
  179. package/dist/sync/BootstrapHelper.d.ts +127 -0
  180. package/dist/sync/BootstrapHelper.js +434 -0
  181. package/dist/sync/ConnectionManager.d.ts +136 -0
  182. package/dist/sync/ConnectionManager.js +465 -0
  183. package/dist/sync/HydrationCoordinator.d.ts +137 -0
  184. package/dist/sync/HydrationCoordinator.js +468 -0
  185. package/dist/sync/NetworkProbe.d.ts +43 -0
  186. package/dist/sync/NetworkProbe.js +113 -0
  187. package/dist/sync/OfflineFlush.d.ts +9 -0
  188. package/dist/sync/OfflineFlush.js +22 -0
  189. package/dist/sync/OfflineTransactionStore.d.ts +37 -0
  190. package/dist/sync/OfflineTransactionStore.js +263 -0
  191. package/dist/sync/SyncWebSocket.d.ts +663 -0
  192. package/dist/sync/SyncWebSocket.js +1336 -0
  193. package/dist/sync/createIntentStream.d.ts +33 -0
  194. package/dist/sync/createIntentStream.js +243 -0
  195. package/dist/sync/createPresenceStream.d.ts +46 -0
  196. package/dist/sync/createPresenceStream.js +192 -0
  197. package/dist/sync/createSnapshot.d.ts +33 -0
  198. package/dist/sync/createSnapshot.js +124 -0
  199. package/dist/sync/participants.d.ts +114 -0
  200. package/dist/sync/participants.js +336 -0
  201. package/dist/sync/schemas.d.ts +79 -0
  202. package/dist/sync/schemas.js +78 -0
  203. package/dist/testing/fixtures/bootstrap.d.ts +45 -0
  204. package/dist/testing/fixtures/bootstrap.js +53 -0
  205. package/dist/testing/fixtures/deltas.d.ts +86 -0
  206. package/dist/testing/fixtures/deltas.js +139 -0
  207. package/dist/testing/fixtures/models.d.ts +82 -0
  208. package/dist/testing/fixtures/models.js +270 -0
  209. package/dist/testing/helpers/react-wrapper.d.ts +66 -0
  210. package/dist/testing/helpers/react-wrapper.js +64 -0
  211. package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
  212. package/dist/testing/helpers/sync-engine-harness.js +70 -0
  213. package/dist/testing/helpers/wait.d.ts +25 -0
  214. package/dist/testing/helpers/wait.js +44 -0
  215. package/dist/testing/index.d.ts +21 -0
  216. package/dist/testing/index.js +32 -0
  217. package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
  218. package/dist/testing/mocks/MockMutationExecutor.js +139 -0
  219. package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
  220. package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
  221. package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
  222. package/dist/testing/mocks/MockSyncContext.js +100 -0
  223. package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
  224. package/dist/testing/mocks/MockSyncStore.js +171 -0
  225. package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
  226. package/dist/testing/mocks/MockWebSocket.js +117 -0
  227. package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
  228. package/dist/transactions/OptimisticEchoTracker.js +104 -0
  229. package/dist/transactions/TransactionQueue.d.ts +499 -0
  230. package/dist/transactions/TransactionQueue.js +1895 -0
  231. package/dist/transactions/index.d.ts +16 -0
  232. package/dist/transactions/index.js +7 -0
  233. package/dist/transactions/mutation-error-handler.d.ts +5 -0
  234. package/dist/transactions/mutation-error-handler.js +39 -0
  235. package/dist/types/global.d.ts +107 -0
  236. package/dist/types/global.js +38 -0
  237. package/dist/types/index.d.ts +241 -0
  238. package/dist/types/index.js +70 -0
  239. package/dist/types/streams.d.ts +495 -0
  240. package/dist/types/streams.js +11 -0
  241. package/dist/utils/asyncIterator.d.ts +41 -0
  242. package/dist/utils/asyncIterator.js +142 -0
  243. package/dist/utils/duration.d.ts +28 -0
  244. package/dist/utils/duration.js +47 -0
  245. package/dist/utils/mobx-setup.d.ts +42 -0
  246. package/dist/utils/mobx-setup.js +381 -0
  247. package/docs/api-keys.md +24 -0
  248. package/docs/api.md +230 -0
  249. package/docs/audit.md +81 -0
  250. package/docs/capabilities.md +163 -0
  251. package/docs/client-behavior.md +202 -0
  252. package/docs/data-sources.md +214 -0
  253. package/docs/examples/agent-human.md +84 -0
  254. package/docs/examples/ai-sdk-tool.md +92 -0
  255. package/docs/examples/existing-python-backend.md +249 -0
  256. package/docs/examples/nextjs.md +88 -0
  257. package/docs/examples/server-agent.md +86 -0
  258. package/docs/guarantees.md +148 -0
  259. package/docs/index.md +97 -0
  260. package/docs/integration-guide.md +493 -0
  261. package/docs/interaction-model.md +140 -0
  262. package/docs/mcp/claude-code.md +43 -0
  263. package/docs/mcp/cursor.md +53 -0
  264. package/docs/mcp/windsurf.md +46 -0
  265. package/docs/mcp.md +59 -0
  266. package/docs/quickstart.md +152 -0
  267. package/docs/react.md +115 -0
  268. package/docs/roadmap.md +45 -0
  269. package/examples/README.md +54 -0
  270. package/examples/data-source/README.md +102 -0
  271. package/examples/data-source/ablo-driver.ts +89 -0
  272. package/examples/data-source/customer-server.ts +208 -0
  273. package/examples/data-source/run.ts +101 -0
  274. package/examples/data-source/schema.ts +25 -0
  275. package/examples/quickstart.ts +54 -0
  276. package/examples/tsconfig.json +16 -0
  277. package/llms.txt +143 -0
  278. package/package.json +147 -0
@@ -0,0 +1,495 @@
1
+ /**
2
+ * Multiplayer stream types.
3
+ *
4
+ * Ablo treats humans and agents as participants on live application
5
+ * entities. Participants announce what they are reading or editing,
6
+ * claim intent before writing, and capture context watermarks before
7
+ * long-running AI work. The customer keeps their own schema, agent
8
+ * stack, tools, prompts, and product policy; the sync engine provides
9
+ * the shared coordination substrate.
10
+ */
11
+ import type { InferModel, Schema } from '../schema/schema.js';
12
+ /**
13
+ * Any JSON-serializable value. Used where the SDK accepts free-form
14
+ * metadata that will be persisted / transported as JSON — avoids
15
+ * `unknown` drift while preserving flexibility.
16
+ */
17
+ export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
18
+ readonly [key: string]: JsonValue;
19
+ };
20
+ /**
21
+ * Identity reference for an actor / on-behalf-of slot. Generic
22
+ * protocol vocabulary; works for sessions, agents, and system roles.
23
+ */
24
+ export interface ParticipantRef {
25
+ kind: 'user' | 'agent' | 'system';
26
+ id: string;
27
+ }
28
+ /**
29
+ * Whether the human explicitly approved a change. Open-source
30
+ * consumers that don't track approval keep `auto` as the default.
31
+ */
32
+ export type ConfirmationState = 'auto' | 'previewed' | 'approved' | 'required_human_approval' | 'auto_historical';
33
+ /**
34
+ * Wire-shape of a single sync delta. Carries the dual-attribution
35
+ * fields (`actor`, `onBehalfOf`, `capabilityId`, `confirmationState`,
36
+ * `causedByTaskId`) that the audit substrate stamps onto each row.
37
+ */
38
+ export interface AgentDelta {
39
+ id: number;
40
+ actionType: 'I' | 'U' | 'D' | 'A';
41
+ modelName: string;
42
+ modelId: string;
43
+ data: Record<string, unknown>;
44
+ previousData?: Record<string, unknown>;
45
+ /** Who DID the action. */
46
+ actor?: ParticipantRef | null;
47
+ /** On WHOSE AUTHORITY the actor acted. */
48
+ onBehalfOf?: ParticipantRef | null;
49
+ /** Capability that authorized this commit. */
50
+ capabilityId?: string | null;
51
+ /** Whether the human explicitly approved the change. */
52
+ confirmationState?: ConfirmationState | null;
53
+ /** Turn handle that caused this commit. */
54
+ causedByTaskId?: string | null;
55
+ createdAt: string;
56
+ }
57
+ /**
58
+ * A reference to whoever's authority bounds a joined participant.
59
+ * The spawned participant can never see or do more than this principal.
60
+ * Enforced cryptographically via Biscuit attenuation.
61
+ *
62
+ * • `SessionRef` — human is joining an agent (chat assistant flow)
63
+ * • `AgentRef` — agent spawning a sub-agent (attenuation chain)
64
+ * • omitted — the API key on the Ablo client is the ceiling
65
+ */
66
+ export type Principal = SessionRef | AgentRef;
67
+ export interface SessionRef {
68
+ readonly kind: 'session';
69
+ readonly id: string;
70
+ readonly userId: string;
71
+ readonly organizationId: string;
72
+ }
73
+ export interface AgentRef {
74
+ readonly kind: 'agent';
75
+ readonly id: string;
76
+ readonly capabilityToken: string;
77
+ }
78
+ /**
79
+ * Flat snapshot view returned from `participant.snapshot(...)`.
80
+ *
81
+ * - Per-model buckets: `snap.<modelName>[id] → entity` — typed from
82
+ * the schema via `InferModel`, NOT `unknown`. So
83
+ * `snap.clauses[clauseId].text` has `string` (or whatever Zod
84
+ * inferred from the model's shape).
85
+ * - `stamp` — opaque version marker; thread into writes as
86
+ * `{ readAt: snap.stamp }` so the server can reject stale writes.
87
+ * - `signal` — AbortSignal that fires if any captured entity
88
+ * receives a delta during the window. Pass into the LLM call so
89
+ * mid-generation invalidations abort the token stream instead of
90
+ * completing against a dead snapshot.
91
+ * - `onChange(fn)` — callback form for non-abort use cases (logging,
92
+ * UI flags, partial regeneration). Returns an unsubscribe.
93
+ *
94
+ * The per-model buckets collide with the three concurrency fields if
95
+ * you name a model `stamp` / `signal` / `onChange`. We throw at
96
+ * snapshot time with a clear error so the mistake is loud.
97
+ */
98
+ export type Snapshot<TSchema extends Schema = Schema, ModelName extends keyof TSchema['models'] = keyof TSchema['models']> = {
99
+ readonly stamp: number;
100
+ readonly signal: AbortSignal;
101
+ onChange(listener: (change: ContextChange) => void): () => void;
102
+ } & {
103
+ readonly [M in ModelName]: Readonly<Record<string, InferModel<TSchema, M>>>;
104
+ };
105
+ export interface ContextChange {
106
+ readonly model: string;
107
+ readonly id: string;
108
+ readonly severity: 'semantic' | 'metadata';
109
+ }
110
+ /**
111
+ * Mutation-time staleness mode. Passed on every write that follows a
112
+ * snapshot. Defaults to `'reject'` when `readAt` is provided without
113
+ * `onStale`.
114
+ */
115
+ export type OnStaleMode = 'reject' | 'flag' | 'merge' | 'force';
116
+ export interface TargetRange {
117
+ readonly startLine: number;
118
+ readonly endLine: number;
119
+ readonly startColumn?: number;
120
+ readonly endColumn?: number;
121
+ }
122
+ /**
123
+ * A pointer to one entity, optionally narrowed to a structured
124
+ * subtarget. `type` and `id` are customer schema vocabulary; `path`,
125
+ * `range`, `field`, and `meta` are generic coordination hints for
126
+ * products like code editors, document editors, and design tools.
127
+ */
128
+ export interface EntityRef {
129
+ readonly type: string;
130
+ readonly id: string;
131
+ readonly path?: string;
132
+ readonly range?: TargetRange;
133
+ readonly field?: string;
134
+ readonly meta?: Record<string, unknown>;
135
+ }
136
+ /**
137
+ * A pointer to one entity the participant is acting on. Either a
138
+ * typed `EntityRef` (`{ type, id, ... }`), or a tuple
139
+ * `['Clause', 'cl_3']` for ergonomic inline use. The verb methods
140
+ * below accept both.
141
+ */
142
+ export type PresenceTarget = EntityRef | readonly [type: string, id: string];
143
+ /**
144
+ * Reactive livestream of what every multiplayer participant is doing.
145
+ * Every participant gets one; it's always on, always current.
146
+ */
147
+ export interface PresenceStream {
148
+ /**
149
+ * This participant's own broadcast state. Mirrors what every other
150
+ * participant sees for this one. Read-only from the owner's side —
151
+ * mutate via `update(...)` below.
152
+ */
153
+ readonly self: Peer;
154
+ /**
155
+ * Push a new activity to the livestream. Synchronous — the sync
156
+ * engine ships the frame on the already-open WebSocket; there's
157
+ * no request-response round-trip to wait on. Callers update as
158
+ * often as state meaningfully changes (on read, on generate-start,
159
+ * on partial-output, on write, on done).
160
+ *
161
+ * Prefer the verb methods below (`editing`, `viewing`, ...) for
162
+ * canonical actions — they read as one-line sentences and don't
163
+ * force the caller to remember the action-string vocabulary.
164
+ */
165
+ update(activity: Activity): void;
166
+ /** Participant is actively modifying this entity. */
167
+ editing(target: PresenceTarget, detail?: string): void;
168
+ /** Participant is reading this entity; no modifications. */
169
+ reading(target: PresenceTarget, detail?: string): void;
170
+ /** Participant is reading this entity; no modifications. */
171
+ viewing(target: PresenceTarget, detail?: string): void;
172
+ /** Participant has stepped away from any specific entity. */
173
+ idle(): void;
174
+ /**
175
+ * Reactive view of every OTHER participant's current activity on
176
+ * this participant's sync groups. Reads return the current snapshot;
177
+ * pair with `subscribe(listener)` below to get notified on changes.
178
+ *
179
+ * An LLM pipeline can include `presence.others` in its system prompt
180
+ * so the model literally reasons with knowledge of what other
181
+ * agents are doing right now: "copy-bot is generating a new title
182
+ * for slide 5; don't duplicate that work."
183
+ */
184
+ readonly others: ReadonlyArray<Peer>;
185
+ /** Subset of `others` filtered to a specific sync group. */
186
+ othersIn(syncGroup: string): ReadonlyArray<Peer>;
187
+ /**
188
+ * Framework-agnostic reactivity primitive. Register a callback that
189
+ * fires every time `others` / `othersIn(...)` content changes (a
190
+ * peer joined, left, or updated its activity). Returns an
191
+ * unsubscribe fn.
192
+ *
193
+ * React binding:
194
+ * ```ts
195
+ * const others = useSyncExternalStore(
196
+ * presence.subscribe,
197
+ * () => presence.others,
198
+ * );
199
+ * ```
200
+ *
201
+ * MobX binding:
202
+ * ```ts
203
+ * autorun(() => {
204
+ * // Triggered on every presence change because the observable
205
+ * // version counter inside presence is read here.
206
+ * const peers = presence.others;
207
+ * // ...
208
+ * });
209
+ * ```
210
+ */
211
+ subscribe(listener: () => void): () => void;
212
+ /**
213
+ * Async-iterable view of the peer roster. Each iteration yields the
214
+ * current `others` snapshot on every mutation — so the consumer
215
+ * sees the world as it changes without registering a callback.
216
+ *
217
+ * ```ts
218
+ * for await (const peers of participant.presence) {
219
+ * renderAvatars(peers);
220
+ * if (peers.length === 0) break; // iteration stops, subscription drops
221
+ * }
222
+ * ```
223
+ *
224
+ * Each `for await` creates an independent iterator — two loops on
225
+ * the same stream both see every update; they don't steal values
226
+ * from each other. Breaking out of the loop (or throwing) tears
227
+ * down the underlying subscription cleanly via the iterator's
228
+ * `return()` hook.
229
+ */
230
+ [Symbol.asyncIterator](): AsyncIterableIterator<ReadonlyArray<Peer>>;
231
+ }
232
+ /**
233
+ * What a participant is currently doing. This is BOTH the SDK and the
234
+ * wire shape — Ablo broadcasts presence on the same WebSocket frame
235
+ * format (`presence_update`) the sync-server has always accepted, so
236
+ * the canonical activity type and the wire activity field are one and
237
+ * the same.
238
+ *
239
+ * Every activity is about a single entity-in-focus. Agents that reason
240
+ * over multiple entities call `presence.update(...)` whenever focus
241
+ * shifts; other participants see the transition in real time.
242
+ */
243
+ export interface Activity {
244
+ /** Entity type the participant is focused on (e.g. "Slide", "Document"). */
245
+ readonly entityType: string;
246
+ /** Specific entity id. */
247
+ readonly entityId: string;
248
+ /** Optional path for file/document-like targets. */
249
+ readonly path?: string;
250
+ /** Optional line/column range for partial-entity coordination. */
251
+ readonly range?: TargetRange;
252
+ /** Optional field/property path for field-level coordination. */
253
+ readonly field?: string;
254
+ /** App-defined structured metadata. Display-only unless app policy uses it. */
255
+ readonly meta?: Record<string, unknown>;
256
+ /**
257
+ * What the participant is doing to that entity. Canonical values:
258
+ * `'editing'` / `'reviewing'` / `'generating'` / `'analyzing'` /
259
+ * `'executing'`. Free-form strings are accepted for app-specific
260
+ * phases.
261
+ */
262
+ readonly action: string;
263
+ /** Human-readable detail — "slide 3", "cell A1:B5", etc. */
264
+ readonly detail?: string;
265
+ /**
266
+ * Backpressure signal — load factor in `[0.0, 1.0]`. When set,
267
+ * orchestrator agents reading peer activity can route work around
268
+ * overloaded fleet members. Convention: `0.0` = idle, `1.0` = at
269
+ * capacity, intermediate values = "I have headroom but prefer not."
270
+ * Optional — agents that don't participate in load-aware routing
271
+ * leave this unset; orchestrators ignore them in load calculations.
272
+ * Hub treats it opaquely.
273
+ */
274
+ readonly loadFactor?: number;
275
+ /**
276
+ * Backpressure gate — explicit signal for new work assignments.
277
+ * Defaults to true when unset (everyone accepts work by default).
278
+ * Set false during graceful shutdown, capacity exhaustion, or when
279
+ * the agent is committed to a long-running step it cannot preempt.
280
+ * Orchestrators MUST treat false as "skip this peer for new work,"
281
+ * and SHOULD treat true with high `loadFactor` as "available but
282
+ * deprioritize."
283
+ */
284
+ readonly acceptingNewWork?: boolean;
285
+ }
286
+ /**
287
+ * One participant's live state as seen by everyone else in scope.
288
+ *
289
+ * This is the canonical engine vocabulary. The server's older wire
290
+ * frame still emits `userId` / `isAgent` / `updatedAt`; those names
291
+ * are deprecated and translated at the inbound boundary
292
+ * (`createPresenceStream`) into the names below. New code reads and
293
+ * writes this shape only.
294
+ */
295
+ export interface Peer {
296
+ readonly participantKind: 'human' | 'agent';
297
+ readonly participantId: string;
298
+ readonly label?: string;
299
+ readonly syncGroups: readonly string[];
300
+ readonly activity: Activity;
301
+ /** Server timestamp of the most recent frame from this participant. */
302
+ readonly lastActive: string;
303
+ /** Pending-mutation intents this participant has declared. */
304
+ readonly activeIntents?: ReadonlyArray<IntentClaim>;
305
+ }
306
+ /**
307
+ * Pending-mutation intent on the wire. Declared via `intent_begin`,
308
+ * cleared on `intent_abandon` / commit / disconnect / TTL expiry.
309
+ * Server stamps `declaredAt` and `expiresAt` (ms epoch). The SDK's
310
+ * `IntentStream.others` exposes a richer `ActiveIntent` view (defined
311
+ * below) that adds `heldBy` so callers know which participant owns it.
312
+ */
313
+ export interface IntentClaim {
314
+ readonly intentId: string;
315
+ readonly entityType: string;
316
+ readonly entityId: string;
317
+ readonly path?: string;
318
+ readonly range?: TargetRange;
319
+ readonly action: string;
320
+ readonly field?: string;
321
+ readonly meta?: Record<string, unknown>;
322
+ readonly declaredAt: number;
323
+ readonly expiresAt: number;
324
+ }
325
+ /**
326
+ * Transition type carried on every presence frame from the server.
327
+ * - `'enter'` — first frame the receiver sees for this peer.
328
+ * - `'update'` — activity / intent change on an already-known peer.
329
+ * - `'leave'` — peer departed (explicit disconnect or TTL expiry).
330
+ */
331
+ export type PresenceKind = 'enter' | 'update' | 'leave';
332
+ /** Outbound `presence_update` payload. */
333
+ export interface PresenceUpdatePayload {
334
+ readonly status: 'online' | 'away' | 'offline' | (string & {});
335
+ readonly activity?: Activity;
336
+ readonly isAgent?: boolean;
337
+ }
338
+ /**
339
+ * Intent broadcasts — "I'm about to do X on Y." Broadcasts flow on
340
+ * the same WS as presence, so every participant sees them in real
341
+ * time. Cooperative mutex: the intent doesn't enforce exclusion; it
342
+ * announces. Other agents observe and yield. This is cheaper and
343
+ * more flexible than a central lock table and composes with presence.
344
+ */
345
+ /**
346
+ * Options common to every verb-style intent announcement
347
+ * (`intents.analyzing`, `.drafting`, etc.).
348
+ *
349
+ * The one required field is the *target* — everything else is a
350
+ * sensible default. Prefer the verb methods in `IntentStream` below
351
+ * (`analyzing(entity, { ttl: '3m' })`) over the raw `announce(...)`
352
+ * escape hatch.
353
+ */
354
+ export interface IntentOptions {
355
+ /**
356
+ * How long before the server auto-expires this intent if the
357
+ * participant doesn't finish the work. Accepts either a number (in
358
+ * seconds — back-compat with `ttlSeconds`) or a duration string:
359
+ * `'500ms'`, `'30s'`, `'3m'`, `'24h'`.
360
+ */
361
+ readonly ttl?: Duration;
362
+ }
363
+ /** Re-export of the duration helper shape. See `./duration.ts`. */
364
+ export type Duration = import('../utils/duration.js').Duration;
365
+ export interface ClaimOptions extends IntentOptions {
366
+ /**
367
+ * Free-form reason describing why you're claiming. Surfaces in conflict
368
+ * messages and the activity overlay. Defaults to `'editing'`. Common
369
+ * values: `'editing'`, `'writing'`, `'reviewing'`, custom strings for
370
+ * app-specific phases.
371
+ */
372
+ readonly reason?: string;
373
+ }
374
+ export interface IntentStream {
375
+ /**
376
+ * Claim an exclusive intent on a target. Returns a handle — call
377
+ * `.revoke()` to cancel, let it expire via TTL, or use `await using`
378
+ * (TC39 explicit resource management) to auto-revoke on scope exit.
379
+ *
380
+ * Server rejects via `intent_rejected` when another participant
381
+ * already holds a claim on the same target. Default `reason` is
382
+ * `'editing'`; pass `{reason: 'writing'}` (or any string) to override.
383
+ *
384
+ * The frame ships on the open WS immediately. One method, one shape —
385
+ * the verb shortcuts (`editing`, `writing`, `announce`) and the
386
+ * scoped `claim(reason, opts)` overload were collapsed into this
387
+ * single primitive.
388
+ */
389
+ claim(target: PresenceTarget, opts?: ClaimOptions): Claim;
390
+ /**
391
+ * Reactive view of every other participant's active intents.
392
+ * Reads return the current snapshot; pair with `subscribe(...)`
393
+ * below to get notified on change.
394
+ */
395
+ readonly others: ReadonlyArray<ActiveIntent>;
396
+ /**
397
+ * Framework-agnostic reactivity. Same contract as
398
+ * `PresenceStream.subscribe` — register a listener fired on every
399
+ * change (announce / revoke / TTL expiry received from the server),
400
+ * returns an unsubscribe fn. Use `useSyncExternalStore` in React or
401
+ * `autorun` in MobX.
402
+ */
403
+ subscribe(listener: () => void): () => void;
404
+ /**
405
+ * Observe server-side intent rejections. Fires when the server
406
+ * rejects an `intents.writing(...)` / `announce(...)` call because
407
+ * another participant already holds an open claim on the same
408
+ * target (cooperative mutex → enforced at the server boundary).
409
+ *
410
+ * Use this to surface conflicts to the user:
411
+ * ```ts
412
+ * participant.intents.onRejected((r) => {
413
+ * toast.error(`${r.heldBy} is editing — try again in a moment`);
414
+ * });
415
+ * ```
416
+ *
417
+ * Returns an unsubscribe fn.
418
+ */
419
+ onRejected(listener: (rejection: IntentRejection) => void): () => void;
420
+ /**
421
+ * Async-iterable view of everyone else's open intents. Each
422
+ * iteration yields the current snapshot on every mutation.
423
+ *
424
+ * ```ts
425
+ * for await (const openIntents of participant.intents) {
426
+ * if (openIntents.some((i) => i.target.id === clauseId)) wait();
427
+ * }
428
+ * ```
429
+ */
430
+ [Symbol.asyncIterator](): AsyncIterableIterator<ReadonlyArray<ActiveIntent>>;
431
+ }
432
+ /**
433
+ * Shape of an `intent_rejected` event delivered to
434
+ * `IntentStream.onRejected`. Server rejects an incoming claim when
435
+ * another participant already holds an open intent on the same target.
436
+ */
437
+ export interface IntentRejection {
438
+ /** The rejected claim's id (the one the caller just tried to mint). */
439
+ readonly intentId: string;
440
+ /** Why the server rejected it — currently always `'conflict'`. */
441
+ readonly reason: 'conflict';
442
+ /** The target that's already held. */
443
+ readonly target: {
444
+ readonly entityType: string;
445
+ readonly entityId: string;
446
+ readonly path?: string;
447
+ readonly range?: TargetRange;
448
+ readonly field?: string;
449
+ readonly meta?: Record<string, unknown>;
450
+ };
451
+ /** Participant id holding the existing claim. */
452
+ readonly heldBy: string;
453
+ /** The existing claim's id (for audit / retry correlation). */
454
+ readonly heldByIntentId: string;
455
+ /** When the existing claim expires (ms since epoch). */
456
+ readonly heldByExpiresAt: number;
457
+ }
458
+ export interface IntentDeclaration {
459
+ readonly target: EntityRef;
460
+ /** Human-readable reason — "rewriting title" / "restyling chart". */
461
+ readonly reason: string;
462
+ /**
463
+ * Expiry — auto-revoke if the participant doesn't finish in time.
464
+ * Number = seconds (back-compat); string = duration (`'3m'`).
465
+ */
466
+ readonly ttlSeconds?: Duration;
467
+ }
468
+ /**
469
+ * Handle returned from `announce(...)` / `analyzing(...)` / etc.
470
+ *
471
+ * Implements `Symbol.asyncDispose` so callers can write:
472
+ *
473
+ * ```ts
474
+ * {
475
+ * await using work = participant.intents.analyzing(clause, { ttl: '3m' });
476
+ * // ... do the work; intent auto-revokes when the block exits
477
+ * }
478
+ * ```
479
+ */
480
+ export interface Claim extends AsyncDisposable {
481
+ readonly id: string;
482
+ revoke(): void;
483
+ }
484
+ export interface ActiveIntent extends IntentDeclaration {
485
+ readonly id: string;
486
+ readonly heldBy: string;
487
+ /**
488
+ * Whether the holding participant is a human (session) or an agent.
489
+ * First-class field so UIs can style "agent editing X" differently
490
+ * from "user editing X" without string-parsing `heldBy`.
491
+ */
492
+ readonly participantKind: 'human' | 'agent';
493
+ readonly announcedAt: string;
494
+ readonly expiresAt: string;
495
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Multiplayer stream types.
3
+ *
4
+ * Ablo treats humans and agents as participants on live application
5
+ * entities. Participants announce what they are reading or editing,
6
+ * claim intent before writing, and capture context watermarks before
7
+ * long-running AI work. The customer keeps their own schema, agent
8
+ * stack, tools, prompts, and product policy; the sync engine provides
9
+ * the shared coordination substrate.
10
+ */
11
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `asyncIteratorFrom` — adapt any callback-subscription primitive
3
+ * into an async iterable.
4
+ *
5
+ * The inputs are two functions:
6
+ *
7
+ * - `subscribe(onChange): unsubscribe` — the existing reactivity
8
+ * primitive on `PresenceStream` / `IntentStream`. We register a
9
+ * listener that enqueues a value every time the source mutates;
10
+ * we tear it down in `return()`.
11
+ * - `getSnapshot()` — read the latest value to hand to the
12
+ * consumer. Called on every mutation notification.
13
+ *
14
+ * Back-pressure: an unlimited queue. If the consumer is slower than
15
+ * the producer (rare for presence — mutations are <1/s per peer),
16
+ * memory grows monotonically inside the iterator. For the current
17
+ * presence workload this is fine; if we ever surface a high-frequency
18
+ * stream (deltas at full firehose) we can bound the queue or drop
19
+ * coalescable values.
20
+ *
21
+ * Multiple iterators: each call to the returned factory creates an
22
+ * independent iterator with its own subscription. Iterators don't
23
+ * steal values from each other — two `for await` loops on the same
24
+ * stream both observe every mutation.
25
+ */
26
+ /**
27
+ * Variant of `asyncIteratorFrom` for event-per-iteration streams.
28
+ *
29
+ * Unlike the snapshot variant (where every notification yields the
30
+ * *current value* — coalescing bursts is fine because state is the
31
+ * consumer's concern), this variant yields the *specific value*
32
+ * pushed by each event. Use when the underlying stream delivers
33
+ * discrete events that must not be dropped — e.g. `DeltaEnvelope`
34
+ * firehose.
35
+ *
36
+ * The `subscribe(push): unsubscribe` signature takes a callback that
37
+ * enqueues an event. The consumer's `for await` receives every
38
+ * enqueued value in order.
39
+ */
40
+ export declare function asyncIteratorFromEvents<T>(subscribe: (push: (value: T) => void) => () => void): AsyncIterableIterator<T>;
41
+ export declare function asyncIteratorFrom<T>(subscribe: (listener: () => void) => () => void, getSnapshot: () => T): AsyncIterableIterator<T>;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * `asyncIteratorFrom` — adapt any callback-subscription primitive
3
+ * into an async iterable.
4
+ *
5
+ * The inputs are two functions:
6
+ *
7
+ * - `subscribe(onChange): unsubscribe` — the existing reactivity
8
+ * primitive on `PresenceStream` / `IntentStream`. We register a
9
+ * listener that enqueues a value every time the source mutates;
10
+ * we tear it down in `return()`.
11
+ * - `getSnapshot()` — read the latest value to hand to the
12
+ * consumer. Called on every mutation notification.
13
+ *
14
+ * Back-pressure: an unlimited queue. If the consumer is slower than
15
+ * the producer (rare for presence — mutations are <1/s per peer),
16
+ * memory grows monotonically inside the iterator. For the current
17
+ * presence workload this is fine; if we ever surface a high-frequency
18
+ * stream (deltas at full firehose) we can bound the queue or drop
19
+ * coalescable values.
20
+ *
21
+ * Multiple iterators: each call to the returned factory creates an
22
+ * independent iterator with its own subscription. Iterators don't
23
+ * steal values from each other — two `for await` loops on the same
24
+ * stream both observe every mutation.
25
+ */
26
+ /**
27
+ * Variant of `asyncIteratorFrom` for event-per-iteration streams.
28
+ *
29
+ * Unlike the snapshot variant (where every notification yields the
30
+ * *current value* — coalescing bursts is fine because state is the
31
+ * consumer's concern), this variant yields the *specific value*
32
+ * pushed by each event. Use when the underlying stream delivers
33
+ * discrete events that must not be dropped — e.g. `DeltaEnvelope`
34
+ * firehose.
35
+ *
36
+ * The `subscribe(push): unsubscribe` signature takes a callback that
37
+ * enqueues an event. The consumer's `for await` receives every
38
+ * enqueued value in order.
39
+ */
40
+ export function asyncIteratorFromEvents(subscribe) {
41
+ const queue = [];
42
+ const resolvers = [];
43
+ let done = false;
44
+ const push = (value) => {
45
+ if (done)
46
+ return;
47
+ const resolver = resolvers.shift();
48
+ if (resolver) {
49
+ resolver({ value, done: false });
50
+ }
51
+ else {
52
+ queue.push(value);
53
+ }
54
+ };
55
+ const unsubscribe = subscribe(push);
56
+ const finish = () => {
57
+ done = true;
58
+ unsubscribe();
59
+ for (const r of resolvers)
60
+ r({ value: undefined, done: true });
61
+ resolvers.length = 0;
62
+ queue.length = 0;
63
+ return { value: undefined, done: true };
64
+ };
65
+ return {
66
+ async next() {
67
+ if (done)
68
+ return { value: undefined, done: true };
69
+ if (queue.length > 0) {
70
+ return { value: queue.shift(), done: false };
71
+ }
72
+ return new Promise((resolve) => {
73
+ resolvers.push(resolve);
74
+ });
75
+ },
76
+ async return() {
77
+ return finish();
78
+ },
79
+ async throw(err) {
80
+ finish();
81
+ throw err;
82
+ },
83
+ [Symbol.asyncIterator]() {
84
+ return this;
85
+ },
86
+ };
87
+ }
88
+ export function asyncIteratorFrom(subscribe, getSnapshot) {
89
+ const queue = [];
90
+ // Pending `next()` callers waiting for a value. Empty when the
91
+ // consumer is keeping up; holds 0-or-1 resolver when they're
92
+ // awaiting. We never hold more than one at a time — a consumer
93
+ // that calls `next()` twice without awaiting the first breaks
94
+ // the async-iterator contract.
95
+ const resolvers = [];
96
+ let done = false;
97
+ const push = () => {
98
+ if (done)
99
+ return;
100
+ const value = getSnapshot();
101
+ const resolver = resolvers.shift();
102
+ if (resolver) {
103
+ resolver({ value, done: false });
104
+ }
105
+ else {
106
+ queue.push(value);
107
+ }
108
+ };
109
+ const unsubscribe = subscribe(push);
110
+ const finish = () => {
111
+ done = true;
112
+ unsubscribe();
113
+ // Resolve any dangling readers so their awaits don't leak.
114
+ for (const r of resolvers)
115
+ r({ value: undefined, done: true });
116
+ resolvers.length = 0;
117
+ queue.length = 0;
118
+ return { value: undefined, done: true };
119
+ };
120
+ return {
121
+ async next() {
122
+ if (done)
123
+ return { value: undefined, done: true };
124
+ if (queue.length > 0) {
125
+ return { value: queue.shift(), done: false };
126
+ }
127
+ return new Promise((resolve) => {
128
+ resolvers.push(resolve);
129
+ });
130
+ },
131
+ async return() {
132
+ return finish();
133
+ },
134
+ async throw(err) {
135
+ finish();
136
+ throw err;
137
+ },
138
+ [Symbol.asyncIterator]() {
139
+ return this;
140
+ },
141
+ };
142
+ }