@jskit-ai/agent-docs 0.1.269 → 0.1.271

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.
Files changed (48) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +3 -2
  2. package/guide/agent/app-extras/assistant.md +35 -0
  3. package/guide/agent/app-setup/database-layer.md +4 -0
  4. package/guide/agent/app-setup/existing-application-migration.md +3 -0
  5. package/guide/agent/app-setup/initial-scaffolding.md +76 -0
  6. package/guide/agent/framework/typed-features.md +41 -0
  7. package/package.json +1 -1
  8. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  9. package/patterns/minimal-foundation/PATTERN.md +3 -1
  10. package/patterns/minimal-foundation/example/package.json +4 -4
  11. package/patterns/minimal-foundation/example/server.js +9 -6
  12. package/patterns/minimal-foundation/example/tests/server/smoke.test.js +3 -4
  13. package/patterns/shell-foundation/example/package.json +5 -5
  14. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  15. package/patterns/shell-foundation/example/server.js +9 -6
  16. package/patterns/shell-foundation/example/tests/server/smoke.test.js +3 -4
  17. package/reference/autogen/PATTERN_INDEX.md +37 -37
  18. package/reference/autogen/packages/agent.md +1 -1
  19. package/reference/autogen/packages/assistant-runtime.md +1 -0
  20. package/reference/autogen/packages/crud-core.md +3 -2
  21. package/reference/autogen/packages/json-rest-api-core.md +1 -0
  22. package/reference/autogen/packages/kernel.md +24 -1
  23. package/skills/jskit/references/existing-application-migration.md +3 -0
  24. package/skills/jskit/references/heuristics/INDEX.md +1 -1
  25. package/skills/jskit/references/heuristics/typed-features.md +2 -0
  26. package/skills/jskit/references/pattern-index.md +37 -37
  27. package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +3 -1
  28. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +4 -4
  29. package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +9 -6
  30. package/skills/jskit/references/patterns/app/minimal-foundation/example/tests/server/smoke.test.js +3 -4
  31. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +5 -5
  32. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  33. package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +9 -6
  34. package/skills/jskit/references/patterns/app/shell-foundation/example/tests/server/smoke.test.js +3 -4
  35. package/skills/jskit/references/patterns/assistant/assistant-surface/PATTERN.md +5 -0
  36. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  37. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  38. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  39. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  40. package/skills/jskit/references/patterns/crud/json-api-resource-package/PATTERN.md +11 -0
  41. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  42. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  43. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  44. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  45. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  46. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  47. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  48. package/skills/jskit/references/typed-features.md +41 -0
@@ -2473,8 +2473,9 @@ accounts) authorizes the same connection again and stops the reply when access i
2473
2473
  gone. A configured `effort` reaches every provider as the AI SDK's standard
2474
2474
  reasoning setting; without one, the engine's default applies (adaptive thinking at
2475
2475
  high effort for Anthropic-format models). The system prompt is the configured
2476
- instructions, then the summary, then the engine's state note with the current UTC
2477
- time.
2476
+ instructions, then the summary. The engine's state note with the current UTC time
2477
+ follows the conversation as its last message, so the provider's prompt cache keeps
2478
+ the whole history from one turn to the next.
2478
2479
 
2479
2480
  An application can set server-owned `limits.maxApiToolProgressCharacters` to a
2480
2481
  positive safe integer to bound the decoded text accompanying confirmed API tool
@@ -164,6 +164,41 @@ Workspace scope is valid only when both the runtime and its settings surface
164
164
  are workspace-aware. Requests must retain the selected workspace through the
165
165
  server action boundary.
166
166
 
