@abloatai/ablo 0.3.0 → 0.4.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 (97) hide show
  1. package/CHANGELOG.md +43 -5
  2. package/NOTICE +2 -2
  3. package/README.md +30 -28
  4. package/dist/agent/Agent.d.ts +1 -1
  5. package/dist/agent/Agent.js +1 -1
  6. package/dist/agent/index.d.ts +4 -4
  7. package/dist/agent/index.js +6 -6
  8. package/dist/agent/types.d.ts +1 -1
  9. package/dist/ai-sdk/index.d.ts +3 -3
  10. package/dist/ai-sdk/index.js +3 -3
  11. package/dist/ai-sdk/intent-broadcast.d.ts +1 -1
  12. package/dist/ai-sdk/intent-broadcast.js +1 -1
  13. package/dist/auth/index.d.ts +1 -1
  14. package/dist/client/Ablo.d.ts +8 -14
  15. package/dist/client/Ablo.js +32 -1
  16. package/dist/client/auth.d.ts +3 -3
  17. package/dist/client/auth.js +5 -5
  18. package/dist/client/createModelProxy.d.ts +110 -32
  19. package/dist/client/createModelProxy.js +77 -38
  20. package/dist/client/index.d.ts +2 -2
  21. package/dist/client/index.js +2 -2
  22. package/dist/config/index.d.ts +1 -1
  23. package/dist/config/index.js +1 -1
  24. package/dist/core/index.d.ts +1 -1
  25. package/dist/core/index.js +2 -2
  26. package/dist/errors.d.ts +1 -1
  27. package/dist/errors.js +1 -1
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +9 -9
  30. package/dist/interfaces/headless.d.ts +1 -1
  31. package/dist/interfaces/headless.js +2 -2
  32. package/dist/policy/index.d.ts +2 -2
  33. package/dist/policy/index.js +2 -2
  34. package/dist/principal.d.ts +1 -1
  35. package/dist/principal.js +1 -1
  36. package/dist/react/ClientSideSuspense.d.ts +1 -1
  37. package/dist/react/SyncGroupProvider.js +1 -1
  38. package/dist/react/context.d.ts +1 -1
  39. package/dist/react/context.js +1 -1
  40. package/dist/react/index.d.ts +1 -1
  41. package/dist/react/index.js +1 -1
  42. package/dist/react/useCurrentUserId.js +1 -1
  43. package/dist/react/useErrorListener.js +1 -1
  44. package/dist/react/useMutate.d.ts +1 -1
  45. package/dist/react/useMutationFailureListener.js +1 -1
  46. package/dist/react/useReader.d.ts +1 -1
  47. package/dist/schema/field.d.ts +1 -1
  48. package/dist/schema/field.js +1 -1
  49. package/dist/schema/index.d.ts +2 -2
  50. package/dist/schema/index.js +2 -2
  51. package/dist/schema/model.d.ts +2 -2
  52. package/dist/schema/model.js +2 -2
  53. package/dist/schema/queries.d.ts +1 -1
  54. package/dist/schema/queries.js +1 -1
  55. package/dist/schema/relation.d.ts +1 -1
  56. package/dist/schema/relation.js +1 -1
  57. package/dist/schema/schema.d.ts +1 -1
  58. package/dist/schema/schema.js +1 -1
  59. package/dist/source/index.d.ts +22 -28
  60. package/dist/source/index.js +23 -20
  61. package/dist/source/pushQueue.d.ts +1 -1
  62. package/dist/source/pushQueue.js +2 -2
  63. package/dist/sync/SyncWebSocket.d.ts +14 -0
  64. package/dist/sync/createIntentStream.js +7 -0
  65. package/dist/testing/fixtures/models.d.ts +1 -1
  66. package/dist/testing/fixtures/models.js +1 -1
  67. package/dist/testing/helpers/react-wrapper.d.ts +2 -2
  68. package/dist/testing/helpers/react-wrapper.js +2 -2
  69. package/dist/testing/index.d.ts +1 -1
  70. package/dist/testing/index.js +1 -1
  71. package/dist/types/streams.d.ts +39 -0
  72. package/docs/api-keys.md +2 -2
  73. package/docs/api.md +81 -23
  74. package/docs/capabilities.md +1 -1
  75. package/docs/client-behavior.md +7 -7
  76. package/docs/data-sources.md +52 -18
  77. package/docs/examples/agent-human.md +3 -3
  78. package/docs/examples/ai-sdk-tool.md +16 -33
  79. package/docs/examples/existing-python-backend.md +10 -10
  80. package/docs/examples/nextjs.md +1 -1
  81. package/docs/examples/server-agent.md +2 -2
  82. package/docs/index.md +1 -1
  83. package/docs/integration-guide.md +15 -14
  84. package/docs/interaction-model.md +16 -4
  85. package/docs/mcp.md +1 -1
  86. package/docs/quickstart.md +23 -21
  87. package/docs/react.md +3 -3
  88. package/docs/roadmap.md +1 -1
  89. package/examples/README.md +3 -3
  90. package/examples/data-source/README.md +1 -1
  91. package/examples/data-source/ablo-driver.ts +5 -5
  92. package/examples/data-source/customer-server.ts +10 -10
  93. package/examples/data-source/run.ts +9 -11
  94. package/examples/data-source/schema.ts +1 -1
  95. package/examples/quickstart.ts +2 -2
  96. package/llms.txt +8 -8
  97. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,17 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Per-entity coordination intents on the model accessor.
