@jskit-ai/agent-docs 0.1.298 → 0.1.300

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 (45) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +23 -6
  2. package/guide/agent/framework/side-effects.md +70 -0
  3. package/guide/agent/index.md +2 -0
  4. package/package.json +1 -1
  5. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  6. package/patterns/minimal-foundation/example/AGENTS.md +6 -0
  7. package/patterns/minimal-foundation/example/bin/develop.js +9 -3
  8. package/patterns/minimal-foundation/example/package.json +3 -3
  9. package/patterns/shell-foundation/example/AGENTS.md +6 -0
  10. package/patterns/shell-foundation/example/bin/develop.js +9 -3
  11. package/patterns/shell-foundation/example/package.json +4 -4
  12. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  13. package/reference/autogen/KERNEL_MAP.md +11 -0
  14. package/reference/autogen/PATTERN_INDEX.md +38 -38
  15. package/reference/autogen/packages/assistant-core.md +1 -1
  16. package/reference/autogen/packages/auth-provider-local-core.md +2 -0
  17. package/reference/autogen/packages/connectors-catalog.md +4 -0
  18. package/reference/autogen/packages/connectors-core.md +3 -2
  19. package/reference/autogen/packages/kernel.md +22 -0
  20. package/reference/autogen/packages/payments-core.md +1 -1
  21. package/reference/autogen/packages/shell-web.md +16 -0
  22. package/reference/autogen/packages/workspaces-core.md +4 -0
  23. package/skills/jskit/SKILL.md +1 -0
  24. package/skills/jskit/references/pattern-index.md +38 -38
  25. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +6 -0
  26. package/skills/jskit/references/patterns/app/minimal-foundation/example/bin/develop.js +9 -3
  27. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +3 -3
  28. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +6 -0
  29. package/skills/jskit/references/patterns/app/shell-foundation/example/bin/develop.js +9 -3
  30. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +4 -4
  31. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  32. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +3 -3
  33. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  34. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  35. package/skills/jskit/references/patterns/connectors/api-key-connection/example/connections.js +4 -1
  36. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  37. package/skills/jskit/references/patterns/connectors/firebase-messaging/example/server/notifications.js +4 -1
  38. package/skills/jskit/references/patterns/crud/crud-feature-package/example/packages/books/package.json +3 -3
  39. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +2 -2
  40. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +2 -2
  41. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  42. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  43. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +3 -3
  44. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +6 -6
  45. package/skills/jskit/references/side-effects.md +65 -0
@@ -1274,8 +1274,17 @@ supplied binding never blocks on message sending or unknown delivery.
1274
1274
  An active goal flashes red; a paused goal stays orange. `elapsedSeconds` is the
1275
1275
  accumulated **active running time**, and `sampledAt` is its sample time in epoch
1276
1276
  **milliseconds**. The UI adds time only while active. Omit elapsed time if the
1277
- backend cannot supply it truthfully. The clock hides in a narrow conversation
1278
- pane; the light stays visible and details retain the time. Reduced motion uses
1277
+ backend cannot supply it truthfully. Only an active goal's chip shows its clock;
1278
+ a `paused`, `blocked`, `usageLimited` or `budgetLimited` goal's chip shows its
1279
+ status label (for example "Goal paused") instead, and its details keep the
1280
+ elapsed time. A `usageLimited` goal is waiting at its AI account's limit: its
1281
+ label names the limit and, from an optional `resetsAt` (epoch **seconds**), when it
1282
+ resets ("Waiting: AI usage limit, resets at 05:00", with the day when the reset is
1283
+ not today); with `outOfCredit: true` it reads "Out of credit: top up or switch AI
1284
+ account". Its details say the goal does not continue by itself and offer Resume
1285
+ and Cancel, and Pause when `pauseBlocked` is set. In a narrow conversation pane
1286
+ the clock hides and a long label ends in an ellipsis; the light stays visible
1287
+ and details retain the time and full label. Reduced motion uses
1279
1288
  a steady red light. Pause follows the command's declared behavior:
1280
1289
  `pauseInterruptsTurn: true` means it stops the current turn and keeps the goal;
1281
1290
  otherwise it prevents further automatic turns while Stop interrupts current work.
@@ -1286,10 +1295,18 @@ adapter. For a low-level adapter, omit `goal` or use `enabled: false` when goals
1286
1295
  are unsupported. The supplied binding hides unsupported controls automatically.
1287
1296
 