167
+ ### Who may change the instructions
168
+
169
+ The settings hold the instructions every conversation on the surface follows,
170
+ so changing them needs more than reading them. Any signed-in user may read
171
+ them (a workspace settings surface also needs `workspace.settings.view`, a
172
+ console-owner surface the console owner). Changing them needs:
173
+
174
+ | Settings surface | Permission that changes the instructions |
175
+ | --- | --- |
176
+ | requires a workspace | `workspace.settings.update` |
177
+ | console-owner access | the console owner |
178
+ | any other | `assistant.settings.update` |
179
+
180
+ Grant `assistant.settings.update` to the application's administrators through
181
+ the request context's permissions, or name another permission the application
182
+ already grants in server config:
183
+
184
+ ```js
185
+ // config/server.js
186
+ export const config = {
187
+ assistantServer: {
188
+ home: {
189
+ aiIntegrationId: "assistant",
190
+ settingsUpdatePermission: "app.admin"
191
+ // or { require: "any", permissions: ["app.admin", "app.owner"] }
192
+ }
193
+ }
194
+ };
195
+ ```
196
+
197
+ `settingsUpdatePermission: { require: "authenticated" }` lets every signed-in
198
+ user change the instructions; choose it only deliberately. A malformed value
199
+ stops the server from starting. Anyone else is refused with
200
+ `assistant.runtime.settings.updateForbidden` (403).
201
+
167
202
  ### Existing SQL conversations
168
203
 
169
204
  The optional database integration uses the common conversation runtime for
@@ -45,6 +45,10 @@ also derives its dialect from the installed driver and rejects a conflicting
45
45
  The application or hosting environment creates the database and provides its
46
46
  credentials. JSKIT never guesses or commits them.
47
47
 