8
+
9
+ Coordinate writes to an entity through the same accessor you read it with —
10
+ `ablo.<model>.intent(id)`, returning a `ModelIntentHandle`. Intent state is one
11
+ self-describing object (`{ object: 'intent', id, status, target, action, heldBy,
12
+ participantKind, createdAt?, expiresAt? }`) with a single lifecycle:
13
+ `status: 'active' | 'committed' | 'expired' | 'canceled'`. An `active` intent is
14
+ the lock.
15
+
16
+ ### Added
17
+ - `ablo.<model>.intent(id)` → `ModelIntentHandle<T>`, beside `create` / `update`
18
+ / `retrieve` / `load` on every model.
19
+ - Read side (any participant, synchronous + reactive): `current` (the holder's
20
+ intent, or `null`), `status` (`'idle'` when free), `settled()`.
21
+ - Write side (the holder): `acquire()`, `acquireOrAwait()`, lease-guarded
22
+ `update()`, `release()`, `revoke()`.
23
+ - `AsyncDisposable`: `await using lock = ablo.<model>.intent(id)` auto-releases
24
+ on scope exit.
25
+ - `acquireOrAwait()` — serialize-on-contention: take the lease, or wait out the
26
+ current holder, re-read the changed row, then take it. The caller never branches
27
+ on who holds the target — it just gets the target safely. Bind it to an agent's
28
+ write-tool boundary so agents never reason about coordination.
29
+ - New exports: `ModelIntentHandle`, `ModelIntentAcquireOptions`.
30
+
31
+ ### Changed
32
+ - `acquire()` is fire-and-forget over the socket — it does not throw on conflict.
33
+ Resolve contention with `acquireOrAwait()` (wait) or read `current` for a
34
+ reactive "who's editing" badge, rather than catching a rejection.
35
+
36
+ ### Deprecated
37
+ - Participant-level `intents.claim()` / `onRejected()` and the `intent_rejected`
38
+ wire frame still work but are superseded by the per-model handle. Their removal
39
+ is a future breaking change.
40
+
3
41
  ## Unreleased
4
42
 
5
43
  Schema-driven identity sync-group composition, plus a terser capability surface.
6
44
 
7
45
  The convention for deriving a participant's allowed sync-groups from its identity is now declared on the consumer's schema as an open registration. Consumers with a `{ regionId, customerId }` identity shape declare their own roles instead of receiving any built-in prefixes from the SDK.
8
46
 
9
- Capability fields shed their redundant `allowed` prefix to match the surrounding vocabulary — capability inputs always describe what the bearer *can* touch, so the prefix was doing no disambiguation work for the consumer.
47
+ Capability fields shed their redundant `allowed` prefix to match the surrounding vocabulary — capability inputs always describe what the bearer _can_ touch, so the prefix was doing no disambiguation work for the consumer.
10
48
 
11
49
  ### Added
12
50
 
13
51
  - `DefineSchemaOptions.identityRoles?: readonly IdentityRole[]` — open registration of identity-anchored sync-group roles on `defineSchema(...)`. Each `IdentityRole` declares `{ kind, template, extract }`: a diagnostic label, a `'<prefix>:{id}'` template, and a pure extractor function from an opaque identity context to zero-or-more ids. No closed enum; consumers fully control both the template strings and the extraction logic.
14
- - `composeIdentitySyncGroups(identity, schema)` exported from `@ablo/sync-engine/schema` — walks the schema's registered `identityRoles`, calls each extractor, and substitutes ids into templates. Stable, deduped output. Returns `[]` when no roles are registered.
52
+ - `composeIdentitySyncGroups(identity, schema)` exported from `@abloatai/ablo/schema` — walks the schema's registered `identityRoles`, calls each extractor, and substitutes ids into templates. Stable, deduped output. Returns `[]` when no roles are registered.
15
53
  - `Schema.identityRoles: readonly IdentityRole[]` — the registered list, accessible on every `defineSchema(...)` result.
16
54
  - New exported types: `IdentityRole`, `IdentityContext`.
17
55
 