1288
1297
  Claude Pause retains the objective and native identity in the existing conversation
1289
- binding before interrupting execution. When another message starts a process, the
1290
- native owner clears the restored checker with Claude's local command and requires
1291
- its exact successful receipt before admitting user input. Ordinary messages and
1292
- process restart keep the goal paused. Explicit Resume reinstalls the objective;
1298
+ binding before interrupting execution. It sends nothing to Claude, so it works when
1299
+ the account is out of credit. When another message starts a process, the
1300
+ native owner clears the restored checker with Claude's local command (no model
1301
+ request) and requires its exact successful receipt before admitting user input.
1302
+ Ordinary messages and process restart keep the goal paused. Only that retained
1303
+ Pause reads `paused`: a Claude goal whose turn ended any other way (an interrupt,
1304
+ a process restart) keeps its checker armed, so the next turn continues it and it
1305
+ reads `active`. When Claude's own history ends the goal's turn with its usage-limit
1306
+ message, the goal reads `usageLimited` with that message's `resetsAt`; its
1307
+ credit-balance message reads `usageLimited` with `outOfCredit: true`. A later
1308
+ answer returns it to `active`. Hosts that restart goals must not resume a waiting
1309
+ goal: Resume after the reset stays the person's choice. Explicit Resume reinstalls the objective;
1293
1310
  Cancel removes the retained goal. A failed native clear leaves the new message
1294
1311
  unsent and retains Pause for recovery. Hosts supplying their own Claude store must
1295
1312
  round-trip the optional `pausedGoal` field with the existing conversation state.
@@ -0,0 +1,70 @@
1
+ <!-- Generated by `npm run agent-docs:build` from `packages/agent-docs/site/guide/framework/side-effects.md`. Do not edit manually. -->
2
+
3
+ ---
4
+ title: Side effects and test copies
5
+ description: Follow SIDE_EFFECTS so a test copy never sends real email, messages or payments.
6
+ ---
7
+
8
+ # Side effects and test copies
9
+
10
+ An application reads one environment variable, `SIDE_EFFECTS`:
11
+
12
+ - `live`: the application acts for real.
13
+ - Anything else, including unset, means `simulate`: the copy is a **test
14
+ copy**. It sends no real email, SMS, push notification, payment or
15
+ third-party webhook. It simulates each one, writes it to the log, and every
16
+ page shows a small "Test copy" banner.
17
+
18
+ Only the copy that serves real people sets `SIDE_EFFECTS=live`. Development,
19
+ previews, tests and staging leave it unset. The name has no platform prefix on
20
+ purpose: any host can set it. The host sets it; a request never chooses it.
21
+
22
+ ## What JSKIT does
23
+
24
+ - Every HTML page the server sends, and every page the Vite development server
25
+ sends, names the setting in a `jskit-side-effects` meta tag. The shell
26
+ (`@jskit-ai/shell-web`) shows the "Test copy" banner from it, with no
27
+ application code. The banner never takes a click or a tap.
28
+ - The server logs once at startup whether it acts for real. A production server
29
+ that simulates logs a warning.
30
+ - Local-password emails (confirmation and password reset) with an installed
31
+ sender are logged, not sent.
32
+ - Workspace invitations with a provided mailer are logged. The invitation
33
+ answers `status: "simulated"`, and the inviter copies the link.
34
+ - Accounts-hub email is logged, not sent.
35
+ - Connector operations that act on the world (send an email or message, post,
36
+ book or cancel, charge, invoice, trigger another system's events) are marked
37
+ `sideEffect`. They are checked like real ones, then logged, and `invoke`
38
+ answers `{ simulated: true }`. Reads run for real. `createConnectorsFeature`
39
+ reads the setting itself; give `createConnectionService` and
40
+ `createPaymentCheckoutService` `sideEffects: resolveSideEffectsMode(env)`.
41
+ - Payment checkout and the billing portal in the live payment environment are
42
+ refused with `payment_test_copy`; the sandbox environment works either way.
43
+
44
+ Supabase sends its own authentication emails from Supabase's servers; JSKIT
45
+ cannot simulate them. Point a test copy at a separate Supabase project.
46
+
47
+ ## The application's own side effects
48
+
49
+ Code the application writes that reaches the world follows the same rule.
50
+ Decide at the point of the effect: log what would happen and answer as if it
51
+ were done.
52
+
53
+ ```js
54
+ import { sideEffectsAreLive } from "@jskit-ai/kernel/shared/support/sideEffects";
55
+ import { logSimulatedSideEffect } from "@jskit-ai/kernel/server/runtime/sideEffects";
56
+
57
+ async function sendReminder({ env, logger, mailer }, message) {
58
+ if (!sideEffectsAreLive(env)) {
59
+ logSimulatedSideEffect(logger, { kind: "email", summary: `the reminder to ${message.to}`, details: { to: message.to } });
60
+ return { simulated: true };
61
+ }
62
+ return mailer.send(message);
63
+ }
64
+ ```
65
+
66
+ - Read `env` from the `runtime.env` capability, never a request.
67
+ - A configured driver or key (`EMAIL_DRIVER=ses`, a live payment key) does not
68
+ make a test copy live; only `SIDE_EFFECTS=live` does.
69
+ - Test both modes. A test that proves a real sender against a fake transport
70
+ sets `SIDE_EFFECTS: "live"` in its environment.
@@ -40,6 +40,8 @@ apply or adapt. It does not prescribe an agent host or project orchestrator.
40
40
  - [Sanity checks](/guide/framework/sanity-checks): JSKIT's own pitfalls, and
41
41
  the JSKIT calls that check data access, tests, a refactor, a screen or a
42
42
  deployment before calling it done
43
+ - [Side effects and test copies](/guide/framework/side-effects): read before
44
+ code that sends email, messages or payments, or calls another system
43
45
  - [Translating a JSKIT app](/guide/framework/translation)
44
46
  - [Complete source pattern library](/patterns/)
45
47
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.298",
3
+ "version": "0.1.300",
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.19",
8
+ "@jskit-ai/kernel": "0.2.20",
9
9
  "json-rest-schema": "^1.0.20"
10
10
  },
