@abloatai/ablo 0.59.2 → 0.60.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 +60 -0
- package/README.md +1 -1
- package/dist/ai-sdk.js.map +1 -1
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +2 -0
- package/dist/sessions.js.map +1 -0
- package/docs/agent-messaging.md +3 -2
- package/docs/agents.md +49 -32
- package/docs/api-keys.md +18 -12
- package/docs/client-behavior.md +6 -4
- package/docs/coordination.md +6 -3
- package/docs/customer-organizations.md +8 -19
- package/docs/deployment.md +7 -6
- package/docs/examples/existing-python-backend.md +15 -18
- package/docs/examples/nextjs.md +33 -66
- package/docs/examples/scoped-agent.md +4 -3
- package/docs/examples/server-agent.md +4 -3
- package/docs/groups.md +11 -12
- package/docs/identity.md +33 -29
- package/docs/integration-guide.md +12 -18
- package/docs/options.md +80 -29
- package/docs/react.md +8 -52
- package/docs/security.md +1 -1
- package/docs/sessions.md +97 -69
- package/docs/transports.md +124 -0
- package/examples/README.md +7 -0
- package/examples/terminal-showcase/index.ts +219 -0
- package/examples/terminal-showcase/schema.ts +12 -0
- package/llms.txt +9 -9
- package/package.json +10 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.60.0
|
|
4
|
+
|
|
5
|
+
### Sessions are the connection boundary for people and agents
|
|
6
|
+
|
|
7
|
+
`Sessions({ schema, apiKey })` is now the dedicated session issuer. Backends create scoped
|
|
8
|
+
agent sessions with `sessions.create({ agent, can, groups })` and expose browser
|
|
9
|
+
sessions with `sessions.handler({ authenticate, grant })`. Both return the same
|
|
10
|
+
short-lived session contract, and both are supplied to clients through
|
|
11
|
+
`Ablo({ schema, session })`:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const workerAccess = {
|
|
15
|
+
records: ['read', 'update'],
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
import Sessions from '@abloatai/ablo/sessions';
|
|
19
|
+
|
|
20
|
+
const sessions = Sessions({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
21
|
+
|
|
22
|
+
const session = () =>
|
|
23
|
+
sessions.create({
|
|
24
|
+
agent: { id: stableWorkerId },
|
|
25
|
+
groups: [workspaceGroup],
|
|
26
|
+
can: workerAccess,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const agent = Ablo({ schema, session });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Session clients default to one reconnecting WebSocket for commits, claims,
|
|
33
|
+
observation, presence, and collaboration. API-key clients remain HTTP by
|
|
34
|
+
default, and bounded session work can select `transport: 'http'` explicitly.
|
|
35
|
+
Model calls do not open additional sockets.
|
|
36
|
+
|
|
37
|
+
An async session provider represents one renewable logical identity. The client
|
|
38
|
+
caches each short-lived credential until it approaches `expiresAt`, pre-mints a
|
|
39
|
+
replacement, and reconnects with that replacement when necessary. Durable
|
|
40
|
+
observation resumes from its acknowledged cursor across socket replacement.
|
|
41
|
+
A provider resolving `null` means the application login ended and terminates the
|
|
42
|
+
session; a thrown error remains transient. A static session object cannot renew
|
|
43
|
+
itself and ends when its bearer expires. In-flight commits whose outcome became
|
|
44
|
+
ambiguous still reject and can be retried with their original idempotency key.
|
|
45
|
+
|
|
46
|
+
The browser client now names its session route as
|
|
47
|
+
`session: { endpoint: '/api/ablo-session' }`; `authEndpoint` is removed. Public
|
|
48
|
+
connection scope is `groups`; public `syncGroups` is removed. The overlapping
|
|
49
|
+
`agents.create`, `join`, and `useJoin` lifecycles are also removed: connection
|
|
50
|
+
groups define visibility, `usePeers` reads presence, and row claims own
|
|
51
|
+
exclusion.
|
|
52
|
+
|
|
53
|
+
Internally, session contract, creation, handler, source normalization, and
|
|
54
|
+
credential renewal now live beneath one `sessions` boundary. HTTP bootstrap and
|
|
55
|
+
the live socket consume the same normalized session access, so credential
|
|
56
|
+
identity and renewal policy cannot diverge.
|
|
57
|
+
|
|
58
|
+
Session issuance no longer occupies a property on `Ablo(...)`. That client owns
|
|
59
|
+
the schema model namespace, so an application model named `sessions` works as
|
|
60
|
+
`ablo.sessions` like any other model. Issuance and lifecycle administration stay
|
|
61
|
+
server-only behind the explicit `@abloatai/ablo/sessions` import.
|
|
62
|
+
|
|
3
63
|
## 0.59.2
|
|
4
64
|
|
|
5
65
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -127,7 +127,7 @@ flattening the implementation into `packages/ablo`:
|
|
|
127
127
|
- `packages/ablo` is the branded public facade. Its files mostly re-export the
|
|
128
128
|
package that owns each API.
|
|
129
129
|
- `packages/transaction` owns the shared model-operation contracts and the
|
|
130
|
-
|
|
130
|
+
headless HTTP/WebSocket transport implementation.
|
|
131
131
|
- `packages/humans` owns the reactive WebSocket/local/React implementation.
|
|
132
132
|
|
|
133
133
|
That means searching only inside `packages/ablo/src` will not find the
|
package/dist/ai-sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACnD,CAAC,CAAC;AAYH,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAC5B,KAA2B,EAC3B,
|
|
1
|
+
{"version":3,"file":"ai-sdk.js","sourceRoot":"","sources":["../src/ai-sdk.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAE7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACnD,CAAC,CAAC;AAYH,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAC5B,KAA2B,EAC3B,UAAwC,EAAE;IAE1C,MAAM,EAAE,OAAO,EAAE,GAAG,2BAA2B,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CACjC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CACzE,CAAC;IACF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAC5B,QAAQ,EACR,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,EACjE,CAAC,CACF,CAAC;IACF,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,0DAA0D,OAAO,EAAE;KAC7E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { Sessions, Sessions as default } from '@abloatai/transaction/sessions';
|
|
2
|
+
export type { AbloSession, CreateAgentSessionParams, CreateSessionParams, CreateUserSessionParams, RevokeSessionParams, RotateSessionParams, SessionCredential, SessionEndpoint, SessionHandler, SessionHandlerOptions, SessionProvider, SessionProviderResult, SessionRevocation, SessionRotation, SessionScope, SessionSource, SessionsClient, SessionsOptions, } from '@abloatai/transaction/sessions';
|
|
3
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAC/E,YAAY,EACV,WAAW,EACX,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,GAChB,MAAM,gCAAgC,CAAC"}
|
package/dist/sessions.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAAI,OAAO,EAAE,MAAM,gCAAgC,CAAC"}
|
package/docs/agent-messaging.md
CHANGED
|
@@ -75,11 +75,12 @@ await ablo.ready();
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
The secret `apiKey` is server-only. Browser clients must not receive it; live UIs
|
|
78
|
-
use the
|
|
78
|
+
use the reactive client with `session.endpoint`, which owns their WebSocket and
|
|
79
|
+
short-lived user-session renewal.
|
|
79
80
|
|
|
80
81
|
If your backend mints restricted agent tokens, register the database once from a
|
|
81
82
|
secret-key server process as above. Workers using the restricted token can then
|
|
82
|
-
construct `Ablo({ schema,
|
|
83
|
+
construct `Ablo({ schema, session, transport: "http" })` because the project
|
|
83
84
|
already has a registered data plane.
|
|
84
85
|
|
|
85
86
|
## Link a message to a claim
|
package/docs/agents.md
CHANGED
|
@@ -22,21 +22,33 @@ These are observational reads. Use `read({ id })` only when a later Ablo write
|
|
|
22
22
|
depends on that exact version and will pass it through `reads`.
|
|
23
23
|
|
|
24
24
|
Most agents wake on a trigger, read what they need, write a result, and go idle.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
Trusted service work uses an API key and plain HTTP. An agent that needs its own
|
|
26
|
+
scoped identity receives a session; session clients use one reconnecting
|
|
27
|
+
WebSocket by default. The server resolves the org, scope, and actor from the
|
|
28
|
+
credential on both carriers.
|
|
29
|
+
|
|
30
|
+
Create scoped credentials with the server-only issuer:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import Sessions from '@abloatai/ablo/sessions';
|
|
34
|
+
|
|
35
|
+
const sessions = Sessions({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
API-key services use HTTP. Session agents use a multiplexed WebSocket without
|
|
39
|
+
installing the human materializer. People add the `humans()` plugin for a local reactive graph. All three operate on the same typed,
|
|
33
40
|
coordinated state and enter the same server-side commit and claim paths.
|
|
34
41
|
|
|
35
42
|
```ts
|
|
43
|
+
const session = () => sessions.create({
|
|
44
|
+
agent: { id: stableWorkerId },
|
|
45
|
+
can: workerAccess,
|
|
46
|
+
groups: [workspaceGroup],
|
|
47
|
+
});
|
|
48
|
+
|
|
36
49
|
const ablo = Ablo({
|
|
37
50
|
schema,
|
|
38
|
-
|
|
39
|
-
transport: 'websocket',
|
|
51
|
+
session,
|
|
40
52
|
cursorStore,
|
|
41
53
|
});
|
|
42
54
|
|
|
@@ -73,14 +85,14 @@ authenticates; the [schema](/installation) defines what you can call.
|
|
|
73
85
|
|
|
74
86
|
## The agent client
|
|
75
87
|
|
|
76
|
-
Same `Ablo()` entry point as everywhere else
|
|
77
|
-
socket
|
|
88
|
+
Same `Ablo()` entry point as everywhere else. An API-key client is HTTP: no
|
|
89
|
+
socket or connection state, just your schema and service credential.
|
|
78
90
|
|
|
79
91
|
```ts
|
|
80
92
|
import Ablo from "@abloatai/ablo";
|
|
81
93
|
import { schema } from "./schema";
|
|
82
94
|
|
|
83
|
-
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY
|
|
95
|
+
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
84
96
|
|
|
85
97
|
// Reads + writes, fully typed off your schema.
|
|
86
98
|
// `get` resolves to the row, or `undefined` when none matches.
|
|
@@ -99,12 +111,12 @@ and `claim`. It does **not** expose stateful-only `local` reads or model
|
|
|
99
111
|
selected WebSocket transport, or holds one POST/SSE response until the context
|
|
100
112
|
changes on the HTTP transport.
|
|
101
113
|
|
|
102
|
-
##
|
|
114
|
+
## Scoped agent sessions
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
`Sessions(...).create({ agent })` is the one issuance path. It returns a restricted
|
|
117
|
+
credential; construct the schema-typed client in the runtime that executes the
|
|
118
|
+
agent. This keeps session minting, revocation, and rotation under one resource
|
|
119
|
+
for both people and agents.
|
|
108
120
|
|
|
109
121
|
Derive identity and groups from the run row or trusted job payload—not from
|
|
110
122
|
model output or an HTTP request body. A serverless handler normally creates and
|
|
@@ -114,12 +126,14 @@ disposes one child per invocation:
|
|
|
114
126
|
const run = await control.runs.read({ id: verifiedRunId });
|
|
115
127
|
if (!run) throw new Error('run not found');
|
|
116
128
|
|
|
117
|
-
const
|
|
118
|
-
id: `run:${run.id}
|
|
119
|
-
name: 'run-worker',
|
|
129
|
+
const session = await sessions.create({
|
|
130
|
+
agent: { id: `run:${run.id}` },
|
|
120
131
|
can: { records: ['read', 'update'] },
|
|
121
|
-
|
|
132
|
+
groups: [`workspace:${run.workspaceId}`],
|
|
133
|
+
userMeta: { name: 'run-worker' },
|
|
122
134
|
});
|
|
135
|
+
|
|
136
|
+
const agent = Ablo({ schema, session, transport: 'http' });
|
|
123
137
|
try {
|
|
124
138
|
await executeRun(agent, run);
|
|
125
139
|
} finally {
|
|
@@ -128,29 +142,32 @@ try {
|
|
|
128
142
|
```
|
|
129
143
|
|
|
130
144
|
Use a stable id only when one logical run is serialized; two concurrent workers
|
|
131
|
-
that share an id appear as the same participant.
|
|
132
|
-
|
|
145
|
+
that share an id appear as the same participant. Generate a different id for
|
|
146
|
+
each independent concurrent run.
|
|
133
147
|
|
|
134
|
-
A long-running worker may cache one
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
reuse
|
|
148
|
+
A long-running worker may cache one client per stable scope and provide an
|
|
149
|
+
async credential resolver that re-mints the same session identity. The cache
|
|
150
|
+
owns lifecycle: evict idle clients, call `dispose()` on eviction, and dispose
|
|
151
|
+
every client during graceful shutdown. Never reuse a client for a different
|
|
152
|
+
workspace or capability set.
|
|
138
153
|
|
|
139
154
|
```ts
|
|
140
155
|
const agents: Record<
|
|
141
156
|
string,
|
|
142
|
-
|
|
157
|
+
ReturnType<typeof Ablo> | undefined
|
|
143
158
|
> = {};
|
|
144
159
|
|
|
145
160
|
async function agentFor(run: Run) {
|
|
146
161
|
const key = `${run.workspaceId}:${run.workerSlot}`;
|
|
147
162
|
const cached = agents[key];
|
|
148
163
|
if (cached) return cached;
|
|
149
|
-
const
|
|
150
|
-
|
|
164
|
+
const agentId = `worker:${key}`;
|
|
165
|
+
const session = () => sessions.create({
|
|
166
|
+
agent: { id: agentId },
|
|
151
167
|
can: { records: ['read', 'update'] },
|
|
152
|
-
|
|
168
|
+
groups: [`workspace:${run.workspaceId}`],
|
|
153
169
|
});
|
|
170
|
+
const created = Ablo({ schema, session });
|
|
154
171
|
agents[key] = created;
|
|
155
172
|
return created;
|
|
156
173
|
}
|
package/docs/api-keys.md
CHANGED
|
@@ -28,7 +28,7 @@ and remap them before each command.
|
|
|
28
28
|
| Prepare a branch once, including CI | expiring `sk_` bound to that branch | `npx ablo dev --no-watch --branch <ref>`; headless CI supplies an `mk_` credential through `ABLO_API_KEY`. |
|
|
29
29
|
| Run the production backend | `sk_` bound to the production root | Store it as the deployment's `ABLO_API_KEY`. |
|
|
30
30
|
| Read in a browser | `pk_` | Publishable, read-only key. |
|
|
31
|
-
| Write in a browser as a user | short-lived `ek_` | Your backend exposes
|
|
31
|
+
| Write in a browser as a user | short-lived `ek_` | Your backend exposes a session endpoint and mints it. |
|
|
32
32
|
|
|
33
33
|
The everyday loop is therefore:
|
|
34
34
|
|
|
@@ -71,16 +71,18 @@ already carries the target.
|
|
|
71
71
|
|
|
72
72
|
## Which credential to pass to the SDK
|
|
73
73
|
|
|
74
|
-
There
|
|
75
|
-
Pick your row:
|
|
74
|
+
There are two ordinary identity inputs: `apiKey` for a key the process owns and
|
|
75
|
+
`session` for a scoped actor. Pick your row:
|
|
76
76
|
|
|
77
77
|
| Where your code runs | What to pass | Example |
|
|
78
78
|
|---|---|---|
|
|
79
79
|
| **Server / worker / agent** (can hold a secret) | your secret `sk_`: it defaults to `ABLO_API_KEY`, so usually pass **nothing** | `Ablo({ schema })` |
|
|
80
80
|
| **Browser: read-only** | a publishable `pk_` (safe to ship) | `Ablo({ schema, apiKey: process.env.NEXT_PUBLIC_ABLO_PUBLISHABLE_KEY })` |
|
|
81
|
-
| **Browser: writing as the signed-in user** | `
|
|
81
|
+
| **Browser: writing as the signed-in user** | `session.endpoint`: the route on your own backend that mints a short-lived per-user token | `Ablo({ schema, session: { endpoint: '/api/ablo-session' } })` |
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
The names follow ownership: a process owns an API key; an actor runs through a
|
|
84
|
+
session, regardless of whether that session is already minted, renewable, or
|
|
85
|
+
fetched from a browser endpoint.
|
|
84
86
|
|
|
85
87
|
The `mk_` credential created by `ablo login` is different: it is a CLI
|
|
86
88
|
control-plane credential, not an application API key. It can manage projects
|
|
@@ -104,24 +106,28 @@ For an `ek_`, the server mints and the client holds the short-lived result.
|
|
|
104
106
|
public `pk_` is **read-only** — it can't carry one specific user's write authority. So when
|
|
105
107
|
the browser writes *as the logged-in user*, your backend (which holds the secret `sk_` and
|
|
106
108
|
knows who's signed in) mints a short-lived per-user token with `sessions.create({ user, can })`,
|
|
107
|
-
and the browser's `
|
|
109
|
+
and the browser's `session.endpoint` fetches it. You don't manage refresh — the SDK calls the
|
|
108
110
|
function once before connecting and then keeps the token fresh (re-mint before expiry, and on
|
|
109
111
|
tab-focus / network-online / device-wake). For a read-only app you don't need
|
|
110
112
|
any of this — just the `pk_` above.
|
|
111
113
|
|
|
112
114
|
Server-side, because `apiKey` defaults to `process.env.ABLO_API_KEY`, most backend and agent
|
|
113
115
|
code passes nothing. The secret `sk_` is **server-only** — never in a
|
|
114
|
-
browser bundle. There is no `getToken
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
browser bundle. There is no `getToken`, `as`, or separate auth-endpoint option:
|
|
117
|
+
`apiKey` is the key a process owns, while `session` is a minted resource, a
|
|
118
|
+
renewal provider, or `{ endpoint }`. Set exactly one identity input.
|
|
117
119
|
|
|
118
120
|
### Minting per-user / agent tokens (server-side, with your `sk_`)
|
|
119
121
|
|
|
122
|
+
Construct the dedicated issuer with
|
|
123
|
+
`Sessions({ schema, apiKey: process.env.ABLO_API_KEY })` from
|
|
124
|
+
`@abloatai/ablo/sessions`. It is server-only and does not create a participant
|
|
125
|
+
connection.
|
|
126
|
+
|
|
120
127
|
| Mint | Call | Result |
|
|
121
128
|
|---|---|---|
|
|
122
|
-
| Human end-user session | `await
|
|
123
|
-
|
|
|
124
|
-
| Raw delegated agent token | `await server.sessions.create({ agent: { id }, can: { records: ['update'] } })` | `rk_` for another runtime |
|
|
129
|
+
| Human end-user session | `await sessions.create({ user: { id }, can: { records: ['read'] } })` | `ek_` (scoped to `can`) |
|
|
130
|
+
| Agent session | `await sessions.create({ agent: { id }, can: { records: ['update'] } })` | Scoped `rk_` for the agent runtime |
|
|
125
131
|
|
|
126
132
|
The principal kind comes from *which* shape you pass — `{ user, can }` → `user`, `{ agent, can }` → `agent`.
|
|
127
133
|
|
package/docs/client-behavior.md
CHANGED
|
@@ -28,10 +28,12 @@ const ablo = Ablo({
|
|
|
28
28
|
});
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The package-root export is the
|
|
32
|
-
handlers, and other server operations.
|
|
33
|
-
|
|
34
|
-
[
|
|
31
|
+
The package-root export is the headless coordination client for agents, workers,
|
|
32
|
+
route handlers, and other server operations. Trusted API-key clients use HTTP.
|
|
33
|
+
Scoped session clients use one reconnecting WebSocket for commits and live
|
|
34
|
+
coordination; point reads and administration remain HTTP. See [Transports](./transports.md)
|
|
35
|
+
for the lifecycle and [Options](./options.md) for the constructor. A human-facing
|
|
36
|
+
local graph is added through the [React client](./react.md).
|
|
35
37
|
|
|
36
38
|
Your database connects out of band — through logical replication (`npx ablo
|
|
37
39
|
connect`), or the signed [Data Source](./data-sources.md) endpoint as the
|
package/docs/coordination.md
CHANGED
|
@@ -206,11 +206,15 @@ same credential represent the same participant and do not exclude one another.
|
|
|
206
206
|
Mint a distinct scoped session for each independently coordinated agent:
|
|
207
207
|
|
|
208
208
|
```ts
|
|
209
|
-
|
|
209
|
+
import Sessions from '@abloatai/ablo/sessions';
|
|
210
|
+
|
|
211
|
+
const sessions = Sessions({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
212
|
+
const session = await sessions.create({
|
|
210
213
|
agent: { id: `forecast-agent-${workerId}` },
|
|
214
|
+
can: { records: ['read', 'update'] },
|
|
211
215
|
});
|
|
212
216
|
|
|
213
|
-
const agent = Ablo({ schema,
|
|
217
|
+
const agent = Ablo({ schema, session });
|
|
214
218
|
```
|
|
215
219
|
|
|
216
220
|
Functional updates do not require distinct participant identities because they
|
|
@@ -305,7 +309,6 @@ The main methods are:
|
|
|
305
309
|
| `claim.state({ id })` | Read the current holder without blocking. |
|
|
306
310
|
| `claim.queue({ id })` | Read the current wait order. |
|
|
307
311
|
| `claim.release({ id })` | Release early when you do not hold a handle. |
|
|
308
|
-
| `join({ scope })` | Observe presence for a broader scope. |
|
|
309
312
|
|
|
310
313
|
This page owns row-backed claims: `model.claim({ id })` reads and claims an Ablo
|
|
311
314
|
model row, and the handle carries fresh data. Identifier-only claims before an
|
|
@@ -47,27 +47,16 @@ export const schema = defineSchema(
|
|
|
47
47
|
```ts
|
|
48
48
|
// 2. app/api/ablo-session/route.ts — mint for one customer, on your backend.
|
|
49
49
|
import { syncGroup } from '@abloatai/ablo/schema';
|
|
50
|
-
import {
|
|
51
|
-
import { ablo } from '@/ablo/server';
|
|
50
|
+
import { sessions } from '@/ablo/sessions';
|
|
52
51
|
|
|
53
|
-
export
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const session = await ablo.sessions.create({
|
|
52
|
+
export const POST = sessions.handler({
|
|
53
|
+
authenticate: () => currentSignedInMember(),
|
|
54
|
+
grant: ({ principal: member }) => ({
|
|
57
55
|
user: { id: member.userId },
|
|
58
56
|
can: { customers: ['read'], decks: ['read', 'create', 'update'] },
|
|
59
|
-
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
return Response.json(
|
|
63
|
-
credentialEndpointSuccessSchema.parse({
|
|
64
|
-
token: session.token,
|
|
65
|
-
expiresAt: session.expiresAt,
|
|
66
|
-
credentialKind: 'ephemeral',
|
|
67
|
-
}),
|
|
68
|
-
{ headers: { 'Cache-Control': 'no-store' } },
|
|
69
|
-
);
|
|
70
|
-
}
|
|
57
|
+
groups: [syncGroup('customer', member.customerId)],
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
71
60
|
```
|
|
72
61
|
|
|
73
62
|
That is the whole integration. The rest of this page is why each line is where
|
|
@@ -130,7 +119,7 @@ kind is the one you declared in `groups.root`, and the id is your own
|
|
|
130
119
|
identifier for the customer.
|
|
131
120
|
|
|
132
121
|
```ts
|
|
133
|
-
|
|
122
|
+
groups: [syncGroup('customer', member.customerId)]
|
|
134
123
|
```
|
|
135
124
|
|
|
136
125
|
Resolve `member.customerId` from the membership you just authenticated on the
|
package/docs/deployment.md
CHANGED
|
@@ -120,16 +120,17 @@ is genuinely unavailable.
|
|
|
120
120
|
|
|
121
121
|
## 2. The credential each runtime holds
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
Credential configuration follows the runtime. Long-lived root keys use
|
|
124
|
+
`apiKey`; scoped actors use `session`; browser login exchange uses
|
|
125
|
+
`session.endpoint`:
|
|
125
126
|
|
|
126
127
|
| Runtime | Credential | Notes |
|
|
127
128
|
|---|---|---|
|
|
128
129
|
| Server, worker, agent, cron | `sk_` in `ABLO_API_KEY` | Defaults from the environment, so most code passes nothing. |
|
|
129
|
-
| Serverless function | `sk_` in `ABLO_API_KEY
|
|
130
|
-
|
|
|
130
|
+
| Serverless function | `sk_` in `ABLO_API_KEY` | Stateless request/response; nothing held open across invocations. |
|
|
131
|
+
| Long-running agent | `session: () => sessions.create(...)` | One renewable identity and one multiplexed WebSocket per client and Ablo cell; checkpoint durable deltas before acknowledging. |
|
|
131
132
|
| Browser, read-only | root-bound `pk_` | Publishable, safe to ship, and read-only. |
|
|
132
|
-
| Browser, writing as the signed-in user | `
|
|
133
|
+
| Browser, writing as the signed-in user | `session: { endpoint }` | A route on your backend mints a short-lived `ek_` per user. |
|
|
133
134
|
|
|
134
135
|
[API Keys](./api-keys.md) covers the model; [Sessions](./sessions.md) covers
|
|
135
136
|
minting. Two things bite specifically at deploy time.
|
|
@@ -284,7 +285,7 @@ and what each promises.
|
|
|
284
285
|
browser bundle.
|
|
285
286
|
3. `ablo plan` reviewed, followed by fingerprint-gated `ablo push --yes`.
|
|
286
287
|
4. `ablo status --json` gating the deploy on an empty `blockers` array.
|
|
287
|
-
5. Browser clients on a root-bound `pk_` or
|
|
288
|
+
5. Browser clients on a root-bound `pk_` or `session.endpoint`, not a secret key.
|
|
288
289
|
6. Webhook endpoints registered at their deployed URLs, with the signing secret
|
|
289
290
|
in your environment.
|
|
290
291
|
|
|
@@ -48,12 +48,18 @@ export const schema = defineSchema({
|
|
|
48
48
|
```ts
|
|
49
49
|
// web/ablo.ts — SERVER-ONLY client (holds the sk_ key; never imported in the browser).
|
|
50
50
|
import Ablo from '@abloatai/ablo';
|
|
51
|
+
import Sessions from '@abloatai/ablo/sessions';
|
|
51
52
|
import { schema } from './ablo/schema';
|
|
52
53
|
|
|
53
54
|
export const ablo = Ablo({
|
|
54
55
|
schema,
|
|
55
56
|
apiKey: process.env.ABLO_API_KEY,
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
export const sessions = Sessions({
|
|
60
|
+
schema,
|
|
61
|
+
apiKey: process.env.ABLO_API_KEY,
|
|
62
|
+
});
|
|
57
63
|
```
|
|
58
64
|
|
|
59
65
|
Mount the React provider near the app root. Build the browser client first —
|
|
@@ -69,11 +75,11 @@ import { Ablo } from '@abloatai/ablo/react';
|
|
|
69
75
|
import { AbloProvider } from '@abloatai/ablo/react';
|
|
70
76
|
import { schema } from '@/ablo/schema';
|
|
71
77
|
|
|
72
|
-
// Browser client: no secret key — `
|
|
78
|
+
// Browser client: no secret key — `session.endpoint` points at the session route
|
|
73
79
|
// your server exposes (below); the SDK fetches and refreshes the token.
|
|
74
80
|
const ablo = Ablo({
|
|
75
81
|
schema,
|
|
76
|
-
|
|
82
|
+
session: { endpoint: '/api/ablo-session' },
|
|
77
83
|
});
|
|
78
84
|
|
|
79
85
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
@@ -86,26 +92,17 @@ browser only ever sees the short-lived token:
|
|
|
86
92
|
|
|
87
93
|
```ts
|
|
88
94
|
// web/app/api/ablo-session/route.ts
|
|
89
|
-
import {
|
|
90
|
-
import { credentialEndpointSuccessSchema } from '@abloatai/ablo/auth';
|
|
95
|
+
import { sessions } from '@/ablo';
|
|
91
96
|
|
|
92
97
|
export const runtime = 'nodejs';
|
|
93
98
|
|
|
94
|
-
export
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
user: { id:
|
|
99
|
+
export const POST = sessions.handler({
|
|
100
|
+
authenticate: () => currentUser(), // your auth; null when signed out
|
|
101
|
+
grant: ({ principal: user }) => ({
|
|
102
|
+
user: { id: user.id },
|
|
98
103
|
can: { records: ['read', 'update'] },
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
credentialEndpointSuccessSchema.parse({
|
|
102
|
-
token,
|
|
103
|
-
expiresAt,
|
|
104
|
-
credentialKind: 'ephemeral',
|
|
105
|
-
}),
|
|
106
|
-
{ headers: { 'Cache-Control': 'no-store' } },
|
|
107
|
-
);
|
|
108
|
-
}
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
109
106
|
```
|
|
110
107
|
|
|
111
108
|
## 2. Add Live Reads In The UI
|