@@ -47,7 +85,7 @@ Declarative props absorb every class of lifecycle glue; the status hook returns
47
85
 
48
86
  ### Added
49
87
 
50
- - `<AbloProvider>` — umbrella provider at `@ablo/sync-engine/react`. Props include data config (`schema`, `url`, `userId`, `organizationId`), auth (`capabilityToken` / `apiKey` / session cookie fallback), declarative behavior (`preventUnsavedChanges`, `lostConnectionTimeout`, `postBootstrap`), callbacks (`onSessionExpired`, `onError`, `resolveUsers`), and DI escape hatches.
88
+ - `<AbloProvider>` — umbrella provider at `@abloatai/ablo/react`. Props include data config (`schema`, `url`, `userId`, `organizationId`), auth (`capabilityToken` / `apiKey` / session cookie fallback), declarative behavior (`preventUnsavedChanges`, `lostConnectionTimeout`, `postBootstrap`), callbacks (`onSessionExpired`, `onError`, `resolveUsers`), and DI escape hatches.
51
89
  - `<SyncGroupProvider id="matter:...">` + `useSyncGroup()` — per-entity scope context.
52
90
  - `<ClientSideSuspense fallback={...}>` — gate renders until the engine reports `connected`. Phase-1 non-Suspense; phase-2 upgrades to real Suspense.
53
91
  - `useSyncStatus()` rewritten as a tagged union: `{ name: 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'needs-auth', ... }`. Impossible states are unrepresentable.
@@ -155,7 +193,7 @@ The SDK covers exactly three integration shapes. Each has a canonical example in
155
193
 
156
194
  ### Ergonomics (package-wide)
157
195
 
158
- - **`Ablo` class** — `import Ablo from '@ablo/sync-engine'` / `new Ablo({ schema })`. Matches `new Stripe()` / `new OpenAI()` / `new Anthropic()` pattern. `createMesh(opts)` stays available as the functional alias.
196
+ - **`Ablo` class** — `import Ablo from '@abloatai/ablo'` / `new Ablo({ schema })`. Matches `new Stripe()` / `new OpenAI()` / `new Anthropic()` pattern. `createMesh(opts)` stays available as the functional alias.
159
197
  - **Model-scoped joins** — `ablo.matters.join(id, { label })` desugars to the generic `join`. Proxy-based so the namespace adapts to any schema. Collisions with reserved admin fields (`roles`, `members`, `audit`, `capabilities`) throw at construction time.
160
198
  - **Flat scope form** — `scope: { matters: id }` alongside the array form.
161
199
  - **`as` alias** — `{ as: session({...}) }` replaces the security-jargon `onBehalfOf`; both still accepted.
@@ -199,7 +237,7 @@ Initial release.
199
237
  - **AI Agent SDK**: `SyncAgent` for backend/AI agent participation as first-class sync citizens
200
238
  - **Pluggable auth**: `AuthProvider` interface with built-in API key, JWT, and session providers
201
239
  - **Security**: IndexedDB cleanup on session expiry and sync group revocation
202
- - **Testing utilities**: `@ablo/sync-engine/testing` subpath with mocks, fixtures, and harness
240
+ - **Testing utilities**: `@abloatai/ablo/testing` subpath with mocks, fixtures, and harness
203
241
 
204
242
  ### Test Coverage
205
243
 
package/NOTICE CHANGED
@@ -1,4 +1,4 @@
1
- @ablo/sync-engine
1
+ @abloatai/ablo
2
2
  Copyright 2025-2026 Fablo Innovation AB
3
3
 
4
4
  This product includes software developed by Fablo Innovation AB
@@ -7,6 +7,6 @@ This product includes software developed by Fablo Innovation AB
7
7
  "Ablo" is a trademark of Fablo Innovation AB. This license does not grant
8
8
  permission to use the Ablo name, logo, or trademarks. Third parties
9
9
  may describe their use of or compatibility with Ablo factually (e.g.,
10
- "built with @ablo/sync-engine") but may not use the Ablo name in a way
10
+ "built with @abloatai/ablo") but may not use the Ablo name in a way
11
11
  that suggests endorsement, affiliation, or origin without written
12
12
  permission.
package/README.md CHANGED
@@ -13,7 +13,7 @@ schema -> ablo.<model>.create/load/edit/update(...)
13
13
  ## Install
14
14
 
15
15
  ```bash
16
- npm install @ablo/sync-engine
16
+ npm install @abloatai/ablo
17
17
  ```
18
18
 
19
19
  Requires Node 22+ and TypeScript 5+.
@@ -32,9 +32,9 @@ provider. Do not ship `ABLO_API_KEY` in a browser bundle.
32
32
 
33
33
  ## Quick Start
34
34
 
