@abloatai/ablo 0.18.0 → 0.19.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 +37 -0
- package/dist/ObjectPool.d.ts +14 -1
- package/dist/ObjectPool.js +8 -1
- package/dist/SyncClient.js +7 -1
- package/dist/SyncEngineContext.js +2 -0
- package/dist/ai-sdk/coordination-context.d.ts +2 -1
- package/dist/ai-sdk/coordination-context.js +3 -3
- package/dist/ai-sdk/index.d.ts +3 -4
- package/dist/ai-sdk/index.js +2 -3
- package/dist/ai-sdk/wrap.d.ts +13 -18
- package/dist/ai-sdk/wrap.js +2 -6
- package/dist/cli.cjs +253 -160
- package/dist/client/Ablo.d.ts +83 -22
- package/dist/client/Ablo.js +103 -30
- package/dist/client/ApiClient.d.ts +2 -2
- package/dist/client/ApiClient.js +27 -32
- package/dist/client/auth.d.ts +26 -0
- package/dist/client/auth.js +208 -0
- package/dist/client/createModelProxy.d.ts +16 -11
- package/dist/client/createModelProxy.js +15 -15
- package/dist/client/sessionMint.d.ts +10 -0
- package/dist/client/sessionMint.js +8 -1
- package/dist/coordination/schema.d.ts +10 -10
- package/dist/coordination/schema.js +7 -8
- package/dist/coordination/trace.d.ts +91 -0
- package/dist/coordination/trace.js +147 -0
- package/dist/errorCodes.d.ts +2 -0
- package/dist/errorCodes.js +2 -0
- package/dist/errors.d.ts +1 -2
- package/dist/errors.js +6 -9
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -0
- package/dist/interfaces/index.d.ts +45 -2
- package/dist/policy/types.d.ts +18 -9
- package/dist/policy/types.js +26 -16
- package/dist/react/AbloProvider.d.ts +2 -2
- package/dist/schema/ddl.d.ts +36 -0
- package/dist/schema/ddl.js +66 -0
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/sync/HydrationCoordinator.js +7 -3
- package/dist/sync/SyncWebSocket.d.ts +11 -2
- package/dist/sync/SyncWebSocket.js +67 -3
- package/dist/sync/createClaimStream.d.ts +9 -2
- package/dist/sync/createClaimStream.js +9 -7
- package/dist/sync/participants.d.ts +12 -11
- package/dist/sync/participants.js +5 -5
- package/dist/transactions/TransactionQueue.d.ts +1 -0
- package/dist/transactions/TransactionQueue.js +40 -3
- package/dist/types/streams.d.ts +57 -135
- package/docs/coordination.md +16 -0
- package/docs/debugging.md +194 -0
- package/docs/index.md +1 -0
- package/package.json +1 -1
- package/dist/ai-sdk/claim-broadcast.d.ts +0 -83
- package/dist/ai-sdk/claim-broadcast.js +0 -79
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **Claim observability — a `ClaimLog` you can print or assert on.** A new
|
|
8
|
+
`observability` provider hook lets you tap every claim event and stale-write
|
|
9
|
+
collision the client sees. Hand `new ClaimLog()` to `Ablo({ observability })`
|
|
10
|
+
and it collects an ordered, readable log — `formatClaim` / `formatConflict`
|
|
11
|
+
render one line per event, and `collisions()` returns the conflicts for eval
|
|
12
|
+
assertions. New exports: `ClaimLog`, `formatClaim`, `formatConflict`,
|
|
13
|
+
`noopObservability`, and the types `ClaimLogEntry`, `ClaimEvent`,
|
|
14
|
+
`ConflictEvent`, `SyncObservabilityProvider`. Spread `noopObservability` to
|
|
15
|
+
override only the hooks you care about.
|
|
16
|
+
|
|
17
|
+
**AWS-shaped CLI credential store + `ablo config`.** Local CLI state is now split
|
|
18
|
+
into two files, matching `~/.aws/config` vs `~/.aws/credentials`: `config.json`
|
|
19
|
+
holds non-secret settings (active environment + active project) and is safe to
|
|
20
|
+
print or let an agent read; `credentials.json` holds the keys (0600, never
|
|
21
|
+
printed), keyed by project profile then environment. Per-project profiles follow
|
|
22
|
+
Stripe's model — `ablo projects use <slug>` selects the active profile, and a
|
|
23
|
+
key's project is fixed at mint so selecting a project never re-scopes an existing
|
|
24
|
+
key. `ablo status` now reports the resolved profile and environment.
|
|
25
|
+
|
|
26
|
+
**Schema JSON-column reconciliation.** `generateJsonColumnReconciliation` (new
|
|
27
|
+
export) emits the DDL to reconcile JSON-backed columns when adopting or evolving
|
|
28
|
+
an existing schema.
|
|
29
|
+
|
|
30
|
+
**Breaking (0.x):**
|
|
31
|
+
- The claim handle type `ClaimHandle` is renamed to **`Claim`**, and its
|
|
32
|
+
identifier field is `id` (was `claimId`). Update type imports and any code
|
|
33
|
+
reading `.claimId`.
|
|
34
|
+
- The ai-sdk `claimBroadcastMiddleware` (and `./ai-sdk/claim-broadcast`) is
|
|
35
|
+
removed — coordination broadcast is handled by `coordinationContextMiddleware`.
|
|
36
|
+
Import `ClaimTarget` from the package root or `@abloatai/ablo` ai-sdk's
|
|
37
|
+
`coordination-context` instead of `claim-broadcast`. The inline-claim option is
|
|
38
|
+
`reason` (not the pre-0.12 `action`); the ai-sdk docs are corrected to match.
|
|
39
|
+
|
|
3
40
|
## 0.18.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/dist/ObjectPool.d.ts
CHANGED
|
@@ -111,7 +111,20 @@ export declare class ObjectPool {
|
|
|
111
111
|
__class?: string;
|
|
112
112
|
modelName?: string;
|
|
113
113
|
id?: string;
|
|
114
|
-
}, ModelClass?: new (data: Record<string, unknown>) => Model
|
|
114
|
+
}, ModelClass?: new (data: Record<string, unknown>) => Model, opts?: {
|
|
115
|
+
/**
|
|
116
|
+
* Throw (instead of warn + return null) when the wire names a model
|
|
117
|
+
* this client never registered. Passed by the developer-await read
|
|
118
|
+
* path (`HydrationCoordinator`'s network leg) so a schema collision
|
|
119
|
+
* — the org's pushed schema names `Document` but this client only
|
|
120
|
+
* registered `documents` — surfaces AT the row that failed, with the
|
|
121
|
+
* known-model list and `ablo status` pointer, instead of bubbling up
|
|
122
|
+
* three layers later as a misleading `entity_not_found`. Left off
|
|
123
|
+
* (default) for the continuous delta loop and IDB-cache hydration,
|
|
124
|
+
* where a single unknown typename must not wedge the whole batch.
|
|
125
|
+
*/
|
|
126
|
+
strict?: boolean;
|
|
127
|
+
}): Model | null;
|
|
115
128
|
/**
|
|
116
129
|
* Clear the object pool
|
|
117
130
|
* @param options.preserveObserved - If true, keep models that are being observed by React
|
package/dist/ObjectPool.js
CHANGED
|
@@ -603,7 +603,7 @@ export class ObjectPool {
|
|
|
603
603
|
create(typename, data) {
|
|
604
604
|
return this.createFromData({ ...data, __typename: typename });
|
|
605
605
|
}
|
|
606
|
-
createFromData(data, ModelClass) {
|
|
606
|
+
createFromData(data, ModelClass, opts) {
|
|
607
607
|
// Support multiple model identifier fields for backwards compatibility
|
|
608
608
|
const modelName = data.__typename ?? data.__class ?? data.modelName ?? 'Unknown';
|
|
609
609
|
const Constructor = ModelClass || this.registry.getModelByName(modelName);
|
|
@@ -613,6 +613,13 @@ export class ObjectPool {
|
|
|
613
613
|
getContext().modelDebugLogger?.logError('Unknown', 'CREATE', 'No model identifier found', data);
|
|
614
614
|
return null;
|
|
615
615
|
}
|
|
616
|
+
if (opts?.strict) {
|
|
617
|
+
const known = this.registry.getRegisteredModelNames();
|
|
618
|
+
throw new AbloValidationError(`Model "${modelName}" is not registered on this client` +
|
|
619
|
+
(known.length ? ` (known: ${known.join(', ')})` : '') +
|
|
620
|
+
`. The schema pushed to this org may differ from your local ` +
|
|
621
|
+
`schema — run \`ablo status\` to compare.`, { code: 'model_not_registered' });
|
|
622
|
+
}
|
|
616
623
|
getContext().logger.warn(`ObjectPool.createFromData: No constructor found for model "${modelName}"`, { data });
|
|
617
624
|
getContext().modelDebugLogger?.logError(modelName, 'CREATE', `No constructor found for model "${modelName}"`, data);
|
|
618
625
|
return null;
|
package/dist/SyncClient.js
CHANGED
|
@@ -161,8 +161,14 @@ export class SyncClient extends EventEmitter {
|
|
|
161
161
|
// (e.g. `AbloValidationError` with `code: 'schema_...'`,
|
|
162
162
|
// `AbloServerError` with `httpStatus: 500`). Falling back to
|
|
163
163
|
// generic message lets us still see unstructured errors.
|
|
164
|
+
// Mechanic-level breadcrumb only. The authoritative, user-facing
|
|
165
|
+
// reason is logged once at `warn` by `TransactionQueue.handleFailure`
|
|
166
|
+
// (`Permanent error - rolling back`). Logging the same typed cause
|
|
167
|
+
// again here at `warn` is what produced three identical dumps per
|
|
168
|
+
// rejected write — keep it at `debug` so the rollback mechanics are
|
|
169
|
+
// available when debugging but don't double the console noise.
|
|
164
170
|
const abloErr = error instanceof AbloError ? error : undefined;
|
|
165
|
-
getContext().logger.
|
|
171
|
+
getContext().logger.debug('[SyncClient.rollback]', {
|
|
166
172
|
txType: transaction.type,
|
|
167
173
|
modelName: transaction.modelName,
|
|
168
174
|
modelId: transaction.modelId.slice(0, 12),
|
|
@@ -28,6 +28,8 @@ export const noopObservability = {
|
|
|
28
28
|
captureWebSocketError() { },
|
|
29
29
|
captureOfflineFlushFailure() { },
|
|
30
30
|
captureSelfHealing() { },
|
|
31
|
+
captureClaim() { },
|
|
32
|
+
captureConflict() { },
|
|
31
33
|
captureCommitZeroSyncId() { },
|
|
32
34
|
startSpan(_name, _op, fn, _attributes) {
|
|
33
35
|
return fn();
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import type { LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
24
24
|
import type { Ablo } from '../client/Ablo.js';
|
|
25
25
|
import type { SchemaRecord } from '../schema/schema.js';
|
|
26
|
-
import type { ClaimTarget } from '
|
|
26
|
+
import type { ClaimTarget } from '../types/streams.js';
|
|
27
|
+
export type { ClaimTarget };
|
|
27
28
|
export interface CoordinationContextMiddlewareOptions<R extends SchemaRecord = SchemaRecord> {
|
|
28
29
|
readonly agent: Ablo<R> | null;
|
|
29
30
|
readonly target: ClaimTarget | null;
|
|
@@ -38,8 +38,8 @@ export function coordinationContextMiddleware(options) {
|
|
|
38
38
|
return params;
|
|
39
39
|
// Read peer claims on the same target. Synchronous lookup
|
|
40
40
|
// against the engine's reactive claims.others array — no I/O.
|
|
41
|
-
const peerClaims = agent.claims.others.filter((claim) => claim.target.type === target.
|
|
42
|
-
claim.target.id === target.
|
|
41
|
+
const peerClaims = agent.claims.others.filter((claim) => claim.target.type === target.type &&
|
|
42
|
+
claim.target.id === target.id &&
|
|
43
43
|
targetsOverlap(claim.target, target) &&
|
|
44
44
|
!excludeClaimIds.has(claim.id));
|
|
45
45
|
if (peerClaims.length === 0)
|
|
@@ -77,7 +77,7 @@ function targetsOverlap(claimTarget, target) {
|
|
|
77
77
|
* "AI knows," not "AI gets a wall of text."
|
|
78
78
|
*/
|
|
79
79
|
function formatCoordinationNote(claims, target) {
|
|
80
|
-
const entityLabel = target.
|
|
80
|
+
const entityLabel = target.type.toLowerCase();
|
|
81
81
|
if (claims.length === 1) {
|
|
82
82
|
const c = claims[0];
|
|
83
83
|
const details = c.description ? `Declared work: ${c.description}. ` : '';
|
package/dist/ai-sdk/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* wait: 'confirmed',
|
|
28
28
|
* claim: {
|
|
29
29
|
* field: 'title',
|
|
30
|
-
*
|
|
30
|
+
* reason: 'renaming',
|
|
31
31
|
* description,
|
|
32
32
|
* },
|
|
33
33
|
* });
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* ```ts
|
|
52
52
|
* const claim = await ablo.tasks.claim({
|
|
53
53
|
* id,
|
|
54
|
-
*
|
|
54
|
+
* reason: 'rewriting',
|
|
55
55
|
* description: 'Rewriting the task brief before updating follow-up fields.',
|
|
56
56
|
* ttl: '2m',
|
|
57
57
|
* });
|
|
@@ -72,6 +72,5 @@
|
|
|
72
72
|
* to one entity before any tool is chosen; tool implementations stay exactly
|
|
73
73
|
* the same.
|
|
74
74
|
*/
|
|
75
|
-
export {
|
|
76
|
-
export { coordinationContextMiddleware, type CoordinationContextMiddlewareOptions, } from './coordination-context.js';
|
|
75
|
+
export { coordinationContextMiddleware, type CoordinationContextMiddlewareOptions, type ClaimTarget, } from './coordination-context.js';
|
|
77
76
|
export { wrapWithMultiplayer, type WrapWithMultiplayerOptions } from './wrap.js';
|
package/dist/ai-sdk/index.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* wait: 'confirmed',
|
|
28
28
|
* claim: {
|
|
29
29
|
* field: 'title',
|
|
30
|
-
*
|
|
30
|
+
* reason: 'renaming',
|
|
31
31
|
* description,
|
|
32
32
|
* },
|
|
33
33
|
* });
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* ```ts
|
|
52
52
|
* const claim = await ablo.tasks.claim({
|
|
53
53
|
* id,
|
|
54
|
-
*
|
|
54
|
+
* reason: 'rewriting',
|
|
55
55
|
* description: 'Rewriting the task brief before updating follow-up fields.',
|
|
56
56
|
* ttl: '2m',
|
|
57
57
|
* });
|
|
@@ -72,6 +72,5 @@
|
|
|
72
72
|
* to one entity before any tool is chosen; tool implementations stay exactly
|
|
73
73
|
* the same.
|
|
74
74
|
*/
|
|
75
|
-
export { claimBroadcastMiddleware, } from './claim-broadcast.js';
|
|
76
75
|
export { coordinationContextMiddleware, } from './coordination-context.js';
|
|
77
76
|
export { wrapWithMultiplayer } from './wrap.js';
|
package/dist/ai-sdk/wrap.d.ts
CHANGED
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
* model: anthropic('claude-opus-4-7'),
|
|
15
15
|
* agent,
|
|
16
16
|
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
17
|
-
* reason: 'renaming',
|
|
18
|
-
* description: 'Renaming the deck title to match the project brief.',
|
|
19
17
|
* });
|
|
20
18
|
*
|
|
21
19
|
* const result = streamText({
|
|
@@ -31,25 +29,22 @@ import { wrapLanguageModel } from 'ai';
|
|
|
31
29
|
import type { LanguageModelV3, LanguageModelV3Middleware } from '@ai-sdk/provider';
|
|
32
30
|
import type { Ablo } from '../client/Ablo.js';
|
|
33
31
|
import type { SchemaRecord } from '../schema/schema.js';
|
|
34
|
-
import { type ClaimTarget } from './
|
|
35
|
-
export interface WrapWithMultiplayerOptions {
|
|
32
|
+
import { type ClaimTarget } from './coordination-context.js';
|
|
33
|
+
export interface WrapWithMultiplayerOptions<R extends SchemaRecord = SchemaRecord> {
|
|
36
34
|
/** The base language model to wrap. Consumer brings their own. */
|
|
37
35
|
readonly model: LanguageModelV3;
|
|
38
|
-
/** Connected SyncAgent. Null = pass-through wrap (no broadcast, no read). */
|
|
39
|
-
readonly agent: Ablo<SchemaRecord> | null;
|
|
40
|
-
/** Target entity. Null = pass-through wrap. */
|
|
41
|
-
readonly target: ClaimTarget | null;
|
|
42
|
-
/**
|
|
43
|
-
* Optional human-readable phase for the broadcast. Default `'edit'`.
|
|
44
|
-
* Convention: `'edit'`, `'read'`, `'review'`, `'generate'`. The same
|
|
45
|
-
* `reason` field used on every claim surface.
|
|
46
|
-
*/
|
|
47
|
-
readonly reason?: string;
|
|
48
36
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
37
|
+
* Connected Ablo. Null = pass-through wrap (no broadcast, no read).
|
|
38
|
+
*
|
|
39
|
+
* Generic over the schema record (like the two middlewares it composes) so a
|
|
40
|
+
* caller passing a typed `Ablo<typeof schema>` doesn't have to cast: `Ablo<S>`
|
|
41
|
+
* and `Ablo<SchemaRecord>` aren't structurally assignable (the widened version
|
|
42
|
+
* collapses model proxies to an index signature that clashes with the named
|
|
43
|
+
* methods `ready`/`dispose`/…).
|
|
51
44
|
*/
|
|
52
|
-
readonly
|
|
45
|
+
readonly agent: Ablo<R> | null;
|
|
46
|
+
/** Target entity. Null = pass-through wrap. */
|
|
47
|
+
readonly target: ClaimTarget | null;
|
|
53
48
|
/**
|
|
54
49
|
* Optional claimIds to exclude from the coordination-context
|
|
55
50
|
* read — typically the caller's own claim if they're composing
|
|
@@ -68,4 +63,4 @@ export interface WrapWithMultiplayerOptions {
|
|
|
68
63
|
*/
|
|
69
64
|
readonly extraMiddleware?: readonly LanguageModelV3Middleware[];
|
|
70
65
|
}
|
|
71
|
-
export declare function wrapWithMultiplayer(options: WrapWithMultiplayerOptions): ReturnType<typeof wrapLanguageModel>;
|
|
66
|
+
export declare function wrapWithMultiplayer<R extends SchemaRecord = SchemaRecord>(options: WrapWithMultiplayerOptions<R>): ReturnType<typeof wrapLanguageModel>;
|
package/dist/ai-sdk/wrap.js
CHANGED
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
* model: anthropic('claude-opus-4-7'),
|
|
15
15
|
* agent,
|
|
16
16
|
* target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
|
|
17
|
-
* reason: 'renaming',
|
|
18
|
-
* description: 'Renaming the deck title to match the project brief.',
|
|
19
17
|
* });
|
|
20
18
|
*
|
|
21
19
|
* const result = streamText({
|
|
@@ -28,15 +26,13 @@
|
|
|
28
26
|
* ```
|
|
29
27
|
*/
|
|
30
28
|
import { wrapLanguageModel } from 'ai';
|
|
31
|
-
import {
|
|
32
|
-
import { coordinationContextMiddleware } from './coordination-context.js';
|
|
29
|
+
import { coordinationContextMiddleware, } from './coordination-context.js';
|
|
33
30
|
export function wrapWithMultiplayer(options) {
|
|
34
|
-
const { model, agent, target,
|
|
31
|
+
const { model, agent, target, excludeClaimIds, extraMiddleware } = options;
|
|
35
32
|
return wrapLanguageModel({
|
|
36
33
|
model,
|
|
37
34
|
middleware: [
|
|
38
35
|
coordinationContextMiddleware({ agent, target, excludeClaimIds }),
|
|
39
|
-
claimBroadcastMiddleware({ agent, target, reason, description }),
|
|
40
36
|
...(extraMiddleware ?? []),
|
|
41
37
|
],
|
|
42
38
|
});
|