@jskit-ai/agent-docs 0.1.245 → 0.1.247
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/guide/agent/app-extras/assistant-conversation.md +10 -4
- package/guide/agent/app-extras/realtime.md +8 -4
- package/guide/agent/app-setup/multi-homing.md +189 -1
- package/package.json +1 -1
- package/patterns/feature-package/example/booking-engine/package.json +1 -1
- package/patterns/minimal-foundation/example/package.json +3 -3
- package/patterns/shell-foundation/example/package.json +4 -4
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/reference/autogen/KERNEL_MAP.md +2 -0
- package/reference/autogen/PATTERN_INDEX.md +37 -37
- package/reference/autogen/packages/assistant-core.md +1 -1
- package/reference/autogen/packages/database-runtime.md +8 -1
- package/reference/autogen/packages/kernel.md +2 -0
- package/reference/autogen/packages/realtime.md +1 -0
- package/reference/autogen/packages/workspaces-core.md +4 -3
- package/reference/autogen/packages/workspaces-web.md +25 -2
- package/skills/jskit/references/multi-homing.md +189 -1
- package/skills/jskit/references/pattern-index.md +37 -37
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +3 -3
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +4 -4
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
- package/skills/jskit/references/patterns/workspaces/workspace-server/PATTERN.md +6 -0
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/PATTERN.md +10 -2
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/home/workspaces/index.vue +7 -0
|
@@ -725,8 +725,11 @@ actor may recover that conversation. The supplied runtime binding derives this
|
|
|
725
725
|
from its existing access-denial state; a 401/403 still clears private history and
|
|
726
726
|
drafts and offers no error-only Reload. Restore access or sign in through the
|
|
727
727
|
application before reopening. A subscription transport failure says **Chat
|
|
728
|
-
updates could not reconnect. Reload chat to try
|
|
729
|
-
error as `cause`; provider errors keep their own wording.
|
|
728
|
+
updates could not reconnect. Retrying automatically; Reload chat to try now.** and
|
|
729
|
+
retains its original error as `cause`; provider errors keep their own wording.
|
|
730
|
+
After a transport failure or a 5xx subscription response, the subscription retries
|
|
731
|
+
by itself after 1 s, doubling up to 30 s, while the socket stays connected; Reload,
|
|
732
|
+
a reconnect or disposal replaces that pending retry. Denials (4xx) never retry.
|
|
730
733
|
|
|
731
734
|
When `runtime.logger` is supplied, subscriptions emit structured
|
|
732
735
|
`assistant.conversation.subscription` records containing `stage`, `previousStage`,
|
|
@@ -4121,8 +4124,11 @@ model keeps calling native tools that the conversation was not granted stops wit
|
|
|
4121
4124
|
a visible failure and keeps the authored message instead of running without an answer. The common driver's internal configuration
|
|
4122
4125
|
uses that same server startup, authentication, version, observation and cleanup
|
|
4123
4126
|
implementation. `openCodeProcessEnvironment(baseEnv, { cacheRoot, dbPath,
|
|
4124
|
-
inlineConfig, outputTokenMax, password })` assembles native
|
|
4125
|
-
loopback proxy bypass and supplied inline configuration.
|
|
4127
|
+
inlineConfig, outputTokenMax, packageInstalls, password })` assembles native
|
|
4128
|
+
flags, cache, loopback proxy bypass and supplied inline configuration.
|
|
4129
|
+
`packageInstalls: false` is for short-lived servers that need no packages, such
|
|
4130
|
+
as a catalogue read: OpenCode's background plugin install then fails at once, so
|
|
4131
|
+
it cannot hold the server alive after its first SIGTERM. A managed host supplies
|
|
4126
4132
|
its isolated environment, credential roots, approved plugin configuration and
|
|
4127
4133
|
application command hooks around those native values. That boundary preserves
|
|
4128
4134
|
the host's existing permission and process policies while using the same native
|
|
@@ -90,10 +90,14 @@ normal component and placement APIs. Installing realtime does not append it to
|
|
|
90
90
|
the shell.
|
|
91
91
|
|
|
92
92
|
The standard status indicator is a button while disconnected. Clicking it starts
|
|
93
|
-
a fresh connection attempt
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
a fresh connection attempt at once, even while Socket.IO is waiting out a
|
|
94
|
+
reconnection delay; application reconnect controls use `reconnectSocketIoClient`
|
|
95
|
+
from `@jskit-ai/realtime/client/runtime` for the same behavior. The client also
|
|
96
|
+
retries server-initiated disconnects and rejected handshakes, backing off from
|
|
97
|
+
one second to at most thirty seconds. Those extra retries pause while the page
|
|
98
|
+
is hidden or offline. When it becomes visible or online again, a disconnected
|
|
99
|
+
socket reconnects immediately instead of finishing a delay scheduled before the
|
|
100
|
+
device slept. Socket.IO continues to own ordinary transport reconnection.
|
|
97
101
|
Explicit client disconnection and `reconnection: false` remain respected.
|
|
98
102
|
|
|
99
103
|
Every attempt goes through normal authentication. Reconnection does not restore
|
|
@@ -26,6 +26,70 @@ Choose whether the product is:
|
|
|
26
26
|
Do not add workspace machinery to a product that only needs an owner id on one
|
|
27
27
|
domain record.
|
|
28
28
|
|
|
29
|
+
Set the model with `tenancyMode` and `tenancyPolicy` in `config/public.js`:
|
|
30
|
+
|
|
31
|
+
| `tenancyMode` | Workspaces | First workspace | Creating more |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `none` | None | None | No |
|
|
34
|
+
| `personal` | One personal workspace per user | Always provisioned | No |
|
|
35
|
+
| `workspaces` | Named workspaces with membership, invitations and switching | Only with `tenancyPolicy.workspace.autoProvision: true` | With `tenancyPolicy.workspace.allowSelfCreate: true` |
|
|
36
|
+
|
|
37
|
+
In `workspaces` mode the product chooses one of two styles:
|
|
38
|
+
|
|
39
|
+
- **Team-first** (the default, `autoProvision` unset or `false`): a new account
|
|
40
|
+
has no workspace until it creates one or accepts an invitation. Mount the
|
|
41
|
+
**Your workspaces** page (see [Browser composition](#browser-composition)) so
|
|
42
|
+
these people can do either.
|
|
43
|
+
- **Personal workspace plus shared workspaces** (`autoProvision: true`): every
|
|
44
|
+
user, including people who signed up through an invitation, owns one personal
|
|
45
|
+
workspace and can also join or create others.
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
// config/public.js
|
|
49
|
+
config.tenancyMode = "workspaces";
|
|
50
|
+
config.tenancyPolicy = {
|
|
51
|
+
workspace: {
|
|
52
|
+
allowSelfCreate: true,
|
|
53
|
+
autoProvision: true
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Provisioning runs in the users profile-sync lifecycle after registration and
|
|
59
|
+
sign-in. The first run creates the personal workspace, its owner membership and
|
|
60
|
+
its settings; later runs reuse it. The default name is the user's display name,
|
|
61
|
+
or email, followed by "'s Workspace". Set `personalWorkspaceName` in
|
|
62
|
+
`config/server.js` to choose another; it receives the synchronized profile
|
|
63
|
+
(`id`, `displayName`, `email`, `username`), and an empty result keeps the default:
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// config/server.js
|
|
67
|
+
config.personalWorkspaceName = (user) => `${user.displayName || user.email}'s home`;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Development sign-in
|
|
71
|
+
|
|
72
|
+
Development sign-in (`POST /api/dev-auth/login-as`, used by the Playwright
|
|
73
|
+
`loginAsExistingUser()` helper and managed previews) signs in as an existing
|
|
74
|
+
user without changing data. It does not run the profile-sync lifecycle, so
|
|
75
|
+
workspace provisioning and other lifecycle contributors do not run. Create the
|
|
76
|
+
workspaces that seeded users need in seed data, or sign in normally once.
|
|
77
|
+
|
|
78
|
+
## Data model
|
|
79
|
+
|
|
80
|
+
The package migrations create and own these tables:
|
|
81
|
+
|
|
82
|
+
| Table | Columns that applications use |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `workspaces` | `id`, `slug` (unique), `name`, `owner_user_id`, `is_personal`, `avatar_url`, `deleted_at` |
|
|
85
|
+
| `workspace_memberships` | `workspace_id`, `user_id`, `role_sid`, `status`; one row per workspace and user |
|
|
86
|
+
| `workspace_settings` | One row per workspace keyed by `workspace_id`: the eight palette colours and `invites_enabled` |
|
|
87
|
+
| `workspace_invites` | `workspace_id`, `email`, `role_sid`, `status`, `token_hash`, `invited_by_user_id`, `expires_at`, `accepted_at`, `revoked_at` |
|
|
88
|
+
|
|
89
|
+
Application records that belong to a workspace use a non-null `workspace_id`
|
|
90
|
+
foreign key to `workspaces.id`. Read and change these tables through the
|
|
91
|
+
`workspaces.core` repositories and services, not directly.
|
|
92
|
+
|
|
29
93
|
## Server composition
|
|
30
94
|
|
|
31
95
|
Use `workspaces/workspace-server` for role vocabulary, creation policy,
|
|
@@ -35,6 +99,103 @@ Workspace scope must reach repository queries. Parsing a workspace slug or
|
|
|
35
99
|
hiding an admin link is not authorization. Membership and role checks happen
|
|
36
100
|
before an operation discloses records or record existence.
|
|
37
101
|
|
|
102
|
+
### Workspace-scoped features and routes
|
|
103
|
+
|
|
104
|
+
For a standard CRUD resource owned by a workspace, give its table the
|
|
105
|
+
`workspace_id` column, set the resource's `autofilter: "workspace"`, and bind
|
|
106
|
+
`defineCrudJsonApiFeature()` with `ownershipFilter: "workspace"` on a workspace
|
|
107
|
+
surface. JSKIT then filters `workspaceId` by the workspace in the route.
|
|
108
|
+
`workspace_user` also filters by the signed-in user. See the CRUD operations
|
|
109
|
+
guide.
|
|
110
|
+
|
|
111
|
+
A hand-written route registers under `/api/w/:workspaceSlug/...` with
|
|
112
|
+
`contextPolicy: "required"`:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
router.register(
|
|
116
|
+
"GET",
|
|
117
|
+
"/api/w/:workspaceSlug/projects",
|
|
118
|
+
{
|
|
119
|
+
auth: "required",
|
|
120
|
+
surface: "app",
|
|
121
|
+
contextPolicy: "required",
|
|
122
|
+
permission: "projects.view" // optional
|
|
123
|
+
},
|
|
124
|
+
async function (request, reply) {
|
|
125
|
+
const projects = await projectsService.listForWorkspace(request.workspace.id);
|
|
126
|
+
// ...
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Before the handler runs, the auth plugin resolves the slug for the signed-in
|
|
132
|
+
user and sets `request.workspace`, `request.membership` (including `roleSid`
|
|
133
|
+
and `status`) and `request.permissions`. An unknown slug returns 404 and a
|
|
134
|
+
non-member receives 403, unless the route's `surface` uses an access policy with
|
|
135
|
+
`requireWorkspaceMembership: false`. A route with `permission` returns 403 when
|
|
136
|
+
the membership lacks it. Scope queries with `request.workspace.id`; never take a
|
|
137
|
+
workspace id from request input.
|
|
138
|
+
|
|
139
|
+
### Workspace services from another feature
|
|
140
|
+
|
|
141
|
+
Declare `workspaces: "workspaces.core"` in the feature's `requires`. The
|
|
142
|
+
capability provides:
|
|
143
|
+
|
|
144
|
+
| Member | Use |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `workspaces.services.directory` | Workspace lists, context by slug, creation and provisioning |
|
|
147
|
+
| `workspaces.services.members` | Members, roles and invitations |
|
|
148
|
+
| `workspaces.services.pendingInvitations` | Invitation resolution, redemption and acceptance participants |
|
|
149
|
+
| `workspaces.services.settings` | Workspace settings |
|
|
150
|
+
| `workspaces.repositories.workspaces`, `.workspaceMemberships`, `.workspaceSettings`, `.workspaceInvites` | Repository access inside the application's own transactions |
|
|
151
|
+
| `workspaces.tenancyProfile` | The resolved tenancy mode and workspace policy |
|
|
152
|
+
|
|
153
|
+
### Invitation email
|
|
154
|
+
|
|
155
|
+
To send invitation email, set a mailer in `config/server.js`. Without one,
|
|
156
|
+
invitations are still created and delivery reports `mailer_unconfigured`.
|
|
157
|
+
`workspaceInviteMailer` is an object whose `sendWorkspaceInvite(message,
|
|
158
|
+
options)` receives `{ email, inviteUrl, workspace, inviter, roleSid, expiresAt,
|
|
159
|
+
message }`, where the inner `message` is the rendered `{ subject, text, html }`.
|
|
160
|
+
`workspaceInviteEmailTemplate` is an optional function that renders that inner
|
|
161
|
+
message; it receives `{ email, inviteUrl, workspace, inviter, roleSid,
|
|
162
|
+
roleLabel, expiresAt }` (`roleLabel` only when configured). Invitation links use `/invite/:token` unless
|
|
163
|
+
`workspaceInvitations.invitePath` names another path. The
|
|
164
|
+
`workspaces/workspace-server` pattern includes an example renderer.
|
|
165
|
+
|
|
166
|
+
### Workspace colours
|
|
167
|
+
|
|
168
|
+
Each workspace stores a light and a dark palette in `workspace_settings`. On
|
|
169
|
+
workspace routes, `workspaces-web` switches Vuetify to the `workspace-light` or
|
|
170
|
+
`workspace-dark` theme, built from the application's own theme with `primary`,
|
|
171
|
+
`secondary`, `surface` and `surface-variant` replaced by the workspace's
|
|
172
|
+
colours. Other routes use the application's themes.
|
|
173
|
+
|
|
174
|
+
New workspaces start from JSKIT's blue palette unless `config/server.js` sets
|
|
175
|
+
the application's own. Missing fields keep JSKIT's values; an invalid colour
|
|
176
|
+
stops the server at startup:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
// config/server.js
|
|
180
|
+
config.workspaceSettings = {
|
|
181
|
+
defaults: {
|
|
182
|
+
invitesEnabled: true,
|
|
183
|
+
lightPrimaryColor: "#1C1C1C",
|
|
184
|
+
lightSecondaryColor: "#5C5C5C",
|
|
185
|
+
lightSurfaceColor: "#FFFFFF",
|
|
186
|
+
lightSurfaceVariantColor: "#424242",
|
|
187
|
+
darkPrimaryColor: "#F5F5F5",
|
|
188
|
+
darkSecondaryColor: "#BDBDBD",
|
|
189
|
+
darkSurfaceColor: "#121212",
|
|
190
|
+
darkSurfaceVariantColor: "#C8C8C8"
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Defaults apply when a workspace is created. Existing workspaces keep their
|
|
196
|
+
stored colours; change them with `PATCH /api/w/:workspaceSlug/settings` or
|
|
197
|
+
`workspaces.services.settings.updateWorkspaceSettings(workspace, patch)`.
|
|
198
|
+
|
|
38
199
|
`workspace_authenticated` and similar policies must implement their documented
|
|
39
200
|
membership semantics exactly; do not synthesize memberships to compensate for
|
|
40
201
|
a policy/runtime mismatch.
|
|
@@ -265,7 +426,34 @@ placements.
|
|
|
265
426
|
|
|
266
427
|
The route slug, selected workspace, query keys, and server scope stay aligned.
|
|
267
428
|
Invalid workspace URLs show a deliberate not-found/forbidden state instead of
|
|
268
|
-
silently selecting another workspace.
|
|
429
|
+
silently selecting another workspace. The browser waits for the server's answer
|
|
430
|
+
about an unknown slug before deciding, so the surface can show that state.
|
|
431
|
+
|
|
432
|
+
### Surfaces and where `/` lands
|
|
433
|
+
|
|
434
|
+
Workspace surfaces put the slug in their pages root, such as
|
|
435
|
+
`pagesRoot: "w/[workspaceSlug]"` with `requiresWorkspace: true`, and
|
|
436
|
+
`w/[workspaceSlug]/admin` for administration. Keep the default surface
|
|
437
|
+
(`surfaceDefaultId`, usually `home`) free of a workspace requirement: `/`,
|
|
438
|
+
invitation links and other unscoped pages belong to it. With
|
|
439
|
+
`webRootAllowed: "no"`, `/` opens that surface's root.
|
|
440
|
+
|
|
441
|
+
`workspaces-web` remembers the last workspace each person opened in this
|
|
442
|
+
browser. The **Your workspaces** page, `WorkspacesClientElement` from
|
|
443
|
+
`@jskit-ai/workspaces-web/client/components/WorkspacesClientElement`, uses it:
|
|
444
|
+
|
|
445
|
+
1. With pending invitations, it shows them so the person can join or refuse.
|
|
446
|
+
2. Otherwise it opens the last workspace used in this browser, if the person is
|
|
447
|
+
still a member, or their only workspace.
|
|
448
|
+
3. Otherwise it lists their workspaces and, when they have none and
|
|
449
|
+
`allowSelfCreate` is on, offers to create the first one.
|
|
450
|
+
|
|
451
|
+
The page sends signed-out visitors to sign in. The
|
|
452
|
+
`workspaces/workspace-surfaces` pattern mounts it at `/home/workspaces`. The
|
|
453
|
+
product decides where people land: keep a public landing page at `/home` that
|
|
454
|
+
links signed-in people there, or mount the page at `/home` itself so everyone
|
|
455
|
+
goes straight to sign-in or into their workspace. Applications may replace it
|
|
456
|
+
with their own page.
|
|
269
457
|
|
|
270
458
|
Loading uses skeletons; mutation errors use toasts. Warm-cache and browser
|
|
271
459
|
back/forward navigation must restore the correct workspace context.
|
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@local/main": "0.1.0",
|
|
36
36
|
"@fastify/static": "^10.1.3",
|
|
37
|
-
"@jskit-ai/kernel": "0.1.
|
|
37
|
+
"@jskit-ai/kernel": "0.1.248",
|
|
38
38
|
"@tanstack/vue-query": "^5.101.0",
|
|
39
39
|
"fastify": "^5.8.5",
|
|
40
40
|
"json-rest-schema": "^1.0.17",
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"vue": "^3.5.44",
|
|
43
43
|
"vue-router": "^5.3.1",
|
|
44
44
|
"vuetify": "^4.1.2",
|
|
45
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
45
|
+
"@jskit-ai/http-runtime": "0.1.246",
|
|
46
46
|
"@fastify/fast-json-stringify-compiler": "^5.1.0",
|
|
47
47
|
"@vue/devtools-api": "^8.2.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@jskit-ai/config-eslint": "0.1.241",
|
|
51
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
51
|
+
"@jskit-ai/jskit-catalog": "0.1.300",
|
|
52
52
|
"@playwright/test": "1.61.1",
|
|
53
53
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
54
54
|
"eslint": "^10.8.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@local/main": "0.1.0",
|
|
36
36
|
"@fastify/static": "^10.1.3",
|
|
37
|
-
"@jskit-ai/kernel": "0.1.
|
|
37
|
+
"@jskit-ai/kernel": "0.1.248",
|
|
38
38
|
"@tanstack/vue-query": "^5.101.0",
|
|
39
39
|
"fastify": "^5.8.5",
|
|
40
40
|
"json-rest-schema": "^1.0.17",
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
"vue": "^3.5.44",
|
|
43
43
|
"vue-router": "^5.3.1",
|
|
44
44
|
"vuetify": "^4.1.2",
|
|
45
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
45
|
+
"@jskit-ai/http-runtime": "0.1.246",
|
|
46
46
|
"@mdi/js": "^7.4.47",
|
|
47
|
-
"@jskit-ai/shell-web": "0.1.
|
|
47
|
+
"@jskit-ai/shell-web": "0.1.255",
|
|
48
48
|
"@fastify/fast-json-stringify-compiler": "^5.1.0",
|
|
49
49
|
"@vue/devtools-api": "^8.2.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@jskit-ai/config-eslint": "0.1.241",
|
|
53
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
53
|
+
"@jskit-ai/jskit-catalog": "0.1.300",
|
|
54
54
|
"@playwright/test": "1.61.1",
|
|
55
55
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
56
56
|
"eslint": "^10.8.0",
|
|
@@ -81,6 +81,8 @@ Exports
|
|
|
81
81
|
- `normalizeQueryToken(value, { fallback = "__none__" } = {})`
|
|
82
82
|
- `normalizeObject(value, { fallback = {} } = {})`
|
|
83
83
|
- `normalizeIfInSource(source, normalized, fieldName, normalizer = (value) => value)`
|
|
84
|
+
- `normalizeIfPresent(value, normalizer = (entry) => entry)`
|
|
85
|
+
- `normalizeOrNull(value, normalizer = (entry) => entry)`
|
|
84
86
|
- `isRecord(value)`
|
|
85
87
|
- `normalizeArray(value)`
|
|
86
88
|
- `normalizeUniqueTextList(value, { acceptSingle = false } = {})`
|