@rool-dev/sdk 0.12.5 → 2.0.0-dev.4adc2c2
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/README.md +434 -786
- package/dist/agents.d.ts +130 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +348 -0
- package/dist/agents.js.map +1 -0
- package/dist/auth-base.d.ts +43 -88
- package/dist/auth-base.d.ts.map +1 -1
- package/dist/auth-base.js +178 -302
- package/dist/auth-base.js.map +1 -1
- package/dist/auth-browser.d.ts +7 -25
- package/dist/auth-browser.d.ts.map +1 -1
- package/dist/auth-browser.js +45 -82
- package/dist/auth-browser.js.map +1 -1
- package/dist/auth-native.d.ts +9 -56
- package/dist/auth-native.d.ts.map +1 -1
- package/dist/auth-native.js +88 -177
- package/dist/auth-native.js.map +1 -1
- package/dist/auth-node.d.ts +32 -36
- package/dist/auth-node.d.ts.map +1 -1
- package/dist/auth-node.js +338 -265
- package/dist/auth-node.js.map +1 -1
- package/dist/auth-url.d.ts +6 -0
- package/dist/auth-url.d.ts.map +1 -0
- package/dist/auth-url.js +30 -0
- package/dist/auth-url.js.map +1 -0
- package/dist/client.d.ts +35 -290
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +275 -637
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +40 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +175 -0
- package/dist/events.js.map +1 -0
- package/dist/files.d.ts +170 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +846 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +16 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -20
- package/dist/index.js.map +1 -1
- package/dist/machine.d.ts +49 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +142 -0
- package/dist/machine.js.map +1 -0
- package/dist/problem.d.ts +11 -0
- package/dist/problem.d.ts.map +1 -0
- package/dist/problem.js +20 -0
- package/dist/problem.js.map +1 -0
- package/dist/structured.d.ts +84 -0
- package/dist/structured.d.ts.map +1 -0
- package/dist/structured.js +405 -0
- package/dist/structured.js.map +1 -0
- package/dist/types.d.ts +119 -520
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -16
- package/dist/auth.d.ts +0 -99
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -217
- package/dist/auth.js.map +0 -1
- package/dist/client-info.d.ts +0 -18
- package/dist/client-info.d.ts.map +0 -1
- package/dist/client-info.js +0 -25
- package/dist/client-info.js.map +0 -1
- package/dist/event-emitter.d.ts +0 -45
- package/dist/event-emitter.d.ts.map +0 -1
- package/dist/event-emitter.js +0 -86
- package/dist/event-emitter.js.map +0 -1
- package/dist/graphql.d.ts +0 -90
- package/dist/graphql.d.ts.map +0 -1
- package/dist/graphql.js +0 -471
- package/dist/graphql.js.map +0 -1
- package/dist/logger.d.ts +0 -13
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -17
- package/dist/logger.js.map +0 -1
- package/dist/path.d.ts +0 -6
- package/dist/path.d.ts.map +0 -1
- package/dist/path.js +0 -47
- package/dist/path.js.map +0 -1
- package/dist/reroute.d.ts +0 -22
- package/dist/reroute.d.ts.map +0 -1
- package/dist/reroute.js +0 -70
- package/dist/reroute.js.map +0 -1
- package/dist/rest.d.ts +0 -54
- package/dist/rest.d.ts.map +0 -1
- package/dist/rest.js +0 -171
- package/dist/rest.js.map +0 -1
- package/dist/router.d.ts +0 -20
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js +0 -54
- package/dist/router.js.map +0 -1
- package/dist/space-session.d.ts +0 -30
- package/dist/space-session.d.ts.map +0 -1
- package/dist/space-session.js +0 -83
- package/dist/space-session.js.map +0 -1
- package/dist/space.d.ts +0 -161
- package/dist/space.d.ts.map +0 -1
- package/dist/space.js +0 -618
- package/dist/space.js.map +0 -1
- package/dist/subscription.d.ts +0 -36
- package/dist/subscription.d.ts.map +0 -1
- package/dist/subscription.js +0 -419
- package/dist/subscription.js.map +0 -1
- package/dist/webdav.d.ts +0 -171
- package/dist/webdav.d.ts.map +0 -1
- package/dist/webdav.js +0 -481
- package/dist/webdav.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
# Rool SDK
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for
|
|
4
|
-
|
|
5
|
-
> **Naming note:** the SDK predates the Rool Machine branding and calls a Machine a **space** (`createSpace`, `RoolSpace`, `/space/...` paths). The API keeps this legacy naming until v2; read "space" as "Machine" throughout.
|
|
6
|
-
|
|
7
|
-
Core primitives:
|
|
8
|
-
|
|
9
|
-
- **Spaces** — Machines: containers for objects, schema, metadata, conversations, collaborators, and files.
|
|
10
|
-
- **Conversations** — independent interaction histories in a space.
|
|
11
|
-
- **Objects** — JSON records addressed by object paths such as `/space/article/welcome.json`.
|
|
12
|
-
- **Files** — user-visible files stored under `/rool-drive/...` through WebDAV.
|
|
3
|
+
TypeScript SDK for Rool Machines.
|
|
13
4
|
|
|
14
5
|
## Installation
|
|
15
6
|
|
|
@@ -17,991 +8,648 @@ Core primitives:
|
|
|
17
8
|
npm install @rool-dev/sdk
|
|
18
9
|
```
|
|
19
10
|
|
|
20
|
-
##
|
|
11
|
+
## User and session API
|
|
21
12
|
|
|
22
13
|
```typescript
|
|
23
|
-
import { RoolClient } from
|
|
14
|
+
import { RoolClient } from "@rool-dev/sdk";
|
|
15
|
+
|
|
16
|
+
const client = new RoolClient({
|
|
17
|
+
getTokens: () => ({
|
|
18
|
+
accessToken: currentAccessToken,
|
|
19
|
+
roolToken: currentRoolToken,
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const session = await client.getSession();
|
|
24
|
+
const account = await client.getAccount();
|
|
25
|
+
const profile = await client.getProfile();
|
|
26
|
+
const userAppData = await client.getUserAppData();
|
|
27
|
+
const greeting = await client.getGreeting("en");
|
|
28
|
+
|
|
29
|
+
await client.replaceProfile({
|
|
30
|
+
name: "Ada",
|
|
31
|
+
marketingOptIn: true,
|
|
32
|
+
});
|
|
33
|
+
await client.setUserAppData("theme", "dark");
|
|
34
|
+
await client.deleteUserAppData("theme");
|
|
35
|
+
await client.deleteAccount();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`UserAppData` is an opaque cross-device JSON object. App data is changed one key at a time so unrelated settings cannot overwrite each other.
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
const client = new RoolClient();
|
|
40
|
+
## Account events
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
```typescript
|
|
43
|
+
const unsubscribe = client.events.subscribe(async (event) => {
|
|
44
|
+
if (event.type === "session") {
|
|
45
|
+
renderSession(event.session);
|
|
46
|
+
} else if (event.type === "account_changed") {
|
|
47
|
+
renderAccount(await client.getAccount());
|
|
48
|
+
} else if (event.type === "profile_changed") {
|
|
49
|
+
renderProfile(await client.getProfile());
|
|
50
|
+
} else if (event.type === "user_app_data_changed") {
|
|
51
|
+
renderUserAppData(await client.getUserAppData());
|
|
52
|
+
} else if (event.type === "machines_changed") {
|
|
53
|
+
renderMachines(await client.listMachines());
|
|
54
|
+
} else if (event.type === "machine_members_changed") {
|
|
55
|
+
renderMembers(
|
|
56
|
+
event.machineId,
|
|
57
|
+
await client.machine(event.machineId).members.list(),
|
|
58
|
+
);
|
|
32
59
|
}
|
|
60
|
+
});
|
|
33
61
|
|
|
34
|
-
|
|
35
|
-
|
|
62
|
+
unsubscribe();
|
|
63
|
+
```
|
|
36
64
|
|
|
37
|
-
|
|
38
|
-
{ name: 'name', type: { kind: 'string' } },
|
|
39
|
-
{ name: 'mass', type: { kind: 'string' } },
|
|
40
|
-
{ name: 'radius', type: { kind: 'string' } },
|
|
41
|
-
{ name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
|
|
42
|
-
]);
|
|
65
|
+
Account events tell the client when something changed. Fetch the relevant route to get the latest data. The SDK starts with `/v2/session`, then long-polls for changes using its account sync token. Empty polls and network retries do not produce events or refresh the session. If a token expires, the SDK fetches a new session and sends another `session` event.
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
name: 'Sun',
|
|
46
|
-
mass: '1 solar mass',
|
|
47
|
-
radius: '696,340 km',
|
|
48
|
-
});
|
|
67
|
+
`getTokens` may return tokens synchronously or asynchronously. The SDK sends the access token as `Authorization: Bearer …` and the Rool token as `X-Rool-Token`. Bearer-only integrations can use `getAccessToken` instead. Use `apiUrl` to target a non-production server and `fetch` to provide a custom transport.
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
name: 'Earth',
|
|
52
|
-
mass: '1 Earth mass',
|
|
53
|
-
radius: '6,371 km',
|
|
54
|
-
orbits: sun.path,
|
|
55
|
-
});
|
|
69
|
+
## Authentication
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
'Add the other planets in our solar system, each referencing the Sun.'
|
|
59
|
-
);
|
|
71
|
+
Auth clients own login, credential storage, and refresh. `RoolClient` only asks one for tokens and tells it when authentication is invalidated.
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
console.log(`Modified ${objects.length} objects`);
|
|
73
|
+
### Browser authentication
|
|
63
74
|
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
```typescript
|
|
76
|
+
import { BrowserAuth, RoolClient } from "@rool-dev/sdk";
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
const auth = new BrowserAuth();
|
|
79
|
+
const client = new RoolClient({
|
|
80
|
+
getTokens: auth.getTokens,
|
|
81
|
+
onAuthInvalidated: auth.logout,
|
|
82
|
+
});
|
|
69
83
|
|
|
70
|
-
|
|
84
|
+
auth.onAuthStateChanged(renderSignedInState);
|
|
85
|
+
if (!(await auth.initialize())) {
|
|
86
|
+
await auth.login("My App");
|
|
87
|
+
}
|
|
71
88
|
```
|
|
72
89
|
|
|
73
|
-
|
|
90
|
+
`initialize()` processes an auth callback in the URL. Tokens are stored in endpoint-scoped browser storage and refreshed when requested.
|
|
74
91
|
|
|
75
|
-
|
|
92
|
+
### Native authentication
|
|
76
93
|
|
|
77
|
-
-
|
|
78
|
-
- File paths: `/rool-drive/<path/to/file>`
|
|
79
|
-
|
|
80
|
-
`rool-machine:/...` URIs are the user-facing/canonical form for resource references in prompt attachments and interaction history. The exported helpers normalize between these forms when you need them.
|
|
94
|
+
`NativeAuth` uses system-browser PKCE for Google and Apple while also supporting password and magic-link sign-in.
|
|
81
95
|
|
|
82
96
|
```typescript
|
|
83
|
-
import {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
import { App } from "@capacitor/app";
|
|
98
|
+
import { Browser } from "@capacitor/browser";
|
|
99
|
+
import { NativeAuth, RoolClient } from "@rool-dev/sdk";
|
|
100
|
+
|
|
101
|
+
const auth = new NativeAuth({
|
|
102
|
+
redirectUri: "roolandroidauth://auth/callback",
|
|
103
|
+
defaultProvider: "google",
|
|
104
|
+
openExternal: (url) => Browser.open({ url }),
|
|
105
|
+
});
|
|
106
|
+
const client = new RoolClient({
|
|
107
|
+
getTokens: auth.getTokens,
|
|
108
|
+
onAuthInvalidated: auth.logout,
|
|
109
|
+
});
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
111
|
+
App.addListener("appUrlOpen", async ({ url }) => {
|
|
112
|
+
await Browser.close();
|
|
113
|
+
await auth.handleRedirect(url);
|
|
114
|
+
});
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
if (!(await auth.initialize())) await auth.login("My App");
|
|
92
117
|
```
|
|
93
118
|
|
|
94
|
-
|
|
119
|
+
The redirect URI must exactly match the app setup and auth server allowlist. Pass `{ provider: "apple" }` to `login()` or `signup()` to override the default provider.
|
|
95
120
|
|
|
96
121
|
```typescript
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
const result = await auth.signInWithPassword(email, password);
|
|
123
|
+
if (result.status === "verify_required") showCheckYourEmailMessage();
|
|
124
|
+
|
|
125
|
+
await auth.requestMagicLink(email);
|
|
126
|
+
await auth.verify(verifyToken);
|
|
101
127
|
```
|
|
102
128
|
|
|
103
|
-
|
|
129
|
+
On native, an HTTPS magic link only returns to the app when Universal Links or App Links are configured for that domain.
|
|
104
130
|
|
|
105
|
-
###
|
|
131
|
+
### Password and account methods
|
|
106
132
|
|
|
107
|
-
|
|
133
|
+
Browser and native auth clients also provide:
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
- `setPassword(password)`
|
|
136
|
+
- `requestEmailChange(newEmail)`
|
|
137
|
+
- `verify(token)`
|
|
138
|
+
- `logout()`
|
|
139
|
+
- `isAuthenticated()`
|
|
112
140
|
|
|
113
|
-
|
|
114
|
-
await client.login('My App');
|
|
115
|
-
// Browser auth redirects; stop startup until the callback reloads the app.
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
141
|
+
### Node.js authentication
|
|
118
142
|
|
|
119
|
-
|
|
120
|
-
}
|
|
143
|
+
```typescript
|
|
144
|
+
import { RoolClient } from "@rool-dev/sdk";
|
|
145
|
+
import { NodeAuth } from "@rool-dev/sdk/node";
|
|
146
|
+
|
|
147
|
+
const auth = new NodeAuth();
|
|
148
|
+
if (!(await auth.initialize())) await auth.login("My CLI");
|
|
121
149
|
|
|
122
|
-
|
|
150
|
+
const client = new RoolClient({
|
|
151
|
+
getTokens: auth.getTokens,
|
|
152
|
+
onAuthInvalidated: auth.logout,
|
|
153
|
+
});
|
|
123
154
|
```
|
|
124
155
|
|
|
125
|
-
|
|
156
|
+
`NodeAuth` opens the system browser for login and stores endpoint-scoped credentials under `~/.config/rool/`. It refreshes the access and Rool tokens when requested. Pass `apiUrl` to select another deployed environment; the corresponding auth URL is derived from it. When the API uses a loopback URL, pass `authUrl` explicitly.
|
|
126
157
|
|
|
127
|
-
Use
|
|
158
|
+
Use `profile` or `credentialsPath` when an application needs multiple independent accounts:
|
|
128
159
|
|
|
129
160
|
```typescript
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let authenticated = await client.initialize();
|
|
135
|
-
|
|
136
|
-
if (!authenticated) {
|
|
137
|
-
await client.login('My CLI Tool');
|
|
138
|
-
// Re-run initialize after the non-redirect login to hydrate currentUser,
|
|
139
|
-
// user storage, and client-level event subscriptions.
|
|
140
|
-
authenticated = await client.initialize();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!authenticated) throw new Error('Login required');
|
|
161
|
+
const auth = new NodeAuth({
|
|
162
|
+
apiUrl: "https://api.example.com",
|
|
163
|
+
profile: "automation",
|
|
164
|
+
});
|
|
144
165
|
```
|
|
145
166
|
|
|
146
|
-
|
|
167
|
+
API errors are thrown as `RoolProblem` with the server's stable `code`, HTTP `status`, `title`, and `detail`.
|
|
147
168
|
|
|
148
|
-
|
|
149
|
-
system browser. `login()`/`signup()` open the auth server's `/authorize` page
|
|
150
|
-
via the `openExternal` callback you supply; when the deep link returns, feed it
|
|
151
|
-
to `client.handleAuthRedirect(url)` from your platform's deep-link handler. The
|
|
152
|
-
code is exchanged for a session at `/token` and tokens are stored like the
|
|
153
|
-
browser provider.
|
|
169
|
+
## Machine API
|
|
154
170
|
|
|
155
171
|
```typescript
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
import { App } from '@capacitor/app';
|
|
159
|
-
|
|
160
|
-
const client = new RoolClient({
|
|
161
|
-
authProvider: new NativePkceAuthProvider({
|
|
162
|
-
redirectUri: 'roolandroidauth://auth/callback', // must match the server allowlist
|
|
163
|
-
defaultProvider: 'google', // 'google' | 'apple'
|
|
164
|
-
openExternal: (url) => Browser.open({ url }),
|
|
165
|
-
}),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Complete sign-in when the OS hands the app its deep link.
|
|
169
|
-
App.addListener('appUrlOpen', async ({ url }) => {
|
|
170
|
-
if (await client.handleAuthRedirect(url)) {
|
|
171
|
-
await Browser.close();
|
|
172
|
-
// Now authenticated — refresh your UI.
|
|
173
|
-
}
|
|
174
|
-
});
|
|
172
|
+
const created = await client.createMachine({ name: "Research" });
|
|
173
|
+
const machine = client.machine(created.id);
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
const details = await machine.get();
|
|
176
|
+
await machine.settings.replace({ name: "Field research" });
|
|
177
|
+
const copy = await machine.duplicate({ name: "Research copy" });
|
|
178
|
+
const response = await machine.fetchUrl("https://example.com/data.json");
|
|
179
|
+
await client.machine(copy.id).delete();
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
`client.machine(id)` returns a stable machine handle that owns machine-scoped APIs and synchronization state. Creation, listing, sessions, duplication, and `machine.get()` all return the same point-in-time `MachineSummary`; bind its ID to a handle before performing machine operations. Summaries include the machine's inbound email address, lifecycle `state`, and an opaque `meta` JSON object. `fetchUrl()` returns the upstream `Response`, including non-success statuses.
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
no system browser, the server returns the token set as JSON directly.
|
|
184
|
+
## Machine checkpoints
|
|
186
185
|
|
|
187
186
|
```typescript
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
} else {
|
|
193
|
-
// status === 'verify_required': the email isn't verified yet and the server
|
|
194
|
-
// has emailed a magic link. Tell the user to check their inbox.
|
|
187
|
+
const history = await machine.checkpoints.list();
|
|
188
|
+
const checkpoint = history.checkpoints.at(-1);
|
|
189
|
+
if (checkpoint) {
|
|
190
|
+
await machine.checkpoints.restore(checkpoint.id);
|
|
195
191
|
}
|
|
196
|
-
|
|
197
|
-
// Or request a magic link explicitly
|
|
198
|
-
await client.requestMagicLink(email);
|
|
199
192
|
```
|
|
200
193
|
|
|
201
|
-
The
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
> **⚠️ Magic links open the website, not the app, until Universal Links / App
|
|
205
|
-
> Links are configured.** The emailed link is an `https://` URL for the Rool
|
|
206
|
-
> web app. On native, an `https` link only re-opens your app if you've set up
|
|
207
|
-
> [iOS Universal Links](https://developer.apple.com/ios/universal-links/) /
|
|
208
|
-
> [Android App Links](https://developer.android.com/training/app-links) for that
|
|
209
|
-
> domain (custom-scheme deep links don't apply to email links). **Without that
|
|
210
|
-
> setup the magic link completes sign-in in the browser/website, not in the
|
|
211
|
-
> native app** — so for now treat magic links on native as a website hand-off,
|
|
212
|
-
> and prefer password or social sign-in for an in-app experience.
|
|
213
|
-
|
|
214
|
-
### Auth API
|
|
215
|
-
|
|
216
|
-
| Method | Description |
|
|
217
|
-
| --- | --- |
|
|
218
|
-
| `initialize(): Promise<boolean>` | Call on startup. Initializes auth, refreshes user/storage state, and starts client events when authenticated. |
|
|
219
|
-
| `login(appName, params?): Promise<void>` | Start login flow. |
|
|
220
|
-
| `signup(appName, params?): Promise<void>` | Start signup flow. |
|
|
221
|
-
| `verify(token): Promise<boolean>` | Complete email verification token flow; returns `false` when the active auth provider does not implement verification. |
|
|
222
|
-
| `handleAuthRedirect(url): Promise<boolean>` | Complete a native PKCE sign-in from a deep-link callback URL. Returns `false` when the active auth provider does not implement it. |
|
|
223
|
-
| `signInWithPassword(email, password): Promise<PasswordSignInResult>` | Email + password sign-in (native provider). Resolves `{ status: 'signed_in' }`, or `{ status: 'verify_required' }` when the email is unverified (a magic link was emailed). Rejects on bad credentials. |
|
|
224
|
-
| `requestMagicLink(email): Promise<void>` | Email the user a magic sign-in link (native provider). See the caveat below — on native the link opens the **website**, not the app, until Universal Links / App Links are configured. |
|
|
225
|
-
| `logout(): void` | Clear auth state and close open spaces. |
|
|
226
|
-
| `isAuthenticated(): Promise<boolean>` | Whether credentials are held locally. No network call — a server outage does not read as logged out. |
|
|
227
|
-
| `getAuthUser(): AuthUser` | Return auth identity decoded from the token. |
|
|
228
|
-
| `setPassword(password): Promise<void>` | Set/change password and sign out the user's other sessions. |
|
|
229
|
-
| `requestEmailChange(newEmail): Promise<void>` | Start an email address change. The server mails a confirmation link to the new address; the change applies when that link is clicked. |
|
|
230
|
-
|
|
231
|
-
### Changing email
|
|
232
|
-
|
|
233
|
-
`requestEmailChange` starts the flow; nothing changes until the user clicks the confirmation link mailed to the new address. After confirmation the current session no longer belongs to the account — the next API call fails with a 401, which ends the session via `authStateChanged(false)`. Have the user sign in again with the new address.
|
|
194
|
+
The checkpoint collection contains the currently restorable timeline and the `baseCheckpointId` underlying the live filesystem. The live filesystem can contain newer uncheckpointed changes. Restoring a checkpoint preserves those changes as a new checkpoint and causes a watched machine file tree to reconcile completely. Moving backward does not discard later checkpoints, but modifying the filesystem from that earlier position replaces the later timeline.
|
|
195
|
+
|
|
196
|
+
## Machine files
|
|
234
197
|
|
|
235
198
|
```typescript
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
199
|
+
const files = machine.files;
|
|
200
|
+
const path = "/rool-drive/documents/report.pdf";
|
|
201
|
+
|
|
202
|
+
const storage = await files.getStorageUsage();
|
|
203
|
+
console.log(storage.usedBytes, storage.availableBytes);
|
|
204
|
+
await files.createDirectory("/rool-drive/documents");
|
|
205
|
+
const written = await files.write(path, reportBlob, {
|
|
206
|
+
contentType: "application/pdf",
|
|
207
|
+
ifNoneMatch: "*",
|
|
208
|
+
onUploadProgress: ({ transferredBytes, totalBytes }) => {
|
|
209
|
+
if (totalBytes) renderUploadProgress(transferredBytes / totalBytes);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
const info = await files.stat(path);
|
|
213
|
+
const documents = await files.list("/rool-drive/documents");
|
|
214
|
+
const response = await files.read(path, {
|
|
215
|
+
range: { start: 0, end: 1023 },
|
|
216
|
+
ifMatch: info.etag,
|
|
217
|
+
});
|
|
218
|
+
const hydrated = await files.readMultiple([
|
|
219
|
+
"/space/.meta.json",
|
|
220
|
+
"/rool-drive/documents/notes.json",
|
|
221
|
+
]);
|
|
222
|
+
await files.copy(path, "/rool-drive/documents/report-backup.pdf", {
|
|
223
|
+
overwrite: false,
|
|
224
|
+
ifMatch: written.etag,
|
|
225
|
+
});
|
|
226
|
+
await files.move(path, "/rool-drive/documents/final-report.pdf");
|
|
227
|
+
const deleted = await files.deleteMultiple([
|
|
228
|
+
"/rool-drive/documents/final-report.pdf",
|
|
229
|
+
{ path: "/rool-drive/documents/notes.json", ifMatch: '"notes-etag"' },
|
|
230
|
+
]);
|
|
231
|
+
for (const result of deleted) {
|
|
232
|
+
if (!result.ok)
|
|
233
|
+
console.error(`Failed to delete ${result.path}`, result.error);
|
|
245
234
|
}
|
|
246
235
|
```
|
|
247
236
|
|
|
248
|
-
|
|
237
|
+
Paths are absolute machine paths under `/space` or `/rool-drive`. `list()` without a path enumerates those storage roots; pass `{ recursive: true }` to enumerate a complete subtree. File and directory metadata has a discriminating `kind` field. Reads return the native `Response` so callers can stream the body. `readMultiple()` hydrates ordered small files in one request and returns an `ok` result with binary-safe bytes and validators, or a per-file HTTP failure. A batch accepts at most 128 paths, 2 MiB per successful file, and 16 MiB across successful files. Writes accept any `BodyInit`, including `Blob` and `ReadableStream`, and return the same complete file metadata as `stat()` and `list()`. `onUploadProgress` reports transferred bytes and includes the total size when it is known; successful completion is confirmed by the `write()` promise. Copy and move operations overwrite by default; pass `{ overwrite: false }` for create-only behavior.
|
|
249
238
|
|
|
250
|
-
|
|
251
|
-
| --- | --- |
|
|
252
|
-
| `invalid_email` | The new address is not a valid email. |
|
|
253
|
-
| `same_email` | The new address is the account's current address. |
|
|
254
|
-
| `email_in_use` | Another account already uses the new address. |
|
|
255
|
-
| `send_failed` | The confirmation email could not be sent. |
|
|
239
|
+
`deleteMultiple()` sends independent DAV requests with at most eight in flight. It accepts plain paths and targets carrying their own HTTP preconditions, and returns one ordered success or failure result per target. The requests are not atomic. A directory target recursively deletes its contents, so callers should omit redundant descendants; duplicate and overlapping targets otherwise remain independent and can race.
|
|
256
240
|
|
|
257
|
-
|
|
241
|
+
Every file and directory has protected `access` metadata. `currentUser` says whether the requesting user can read or write it. `readableBy` and `writableBy` describe its filesystem audiences as `resource-owner`, `machine-admins`, `machine-editors`, and `machine-members`. For a file, write means changing its contents. For a directory, write means adding, removing, or renaming entries. Members without read access to a directory do not receive that directory through listing, direct lookup, or synchronization.
|
|
258
242
|
|
|
259
|
-
|
|
243
|
+
`getStorageUsage()` reports the used and writable bytes on the machine's complete live persistent filesystem, including public files and private runtime or system state. It excludes checkpoint history and the ephemeral operating-system overlay.
|
|
260
244
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Open a space to receive live events and manage objects, schema, metadata, collaborators, file storage, and conversations. Use a conversation handle for conversation metadata and AI.
|
|
245
|
+
Watch the files when an application needs a live machine file tree:
|
|
264
246
|
|
|
265
247
|
```typescript
|
|
266
|
-
|
|
248
|
+
await files.watch();
|
|
249
|
+
|
|
250
|
+
const unsubscribe = files.tree.subscribe(({ reset, changed, deleted }) => {
|
|
251
|
+
renderFileChanges({ reset, changed, deleted });
|
|
252
|
+
});
|
|
267
253
|
|
|
268
|
-
const
|
|
269
|
-
|
|
254
|
+
const cached = files.tree.get("/rool-drive/documents/final-report.pdf");
|
|
255
|
+
const etag = files.tree.etag("/rool-drive/documents/final-report.pdf");
|
|
256
|
+
const allDocuments = files.tree.list("/rool-drive/documents", {
|
|
257
|
+
recursive: true,
|
|
258
|
+
});
|
|
270
259
|
|
|
271
|
-
|
|
260
|
+
unsubscribe();
|
|
261
|
+
files.unwatch();
|
|
272
262
|
```
|
|
273
263
|
|
|
274
|
-
|
|
264
|
+
`files.watch()` performs a complete `sync-collection`, then keeps the machine's file metadata and ETag cache current with long-poll incremental reports. DAV writes and guest-program changes enter the same tree. An invalid or expired sync token causes an atomic complete reconciliation and a change event with `reset: true`. Transient sync errors are retried and available as `files.watchError`; `files.unwatch()` aborts the active long poll.
|
|
275
265
|
|
|
276
|
-
## Object
|
|
266
|
+
## Rool Object Collections
|
|
277
267
|
|
|
278
|
-
Objects are JSON
|
|
268
|
+
Objects are JSON stored under `/space`. Collections are directories with a `.schema.json` definition and objects are schema-checked JSON files.
|
|
279
269
|
|
|
280
270
|
```typescript
|
|
281
|
-
await
|
|
282
|
-
{ name: 'title', type: { kind: 'string' } },
|
|
283
|
-
{ name: 'status', type: { kind: 'string' } },
|
|
284
|
-
]);
|
|
271
|
+
await machine.files.watch();
|
|
285
272
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
273
|
+
const task = await machine.collections.create("task", {
|
|
274
|
+
fields: [
|
|
275
|
+
{ name: "title", type: { kind: "string" } },
|
|
276
|
+
{ name: "done", type: { kind: "boolean" } },
|
|
277
|
+
],
|
|
290
278
|
});
|
|
291
279
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
280
|
+
const first = await machine.objects.create("/space/task/first.json", {
|
|
281
|
+
title: "First task",
|
|
282
|
+
done: false,
|
|
295
283
|
});
|
|
284
|
+
const objectPaths = machine.objects.list({ collection: "task" });
|
|
285
|
+
const object = await machine.objects.get(first.path);
|
|
286
|
+
const [sameObject, missing] = await machine.objects.getMultiple([
|
|
287
|
+
first.path,
|
|
288
|
+
"/space/task/missing.json",
|
|
289
|
+
]);
|
|
296
290
|
|
|
297
|
-
|
|
298
|
-
await
|
|
299
|
-
await
|
|
300
|
-
|
|
301
|
-
'/space/article/intro.json',
|
|
291
|
+
await machine.objects.patch(first.path, { done: true });
|
|
292
|
+
await machine.objects.move(first.path, "/space/task/renamed.json");
|
|
293
|
+
const [removal] = await machine.objects.removeMultiple([
|
|
294
|
+
"/space/task/renamed.json",
|
|
302
295
|
]);
|
|
296
|
+
if (!removal) throw new Error("Object removal returned no result");
|
|
297
|
+
if (!removal.ok) throw removal.error;
|
|
298
|
+
await machine.collections.remove(task.name);
|
|
299
|
+
```
|
|
303
300
|
|
|
304
|
-
|
|
305
|
-
await space.moveObject(
|
|
306
|
-
'/space/article/welcome.json',
|
|
307
|
-
'/space/article/hello-world.json'
|
|
308
|
-
);
|
|
301
|
+
`machine.objects.list()` returns object paths from the synchronized file tree without reading their bodies. `get()` reads one object and `getMultiple()` preserves the input positions and returns `undefined` for missing objects. Every call reads the current bodies from DAV, with multiple reads using bounded `read-multiple` batches. Collection schemas are read in the same way and schema replacement follows the guest's lazy-migration rule: existing objects are checked again only when edited.
|
|
309
302
|
|
|
310
|
-
|
|
311
|
-
await space.deleteObjects(['/space/article/hello-world.json']);
|
|
312
|
-
```
|
|
303
|
+
Creates are create-only. Metadata, schema, and object replacements plus object patches, moves, and removals use ETags and report status `412` when state changed concurrently. `removeMultiple()` uses a separate conditional DAV request for each object and returns ordered per-object results. Removing a collection recursively deletes its contents. Object moves do not overwrite by default; pass `{ overwrite: true }` explicitly. Patch values of `null` or `undefined` remove fields.
|
|
313
304
|
|
|
314
|
-
|
|
315
|
-
| --- | --- |
|
|
316
|
-
| `getObject(path): Promise<RoolObject | undefined>` | Fetch one object by object path. |
|
|
317
|
-
| `getObjects(paths): Promise<GetObjectsResult>` | Fetch objects in bulk; returns `objects` and `missing`. |
|
|
318
|
-
| `space.putObject(path, body): Promise<{ object, message }>` | Create or replace an object at an exact path. |
|
|
319
|
-
| `space.patchObject(path, { data }): Promise<{ object, message }>` | Patch an object's body; `null`/`undefined` deletes fields. |
|
|
320
|
-
| `space.moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object; `options.body` can replace the body after moving. |
|
|
321
|
-
| `space.deleteObjects(paths): Promise<void>` | Delete object files. |
|
|
305
|
+
The semantic APIs use the machine's shared file tree and sync loop. They do not create separate synchronization state. Body reads and mutations also work without `machine.files.watch()` by reading current DAV state directly; `objects.list()` reflects the shared file tree, so watch the machine's files before enumerating paths.
|
|
322
306
|
|
|
323
|
-
##
|
|
307
|
+
## Agents
|
|
324
308
|
|
|
325
|
-
|
|
309
|
+
Agents and conversations use stable JSON routes; their private machine files are not part of the SDK. Prompting does not require `machine.files.watch()`.
|
|
326
310
|
|
|
327
311
|
```typescript
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
312
|
+
const defaultAgent = await machine.agents.get("rool");
|
|
313
|
+
if (!defaultAgent) throw new Error("Rool agent is unavailable");
|
|
314
|
+
|
|
315
|
+
const conversation = defaultAgent.conversation("research-chat");
|
|
316
|
+
await conversation.prompt("Explain the result.", { effort: "reasoning" });
|
|
317
|
+
|
|
318
|
+
renderSettled(await conversation.listTurns());
|
|
319
|
+
clearUnsettled();
|
|
320
|
+
await conversation.follow({
|
|
321
|
+
onEvent: (event) => {
|
|
322
|
+
if (event.type !== "output.delta") return;
|
|
323
|
+
if (event.content.type === "text") {
|
|
324
|
+
renderUnsettledText(event.content.text);
|
|
325
|
+
} else if (event.content.type === "tool_call") {
|
|
326
|
+
showRunningTool(event.content.id, event.content.name);
|
|
327
|
+
} else if (event.content.type === "tool_result") {
|
|
328
|
+
showToolResult(event.content.id, event.content.content);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
renderSettled(await conversation.listTurns());
|
|
334
333
|
```
|
|
335
334
|
|
|
336
|
-
|
|
335
|
+
`prompt()` starts the conversation's current run and resolves once the server accepts it. The agent runs as a detached job. A conversation can only have one current run; call `cancel()` and wait for `follow()` to finish before prompting again. The `readOnly` option is accepted for compatibility with legacy prompting but currently has no effect.
|
|
337
336
|
|
|
338
|
-
|
|
339
|
-
| --- | --- |
|
|
340
|
-
| `responseSchema` | Request structured JSON text matching a JSON-schema-like shape. |
|
|
341
|
-
| `effort` | `'QUICK'` (fast/read-only), `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'`. |
|
|
342
|
-
| `ephemeral` | Do not record the prompt in interaction history. |
|
|
343
|
-
| `readOnly` | Disable mutation tools. |
|
|
344
|
-
| `parentInteractionId` | Conversation-tree parent. Omit to fetch and continue from the current default leaf; pass `null` for a new root branch. |
|
|
345
|
-
| `attachments` | Existing object/file paths or `rool-machine:/...` URIs, plus local files (`File`, `Blob`, or `{ data, contentType, filename? }`). |
|
|
346
|
-
| `signal` | AbortSignal used to request that the server stop an in-flight prompt. |
|
|
347
|
-
| `eventName` | Optional telemetry event name. Defaults to `'prompt_user'`. |
|
|
337
|
+
`follow()` performs one `GET` of the conversation's current run. It receives the complete unsettled part of the conversation and then continues with new events until that response ends. Tool calls and their results arrive as `output.delta` events with matching IDs. A tool result contains nested content parts and an optional `error` flag. `follow()` returns `false` when there is no current run. A client can always render the conversation from its durable turns plus the events from its latest `follow()` call.
|
|
348
338
|
|
|
349
|
-
|
|
350
|
-
// Read-only quick question
|
|
351
|
-
await conversation.prompt('What topics are covered?', {
|
|
352
|
-
effort: 'QUICK', // fast/read-only
|
|
353
|
-
});
|
|
339
|
+
A `conversation_changed` account event tells clients to fetch the durable turns and follow the current run again. Aborting `follow()` only stops watching. Call `cancel()` to stop the detached job.
|
|
354
340
|
|
|
355
|
-
|
|
356
|
-
await conversation.prompt('Compare these resources', {
|
|
357
|
-
attachments: [
|
|
358
|
-
'/space/article/intro.json',
|
|
359
|
-
'rool-machine:/rool-drive/docs/report.pdf',
|
|
360
|
-
],
|
|
361
|
-
});
|
|
341
|
+
Prompt attachments are existing `/space` or `/rool-drive` paths. Pass a durable user turn's `id` as `replaceTurnId` to replace that message and everything after it. This supports edits and rerolls, including the first message. A replacement gets a new user turn ID; use that ID to edit it again.
|
|
362
342
|
|
|
363
|
-
|
|
364
|
-
await conversation.prompt('Describe this image', {
|
|
365
|
-
attachments: [fileInput.files![0]],
|
|
366
|
-
});
|
|
343
|
+
Pass a JSON Schema as `responseSchema` to request structured output. Tools are skipped for that run. The successful assistant turn contains one JSON content part; the value is JSON directly, not a JSON string.
|
|
367
344
|
|
|
368
|
-
|
|
369
|
-
|
|
345
|
+
```typescript
|
|
346
|
+
await conversation.prompt("Return the number of records.", {
|
|
370
347
|
responseSchema: {
|
|
371
|
-
type:
|
|
372
|
-
properties: {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
},
|
|
348
|
+
type: "object",
|
|
349
|
+
properties: { count: { type: "integer" } },
|
|
350
|
+
required: ["count"],
|
|
351
|
+
additionalProperties: false,
|
|
376
352
|
},
|
|
377
353
|
});
|
|
378
|
-
|
|
354
|
+
await conversation.follow();
|
|
355
|
+
|
|
356
|
+
const turns = await conversation.listTurns();
|
|
357
|
+
const part = turns.at(-1)?.body.content[0];
|
|
358
|
+
if (part?.type !== "json") throw new Error("No structured result");
|
|
359
|
+
console.log(part.value); // { count: ... }
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Custom agent definitions currently contain one plain system prompt. The server owns the executable agent implementation.
|
|
379
363
|
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
364
|
+
```typescript
|
|
365
|
+
const researcher = await machine.agents.create("researcher", {
|
|
366
|
+
system: "Investigate carefully and distinguish facts from uncertainty.",
|
|
367
|
+
});
|
|
368
|
+
const customConversation = await researcher.createConversation({
|
|
369
|
+
name: "Climate report",
|
|
370
|
+
visibility: "private",
|
|
385
371
|
});
|
|
386
|
-
|
|
387
|
-
await promptPromise;
|
|
372
|
+
await customConversation.prompt("Investigate this claim.");
|
|
388
373
|
```
|
|
389
374
|
|
|
390
|
-
|
|
375
|
+
Agents expose `replace()` and `delete()`. Conversations expose metadata replacement, listing, durable turn reads, rename, and deletion. Listed and fetched conversation metadata includes server-managed ISO 8601 `createdAt` and `updatedAt` timestamps plus `isRunning`, which can drive a running indicator without opening every run stream. Visibility defaults to private. The built-in `rool` agent cannot be replaced or deleted.
|
|
391
376
|
|
|
392
|
-
|
|
393
|
-
lives elsewhere — a different component, after a reload, or a prompt another
|
|
394
|
-
client started — stop the conversation itself. A conversation processes one
|
|
395
|
-
run at a time, so no interaction ID is needed. Stopping is best-effort: the
|
|
396
|
-
server halts the agent loop and closes the stream, but an LLM turn already in
|
|
397
|
-
flight keeps generating server-side and is billed.
|
|
377
|
+
## Members and invites
|
|
398
378
|
|
|
399
379
|
```typescript
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
380
|
+
const invite = await machine.invites.create({
|
|
381
|
+
role: "editor",
|
|
382
|
+
maxUses: 1,
|
|
383
|
+
});
|
|
384
|
+
const token = invite.url.split("/").at(-1)!;
|
|
403
385
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
386
|
+
await client.getInvitePreview(token);
|
|
387
|
+
await client.redeemInvite(token);
|
|
388
|
+
|
|
389
|
+
const members = await machine.members.list();
|
|
390
|
+
console.log(members[1].name ?? members[1].email);
|
|
391
|
+
await machine.members.replaceRole(members[1].userId, { role: "viewer" });
|
|
392
|
+
await machine.members.remove(members[1].userId);
|
|
393
|
+
await machine.invites.revoke(invite.id);
|
|
407
394
|
```
|
|
408
395
|
|
|
409
|
-
|
|
410
|
-
| --- | --- |
|
|
411
|
-
| `stopConversation(conversationId): Promise<boolean>` | Stop whatever is running in a conversation. |
|
|
412
|
-
| `conversation.stop(): Promise<boolean>` | Stop this conversation's running work. |
|
|
396
|
+
Invite URLs contain a secret token and are returned only when an invite is created. Invites can optionally be bound to an email address. Role replacement never creates membership or transfers ownership.
|
|
413
397
|
|
|
414
|
-
##
|
|
398
|
+
## Gifts
|
|
415
399
|
|
|
416
|
-
|
|
400
|
+
A gift carries something of value in a short code. Users receive gifts from Rool and give the codes away themselves. Claiming a gift is single-use, and claiming your own gift is allowed.
|
|
417
401
|
|
|
418
402
|
```typescript
|
|
419
|
-
|
|
403
|
+
const { gifts } = await client.listGifts();
|
|
404
|
+
for (const gift of gifts) {
|
|
405
|
+
console.log(gift.code, gift.url, gift.description, gift.claimedAt);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const preview = await client.previewGift(code); // no auth required
|
|
409
|
+
console.log(
|
|
410
|
+
`${preview.holderName ?? "Someone"} gave you ${preview.description}`,
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const { gift } = await client.claimGift(code);
|
|
414
|
+
if (gift.kind === "credits") console.log(`+${gift.credits} credits`);
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Codes are case-insensitive and the dash is optional. Prefer the server-rendered `description` for display. Narrow `gift.kind` when using the structured payload because new gift kinds may be added.
|
|
420
418
|
|
|
421
|
-
|
|
422
|
-
await thread.prompt('Hello from another thread');
|
|
423
|
-
await thread.setSystemInstruction('Answer in haiku');
|
|
419
|
+
A holder can add a note, archive a gift, or replace an unclaimed gift's code. These actions do not change what the gift grants. Archiving only hides it from the holder's normal view.
|
|
424
420
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
421
|
+
```typescript
|
|
422
|
+
await client.updateGift(giftId, { note: "sent to Peter" });
|
|
423
|
+
await client.updateGift(giftId, { archived: true });
|
|
424
|
+
await client.updateGift(giftId, { note: null });
|
|
425
|
+
const updated = await client.rotateGiftCode(giftId);
|
|
428
426
|
```
|
|
429
427
|
|
|
430
|
-
|
|
431
|
-
| --- | --- |
|
|
432
|
-
| `space.conversation(id): ConversationHandle` | Get a stateless conversation-scoped API handle. |
|
|
433
|
-
| `conversation.get(): Promise<Conversation | null>` | Fetch current conversation contents. |
|
|
434
|
-
| `conversation.prompt(text, options?)` | Prompt the conversation. Pass `parentInteractionId` to choose a branch explicitly. |
|
|
435
|
-
| `conversation.setSystemInstruction(value)` | Set or clear the system instruction. |
|
|
436
|
-
| `conversation.rename(name)` / `conversation.delete()` / `conversation.stop()` | Manage the scoped conversation. |
|
|
437
|
-
| `listConversations(): Promise<ConversationMeta[]>` | Fetch the conversation roster. |
|
|
438
|
-
| `createConversation(agent, visibility): Promise<string>` | Create a conversation under an agent; returns the server-minted conversation ID. |
|
|
439
|
-
| `deleteConversation(id): Promise<void>` | Delete a conversation by ID. |
|
|
428
|
+
Gift failures are `RoolProblem` errors. `gift_invalid` means the code or gift is unavailable to the caller. `gift_claimed` means it was already claimed.
|
|
440
429
|
|
|
441
|
-
|
|
430
|
+
## API problems
|
|
442
431
|
|
|
443
|
-
|
|
432
|
+
Each problem `type` links to its entry below.
|
|
444
433
|
|
|
445
|
-
|
|
434
|
+
<a id="problem-authentication_required"></a>
|
|
446
435
|
|
|
447
|
-
|
|
448
|
-
const agents = await space.listAgents(); // always includes 'rool'
|
|
436
|
+
### `authentication_required`
|
|
449
437
|
|
|
450
|
-
|
|
451
|
-
await space.conversation(id).prompt('Hello');
|
|
438
|
+
**Documentation placeholder.**
|
|
452
439
|
|
|
453
|
-
|
|
454
|
-
```
|
|
440
|
+
<a id="problem-invalid_authentication"></a>
|
|
455
441
|
|
|
456
|
-
|
|
457
|
-
| --- | --- |
|
|
458
|
-
| `listAgents(): Promise<string[]>` | The space's agents. Always includes the stock agent `rool`. |
|
|
459
|
-
| `createConversation(agent, visibility): Promise<string>` | Create a conversation under an agent with the given visibility. |
|
|
460
|
-
| `deleteAgent(agent): Promise<void>` | Delete a custom agent and all its conversations. The stock agent `rool` cannot be deleted. |
|
|
442
|
+
### `invalid_authentication`
|
|
461
443
|
|
|
462
|
-
|
|
444
|
+
**Documentation placeholder.**
|
|
463
445
|
|
|
464
|
-
|
|
446
|
+
<a id="problem-email_unverified"></a>
|
|
465
447
|
|
|
466
|
-
|
|
467
|
-
await space.createCollection('article', {
|
|
468
|
-
schemaOrgType: 'Article',
|
|
469
|
-
fields: [
|
|
470
|
-
{ name: 'title', type: { kind: 'string' } },
|
|
471
|
-
{ name: 'status', type: { kind: 'enum', values: ['draft', 'published'] } },
|
|
472
|
-
{ name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
|
|
473
|
-
{ name: 'author', type: { kind: 'ref' } },
|
|
474
|
-
],
|
|
475
|
-
});
|
|
448
|
+
### `email_unverified`
|
|
476
449
|
|
|
477
|
-
|
|
450
|
+
**Documentation placeholder.**
|
|
478
451
|
|
|
479
|
-
|
|
480
|
-
{ name: 'title', type: { kind: 'string' } },
|
|
481
|
-
{ name: 'status', type: { kind: 'string' } },
|
|
482
|
-
]);
|
|
452
|
+
<a id="problem-account_suspended"></a>
|
|
483
453
|
|
|
484
|
-
|
|
485
|
-
const meta = await space.readMeta();
|
|
486
|
-
```
|
|
454
|
+
### `account_suspended`
|
|
487
455
|
|
|
488
|
-
|
|
489
|
-
| --- | --- |
|
|
490
|
-
| `readSchema(): Promise<SpaceSchema>` | Collection definitions, read from `/space/<name>/.schema.json`. |
|
|
491
|
-
| `createCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Create a collection. |
|
|
492
|
-
| `alterCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Replace a collection definition. |
|
|
493
|
-
| `dropCollection(name): Promise<void>` | Remove a collection and its object directory. |
|
|
494
|
-
| `readMeta(): Promise<Record<string, unknown>>` | Read metadata from `/space/.meta.json`. |
|
|
495
|
-
| `writeMeta(meta): Promise<void>` | Write the full metadata blob to `/space/.meta.json`. |
|
|
456
|
+
**Documentation placeholder.**
|
|
496
457
|
|
|
497
|
-
|
|
458
|
+
<a id="problem-invalid_profile"></a>
|
|
498
459
|
|
|
499
|
-
|
|
460
|
+
### `invalid_profile`
|
|
500
461
|
|
|
501
|
-
|
|
462
|
+
**Documentation placeholder.**
|
|
502
463
|
|
|
503
|
-
|
|
504
|
-
await space.deleteObjects(['/space/article/welcome.json']);
|
|
464
|
+
<a id="problem-invalid_user_app_data"></a>
|
|
505
465
|
|
|
506
|
-
|
|
507
|
-
await space.undo();
|
|
508
|
-
}
|
|
509
|
-
```
|
|
466
|
+
### `invalid_user_app_data`
|
|
510
467
|
|
|
511
|
-
|
|
512
|
-
| --- | --- |
|
|
513
|
-
| `canUndo(): Promise<boolean>` | Check whether undo is available. |
|
|
514
|
-
| `canRedo(): Promise<boolean>` | Check whether redo is available. |
|
|
515
|
-
| `undo(): Promise<boolean>` | Restore the latest checkpoint. |
|
|
516
|
-
| `redo(): Promise<boolean>` | Reapply undone work. |
|
|
468
|
+
**Documentation placeholder.**
|
|
517
469
|
|
|
518
|
-
|
|
470
|
+
<a id="problem-invalid_json"></a>
|
|
519
471
|
|
|
520
|
-
|
|
472
|
+
### `invalid_json`
|
|
521
473
|
|
|
522
|
-
|
|
474
|
+
**Documentation placeholder.**
|
|
523
475
|
|
|
524
|
-
|
|
525
|
-
const webdav = space.webdav;
|
|
476
|
+
<a id="problem-payload_too_large"></a>
|
|
526
477
|
|
|
527
|
-
|
|
528
|
-
await webdav.put('/rool-drive/docs/readme.md', '# Hello', {
|
|
529
|
-
contentType: 'text/markdown',
|
|
530
|
-
ifNoneMatch: '*',
|
|
531
|
-
});
|
|
478
|
+
### `payload_too_large`
|
|
532
479
|
|
|
533
|
-
|
|
534
|
-
depth: '1',
|
|
535
|
-
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
536
|
-
});
|
|
480
|
+
**Documentation placeholder.**
|
|
537
481
|
|
|
538
|
-
|
|
539
|
-
console.log(await response.text());
|
|
482
|
+
<a id="problem-user_app_data_too_large"></a>
|
|
540
483
|
|
|
541
|
-
|
|
542
|
-
console.log(file.headers.get('Content-Type'));
|
|
484
|
+
### `user_app_data_too_large`
|
|
543
485
|
|
|
544
|
-
|
|
486
|
+
**Documentation placeholder.**
|
|
545
487
|
|
|
546
|
-
|
|
488
|
+
<a id="problem-not_found"></a>
|
|
547
489
|
|
|
548
|
-
|
|
490
|
+
### `not_found`
|
|
549
491
|
|
|
550
|
-
|
|
551
|
-
let token: string | null = null;
|
|
552
|
-
|
|
553
|
-
async function syncFiles() {
|
|
554
|
-
const result = await space.webdav.syncCollection('/', {
|
|
555
|
-
token,
|
|
556
|
-
level: 'infinite',
|
|
557
|
-
props: ['displayname', 'getetag', 'getlastmodified', 'resourcetype'],
|
|
558
|
-
});
|
|
559
|
-
token = result.token;
|
|
560
|
-
updateFileTree(result.responses);
|
|
561
|
-
}
|
|
492
|
+
**Documentation placeholder.**
|
|
562
493
|
|
|
563
|
-
|
|
564
|
-
space.on('filesReset', () => {
|
|
565
|
-
token = null;
|
|
566
|
-
void syncFiles();
|
|
567
|
-
});
|
|
494
|
+
<a id="problem-checkpoint_not_found"></a>
|
|
568
495
|
|
|
569
|
-
|
|
570
|
-
```
|
|
496
|
+
### `checkpoint_not_found`
|
|
571
497
|
|
|
572
|
-
|
|
573
|
-
| --- | --- |
|
|
574
|
-
| `webdav.href(path)` / `webdav.url(path)` | Return WebDAV href/URL for an absolute SDK path. |
|
|
575
|
-
| `webdav.options(path)` | Send `OPTIONS`. |
|
|
576
|
-
| `webdav.propfind(path, options)` | Read properties/list collections. `depth` is required. |
|
|
577
|
-
| `webdav.syncCollection(path, options)` | WebDAV `REPORT sync-collection`; returns changed responses and next token. |
|
|
578
|
-
| `webdav.get(path, options?)` / `webdav.head(path)` | Read a file; `get` supports byte ranges. |
|
|
579
|
-
| `webdav.put(path, body, options?)` | Write a file/object at an exact path. Parent collection must exist unless `createParents: true` is passed, which creates missing parent collections atomically (intended for `/rool-drive` paths). |
|
|
580
|
-
| `webdav.mkcol(path)` | Create one collection. |
|
|
581
|
-
| `webdav.copy(source, destination, options?)` | Copy a file or collection. |
|
|
582
|
-
| `webdav.move(source, destination, options?)` | Move a file or collection. |
|
|
583
|
-
| `webdav.delete(path, options?)` | Delete a file or collection. |
|
|
584
|
-
| `webdav.lock(path, options)` / `refreshLock(path, token)` / `unlock(token)` | WebDAV write locks. |
|
|
585
|
-
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request. |
|
|
586
|
-
| `space.fetchPath(path, options?)` | Fetch a `/rool-drive/...` file path or `rool-machine:` file URI. |
|
|
498
|
+
**Documentation placeholder.**
|
|
587
499
|
|
|
588
|
-
|
|
500
|
+
<a id="problem-sync_token_required"></a>
|
|
589
501
|
|
|
590
|
-
|
|
502
|
+
### `sync_token_required`
|
|
591
503
|
|
|
592
|
-
|
|
504
|
+
The account event route requires the sync token returned by `/v2/session`.
|
|
593
505
|
|
|
594
|
-
|
|
595
|
-
// Shareable invite link
|
|
596
|
-
const invite = await space.createInvite('editor', { expiresInDays: 7 });
|
|
597
|
-
console.log(invite.url);
|
|
598
|
-
|
|
599
|
-
// Email-guarded invite: single-use, locked to that address, sent by mail
|
|
600
|
-
const emailed = await space.createInvite('viewer', { email: 'colleague@example.com' });
|
|
601
|
-
if (emailed.emailStatus !== 'sent') {
|
|
602
|
-
// Mail did not go out (e.g. no mail provider configured) — share emailed.url yourself
|
|
603
|
-
}
|
|
506
|
+
<a id="problem-invalid_sync_token"></a>
|
|
604
507
|
|
|
605
|
-
|
|
606
|
-
const invites = await space.listInvites();
|
|
607
|
-
await space.revokeInvite(invites[0].inviteId);
|
|
508
|
+
### `invalid_sync_token`
|
|
608
509
|
|
|
609
|
-
|
|
610
|
-
await space.setUserRole(userId, 'admin');
|
|
611
|
-
await space.removeUser(userId);
|
|
612
|
-
```
|
|
510
|
+
The account event history no longer contains everything after this token. Fetch `/v2/session` and continue with its new token.
|
|
613
511
|
|
|
614
|
-
|
|
512
|
+
<a id="problem-invalid_wait_preference"></a>
|
|
615
513
|
|
|
616
|
-
|
|
617
|
-
const preview = await client.previewInvite(token); // no auth required
|
|
618
|
-
console.log(preview.spaceName, preview.role, preview.inviterName);
|
|
514
|
+
### `invalid_wait_preference`
|
|
619
515
|
|
|
620
|
-
|
|
621
|
-
console.log(result.spaceId, result.status); // 'joined' | 'upgraded' | 'already_member'
|
|
622
|
-
```
|
|
516
|
+
The account event wait must be an integer from 0 through 50 seconds.
|
|
623
517
|
|
|
624
|
-
|
|
518
|
+
<a id="problem-internal_error"></a>
|
|
625
519
|
|
|
626
|
-
|
|
627
|
-
import { InviteError } from '@rool-dev/sdk';
|
|
520
|
+
### `internal_error`
|
|
628
521
|
|
|
629
|
-
|
|
630
|
-
await client.redeemInvite(token);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
if (error instanceof InviteError && error.code === 'INVITE_EXPIRED') {
|
|
633
|
-
// Ask for a fresh invite
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
```
|
|
522
|
+
**Documentation placeholder.**
|
|
637
523
|
|
|
638
|
-
|
|
524
|
+
<a id="problem-server_misconfigured"></a>
|
|
639
525
|
|
|
640
|
-
|
|
641
|
-
| --- | --- |
|
|
642
|
-
| `owner` | Full control. |
|
|
643
|
-
| `admin` | Editor capabilities plus user/link management. |
|
|
644
|
-
| `editor` | Create, modify, move, and delete objects/files. |
|
|
645
|
-
| `viewer` | Read-only access. |
|
|
526
|
+
### `server_misconfigured`
|
|
646
527
|
|
|
647
|
-
|
|
528
|
+
**Documentation placeholder.**
|
|
648
529
|
|
|
649
|
-
|
|
530
|
+
<a id="problem-current_run_exists"></a>
|
|
650
531
|
|
|
651
|
-
|
|
532
|
+
### `current_run_exists`
|
|
652
533
|
|
|
653
|
-
|
|
534
|
+
The conversation is already running. Cancel or follow that run before prompting again. This also applies to prompts with `replaceTurnId`.
|
|
654
535
|
|
|
655
|
-
|
|
536
|
+
<a id="problem-replace_turn_not_found"></a>
|
|
656
537
|
|
|
657
|
-
|
|
658
|
-
// The current user's gifts, spent and unspent
|
|
659
|
-
const { gifts } = await client.listGifts();
|
|
660
|
-
for (const gift of gifts) {
|
|
661
|
-
console.log(gift.code, gift.url, gift.description, gift.claimedAt);
|
|
662
|
-
}
|
|
663
|
-
```
|
|
538
|
+
### `replace_turn_not_found`
|
|
664
539
|
|
|
665
|
-
|
|
540
|
+
The user turn supplied as `replaceTurnId` is no longer in the conversation. Fetch the turns again before retrying the edit.
|
|
666
541
|
|
|
667
|
-
|
|
668
|
-
const preview = await client.previewGift(code); // no auth required
|
|
669
|
-
console.log(`${preview.holderName} gave you ${preview.description}`);
|
|
542
|
+
<a id="problem-invalid_member_role"></a>
|
|
670
543
|
|
|
671
|
-
|
|
672
|
-
if (gift.kind === 'credits') console.log(`+${gift.credits} credits`);
|
|
673
|
-
```
|
|
544
|
+
### `invalid_member_role`
|
|
674
545
|
|
|
675
|
-
|
|
546
|
+
**Documentation placeholder.**
|
|
676
547
|
|
|
677
|
-
|
|
548
|
+
<a id="problem-role_not_replaceable"></a>
|
|
678
549
|
|
|
679
|
-
|
|
550
|
+
### `role_not_replaceable`
|
|
680
551
|
|
|
681
|
-
|
|
552
|
+
**Documentation placeholder.**
|
|
682
553
|
|
|
683
|
-
|
|
684
|
-
await client.updateGift(gift.id, { note: 'sent to Peter' });
|
|
685
|
-
await client.updateGift(gift.id, { archived: true });
|
|
686
|
-
await client.updateGift(gift.id, { note: null }); // clear the note
|
|
554
|
+
<a id="problem-membership_not_removable"></a>
|
|
687
555
|
|
|
688
|
-
|
|
689
|
-
console.log(updated.code, updated.url); // the old code and link are dead
|
|
690
|
-
```
|
|
556
|
+
### `membership_not_removable`
|
|
691
557
|
|
|
692
|
-
|
|
558
|
+
**Documentation placeholder.**
|
|
693
559
|
|
|
694
|
-
|
|
695
|
-
| --- | --- |
|
|
696
|
-
| `gift_invalid` | No gift matches that code. |
|
|
697
|
-
| `gift_claimed` | The gift has already been claimed. |
|
|
560
|
+
<a id="problem-invalid_invite"></a>
|
|
698
561
|
|
|
699
|
-
|
|
700
|
-
import { GiftError } from '@rool-dev/sdk';
|
|
562
|
+
### `invalid_invite`
|
|
701
563
|
|
|
702
|
-
|
|
703
|
-
await client.claimGift(code);
|
|
704
|
-
} catch (error) {
|
|
705
|
-
if (error instanceof GiftError && error.code === 'gift_claimed') {
|
|
706
|
-
// Offer a plain signup instead
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
```
|
|
564
|
+
**Documentation placeholder.**
|
|
710
565
|
|
|
711
|
-
|
|
566
|
+
<a id="problem-invite_invalid"></a>
|
|
712
567
|
|
|
713
|
-
###
|
|
568
|
+
### `invite_invalid`
|
|
714
569
|
|
|
715
|
-
|
|
716
|
-
const client = new RoolClient({
|
|
717
|
-
apiUrl: 'https://api.rool.dev',
|
|
718
|
-
authUrl: 'https://rool.dev/auth',
|
|
719
|
-
graphqlUrl: 'https://api.rool.dev/graphql',
|
|
720
|
-
client: {
|
|
721
|
-
appName: 'com.example.app',
|
|
722
|
-
appVersion: '1.4.2',
|
|
723
|
-
osVersion: 'iOS 17.5',
|
|
724
|
-
},
|
|
725
|
-
logger: console,
|
|
726
|
-
});
|
|
727
|
-
```
|
|
570
|
+
**Documentation placeholder.**
|
|
728
571
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
`client` is optional application identity sent on requests alongside the SDK package name/version. Compatibility is based only on the SDK version.
|
|
732
|
-
|
|
733
|
-
| Method/property | Description |
|
|
734
|
-
| --- | --- |
|
|
735
|
-
| `currentUser: CurrentUser | null` | Cached user profile from initialization/fetch. |
|
|
736
|
-
| `getCurrentUser(): Promise<CurrentUser>` | Fetch current user. |
|
|
737
|
-
| `updateCurrentUser(input): Promise<CurrentUser>` | Update `name`, `slug`, or `marketingOptIn`. |
|
|
738
|
-
| `deleteCurrentUser(): Promise<void>` | Mark account for deletion and log out. |
|
|
739
|
-
| `previewInvite(token): Promise<InvitePreview>` | Look up an invite link without redeeming it. No auth required. |
|
|
740
|
-
| `redeemInvite(token): Promise<InviteRedeemResult>` | Redeem an invite, joining (or upgrading in) its space. |
|
|
741
|
-
| `previewGift(code): Promise<GiftPreview>` | Look up a gift without claiming it. No auth required. |
|
|
742
|
-
| `claimGift(code): Promise<GiftClaimResult>` | Claim a gift, granting what it holds to the current account. |
|
|
743
|
-
| `listGifts(): Promise<GiftList>` | The current user's gifts, spent and unspent. |
|
|
744
|
-
| `updateGift(giftId, changes): Promise<Gift>` | Set or clear a gift's note, or change its archived state. |
|
|
745
|
-
| `rotateGiftCode(giftId): Promise<Gift>` | Mint a new code for a gift; the old code and link stop working. |
|
|
746
|
-
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List accessible spaces. |
|
|
747
|
-
| `openSpace(id): Promise<RoolSpace>` | Open/cached live space handle. |
|
|
748
|
-
| `createSpace(name): Promise<RoolSpace>` | Create and open a space. |
|
|
749
|
-
| `duplicateSpace(sourceId, name): Promise<RoolSpace>` | Duplicate a space. |
|
|
750
|
-
| `deleteSpace(id): Promise<void>` | Permanently delete a space. |
|
|
751
|
-
| `importArchive(name, archive): Promise<RoolSpace>` | Import a zip archive as a new space. |
|
|
752
|
-
| `getUserStorage<T>(key): T | undefined` | Sync read from user-storage cache. |
|
|
753
|
-
| `setUserStorage(key, value): void` | Update user storage; `null`/`undefined` deletes. |
|
|
754
|
-
| `getAllUserStorage(): Record<string, unknown>` | Copy all cached user storage. |
|
|
755
|
-
| `reportEvent(event, url?): void` | Fire-and-forget telemetry event. |
|
|
756
|
-
| `destroy(): void` | Close subscriptions, spaces, auth resources, and listeners. |
|
|
757
|
-
| `generateId(): string` | Generate a unique ID suitable for conversation IDs. |
|
|
758
|
-
|
|
759
|
-
### Client events
|
|
572
|
+
<a id="problem-invite_expired"></a>
|
|
760
573
|
|
|
761
|
-
|
|
762
|
-
client.on('authStateChanged', (authenticated) => void 0);
|
|
763
|
-
client.on('currentUserChanged', (user) => void 0); // CurrentUser | null; null on sign-out
|
|
764
|
-
client.on('spaceAdded', (space) => void 0);
|
|
765
|
-
client.on('spaceRemoved', (spaceId) => void 0);
|
|
766
|
-
client.on('spaceRenamed', (spaceId, newName) => void 0);
|
|
767
|
-
client.on('userStorageChanged', ({ key, value, source }) => void 0);
|
|
768
|
-
client.on('connectionStateChanged', (state) => void 0);
|
|
769
|
-
client.on('error', (error, context) => void 0);
|
|
770
|
-
client.on('serverInfoChanged', (info) => void 0);
|
|
771
|
-
client.on('unsupported', (info) => void 0); // SDK older than server minimum
|
|
772
|
-
```
|
|
574
|
+
### `invite_expired`
|
|
773
575
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
Properties: `id`, `name`, `role`, `memberCount`, `openSpaceResult`, `route`, `webdav`.
|
|
777
|
-
|
|
778
|
-
| Method | Description |
|
|
779
|
-
| --- | --- |
|
|
780
|
-
| `conversation(conversationId): ConversationHandle` | Get a stateless conversation-scoped handle. |
|
|
781
|
-
| `getObject`, `getObjects` | Read object data. |
|
|
782
|
-
| `listConversations`, `createConversation`, `deleteConversation` | Fetch, create, and delete conversations. |
|
|
783
|
-
| `listAgents`, `deleteAgent` | List the space's agents and delete a custom agent. |
|
|
784
|
-
| `readMeta`, `writeMeta`, `readSchema` | Read and write WebDAV-backed metadata and schema. |
|
|
785
|
-
| `canUndo`, `canRedo`, `undo`, `redo` | Space history controls. |
|
|
786
|
-
| `stopConversation(conversationId): Promise<boolean>` | Stop whatever is running in a conversation. |
|
|
787
|
-
| `fetch(url, init?): Promise<Response>` | Proxy an external HTTP request through the server to bypass browser CORS. |
|
|
788
|
-
| `close(): void` | Stop the space subscription. |
|
|
789
|
-
| `rename(newName): Promise<void>` | Rename the space. |
|
|
790
|
-
| `delete(): Promise<void>` | Permanently delete the space. |
|
|
791
|
-
| `listUsers(): Promise<SpaceMember[]>` | List collaborators. |
|
|
792
|
-
| `setUserRole(userId, role): Promise<void>` | Change an existing member's role. |
|
|
793
|
-
| `removeUser(userId): Promise<void>` | Remove collaborator. |
|
|
794
|
-
| `createInvite(role, options?): Promise<SpaceInviteCreated>` | Mint an invite link; `options` takes `email`, `expiresInDays`, `maxUses`. |
|
|
795
|
-
| `listInvites(): Promise<SpaceInvite[]>` | List currently redeemable invites. |
|
|
796
|
-
| `revokeInvite(inviteId): Promise<boolean>` | Revoke an invite so its link stops working. |
|
|
797
|
-
| `exportArchive(): Promise<Blob>` | Export a space archive. |
|
|
798
|
-
| `refresh(): Promise<OpenSpaceResult>` | Refresh identity, access, member count, and conversation metadata. |
|
|
799
|
-
| `fetchPath(path, options?): Promise<Response>` | Fetch a `/rool-drive/...` file. |
|
|
800
|
-
|
|
801
|
-
Events:
|
|
576
|
+
**Documentation placeholder.**
|
|
802
577
|
|
|
803
|
-
|
|
804
|
-
space.on('conversationUpdated', ({ conversationId, conversation, source, timestamp }) => void 0);
|
|
805
|
-
space.on('syncError', (error) => void 0);
|
|
806
|
-
space.on('filesChanged', ({ spaceId, source, timestamp }) => void 0);
|
|
807
|
-
space.on('filesReset', ({ spaceId, source, timestamp }) => void 0);
|
|
808
|
-
space.on('connectionStateChanged', (state) => void 0);
|
|
809
|
-
```
|
|
810
|
-
## Import/Export
|
|
578
|
+
<a id="problem-invite_revoked"></a>
|
|
811
579
|
|
|
812
|
-
|
|
813
|
-
const archive = await space.exportArchive();
|
|
814
|
-
const imported = await client.importArchive('Imported Data', archive);
|
|
815
|
-
```
|
|
580
|
+
### `invite_revoked`
|
|
816
581
|
|
|
817
|
-
|
|
582
|
+
**Documentation placeholder.**
|
|
818
583
|
|
|
819
|
-
|
|
584
|
+
<a id="problem-invite_exhausted"></a>
|
|
820
585
|
|
|
821
|
-
|
|
822
|
-
// Outcome of signInWithPassword. 'verify_required' means the account's email
|
|
823
|
-
// isn't verified yet and the server has emailed a magic link.
|
|
824
|
-
type PasswordSignInResult = { status: 'signed_in' | 'verify_required' };
|
|
825
|
-
|
|
826
|
-
type FieldType =
|
|
827
|
-
| { kind: 'string' }
|
|
828
|
-
| { kind: 'number' }
|
|
829
|
-
| { kind: 'boolean' }
|
|
830
|
-
| { kind: 'array'; inner?: FieldType }
|
|
831
|
-
| { kind: 'maybe'; inner: FieldType }
|
|
832
|
-
| { kind: 'enum'; values: string[] }
|
|
833
|
-
| { kind: 'literal'; value: string | number | boolean }
|
|
834
|
-
| { kind: 'ref' };
|
|
835
|
-
|
|
836
|
-
interface FieldDef {
|
|
837
|
-
name: string;
|
|
838
|
-
type: FieldType;
|
|
839
|
-
}
|
|
586
|
+
### `invite_exhausted`
|
|
840
587
|
|
|
841
|
-
|
|
842
|
-
fields: FieldDef[];
|
|
843
|
-
schemaOrgType?: string;
|
|
844
|
-
}
|
|
588
|
+
**Documentation placeholder.**
|
|
845
589
|
|
|
846
|
-
|
|
590
|
+
<a id="problem-invite_email_mismatch"></a>
|
|
847
591
|
|
|
848
|
-
|
|
849
|
-
path: string;
|
|
850
|
-
body: Record<string, unknown>;
|
|
851
|
-
}
|
|
592
|
+
### `invite_email_mismatch`
|
|
852
593
|
|
|
853
|
-
|
|
854
|
-
objects: RoolObject[];
|
|
855
|
-
missing: string[];
|
|
856
|
-
}
|
|
594
|
+
**Documentation placeholder.**
|
|
857
595
|
|
|
858
|
-
|
|
859
|
-
// owner-only; 'temporary' is private plus auto-delete once it sits idle.
|
|
860
|
-
type ConversationVisibility = 'shared' | 'private' | 'temporary';
|
|
861
|
-
|
|
862
|
-
// Lightweight conversation roster entry (no interaction bodies), returned by
|
|
863
|
-
// openSpace/listConversations. `updatedAt` drives last-activity display.
|
|
864
|
-
interface ConversationMeta {
|
|
865
|
-
id: string;
|
|
866
|
-
agent: string; // owning agent ('rool' is the stock agent)
|
|
867
|
-
visibility: ConversationVisibility;
|
|
868
|
-
name: string | null;
|
|
869
|
-
systemInstruction: string | null;
|
|
870
|
-
createdAt: number;
|
|
871
|
-
createdBy: string;
|
|
872
|
-
interactionCount: number;
|
|
873
|
-
updatedAt: number;
|
|
874
|
-
}
|
|
596
|
+
<a id="problem-gift_invalid"></a>
|
|
875
597
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
interface SpaceInvite {
|
|
879
|
-
inviteId: string;
|
|
880
|
-
spaceId: string;
|
|
881
|
-
role: InviteRole;
|
|
882
|
-
email: string | null;
|
|
883
|
-
createdBy: string;
|
|
884
|
-
createdAt: string;
|
|
885
|
-
expiresAt: string;
|
|
886
|
-
maxUses: number | null;
|
|
887
|
-
useCount: number;
|
|
888
|
-
}
|
|
598
|
+
### `gift_invalid`
|
|
889
599
|
|
|
890
|
-
|
|
891
|
-
// The invite is always minted and its `url` is usable regardless of this value;
|
|
892
|
-
// only the email delivery is reflected here.
|
|
893
|
-
// - 'sent': email dispatched
|
|
894
|
-
// - 'not_configured': server has no mail provider (local dev)
|
|
895
|
-
// - 'failed': provider rejected the send
|
|
896
|
-
// - 'cooldown': a recent invite to this same address was already emailed
|
|
897
|
-
// - 'rate_limited': the inviter hit their daily email-invite cap
|
|
898
|
-
// Treat unknown values as not sent.
|
|
899
|
-
type InviteEmailStatus =
|
|
900
|
-
| 'sent'
|
|
901
|
-
| 'not_configured'
|
|
902
|
-
| 'failed'
|
|
903
|
-
| 'cooldown'
|
|
904
|
-
| 'rate_limited'
|
|
905
|
-
| (string & {});
|
|
906
|
-
|
|
907
|
-
interface SpaceInviteCreated {
|
|
908
|
-
inviteId: string;
|
|
909
|
-
spaceId: string;
|
|
910
|
-
role: InviteRole;
|
|
911
|
-
email: string | null;
|
|
912
|
-
expiresAt: string;
|
|
913
|
-
maxUses: number | null;
|
|
914
|
-
url: string; // contains the secret token; only available at mint time
|
|
915
|
-
emailStatus: InviteEmailStatus | null;
|
|
916
|
-
}
|
|
600
|
+
The code is not valid, or the requested gift does not belong to the current user.
|
|
917
601
|
|
|
918
|
-
|
|
919
|
-
spaceId: string;
|
|
920
|
-
spaceName: string;
|
|
921
|
-
role: InviteRole;
|
|
922
|
-
email: string | null;
|
|
923
|
-
inviterName: string | null;
|
|
924
|
-
}
|
|
602
|
+
<a id="problem-gift_claimed"></a>
|
|
925
603
|
|
|
926
|
-
|
|
927
|
-
spaceId: string;
|
|
928
|
-
role: RoolUserRole;
|
|
929
|
-
status: 'joined' | 'upgraded' | 'already_member';
|
|
930
|
-
}
|
|
604
|
+
### `gift_claimed`
|
|
931
605
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
interface Gift {
|
|
935
|
-
id: string;
|
|
936
|
-
code: string; // display form, e.g. "K7M2-9QRT"
|
|
937
|
-
url: string; // claim URL carrying the code
|
|
938
|
-
gift: GiftPayload;
|
|
939
|
-
description: string; // e.g. "10,000 AI credits"
|
|
940
|
-
claimedAt: string | null; // null while unspent
|
|
941
|
-
claimedByName: string | null; // claimer's display name, when they have one
|
|
942
|
-
createdAt: string;
|
|
943
|
-
note: string | null; // the holder's own reminder
|
|
944
|
-
archivedAt: string | null; // set when hidden; the code still claims
|
|
945
|
-
}
|
|
606
|
+
The gift has already been claimed. A claimed gift cannot be claimed again or given a new code.
|
|
946
607
|
|
|
947
|
-
|
|
948
|
-
note?: string | null; // undefined leaves as-is, null clears
|
|
949
|
-
archived?: boolean;
|
|
950
|
-
}
|
|
608
|
+
<a id="problem-invalid_input"></a>
|
|
951
609
|
|
|
952
|
-
|
|
953
|
-
gifts: Gift[];
|
|
954
|
-
}
|
|
610
|
+
### `invalid_input`
|
|
955
611
|
|
|
956
|
-
|
|
957
|
-
holderName: string | null;
|
|
958
|
-
gift: GiftPayload;
|
|
959
|
-
description: string;
|
|
960
|
-
}
|
|
612
|
+
The gift update is empty or contains an invalid note or archived value.
|
|
961
613
|
|
|
962
|
-
|
|
963
|
-
gift: GiftPayload;
|
|
964
|
-
description: string;
|
|
965
|
-
}
|
|
614
|
+
## Development
|
|
966
615
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
| string;
|
|
972
|
-
|
|
973
|
-
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
974
|
-
|
|
975
|
-
interface PromptOptions {
|
|
976
|
-
responseSchema?: Record<string, unknown>;
|
|
977
|
-
effort?: PromptEffort;
|
|
978
|
-
parentInteractionId?: string | null;
|
|
979
|
-
ephemeral?: boolean;
|
|
980
|
-
readOnly?: boolean;
|
|
981
|
-
attachments?: PromptAttachment[];
|
|
982
|
-
signal?: AbortSignal;
|
|
983
|
-
eventName?: string;
|
|
984
|
-
}
|
|
616
|
+
```bash
|
|
617
|
+
pnpm build
|
|
618
|
+
pnpm typecheck
|
|
619
|
+
```
|
|
985
620
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
621
|
+
The user-route smoke test must target a dedicated non-production account. It replaces profile and app data, then schedules and cancels account deletion. The member-route smoke test requires a second non-production account to exercise invite redemption and membership changes. The local gift fixture accounts are deleted after the gift smoke test.
|
|
622
|
+
|
|
623
|
+
Copy `.env.example` to the ignored `.env` file and configure the local endpoints and expected primary account ID.
|
|
624
|
+
|
|
625
|
+
| Variable | Purpose |
|
|
626
|
+
| ------------------------------- | ---------------------------------------------------------------------- |
|
|
627
|
+
| `ROOL_TEST_API_URL` | API origin. HTTPS is required except for loopback development servers. |
|
|
628
|
+
| `ROOL_TEST_AUTH_URL` | Auth endpoint override required for a loopback API. |
|
|
629
|
+
| `ROOL_TEST_ROUTER_URL` | Local machine-router origin. |
|
|
630
|
+
| `ROOL_TEST_USER_ID` | Expected primary account ID, preventing mutation of the wrong account. |
|
|
631
|
+
| `ROOL_TEST_GIFT_HOLDER_EMAIL` | Local fixture account that holds a gift. |
|
|
632
|
+
| `ROOL_TEST_GIFT_CLAIMANT_EMAIL` | Local fixture account that claims the gift. |
|
|
633
|
+
| `ROOL_TEST_INTERNAL_SECRET` | Local rool-server secret used to create and remove fixtures. |
|
|
634
|
+
|
|
635
|
+
Log the `sdk-v2-primary` and `sdk-v2-member` Node profiles into two dedicated accounts:
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
node --env-file=.env --import tsx test/integration/v2/login.ts
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
Then run the complete v2 smoke-test suite:
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
pnpm test:v2
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
To run one smoke test manually, invoke its script directly, for example:
|
|
648
|
+
|
|
649
|
+
```bash
|
|
650
|
+
node --env-file=.env --import tsx test/integration/v2/machine-routes.test.ts
|
|
1003
651
|
```
|
|
1004
652
|
|
|
1005
653
|
## License
|
|
1006
654
|
|
|
1007
|
-
MIT
|
|
655
|
+
MIT — see [LICENSE](../../LICENSE).
|