@abloatai/ablo 0.10.0 → 0.11.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.
- package/CHANGELOG.md +16 -0
- package/README.md +2 -1
- package/dist/BaseSyncedStore.d.ts +75 -0
- package/dist/BaseSyncedStore.js +193 -8
- package/dist/Database.d.ts +10 -2
- package/dist/Database.js +15 -1
- package/dist/SyncClient.d.ts +12 -1
- package/dist/SyncClient.js +110 -26
- package/dist/agent/Agent.d.ts +9 -9
- package/dist/agent/Agent.js +16 -16
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.js +1 -1
- package/dist/ai-sdk/{intent-broadcast.d.ts → claim-broadcast.d.ts} +10 -10
- package/dist/ai-sdk/{intent-broadcast.js → claim-broadcast.js} +6 -6
- package/dist/ai-sdk/coordination-context.d.ts +9 -9
- package/dist/ai-sdk/coordination-context.js +8 -8
- package/dist/ai-sdk/index.d.ts +1 -1
- package/dist/ai-sdk/index.js +1 -1
- package/dist/ai-sdk/wrap.d.ts +4 -4
- package/dist/ai-sdk/wrap.js +4 -4
- package/dist/api/index.d.ts +2 -2
- package/dist/cli.cjs +254 -48
- package/dist/client/Ablo.d.ts +30 -63
- package/dist/client/Ablo.js +108 -102
- package/dist/client/ApiClient.d.ts +6 -5
- package/dist/client/ApiClient.js +83 -62
- package/dist/client/createModelProxy.d.ts +16 -54
- package/dist/client/createModelProxy.js +44 -16
- package/dist/client/httpClient.d.ts +2 -0
- package/dist/client/httpClient.js +1 -1
- package/dist/client/index.d.ts +3 -3
- package/dist/client/writeOptionsSchema.d.ts +4 -4
- package/dist/client/writeOptionsSchema.js +4 -4
- package/dist/coordination/schema.d.ts +249 -38
- package/dist/coordination/schema.js +172 -39
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +4 -4
- package/dist/errorCodes.d.ts +9 -9
- package/dist/errorCodes.js +15 -15
- package/dist/errors.d.ts +51 -2
- package/dist/errors.js +94 -5
- package/dist/interfaces/index.d.ts +8 -4
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/types.d.ts +13 -13
- package/dist/policy/types.js +8 -8
- package/dist/react/AbloProvider.d.ts +51 -4
- package/dist/react/AbloProvider.js +95 -11
- package/dist/react/context.d.ts +26 -9
- package/dist/react/context.js +2 -2
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +4 -4
- package/dist/react/useAblo.js +5 -5
- package/dist/react/{useIntent.d.ts → useClaim.d.ts} +9 -9
- package/dist/react/useClaim.js +42 -0
- package/dist/schema/index.js +1 -1
- package/dist/schema/sugar.d.ts +3 -3
- package/dist/schema/sugar.js +3 -3
- package/dist/schema/sync-delta-wire.d.ts +8 -8
- package/dist/server/commit.d.ts +2 -2
- package/dist/sync/AreaOfInterestManager.d.ts +162 -0
- package/dist/sync/AreaOfInterestManager.js +233 -0
- package/dist/sync/BootstrapHelper.d.ts +9 -1
- package/dist/sync/BootstrapHelper.js +15 -5
- package/dist/sync/NetworkProbe.d.ts +1 -1
- package/dist/sync/NetworkProbe.js +1 -1
- package/dist/sync/SyncWebSocket.d.ts +59 -25
- package/dist/sync/SyncWebSocket.js +123 -26
- package/dist/sync/awaitClaimGrant.d.ts +40 -0
- package/dist/sync/awaitClaimGrant.js +86 -0
- package/dist/sync/createClaimStream.d.ts +34 -0
- package/dist/sync/{createIntentStream.js → createClaimStream.js} +92 -81
- package/dist/sync/createPresenceStream.js +3 -2
- package/dist/sync/participants.d.ts +10 -10
- package/dist/sync/participants.js +17 -10
- package/dist/sync/schemas.d.ts +8 -8
- package/dist/transactions/TransactionQueue.d.ts +12 -0
- package/dist/transactions/TransactionQueue.js +126 -8
- package/dist/types/global.d.ts +10 -10
- package/dist/types/global.js +3 -3
- package/dist/types/index.d.ts +9 -7
- package/dist/types/index.js +2 -2
- package/dist/types/streams.d.ts +114 -98
- package/dist/types/streams.js +1 -1
- package/dist/utils/asyncIterator.d.ts +1 -1
- package/dist/utils/asyncIterator.js +1 -1
- package/dist/wire/frames.d.ts +2 -2
- package/docs/migration.md +52 -0
- package/package.json +3 -2
- package/dist/react/useIntent.js +0 -42
- package/dist/sync/awaitIntentGrant.d.ts +0 -40
- package/dist/sync/awaitIntentGrant.js +0 -62
- package/dist/sync/createIntentStream.d.ts +0 -34
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { syncGroupInputSchema } from '../schema/roles.js';
|
|
2
3
|
/**
|
|
3
4
|
* Coordination wire schema — the ONE canonical source for the three layers
|
|
4
5
|
* that keep humans and agents from clobbering each other on a shared row.
|
|
@@ -6,7 +7,7 @@ import { z } from 'zod';
|
|
|
6
7
|
* one decision") for the conceptual model. The layers, outer-to-inner:
|
|
7
8
|
*
|
|
8
9
|
* 1. PRESENCE (observation) — who is working where; NEVER enforces.
|
|
9
|
-
* 2. PESSIMISTIC (claims/leases) — `
|
|
10
|
+
* 2. PESSIMISTIC (claims/leases) — `claim_begin`/`claim_abandon`;
|
|
10
11
|
* mutual exclusion between participants.
|
|
11
12
|
* 3. OPTIMISTIC (stale-context) — `readAt` + `onStale` write-guard;
|
|
12
13
|
* last-writer-wins lost-update detection.
|
|
@@ -14,8 +15,8 @@ import { z } from 'zod';
|
|
|
14
15
|
* Both the SDK (`types/streams.ts`) and the sync-server (`hub/types.ts`,
|
|
15
16
|
* `presence/*`) derive their TypeScript types from THESE schemas via
|
|
16
17
|
* `z.infer`, instead of re-declaring overlapping shapes. That collapses the
|
|
17
|
-
* field drift this surface accreted — e.g. the SDK's
|
|
18
|
-
* `status`/`error`, `onStale` declared 5×, `
|
|
18
|
+
* field drift this surface accreted — e.g. the SDK's claim view dropping
|
|
19
|
+
* `status`/`error`, `onStale` declared 5×, `ClaimStatus` declared 2× — into
|
|
19
20
|
* a single definition that the wire ingest can also validate at runtime.
|
|
20
21
|
*/
|
|
21
22
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -30,7 +31,39 @@ export const targetRangeSchema = z.object({
|
|
|
30
31
|
});
|
|
31
32
|
export const participantKindSchema = z.enum(['user', 'agent', 'system']);
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
+
* Wire-tolerant participant kind for INGEST. The claim/presence streams
|
|
35
|
+
* historically labelled a non-agent participant `'human'`, while the
|
|
36
|
+
* capability/identity/lease surfaces all say `'user'` — the same participant,
|
|
37
|
+
* two dialects. This normalizes the legacy `'human'` to the canonical `'user'`
|
|
38
|
+
* on read so every consumer switches on ONE vocabulary. Producers emit
|
|
39
|
+
* canonical {@link participantKindSchema} values; this only forgives an older
|
|
40
|
+
* frame still carrying `'human'`. Additive — never widens the output union.
|
|
41
|
+
*/
|
|
42
|
+
export const wireParticipantKindSchema = z.preprocess((value) => (value === 'human' ? 'user' : value), participantKindSchema);
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a peer's kind from an inbound presence/claim frame. Prefers the
|
|
45
|
+
* server-stamped `participantKind` (normalized via
|
|
46
|
+
* {@link wireParticipantKindSchema}); frames from servers that predate the
|
|
47
|
+
* field fall back to the lossy `isAgent` boolean — which can say 'agent' or
|
|
48
|
+
* 'user' but never 'system' (the flatten this field exists to remove).
|
|
49
|
+
*/
|
|
50
|
+
export function participantKindFromWire(wireKind, isAgent) {
|
|
51
|
+
const parsed = wireParticipantKindSchema.safeParse(wireKind);
|
|
52
|
+
if (parsed.success)
|
|
53
|
+
return parsed.data;
|
|
54
|
+
return isAgent ? 'agent' : 'user';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The peer-visible explanation a claim/claim carries, lifted from its opaque
|
|
58
|
+
* `meta.description`. One place for the `typeof meta?.description === 'string'`
|
|
59
|
+
* unfold that the claim/claim/presence surfaces each re-implemented — callers
|
|
60
|
+
* with an explicit `description` field still prefer it (`explicit ?? fromMeta`).
|
|
61
|
+
*/
|
|
62
|
+
export function descriptionFromMeta(meta) {
|
|
63
|
+
return typeof meta?.description === 'string' ? meta.description : undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What a claim / claim / activity points at. The common locator shared by
|
|
34
67
|
* all three layers — an entity, optionally narrowed to a path, range, or
|
|
35
68
|
* field, with opaque app metadata.
|
|
36
69
|
*/
|
|
@@ -64,34 +97,58 @@ export const writeGuardSchema = z.object({
|
|
|
64
97
|
bypass: z.boolean().optional(),
|
|
65
98
|
});
|
|
66
99
|
// ─────────────────────────────────────────────────────────────────────────
|
|
67
|
-
// Layer 2 — PESSIMISTIC claim /
|
|
100
|
+
// Layer 2 — PESSIMISTIC claim / claim-lease
|
|
68
101
|
// ─────────────────────────────────────────────────────────────────────────
|
|
69
102
|
/**
|
|
70
|
-
* Lifecycle of an
|
|
103
|
+
* Lifecycle of an claim — the Stripe `PaymentIntent.status` shape. Absent on
|
|
71
104
|
* the wire ⇒ `'active'` (additive back-compat). The server stamps `'active'`
|
|
72
|
-
* on `
|
|
105
|
+
* on `claim_begin` and emits a single terminal frame (`committed` /
|
|
73
106
|
* `canceled` / `expired`) as the claim ends, so contenders learn *how* it
|
|
74
107
|
* resolved, not merely that it vanished.
|
|
75
108
|
*/
|
|
76
|
-
export const
|
|
109
|
+
export const claimStatusSchema = z.enum([
|
|
77
110
|
'active',
|
|
78
111
|
'committed',
|
|
79
112
|
'expired',
|
|
80
113
|
'canceled',
|
|
81
114
|
]);
|
|
115
|
+
const wireClaimBaseSchema = targetRefSchema.extend({
|
|
116
|
+
claimId: z.string(),
|
|
117
|
+
/** Verb the agent expects: 'update' | 'create' | 'editing' | 'reviewing' … */
|
|
118
|
+
action: z.string(),
|
|
119
|
+
/** Server-stamped declaration time (epoch ms). */
|
|
120
|
+
declaredAt: z.number(),
|
|
121
|
+
/** Server-computed TTL deadline (epoch ms). Readers treat as advisory. */
|
|
122
|
+
expiresAt: z.number(),
|
|
123
|
+
status: claimStatusSchema.optional(),
|
|
124
|
+
});
|
|
125
|
+
export const wireClaimSummarySchema = wireClaimBaseSchema.pick({
|
|
126
|
+
claimId: true,
|
|
127
|
+
action: true,
|
|
128
|
+
declaredAt: true,
|
|
129
|
+
expiresAt: true,
|
|
130
|
+
entityType: true,
|
|
131
|
+
entityId: true,
|
|
132
|
+
field: true,
|
|
133
|
+
meta: true,
|
|
134
|
+
});
|
|
82
135
|
/** Why a claim ended in a non-success terminal state. */
|
|
83
|
-
export const
|
|
136
|
+
export const claimErrorSchema = z.object({
|
|
84
137
|
code: z.string(),
|
|
85
138
|
message: z.string().optional(),
|
|
86
139
|
/** Participant already holding the target (conflict rejections). */
|
|
87
140
|
heldBy: z.string().optional(),
|
|
88
|
-
|
|
141
|
+
heldByClaimId: z.string().optional(),
|
|
89
142
|
heldByExpiresAt: z.number().optional(),
|
|
143
|
+
/** Rich holder context for conflict rejections. Additive: older frames omit it. */
|
|
144
|
+
heldByClaim: wireClaimSummarySchema.optional(),
|
|
145
|
+
/** Optional conflict-policy explanation. Additive: older frames omit it. */
|
|
146
|
+
policyReason: z.string().optional(),
|
|
90
147
|
});
|
|
91
148
|
/**
|
|
92
|
-
* A declared pending-mutation
|
|
93
|
-
* `
|
|
94
|
-
* `action`, and a chosen `
|
|
149
|
+
* A declared pending-mutation claim — the unit broadcast in presence
|
|
150
|
+
* `activeClaims`. Clients supply the descriptive `targetRef` fields, an
|
|
151
|
+
* `action`, and a chosen `claimId`; the SERVER stamps `declaredAt` /
|
|
95
152
|
* `expiresAt` and may set `status` / `error`.
|
|
96
153
|
*
|
|
97
154
|
* `status` and `error` are OPTIONAL: this single shape serves both the
|
|
@@ -100,61 +157,132 @@ export const intentErrorSchema = z.object({
|
|
|
100
157
|
* was used, so the two prior copies collapse into this one without breaking
|
|
101
158
|
* SDK consumers.
|
|
102
159
|
*/
|
|
103
|
-
export const
|
|
104
|
-
|
|
105
|
-
|
|
160
|
+
export const wireClaimSchema = wireClaimBaseSchema.extend({
|
|
161
|
+
error: claimErrorSchema.optional(),
|
|
162
|
+
});
|
|
163
|
+
export const claimRejectionSchema = z.object({
|
|
164
|
+
claimId: z.string(),
|
|
165
|
+
reason: z.string(),
|
|
166
|
+
target: targetRefSchema.optional(),
|
|
167
|
+
heldBy: z.string().optional(),
|
|
168
|
+
heldByClaimId: z.string().optional(),
|
|
169
|
+
heldByExpiresAt: z.number().optional(),
|
|
170
|
+
heldByClaim: wireClaimSummarySchema.optional(),
|
|
171
|
+
policyReason: z.string().optional(),
|
|
172
|
+
});
|
|
173
|
+
/**
|
|
174
|
+
* What a {@link ModelClaim} points at — the SDK-facing target locator, keyed by
|
|
175
|
+
* `model`/`id` (the `ablo.<model>` vocabulary) rather than the wire's
|
|
176
|
+
* `entityType`/`entityId`. Structurally the public `ModelTarget`.
|
|
177
|
+
*/
|
|
178
|
+
export const modelTargetSchema = z
|
|
179
|
+
.object({
|
|
180
|
+
model: z.string(),
|
|
181
|
+
id: z.string(),
|
|
182
|
+
path: z.string().optional(),
|
|
183
|
+
range: targetRangeSchema.optional(),
|
|
184
|
+
field: z.string().optional(),
|
|
185
|
+
meta: z.record(z.string(), z.unknown()).optional(),
|
|
186
|
+
})
|
|
187
|
+
.readonly();
|
|
188
|
+
/**
|
|
189
|
+
* A claim as surfaced to SDK callers and the HTTP claim routes
|
|
190
|
+
* (`ablo.<model>.claim.state`, `/v1/claims`) — the resolved, peer-readable
|
|
191
|
+
* view of one active or queued claim. The ONE canonical shape: the client
|
|
192
|
+
* (`Ablo.ts`) derives its `ModelClaim` from this, and the sync-server's two
|
|
193
|
+
* route copies adopt it once the engine dist is rebuilt.
|
|
194
|
+
*
|
|
195
|
+
* `expiresAt` is **epoch-ms** (a number) here — the same representation as the
|
|
196
|
+
* WS `WireClaim`, so there is ONE timestamp encoding across wire, SDK, HTTP,
|
|
197
|
+
* and errors (Stripe-style integer unix timestamps; no ISO string anywhere).
|
|
198
|
+
* `participantKind` ingests via {@link wireParticipantKindSchema} so a legacy
|
|
199
|
+
* `'human'` frame normalizes to `'user'`.
|
|
200
|
+
*/
|
|
201
|
+
export const modelClaimSchema = z
|
|
202
|
+
.object({
|
|
203
|
+
id: z.string(),
|
|
204
|
+
actor: z.string(),
|
|
205
|
+
participantKind: wireParticipantKindSchema,
|
|
106
206
|
action: z.string(),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
207
|
+
description: z.string().optional(),
|
|
208
|
+
field: z.string().optional(),
|
|
209
|
+
status: z.enum(['active', 'queued']).optional(),
|
|
210
|
+
position: z.number().optional(),
|
|
110
211
|
expiresAt: z.number(),
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
212
|
+
target: modelTargetSchema,
|
|
213
|
+
})
|
|
214
|
+
.readonly();
|
|
114
215
|
/**
|
|
115
|
-
* `
|
|
216
|
+
* `claim_begin` payload (client → server). The descriptive target + action,
|
|
116
217
|
* plus an optional duration hint and the opt-in fair-queue flag. The server
|
|
117
218
|
* stamps the lifecycle/timestamp fields, so they are NOT part of the inbound
|
|
118
219
|
* shape — this is exactly what the WS ingest validates.
|
|
119
220
|
*/
|
|
120
|
-
export const
|
|
121
|
-
|
|
221
|
+
export const claimBeginPayloadSchema = targetRefSchema.extend({
|
|
222
|
+
claimId: z.string(),
|
|
122
223
|
action: z.string(),
|
|
123
224
|
/** Hint for `expiresAt`; the server caps it. */
|
|
124
225
|
estimatedMs: z.number().optional(),
|
|
125
226
|
/**
|
|
126
227
|
* Opt into the fair wait queue: when the target is already held, the server
|
|
127
|
-
* enqueues this claim (FIFO) and replies `
|
|
128
|
-
* `
|
|
228
|
+
* enqueues this claim (FIFO) and replies `claim_queued` → later
|
|
229
|
+
* `claim_granted`, instead of `claim_rejected`. Clients that set this MUST
|
|
129
230
|
* handle the grant.
|
|
130
231
|
*/
|
|
131
232
|
queue: z.boolean().optional(),
|
|
132
233
|
});
|
|
133
234
|
/**
|
|
134
|
-
* `
|
|
135
|
-
* carried so the server can DEQUEUE a still-*waiting* (not held)
|
|
136
|
-
* the FIFO line — the held-
|
|
235
|
+
* `claim_abandon` payload (client → server). `entityType`/`entityId` are
|
|
236
|
+
* carried so the server can DEQUEUE a still-*waiting* (not held) claim from
|
|
237
|
+
* the FIFO line — the held-claim path needs only `claimId`. (The previous
|
|
137
238
|
* wire type omitted these two even though the handler reads them; the schema
|
|
138
239
|
* documents what the code actually uses.)
|
|
139
240
|
*/
|
|
140
|
-
export const
|
|
141
|
-
|
|
241
|
+
export const claimAbandonPayloadSchema = z.object({
|
|
242
|
+
claimId: z.string(),
|
|
142
243
|
entityType: z.string().optional(),
|
|
143
244
|
entityId: z.string().optional(),
|
|
144
245
|
});
|
|
145
246
|
/**
|
|
146
|
-
* `
|
|
247
|
+
* `claim_reorder` payload (client → server). A privileged participant (e.g. a
|
|
147
248
|
* supervisor over its sub-agents) re-ranks the FIFO wait queue for an entity:
|
|
148
|
-
* `order` lists waiters by `heldBy`+`
|
|
249
|
+
* `order` lists waiters by `heldBy`+`claimId` in the desired priority. Waiters
|
|
149
250
|
* not listed keep their relative order behind the listed ones. The server gates
|
|
150
|
-
* who may call this; an unauthorized sender is dropped. Unlike `
|
|
251
|
+
* who may call this; an unauthorized sender is dropped. Unlike `claim_abandon`
|
|
151
252
|
* (acts on the caller's own entry), reorder acts on OTHER participants' queue
|
|
152
253
|
* positions — hence the authorization gate.
|
|
153
254
|
*/
|
|
154
|
-
export const
|
|
255
|
+
export const claimReorderPayloadSchema = z.object({
|
|
155
256
|
entityType: z.string(),
|
|
156
257
|
entityId: z.string(),
|
|
157
|
-
order: z.array(z.object({ heldBy: z.string(),
|
|
258
|
+
order: z.array(z.object({ heldBy: z.string(), claimId: z.string() })),
|
|
259
|
+
});
|
|
260
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
261
|
+
// Read interest — area-of-interest navigation (update_subscription)
|
|
262
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
263
|
+
/**
|
|
264
|
+
* `update_subscription` payload (client → server). Replaces the connection's
|
|
265
|
+
* connection-level read interest with the COMPLETE set of sync groups — the
|
|
266
|
+
* READ counterpart to a claim (no write-claim, no TTL). Each entry is a
|
|
267
|
+
* {@link syncGroupInputSchema} (`'default'` or a branded `kind:id`), so a
|
|
268
|
+
* malformed group is rejected at ingest instead of being silently indexed.
|
|
269
|
+
* This is untrusted client input, so the element type is strict.
|
|
270
|
+
*/
|
|
271
|
+
export const updateSubscriptionPayloadSchema = z.object({
|
|
272
|
+
syncGroups: z.array(syncGroupInputSchema),
|
|
273
|
+
});
|
|
274
|
+
/**
|
|
275
|
+
* `subscription_ack` payload (server → client). Echoes the connection's
|
|
276
|
+
* effective read set after the update (unchanged on rejection — the update is
|
|
277
|
+
* atomic). `error` is present iff `success` is false (e.g. a scoped key
|
|
278
|
+
* requesting a group outside its grant). `syncGroups` is lenient
|
|
279
|
+
* (`z.string()`) here, not branded: it is the server's own echo for display,
|
|
280
|
+
* not untrusted input, and includes base anchors like `org:<id>`.
|
|
281
|
+
*/
|
|
282
|
+
export const subscriptionAckPayloadSchema = z.object({
|
|
283
|
+
success: z.boolean(),
|
|
284
|
+
syncGroups: z.array(z.string()),
|
|
285
|
+
error: z.object({ code: z.string(), message: z.string() }).optional(),
|
|
158
286
|
});
|
|
159
287
|
// ─────────────────────────────────────────────────────────────────────────
|
|
160
288
|
// Commit operation — carries the optimistic write-guard (Layer 3)
|
|
@@ -191,7 +319,7 @@ export const presenceActivitySchema = targetRefSchema.extend({
|
|
|
191
319
|
});
|
|
192
320
|
/**
|
|
193
321
|
* Full `presence_update` frame as the server broadcasts it. The activity +
|
|
194
|
-
* `
|
|
322
|
+
* `activeClaims` are the observation surface for the other two layers —
|
|
195
323
|
* rendered, never acted on as enforcement.
|
|
196
324
|
*/
|
|
197
325
|
export const presenceUpdateFrameSchema = z.object({
|
|
@@ -204,6 +332,11 @@ export const presenceUpdateFrameSchema = z.object({
|
|
|
204
332
|
customStatus: z.string().optional(),
|
|
205
333
|
activity: presenceActivitySchema.optional(),
|
|
206
334
|
isAgent: z.boolean().optional(),
|
|
207
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Server-stamped canonical kind. Additive — older servers omit it and
|
|
337
|
+
* readers fall back to `isAgent` (see {@link participantKindFromWire}).
|
|
338
|
+
*/
|
|
339
|
+
participantKind: wireParticipantKindSchema.optional(),
|
|
340
|
+
activeClaims: z.array(wireClaimSchema).optional(),
|
|
208
341
|
delegatedFrom: z.string().nullish(),
|
|
209
342
|
});
|
package/dist/core/index.d.ts
CHANGED
|
@@ -23,6 +23,6 @@ export { computeFKDepthPriority, type InternalAbloOptions } from '../client/Ablo
|
|
|
23
23
|
export type { SyncLogger, SyncObservabilityProvider, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, } from '../interfaces/index.js';
|
|
24
24
|
export { SyncWebSocket, type SyncDelta, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
|
|
25
25
|
export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
26
|
+
export { createClaimStream, type AttachableClaimStream, type ClaimStreamConfig, } from '../sync/createClaimStream.js';
|
|
27
|
+
export { awaitClaimGrant, type GrantTransport, } from '../sync/awaitClaimGrant.js';
|
|
28
28
|
export { LoadStrategy } from '../types/index.js';
|
package/dist/core/index.js
CHANGED
|
@@ -30,13 +30,13 @@ export { computeFKDepthPriority } from '../client/Ablo.js';
|
|
|
30
30
|
// multi-agent demo harnesses.
|
|
31
31
|
export { SyncWebSocket, } from '../sync/SyncWebSocket.js';
|
|
32
32
|
export { BootstrapHelper } from '../sync/BootstrapHelper.js';
|
|
33
|
-
//
|
|
33
|
+
// Claim coordination primitives (the lower-level pieces behind the
|
|
34
34
|
// consumer-facing `ablo.<model>.claim`). The stream factory builds the
|
|
35
|
-
// announce/await machinery on a SyncWebSocket; `
|
|
35
|
+
// announce/await machinery on a SyncWebSocket; `awaitClaimGrant` is the
|
|
36
36
|
// fair-queue grant coordinator. Exposed on /core for framework-level
|
|
37
37
|
// orchestration and e2e harnesses — NOT on the consumer `.` root.
|
|
38
|
-
export {
|
|
39
|
-
export {
|
|
38
|
+
export { createClaimStream, } from '../sync/createClaimStream.js';
|
|
39
|
+
export { awaitClaimGrant, } from '../sync/awaitClaimGrant.js';
|
|
40
40
|
// Schema/model load strategy enum — referenced by model registration in
|
|
41
41
|
// framework code.
|
|
42
42
|
export { LoadStrategy } from '../types/index.js';
|
package/dist/errorCodes.d.ts
CHANGED
|
@@ -37,9 +37,9 @@ import { z } from 'zod';
|
|
|
37
37
|
* code, a changed HTTP status, an envelope field. Emitted in `errors.json`
|
|
38
38
|
* and on the `Ablo-Version` response header so a consumer can detect drift.
|
|
39
39
|
*/
|
|
40
|
-
export declare const ERROR_CONTRACT_VERSION = "2026-06-
|
|
40
|
+
export declare const ERROR_CONTRACT_VERSION = "2026-06-13";
|
|
41
41
|
/** Coarse grouping for metrics dashboards and docs sectioning. */
|
|
42
|
-
export type ErrorCategory = 'auth' | 'permission' | 'capability' | 'claim' | 'conflict' | 'validation' | 'not_found' | 'tenant' | 'schema' | '
|
|
42
|
+
export type ErrorCategory = 'auth' | 'permission' | 'capability' | 'claim' | 'conflict' | 'validation' | 'not_found' | 'tenant' | 'schema' | 'claim' | 'bootstrap' | 'transport' | 'rate_limit' | 'server' | 'client';
|
|
43
43
|
/**
|
|
44
44
|
* The closed taxonomy of *how a failure recovers* — one rung above the raw
|
|
45
45
|
* `code`. Where `code` says **what** went wrong, `RecoveryClass` says **what
|
|
@@ -151,8 +151,8 @@ export declare const ERROR_CODES: {
|
|
|
151
151
|
readonly claim_conflict: ErrorCodeSpec;
|
|
152
152
|
readonly claim_lost: ErrorCodeSpec;
|
|
153
153
|
readonly entity_claimed: ErrorCodeSpec;
|
|
154
|
-
readonly intent_conflict: ErrorCodeSpec;
|
|
155
154
|
readonly malformed_claim: ErrorCodeSpec;
|
|
155
|
+
readonly malformed_subscription: ErrorCodeSpec;
|
|
156
156
|
readonly model_claimed: ErrorCodeSpec;
|
|
157
157
|
readonly model_claimed_timeout: ErrorCodeSpec;
|
|
158
158
|
readonly model_claim_not_configured: ErrorCodeSpec;
|
|
@@ -207,11 +207,11 @@ export declare const ERROR_CODES: {
|
|
|
207
207
|
readonly required_field_added: ErrorCodeSpec;
|
|
208
208
|
readonly enum_value_removed: ErrorCodeSpec;
|
|
209
209
|
readonly risky_cast: ErrorCodeSpec;
|
|
210
|
-
readonly
|
|
211
|
-
readonly
|
|
212
|
-
readonly
|
|
213
|
-
readonly
|
|
214
|
-
readonly
|
|
210
|
+
readonly claim_lease_unavailable: ErrorCodeSpec;
|
|
211
|
+
readonly claim_not_wired: ErrorCodeSpec;
|
|
212
|
+
readonly claim_queued: ErrorCodeSpec;
|
|
213
|
+
readonly claim_wait_aborted: ErrorCodeSpec;
|
|
214
|
+
readonly claim_wait_poll_interval_required: ErrorCodeSpec;
|
|
215
215
|
readonly grant_timeout: ErrorCodeSpec;
|
|
216
216
|
readonly slide_intent_missing_deck_id: ErrorCodeSpec;
|
|
217
217
|
readonly slide_intent_unknown_sibling: ErrorCodeSpec;
|
|
@@ -311,7 +311,7 @@ export declare const ERROR_CODES: {
|
|
|
311
311
|
readonly upload_items_required: ErrorCodeSpec;
|
|
312
312
|
readonly presigned_url_failed: ErrorCodeSpec;
|
|
313
313
|
readonly task_id_required: ErrorCodeSpec;
|
|
314
|
-
readonly
|
|
314
|
+
readonly claim_id_required: ErrorCodeSpec;
|
|
315
315
|
readonly commit_operation_action_required: ErrorCodeSpec;
|
|
316
316
|
readonly commit_operation_unsupported: ErrorCodeSpec;
|
|
317
317
|
readonly usage_invalid: ErrorCodeSpec;
|
package/dist/errorCodes.js
CHANGED
|
@@ -37,7 +37,7 @@ import { z } from 'zod';
|
|
|
37
37
|
* code, a changed HTTP status, an envelope field. Emitted in `errors.json`
|
|
38
38
|
* and on the `Ablo-Version` response header so a consumer can detect drift.
|
|
39
39
|
*/
|
|
40
|
-
export const ERROR_CONTRACT_VERSION = '2026-06-
|
|
40
|
+
export const ERROR_CONTRACT_VERSION = '2026-06-13';
|
|
41
41
|
/**
|
|
42
42
|
* The closed taxonomy of *how a failure recovers* — one rung above the raw
|
|
43
43
|
* `code`. Where `code` says **what** went wrong, `RecoveryClass` says **what
|
|
@@ -141,7 +141,7 @@ export const ERROR_CODES = {
|
|
|
141
141
|
byo_role_unreadable: wire('permission', 403, false, 'The direct Postgres connector role could not be introspected.'),
|
|
142
142
|
byo_tenant_tables_unforced_rls: wire('permission', 403, false, 'Tenant tables do not have RLS forced under the direct Postgres connector role.'),
|
|
143
143
|
byo_host_not_allowed: wire('permission', 403, false, 'The direct Postgres connector host resolves to a private, loopback, or link-local address and cannot be used.'),
|
|
144
|
-
// ── claim /
|
|
144
|
+
// ── claim / claim conflict (409) ──────────────────────────────────
|
|
145
145
|
// Held-claim rejections are NOT queue-retryable (gRPC FAILED_PRECONDITION /
|
|
146
146
|
// ABORTED semantics; Replicache/Zero SETTLE a rejected mutation — reject the
|
|
147
147
|
// caller, roll back the optimistic effect — instead of resending it).
|
|
@@ -154,8 +154,8 @@ export const ERROR_CODES = {
|
|
|
154
154
|
claim_conflict: wire('claim', 409, false, 'The target entity is claimed by another participant.'),
|
|
155
155
|
claim_lost: wire('claim', 409, false, 'A previously held claim was lost before the write applied.'),
|
|
156
156
|
entity_claimed: wire('claim', 409, false, 'The target entity is currently claimed; write was blocked.'),
|
|
157
|
-
intent_conflict: wire('claim', 409, false, 'An intent on the target conflicts with an active intent (server-internal alias of claim_conflict).'),
|
|
158
157
|
malformed_claim: wire('claim', 400, false, 'The claim payload was malformed.'),
|
|
158
|
+
malformed_subscription: wire('validation', 400, false, 'The update_subscription payload was malformed; expected { syncGroups: string[] }.'),
|
|
159
159
|
model_claimed: wire('claim', 409, false, 'The model instance is claimed by another participant.'),
|
|
160
160
|
model_claimed_timeout: wire('claim', 409, false, 'Timed out waiting for a model claim to clear.'),
|
|
161
161
|
model_claim_not_configured: client('claim', 'Claiming was requested on a model that has no claim configuration.'),
|
|
@@ -164,7 +164,7 @@ export const ERROR_CODES = {
|
|
|
164
164
|
idempotency_conflict: wire('conflict', 409, false, 'The same Idempotency-Key was reused with a different request body.'),
|
|
165
165
|
idempotency_key_too_long: wire('validation', 400, false, 'The supplied Idempotency-Key exceeds the maximum length.'),
|
|
166
166
|
// ── validation (400 / 422) ─────────────────────────────────────────
|
|
167
|
-
write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `
|
|
167
|
+
write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `claim`) failed validation against the write-options schema.'),
|
|
168
168
|
source_operation_id_required: client('validation', 'A data-source operation arrived without the entity `id` it targets.'),
|
|
169
169
|
source_adapter_misconfigured: client('validation', 'The data-source ORM adapter could not map a schema model onto the backing client (missing delegate or model).'),
|
|
170
170
|
source_event_invalid: client('validation', 'A data-source outbox event could not be built — the operation carries no entity id and none was supplied.'),
|
|
@@ -222,15 +222,15 @@ export const ERROR_CODES = {
|
|
|
222
222
|
required_field_added: client('schema', 'Migration adds a new required field.'),
|
|
223
223
|
enum_value_removed: client('schema', 'Migration removes an enum value (destructive classification).'),
|
|
224
224
|
risky_cast: client('schema', 'Migration would perform a risky column type cast.'),
|
|
225
|
-
// ──
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
grant_timeout: wire('
|
|
232
|
-
slide_intent_missing_deck_id: wire('
|
|
233
|
-
slide_intent_unknown_sibling: wire('
|
|
225
|
+
// ── claim / lease (409 / transport) ───────────────────────────────
|
|
226
|
+
claim_lease_unavailable: wire('claim', 503, true, 'The claim-lease coordination subsystem is unavailable; retry.'),
|
|
227
|
+
claim_not_wired: client('claim', 'Claim support was used but is not wired in this runtime.'),
|
|
228
|
+
claim_queued: wire('claim', 409, true, 'The claim was queued behind an active lease holder.'),
|
|
229
|
+
claim_wait_aborted: wire('claim', 409, true, 'Waiting for the claim lease was aborted.'),
|
|
230
|
+
claim_wait_poll_interval_required: client('claim', 'A poll interval is required when waiting on an claim.'),
|
|
231
|
+
grant_timeout: wire('claim', 504, true, 'Timed out waiting for a capability grant.'),
|
|
232
|
+
slide_intent_missing_deck_id: wire('claim', 400, false, 'A slide claim was missing its deck id.'),
|
|
233
|
+
slide_intent_unknown_sibling: wire('claim', 400, false, 'A slide claim referenced an unknown sibling slide.'),
|
|
234
234
|
// ── bootstrap (transport) ──────────────────────────────────────────
|
|
235
235
|
bootstrap_fetch_timeout: wire('bootstrap', 504, true, 'The bootstrap fetch timed out.'),
|
|
236
236
|
bootstrap_offline: wire('bootstrap', 503, true, 'Bootstrap could not run because the client is offline.'),
|
|
@@ -333,7 +333,7 @@ export const ERROR_CODES = {
|
|
|
333
333
|
upload_items_required: wire('validation', 400, false, 'The request must include a non-empty items array.'),
|
|
334
334
|
presigned_url_failed: wire('server', 500, true, 'Failed to generate a presigned upload URL.'),
|
|
335
335
|
task_id_required: wire('validation', 400, false, 'A task id is required for this request.'),
|
|
336
|
-
|
|
336
|
+
claim_id_required: wire('validation', 400, false, 'An claim id is required for this request.'),
|
|
337
337
|
commit_operation_action_required: wire('validation', 400, false, 'A commit operation is missing its `action`.'),
|
|
338
338
|
commit_operation_unsupported: wire('validation', 400, false, 'A commit operation used an unsupported `action`.'),
|
|
339
339
|
usage_invalid: wire('validation', 400, false, 'The usage request was invalid.'),
|
|
@@ -348,7 +348,7 @@ export const ERROR_CODES = {
|
|
|
348
348
|
parent_turn_foreign_agent: wire('permission', 403, false, 'The parent turn belongs to a different agent.'),
|
|
349
349
|
turn_not_found: wire('not_found', 404, false, 'The referenced turn does not exist.'),
|
|
350
350
|
turn_foreign_agent: wire('permission', 403, false, 'The turn belongs to a different agent.'),
|
|
351
|
-
invalid_intent: wire('validation', 400, false, 'The
|
|
351
|
+
invalid_intent: wire('validation', 400, false, 'The claim request was invalid.'),
|
|
352
352
|
schema_too_large: wire('validation', 413, false, 'The submitted schema exceeds the maximum size.'),
|
|
353
353
|
invalid_schema: wire('validation', 400, false, 'The submitted schema could not be parsed.'),
|
|
354
354
|
incompatible_change: wire('conflict', 409, false, 'The schema change is incompatible with the current schema.'),
|
package/dist/errors.d.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* Both work on every subclass.
|
|
20
20
|
*/
|
|
21
21
|
import type { ErrorCode } from './errorCodes.js';
|
|
22
|
+
import { type WireClaimSummary, type ModelClaim, type ModelTarget, type ParticipantKind } from './coordination/schema.js';
|
|
22
23
|
export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errorCodes.js';
|
|
23
24
|
export { ERROR_CODES, ERROR_CONTRACT_VERSION, errorCodeSpec, isRetryableCode, classifyRecovery, recoveryClassSchema, RECOVERY_CLASSES, } from './errorCodes.js';
|
|
24
25
|
/** Common shape for all errors thrown by this SDK. */
|
|
@@ -155,6 +156,32 @@ export declare class AbloStaleContextError extends AbloError {
|
|
|
155
156
|
}>;
|
|
156
157
|
});
|
|
157
158
|
}
|
|
159
|
+
export interface ClaimContext {
|
|
160
|
+
readonly id?: string;
|
|
161
|
+
readonly claimId?: string;
|
|
162
|
+
readonly actor?: string;
|
|
163
|
+
readonly participantKind?: ParticipantKind;
|
|
164
|
+
readonly action?: string;
|
|
165
|
+
readonly description?: string;
|
|
166
|
+
readonly field?: string;
|
|
167
|
+
readonly status?: string;
|
|
168
|
+
readonly position?: number;
|
|
169
|
+
/** Epoch-ms the claim expires. One timestamp encoding everywhere. */
|
|
170
|
+
readonly expiresAt?: number;
|
|
171
|
+
readonly declaredAt?: number;
|
|
172
|
+
readonly entityType?: string;
|
|
173
|
+
readonly entityId?: string;
|
|
174
|
+
readonly target?: Partial<ModelTarget>;
|
|
175
|
+
readonly meta?: Record<string, unknown>;
|
|
176
|
+
}
|
|
177
|
+
export type ClaimErrorClaim = WireClaimSummary | ClaimContext;
|
|
178
|
+
export declare function formatClaimedErrorMessage(args: {
|
|
179
|
+
readonly targetLabel: string;
|
|
180
|
+
readonly heldBy?: string;
|
|
181
|
+
readonly claim?: ClaimErrorClaim;
|
|
182
|
+
readonly policyReason?: string;
|
|
183
|
+
readonly fallback?: string;
|
|
184
|
+
}): string;
|
|
158
185
|
/**
|
|
159
186
|
* The target entity is currently claimed by another participant and the caller
|
|
160
187
|
* asked the SDK not to read/write through that claim.
|
|
@@ -164,15 +191,36 @@ export declare class AbloStaleContextError extends AbloError {
|
|
|
164
191
|
*/
|
|
165
192
|
export declare class AbloClaimedError extends AbloError {
|
|
166
193
|
readonly type: "AbloClaimedError";
|
|
167
|
-
readonly claims?: ReadonlyArray<
|
|
194
|
+
readonly claims?: ReadonlyArray<ClaimErrorClaim>;
|
|
168
195
|
constructor(message: string, options?: {
|
|
169
196
|
code?: ErrorCode;
|
|
170
197
|
httpStatus?: number;
|
|
171
198
|
requestId?: string;
|
|
172
199
|
cause?: unknown;
|
|
173
|
-
claims?: ReadonlyArray<
|
|
200
|
+
claims?: ReadonlyArray<ClaimErrorClaim>;
|
|
174
201
|
});
|
|
175
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* The `/`-joined human label for a claim target — `model/id/field`, dropping
|
|
205
|
+
* absent parts, falling back to `'target'`. The one place this join lived in
|
|
206
|
+
* three copies (client `Ablo`, HTTP `ApiClient`, `awaitClaimGrant`).
|
|
207
|
+
*/
|
|
208
|
+
export declare function claimTargetLabel(target: {
|
|
209
|
+
readonly model?: string;
|
|
210
|
+
readonly id?: string;
|
|
211
|
+
readonly field?: string;
|
|
212
|
+
}): string;
|
|
213
|
+
/**
|
|
214
|
+
* Build the {@link AbloClaimedError} for a contended `ablo.<model>` write — the
|
|
215
|
+
* single factory shared by the realtime client (`Ablo`) and the HTTP client
|
|
216
|
+
* (`ApiClient`), which carried byte-identical copies. The first claim is the
|
|
217
|
+
* holder whose metadata shapes the message.
|
|
218
|
+
*/
|
|
219
|
+
export declare function claimedError(target: {
|
|
220
|
+
readonly model?: string;
|
|
221
|
+
readonly id?: string;
|
|
222
|
+
readonly field?: string;
|
|
223
|
+
}, claims: readonly ModelClaim[], code: 'model_claimed' | 'model_claimed_timeout' | 'queue_too_deep'): AbloClaimedError;
|
|
176
224
|
/**
|
|
177
225
|
* Structured description of the capability an agent would need to
|
|
178
226
|
* satisfy a denied request. Mirrors the x402 `paymentRequirements`
|
|
@@ -304,6 +352,7 @@ export declare function errorFromWire(message: string, opts?: {
|
|
|
304
352
|
httpStatus?: number;
|
|
305
353
|
requestId?: string;
|
|
306
354
|
requiredCapability?: RequiredCapability;
|
|
355
|
+
claims?: ReadonlyArray<ClaimErrorClaim>;
|
|
307
356
|
}): AbloError;
|
|
308
357
|
/**
|
|
309
358
|
* Translate an HTTP response into the appropriate typed error.
|