11
11
  "exports": {
@@ -26,6 +26,12 @@ factory or client provider), read the client features guide at
26
26
  Simple, direct code comes first: never add a layer, wrapper, registry or tiny
27
27
  module only to satisfy a rule. `npm run lint` checks its rules too.
28
28
 
29
+ Before writing code that sends email, SMS, push notifications or payments, or
30
+ calls another system, read the side effects guide at
31
+ `https://mobily-enterprises.github.io/jskit-ai/guide/framework/side-effects`.
32
+ Unless `SIDE_EFFECTS` is exactly `live`, the app is a test copy: it simulates
33
+ them and logs them instead.
34
+
29
35
  Before implementation, read the narrow relevant source pattern completely. If
30
36
  its owner package is installed, prefer that version-matched package-owned copy.
31
37
  Do not install an unrelated runtime package only to read documentation.
@@ -1,4 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
2
5
  import { startServer } from "../server.js";
3
6
  import { DEFAULT_PORT } from "../server/lib/runtimeEnv.js";
4
7
 
@@ -43,10 +46,13 @@ process.once("SIGTERM", () => requestShutdown("SIGTERM"));
43
46
  try {
44
47
  apiServer = await startServer({ host: LOOPBACK_HOST, port: 0 });
45
48
  const apiPort = /** @type {import("node:net").AddressInfo} */ (apiServer.server.address()).port;
46
- const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
49
+ // Vite runs directly under node rather than through `npm run dev`: npm exits on SIGTERM without stopping
50
+ // the script it started, which left Vite serving the port after this launcher stopped.
51
+ const vitePackageFile = createRequire(import.meta.url).resolve("vite/package.json");
52
+ const viteCli = path.join(path.dirname(vitePackageFile), JSON.parse(readFileSync(vitePackageFile, "utf8")).bin.vite);
47
53
  viteProcess = spawn(
48
- npmCommand,
49
- ["run", "dev", "--", "--host", previewHost, "--port", String(previewPort), "--strictPort"],
54
+ process.execPath,
55
+ [viteCli, "--host", previewHost, "--port", String(previewPort), "--strictPort"],
50
56
  {
51
57
  cwd: process.cwd(),
52
58
  env: {
@@ -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.2.19",
37
+ "@jskit-ai/kernel": "0.2.20",
38
38
  "@jskit-ai/material-web": "0.1.5",
39
39
  "@tanstack/vue-query": "^5.101.0",
40
40
  "fastify": "^5.12.7",
@@ -44,13 +44,13 @@
44
44
  "vue": "^3.5.44",
45
45
  "vue-router": "^5.3.1",
46
46
  "vuetify": "^4.3.0",
47
- "@jskit-ai/http-runtime": "0.2.19",
47
+ "@jskit-ai/http-runtime": "0.2.20",
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.250",
53
- "@jskit-ai/jskit-catalog": "0.2.50",
53
+ "@jskit-ai/jskit-catalog": "0.2.52",
54
54
  "@playwright/test": "1.61.1",
55
55
  "@vitejs/plugin-vue": "^6.0.7",
56
56
  "eslint": "^10.8.0",
@@ -26,6 +26,12 @@ factory or client provider), read the client features guide at
26
26
  Simple, direct code comes first: never add a layer, wrapper, registry or tiny
27
27
  module only to satisfy a rule. `npm run lint` checks its rules too.
28
28
 
29
+ Before writing code that sends email, SMS, push notifications or payments, or
30
+ calls another system, read the side effects guide at
31
+ `https://mobily-enterprises.github.io/jskit-ai/guide/framework/side-effects`.
32
+ Unless `SIDE_EFFECTS` is exactly `live`, the app is a test copy: it simulates
33
+ them and logs them instead.
34
+
29
35
  Before implementation, read the narrow relevant source pattern completely. If
30
36
  its owner package is installed, prefer that version-matched package-owned copy.
31
37
  Do not install an unrelated runtime package only to read documentation.
@@ -1,4 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
2
5
  import { startServer } from "../server.js";
3
6
  import { DEFAULT_PORT } from "../server/lib/runtimeEnv.js";
4
7
 
@@ -43,10 +46,13 @@ process.once("SIGTERM", () => requestShutdown("SIGTERM"));
43
46
  try {
44
47
  apiServer = await startServer({ host: LOOPBACK_HOST, port: 0 });
45
48
  const apiPort = /** @type {import("node:net").AddressInfo} */ (apiServer.server.address()).port;
46
- const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
49
+ // Vite runs directly under node rather than through `npm run dev`: npm exits on SIGTERM without stopping
50
+ // the script it started, which left Vite serving the port after this launcher stopped.
51
+ const vitePackageFile = createRequire(import.meta.url).resolve("vite/package.json");
52
+ const viteCli = path.join(path.dirname(vitePackageFile), JSON.parse(readFileSync(vitePackageFile, "utf8")).bin.vite);
47
53
  viteProcess = spawn(
48
- npmCommand,
49
- ["run", "dev", "--", "--host", previewHost, "--port", String(previewPort), "--strictPort"],
54
+ process.execPath,
55
+ [viteCli, "--host", previewHost, "--port", String(previewPort), "--strictPort"],
50
56
  {
51
57
  cwd: process.cwd(),
52
58
  env: {
@@ -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.2.19",
37
+ "@jskit-ai/kernel": "0.2.20",
38
38
  "@jskit-ai/material-web": "0.1.5",
39
39
  "@tanstack/vue-query": "^5.101.0",
40
40
  "fastify": "^5.12.7",
@@ -44,14 +44,14 @@
44
44
  "vue": "^3.5.44",
45
45
  "vue-router": "^5.3.1",
46
46
  "vuetify": "^4.3.0",
47
- "@jskit-ai/http-runtime": "0.2.19",
48
- "@jskit-ai/shell-web": "0.2.25",
47
+ "@jskit-ai/http-runtime": "0.2.20",
48
+ "@jskit-ai/shell-web": "0.2.26",
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.250",
54
- "@jskit-ai/jskit-catalog": "0.2.50",
54
+ "@jskit-ai/jskit-catalog": "0.2.52",
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.19"
12
+ "@jskit-ai/kernel": "0.2.20"
13
13
  },
14
14
  "jskit": {
15
15
  "kind": "runtime",
@@ -170,6 +170,17 @@ Exports
170
170
  Local functions
171
171
  - `normalizePlacementRenderers(value = {})`
172
172
 
173
+ ### `support/sideEffects.js`
174
+ Exports
175
+ - `SIDE_EFFECTS_ENV_KEY`
176
+ - `SIDE_EFFECTS_LIVE`
177
+ - `SIDE_EFFECTS_META_NAME`
178
+ - `SIDE_EFFECTS_SIMULATE`
179
+ - `pageSideEffectsMode(documentObject = globalThis.document)`
180
+ - `resolveSideEffectsMode(env)`
181
+ - `sideEffectsAreLive(env)`
182
+ - `withSideEffectsMeta(html, mode)`
183
+
173
184
  ### `support/silentLogger.js`
174
185
  Exports
175
186
  - `silentLogger`