sygnal 6.0.0 → 6.1.1

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 (96) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/dist/ai.cjs.js +115 -0
  3. package/dist/ai.cjs.js.map +1 -0
  4. package/dist/ai.esm.js +2 -0
  5. package/dist/ai.esm.js.map +1 -0
  6. package/dist/astro/index.cjs.js +659 -18
  7. package/dist/astro/index.cjs.js.map +1 -1
  8. package/dist/astro/index.mjs +659 -18
  9. package/dist/astro/index.mjs.map +1 -1
  10. package/dist/astro/server.cjs.js.map +1 -1
  11. package/dist/astro/server.mjs.map +1 -1
  12. package/dist/devtools.cjs.js +413 -4
  13. package/dist/devtools.cjs.js.map +1 -1
  14. package/dist/devtools.esm.js +413 -5
  15. package/dist/devtools.esm.js.map +1 -1
  16. package/dist/diagnostics.cjs.js +170 -12
  17. package/dist/diagnostics.cjs.js.map +1 -1
  18. package/dist/diagnostics.esm.js +170 -12
  19. package/dist/diagnostics.esm.js.map +1 -1
  20. package/dist/guide/accessibility.md +54 -1
  21. package/dist/guide/agent.md +596 -0
  22. package/dist/guide/ai-chat.md +693 -0
  23. package/dist/guide/ai-decisions.md +330 -0
  24. package/dist/guide/forms-reference.md +28 -0
  25. package/dist/guide/forms.md +2 -1
  26. package/dist/guide/http.md +2 -0
  27. package/dist/guide/mcp-apps.md +272 -0
  28. package/dist/guide/recipes/ai-form-fill.md +163 -0
  29. package/dist/guide/recipes/ai-summarize.md +155 -0
  30. package/dist/guide/recipes/ai-support-inbox.md +190 -0
  31. package/dist/guide/recipes/overview.md +10 -0
  32. package/dist/guide/webmcp.md +226 -0
  33. package/dist/index.cjs.js +7841 -3370
  34. package/dist/index.cjs.js.map +1 -1
  35. package/dist/index.d.ts +1332 -6
  36. package/dist/index.esm.js +7139 -2696
  37. package/dist/index.esm.js.map +1 -1
  38. package/dist/sygnal.min.js +1 -1
  39. package/dist/sygnal.min.js.map +1 -1
  40. package/dist/vike/config/package.json +1 -1
  41. package/dist/vite/plugin.cjs.js +659 -18
  42. package/dist/vite/plugin.cjs.js.map +1 -1
  43. package/dist/vite/plugin.mjs +659 -18
  44. package/dist/vite/plugin.mjs.map +1 -1
  45. package/llms.txt +7 -1
  46. package/package.json +15 -1
  47. package/src/ai.d.ts +1186 -0
  48. package/src/ai.ts +23 -0
  49. package/src/core/hooks.ts +1 -1
  50. package/src/devtools.d.ts +20 -1
  51. package/src/devtools.ts +3 -0
  52. package/src/extra/ai/agent/index.ts +484 -0
  53. package/src/extra/ai/answers.ts +115 -0
  54. package/src/extra/ai/chat/behavior.ts +336 -0
  55. package/src/extra/ai/chat/driver.ts +271 -0
  56. package/src/extra/ai/chat/memoryTransport.ts +61 -0
  57. package/src/extra/ai/chat/output.ts +37 -0
  58. package/src/extra/ai/commandBar.ts +281 -0
  59. package/src/extra/ai/decide.ts +104 -0
  60. package/src/extra/ai/index.ts +42 -0
  61. package/src/extra/ai/link.ts +90 -0
  62. package/src/extra/ai/mcpApp.ts +314 -0
  63. package/src/extra/ai/messages.ts +51 -0
  64. package/src/extra/ai/schema/index.ts +166 -0
  65. package/src/extra/ai/schema/jsonSchema.ts +72 -0
  66. package/src/extra/ai/schema/strict.ts +152 -0
  67. package/src/extra/ai/transports/agui.ts +177 -0
  68. package/src/extra/ai/transports/anthropicMessages.ts +175 -0
  69. package/src/extra/ai/transports/chatCompletions.ts +105 -0
  70. package/src/extra/ai/transports/chromePrompt.ts +77 -0
  71. package/src/extra/ai/transports/encodeOpenResponses.ts +87 -0
  72. package/src/extra/ai/transports/fromAISDK.ts +126 -0
  73. package/src/extra/ai/transports/openResponses.ts +105 -0
  74. package/src/extra/ai/transports/shared.ts +125 -0
  75. package/src/extra/ai/transports/tools.ts +59 -0
  76. package/src/extra/ai/transports/uiMessageStream.ts +107 -0
  77. package/src/extra/ai/webmcp.ts +260 -0
  78. package/src/extra/copyAsTest.ts +2 -2
  79. package/src/extra/devMcp.ts +386 -0
  80. package/src/extra/devtoolsActions.ts +1 -1
  81. package/src/extra/diagnostics/checks/actionLog.ts +3 -3
  82. package/src/extra/diagnostics/checks/chat.ts +71 -0
  83. package/src/extra/diagnostics/checks/forms.ts +8 -0
  84. package/src/extra/diagnostics/checks/index.ts +7 -1
  85. package/src/extra/diagnostics/checks/public.d.ts +63 -2
  86. package/src/extra/diagnostics/checks/wiring.ts +5 -2
  87. package/src/extra/diagnostics/codes.ts +67 -0
  88. package/src/extra/form.ts +22 -6
  89. package/src/extra/formTool.ts +150 -0
  90. package/src/extra/testing.ts +125 -8
  91. package/src/index.d.ts +119 -3
  92. package/src/index.ts +21 -1
  93. package/src/shared.ts +9 -0
  94. package/src/vite/mcp.ts +568 -0
  95. package/src/vite/plugin.d.ts +43 -5
  96. package/src/vite/plugin.ts +109 -22