35
- ```ts
36
- import Ablo from '@ablo/sync-engine';
37
- import { defineSchema, model, z } from '@ablo/sync-engine/schema';
35
+ ````ts
36
+ import Ablo from '@abloatai/ablo';
37
+ import { defineSchema, model, z } from '@abloatai/ablo/schema';
38
38
 
39
39
  const schema = defineSchema({
40
40
  weatherReports: model({
@@ -64,13 +64,13 @@ const updated = await ablo.weatherReports.update(created.id, {
64
64
  console.log({ id: updated.id, status: updated.status });
65
65
 
66
66
  await ablo.dispose();
67
- ```
67
+ ```c
68
68
 
69
69
  Expected output:
70
70
 
71
71
  ```txt
72
72
  { id: '...', status: 'ready' }
73
- ```
73
+ ````
74
74
 
75
75
  Pass `schema` for typed model resources. Omit it only for advanced server-side
76
76
  resource clients such as custom agents and MCP routes.
@@ -87,33 +87,37 @@ future agents, read [Integration Guide](./docs/integration-guide.md).
87
87
 
88
88
  ## AI Activity on Existing State
89
89
 
90
- Use `edit` when AI or background work will touch an existing row for more than a
91
- quick write. Other participants can see the activity while your code runs. The
92
- activity is cleared when `update` finishes; call `release` if the work ends
93
- without a write.
90
+ When AI or background work will touch an existing row for more than a quick
91
+ write, coordinate through `ablo.<model>.intent(id)` the coordination accessor
92
+ that sits beside `create`/`update`/`retrieve`. It returns a handle
93
+ synchronously, so you can see who's already working on a row before you start.
94
94
 
95
95
  ```ts
96
- const edit = await ablo.weatherReports.edit('weather_stockholm', {
97
- activity: 'checking_weather',
98
- field: 'forecast',
99
- ttl: '2m',
100
- });
96
+ const report = ablo.weatherReports.intent('weather_stockholm');
97
+
98
+ // Read side: is someone already on it? Wait for them to finish.
99
+ if (report.current) {
100
+ report.current.heldBy; // 'agent:forecaster'
101
+ await report.settled();
102
+ }
103
+
104
+ // Write side: acquire so other participants yield while we work.
105
+ await report.acquire({ action: 'checking_weather', field: 'forecast', ttl: '2m' });
101
106
 
102
107
  // Your existing weather tool or agent call. While this runs, other clients see
103
108
  // that weather_stockholm is being checked.
104
- const weather = await weatherAgent.getWeather(edit.current.location, {
105
- signal: edit.signal,
106
- });
109
+ const row = ablo.weatherReports.retrieve('weather_stockholm');
110
+ const weather = await weatherAgent.getWeather(row.location);
107
111
 