48
+ The running database provider registers a `database` health check that runs
49
+ `select 1` within two seconds. The foundations' `GET /api/health` answers `503`
50
+ while it fails; see [Health endpoint](./initial-scaffolding.md#health-endpoint).
51
+
48
52
  ## Migration configuration
49
53
 
50
54
  Use the fixed-dialect pattern owned by the selected driver. The important
@@ -183,6 +183,9 @@ The JSKIT host defaults to plain output and full write responses; standard
183
183
  document repositories explicitly request JSON:API output and full create/patch
184
184
  responses. A plain collection is still `{ data: [...], meta?, links? }`; use
185
185
  `extractJsonRestCollectionRows()` when a custom repository needs its rows.
186
+ A query without a page returns only the resource's first 20 rows; a
187
+ server-side read that needs every row uses `queryAllJsonRestCollectionRows()`
188
+ ([every row](/guide/framework/typed-features#every-row)).
186
189
 
187
190
  ```js
188
191
  await jsonRestApi.resources.books.patch({
@@ -100,6 +100,82 @@ loading. Do not replace them with generic spinners. Keep failures local when a
100
100
  resource cannot render; use shared toast/snackbar feedback for transient user
101
101
  commands so errors do not push the working page down.
102
102
 
103
+ ## Health endpoint
104
+
105
+ Both foundations register `GET /api/health` after `createInstalledRuntime` and
106
+ answer from the installed runtime's `health.check()`: `200` with
107
+ `{ ok: true, checks, app }` when every check the installed packages register
108
+ passes, and `503` with `ok: false` when one fails. `@jskit-ai/database-runtime`
109
+ registers a `database` check that runs `select 1`. Each check has two seconds;
110
+ a failure is logged on the server and the response only names which checks
111
+ passed, so the route can stay public for load balancers and uptime monitors.
112
+ Requests that arrive while a check runs share it. An application that adopted
113
+ an older foundation, where the route answered `ok: true` before the runtime was
114
+ created, moves the route after `createInstalledRuntime` in the same way.
115
+
116
+ A package adds its own check through the optional `runtime.health`
117
+ capability: `optional: { health: "runtime.health" }`, then
118
+ `health?.register("queue", () => queue.ping())` in `setup()`. The check resolves
119
+ when healthy and rejects or hangs when not.
120
+
121
+ ## Security headers
122
+
123
+ `createInstalledRuntime` adds security headers to every response its Fastify
124
+ server sends, including error and not-found responses. A route that sets one of
125
+ these headers itself keeps its own value.
126
+
127
+ | Header | When | Default |
128
+ | --- | --- | --- |
129
+ | `X-Content-Type-Options: nosniff` | always | on |
130
+ | `Referrer-Policy` | always | `strict-origin-when-cross-origin` |
131
+ | `Strict-Transport-Security` | production | `max-age=31536000` |
132
+ | `Content-Security-Policy: frame-ancestors` | production | `'self'` |
133
+ | `Content-Security-Policy-Report-Only` | production | the content policy below |
134
+
135
+ Production means `NODE_ENV=production`. A host that starts the server with a
136
+ plain `npm start` runs it in development; its Serve command must set the
137
+ variable, for example `env NODE_ENV=production npm start`. Development sends only
138
+ nosniff and the Referrer-Policy, so an editor's preview, such as Vibe64's, can
139
+ frame the application and inject its bridge script.
140
+
141
+ The content policy allows JSKIT's client: the application's own scripts and
142
+ WebAssembly, inline styles (Vuetify and the loading shell use them), images
143
+ from the application, `data:`, `blob:` and any HTTPS source, fonts and media
144
+ from the application and `data:`, same-origin requests and sockets, and no
145
+ plugins, foreign base URLs or foreign form posts. It is reported, not enforced,
146
+ by default: browsers log violations in the console without blocking anything,
147
+ because an application's own additions, such as a sign-in provider, an
148
+ analytics script or a map, are not known to JSKIT. Enforce it once the
149
+ application's pages run without violations. Framing is always enforced, since
150
+ browsers ignore `frame-ancestors` in a report-only policy.
151
+
152
+ Change the defaults in `config/server.js`:
153
+
154
+ ```js
155
+ export const config = {
156
+ securityHeaders: {
157
+ // Another site that frames production, such as the editor that previews it.
158
+ frameAncestors: ["'self'", "https://editor.example.com"],
159
+ contentSecurityPolicy: {
160
+ mode: "enforce", // "report-only" (default), "enforce" or "off"
161
+ directives: {
162
+ "connect-src": ["'self'", "https://auth.example.com"],
163
+ "form-action": false // false leaves a directive out
164
+ }
165
+ }
166
+ }
167
+ };
168
+ ```
169
+
170
+ `noSniff: false`, `referrerPolicy: false`, `strictTransportSecurity: false` and
171
+ `frameAncestors: false` leave that header out; `referrerPolicy` and
172
+ `strictTransportSecurity` also take their own header value. A directive's
173
+ sources replace JSKIT's for that directive; framing is set only through
174
+ `frameAncestors`. An invalid value stops the server from starting. A server
175
+ composed without `createInstalledRuntime` calls
176
+ `registerSecurityHeaders(fastify, { config, env })` from
177
+ `@jskit-ai/kernel/server/http` before it registers any plugin.
178
+
103
179
  ## Verification
104
180
 
105
181
  Pattern contract tests keep both examples concrete, parseable, npm-published,
@@ -683,6 +683,47 @@ const trx = resolveTransaction(options);
683
683
  const trx = options?.trx || options?.transaction || null;
684
684
  ```
685
685
 
686
+ ### Every row
687
+
688
+ **A server-side resource query that needs every row it matches reads them
689
+ with `queryAllJsonRestCollectionRows(resource, params, context)`
690
+ (`@jskit-ai/json-rest-api-core/server/jsonRestApiHost`). A query without a
691
+ page means "the first 20 rows".**
692
+
693
+ - json-rest-api answers a query that names no page with the resource's
694
+ default page, 20 rows, and says nothing about the rest. The CRUD
695
+ repository's `queryDocuments(query)` without a `limit` is the same first
696
+ page.
697
+ - The helper reads cursor pages of the resource's maximum size
698
+ (`queryMaxLimit`, 100 unless the resource sets it) in id order until none
699
+ follows, keeping the call's transaction and context, so no row is skipped
700
+ or repeated. It sets the page and sort itself; sort the rows it returns.
701
+ - A lookup by a unique key (an id, a slug, a token hash, a unique pair such
702
+ as workspace and user) matches at most one row and may read `rows[0]` of a
703
+ plain query. A read of the newest or first few rows names its own `sort`
704
+ and `page`.
705
+ - HTTP list routes keep the caller's page: their default and maximum page
706
+ sizes are public behaviour, not a server-side limit to raise.
707
+
708
+ Why: a list read without a page looks complete until the 21st row, then
709
+ silently drops the rest from every person picker, scope and delivery built on it.
710
+
711
+ ```js
712
+ // good
713
+ const rows = await queryAllJsonRestCollectionRows(api.resources.workspaceMemberships, {
714
+ queryParams: { filters: { workspace: workspaceId, status: "active" }, include: ["user"] },
715
+ transaction: resolveTransaction(options),
716
+ format: "plain"
717
+ }, options.context);
718
+
719
+ // bad: the first 20 members only
720
+ const rows = extractJsonRestCollectionRows(await api.resources.workspaceMemberships.query({
721
+ queryParams: { filters: { workspace: workspaceId, status: "active" }, include: ["user"] },
722
+ transaction: resolveTransaction(options),
723
+ format: "plain"
724
+ }, createJsonRestContext(options.context)));
725
+ ```
726
+
686
727
  ### Routes
687
728
 
688
729
  **A route does HTTP work only (status, cookies, redirects) and runs an action
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.269",
3
+ "version": "0.1.271",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -5,7 +5,7 @@
5
5
  "private": true,
6
6
  "type": "module",
7
7
  "dependencies": {
8
- "@jskit-ai/kernel": "0.2.7",
8
+ "@jskit-ai/kernel": "0.2.8",
9
9
  "json-rest-schema": "^1.0.17"
10
10
  },
11
11
  "exports": {
@@ -146,7 +146,9 @@ verification, not only against Vite development mode.
146
146
  After adapting the example, install the declared packages once and run
147
147
  `npm run develop` for the live application. Then run the application's lint,
148
148
  server tests, client tests, production build, and focused browser smoke.
149
- Confirm `/api/health` and the first product route.
149
+ Confirm `/api/health` and the first product route. The health route answers
150
+ from the installed runtime's `health.check()` after `createInstalledRuntime`, so
151
+ it returns `503` while a registered check, such as the database's, fails.
150
152
 
151
153
  ## Avoid
152
154
 
@@ -34,22 +34,22 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.2.7",
37
+ "@jskit-ai/kernel": "0.2.8",
38
38
  "@tanstack/vue-query": "^5.101.0",
39
- "fastify": "^5.8.5",
39
+ "fastify": "^5.12.7",
40
40
  "pino": "^10.3.1",
41
41
  "json-rest-schema": "^1.0.17",
42
42
  "pinia": "^4.0.3",
43
43
  "vue": "^3.5.44",
44
44
  "vue-router": "^5.3.1",
45
45
  "vuetify": "^4.1.2",
46
- "@jskit-ai/http-runtime": "0.2.7",
46
+ "@jskit-ai/http-runtime": "0.2.8",
47
47
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
48
48
  "@vue/devtools-api": "^8.2.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@jskit-ai/config-eslint": "0.1.245",
52
- "@jskit-ai/jskit-catalog": "0.2.21",
52
+ "@jskit-ai/jskit-catalog": "0.2.23",
53
53
  "@playwright/test": "1.61.1",
54
54
  "@vitejs/plugin-vue": "^6.0.7",
55
55
  "eslint": "^10.8.0",
@@ -102,12 +102,6 @@ async function createServer({ runtimeEnv }) {
102
102
  }
103
103
  });
104
104
 
105
- app.get("/api/health", async () => {
106
- return {
107
- ok: true,
108
- app: "reading-room"
109
- };
110
- });
111
105
  const appRoot = path.resolve(process.cwd());
112
106
  const distRoot = path.resolve(appRoot, "dist");
113
107
  const hasWebBuild = existsSync(path.resolve(distRoot, SPA_INDEX_FILE));
@@ -124,6 +118,15 @@ async function createServer({ runtimeEnv }) {
124
118
  fastify: app
125
119
  });
126
120
 
121
+ // Healthy when every check the installed packages register passes, such as the database's.
122
+ app.get("/api/health", async (request, reply) => {
123
+ const health = await runtime.health.check();
124
+ return reply.code(health.ok ? 200 : 503).send({
125
+ ...health,
126
+ app: "reading-room"
127
+ });
128
+ });
129
+
127
130
  registerSurfaceRequestConstraint({
128
131
  fastify: app,
129
132
  surfaceRuntime,
@@ -3,15 +3,14 @@ import test from "node:test";
3
3
  import { createServer } from "../../server.js";
4
4
  import { resolveRuntimeEnv } from "../../server/lib/runtimeEnv.js";
5
5
 
6
- test("GET /api/health returns built-in health response", async () => {
6
+ test("GET /api/health reports the installed runtime's health checks", async (context) => {
7
7
  const app = await createServer({ runtimeEnv: resolveRuntimeEnv() });
8
+ context.after(() => app.close());
8
9
  const response = await app.inject({
9
10
  method: "GET",
10
11
  url: "/api/health"
11
12
  });
12
13
 
13
14
  assert.equal(response.statusCode, 200);
14
- assert.equal(response.json().ok, true);
15
-
16
- await app.close();
15
+ assert.deepEqual(response.json(), { ok: true, checks: {}, app: "reading-room" });
17
16
  });
@@ -34,24 +34,24 @@
34
34
  "dependencies": {
35
35
  "@local/main": "0.1.0",
36
36
  "@fastify/static": "^10.1.3",
37
- "@jskit-ai/kernel": "0.2.7",
37
+ "@jskit-ai/kernel": "0.2.8",
38
38
  "@tanstack/vue-query": "^5.101.0",
39
- "fastify": "^5.8.5",
39
+ "fastify": "^5.12.7",
40
40
  "pino": "^10.3.1",
41
41
  "json-rest-schema": "^1.0.17",
42
42
  "pinia": "^4.0.3",
43
43
  "vue": "^3.5.44",
44
44
  "vue-router": "^5.3.1",
45
45
  "vuetify": "^4.1.2",
46
- "@jskit-ai/http-runtime": "0.2.7",
46
+ "@jskit-ai/http-runtime": "0.2.8",
47
47
  "@mdi/js": "^7.4.47",
48
- "@jskit-ai/shell-web": "0.2.8",
48
+ "@jskit-ai/shell-web": "0.2.9",
49
49
  "@fastify/fast-json-stringify-compiler": "^5.1.0",
50
50
  "@vue/devtools-api": "^8.2.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@jskit-ai/config-eslint": "0.1.245",
54
- "@jskit-ai/jskit-catalog": "0.2.21",
54
+ "@jskit-ai/jskit-catalog": "0.2.23",
55
55
  "@playwright/test": "1.61.1",
56
56
  "@vitejs/plugin-vue": "^6.0.7",
57
57
  "eslint": "^10.8.0",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "description": "App-local runtime composition and lightweight glue.",
11
11
  "dependencies": {
12
- "@jskit-ai/kernel": "0.2.7"
12
+ "@jskit-ai/kernel": "0.2.8"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",
@@ -102,12 +102,6 @@ async function createServer({ runtimeEnv }) {
102
102
  }
103
103
  });
104
104
 
105
- app.get("/api/health", async () => {
106
- return {
107
- ok: true,
108
- app: "reading-room"
109
- };
110
- });
111
105
  const appRoot = path.resolve(process.cwd());
112
106
  const distRoot = path.resolve(appRoot, "dist");
113
107
  const hasWebBuild = existsSync(path.resolve(distRoot, SPA_INDEX_FILE));
@@ -124,6 +118,15 @@ async function createServer({ runtimeEnv }) {
124
118
  fastify: app
125
119
  });
126
120
 
121
+ // Healthy when every check the installed packages register passes, such as the database's.
122
+ app.get("/api/health", async (request, reply) => {
123
+ const health = await runtime.health.check();
124
+ return reply.code(health.ok ? 200 : 503).send({
125
+ ...health,
126
+ app: "reading-room"
127
+ });
128
+ });
129
+
127
130
  registerSurfaceRequestConstraint({
128
131
  fastify: app,
129
132
  surfaceRuntime,
@@ -3,15 +3,14 @@ import test from "node:test";
3
3
  import { createServer } from "../../server.js";
4
4
  import { resolveRuntimeEnv } from "../../server/lib/runtimeEnv.js";
5
5
 
6
- test("GET /api/health returns built-in health response", async () => {
6
+ test("GET /api/health reports the installed runtime's health checks", async (context) => {
7
7
  const app = await createServer({ runtimeEnv: resolveRuntimeEnv() });
8
+ context.after(() => app.close());
8
9
  const response = await app.inject({
9
10
  method: "GET",
10
11
  url: "/api/health"
11
12
  });
12
13
 
13
14
  assert.equal(response.statusCode, 200);
14
- assert.equal(response.json().ok, true);
15
-
16
- await app.close();
15
+ assert.deepEqual(response.json(), { ok: true, checks: {}, app: "reading-room" });
17
16
  });