package/CHANGELOG.md CHANGED
@@ -2,6 +2,111 @@
2
2
 
3
3
  All notable changes to Sygnal are listed here. Versions follow [semantic versioning](https://semver.org). Releases before 5.4.0 are described in the [GitHub releases](https://github.com/tpresley/sygnal/releases) and tags.
4
4
 
5
+ ## 6.1.1 — 2026-10-10
6
+
7
+ Editor tooling (PLAN-7). A language server, `sygnal-check lsp`, shows the checker's findings in the editor while you type: an intent selector that matches nothing, an action with no model entry, an EVENTS type nobody listens to, and the other wiring bugs that fail silently at runtime and that TypeScript can't see. It runs in VS Code (a new extension), Claude Code (a new plugin), Neovim, Helix, JetBrains IDEs (LSP4IJ) and any other LSP editor, all from the one server in `sygnal-check`, so the editor, the CLI, the Vite overlay and CI report the same findings from the same config file.
8
+
9
+ `sygnal` itself only changes in `sygnal/vite` (the `check` option reads the config file) and in the static diagnostic types: the core doesn't change (the size gate measures 42,690 B, as in 6.1.0).
10
+
11
+ **Measured impact.** On a generated 20,000-line project (`dev-plans/research/p7-spikes/0-A`, Apple M3 Max): a cold check takes about 230 ms (was about 400 ms) and a re-check after a one-file edit about 80 ms with the parse cache; in the editor an edit shows its findings about 260 ms later (150 ms of it the typing pause), and a hover answers in under 1 ms while a check runs. The server reports exactly what the CLI reports on every example app and fixture set (40 parity cases). Verified clients: VS Code 1.138, Claude Code 2.1.287, Neovim 0.12, Helix 25.07 and IntelliJ IDEA Community 2025.2 with LSP4IJ 0.21. Size: the kanban gate is unchanged, (a) 42,690 B with `nativeGlobalThis: false` (budget 42,700 B) and (b) 38,679 B by default. Whether in-editor findings help coding agents is measured after the release (PLAN-7 E-1).
12
+
13
+ **Companion packages.**
14
+ - **`sygnal-check` 0.4.0:** the language server (`sygnal-check lsp`), the config file, ranges, `related` locations and fixes on every finding, `sources` and a parse cache in the API, and a faster checker.
15
+ - **`create-sygnal-app` 2.2.0:** every template depends on `sygnal` ^6.1.1 and `sygnal-check` ^0.4.0, recommends the VS Code extension and carries `sygnal-check.config.json` (`strict: true`).
16
+ - **VS Code extension 0.1.0** (`sygnal.sygnal`, VS Code Marketplace and Open VSX) and **Claude Code plugin 0.1.0** (`/plugin marketplace add tpresley/sygnal`): new; see the [editor setup guide](https://sygnal.js.org/integration/editors/).
17
+
18
+ ### Added
19
+
20
+ - **`sygnal-check lsp`: a language server.** Stdio, part of `sygnal-check` (no new dependency). Findings for the whole project while you type, including unsaved buffers, with exact ranges and related locations; a hover with each code's explanation; quick fixes ("did you mean", the strict rewrites, "add a model entry"), "fix all" (what `--fix` does, on the unsaved text) and "suppress on this line". Checks run in a worker thread: on a 20,000-line project an edit shows its findings in about 260 ms (150 ms of it the typing pause) and a hover answers in under 1 ms during a check. Diagnostics are pushed, never pulled. Settings (`sygnal.strict`, `a11y`, `ignore`, `paths`, `verbose`, `trace.server`) override the config file per folder.
21
+ - **VS Code extension** (`editors/vscode/`, id `sygnal.sygnal`): runs the project's `sygnal-check` when it has the language server, else a bundled copy (always the bundled one in an untrusted workspace); commands Restart Server, Show Output, Explain Code…, Fix All in File; per-folder settings; snippets for a canonical component, an `EFFECT` entry and an `EVENTS` entry (JS and TS).
22
+ - **Claude Code plugin** (`editors/claude-code/`, marketplace `.claude-plugin/marketplace.json`: `/plugin marketplace add tpresley/sygnal`, `/plugin install sygnal@sygnal`): the `sygnal-dev` skill in one install, and the language server, so the agent sees findings after its edits. Without a capable `sygnal-check` in the project the server stays quiet.
23
+ - **Templates** (`create-sygnal-app`, all 10): `.vscode/extensions.json` recommends the extension, and `sygnal-check.config.json` sets `strict: true` (the Vike templates also `paths: ["pages"]`), so `npx sygnal-check`, the dev overlay and the editor check the same files the same way.
24
+ - **`sygnal-check` config file.** `sygnal-check.config.json`, or a `"sygnal-check"` key in `package.json`: `strict`, `a11y`, `ignore`, `paths`, `includeTests`. JSON only. It is found from the working directory up to the nearest project root. The CLI (`--config <file>`, `--no-config`), the MCP server, `check()` / `graph()` and the Vite plugin read it, so the editor, the dev overlay and CI agree. Flags and explicit options win over it; problems in it are SYG900 findings on the file, never a crash.
25
+ - **`sygnal/vite`: the `check` option defaults to the config file.** Explicit `check.*` options win, then the config file, then the `diagnostics.strict` / `diagnostics.ignore` defaults. Editing the config file re-checks. An older `sygnal-check` without config support gets exactly the options it got before. `plugin.d.ts` now also types `check.a11y`.
26
+ - **Ranges on findings.** Every `sygnal-check` diagnostic has `endLine` / `endColumn` (exclusive, UTF-16 columns, as editors count). Findings on multi-line elements underline the tag name; `sygnal-ignore` comments keep matching where they did. Pair and cross-file findings carry `related` locations (SYG104: the child element that renders the class; SYG105, SYG128, SYG129, SYG440, SYG609, SYG643).
27
+ - **Fixes as edits.** Diagnostics carry `fixes: [{ title, kind, preferred?, fixAll?, edits }]`: every unambiguous "did you mean" (SYG110, SYG112, SYG127, SYG140–142, SYG150, SYG151, SYG223, SYG226, SYG641, SYG707; SYG105 as a non-preferred rename), the `--fix` rewrites (SYG504, SYG505, SYG506, SYG612) with their import edits, "add a model entry" for SYG101 and "remove the unreachable entry" for SYG102. `--fix` applies the same `fixAll` edits an editor gets. `controlFixes()` returns the `--controls` conversions one selector at a time.
28
+ - **In-memory sources and a parse cache** for editors and watchers. `check()`, `checkFiles()`, `graph()`, `graphFiles()` and `buildProject()` take `sources` (unsaved buffers; new files resolve as imports) and `cache` (`createParseCache()`: unchanged files aren't parsed again). `fixFiles()` with `sources` fixes in memory and returns the texts and edits. The MCP server keeps a cache.
29
+ - **Types:** `InspectDiagnostic` gains the optional `endLine`, `endColumn`, `related` and `fixes` (`InspectRelatedLocation`, `InspectFix`, `InspectTextEdit`).
30
+
31
+ ### Changed
32
+
33
+ - SYG900's explanation also covers config-file findings.
34
+ - `sygnal-check` performance: walks over a whole file use a flat node list built once per parse. A 20,000-line project checks in about 230 ms cold (was about 400 ms) and re-checks after a one-file edit in about 80 ms with a cache (was about 350 ms without one).
35
+
36
+ ### Fixed
37
+
38
+ - `sygnal-check`: three module-level caches keyed by AST nodes (behavior definitions, widgets, a11y `describe()`) could serve one build's data to the next when ASTs are reused; now keyed per project.
39
+
40
+ ## 6.1.0 — 2026-10-10
41
+
42
+ A new subpath, `sygnal/ai`, covers the two AI jobs Sygnal apps now meet: calling language models from the app, and letting agents operate it. A chat request is a driver request, like HTTP: the reply streams into a `delta` action and arrives whole as `ok`, through the same reply actions as `makeFetchDriver`, with transports for local models (Ollama through `openResponses()`), an AI SDK route on your server (`uiMessageStream()`), Anthropic, OpenAI-compatible APIs, AG-UI and Chrome's built-in model. `decide()` asks a decision model typed questions through the fetch driver. One declaration per component, the `agent` static, says what an LLM may read and do in the app; the same declaration becomes the tools of an in-app assistant (the `chat` behavior), a command bar (`commandBar`), browser agents through WebMCP (`experimentalExposeWebMcp`), an MCP Apps host (`makeMcpAppDriver`), and coding agents through the dev server (`sygnal({ mcp: true })`). Tests answer chat requests and call agent tools without a model.
43
+
44
+ Everything is additive and pay-per-use: the core doesn't change (the size gate measures 42,690 B, as in 6.0.0), an app that doesn't import `sygnal/ai` gets 0 B of it, and each part costs bytes only when it's used. WebMCP support is experimental and outside semver until Chrome's origin trial ends (D241). Covers `sygnal`, `sygnal-check` and `create-sygnal-app`; the few changes that can show up in existing code or CI are under [Changed](#changed).
45
+
46
+ **Measured impact** (agent evals, [REPORT-v6](evals/agent-ergonomics/results/REPORT-v6.md)). Four new tasks, each also built with React, the AI SDK 7 (`useChat`, client tools) and WebMCP directly: a streaming chat, an assistant operating an existing app (with consent before a removal), ticket triage with a decision model and escalation to chat, and making a board operable by browser agents through WebMCP.
47
+ - Sygnal passes every trial on Opus 5.5, Sonnet 5.5 and Haiku 5.5 (60/60); React passes 59/60 (one app-level timing failure). Sygnal takes 1.07× React's time on Opus, 1.67× on Sonnet and 1.36× on Haiku, and costs 1.5–3.1× as much: Sygnal agents read the skill and the AI guides (peak context +32–35k), where React agents rely on what they already know about the AI SDK. Sygnal agents wrote less code on Opus and Haiku (0.75×, 0.83×) and always wrote a test. The two guide gaps that sent agents into the built code (what `uiMessageStream` POSTs, the default WebMCP confirmation dialog) are documented now.
48
+ - The apps the agents built can be operated by a small local model (qwen3:8b) through their declared tools: on the Opus trials' own code, Sygnal 15/15 and 20/20 tool tasks, React 15/15 and 18/20.
49
+ - The AI additions to `llms.txt` and the skill didn't slow down the older tasks (S-14 re-baseline, Opus, every trial passing): tiers 1–2 learn time 3.0 → 3.3 s and peak context 33.8k → 33.7k; the everyday tasks 9.7 → 9.0 s and 42.8k → 43.7k.
50
+ - Size: the kanban gate is unchanged, (a) 42,690 B with `nativeGlobalThis: false` (budget 42,700 B) and (b) 38,679 B by default. `sygnal/ai` is tree-shaken from apps that don't import it (0 B; a test gates it), and `form` grows by about 8 B for its `tool` hook. When used (gzip, approximate): the chat driver 2.5 KB plus a transport (`chromePrompt` 0.5 KB, `fromAISDK` 1.2 KB, `uiMessageStream` 1.9 KB, `openResponses` and `chatCompletions` 2.3 KB, `anthropicMessages` and `agui` 2.9 KB; about 1 KB more with `strictSchemas`); the agent layer 3.5 KB plus 1.4 KB for schemas; WebMCP 2.2 KB on top of those; the `chat` behavior 2.8 KB (7.8 KB with the agent layer); `commandBar` 9.3 KB with everything it uses; `makeMcpAppDriver` 2.2 KB (7.2 KB with agent tools); `formTool` 1.1 KB.
51
+
52
+ **Companion packages.**
53
+ - **`sygnal-check` 0.3.0:** rules for `sygnal/ai` (SYG150–153, static SYG240 and SYG243, SYG440, SYG441; SYG102 counts `agent.actions`, the chat `delta`/`tool` reply keys and `decide()`'s `ok`/`error`), two accessibility rules (SYG730 for hover-only actions, SYG731 for toggled state shown only by a class), the `agent` static in `--graph` and the MCP `graph` tool, and its stdio MCP server speaks MCP 2026-07-28 next to the `initialize` revisions; details under Added and Changed. Its caret range doesn't reach 0.3.0 from `^0.2.0`, so update the dev dependency.
54
+ - **`create-sygnal-app` 2.1.0:** a new template, `--template mcp-app` (JS and TS): an MCP App view written with Sygnal (`makeMcpAppDriver`), built into one HTML file, with its MCP server (`@modelcontextprotocol/sdk`, over HTTP or stdio) and tests. All ten templates depend on `sygnal` ^6.1.0 and `sygnal-check` ^0.3.0.
55
+
56
+ ### Added
57
+
58
+ - **Chat requests: `makeChatDriver()`** ([AI chat guide](https://sygnal.js.org/guide/ai-chat/)). `run(App, { LLM: makeChatDriver({ transport }) })`; a model entry sends the whole conversation and names its reply actions:
59
+ ```jsx
60
+ SEND: { STATE: (s) => ({ ...s, messages: [...s.messages, { role: 'user', content: s.prompt }], prompt: '' }),
61
+ LLM: (s) => ({ messages: [...s.messages, { role: 'user', content: s.prompt }], key: 'reply', delta: 'DELTA', ok: 'DONE', error: 'FAILED' }) },
62
+ DELTA: (s, { text }) => ({ ...s, draft: text }), // all the text so far
63
+ DONE: (s, { message }) => ({ ...s, messages: [...s.messages, message], draft: '' }),
64
+ ```
65
+ - `ok` gets `{ message, text, toolCalls, finishReason, usage }`, plus `value` for structured output (`output: schema`, any Standard Schema with a JSON Schema form, validated); `error` gets `{ error, request, issues }` (`error.status` for HTTP failures); `tool` gets each completed tool call (`{ call: { id, name, input } }`) for the app to run; `delta` fires at most once per frame (reasoning too), with a timer fallback in hidden tabs (`coalesce` changes it);
66
+ - `latest` is on per `key`; `{ abort: 'reply' }` stops a reply and nothing more arrives; a disposed instance's requests are aborted; replies go to the instance that sent the request, Collection items included;
67
+ - messages use the AI SDK's `UIMessage` parts (`text`, `reasoning`, `tool-<name>` with `state`, `data-*`, files, sources), so they round-trip to AI SDK servers; `messageText(message)` renders one, and `withToolResults(message, results)` fills in tool outputs before the next request;
68
+ - diagnostics: SYG673 (a malformed stream event), SYG677 (a request sent from outside a component), SYG678 (a failed request without an `error` action), SYG679 (an invalid request).
69
+ - **Transports.** `openResponses()` (the Open Responses API: OpenAI, Ollama, vLLM, LM Studio, ...; `baseURL` defaults to `/v1`), `chatCompletions()` (OpenAI-compatible Chat Completions), `uiMessageStream('/api/chat')` (an AI SDK 7 route on your server; checked against `ai` 7.0.137), `anthropicMessages()` (text, thinking with signed round trips, tools, server tools, `output_config.format`), `agui()` (AG-UI, with shared state as a `data-agui-state` part), `fromAISDK({ streamText, models })` (the AI SDK in-process, for servers and tests) and `chromePrompt()` (Chrome's built-in model, with `status()`). HTTP transports take `fetch`, `headers` and `body`. `strict: strictSchemas` (an import) sends provider-strict tool and output schemas, falling back per tool when a schema can't be strict (SYG675); `strict: true` without it is SYG672. `encodeOpenResponses()` turns text and tool calls into an Open Responses event stream, for test servers and demos.
70
+ - **Decisions: `decide()`, `choice()`, `noul()`, `score()` and `answers()`** ([decisions guide](https://sygnal.js.org/guide/ai-decisions/)). `decide({ model, state, questions })` is a `makeFetchDriver` request to a decision model (with reply actions, or as a `resources` entry; the default URL is `/api/decide`), and its answers are typed from the questions (`answers.topic.choice` and `.confidence`, `answers.urgent.noul`, `answers.mood.score`). `decide.openai()` maps to OpenAI's predicate API. `answers(questions, picks)` builds typed replies for tests and fixtures.
71
+ - **The `agent` static and `agentTools()`** ([agent guide](https://sygnal.js.org/guide/agent/)). `TodoApp.agent = { name: 'todos', description, read: (state) => projection, actions: { ADD: { description, input: z.string().min(1) }, CLEAR_DONE: { description, consequential: true } } }` declares what an LLM may read and do; `agentTools(app, options)` turns the declarations of the components on screen into tools (`todos_add`, `todos_clear_done`, `todos_read`) and runs their calls:
72
+ - a call is normalized, unwrapped, repaired (numeric and boolean strings; `repair: false` turns it off), validated against `input` (any Standard Schema with a JSON Schema form, or `jsonSchema(json)`), dispatched with `cause: 'agent'`, and answered with the new state once it has rendered; calls run one at a time;
73
+ - a `consequential` action waits for a confirmation; `when: (state) => bool` hides a tool; a call that changes nothing is `ok: false` unless the action is `idempotent`; a reducer refuses with `abort('reason')` (exported from `sygnal`, 0 core bytes);
74
+ - Collection items declare `agent` too: one tool per action with an `id` (or `item`) parameter listing the live keys, labelled by `agent.label(state)`; an item hidden by a filter gets an error that says so;
75
+ - `untrusted: true` marks a `read` projection that holds text users typed (inferred when it has strings: SYG244); its contents reach models only through the read tool, never through tool descriptions;
76
+ - `toJsonSchema()`, `parseInput()`, `jsonSchema()` and `outputJsonSchema()` expose the schema contract. Diagnostics: SYG240 (an input with no JSON Schema form), SYG241 (`read`, `when` or `label` threw), SYG243 (a lossy schema conversion), SYG440 (two declarations with the same name), SYG441 (Collection items without unique ids).
77
+ - **An in-app assistant: the `chat` behavior.** `App.uses = { assistant: chat({ form: '.ask', prompt: '.prompt', stop: '.stop', instructions }) }` keeps the conversation in `state.assistant` (`messages`, `prompt`, `draft`, `draftReasoning`, `status`, `pending`, `error`), sends on submit, streams, and runs the tool loop over the `agent` declarations of the host and its live descendants (`agent: false` or a list of components narrows them; `maxSteps`). Consequential calls wait in `pending` for `approve`/`deny`; `regenerate` and `{ text }` sends work; messages have stable ids. The `read` projections reach the model as a framed data block before the last user message, rebuilt per request, never in the instructions. SYG442 when the behavior can't reach its app.
78
+ - **A command bar: `commandBar`.** `commandBar({ input, decide, below, escalate, freeText })` turns a typed command into one agent call through one `decide()` request (the action and its target as choices over the live, labelled keys); below the confidence threshold it reports `unsure`, or hands the text to a `chat` assistant. `run` adds a Go button.
79
+ - **`experimentalExposeWebMcp(app, options)`** (experimental, outside semver; [WebMCP guide](https://sygnal.js.org/guide/webmcp/)) registers the agent tools with the browser's `document.modelContext` (WebMCP: Chrome, behind a flag or the origin trial), re-registers them when they change and unregisters them on dispose. Sygnal validates inputs, enforces size budgets (SYG242) and asks before a consequential call with an accessible default `<dialog>` (`confirm` replaces it). SYG674 (no WebMCP in this browser), SYG676 (a registration was rejected). Tested in Chromium natively and with `@mcp-b/webmcp-polyfill` in Chromium, Firefox and WebKit; `sygnal` doesn't depend on the polyfill.
80
+ - **`formTool()`** (experimental): `form(schema, { tool: formTool({ name, description, autosubmit }) })` makes a `form` behavior a declarative WebMCP tool (`toolname`, `tooldescription`, parameter descriptions from labels or `aria-label`); an agent's submit runs `form.SUBMIT` and gets `{ ok, values }` or the errors back. `autosubmit` is off by default, so the user submits. A plain object as `tool` is SYG245.
81
+ - **`form.SET`**: `{ values }` sets several fields at once, as if typed (for an assistant that fills in a form).
82
+ - **MCP Apps: `makeMcpAppDriver()`** ([MCP Apps guide](https://sygnal.js.org/guide/mcp-apps/)). A Sygnal app as the interactive view of an MCP tool in Claude, ChatGPT, VS Code and other hosts (protocol 2026-01-26): sources for the tool input and result, the host context and theme; sinks to call server tools, send messages, update the model's context, open links, download files, read resources and log, with reply actions; `autoResize`; optional `tools: agentTools`, so the host can call the view's agent tools.
83
+ - **The dev MCP endpoint: `sygnal({ mcp: true })`** ([agents integration](https://sygnal.js.org/integration/agents/#dev-server-mcp-endpoint)). The Vite dev server serves `/__sygnal/mcp`, where a coding agent can read every `run()` app's state, dispatch actions (`cause: 'agent'`), see the component tree, recent actions and diagnostics, copy actions as a test, call the page's agent tools (a consequential one asks in the page) and run `sygnal-check`. Loopback peers with a local `Host` and `Origin` only; never in builds, `vite preview` or Vitest. Speaks MCP 2024-11-05 to 2025-11-25 and the stateless 2026-07-28 revision, with no dependency.
84
+ - **Testing** (`renderComponent`): the sink named `LLM` (`llmSink` option) runs the real chat driver over an in-memory transport: `t.requests('LLM')`, `await t.stream('LLM', ['Hel', 'lo'])` (ends the reply unless `{ end: false }`), `t.respond('LLM', text)`, `t.fail('LLM', 429)`, also under fake timers. `t.tools()`, `await t.callTool(name, input)` (a consequential tool needs `{ confirm: true }`) and `t.agentContext()` exercise the `agent` declarations; `t.actions` records `cause: 'agent'`.
85
+ - **Diagnostics:** SYG670 (an auth header sent from the browser to a hosted endpoint; the request fails) and the codes listed above, with explanations in `sygnal-check explain` and the [error reference](https://sygnal.js.org/reference/errors/).
86
+ - **`sygnal-check`:** SYG150 (an agent action with no model entry), SYG151 (`agents`, `tools`, ... instead of `agent`), SYG152 (a chat request without `ok`), SYG153 (a bare WebMCP form attribute); static SYG240 (unwrapped Valibot, Zod Mini or raw JSON Schema as `input`) and SYG243 (a `Date` input); SYG440, SYG441; SYG730 and SYG731 in the accessibility lane; the `agent` static in `--graph` and the MCP `graph` tool (trigger `agent`). The `sygnal-check mcp` server answers MCP 2026-07-28 requests (`server/discover`, no `initialize`) as well as the `initialize` revisions up to 2025-11-25.
87
+ - **`create-sygnal-app --template mcp-app`** (JS and TS); see Companion packages.
88
+ - **Docs:** the [AI chat](https://sygnal.js.org/guide/ai-chat/), [Decisions](https://sygnal.js.org/guide/ai-decisions/), [Agents](https://sygnal.js.org/guide/agent/), [WebMCP](https://sygnal.js.org/guide/webmcp/) and [MCP Apps](https://sygnal.js.org/guide/mcp-apps/) guides (also in the package, `dist/guide/`), recipes for a support inbox, filling in a form and summarizing, and live demos that stream from a demo server through the real `openResponses()` transport. `llms.txt` and the `sygnal-dev` skill get an AI section.
89
+
90
+ ### Changed
91
+
92
+ - **`ActionCause` includes `'agent'`** (TypeScript): an exhaustive `switch` over an action's `cause` (`t.actions`, the DevTools action log) needs a case for it.
93
+ - **New `sygnal-check` warnings on existing code.** SYG731 flags click targets whose toggled state is shown only by a class, with no `aria-pressed`, `aria-expanded`, ... (common in tab bars and filter buttons); SYG730 flags actions reachable only by hovering. `sygnal-check` exits 1 on warnings by default, so a CI step that runs it can fail after the update: add the ARIA state, silence a finding you keep on purpose (`// sygnal-ignore SYG731`), or run with `--fail-on=error` while you work through them. SYG150–153 concern only `sygnal/ai` code.
94
+ - **`sygnal-check mcp` answers an `initialize` with 2025-11-25** when the client asks for it or for a version the server doesn't know (2025-06-18 before).
95
+ - **Reserved names:** the agent layer reads a static named `agent` (only when one runs); in `renderComponent()`, a sink named `LLM` without a driver gets the chat fake (`llmSink` picks another name).
96
+ - **`fromAISDK` resolves a request's string `model` only through its `models` option** (a provider, a registry lookup or a map); without it a string model is an error, never the AI SDK's global provider.
97
+ - **The `form` behavior** reads a `tool` option (made by `formTool()`; SYG245 otherwise) and handles `form.SET`.
98
+
99
+ ### Fixed
100
+
101
+ - `sygnal/vite`'s types accept `devtools: { redux }` (the declaration said `boolean`).
102
+ - The build keeps one TypeScript program alive at a time (peak memory 5.4 GB → 1.7 GB), so `npm run build` runs with Node's default heap; `dist/` is unchanged by it.
103
+
104
+ ### Security
105
+
106
+ - **No user text in tool descriptions or instructions** (D286, D296, G-631, G-644). Text from `read` projections and `agent.label` reaches a model only as data: through the read tool (WebMCP, MCP Apps; its results carry `untrustedContentHint`) or the `chat` behavior's framed app-state block ("data, not instructions"). Item tools list ids only, and tool descriptions hold only the fixed names you declared. `untrusted: false` keeps labels for projections without user text.
107
+ - **Never put a provider API key in browser code** (SYG670). A transport refuses to send an `Authorization` or API-key header from the browser to another origin's hosted endpoint; same-origin requests (your own proxy) and local hosts are allowed. The guides ship production chat through your server (`uiMessageStream('/api/chat')`) and warn against open relays.
108
+ - Consequential agent actions always wait for a confirmation: the `chat` behavior's `pending`, the WebMCP dialog, an in-page confirmation for the dev MCP endpoint; `t.callTool` without `confirm` throws. The dev MCP endpoint accepts only loopback peers with a local `Host` and `Origin`.
109
+
5
110
  ## 6.0.0 — 2026-10-08
6
111
 
7
112
  Network calls get a first-class layer. A request names the actions its answer becomes (`HTTP: (state) => ({ url, ok: 'LOADED', error: 'FAILED' })`), so there is no `select()` round trip; `makeFetchDriver()` replaces hand-written `fetch` drivers and request-id bookkeeping; `makeSocketDriver()` and the `connections` static open, close and reconnect WebSockets and server-sent events from state. `renderComponent()` tests answer requests and script sockets without wiring a driver. Tests can run on fake timers, against a real DOM, and read `t.state`. This release also fixes Switchable, calculated-field, Collection and Vike bugs that the agent evals found, and makes apps about 6 KB smaller: DevTools leave production builds (about 2 KB, every bundler) and `sygnal/vite` drops xstream's `globalthis` polyfill (about 4 KB).
package/dist/ai.cjs.js ADDED
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ var sygnal = require('sygnal');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, 'agentTools', {
8
+ enumerable: true,
9
+ get: function () { return sygnal.agentTools; }
10
+ });
11
+ Object.defineProperty(exports, 'agui', {
12
+ enumerable: true,
13
+ get: function () { return sygnal.agui; }
14
+ });
15
+ Object.defineProperty(exports, 'answers', {
16
+ enumerable: true,
17
+ get: function () { return sygnal.answers; }
18
+ });
19
+ Object.defineProperty(exports, 'anthropicMessages', {
20
+ enumerable: true,
21
+ get: function () { return sygnal.anthropicMessages; }
22
+ });
23
+ Object.defineProperty(exports, 'chat', {
24
+ enumerable: true,
25
+ get: function () { return sygnal.chat; }
26
+ });
27
+ Object.defineProperty(exports, 'chatCompletions', {
28
+ enumerable: true,
29
+ get: function () { return sygnal.chatCompletions; }
30
+ });
31
+ Object.defineProperty(exports, 'choice', {
32
+ enumerable: true,
33
+ get: function () { return sygnal.choice; }
34
+ });
35
+ Object.defineProperty(exports, 'chromePrompt', {
36
+ enumerable: true,
37
+ get: function () { return sygnal.chromePrompt; }
38
+ });
39
+ Object.defineProperty(exports, 'commandBar', {
40
+ enumerable: true,
41
+ get: function () { return sygnal.commandBar; }
42
+ });
43
+ Object.defineProperty(exports, 'decide', {
44
+ enumerable: true,
45
+ get: function () { return sygnal.decide; }
46
+ });
47
+ Object.defineProperty(exports, 'encodeOpenResponses', {
48
+ enumerable: true,
49
+ get: function () { return sygnal.encodeOpenResponses; }
50
+ });
51
+ Object.defineProperty(exports, 'experimentalExposeWebMcp', {
52
+ enumerable: true,
53
+ get: function () { return sygnal.experimentalExposeWebMcp; }
54
+ });
55
+ Object.defineProperty(exports, 'formTool', {
56
+ enumerable: true,
57
+ get: function () { return sygnal.formTool; }
58
+ });
59
+ Object.defineProperty(exports, 'fromAISDK', {
60
+ enumerable: true,
61
+ get: function () { return sygnal.fromAISDK; }
62
+ });
63
+ Object.defineProperty(exports, 'jsonSchema', {
64
+ enumerable: true,
65
+ get: function () { return sygnal.jsonSchema; }
66
+ });
67
+ Object.defineProperty(exports, 'makeChatDriver', {
68
+ enumerable: true,
69
+ get: function () { return sygnal.makeChatDriver; }
70
+ });
71
+ Object.defineProperty(exports, 'makeMcpAppDriver', {
72
+ enumerable: true,
73
+ get: function () { return sygnal.makeMcpAppDriver; }
74
+ });
75
+ Object.defineProperty(exports, 'messageText', {
76
+ enumerable: true,
77
+ get: function () { return sygnal.messageText; }
78
+ });
79
+ Object.defineProperty(exports, 'noul', {
80
+ enumerable: true,
81
+ get: function () { return sygnal.noul; }
82
+ });
83
+ Object.defineProperty(exports, 'openResponses', {
84
+ enumerable: true,
85
+ get: function () { return sygnal.openResponses; }
86
+ });
87
+ Object.defineProperty(exports, 'outputJsonSchema', {
88
+ enumerable: true,
89
+ get: function () { return sygnal.outputJsonSchema; }
90
+ });
91
+ Object.defineProperty(exports, 'parseInput', {
92
+ enumerable: true,
93
+ get: function () { return sygnal.parseInput; }
94
+ });
95
+ Object.defineProperty(exports, 'score', {
96
+ enumerable: true,
97
+ get: function () { return sygnal.score; }
98
+ });
99
+ Object.defineProperty(exports, 'strictSchemas', {
100
+ enumerable: true,
101
+ get: function () { return sygnal.strictSchemas; }
102
+ });
103
+ Object.defineProperty(exports, 'toJsonSchema', {
104
+ enumerable: true,
105
+ get: function () { return sygnal.toJsonSchema; }
106
+ });
107
+ Object.defineProperty(exports, 'uiMessageStream', {
108
+ enumerable: true,
109
+ get: function () { return sygnal.uiMessageStream; }
110
+ });
111
+ Object.defineProperty(exports, 'withToolResults', {
112
+ enumerable: true,
113
+ get: function () { return sygnal.withToolResults; }
114
+ });
115
+ //# sourceMappingURL=ai.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/ai.esm.js ADDED
@@ -0,0 +1,2 @@
1
+ export { agentTools, agui, answers, anthropicMessages, chat, chatCompletions, choice, chromePrompt, commandBar, decide, encodeOpenResponses, experimentalExposeWebMcp, formTool, fromAISDK, jsonSchema, makeChatDriver, makeMcpAppDriver, messageText, noul, openResponses, outputJsonSchema, parseInput, score, strictSchemas, toJsonSchema, uiMessageStream, withToolResults } from 'sygnal';
2
+ //# sourceMappingURL=ai.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}