108
- await edit.update({
112
+ await report.update({
109
113
  status: 'ready',
110
114
  forecast: weather.summary,
111
115
  });
112
116
  ```
113
117
 
114
- Ablo does not fetch the weather. It keeps the activity visible, gives the agent
115
- call an abort signal if the row changes, and clears the activity when
116
- `edit.update(...)` finishes.
118
+ Ablo does not fetch the weather. It keeps the activity visible while the work
119
+ runs, rejects `report.update(...)` with `AbloStaleContextError` if the row
120
+ changed under you, and releases the intent automatically once the write lands.
117
121
 
118
122
  ## Multiplayer
119
123
 
@@ -123,8 +127,8 @@ the same shared resource stream.
123
127
 
124
128
  - `ablo.<model>.create/update/delete` fan out confirmed deltas to subscribers.
125
129
  - `useAblo(...)` gives React clients the live row plus active intents.
126
- - `ablo.<model>.edit(...)` lets humans and agents see active work before a write lands.
127
- - `ablo.intents` remains available for custom lower-level coordination.
130
+ - `ablo.<model>.intent(id)` lets humans and agents see and coordinate active work before a write lands.
131
+ - `ablo.intents` remains available for custom lower-level coordination across resources.
128
132
 
129
133
  If a team writes directly to its own database outside Ablo, that write bypasses
130
134
  the multiplayer stream until the app reports it through Data Source events.
@@ -162,9 +166,7 @@ if (busy.length > 0) {
162
166
  console.log(`${busy[0].actor} is ${busy[0].action}`);
163
167
  }
164
168
 
165
- await ablo.intents.waitFor(
166
- { resource: 'weatherReports', id: 'weather_stockholm' },
167
- );
169
+ await ablo.intents.waitFor({ resource: 'weatherReports', id: 'weather_stockholm' });
168
170
  ```
169
171
 
170
172
  Policy names are literal:
@@ -203,7 +205,7 @@ Data Source endpoint. Your app keeps the database credentials; Ablo sends signed
203
205
  commit requests to your route.
204
206
 
205
207
  ```bash
206
- ABLO_DATA_SOURCE_SIGNING_SECRET=whsec_...
208
+ ABLO_API_KEY=sk_live_...
207
209
  ```
208
210
 
209
211
  See [Connect Your Database](./docs/data-sources.md) for the route and commit shape.
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * ```ts
11
11
  * import { generateText, tool, stepCountIs } from 'ai';
12
- * import { Agent } from '@ablo/sync-engine-internal/agent';
12
+ * import { Agent } from '@abloatai/ablo/agent';
13
13
  *
14
14
  * const perception = new Agent({
15
15
  * syncServerUrl: 'http://localhost:8080',
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * ```ts
11
11
  * import { generateText, tool, stepCountIs } from 'ai';
12
- * import { Agent } from '@ablo/sync-engine-internal/agent';
12
+ * import { Agent } from '@abloatai/ablo/agent';
13
13
  *
14
14
  * const perception = new Agent({
15
15
  * syncServerUrl: 'http://localhost:8080',
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @ablo/sync-engine-internal/agent — Agent SDK helpers
2
+ * @abloatai/ablo/agent — Agent SDK helpers
3
3
  *
4
4
  * Two entry points depending on agent lifetime:
5
5
  *
@@ -12,7 +12,7 @@
12
12
  * server-issued capability token instead of session cookies.
13
13
  *
14
14
  * ```ts
15
- * import Ablo from '@ablo/sync-engine';
15
+ * import Ablo from '@abloatai/ablo';
16
16
  *
17
17
  * const ablo = Ablo({
18
18
  * schema,
@@ -44,7 +44,7 @@
44
44
  *
45
45
  * ```ts
46
46
  * import { generateText, tool, stepCountIs } from 'ai';
47
- * import { Agent } from '@ablo/sync-engine-internal/agent';
47
+ * import { Agent } from '@abloatai/ablo/agent';
48
48
  *
49
49
  * const perception = new Agent({
50
50
  * syncServerUrl: 'http://localhost:8080',
@@ -78,7 +78,7 @@
78
78
  * ```ts
79
79
  * import { tool } from 'ai';
80
80
  * import { z } from 'zod';
81
- * import { Agent, type AgentContext } from '@ablo/sync-engine-internal/agent';
81
+ * import { Agent, type AgentContext } from '@abloatai/ablo/agent';
82
82
  *
83
83
  * export const updateSlideTool = () => tool({
84
84
  * description: 'Update a slide title',
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @ablo/sync-engine-internal/agent — Agent SDK helpers
2
+ * @abloatai/ablo/agent — Agent SDK helpers
3
3
  *
4
4
  * Two entry points depending on agent lifetime:
5
5
  *
@@ -12,7 +12,7 @@
12
12
  * server-issued capability token instead of session cookies.
13
13
  *
14
14
  * ```ts
15
- * import Ablo from '@ablo/sync-engine';
15
+ * import Ablo from '@abloatai/ablo';
16
16
  *
17
17
  * const ablo = Ablo({
18
18
  * schema,
@@ -44,7 +44,7 @@
44
44
  *
45
45
  * ```ts
46
46
  * import { generateText, tool, stepCountIs } from 'ai';
47
- * import { Agent } from '@ablo/sync-engine-internal/agent';
47
+ * import { Agent } from '@abloatai/ablo/agent';
48
48
  *
49
49
  * const perception = new Agent({
50
50
  * syncServerUrl: 'http://localhost:8080',
@@ -78,7 +78,7 @@
78
78
  * ```ts
79
79
  * import { tool } from 'ai';
80
80
  * import { z } from 'zod';
81
- * import { Agent, type AgentContext } from '@ablo/sync-engine-internal/agent';
81
+ * import { Agent, type AgentContext } from '@abloatai/ablo/agent';
82
82
  *
83
83
  * export const updateSlideTool = () => tool({
84
84
  * description: 'Update a slide title',
@@ -117,12 +117,12 @@
117
117
  // `Agent` is the class AND the namespace for its types. Reach for
118
118
  // options, context, and session options via dot access:
119
119
  //
120
- // import { Agent } from '@ablo/sync-engine-internal/agent';
120
+ // import { Agent } from '@abloatai/ablo/agent';
121
121
  // const opts: Agent.Options = { ... };
122
122
  // const ctx: Agent.Context = { perception };
123
123
  // const s: Agent.SessionOptions = { ... };
124
124
  //
125
125
  // Everything else (Activity, Claim, Turn, Peer, ActiveIntent, ...)
126
126
  // lives on the `Ablo.*` namespace via
127
- // `import type { Ablo } from '@ablo/sync-engine'`.
127
+ // `import type { Ablo } from '@abloatai/ablo'`.
128
128
  export { Agent } from './Agent.js';
@@ -35,7 +35,7 @@ export interface PresenceAnnouncer {
35
35
  *
36
36
  * ```ts
37
37
  * import { generateText, tool } from 'ai';
38
- * import { Agent, type AgentContext } from '@ablo/sync-engine-internal/agent';
38
+ * import { Agent, type AgentContext } from '@abloatai/ablo/agent';
39
39
  *
40
40
  * const updateSlideTool = () => tool({
41
41
  * inputSchema: z.object({ id: z.string(), title: z.string() }),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@ablo/sync-engine-internal/ai-sdk` — multiplayer-with-AI as language model
2
+ * `@abloatai/ablo/ai-sdk` — multiplayer-with-AI as language model
3
3
  * middleware.
4
4
  *
5
5
  * Two cross-cutting middlewares for any AI SDK consumer using
@@ -24,7 +24,7 @@
24
24
  * import {
25
25
  * intentBroadcastMiddleware,
26
26
  * coordinationContextMiddleware,
27
- * } from '@ablo/sync-engine-internal/ai-sdk';
27
+ * } from '@abloatai/ablo/ai-sdk';
28
28
  *
29
29
  * const target = { entityType: 'SlideDeck', entityId: 'deck-abc' };
30
30
  *
@@ -48,7 +48,7 @@
48
48
  * Or use the convenience composition for the common case:
49
49
  *
50
50
  * ```ts
51
- * import { wrapWithMultiplayer } from '@ablo/sync-engine-internal/ai-sdk';
51
+ * import { wrapWithMultiplayer } from '@abloatai/ablo/ai-sdk';
52
52
  *
53
53
  * const wrappedModel = wrapWithMultiplayer({
54
54
  * model: anthropic('claude-opus-4-7'),
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@ablo/sync-engine-internal/ai-sdk` — multiplayer-with-AI as language model
2
+ * `@abloatai/ablo/ai-sdk` — multiplayer-with-AI as language model
3
3
  * middleware.
4
4
  *
5
5
  * Two cross-cutting middlewares for any AI SDK consumer using
@@ -24,7 +24,7 @@
24
24
  * import {
25
25
  * intentBroadcastMiddleware,
26
26
  * coordinationContextMiddleware,
27
- * } from '@ablo/sync-engine-internal/ai-sdk';
27
+ * } from '@abloatai/ablo/ai-sdk';
28
28
  *
29
29
  * const target = { entityType: 'SlideDeck', entityId: 'deck-abc' };
30
30
  *
@@ -48,7 +48,7 @@
48
48
  * Or use the convenience composition for the common case:
49
49
  *
50
50
  * ```ts
51
- * import { wrapWithMultiplayer } from '@ablo/sync-engine-internal/ai-sdk';
51
+ * import { wrapWithMultiplayer } from '@abloatai/ablo/ai-sdk';
52
52
  *
53
53
  * const wrappedModel = wrapWithMultiplayer({
54
54
  * model: anthropic('claude-opus-4-7'),
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Open-source-clean: depends only on `@ai-sdk/provider` types and
13
13
  * the package's own `SyncAgent`. No app-specific assumptions —
14
- * Ablo's web app uses this, but so can any consumer of `@ablo/sync-engine`.
14
+ * Ablo's web app uses this, but so can any consumer of `@abloatai/ablo`.
15
15
  *
16
16
  * Cost: one WS frame at stream start (`intent_begin`), one at end
17
17
  * (`intent_abandon`). No DB I/O, no extra LLM tokens.
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Open-source-clean: depends only on `@ai-sdk/provider` types and
13
13
  * the package's own `SyncAgent`. No app-specific assumptions —
14
- * Ablo's web app uses this, but so can any consumer of `@ablo/sync-engine`.
14
+ * Ablo's web app uses this, but so can any consumer of `@abloatai/ablo`.
15
15
  *
16
16
  * Cost: one WS frame at stream start (`intent_begin`), one at end
17
17
  * (`intent_abandon`). No DB I/O, no extra LLM tokens.
@@ -63,7 +63,7 @@ export declare function resolveIdentity(options: ResolveIdentityRequest): Promis
63
63
  /**
64
64
  * Capability-token refresh scheduler.
65
65
  *
66
- * Long-lived `@ablo/sync-engine` clients hold a server-issued capability
66
+ * Long-lived `@abloatai/ablo` clients hold a server-issued capability
67
67
  * token whose TTL (1h default) is shorter than typical browser sessions.
68
68
  * Without proactive refresh, the WebSocket would either be force-closed
69
69
  * by the server at expiry (code 1008) or fail its next reconnect with
@@ -5,7 +5,7 @@
5
5
  * bootstrap, offline queue, DI adapters) behind a single function call.
6
6
  *
7
7
  * Usage:
8
- * import { Ablo } from '@ablo/sync-engine/client';
8
+ * import { Ablo } from '@abloatai/ablo/client';
9
9
  * import { schema } from './schema';
10
10
  *
11
11
  * const sync = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
@@ -21,7 +21,7 @@ import { ObjectPool } from '../ObjectPool.js';
21
21
  import type { SyncStoreContract } from '../react/context.js';
22
22
  import type { SyncWebSocket } from '../sync/SyncWebSocket.js';
23
23
  import { type SyncStatus } from '../BaseSyncedStore.js';
24
- import type { IntentStream, PresenceStream, Snapshot } from '../types/streams.js';
24
+ import type { IntentStream, IntentWaitOptions, PresenceStream, Snapshot } from '../types/streams.js';
25
25
  import type { ParticipantManager } from '../sync/participants.js';
26
26
  import type { ActiveIntent, Duration, TargetRange } from '../types/streams.js';
27
27
  import { type AbloApi, type AbloApiClientOptions, type AbloApiIntents } from './ApiClient.js';
@@ -48,7 +48,7 @@ export interface Turn {
48
48
  * `ApiKeySetter` exactly so any rotation pattern that works with
49
49
  * `@anthropic-ai/sdk` works here.
50
50
  *
51
- * Re-exported from `./auth` so existing import paths (`@ablo/sync-engine`)
51
+ * Re-exported from `./auth` so existing import paths (`@abloatai/ablo`)
52
52
  * keep resolving; the canonical definition lives there alongside the
53
53
  * resolvers that consume it.
54
54
  */
@@ -69,8 +69,7 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
69
69
  */
70
70
  authToken?: string | null | undefined;
71
71
  /**
72
- * Override the Ablo API base URL. Defaults to hosted production and reads
73
- * `process.env['ABLO_BASE_URL']` if unset.
72
+ * Override the Ablo API base URL. Defaults to hosted production.
74
73
  */
75
74
  baseURL?: string | null | undefined;
76
75
  /** Per-request timeout in milliseconds. */
@@ -118,7 +117,7 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
118
117
  apiKey?: string | ApiKeySetter | null | undefined;
119
118
  /**
120
119
  * Bearer auth token. Sent as `Authorization: Bearer <token>` on
121
- * every request. Defaults to `process.env['ABLO_AUTH_TOKEN']`.
120
+ * every request.
122
121
  *
123
122
  * Use this for self-hosted deployments where your auth layer mints
124
123
  * cap tokens directly. Hosted-cloud consumers pass `apiKey` instead;
@@ -129,7 +128,6 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
129
128
  * Override the default base URL. Defaults to
130
129
  * `wss://mesh.ablo.finance` for hosted production; pass an explicit
131
130
  * URL for self-hosted or staging (e.g. `wss://mesh-staging.ablo.finance`).
132
- * Reads `process.env['ABLO_BASE_URL']` if unset.
133
131
  */
134
132
  baseURL?: string | null | undefined;
135
133
  /**
@@ -327,7 +325,7 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
327
325
  * deprecated aliases for one release cycle so consumers can migrate
328
326
  * without a flag day.
329
327
  */
330
- export type { ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ModelEditHandle, ModelEditOptions, ModelOperations, } from './createModelProxy.js';
328
+ export type { ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ModelIntentAcquireOptions, ModelIntentHandle, ModelOperations, } from './createModelProxy.js';
331
329
  import type { ModelOperations } from './createModelProxy.js';
332
330
  export type ResourceOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
333
331
  export type CommitWait = 'queued' | 'confirmed';
@@ -366,11 +364,7 @@ export interface BusyOptions {
366
364
  /** HTTP API polling interval while waiting. WebSocket clients ignore it. */
367
365
  readonly busyPollInterval?: number;
368
366
  }
369
- export interface IntentWaitOptions {
370
- readonly timeout?: number;
371
- readonly pollInterval?: number;
372
- readonly signal?: AbortSignal;
373
- }
367
+ export type { IntentWaitOptions } from '../types/streams.js';
374
368
  export interface ResourceReadOptions extends BusyOptions {
375
369
  }
376
370
  export interface IntentCreateOptions {
@@ -816,7 +810,7 @@ export declare namespace Ablo {
816
810
  type Event = import('../source/index.js').SourceEvent;
817
811
  type EventsResult = import('../source/index.js').SourceEventsResult;
818
812
  type Scope = import('../source/index.js').SourceScope;
819
- type Secret = import('../source/index.js').SourceSecret;
813
+ type ApiKey = import('../source/index.js').SourceApiKey;
820
814
  type Options<S extends _SchemaTypes.SchemaRecord = _SchemaTypes.SchemaRecord, TAuth = unknown> = import('../source/index.js').AbloSourceOptions<S, TAuth>;
821
815
  type ModelHandlers<Row, CreateInput, TAuth = unknown> = import('../source/index.js').SourceModelHandlers<Row, CreateInput, TAuth>;
822
816
  type SignatureVerificationResult = import('../source/index.js').SourceSignatureVerificationResult;
@@ -5,7 +5,7 @@
5
5
  * bootstrap, offline queue, DI adapters) behind a single function call.
6
6
  *
7
7
  * Usage:
8
- * import { Ablo } from '@ablo/sync-engine/client';
8
+ * import { Ablo } from '@abloatai/ablo/client';
9
9
  * import { schema } from './schema';
10
10
  *
11
11
  * const sync = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
@@ -1147,6 +1147,37 @@ export function Ablo(options) {
1147
1147
  getLastSyncId: () => store.getSyncWebSocket()?.getLastSyncId() ?? store.lastSyncId ?? 0,
1148
1148
  entities: { [modelKey]: id },
1149
1149
  }),
1150
+ observe: (target) => {
1151
+ // The live intent stream only tracks *open* (active) claims;
1152
+ // terminal states (committed / expired / canceled) drop out of
1153
+ // the list entirely — exactly the ephemeral coordination model.
1154
+ // So a present entry is, by definition, `status: 'active'`.
1155
+ const held = publicIntents.list({
1156
+ resource: target.resource,
1157
+ id: target.id,
1158
+ })[0];
1159
+ if (!held)
1160
+ return null;
1161
+ return {
1162
+ object: 'intent',
1163
+ id: held.id,
1164
+ status: 'active',
1165
+ target: {
1166
+ type: held.target.resource,
1167
+ id: held.target.id,
1168
+ ...(held.target.path ? { path: held.target.path } : {}),
1169
+ ...(held.target.range ? { range: held.target.range } : {}),
1170
+ ...(held.target.field ? { field: held.target.field } : {}),
1171
+ ...(held.target.meta ? { meta: held.target.meta } : {}),
1172
+ },
1173
+ action: held.action,
1174
+ heldBy: held.actor,
1175
+ participantKind: held.participantKind,
1176
+ expiresAt: held.expiresAt,
1177
+ };
1178
+ },
1179
+ waitFor: (target, waitOptions) => publicIntents.waitFor({ resource: target.resource, id: target.id }, waitOptions),
1180
+ selfParticipantId: participantId,
1150
1181
  });
1151
1182
  }
1152
1183
  const commits = {
@@ -7,9 +7,9 @@
7
7
  * with an actionable message — so the constructor reads as a
8
8
  * sequence of named decisions rather than a stream of `??`-chains.
9
9
  *
10
- * Precedence for every resolver: explicit option → environment
11
- * variable built-in default. The same shape Anthropic, OpenAI,
12
- * and Stripe SDKs use.
10
+ * Customer-facing env surface is intentionally small: `ABLO_API_KEY`
11
+ * is the only environment fallback. Other routing/auth overrides are
12
+ * explicit options so generated apps do not accrete hidden env knobs.
13
13
  */
14
14
  /**
15
15
  * Async callable that resolves to a fresh API key. Mirrors the shape
@@ -7,9 +7,9 @@
7
7
  * with an actionable message — so the constructor reads as a
8
8
  * sequence of named decisions rather than a stream of `??`-chains.
9
9
  *
10
- * Precedence for every resolver: explicit option → environment
11
- * variable built-in default. The same shape Anthropic, OpenAI,
12
- * and Stripe SDKs use.
10
+ * Customer-facing env surface is intentionally small: `ABLO_API_KEY`
11
+ * is the only environment fallback. Other routing/auth overrides are
12
+ * explicit options so generated apps do not accrete hidden env knobs.
13
13
  */
14
14
  import { AbloAuthenticationError } from '../errors.js';
15
15
  /**
@@ -25,11 +25,11 @@ export function resolveApiKey(input) {
25
25
  return input.options.apiKey ?? input.env.ABLO_API_KEY ?? null;
26
26
  }
27
27
  export function resolveAuthToken(input) {
28
- return input.options.authToken ?? input.env.ABLO_AUTH_TOKEN ?? null;
28
+ return input.options.authToken ?? null;
29
29
  }
30
30
  export const ABLO_DEFAULT_BASE_URL = 'wss://mesh.ablo.finance';
31
31
  export function resolveBaseURL(input) {
32
- return (input.options.baseURL ?? input.env.ABLO_BASE_URL ?? ABLO_DEFAULT_BASE_URL);
32
+ return input.options.baseURL ?? ABLO_DEFAULT_BASE_URL;
33
33
  }
34
34
  /**
35
35
  * Browser guard — apiKey is server-side-only by default. Same check