From 4f0a4ddfba91798060b15aa24a6b372870264dbe Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 3 Aug 2026 12:17:27 +0200 Subject: [PATCH 01/50] feat: deliver portable agent-native Flect foundation --- .agents/skills/flect-quality/SKILL.md | 158 + .../skills/flect-quality/agents/openai.yaml | 4 + .agents/skills/flect/SKILL.md | 66 + .gitignore | 4 +- AGENTS.md | 41 + ARCHITECTURE.md | 575 +- CONTRIBUTING.md | 45 +- DESIGN.md | 57 +- PRODUCT.md | 7 + README.md | 265 +- VISION.md | 23 +- assets/agent-integrations/opencode/flect.js | 82 + bun.lock | 334 +- cli/flect-client.test.ts | 165 + cli/flect-client.ts | 299 + cli/flect.test.ts | 96 + cli/flect.ts | 195 + docs/capsule-format.md | 264 + docs/local-control.md | 256 + docs/performance.md | 63 + docs/product-capabilities.md | 197 + docs/product-quality.md | 547 ++ docs/recovery.md | 85 + docs/sharing.md | 99 + .../plans/2026-07-31-flect-chat-markdown.md | 794 +++ .../plans/2026-07-31-flect-embedded-axi.md | 1372 +++++ .../2026-07-31-flect-observable-control.md | 1351 +++++ ...26-08-01-flect-accessibility-appearance.md | 45 + .../plans/2026-08-01-flect-capsule-format.md | 31 + .../plans/2026-08-01-flect-distribution.md | 37 + .../2026-08-01-flect-opfs-git-workspace.md | 102 + .../2026-08-01-flect-pi-authentication.md | 100 + ...026-08-01-flect-product-http-capability.md | 10 + ...-08-01-flect-product-quality-foundation.md | 295 + .../plans/2026-08-01-flect-role-continuity.md | 61 + .../2026-08-01-flect-shape-use-workbench.md | 157 + .../2026-08-02-flect-permission-lifecycle.md | 460 ++ ...8-02-flect-portable-extension-lifecycle.md | 264 + ...026-08-03-flect-native-update-uninstall.md | 734 +++ .../2026-08-03-flect-product-adapters.md | 341 ++ .../plans/2026-08-03-flect-product-sdk.md | 338 ++ .../2026-08-03-flect-sharing-collaboration.md | 493 ++ ...7-30-flect-self-contained-shaper-design.md | 17 +- .../2026-07-31-flect-chat-markdown-design.md | 291 + .../2026-07-31-flect-embedded-axi-design.md | 695 +++ ...6-07-31-flect-observable-control-design.md | 839 +++ ...1-flect-accessibility-appearance-design.md | 85 + .../2026-08-01-flect-distribution-design.md | 97 + ...26-08-01-flect-pi-authentication-design.md | 197 + ...01-flect-product-http-capability-design.md | 27 + ...-01-flect-product-quality-system-design.md | 143 + ...2026-08-01-flect-role-continuity-design.md | 130 + ...-08-01-flect-shape-use-workbench-design.md | 180 + ...08-02-flect-permission-lifecycle-design.md | 314 + ...ect-portable-extension-lifecycle-design.md | 192 + ...03-flect-native-update-uninstall-design.md | 158 + ...2026-08-03-flect-product-adapter-design.md | 215 + .../2026-08-03-flect-product-sdk-design.md | 301 + ...8-03-flect-sharing-collaboration-design.md | 357 ++ docs/trust-model.md | 192 +- docs/updates-and-uninstall.md | 69 + .../2026-07-31-chat-markdown-verification.md | 161 + .../2026-07-31-embedded-axi-verification.md | 136 + ...6-07-31-observable-control-verification.md | 131 + .../2026-07-31-t3code-design-ux-audit.md | 220 + ...1-accessibility-appearance-verification.md | 81 + .../2026-08-01-browser-build-verification.md | 74 + ...2026-08-01-browser-package-verification.md | 52 + .../2026-08-01-capsule-verification.md | 89 + .../2026-08-01-distribution-verification.md | 97 + ...import-and-gate-regression-verification.md | 66 + .../2026-08-01-opfs-git-verification.md | 108 + .../2026-08-01-performance-verification.md | 79 + ...26-08-01-pi-authentication-verification.md | 115 + .../2026-08-01-product-quality-baseline.md | 455 ++ ...2026-08-01-role-continuity-verification.md | 80 + ...-08-01-shape-use-workbench-verification.md | 229 + ...026-08-01-storage-and-lock-verification.md | 66 + ...08-02-permission-lifecycle-verification.md | 83 + ...rtable-extension-lifecycle-verification.md | 158 + ...product-quality-foundation-verification.md | 88 + ...03-native-update-uninstall-verification.md | 133 + ...2026-08-03-product-adapter-verification.md | 146 + .../2026-08-03-product-sdk-verification.md | 219 + ...26-08-03-sharing-collaboration-baseline.md | 61 + ...8-03-sharing-collaboration-verification.md | 212 + .../2026-07-31-chat-markdown-compact.png | Bin 0 -> 119963 bytes .../2026-07-31-chat-markdown-desktop.png | Bin 0 -> 148538 bytes ...-02-portable-extension-native-accepted.png | Bin 0 -> 188346 bytes ...02-portable-extension-native-connected.png | Bin 0 -> 188947 bytes ...8-02-portable-extension-native-failure.png | Bin 0 -> 287139 bytes ...-08-02-portable-extension-native-final.png | Bin 0 -> 188947 bytes ...02-portable-extension-native-recovered.png | Bin 0 -> 270352 bytes ...08-02-portable-extension-native-review.png | Bin 0 -> 261059 bytes ...ive-update-uninstall-diagnostics-final.png | Bin 0 -> 301356 bytes ...-update-uninstall-diagnostics-scrolled.png | Bin 0 -> 273375 bytes ...03-native-update-uninstall-diagnostics.png | Bin 0 -> 273375 bytes ...8-03-native-update-uninstall-installed.png | Bin 0 -> 188505 bytes ...extension-native-accepted-first-launch.png | Bin 0 -> 1539149 bytes ...able-extension-native-accepted-restart.png | Bin 0 -> 188284 bytes .../2026-08-03-product-adapter-installed.png | Bin 0 -> 672104 bytes .../2026-08-03-product-adapter-native.png | Bin 0 -> 589634 bytes .../2026-08-03-product-sdk-installed.png | Bin 0 -> 185432 bytes .../assets/2026-08-03-product-sdk-native.png | Bin 0 -> 489311 bytes .../2026-08-03-sharing-native-review.png | Bin 0 -> 281686 bytes .../assets/2026-08-03-sharing-native.png | Bin 0 -> 188314 bytes examples/product-adapter/README.md | 106 + .../product-adapter/reference-product.test.ts | 231 + examples/product-adapter/reference-product.ts | 464 ++ examples/product-sdk/README.md | 50 + examples/product-sdk/brokered-incidents.ts | 182 + examples/product-sdk/browser-projects.ts | 253 + examples/product-sdk/offline-board.ts | 171 + examples/product-sdk/private-sharing.ts | 59 + .../product-sdk/reference-products.test.ts | 459 ++ examples/product-sdk/reference-support.ts | 160 + package.json | 38 +- packages/product/README.md | 193 + packages/product/package.json | 49 + packages/product/src/adoption.test.ts | 292 + packages/product/src/adoption.ts | 468 ++ packages/product/src/capsule.ts | 288 + packages/product/src/contracts.test.ts | 152 + packages/product/src/contracts.ts | 10 + packages/product/src/extensions.ts | 358 ++ packages/product/src/host.test.ts | 93 + packages/product/src/host.ts | 4 + packages/product/src/host/product-events.ts | 198 + packages/product/src/host/product-graphql.ts | 270 + packages/product/src/host/product-http.ts | 214 + .../product/src/host/share-source.test.ts | 100 + packages/product/src/host/share-source.ts | 127 + packages/product/src/index.ts | 3 + packages/product/src/integration.test.ts | 246 + packages/product/src/integration.ts | 382 ++ packages/product/src/portable-tar.ts | 205 + packages/product/src/product-adapter.ts | 18 + packages/product/src/product-capability.ts | 506 ++ packages/product/src/product-events.ts | 67 + packages/product/src/product-graphql.ts | 114 + packages/product/src/product-http.ts | 82 + packages/product/src/share.test.ts | 308 + packages/product/src/share.ts | 305 + packages/product/tsconfig.json | 19 + playwright.config.ts | 5 +- scripts/build-sidecar.ts | 53 +- scripts/compare-release-builds.test.ts | 80 + scripts/compare-release-builds.ts | 223 + scripts/generate-flect-skill.test.ts | 88 + scripts/generate-flect-skill.ts | 130 + scripts/native-update-fixture-dogfood.test.ts | 26 + scripts/native-update-fixture-dogfood.ts | 764 +++ scripts/package-product-sdk.ts | 352 ++ scripts/package-release.test.ts | 172 + scripts/package-release.ts | 619 +- scripts/product-quality-coverage.test.ts | 74 + scripts/product-quality-coverage.ts | 102 + scripts/product-sdk-package.test.ts | 50 + scripts/release-update.test.ts | 168 + scripts/release-update.ts | 228 + scripts/smoke-pi.ts | 59 +- scripts/vite-development-smoke.test.ts | 32 + server/app.test.ts | 174 + server/app.ts | 286 +- server/control-broker.test.ts | 298 + server/control-broker.ts | 672 +++ server/control-descriptor.test.ts | 76 + server/control-descriptor.ts | 123 + server/credential-prompt-host.test.ts | 186 + server/credential-prompt-host.ts | 304 + server/desktop-capsule-loader.test.ts | 12 + server/desktop-capsule-loader.ts | 5 + server/index.ts | 4 +- server/mcp-adapter.test.ts | 123 + server/mcp-adapter.ts | 206 + server/pi-extension-isolation.test.ts | 31 + server/pi-extension-isolation.ts | 94 + server/pi-model-runtime.ts | 20 + server/pi-runtime.test.ts | 191 +- server/pi-runtime.ts | 454 +- server/pi-services.ts | 21 + server/pi-workbench-bridge.test.ts | 38 + server/pi-workbench-bridge.ts | 52 + server/provider-authentication.test.ts | 475 ++ server/provider-authentication.ts | 771 +++ server/rpc-handlers.test.ts | 127 +- server/rpc-handlers.ts | 33 + server/runtime.ts | 25 + server/sidecar-mode.test.ts | 54 + server/sidecar-mode.ts | 43 + server/sidecar.ts | 49 +- server/test-runtime.ts | 476 +- shared/browser-build.ts | 144 + shared/browser-package.ts | 52 + shared/capsule-fixture.ts | 31 + shared/capsule-protocol.test.ts | 116 + shared/capsule-protocol.ts | 146 + shared/capsule.test.ts | 269 + shared/capsule.ts | 1 + shared/contracts.test.ts | 184 + shared/contracts.ts | 422 +- shared/control-channel.ts | 134 + shared/control.test.ts | 613 ++ shared/control.ts | 971 ++++ shared/extensions.test.ts | 228 +- shared/extensions.ts | 56 +- shared/git-workspace.ts | 559 ++ shared/interface-actions.test.ts | 142 + shared/interface-actions.ts | 89 + shared/interface-document.ts | 24 +- shared/native-update.ts | 91 + shared/performance-budgets.ts | 26 + shared/product-adapter.ts | 1 + shared/product-capability.test.ts | 132 + shared/product-capability.ts | 1 + shared/product-events.test.ts | 138 + shared/product-events.ts | 1 + shared/product-graphql.test.ts | 141 + shared/product-graphql.ts | 1 + shared/product-http.ts | 1 + shared/project-import.ts | 27 + shared/role-continuity.test.ts | 128 + shared/role-continuity.ts | 151 + shared/rpc.ts | 135 + shared/sandbox.ts | 5 +- shared/setup.ts | 58 + shared/share-installation.test.ts | 172 + shared/share-installation.ts | 195 + shared/share-review.ts | 85 + shared/share.ts | 1 + shared/uninstall.ts | 67 + src-tauri/Cargo.lock | 405 +- src-tauri/Cargo.toml | 5 + src-tauri/gen/schemas/acl-manifests.json | 2 +- src-tauri/gen/schemas/desktop-schema.json | 54 + src-tauri/gen/schemas/macOS-schema.json | 54 + src-tauri/src/lib.rs | 732 ++- src-tauri/src/main.rs | 8 +- src-tauri/src/native_update.rs | 267 + src-tauri/tauri.conf.json | 5 +- src-tauri/tauri.release.conf.json | 5 + src/app.test.tsx | 458 +- src/app.tsx | 1050 ++-- src/axi/agent-command-bridge.test.ts | 257 + src/axi/agent-command-bridge.ts | 118 + src/axi/agent-command-bus.test.ts | 119 + src/axi/agent-command-bus.ts | 181 + src/axi/agent-gateway.ts | 135 + src/axi/broker-gateway.ts | 57 + src/axi/command.test.ts | 321 ++ src/axi/command.ts | 1085 ++++ src/axi/contracts.ts | 42 + src/axi/gateway.ts | 70 + src/axi/output.test.ts | 113 + src/axi/output.ts | 105 + src/axi/program.test.ts | 942 +++ src/axi/program.ts | 823 +++ src/build/browser-build-diagnostic.tsx | 315 ++ src/build/browser-build-digest.ts | 40 + src/build/browser-build-store.test.ts | 85 + src/build/browser-build-store.ts | 262 + src/build/browser-build-worker.ts | 317 ++ src/build/browser-build.test.ts | 195 + src/build/browser-build.ts | 239 + src/build/browser-package-cache.ts | 307 + src/build/browser-package-diagnostic.tsx | 175 + src/build/browser-package-resolver.test.ts | 105 + src/build/browser-package-resolver.ts | 289 + src/build/framework-capsule.test.ts | 106 + src/build/framework-capsule.ts | 129 + src/build/portable-package-manifest.test.ts | 48 + src/build/portable-package-manifest.ts | 76 + src/build/proposal-build.test.ts | 187 + src/build/proposal-build.ts | 210 + src/build/restricted-css.test.ts | 56 + src/build/restricted-css.ts | 57 + .../product-adoption-diagnostic.test.tsx | 85 + .../product-adoption-diagnostic.tsx | 391 ++ .../product-capability-broker.test.ts | 372 ++ src/capabilities/product-capability-broker.ts | 641 +++ .../product-capability-decision-store.test.ts | 283 + .../product-capability-decision-store.ts | 248 + .../product-capability-diagnostic.tsx | 166 + ...product-capability-registry-policy.test.ts | 202 + .../product-capability-registry.test.ts | 127 + .../product-capability-registry.ts | 171 + .../product-event-registry.test.ts | 299 + src/capabilities/product-event-registry.ts | 168 + src/capabilities/product-events.test.ts | 283 + src/capabilities/product-events.ts | 1 + src/capabilities/product-graphql.test.ts | 227 + src/capabilities/product-graphql.ts | 1 + src/capabilities/product-http.test.ts | 163 + src/capabilities/product-http.ts | 1 + src/capabilities/product-integration.test.ts | 195 + src/capabilities/product-integration.ts | 40 + src/capabilities/product-operation-failure.ts | 32 + .../reference-product-diagnostic.tsx | 357 ++ src/capsule/capsule-frame-diagnostic.tsx | 60 + src/capsule/capsule-store.test.ts | 35 + src/capsule/capsule-store.ts | 138 + src/components/activity-card.test.tsx | 46 + src/components/activity-card.tsx | 101 + src/components/agent-rail.test.tsx | 560 +- src/components/agent-rail.tsx | 1000 +++- src/components/capsule-frame.test.tsx | 267 + src/components/capsule-frame.tsx | 504 ++ src/components/composer-actions-menu.test.tsx | 116 +- src/components/composer-actions-menu.tsx | 362 +- src/components/composer.test.tsx | 74 +- src/components/composer.tsx | 166 +- src/components/diagnostics-panel.test.tsx | 329 ++ src/components/diagnostics-panel.tsx | 445 ++ src/components/extension-review.test.tsx | 245 + src/components/extension-review.tsx | 416 ++ src/components/icons.tsx | 83 + src/components/interface-renderer.test.tsx | 30 +- src/components/interface-renderer.tsx | 24 +- src/components/markdown-code-block.test.tsx | 124 + src/components/markdown-code-block.tsx | 135 + src/components/markdown-highlighter.test.ts | 107 + src/components/markdown-highlighter.ts | 105 + src/components/markdown-policy.test.ts | 65 + src/components/markdown-policy.ts | 51 + .../markdown-table-serialization.test.ts | 70 + .../markdown-table-serialization.ts | 34 + src/components/markdown-table.test.tsx | 123 + src/components/markdown-table.tsx | 138 + src/components/message-content.test.tsx | 252 + src/components/message-content.tsx | 432 +- src/components/model-menu.test.tsx | 114 +- src/components/model-menu.tsx | 376 +- src/components/provider-auth-panel.test.tsx | 175 + src/components/provider-auth-panel.tsx | 304 + src/components/role-aware-shell.test.tsx | 134 +- src/components/role-aware-shell.tsx | 344 +- src/components/role-switcher.test.tsx | 34 +- src/components/role-switcher.tsx | 33 +- src/components/share-library.test.tsx | 129 + src/components/share-library.tsx | 269 + src/components/share-review.test.tsx | 296 + src/components/share-review.tsx | 327 ++ src/components/share-source-dialog.test.tsx | 157 + src/components/share-source-dialog.tsx | 371 ++ src/execution/npm-registry.ts | 26 + src/extensions/extension-catalog.test.ts | 302 + src/extensions/extension-catalog.ts | 574 ++ .../portable-extension-host.test.ts | 327 ++ src/extensions/portable-extension-host.ts | 365 ++ src/git/git-share-import-diagnostic.tsx | 152 + src/git/git-share-lifecycle-diagnostic.tsx | 349 ++ src/git/git-transaction-diagnostic.tsx | 152 + src/git/git-workspace-diagnostic.tsx | 225 + src/git/git-workspace-import.test.ts | 215 + src/git/git-workspace-worker.ts | 1979 +++++++ src/git/git-workspace.ts | 653 +++ src/git/repository-tar.test.ts | 216 + src/git/repository-tar.ts | 399 ++ src/git/storage-reset-diagnostic.tsx | 42 + src/hooks/use-agent-session.test.tsx | 166 +- src/hooks/use-agent-session.ts | 991 ++-- src/hooks/use-native-setup.test.tsx | 118 + src/hooks/use-native-setup.ts | 168 + src/hooks/use-native-update.test.tsx | 116 + src/hooks/use-native-update.ts | 123 + src/hooks/use-sticky-follow.test.tsx | 123 + src/hooks/use-sticky-follow.ts | 124 + src/hooks/use-workspace.test.tsx | 290 + src/hooks/use-workspace.ts | 432 ++ src/lib/agent-integration.test.ts | 239 + src/lib/agent-integration.ts | 557 ++ src/lib/agent-workspace.test.ts | 1037 ++++ src/lib/agent-workspace.ts | 1840 ++++++ src/lib/api.test.ts | 83 + src/lib/api.ts | 122 +- src/lib/browser-capsule-loader.test.ts | 64 + src/lib/browser-capsule-loader.ts | 85 + src/lib/clipboard.test.ts | 41 + src/lib/clipboard.ts | 36 + src/lib/git-interface-repository.test.ts | 957 ++++ src/lib/git-interface-repository.ts | 786 +++ src/lib/interface-repository.test.ts | 24 +- src/lib/interface-repository.ts | 4 +- src/lib/native-update.test.ts | 150 + src/lib/native-update.ts | 89 + src/lib/operation-journal.test.ts | 274 + src/lib/operation-journal.ts | 208 + src/lib/role-continuity-repository.test.ts | 191 + src/lib/role-continuity-repository.ts | 249 + src/lib/role-continuity.test.ts | 201 + src/lib/role-continuity.ts | 123 + src/lib/runtime.ts | 459 +- src/lib/shaping-kernel.test.ts | 71 + src/lib/shaping-kernel.ts | 56 + src/lib/shell-link.test.ts | 76 + src/lib/shell-link.ts | 137 + src/lib/tauri-transport.test.ts | 257 +- src/lib/tauri-transport.ts | 224 +- src/lib/uninstall.test.ts | 169 + src/lib/uninstall.ts | 179 + src/lib/web-project-import.test.ts | 245 + src/lib/web-project-import.ts | 392 ++ src/lib/workbench-state.test.ts | 162 + src/lib/workbench-state.ts | 121 + src/lib/workspace-control-bridge.test.ts | 381 ++ src/lib/workspace-control-bridge.ts | 174 + src/lib/workspace-control-transport.ts | 155 + src/lib/workspace-controller.test.ts | 3774 ++++++++++++ src/lib/workspace-controller.ts | 5039 +++++++++++++++++ src/main.tsx | 109 +- src/sandbox/quickjs.ts | 17 +- .../private-share-source-registry.test.ts | 112 + src/sharing/private-share-source-registry.ts | 116 + src/sharing/share-archive.test.ts | 252 + src/sharing/share-archive.ts | 191 + src/sharing/share-candidate-store.test.ts | 44 + src/sharing/share-candidate-store.ts | 147 + src/sharing/share-installation-store.test.ts | 121 + src/sharing/share-installation-store.ts | 194 + src/sharing/share-quarantine.test.ts | 269 + src/sharing/share-quarantine.ts | 303 + src/sharing/share-repository.test.ts | 657 +++ src/sharing/share-repository.ts | 917 +++ src/sharing/share-review.test.ts | 195 + src/sharing/share-review.ts | 211 + src/sharing/share-signature-verifier.test.ts | 109 + src/sharing/share-signature-verifier.ts | 108 + src/sharing/share-source-resolver.test.ts | 261 + src/sharing/share-source-resolver.ts | 238 + src/shell/bun-command-live.ts | 29 +- src/shell/flect-command.test.ts | 563 ++ src/shell/flect-command.ts | 404 ++ src/shell/git-command.ts | 552 ++ src/shell/persistent-workspace-fs.test.ts | 94 + src/shell/persistent-workspace-fs.ts | 360 ++ src/shell/sandboxed-shell-service.ts | 21 +- src/shell/sandboxed-shell.test.ts | 110 +- src/shell/sandboxed-shell.ts | 234 +- src/styles.css | 3950 +++++++++++-- src/types/wasm-git.d.ts | 40 + src/vite-env.d.ts | 1 + tests/e2e/accessibility.spec.ts | 166 + tests/e2e/browser-build.spec.ts | 41 + tests/e2e/browser-package.spec.ts | 32 + tests/e2e/capsule-frame.spec.ts | 95 + tests/e2e/embedded-axi.spec.ts | 299 + tests/e2e/flect.spec.ts | 1542 ++++- tests/e2e/native-update.spec.ts | 76 + tests/e2e/opfs-git.spec.ts | 273 + tests/e2e/performance.spec.ts | 208 + tests/e2e/portable-extensions.spec.ts | 304 + tests/e2e/product-adapter.spec.ts | 84 + tests/e2e/product-adoption.spec.ts | 99 + tests/e2e/product-capability.spec.ts | 281 + tests/e2e/reset-browser-workspace.ts | 12 + tests/e2e/sharing.spec.ts | 942 +++ .../pi-extensions/fail-on-agent-start.ts | 7 + .../fixtures/portable-extensions/capsules.ts | 156 + .../reference-product-capsule.test.ts | 43 + tests/fixtures/reference-product-capsule.ts | 108 + tests/fixtures/sharing/generate.test.ts | 212 + tests/fixtures/sharing/generate.ts | 661 +++ tests/fixtures/static-site/app.js | 3 + tests/fixtures/static-site/index.html | 7 + tests/fixtures/static-site/mark.svg | 4 + tests/fixtures/static-site/styles.css | 3 + tests/fixtures/vite-react/index.html | 12 + tests/fixtures/vite-react/package.json | 18 + tests/fixtures/vite-react/src/main.jsx | 17 + tests/fixtures/vite-react/src/styles.css | 27 + tests/fixtures/vite-typescript/index.html | 12 + tests/fixtures/vite-typescript/src/main.ts | 11 + tests/fixtures/vite-typescript/src/styles.css | 21 + tsconfig.app.json | 8 +- tsconfig.server.json | 19 +- vite.config.ts | 41 +- vitest.config.ts | 13 + 477 files changed, 107118 insertions(+), 2870 deletions(-) create mode 100644 .agents/skills/flect-quality/SKILL.md create mode 100644 .agents/skills/flect-quality/agents/openai.yaml create mode 100644 .agents/skills/flect/SKILL.md create mode 100644 assets/agent-integrations/opencode/flect.js create mode 100644 cli/flect-client.test.ts create mode 100644 cli/flect-client.ts create mode 100644 cli/flect.test.ts create mode 100644 cli/flect.ts create mode 100644 docs/capsule-format.md create mode 100644 docs/local-control.md create mode 100644 docs/performance.md create mode 100644 docs/product-capabilities.md create mode 100644 docs/product-quality.md create mode 100644 docs/recovery.md create mode 100644 docs/sharing.md create mode 100644 docs/superpowers/plans/2026-07-31-flect-chat-markdown.md create mode 100644 docs/superpowers/plans/2026-07-31-flect-embedded-axi.md create mode 100644 docs/superpowers/plans/2026-07-31-flect-observable-control.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-accessibility-appearance.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-capsule-format.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-distribution.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-opfs-git-workspace.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-pi-authentication.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-product-http-capability.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-product-quality-foundation.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-role-continuity.md create mode 100644 docs/superpowers/plans/2026-08-01-flect-shape-use-workbench.md create mode 100644 docs/superpowers/plans/2026-08-02-flect-permission-lifecycle.md create mode 100644 docs/superpowers/plans/2026-08-02-flect-portable-extension-lifecycle.md create mode 100644 docs/superpowers/plans/2026-08-03-flect-native-update-uninstall.md create mode 100644 docs/superpowers/plans/2026-08-03-flect-product-adapters.md create mode 100644 docs/superpowers/plans/2026-08-03-flect-product-sdk.md create mode 100644 docs/superpowers/plans/2026-08-03-flect-sharing-collaboration.md create mode 100644 docs/superpowers/specs/2026-07-31-flect-chat-markdown-design.md create mode 100644 docs/superpowers/specs/2026-07-31-flect-embedded-axi-design.md create mode 100644 docs/superpowers/specs/2026-07-31-flect-observable-control-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-accessibility-appearance-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-distribution-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-pi-authentication-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-product-http-capability-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-product-quality-system-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-role-continuity-design.md create mode 100644 docs/superpowers/specs/2026-08-01-flect-shape-use-workbench-design.md create mode 100644 docs/superpowers/specs/2026-08-02-flect-permission-lifecycle-design.md create mode 100644 docs/superpowers/specs/2026-08-02-flect-portable-extension-lifecycle-design.md create mode 100644 docs/superpowers/specs/2026-08-03-flect-native-update-uninstall-design.md create mode 100644 docs/superpowers/specs/2026-08-03-flect-product-adapter-design.md create mode 100644 docs/superpowers/specs/2026-08-03-flect-product-sdk-design.md create mode 100644 docs/superpowers/specs/2026-08-03-flect-sharing-collaboration-design.md create mode 100644 docs/updates-and-uninstall.md create mode 100644 docs/verification/2026-07-31-chat-markdown-verification.md create mode 100644 docs/verification/2026-07-31-embedded-axi-verification.md create mode 100644 docs/verification/2026-07-31-observable-control-verification.md create mode 100644 docs/verification/2026-07-31-t3code-design-ux-audit.md create mode 100644 docs/verification/2026-08-01-accessibility-appearance-verification.md create mode 100644 docs/verification/2026-08-01-browser-build-verification.md create mode 100644 docs/verification/2026-08-01-browser-package-verification.md create mode 100644 docs/verification/2026-08-01-capsule-verification.md create mode 100644 docs/verification/2026-08-01-distribution-verification.md create mode 100644 docs/verification/2026-08-01-import-and-gate-regression-verification.md create mode 100644 docs/verification/2026-08-01-opfs-git-verification.md create mode 100644 docs/verification/2026-08-01-performance-verification.md create mode 100644 docs/verification/2026-08-01-pi-authentication-verification.md create mode 100644 docs/verification/2026-08-01-product-quality-baseline.md create mode 100644 docs/verification/2026-08-01-role-continuity-verification.md create mode 100644 docs/verification/2026-08-01-shape-use-workbench-verification.md create mode 100644 docs/verification/2026-08-01-storage-and-lock-verification.md create mode 100644 docs/verification/2026-08-02-permission-lifecycle-verification.md create mode 100644 docs/verification/2026-08-02-portable-extension-lifecycle-verification.md create mode 100644 docs/verification/2026-08-02-product-quality-foundation-verification.md create mode 100644 docs/verification/2026-08-03-native-update-uninstall-verification.md create mode 100644 docs/verification/2026-08-03-product-adapter-verification.md create mode 100644 docs/verification/2026-08-03-product-sdk-verification.md create mode 100644 docs/verification/2026-08-03-sharing-collaboration-baseline.md create mode 100644 docs/verification/2026-08-03-sharing-collaboration-verification.md create mode 100644 docs/verification/assets/2026-07-31-chat-markdown-compact.png create mode 100644 docs/verification/assets/2026-07-31-chat-markdown-desktop.png create mode 100644 docs/verification/assets/2026-08-02-portable-extension-native-accepted.png create mode 100644 docs/verification/assets/2026-08-02-portable-extension-native-connected.png create mode 100644 docs/verification/assets/2026-08-02-portable-extension-native-failure.png create mode 100644 docs/verification/assets/2026-08-02-portable-extension-native-final.png create mode 100644 docs/verification/assets/2026-08-02-portable-extension-native-recovered.png create mode 100644 docs/verification/assets/2026-08-02-portable-extension-native-review.png create mode 100644 docs/verification/assets/2026-08-03-native-update-uninstall-diagnostics-final.png create mode 100644 docs/verification/assets/2026-08-03-native-update-uninstall-diagnostics-scrolled.png create mode 100644 docs/verification/assets/2026-08-03-native-update-uninstall-diagnostics.png create mode 100644 docs/verification/assets/2026-08-03-native-update-uninstall-installed.png create mode 100644 docs/verification/assets/2026-08-03-portable-extension-native-accepted-first-launch.png create mode 100644 docs/verification/assets/2026-08-03-portable-extension-native-accepted-restart.png create mode 100644 docs/verification/assets/2026-08-03-product-adapter-installed.png create mode 100644 docs/verification/assets/2026-08-03-product-adapter-native.png create mode 100644 docs/verification/assets/2026-08-03-product-sdk-installed.png create mode 100644 docs/verification/assets/2026-08-03-product-sdk-native.png create mode 100644 docs/verification/assets/2026-08-03-sharing-native-review.png create mode 100644 docs/verification/assets/2026-08-03-sharing-native.png create mode 100644 examples/product-adapter/README.md create mode 100644 examples/product-adapter/reference-product.test.ts create mode 100644 examples/product-adapter/reference-product.ts create mode 100644 examples/product-sdk/README.md create mode 100644 examples/product-sdk/brokered-incidents.ts create mode 100644 examples/product-sdk/browser-projects.ts create mode 100644 examples/product-sdk/offline-board.ts create mode 100644 examples/product-sdk/private-sharing.ts create mode 100644 examples/product-sdk/reference-products.test.ts create mode 100644 examples/product-sdk/reference-support.ts create mode 100644 packages/product/README.md create mode 100644 packages/product/package.json create mode 100644 packages/product/src/adoption.test.ts create mode 100644 packages/product/src/adoption.ts create mode 100644 packages/product/src/capsule.ts create mode 100644 packages/product/src/contracts.test.ts create mode 100644 packages/product/src/contracts.ts create mode 100644 packages/product/src/extensions.ts create mode 100644 packages/product/src/host.test.ts create mode 100644 packages/product/src/host.ts create mode 100644 packages/product/src/host/product-events.ts create mode 100644 packages/product/src/host/product-graphql.ts create mode 100644 packages/product/src/host/product-http.ts create mode 100644 packages/product/src/host/share-source.test.ts create mode 100644 packages/product/src/host/share-source.ts create mode 100644 packages/product/src/index.ts create mode 100644 packages/product/src/integration.test.ts create mode 100644 packages/product/src/integration.ts create mode 100644 packages/product/src/portable-tar.ts create mode 100644 packages/product/src/product-adapter.ts create mode 100644 packages/product/src/product-capability.ts create mode 100644 packages/product/src/product-events.ts create mode 100644 packages/product/src/product-graphql.ts create mode 100644 packages/product/src/product-http.ts create mode 100644 packages/product/src/share.test.ts create mode 100644 packages/product/src/share.ts create mode 100644 packages/product/tsconfig.json create mode 100644 scripts/compare-release-builds.test.ts create mode 100644 scripts/compare-release-builds.ts create mode 100644 scripts/generate-flect-skill.test.ts create mode 100644 scripts/generate-flect-skill.ts create mode 100644 scripts/native-update-fixture-dogfood.test.ts create mode 100644 scripts/native-update-fixture-dogfood.ts create mode 100644 scripts/package-product-sdk.ts create mode 100644 scripts/product-quality-coverage.test.ts create mode 100644 scripts/product-quality-coverage.ts create mode 100644 scripts/product-sdk-package.test.ts create mode 100644 scripts/release-update.test.ts create mode 100644 scripts/release-update.ts create mode 100644 scripts/vite-development-smoke.test.ts create mode 100644 server/control-broker.test.ts create mode 100644 server/control-broker.ts create mode 100644 server/control-descriptor.test.ts create mode 100644 server/control-descriptor.ts create mode 100644 server/credential-prompt-host.test.ts create mode 100644 server/credential-prompt-host.ts create mode 100644 server/desktop-capsule-loader.test.ts create mode 100644 server/desktop-capsule-loader.ts create mode 100644 server/mcp-adapter.test.ts create mode 100644 server/mcp-adapter.ts create mode 100644 server/pi-extension-isolation.test.ts create mode 100644 server/pi-extension-isolation.ts create mode 100644 server/pi-model-runtime.ts create mode 100644 server/pi-services.ts create mode 100644 server/pi-workbench-bridge.test.ts create mode 100644 server/pi-workbench-bridge.ts create mode 100644 server/provider-authentication.test.ts create mode 100644 server/provider-authentication.ts create mode 100644 server/sidecar-mode.test.ts create mode 100644 server/sidecar-mode.ts create mode 100644 shared/browser-build.ts create mode 100644 shared/browser-package.ts create mode 100644 shared/capsule-fixture.ts create mode 100644 shared/capsule-protocol.test.ts create mode 100644 shared/capsule-protocol.ts create mode 100644 shared/capsule.test.ts create mode 100644 shared/capsule.ts create mode 100644 shared/control-channel.ts create mode 100644 shared/control.test.ts create mode 100644 shared/control.ts create mode 100644 shared/git-workspace.ts create mode 100644 shared/interface-actions.test.ts create mode 100644 shared/interface-actions.ts create mode 100644 shared/native-update.ts create mode 100644 shared/performance-budgets.ts create mode 100644 shared/product-adapter.ts create mode 100644 shared/product-capability.test.ts create mode 100644 shared/product-capability.ts create mode 100644 shared/product-events.test.ts create mode 100644 shared/product-events.ts create mode 100644 shared/product-graphql.test.ts create mode 100644 shared/product-graphql.ts create mode 100644 shared/product-http.ts create mode 100644 shared/project-import.ts create mode 100644 shared/role-continuity.test.ts create mode 100644 shared/role-continuity.ts create mode 100644 shared/setup.ts create mode 100644 shared/share-installation.test.ts create mode 100644 shared/share-installation.ts create mode 100644 shared/share-review.ts create mode 100644 shared/share.ts create mode 100644 shared/uninstall.ts create mode 100644 src-tauri/src/native_update.rs create mode 100644 src-tauri/tauri.release.conf.json create mode 100644 src/axi/agent-command-bridge.test.ts create mode 100644 src/axi/agent-command-bridge.ts create mode 100644 src/axi/agent-command-bus.test.ts create mode 100644 src/axi/agent-command-bus.ts create mode 100644 src/axi/agent-gateway.ts create mode 100644 src/axi/broker-gateway.ts create mode 100644 src/axi/command.test.ts create mode 100644 src/axi/command.ts create mode 100644 src/axi/contracts.ts create mode 100644 src/axi/gateway.ts create mode 100644 src/axi/output.test.ts create mode 100644 src/axi/output.ts create mode 100644 src/axi/program.test.ts create mode 100644 src/axi/program.ts create mode 100644 src/build/browser-build-diagnostic.tsx create mode 100644 src/build/browser-build-digest.ts create mode 100644 src/build/browser-build-store.test.ts create mode 100644 src/build/browser-build-store.ts create mode 100644 src/build/browser-build-worker.ts create mode 100644 src/build/browser-build.test.ts create mode 100644 src/build/browser-build.ts create mode 100644 src/build/browser-package-cache.ts create mode 100644 src/build/browser-package-diagnostic.tsx create mode 100644 src/build/browser-package-resolver.test.ts create mode 100644 src/build/browser-package-resolver.ts create mode 100644 src/build/framework-capsule.test.ts create mode 100644 src/build/framework-capsule.ts create mode 100644 src/build/portable-package-manifest.test.ts create mode 100644 src/build/portable-package-manifest.ts create mode 100644 src/build/proposal-build.test.ts create mode 100644 src/build/proposal-build.ts create mode 100644 src/build/restricted-css.test.ts create mode 100644 src/build/restricted-css.ts create mode 100644 src/capabilities/product-adoption-diagnostic.test.tsx create mode 100644 src/capabilities/product-adoption-diagnostic.tsx create mode 100644 src/capabilities/product-capability-broker.test.ts create mode 100644 src/capabilities/product-capability-broker.ts create mode 100644 src/capabilities/product-capability-decision-store.test.ts create mode 100644 src/capabilities/product-capability-decision-store.ts create mode 100644 src/capabilities/product-capability-diagnostic.tsx create mode 100644 src/capabilities/product-capability-registry-policy.test.ts create mode 100644 src/capabilities/product-capability-registry.test.ts create mode 100644 src/capabilities/product-capability-registry.ts create mode 100644 src/capabilities/product-event-registry.test.ts create mode 100644 src/capabilities/product-event-registry.ts create mode 100644 src/capabilities/product-events.test.ts create mode 100644 src/capabilities/product-events.ts create mode 100644 src/capabilities/product-graphql.test.ts create mode 100644 src/capabilities/product-graphql.ts create mode 100644 src/capabilities/product-http.test.ts create mode 100644 src/capabilities/product-http.ts create mode 100644 src/capabilities/product-integration.test.ts create mode 100644 src/capabilities/product-integration.ts create mode 100644 src/capabilities/product-operation-failure.ts create mode 100644 src/capabilities/reference-product-diagnostic.tsx create mode 100644 src/capsule/capsule-frame-diagnostic.tsx create mode 100644 src/capsule/capsule-store.test.ts create mode 100644 src/capsule/capsule-store.ts create mode 100644 src/components/activity-card.test.tsx create mode 100644 src/components/activity-card.tsx create mode 100644 src/components/capsule-frame.test.tsx create mode 100644 src/components/capsule-frame.tsx create mode 100644 src/components/diagnostics-panel.test.tsx create mode 100644 src/components/diagnostics-panel.tsx create mode 100644 src/components/extension-review.test.tsx create mode 100644 src/components/extension-review.tsx create mode 100644 src/components/markdown-code-block.test.tsx create mode 100644 src/components/markdown-code-block.tsx create mode 100644 src/components/markdown-highlighter.test.ts create mode 100644 src/components/markdown-highlighter.ts create mode 100644 src/components/markdown-policy.test.ts create mode 100644 src/components/markdown-policy.ts create mode 100644 src/components/markdown-table-serialization.test.ts create mode 100644 src/components/markdown-table-serialization.ts create mode 100644 src/components/markdown-table.test.tsx create mode 100644 src/components/markdown-table.tsx create mode 100644 src/components/message-content.test.tsx create mode 100644 src/components/provider-auth-panel.test.tsx create mode 100644 src/components/provider-auth-panel.tsx create mode 100644 src/components/share-library.test.tsx create mode 100644 src/components/share-library.tsx create mode 100644 src/components/share-review.test.tsx create mode 100644 src/components/share-review.tsx create mode 100644 src/components/share-source-dialog.test.tsx create mode 100644 src/components/share-source-dialog.tsx create mode 100644 src/execution/npm-registry.ts create mode 100644 src/extensions/extension-catalog.test.ts create mode 100644 src/extensions/extension-catalog.ts create mode 100644 src/extensions/portable-extension-host.test.ts create mode 100644 src/extensions/portable-extension-host.ts create mode 100644 src/git/git-share-import-diagnostic.tsx create mode 100644 src/git/git-share-lifecycle-diagnostic.tsx create mode 100644 src/git/git-transaction-diagnostic.tsx create mode 100644 src/git/git-workspace-diagnostic.tsx create mode 100644 src/git/git-workspace-import.test.ts create mode 100644 src/git/git-workspace-worker.ts create mode 100644 src/git/git-workspace.ts create mode 100644 src/git/repository-tar.test.ts create mode 100644 src/git/repository-tar.ts create mode 100644 src/git/storage-reset-diagnostic.tsx create mode 100644 src/hooks/use-native-setup.test.tsx create mode 100644 src/hooks/use-native-setup.ts create mode 100644 src/hooks/use-native-update.test.tsx create mode 100644 src/hooks/use-native-update.ts create mode 100644 src/hooks/use-sticky-follow.test.tsx create mode 100644 src/hooks/use-sticky-follow.ts create mode 100644 src/hooks/use-workspace.test.tsx create mode 100644 src/hooks/use-workspace.ts create mode 100644 src/lib/agent-integration.test.ts create mode 100644 src/lib/agent-integration.ts create mode 100644 src/lib/agent-workspace.test.ts create mode 100644 src/lib/agent-workspace.ts create mode 100644 src/lib/browser-capsule-loader.test.ts create mode 100644 src/lib/browser-capsule-loader.ts create mode 100644 src/lib/clipboard.test.ts create mode 100644 src/lib/clipboard.ts create mode 100644 src/lib/git-interface-repository.test.ts create mode 100644 src/lib/git-interface-repository.ts create mode 100644 src/lib/native-update.test.ts create mode 100644 src/lib/native-update.ts create mode 100644 src/lib/operation-journal.test.ts create mode 100644 src/lib/operation-journal.ts create mode 100644 src/lib/role-continuity-repository.test.ts create mode 100644 src/lib/role-continuity-repository.ts create mode 100644 src/lib/role-continuity.test.ts create mode 100644 src/lib/role-continuity.ts create mode 100644 src/lib/shell-link.test.ts create mode 100644 src/lib/shell-link.ts create mode 100644 src/lib/uninstall.test.ts create mode 100644 src/lib/uninstall.ts create mode 100644 src/lib/web-project-import.test.ts create mode 100644 src/lib/web-project-import.ts create mode 100644 src/lib/workbench-state.test.ts create mode 100644 src/lib/workbench-state.ts create mode 100644 src/lib/workspace-control-bridge.test.ts create mode 100644 src/lib/workspace-control-bridge.ts create mode 100644 src/lib/workspace-control-transport.ts create mode 100644 src/lib/workspace-controller.test.ts create mode 100644 src/lib/workspace-controller.ts create mode 100644 src/sharing/private-share-source-registry.test.ts create mode 100644 src/sharing/private-share-source-registry.ts create mode 100644 src/sharing/share-archive.test.ts create mode 100644 src/sharing/share-archive.ts create mode 100644 src/sharing/share-candidate-store.test.ts create mode 100644 src/sharing/share-candidate-store.ts create mode 100644 src/sharing/share-installation-store.test.ts create mode 100644 src/sharing/share-installation-store.ts create mode 100644 src/sharing/share-quarantine.test.ts create mode 100644 src/sharing/share-quarantine.ts create mode 100644 src/sharing/share-repository.test.ts create mode 100644 src/sharing/share-repository.ts create mode 100644 src/sharing/share-review.test.ts create mode 100644 src/sharing/share-review.ts create mode 100644 src/sharing/share-signature-verifier.test.ts create mode 100644 src/sharing/share-signature-verifier.ts create mode 100644 src/sharing/share-source-resolver.test.ts create mode 100644 src/sharing/share-source-resolver.ts create mode 100644 src/shell/flect-command.test.ts create mode 100644 src/shell/flect-command.ts create mode 100644 src/shell/git-command.ts create mode 100644 src/shell/persistent-workspace-fs.test.ts create mode 100644 src/shell/persistent-workspace-fs.ts create mode 100644 src/types/wasm-git.d.ts create mode 100644 tests/e2e/accessibility.spec.ts create mode 100644 tests/e2e/browser-build.spec.ts create mode 100644 tests/e2e/browser-package.spec.ts create mode 100644 tests/e2e/capsule-frame.spec.ts create mode 100644 tests/e2e/embedded-axi.spec.ts create mode 100644 tests/e2e/native-update.spec.ts create mode 100644 tests/e2e/opfs-git.spec.ts create mode 100644 tests/e2e/performance.spec.ts create mode 100644 tests/e2e/portable-extensions.spec.ts create mode 100644 tests/e2e/product-adapter.spec.ts create mode 100644 tests/e2e/product-adoption.spec.ts create mode 100644 tests/e2e/product-capability.spec.ts create mode 100644 tests/e2e/reset-browser-workspace.ts create mode 100644 tests/e2e/sharing.spec.ts create mode 100644 tests/fixtures/pi-extensions/fail-on-agent-start.ts create mode 100644 tests/fixtures/portable-extensions/capsules.ts create mode 100644 tests/fixtures/reference-product-capsule.test.ts create mode 100644 tests/fixtures/reference-product-capsule.ts create mode 100644 tests/fixtures/sharing/generate.test.ts create mode 100644 tests/fixtures/sharing/generate.ts create mode 100644 tests/fixtures/static-site/app.js create mode 100644 tests/fixtures/static-site/index.html create mode 100644 tests/fixtures/static-site/mark.svg create mode 100644 tests/fixtures/static-site/styles.css create mode 100644 tests/fixtures/vite-react/index.html create mode 100644 tests/fixtures/vite-react/package.json create mode 100644 tests/fixtures/vite-react/src/main.jsx create mode 100644 tests/fixtures/vite-react/src/styles.css create mode 100644 tests/fixtures/vite-typescript/index.html create mode 100644 tests/fixtures/vite-typescript/src/main.ts create mode 100644 tests/fixtures/vite-typescript/src/styles.css diff --git a/.agents/skills/flect-quality/SKILL.md b/.agents/skills/flect-quality/SKILL.md new file mode 100644 index 0000000..a635973 --- /dev/null +++ b/.agents/skills/flect-quality/SKILL.md @@ -0,0 +1,158 @@ +--- +name: flect-quality +description: Evaluate, improve, review, or release Flect against its canonical user-quality contract. Use for product-quality baselines, release readiness, user-outcome audits, evidence verification, gap prioritization, claim validation, dogfooding, or implementation work intended to move an FQ criterion to proven. +--- + +# Flect quality + +Use Flect's public behavior to prove user outcomes. Do not reconstruct the +quality contract inside this skill. + +## Load the contract + +1. Read the repository `AGENTS.md`, `VISION.md`, `PRODUCT.md`, + `ARCHITECTURE.md`, `CONTRIBUTING.md`, and every nearer instruction file. +2. Read `docs/product-quality.md` completely. It owns criteria, constituencies, + release gates, and proof classes. +3. Read the current release claims in `README.md` and the relevant owning + documentation. +4. Read existing reports under `docs/verification/` that claim evidence for + the criteria in scope. +5. Inspect the worktree and record uncommitted-state caveats. Never discard or + overwrite unrelated work. + +## Select the operation + +- **Evaluate:** classify every criterion required by the requested release or + scope and write a dated baseline. +- **Improve:** start from a frozen baseline, select the smallest independently + reviewable gap, and follow the repository's design, plan, TDD, and + verification workflow. +- **Review:** compare a proposed change and its evidence with the affected + criteria; report missing behavior, weakened boundaries, and unsupported + claims before style concerns. +- **Release:** prove every current release claim and every criterion required + by that release class. Any regression, unsupported claim, missing mandatory + evidence, or non-waivable gate failure blocks the release. + +Evaluation and improvement are separate phases. Freeze the observations before +changing the behavior they describe. + +## Build an evidence map + +For every criterion in scope, record: + +- criterion identifier; +- `unimplemented`, `partial`, `implemented`, `proven`, or `regressed`; +- exact evidence path, command, workflow, host, date, and revision; +- missing proof or observed failure; +- release impact; and +- existing GitHub issue, if any. + +Use only the proof classes required by the contract. Prefer the smallest public +surface that establishes the whole outcome: + +- exported Effect contracts and Layers for deterministic behavior; +- production Chromium for visible browser behavior; +- the packaged application and public `flect` executable for native behavior; +- adversarial tests for capabilities, credentials, sandboxes, extensions, and + recovery; +- keyboard, assistive-technology, visual, and dogfooding evidence for outcomes + automation cannot judge alone; +- clean artifacts and clean-machine workflows for release claims; and +- public integration contracts for adopter outcomes. + +Source inspection may identify where to test. Prose, source strings, successful +compilation, test names, screenshots without behavior, generated plans, and an +agent's assertion cannot establish `proven`. + +## Exercise Flect publicly + +Use visible UI, public HTTP/SSE or MCP contracts, and the public `flect` AXI. +Do not drive React internals, mutate browser storage, call private sidecar +modes, or treat direct database or journal inspection as user evidence. + +Start agent-first discovery with `flect`, then request only the bounded state +needed. Keep outside control disabled unless the user explicitly enables it in +the protected shell. Revoke it when live inspection is finished. + +For a complete supported-slice baseline, normally run: + +```bash +bun install --frozen-lockfile +bun run check:quality +bun run check:all +``` + +Run `bun run test:pi-smoke` only when approved provider authentication is +available and a live turn is required. Never capture credentials, private +control capabilities, unbounded logs, or sensitive product output. + +## Record the baseline + +Write `docs/verification/YYYY-MM-DD--verification.md`. Include: + +1. scope, release class, date, revision, branch, dirty-state caveat, and hosts; +2. commands and public workflows actually run; +3. one classification for every criterion in scope; +4. exact evidence and limitations; +5. current claims contradicted by behavior; and +6. unproven release gates and their issue links. + +Keep a baseline immutable after it is frozen except to correct factual or +redaction errors. A later run creates a later report and may supersede it. + +## Reconcile delivery work + +Use `gh-axi` for GitHub inspection and mutation. Search open and closed issues +before creating anything. Do not create one issue per criterion mechanically. + +Create the smallest independently reviewable issue that closes a real behavior +gap. Each issue names: + +- affected `FQ-*` identifiers; +- observable acceptance criteria; +- exact proof classes and public workflows; +- dependencies and explicit non-goals; +- owning documentation; and +- security, recovery, portability, or accessibility constraints that must + remain true. + +Place delivery issues in the dedicated Flect organization project. The project +owns live priority, dependency order, release slice, assignee, and status. Do +not copy that status into `docs/product-quality.md`. + +## Improve one vertical slice + +Before behavior changes, use the repository's required brainstorming and plan +workflow. Split independent subsystems into separate reviewed designs and +plans. Start observable behavior with a failing test and use Effect throughout +the application architecture as required by `AGENTS.md`. + +Require: + +- real Chromium for UI, shaping, import, accessibility, or browser sandbox + behavior; +- packaged-host and public AXI dogfooding for native or external-control + behavior; +- adversarial negative tests for new authority or extension boundaries; +- deterministic recovery tests for changes affecting candidates, persistence, + extensions, or accepted state; and +- updates to the canonical documentation owner without parallel capability + lists or implementation claims. + +Implementation does not make its own criterion `proven`. Re-run the required +public evidence after the change and record a new verification report. + +## Fail closed + +Immediately report and treat as release-blocking when applicable: + +- shipped behavior contradicts a current claim; +- credentials or private capabilities appear in output or artifacts; +- shaped or extension code can bypass typed capabilities or recovery; +- acceptance, rollback, safe mode, or outside-control revocation is not + deterministic; +- evidence is missing, stale, scoped to the wrong host, or derived only from + implementation details; or +- a regression is obscured by changing prose or lowering the criterion. diff --git a/.agents/skills/flect-quality/agents/openai.yaml b/.agents/skills/flect-quality/agents/openai.yaml new file mode 100644 index 0000000..b10f832 --- /dev/null +++ b/.agents/skills/flect-quality/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Flect Quality" + short_description: "Prove and improve Flect user outcomes" + default_prompt: "Use $flect-quality to evaluate Flect against its canonical user-quality contract and produce evidence-backed gaps." diff --git a/.agents/skills/flect/SKILL.md b/.agents/skills/flect/SKILL.md new file mode 100644 index 0000000..4e3d282 --- /dev/null +++ b/.agents/skills/flect/SKILL.md @@ -0,0 +1,66 @@ +--- +name: flect +description: Operate and inspect a running Flect interface through its bounded agent-first command surface. Use when an agent needs to inspect Flect state, read operation evidence, invoke visible actions, shape an interface, manage revisions or models, debug a Flect workspace, or configure explicit native Flect integrations. +--- + +# Flect + +Use the public `flect` command. Treat it as the authoritative command surface for the running interface; do not drive React, storage, broker internals, or private runtime binaries directly. + +## Start with discovery + +1. Run `flect` with no arguments for content-first discovery and relevant next actions. +2. Run `flect inspect` only when more workspace detail is needed. +3. Run `flect action list` before invoking a product action. +4. Prefer default bounded TOON output. Add `--json` only for a consumer that requires JSON and `--full` only when complete authorized text is necessary. + +## Respect authority + +- App Agent uses the accepted product and its projected actions. It does not shape or accept revisions. +- Shaper changes interface candidates inside its disposable sandbox. Run `flect interface validate ` before `flect interface propose `; proposal acceptance remains a user decision. +- Outside agents require the user's explicit local-control grant before live workspace operations become available. +- Help visibility is not authorization. Treat structured `unauthorized`, `conflict`, `rejected`, and `unavailable` results as definitive. + +## Command map + +| Command | Purpose | Intended caller | +| --- | --- | --- | +| `flect` | Show content-first live discovery and relevant next commands. | outside agent, App Agent, Shaper | +| `flect inspect [--fields ]` | Inspect validated live workspace state. | outside agent, App Agent, Shaper | +| `flect logs [--limit ] [--role ]` | Read bounded, correlated operation evidence. | outside agent, App Agent, Shaper | +| `flect watch [--after ]` | Wait for the next reactive workspace event. | outside agent, App Agent, Shaper | +| `flect target ` | Select the visible Use or Shape conversation explicitly. | outside agent, App Agent | +| `flect mode set ` | Compatibility alias for selecting Shape or Use. | outside agent, App Agent | +| `flect prompt \|--stdin` | Ask App Agent to use the accepted product. | outside agent, App Agent | +| `flect shape \|--stdin` | Ask Shaper to prepare a validated interface proposal. | outside agent, Shaper | +| `flect cancel ` | Stop the selected running agent turn. | outside agent | +| `flect action list\|inspect\|invoke` | Discover and invoke actions projected by the visible interface. | outside agent, App Agent | +| `flect product invoke [--input ]` | Invoke a registered product operation without raw HTTP access. | outside agent, App Agent | +| `flect permissions list\|revoke ` | Inspect product permission lifecycle or revoke a visible decision; grants remain protected UI decisions. | outside agent, App Agent, Shaper | +| `flect interface inspect\|schema\|validate\|propose` | Inspect or propose interface documents inside Shaper's sandbox. | Shaper | +| `flect proposal accept\|reject` | Resolve the current validated preview as a protected user decision. | outside agent, App Agent | +| `flect revision list\|rollback` | Inspect revision state or request deterministic rollback. | outside agent, App Agent | +| `flect repository status` | Inspect canonical Git refs, isolation, and conflict state. | outside agent, App Agent, Shaper | +| `flect share list\|inspect` | Inspect bounded inactive shared candidates and retained installations. | outside agent, App Agent, Shaper | +| `flect share open-url\|open-git\|reject\|export` | Route bounded shared-source actions through protected user review. | outside agent | +| `flect share checkpoint --at --write --message ` | Checkpoint bounded Shaper sandbox files onto an exact retained share fork. | Shaper | +| `flect share resolve --base --upstream --fork --write --message ` | Submit an exact bounded Shaper resolution for every reviewed shared conflict path. | Shaper | +| `flect model list\|select\|favorite` | Inspect and select Pi-backed models without exposing credentials. | outside agent, App Agent, Shaper | +| `flect extensions list\|describe\|call` | Discover and call enabled portable extensions for the current role and binding. | outside agent, App Agent, Shaper | +| `flect trusted-extensions enable\|disable ` | Set opt-in loading for the selected Pi role's outside extensions. | outside agent, App Agent | +| `flect safe enter\|restore` | Enter compiled recovery or restore the last-known-good interface. | outside agent, App Agent | +| `flect rail collapse\|expand\|width ` | Set the protected agent rail presentation. | outside agent, App Agent | +| `flect control status\|disable` | Inspect or revoke explicitly granted outside control. | outside agent | +| `flect context --host ` | Emit bounded static guidance plus available live Flect context. | outside agent | +| `flect setup status` | Inspect the fixed shell link and opt-in agent integrations. | outside agent | +| `flect setup shell install\|remove` | Manage only ~/.local/bin/flect for the installed desktop app. | outside agent | +| `flect setup agent install\|remove ` | Manage one ownership-marked ambient context integration. | outside agent | +| `flect setup uninstall inspect\|prepare` | Inspect or remove only Flect-owned integrations before moving the app to Trash. | outside agent | +| `flect mcp` | Serve the compact MCP adapter over stdio. | outside agent | + +## Keep the boundary safe + +- Never request or print model credentials, the local-control capability, private runtime flags, or unbounded logs. +- Use explicit set-shaped commands instead of inventing toggles or raw payload escape hatches. +- Read the returned receipt and resulting workspace state before deciding whether another command is needed. +- Use `flect logs --limit 20` for bounded failure evidence and safe mode for deterministic recovery. diff --git a/.gitignore b/.gitignore index bad842d..766b552 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ .DS_Store .worktrees/ -.repos/effect +.repos/ node_modules/ dist/ +dist-product-sdk/ dist-release/ coverage/ playwright-report/ @@ -13,3 +14,4 @@ src-tauri/binaries/ .env .env.* !.env.example +.tauri-signing-key* diff --git a/AGENTS.md b/AGENTS.md index 77d5c20..e160e52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,10 @@ Before changing Flect: - Keep changes inside the smallest component that owns the behavior. - Use the approved design and implementation plan under `docs/superpowers/` for the initial MVP. +- Read `docs/product-quality.md` before designing, implementing, reviewing, or + releasing user-visible behavior. It owns Flect's stable user outcomes and + proof requirements. Do not claim an outcome is supported or proven without + current linked evidence through the required public interfaces. Repository-wide constraints: @@ -30,6 +34,9 @@ Repository-wide constraints: - Name non-trivial workflows with `Effect.fn`, keep defects distinct from expected typed failures, and provide dependencies through Layers in tests. Use Effect test services such as `TestClock` when behavior depends on time. +- Match expected Effect failures structurally by their stable `_tag`, using + `Effect.catchTag` or `Effect.catchTags` when recovering. Do not use + `instanceof` to route errors in an Effect error channel. - Compose Layers once at the runtime edge. React components may own rendering state, but data access and business workflows must enter through an Effect runtime rather than Promise-shaped application services. @@ -73,6 +80,14 @@ Repository-wide constraints: prompts. App Agent cannot shape revisions or modify recovery. The Shaper may receive explicitly approved UI-shaping capabilities, but it cannot modify the Guardian, safe mode, recovery code, or revision journal. +- Keep accepted App Agent, candidate Preview App Agent, and Shaper conversation, + Pi-session, cancellation, and browser-shell workspace state separate. Preview + App may inherit App authority policy, but it receives only bounded candidate + context and must not read accepted history or accepted workspace files. +- Use explicit typed **Use** and **Shape** workbench transitions. Do not infer a + semantic target from arbitrary prompt text. App Agent may request shaping only + through the bounded typed edit-request tool; revision acceptance remains a + protected user/controller decision. - A shared Pi `ModelRuntime` may resolve models and provider authentication, but Guardian, App Agent, and Shaper sessions use separate `SessionManager`, `SettingsManager`, and `ResourceLoader` instances. An @@ -105,6 +120,25 @@ Repository-wide constraints: Tauri, DOM, storage, module, or arbitrary host-function authority. - Keep runtime automation in TypeScript. Prefer native browser, Bun, Pi, and provider interfaces over repository-owned wrappers or shadow state. +- Route every semantic user, CLI, HTTP, MCP, and platform action through the + same schema-defined `FlectWorkspaceController`. Outside adapters may not + drive DOM selectors, edit client storage, or create parallel revision, + conversation, preference, control, or operation state. +- Keep outside control disabled by default, user-enabled only from the + protected shell, bound to an ephemeral loopback address, authenticated by a + rotating private capability, attributable, and immediately revocable. Never + expose its bearer through output, logs, prompts, argv, tests, screenshots, or + public workspace state. +- Keep operation evidence bounded, in-memory, correlated, and redacted. It may + explain controller, transport, turn, tool, validation, and revision behavior; + it must not become a credential-bearing transcript, durable audit database, + or second source of application truth. +- Keep every agent-facing command surface AXI-compliant: route commands through + the shared Effect parser and `FlectWorkspaceController`, default to bounded + TOON, and preserve stable exit codes and output channels. When command + metadata changes, regenerate `.agents/skills/flect/SKILL.md`. The canonical + reference is `docs/local-control.md`; design rationale lives in + `docs/superpowers/specs/2026-07-31-flect-embedded-axi-design.md`. - Keep platform behavior behind Effect services and Layers. The browser, Tauri host, and macOS Swift code are adapters to shared application capabilities, not alternate homes for product workflows or interface state. @@ -126,12 +160,19 @@ Keep each kind of information in one canonical place and link to it elsewhere: and intentional non-capabilities. - `PRODUCT.md` owns users, positioning, personality, and product principles. - `DESIGN.md` owns the visible design system and interface tokens. +- `docs/product-quality.md` owns stable user outcomes, release gates, and proof + requirements. Dated maturity and evidence belong under `docs/verification/`; + GitHub issues own executable gaps, and the dedicated organization project + owns live priority and status. - `ARCHITECTURE.md` describes only verified behavior and boundaries that exist in the current implementation. Planned behavior must not be written there as though it were shipped. - `docs/trust-model.md` explains the public capability, isolation, permission, and recovery model. Permanent contributor safety constraints remain in the closest `AGENTS.md`. +- `docs/local-control.md` owns the user-facing CLI, MCP, JSON/SSE, descriptor, + lifecycle, and invocation reference for the implemented local control + plane. - reviewed future designs belong in `docs/superpowers/specs/`; durable technical decisions and their tradeoffs belong in `docs/decisions/`. - GitHub issues own executable work and acceptance criteria. The dedicated diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 536fa3b..301e574 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -15,11 +15,16 @@ remote-runtime work belongs in the design documents under `docs/superpowers/`. v Browser or Tauri WebView -> Effect application kernel -> React renderer | | + | +-> FlectWorkspaceController + | -> typed commands and events + | -> bounded OperationJournal + | | +-> disposable QuickJS-NG/WASM Worker | -> typed inert intents only | | +-> role-owned just-bash workspace | -> reserved Bun-compatible command + | -> reserved role-bound `flect` | -> Rifty Workers and preview broker | +-- browser development @@ -33,10 +38,16 @@ Browser or Tauri WebView -> Effect application kernel -> React renderer -> private NDJSON stdio -> compiled Bun sidecar | + optional authenticated + loopback control broker + / | \ + public `flect` JSON/SSE `flect mcp` + | v shared Pi ModelRuntime - / | \ - Guardian session App session Shaper session + / \ + primary session set candidate session set + Guardian/App/Shaper Preview App (+ isolated managers) ``` The browser never imports Pi. React never writes interface storage or revision @@ -60,6 +71,38 @@ Effect is the application architecture, not a utility wrapper: - React owns rendering and ephemeral form state only. Event handlers are thin adapters into the managed Effect runtimes. +`FlectWorkspaceController` is the single semantic command and observable state +authority. Visible controls and authorized outside clients submit the same +strict `FlectCommandEnvelope` union. Its `SubscriptionRef` snapshot contains +validated interface, revision, workbench target and binding, model, +shell-preference, activity, control-client, and bounded diagnostic state. Its +event stream attributes +command, turn, tool, revision, safe-mode, and control transitions. Command IDs +are retained in a bounded idempotency window, and optional expected sequences +fail with a typed conflict instead of applying to newer state. + +`FlectAxiProgram` is the single command-language boundary. One strict parser, +typed gateway, bounded projection, TOON/JSON formatter, and stable `0`/`1`/`2` +exit contract serve native terminal calls and browser-agent calls. The +`AgentCommandBus` is a bounded, scoped queue whose deferred response carries +the captured agent source into `FlectWorkspaceController`; its bridge owns +inspection, log reads, interface proposals, and authorized commands. Queue +capacity, cancellation, shutdown, and timeout fail closed without introducing +another state owner. + +An interactive agent turn may call an authorized controller command from its +reserved Bash while the parent prompt command is waiting. The controller marks +only that exact parent operation as re-entrant, keeps unrelated user/control +commands behind the global permit, and removes the marker with an Effect +finalizer. This lets Shaper checkpoint a retained share fork or submit an exact +reviewed conflict resolution without deadlock or granting App Agent, Capsule, +or unrelated agent turns concurrent mutation. + +`OperationJournal` retains at most 500 records and two MiB in memory. Records +correlate workspace, command, operation, role, session, tool call, revision, +and outside client identifiers. Secret-shaped text is redacted before storage. +It is diagnostic evidence, not revision authority or a durable audit store. + All Effect packages are pinned to one exact version. `.repos/effect` is an ignored checkout of that matching upstream release and is the primary local API reference. @@ -87,7 +130,19 @@ runtime. Trusted browser and native clients schema-encode validated documents before JSON transport. An `InterfaceDocument` contains no generated HTML, CSS, JSX, or executable code path. -The `ShapingKernel` owns the active, proposed, previewed, accepted, +Shaper must terminate a proposal turn through its role-bound browser shell: +it writes `/workspace/interface.json`, runs `flect interface validate`, then +runs `flect interface propose` as its final action. The reserved command reads +only that disposable workspace, decodes unknown JSON through the closed +`InterfaceDocument` Effect Schema, and sends the validated value over the +bounded `AgentCommandBus`; the controller never receives a sandbox path. +Missing fields, invented node types or actions, excess properties, duplicate +identifiers, and tree bounds become safe path-specific output. Shaper receives +one bounded corrective retry in the same Pi session. A second missing or +invalid proposal ends without creating a revision and leaves field-level tool +evidence in the activity UI and journal. + +The `ShapingKernel` owns the active, proposed, previewed, superseded, accepted, last-known-good, rejected, and recovered revision transitions. A Shaper result is decoded as an unknown value and fully validated before it becomes a preview. Ordinary transitions write one versioned journal snapshot before making the @@ -105,39 +160,51 @@ The built-in recovery shell is compiled with the app. `?safe=1` bypasses customized storage without reading or writing it. Invalid persisted state fails closed to that shell in a protected recovery state. The shell renders one compiled composer outside the customizable document, so shaping cannot remove -the user's route back to an agent. A blank workspace routes it to Edit/Shaper; -an accepted product routes it to Run/App Agent. The same mounted composer moves +the user's route back to an agent. A blank workspace selects **Shape**; a +candidate or accepted product selects **Use**. The same mounted composer moves from the centered blank state into the protected right rail when a document or conversation appears. ## Pi trust domains -Flect creates one Pi `ModelRuntime` for provider discovery and authentication, -then creates three isolated agent sessions: +Flect creates one Pi `ModelRuntime` for provider discovery and authentication. +Its primary session set contains three isolated agent sessions: - **Guardian** has immutable recovery instructions. It has no user extensions, skills, templates, themes, context files, or tools. It accepts only a closed set of typed recovery reasons and returns a bounded plain-text diagnostic; it cannot write revisions or perform recovery. -- **App Agent** is the Run-mode agent for using an accepted product experience. +- **App Agent** is the Use agent for an accepted product experience. It has its own prompt, history, operation controller, and role-owned browser-shell workspace. It does not receive shaping context or revision authority. Configured external Pi extensions remain disabled unless the user explicitly enables them for App Agent. - **Shaper** receives the current validated document and a shaping instruction. - It has no ambient host resources. Its only Pi tool is Flect's custom `bash`, - which runs in a disposable browser workspace and returns through a typed - request/result bridge. Configured external Pi extensions remain disabled - unless the user explicitly enables them for Shaper. A shaped document still - returns as an untrusted candidate for Flect to validate. + It has no ambient host resources. Its only Pi tool is custom `bash`, which + runs in a disposable browser workspace and returns through a typed + request/result bridge. The reserved `flect interface validate/propose` + commands are the terminating proposal path. + Configured external Pi extensions remain disabled unless the user explicitly + enables them for Shaper. A shaped document still returns as an untrusted + candidate for Flect to validate. + +While a proposal exists, the client acquires another Pi session set and uses +its App session as **Preview App Agent**. It receives only a bounded public +projection of the candidate document, the candidate revision identifier, and +the user's request. It has its own history, lifecycle, cancellation, Pi session, +and disposable browser-shell workspace; it cannot see accepted-product history +or Keep, Reject, shaping, recovery, credential, or host authority. Repeated +corrections keep this candidate conversation warm. Keep or Reject disposes its +session authority. Each session has its own in-memory `SessionManager`, `SettingsManager`, and `DefaultResourceLoader`. Their only shared object is the provider/model runtime. Prompts and responses are not persisted by Flect. Disposing the Effect runtime -unsubscribes and disposes all three sessions. The client closes its current -agent set when the model changes, the runtime is refreshed, an operation fails, -or the UI unmounts. Session handles are keyed by model selection, and the -runtime evicts and disposes the oldest set before exceeding 32 active sets. +unsubscribes and disposes every acquired session set. The client closes its +primary and candidate handles when the model changes, the runtime is refreshed, +an operation fails, or the UI unmounts. Session handles are keyed by model +selection, and the runtime evicts and disposes the oldest set before exceeding +32 active sets. Each protected session admits one active operation at a time: overlapping shape requests fail with a typed busy conflict, while prompt-stream conflicts become typed non-destructive busy events. App and Shaper cancellation and @@ -145,17 +212,33 @@ browser-shell completion carry an explicit role and cannot affect the other role. The client preserves a busy set, and the shell disables its composer while a shaping proposal is running. Closing or evicting a set interrupts its active operations, waits for completion for up to two seconds, and then -disposes all three sessions. +disposes every session in that set. Raw Shaper output is capped at 256 KiB and raw Guardian output at 16 KiB before either can cross the runtime boundary. ## Role-aware protected shell React derives four workspace phases from the validated revision snapshot: -blank, preview, accepted, and safe. Blank and preview use Edit/Shaper; accepted -opens in Run/App Agent; safe mode replaces both with protected recovery. -Switching roles changes the visible role-owned timeline without relabeling -messages or submitting the other role's draft. +blank, preview, accepted, and safe. A typed `WorkbenchSnapshot` independently +records the visible **Use** or **Shape** target and whether Use is bound to the +accepted or candidate revision. Blank disables Use. A valid proposal +automatically selects candidate Use; **Shape** returns to the warm Shaper; a +valid correction atomically supersedes the candidate and selects Use again. +Safe mode replaces both with protected recovery. Switching targets changes the +visible target-owned timeline without relabeling messages, submitting another +draft, or mounting a second composer. + +App Agent does not rely on a semantic text router. Questions and product work +stay in Use. Its Pi session has a bounded `request_interface_edit` tool; only a +typed tool event can request the controller to hand an explicit edit to Shaper. +The controller correlates revision and operation identifiers and still routes +the result through validation and protected Keep/Reject. + +The current warm-target browser budget is **less than 350 ms** from selecting +the other target until its composer is visible, including the shell's 220 ms +layout motion. The production Chromium workflow repeats both directions and +asserts that the transition makes no Pi prompt or shape request. Native and +long-session performance budgets remain tracked separately. At wide sizes the agent rail is inline, 400 px by default, and keyboard- or pointer-resizable from 340–520 px. At 761–980 px it becomes a right sheet; at @@ -164,8 +247,32 @@ restoration, and reduced-motion behavior remain owned by the compiled shell. The revision decision, model picker, safe mode, rollback, send, and stop controls are protected rather than part of a shaped document. -Pi remains the sole owner of provider login state. Flect neither creates a -credential format nor exposes provider tokens to the WebView, browser APIs, +Turn and tool lifecycle are visible as separate instruments rather than +generic chat sentences. Tool cards show queued/running/succeeded/failed state, +duration, bounded command and result detail, exit status, preview links, and +proposal validation issues. Diagnostics exposes the latest safe correlated +operation evidence and the protected local-control toggle. + +Each role owns a sticky-follow viewport. New content follows only while the +reader remains within 48 px of the bottom. Scrolling away preserves position, +tracks unread updates, and presents a keyboard-operable **Jump to latest** +control; streaming never steals focus. + +Pi remains the sole owner of provider login state. One scoped Effect +authentication coordinator adapts Pi's discovered auth methods into bounded +public status and login events. It permits at most four concurrent provider +logins, keeps at most 32 public events per login, expires unfinished logins +after ten minutes, and normalizes every expected failure into Flect-authored +copy. Active login events live in a private auth `SubscriptionRef`; they never +enter `FlectWorkspaceSnapshot`, operation records, control SSE, AXI, MCP, +agent tools, revision storage, or shaped interfaces. + +Safe selection replies carry only correlated login, prompt, and option +identifiers. Every free-text Pi prompt is sensitive and opens through an +unguessable, one-use `127.0.0.1` form owned by the private runtime. The form is +no-store, script-free, origin-checked, size-bounded, abortable, and passes an +Effect `Redacted` value directly to Pi before wiping it. Flect neither creates +a credential format nor exposes provider tokens to the WebView, browser APIs, logs, fixtures, or interface documents. ## Browser host @@ -181,11 +288,21 @@ sanitized public errors. This loopback API exists for browser development; it is not used by the packaged desktop app and is not approved for network exposure. +The connected browser workspace also registers its controller with the local +control broker through origin-protected internal routes. Those routes are not +the public control API and reject missing or foreign origins. + ## Desktop host -Tauri packages the same Vite build. The Rust host starts one external sidecar, -limits frontend authority to `core:default`, and exposes one private -`rpc_send` command. That command: +Tauri packages the same Vite build with two executables: public `flect` and +private `flect-runtime`. Finder or explicit `flect app` launches the GUI. A +terminal invocation dispatches AXI arguments or `mcp` to the sibling runtime, +inherits stdio, and propagates its exit status. Rust selects only the host mode; +it does not parse domain commands, duplicate Effect policy, or render output. + +In GUI mode the Rust host starts `flect-runtime`, limits frontend authority to +`core:default`, and exposes the private `rpc_send` command plus narrow, +main-window-only native setup and updater commands. `rpc_send`: - accepts only Effect RPC request, acknowledgement, interrupt, EOF, and ping message tags; @@ -194,25 +311,324 @@ limits frontend authority to `core:default`, and exposes one private - emits parsed sidecar responses back to the WebView. The sidecar is a compiled Bun executable running the same `FlectRuntime` -handlers through Effect RPC NDJSON. It does not bind a TCP port. - -This RPC boundary owns Pi runtime operations: health, model discovery and -selection, session creation and close, turns, interruption, shaping proposals, -the narrow Guardian diagnostic, browser-shell results, and their streams. +handlers through Effect RPC NDJSON. Pi traffic never binds a TCP port. The +control broker owns a separate random loopback listener; without an enabled +grant it publishes no descriptor and rejects every request. + +This RPC boundary owns Pi runtime operations: health, provider status and +authentication, model discovery and selection, session creation and close, +turns, interruption, shaping proposals, the narrow Guardian diagnostic, +browser-shell results, and their streams. The client Effect kernel deliberately owns the revision journal so browser and desktop hosts share one client-side state machine. Revision operations are not duplicated in the sidecar. +Diagnostics exposes fixed native setup capabilities. Shell-link commands may +inspect, install, repair, or remove only `~/.local/bin/flect` targeting the +current installed app. Codex and Claude hook mutation and the OpenCode local +plugin are implemented by one Effect `AgentIntegration` service with injected +filesystem and path Layers, strict unknown JSON decoding, atomic private +writes, stable ownership markers, conflict detection, and removal of only +Flect-owned entries. Browser mode reports these host mutations as unavailable. + +The `Uninstall` Effect service composes those ownership-aware operations into a +closed inspect/prepare plan. It derives the application path from the packaged +executable, disables Local control in the protected UI, preserves conflicts and +user data, and never receives a recursive-delete or app-removal operation. AXI +can inspect or prepare the same plan but cannot remove the app. + +The official Tauri updater plugin is registered only when a non-empty public +key was compiled into a release. Rust fixes the HTTPS GitHub manifest endpoint, +checks the `main` window label, retains a plugin candidate behind a single-use +opaque token, and projects only bounded state through four commands. The Effect +adapter decodes every response and invalidates stale tokens; React owns only the +protected review/progress UI. Browser Flect receives an explicit unavailable +Layer. No capsule, agent, extension, product operation, AXI command, MCP method, +or embedded Bash route receives updater authority. + The Tauri window uses isolation mode and a restrictive content security policy. `freezePrototype` is disabled because the current Tauri/React bootstrap fails when it is enabled; it is not treated as a security boundary. The isolation iframe, CSP, capability manifest, strict schemas, narrow command validation, and private process transport remain active. -The current local app bundle is ad-hoc signed so macOS can validate its nested -sidecar and app resources during development. It is not claimed to be Developer -ID signed, notarized, hardened-runtime audited, or protected by the macOS App -Sandbox entitlement. +The current local app bundle is ad-hoc signed so macOS can validate its public +`flect` executable, private `flect-runtime` helper, and app resources during +development. It contains no separately shipped CLI or MCP companion. It is not +claimed to be Developer ID signed, notarized, hardened-runtime audited, or +protected by the macOS App Sandbox entitlement. + +## Local control plane + +`FlectControlBroker` is lifecycle-scoped inside the Bun browser runtime or +desktop sidecar. It binds only to `127.0.0.1` on a random port and stores no +parallel workspace state machine. When the protected UI enables control, the +connected workspace publishes its current snapshot, the broker creates a fresh +256-bit bearer, and `ControlDescriptor` writes the discovery record atomically +with private directory and file permissions. + +The public loopback surface provides authenticated status, instance, +workspace, log, SSE event, and command endpoints. Bodies are bounded to one +MiB. Commands are strict schema values, cannot enable their own grant, and are +queued through a bounded Effect `Queue`; exact results use `Deferred`. +Multiple delivered commands run in scoped fibers so a cancel command can reach +an active App or Shaper turn. Disable is acknowledged through the shared +controller before the broker removes the descriptor, shuts down the queue, +fails pending requests, and invalidates the bearer. + +The browser bridge uses origin-restricted HTTP. The desktop bridge uses the +existing private Effect RPC/Tauri channel. Both publish the same controller +snapshots, events, command outcomes, and named-client attribution. React +subscribes to the controller itself, so an outside action becomes visible +without reload, DOM automation, storage polling, or shadow state. + +The public `flect` AXI program defaults to bounded TOON, supports explicit JSON +and one-event SSE-backed `watch`, and is embedded under the reserved command in +each role sandbox. The native executable dispatches command or MCP mode to the +private runtime without parsing domain arguments. MCP v2 advertises four +compact tools for inspect, command, event waiting, and logs; its command input +comes from the same Effect Schema union. Neither adapter prints or returns the +bearer. Complete invocation and security details are in +[`docs/local-control.md`](docs/local-control.md). + +## Portable capsule boundary + +The host-neutral codec in `shared/capsule.ts` is the executable `.flect` +format contract. It produces deterministic bounded ustar archives, strictly +decodes the versioned manifest, verifies every SHA-256 payload, and rejects +unknown fields, unsafe paths, duplicate entries, unsupported versions, and +authority-bearing state. Browser and desktop loaders enter through this same +decoder. + +The end-user flow exports an accepted interface and imports a verified capsule +as an isolated proposal. Import never replaces accepted state directly; the +ordinary preview/keep/discard boundary remains authoritative. Declarative +entrypoints use the trusted closed renderer. Compiled HTML entrypoints run in +an opaque-origin `allow-scripts` iframe with a network-denying CSP and one +rate/size-bounded Effect Schema `MessageChannel`. Capsule messages and host +replies are JSON-only, limited to 64 KiB, correlated by an intent identifier, +and closed over a versioned success/failure union. Capsules receive no direct +host authority. Safe mode bypasses the frame. Shaper may invoke import through its +reserved sandboxed `flect capsule import` command; App Agent may not. +Before Keep, the protected shell projects a bounded review from the decoded +manifest: publisher, version, source revision, signature presence, contents, +platforms, and all requested capabilities. Required capabilities are activation +preconditions. The shell evaluates the declared semver range against the actual +Flect package version and the declared platform set against the browser or +native host; incompatibility blocks activation at the UI and controller while +leaving the isolated preview inspectable. A required request without a +registered grant keeps the preview inspectable but blocks acceptance in both +the UI and controller; optional requests remain ungranted. Protected review +projects availability separately from requested, granted, denied, expired, and +revoked lifecycle state. + +Named product operations use an Effect decision store, capability broker, +registry, and adapter. A trusted host maps a stable operation ID to one strict +manifest capability, operation/resource/data scope, permitted confirmation +policies, optional limits, and bounded request/response projections. The +controller hashes the exact capsule archive and binds each decision to that +digest, provenance revision, capsule ID, workspace ID, and declared request. +Session and one-use decisions stay in memory; workspace and persistent +decisions use the strict version-2 store and matching legacy boolean state is +migrated without widening authority. No decision is copied into the capsule. +Capsule, App Agent, embedded `flect`, outside +CLI, JSON, and MCP calls enter the same closed `invoke-product-operation` +command and workspace controller. Capsule-origin commands additionally bind the +current accepted/candidate capsule ID and intent ID; undeclared, stale, +unregistered, or ungranted operations fail before the adapter. + +The broker atomically reserves one-use and rate allowance before independent +product authorization, validates the product's exact operation/resource/data +projection against that reservation, and then invokes the adapter. Approval +does not override product denial. Every attempted registry invocation adds +bounded structured capability metadata to the operation journal without raw +input, output, headers, or credentials. The public AXI snapshot includes the +same reactive projections; `flect permissions list` can inspect them and paired +outside control can revoke a visible decision, but no agent-facing command can +create one. + +`ProductHttp` owns HTTPS origin, path prefix, +method, header, request/response byte, and deadline policy. Callers supply only +the registered operation input. Absolute/cross-origin paths, undeclared methods +or headers, caller authorization/cookie headers, oversized traffic, redirects, +and deadlines fail through a typed sanitized error. Host credential headers are +injected privately, `fetch` omits browser credentials, only allowlisted response +headers and bounded bytes return, and neither capsule nor Pi receives raw +`fetch`. `ProductGraphql` builds on that service with an exact endpoint, +operation kind/name, SHA-256-pinned document, bounded variables/result, and +sanitized GraphQL failures. `ProductEvents` owns a scoped connector and bounded +backpressure queue, validates canonical decimal sequence order, resumes from +the last accepted cursor for a finite number of retries, and aborts on caller +cancellation. `ProductEventRegistry` holds the exact broker reservation open +and interrupts the connector when that decision expires, changes, or is +revoked. + +Public adopter contracts and transport-only services live in the independently +packable `packages/product` workspace as `@flect/product`. Its Effect Schema +metadata separates product identity/compatibility, recommended capsule digest, +capability and extension sets, declarative migrations, inference policy, prior +connection state, and user-owned fork/export references. A validated branded +`ProductIntegration` supplies only named bounded closures. Flect's private +`product-integration` Layer bridge composes those public operation/event types +with the protected broker, decision store, and registries; the package has no +route to grant, activate, write workspace state, or replace recovery. + +Adoption evaluation is deterministic and model-free. It compares a validated +integration with host facts and the prior connection record and returns ordered +public diagnostics for ready, offline, update, review, compatibility, +authentication, migration, and detach states. Product connection records are +independent from `ProductUserState`, so detaching a product retains personal +Git/export references for protected continuation or export. Model/inference +ownership is not an authorization input. + +The stock Flect distribution registers no product operations; adopters compose +their policies and operations at the trusted runtime root. Browser and the +current desktop WebView use these same provider-neutral service contracts. +Ordinary HTTP/GraphQL uses CORS-aware fetch. Protected review offers once, +session, workspace, +persistent, deny, and revoke actions only when permitted by the host manifest; +safe mode uses the same model-free protected projection. One-use consumption, +expiry, and rate enforcement are implemented in the broker. Custom duration or +rate editing in the protected UI and a privileged native credential/transport +adapter are not yet shipped. + +Three public-boundary reference products exercise offline state, +browser-direct fixed GraphQL/events, and a named authenticated broker. Each +ships a recommended capsule with public App Agent guidance and an optional +Shaper role, while the Flect-owned harness supplies grants and protected state. + +Signature verification, update/fork lineage, uninstall, +and framework source builds remain later layers over the format and frame +contracts. Verified archive-local stylesheets, classic scripts, images, +fonts/media, and CSS URLs are projected into the generated `srcdoc`: text is +strict UTF-8, binary assets become `data:` URLs, and unresolved or remote +references remain unavailable under the deny-by-default CSP. Module graphs, +import maps, and source dependency resolution are not claimed. + +URL installation is a user-initiated protected-shell operation over HTTPS, with +loopback HTTP reserved for local development. The browser request omits +credentials, bypasses cache, follows CORS, and is bounded by a 20-second timeout +and the format's 32 MiB ceiling while streaming. Only bytes that pass the same +strict capsule decoder become a candidate; a fetch, CORS, size, timeout, or +integrity failure leaves accepted state untouched. + +If an installed and candidate capsule share an ID, the protected shell labels +the transition with both versions. A different ID is labeled as a replacement. +In both cases the accepted archive remains bound until Keep, so Reject is a +lossless return to the installed version. Personal-fork lineage and compatible +merge policy remain future layers rather than implicit overwrite behavior. + +Plain static-site import is a protected pre-capsule adapter. It validates all +directory-relative paths, ignores VCS/dependency/cache and common +credential/private-key entries without reading their contents, requires one +root `index.html`, applies the capsule bounds, and +encodes the unchanged supported files into a local capsule. Source is not +executed during inspection. The ordinary manifest review and opaque compiled +frame remain the only path to preview. The current adapter does not yet commit +source to accepted state directly: it checkpoints recognizable files under +`project/` on isolated `flect/authoring`; the existing proposal-source delta +promotes them to `flect/accepted` only on Keep. It does not yet analyze +multi-page routes, archives, repositories, or content-dependent secrets. Its bounded text inspection does report forms, +module graphs, remote URLs, storage, and workers as visible manifest requests; +unsupported required assumptions block Keep. + +Compiled capsule bytes persist through `CapsuleStore`, independently from the +user-controlled interface Git repository. The production store uses the +pinned `@riftydev/vfs` OPFS adapter and falls back to an in-memory VFS only +when OPFS is unavailable. It writes immutable archive objects first at +`/flect-capsules/default/objects/.flect`, then atomically advances the +versioned accepted, candidate, and last-known-good bindings in +`/flect-capsules/default/bindings.json`. Startup re-decodes and hash-verifies +every bound archive before reconstructing presentation; a candidate is exposed +only when the shaping kernel also reports a proposal. Invalid or mismatched +bindings fail closed without replacing accepted state. Export reads the exact +accepted archive object, preserving the original bytes across reload. +The store exposes whether this binding is durable or session-only through the +public workspace snapshot. Diagnostics keeps a session-only warning visible +even while collapsed and states exactly which compiled state will be lost; +the fallback is never presented as durable persistence. + +This persistence contract currently supports a compiled HTML entrypoint plus +the bounded local asset classes above. Framework source builds and module +graphs, additional capability adapters, +signing, installation/update lineage, and uninstall remain open. Accepting an +already-restored candidate is also held open until the pinned Wasm-Git adapter +reliably advances the accepted snapshot commit after worker restart. + +## Restricted browser build boundary + +`BrowserBuild` is an Effect service over a schema-decoded, one-build-per-Worker +protocol. A request contains an exact source revision, entrypoint, and bounded +mirrored file set. The Worker validates every path and byte limit, copies only +that set into Rolldown's disposable memfs, compiles browser JavaScript, +TypeScript, JSX, and TSX, and returns bounded output files plus deterministic +SHA-256 input and artifact digests. Completion, failure, timeout, and +interruption terminate the Worker. The service updates its in-memory +last-successful artifact only after a valid success response, so a later +compiler failure cannot replace the preview fallback. + +Rolldown 1.2 removed its earlier experimental CSS bundling. Flect therefore +owns a deliberately smaller CSS adapter: local relative `.css` imports resolve +only to files in the mirrored input, become inert modules for the JavaScript +graph, and their strict-UTF-8 contents are emitted in deterministic path order +as `app.css`. It is not a Vite plugin host and does not claim CSS modules, +PostCSS, preprocessors, remote imports, or `url(...)` rewriting. + +`ProposalBuild` reads only the exact proposal commit from embedded Git, guarded +by the expected accepted and last-known-good commits. It strips the protected +`project/` prefix and hands that immutable snapshot to `BrowserBuild`; a dirty +authoring mirror or stale ref cannot enter the artifact. Successful artifacts +are written object-first to `/flect-builds/default/objects//`, with a +strict manifest and per-output hashes, before the last-successful binding is +advanced. Startup reconstructs and rehashes every output from OPFS; unsupported +hosts fall back to memory without changing the build authority boundary. +The workspace controller projects dependency resolution, lock checkpoint, +compile, package, success, and sanitized failure as typed `build-progress` +events and a reactive build snapshot. Active or failed work stays visible in +the protected rail, and outside clients observing the public snapshot see the +same phases; progress is not inferred from prose or private Worker logs. + +Production Chromium proves this substrate with a real guarded Git proposal +containing React 19 TSX, stateful interaction, and imported local CSS, then a +new broken proposal commit, last-successful retention, explicit +cross-origin-isolation gating, Worker disposal, and OPFS restoration after page +reload. The protected **Import app project** flow also recognizes standard Vite +browser entrypoints, checkpoints source on `flect/authoring`, supersedes the +candidate from that source, compiles the exact guarded proposal, packages only +the verified outputs plus the inert import report, and opens the normal +Keep/Reject review. The review exposes the exact source revision and artifact +digest. Chromium proves Vite TypeScript Keep/reload/export and a React JSX +import whose cached dependency graph rebuilds with the npm registry blocked. + +`BrowserPackageResolver` supplies the package-lock/cache part of that boundary. +For the portable import path, Flect derives an inert manifest containing only +package identity and runtime `dependencies`; scripts and development tools +remain recognizable in Git source but cannot enter package resolution or run. +The resolver hashes that exact portable manifest plus an optional lock, checks the +content-addressed OPFS cache first, and otherwise delegates registry resolution +to the existing Rifty npm client behind Flect's credential-free HTTPS registry +broker. The installer stages into a disposable VFS, validates tarball +integrity, runs no lifecycle scripts, and returns a bounded delta. Flect then +requires npm lockfile version 3, exact semantic versions, SHA-512 integrity, +and approved-registry HTTPS tarball sources for every non-root entry. + +The resolved `node_modules` graph and lock are bounded, deterministically +hashed, written object-first under `/flect-packages/default/objects//`, +and bound from the package-input digest only after every file exists. Cache +loads revalidate the manifest, lock, every file hash, and aggregate graph hash. +Before compilation, `ProposalBuild` compares the resolved lock with the exact +guarded proposal. A new or changed lock is checkpointed as +`project/package-lock.json` on `flect/authoring`; the shaping kernel then +supersedes the candidate through its normal receipt-guarded proposal +transaction. Compilation rereads that new proposal commit and passes its lock +back into the resolver. `ProposalBuild` replaces any source-tree dependency +directory with this verified graph and records its graph digest in the compiler +request, capsule build receipt, and persisted build artifact. Production +Chromium proves the accepted lock, exact versions, and integrity fields are +present in an ordinary exported Git repository, then proves the warm graph is +reused with npm registry access blocked. General registry selection, lock +conflict UX, cache management, and broader package/framework compatibility +remain open. ## QuickJS logic sandbox @@ -253,11 +669,19 @@ the response. Prompt turns and Shaper proposals both carry this typed event; the bridge times out, cleans up on interruption and session disposal, and never carries a filesystem handle or credential. -`SandboxedShell` uses `just-bash@3.2.0` with a memory VFS, hardened execution -limits, and one reserved `bun` command. Static AST rewriting prevents aliases, -functions, PATH changes, workspace files, packages, and extensions from -shadowing that command. Direct-network, JavaScript-evaluation, Python, SQLite, -and compression commands are excluded. The Vite build replaces the package's +`SandboxedShell` uses `just-bash@3.2.0` with a role-isolated working mirror, +hardened execution limits, and reserved `bun`, `flect`, and `git` commands. +Supported browser and WebView hosts persist each mirror under a separate +namespaced OPFS root; an unavailable OPFS surface falls back to a disposable +memory mirror without changing the capability boundary. Static AST rewriting +prevents +aliases, functions, PATH changes, workspace files, packages, and extensions +from shadowing any reserved command. The `flect` adapter captures the trusted +role, +session, parent operation, and tool call; it uses a bounded Effect queue and +cannot be reclassified through shell input. Direct-network, +JavaScript-evaluation, Python, SQLite, and compression commands are excluded. +The Vite build replaces the package's otherwise reachable `node:zlib` import with a fail-closed adapter, so compressed ripgrep input is unsupported without pulling Node compatibility into the browser. @@ -279,13 +703,30 @@ Flect storage, credentials, native bridge, or parent DOM. Effect scopes, deadlines, interruption, and finalizers own every Worker, iframe, active run, and preview. -The role workspace is currently disposable memory. It is not the future -canonical OPFS/libgit2 workspace, and changing it does not activate an -interface revision. Rifty and just-bash are cooperative execution substrates, +The role workspace has no handle to the canonical OPFS/libgit2 repository, +and changing it does not activate an interface revision. The reserved Git +adapter exposes role-bound `status`, `branch`, `rev-parse`, and `log`; Shaper +observes the actual proposal ref once one exists. A guarded full-ref snapshot +seeds the mirror. Shaper alone may run bounded `add -A`, `commit -m`, and +`restore .`; accepted and proposal transitions carry the source delta. Patch +producing `git diff` stays unavailable until the browser engine proves it. +Rifty and just-bash are cooperative execution substrates, not hostile-code containment. The trust boundary is the isolated realm plus strict schemas, typed brokers, bounds, capability denial, validation, and deterministic recovery. +Retained sharing source uses separate opaque `flect/shared/...` base, upstream, +fork, and candidate refs in the same Worker. Shaper's embedded `flect share +checkpoint` reads only explicitly named files from its sandbox and submits a +strict optimistic command; the controller and `ShareRepository` guard every +other ref and update the observable installation record. Clean upstream +updates pass a deterministic three-way resolved snapshot into the Worker. The +Worker writes and verifies the exact tree and a two-parent merge commit before +moving the candidate. Conflicts use Flect's deterministic byte-level three-way +policy so different Git engines cannot silently choose different review paths; +Shape resolution rechecks the exact refs and every recorded conflict path. See +[`docs/sharing.md`](docs/sharing.md) for the user lifecycle and current limits. + Build-gated diagnostics still exercise fixed JavaScript, WASI Preview 1, npm fixture, Bun command, and preview flows. The ordinary composer is separately tested in production Chromium with a full Pi event -> browser shell -> result @@ -294,6 +735,35 @@ round trip. The exact surface is maintained in ## Recovery and failure behavior +Accepted and candidate interface state remains solely owned by the shaping +kernel and its Effect-owned Git repository. The repository is an ordinary Git +tree persisted in OPFS by a dedicated Wasm/libgit2 Worker. Guarded checkpoint +transactions verify expected accepted, proposal, and last-known-good refs +under a cross-context Web Lock before updating `flect.json` and the validated +snapshot. A protected activation receipt lives outside user-controlled Git +state; ref or receipt disagreement fails closed. Complete export includes +`.git`, and `flect repository status` projects bounded reactive ref and conflict +state through the shared controller. On the first load of a new repository, +the repository service commits the built-in interface and establishes matching +accepted, last-known-good, and activation state before exposing the workspace. +This makes a no-edit reload durable without weakening receipt/ref +reconciliation. Legacy state follows the separate, explicit migration path. +A separate bounded role-continuity projection +stores only three isolated composer drafts and completed App, Preview App, and +Shaper messages. It is not interface history and can be discarded without +touching revisions. Preview continuity is restored only for an exact candidate +revision match; partial assistant streams, activities, credentials, auth +events, Pi sessions, control grants, and provider state are excluded. + +The continuity repository uses strict Effect Schemas, a 512 KiB aggregate +limit, generation checks, and Web Locks where available. It rereads inside the +lock and replaces one complete Web Storage value, so a stale tab or failed +quota write cannot silently replace the prior valid record. Safe mode does not +hydrate continuity content. Its compiled controls may inspect bounded +metadata, export a valid decoded record, or discard continuity independently +of last-known-good restoration. The exact lifecycle and current proof boundary +are in [`docs/recovery.md`](docs/recovery.md). + - invalid interface state -> compiled recovery shell; - safe mode -> bypass customized state; - invalid Shaper output -> no proposal; @@ -309,6 +779,16 @@ round trip. The exact surface is maintained in - public runtime failures -> stable redacted messages, never raw provider errors. +## Performance boundary + +Flect measures production-browser startup, initial resources, protected +composer interaction, candidate rebuilds, warm Use–Shape switches, Markdown, +cancellation, and garbage-collected repeated-cycle heap growth without adding +telemetry or another state owner. The one machine-readable release contract is +`shared/performance-budgets.ts`; the measurement environment, variance policy, +native-host complement, and rationale are documented in +[`docs/performance.md`](docs/performance.md). + ## Pinned implementation sources The implementation was checked against: @@ -319,11 +799,14 @@ The implementation was checked against: `b4f293684bba718d59cc1157679bcf6157b3a7f5`; - Rifty leaf packages `0.2.0`, evaluated upstream commit `207e0ee9f108d6457e2448c956b84c2758e62671`; +- `@rolldown/browser` `1.2.1`, release tag `v1.2.1` at + `93c535d8875daacde3afa84c0d4e9d26e87453e9`; - `just-bash` `3.2.0` and `esbuild-wasm` `0.28.1`; - Tauri CLI `2.11.4`, JavaScript API `2.11.1`, Rust crate `2.11.5`, and shell plugin `2.3.5`; - `quickjs-emscripten-core` and the QuickJS-NG release-sync variant `0.32.0`; - and -- Playwright `1.62.0`. +- Playwright `1.62.0`; and +- Model Context Protocol split server/client packages `2.0.0` and Zod + `4.2.0`. Exact package versions are recorded in `package.json` and `bun.lock`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5e08be..6951751 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,16 +70,20 @@ This runs: 6. a release-mode macOS application bundle build. Playwright uses `FLECT_TEST_MODE=1`, a deterministic in-memory runtime, and no -provider credentials. It covers a streamed turn, model-backed-shaping behavior -through the test Layer, accept/reject, persistence, rollback, corrupt-journal -recovery, keyboard submission, reduced motion, the QuickJS isolation check, and -compact layout. Unexpected browser console errors, page errors, and failed -local application requests fail the test. +provider credentials. It covers streamed turns, schema-driven Shaper tool +activity, accept/reject, persistence, rollback, corrupt-journal recovery, +sticky follow, keyboard submission, reduced motion, QuickJS isolation, compact +layout, reserved browser `flect` role policy and shell composition, plus a real +public `flect` process driving the same reactive browser workspace. Unexpected +browser console errors, page errors, and failed local application requests +fail the test. Unit and integration coverage additionally proves the protected fallback composer, typed Guardian diagnostic, session close across both transports, model/refresh lifecycle invalidation, non-destructive busy conflicts in both -directions, and the 32-pair runtime bound. +directions, the 32-pair runtime bound, strict local-control authentication and +descriptor permissions, concurrent cancellation, token rotation, SSE +decoding, CLI behavior, and MCP protocol interoperability. After authenticating Pi, verify the real Guardian/Shaper construction: @@ -94,9 +98,32 @@ bun run build:desktop -- --bundles app open src-tauri/target/release/bundle/macos/Flect.app ``` -The local bundle is ad-hoc signed for development. It is not a substitute for -Developer ID signing, notarization, hardened-runtime review, App Sandbox -entitlements, or distribution testing. +`build:desktop` requests an explicit ad-hoc hardened-runtime signature so the +development bundle is internally valid. `build:desktop:inferred-signing` is +reserved for the release pipeline, where Tauri infers an imported signing +certificate. Do not use the inferred-signing path as a local trust claim. + +The application bundle must contain public `flect` and private +`flect-runtime` in `Contents/MacOS`, with no separately shipped command +companions. +After enabling local control from Diagnostics, smoke-test the installed app +through the public executable rather than a private test hook: + +```bash +src-tauri/target/release/bundle/macos/Flect.app/Contents/MacOS/flect +src-tauri/target/release/bundle/macos/Flect.app/Contents/MacOS/flect inspect +src-tauri/target/release/bundle/macos/Flect.app/Contents/MacOS/flect mcp +``` + +See [`docs/local-control.md`](docs/local-control.md). + +The local bundle is ad-hoc signed with hardened runtime for development. It is +not a substitute for Developer ID signing, notarization, App Sandbox +entitlement review, independent reproducibility, or clean-machine distribution +testing. `bun run release:package` stages the DMG, checksum, demo MP4, and +release evidence under ignored `dist-release/`; it mounts and verifies the DMG +before succeeding. `FLECT_PUBLIC_RELEASE=1 bun run release:package` fails +closed at every public-trust boundary and must not be bypassed. ## Change expectations diff --git a/DESIGN.md b/DESIGN.md index e49435a..38a8a29 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -54,7 +54,7 @@ components: size: "40px" button-primary-hover: backgroundColor: "{colors.flect-rose-hover}" - textColor: "{colors.ink}" + textColor: "{colors.void}" rounded: "{rounded.pill}" size: "40px" prompt-surface: @@ -221,24 +221,26 @@ Its input, model source, attachment entry, voice entry, and submit/cancel state share one visual grammar. The prompt itself is customizable; safe mode always restores the compiled default. -The active role is explicit inside the composer before send: -**Edit · Shaper** changes the interface and **Run · App Agent** uses the -accepted product. Each role keeps its own draft and conversation. Switching -roles changes context; it never submits text or relabels history. +The active target is explicit inside the composer before send: **Shape** changes +the interface and **Use** operates either the candidate or accepted product. +The adjacent identity names Shaper, Preview App Agent, or App Agent. Each target +keeps its own draft and conversation. Switching changes context; it never +submits text or relabels history. ### Adaptive Agent Rail Flect does not bolt a second chat onto the product. The signature composer is one mounted instrument whose position reflects the workspace phase: -- a blank workspace centers the Edit/Shaper composer beneath “What should we +- a blank workspace centers the Shape/Shaper composer beneath “What should we shape?”; - the first message reveals the canvas and moves that same composer into the right conversation rail; - a validated proposal adds a compact Keep/Reject decision immediately above the composer; -- an accepted product defaults to Run/App Agent while Edit remains one explicit - switch away. +- a validated proposal defaults to candidate Use while Shape remains one + explicit switch away; +- an accepted product defaults to Use/App Agent. Above 980px, the rail is inline and resizable from 340–520px, with 400px as the default. From 761–980px it becomes a full-height right sheet. At 760px and @@ -251,6 +253,45 @@ The transition uses measured layout motion for at most 220ms. Under resized rail, collapse, or breakpoint must never create a second composer, horizontal page overflow, or hidden recovery control. +### Chat Markdown + +Agent output uses the same SF/system family as the shell at `1rem` with a +`1.55` dark-surface line height and a maximum assistant measure of `70ch`. +Blocks follow a `0.65rem` rhythm. Headings use a compact fixed-rem hierarchy: +`1.25rem`, `1.125rem`, `1rem`, then `0.875rem` for levels four through six. +The final three levels retain their semantic rank while sharing one quiet +visual band to suit the narrow rail. + +Code and tables are contained instruments, not new cards. They use the +existing Surface, Raised Surface, Line, Ink, and Muted tokens; code is +`0.875rem` mono and table content is `0.8125rem`. Their own viewports own +horizontal overflow, while copy, wrap, and expand actions stay dense on +desktop and reach `44px` at compact widths. Details remain native disclosures, +links use Flect Rose only as an interaction cue, and footnotes remain subdued. +The complete rendering and trust contract lives in +[`docs/superpowers/specs/2026-07-31-flect-chat-markdown-design.md`](docs/superpowers/specs/2026-07-31-flect-chat-markdown-design.md). + +### Activity, Follow, and Diagnostics + +Tool use is a compact instrument in the role timeline, not a generic assistant +sentence and not a developer-console dump. A card always names the tool and +shows queued, running, completed, or failed state. Duration stays visible; +bounded commands, output, exit status, preview links, validation paths, and +operation identifiers live in a native disclosure. Ready Mint and Failure Red +support the label but never carry meaning alone. + +Conversation follow respects the reader. Content follows while the viewport is +within 48px of its bottom. Once the person scrolls away, streaming and tool +updates preserve that position and surface a quiet, keyboard-operable **Jump +to latest** control with an unread count. Switching roles restores each role's +own position. Following never focuses the timeline or composer. + +Diagnostics is a protected disclosure above the composer. At rest it shows +only local-control state and connected-client count. When opened, it exposes +the explicit enable/revoke action and the latest correlated, redacted +operation evidence. It must remain legible and useful without turning the +ordinary product surface into infrastructure chrome. + ## 6. Do's and Don'ts ### Do: diff --git a/PRODUCT.md b/PRODUCT.md index 42f4a3b..dbba814 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -59,6 +59,13 @@ harder to recognize. - Changes should be inspectable, reversible, and attributable before they become more powerful. +## Product Quality + +[`docs/product-quality.md`](docs/product-quality.md) is the canonical contract +for Flect's stable user outcomes, release gates, and required proof. Current +maturity belongs in dated verification evidence and the Flect delivery +project, not in this product context. + ## Accessibility & Inclusion The shell targets WCAG 2.2 AA for text, controls, focus visibility, keyboard diff --git a/README.md b/README.md index 1299254..dc0d7c1 100644 --- a/README.md +++ b/README.md @@ -6,30 +6,26 @@ Flect is an open-source, agent-native interface shell whose running UI can be changed from inside itself. It works as a local macOS app and in a browser, -uses models already authenticated through [Pi](https://pi.dev), and keeps a -protected path back when a customized interface fails. +uses [Pi](https://pi.dev) to connect the model providers a user chooses, and +keeps a protected path back when a customized interface fails. **Flect is a public developer preview of the protected vertical slice:** start -with one excellent composer, shape an interface in Edit mode, inspect and keep -the validated preview, then use the resulting product through its separate App -Agent in Run mode. +with one excellent composer, shape an interface, test the validated candidate +through its own Preview App Agent, switch instantly between **Use** and +**Shape**, then keep or reject it without replacing accepted state early. [Download Flect for Apple Silicon macOS](https://github.com/akua-dev/flect/releases/latest/download/Flect_0.2.0_aarch64.dmg) · [SHA-256 checksum](https://github.com/akua-dev/flect/releases/latest/download/Flect_0.2.0_aarch64.dmg.sha256) +· +[Release evidence](https://github.com/akua-dev/flect/releases/latest/download/Flect_0.2.0_aarch64.release.json) ## Install Flect ### Apple Silicon macOS -The native preview requires Apple Silicon, macOS 12 or newer, and a one-time Pi -provider login. With [Bun](https://bun.sh) installed: - -```bash -bunx pi -``` - -Run `/login` inside Pi, authenticate a supported provider, then quit Pi. +The native preview requires Apple Silicon and macOS 12 or newer. It contains +its own Pi runtime; no terminal login or separate Pi installation is required. Download the DMG and checksum into the same directory and verify them: ```bash @@ -46,24 +42,30 @@ still blocks the app: xattr -dr com.apple.quarantine /Applications/Flect.app ``` +Launch Flect, open the model chooser, and connect a provider under **Pi +providers**. Provider credentials stay in Pi's private local store. Sensitive +manual values open in a separate one-use loopback page that Flect's interface +cannot read. + +Native update review and ownership-safe uninstall preparation live under +**Diagnostics**. Development builds do not contain an update trust key; public +builds fail closed unless the signed updater and Apple trust evidence are +complete. See [Updates and uninstall](docs/updates-and-uninstall.md). + ### Browser and source ```bash git clone https://github.com/akua-dev/flect.git cd flect bun install -bunx pi -``` - -Run `/login` inside Pi, quit Pi, then: - -```bash bun run dev ``` Open [http://127.0.0.1:5173](http://127.0.0.1:5173). Vite serves the UI there; -the origin-restricted local Pi runtime listens on `127.0.0.1:3210`. Provider -credentials remain in Pi and never enter browser storage. +the origin-restricted local Pi runtime listens on `127.0.0.1:3210`. Connect a +provider from the model chooser. Provider credentials remain in Pi and never +enter React state, workspace snapshots, control APIs, browser storage, or +shaped interfaces. ## See it shape @@ -73,44 +75,174 @@ Blank workspaces begin with the Shaper in a centered, protected composer. Ask for an interface and that same composer moves into the conversation rail while the validated result appears on the canvas. -![Flect Edit mode with the centered Shaper composer](assets/screenshots/flect-edit-mode.png) +![Flect Shape target with the centered Shaper composer](assets/screenshots/flect-edit-mode.png) -The proposal is only a preview until you explicitly keep it. Reject and -rollback remain deterministic, and safe mode always bypasses shaped state. +The proposal is only a preview until you explicitly keep it. Flect moves into +candidate **Use** automatically so you can question and exercise the proposed +product through an isolated Preview App Agent. **Shape** returns to the warm +Shaper conversation for a correction; the next valid result atomically +supersedes the candidate while accepted state remains unchanged. Reject, +rollback, and safe mode remain deterministic. ![A validated Flect interface proposal](assets/screenshots/flect-shaper-preview.png) -Keep the interface and switch to Run to speak with its App Agent. Edit and Run -retain distinct histories, drafts, Pi sessions, and sandbox workspaces. +Keep the interface to activate its accepted App Agent. Accepted Use, candidate +Use, and Shape retain distinct histories and drafts; candidate App and Shaper +also retain separate Pi sessions and disposable sandbox workspaces. + +![Flect Use target with the product App Agent](assets/screenshots/flect-run-mode.png) + +## Control the live workspace + +Open **Diagnostics** and enable local control to let a coding agent use the +same workspace through the public `flect` command, authenticated JSON/SSE API, +or compact MCP mode. Outside actions appear immediately in Flect and use the +same validation, revision, model, safe-mode, and cancellation paths as visible +controls. -![Flect Run mode with the product App Agent](assets/screenshots/flect-run-mode.png) +From a source checkout: + +```bash +bun run flect -- +bun run flect -- inspect +bun run flect -- shape "Create a focused project overview" +bun run flect -- target shape +bun run flect -- proposal accept +bun run flect -- permissions list +``` + +The installed app has one public executable. Run it directly, ask Diagnostics +to install the fixed `~/.local/bin/flect` link, or configure MCP with the same +executable plus the `mcp` argument. App Agent and Shaper also receive a +role-bound, unshadowable `flect` command inside their browser Bash sandbox, so +browser-hosted Flect needs no native CLI. Control is off by default and can be +revoked immediately. See the [command and local control guide](docs/local-control.md) +for role authority, TOON/JSON output, agent integrations, MCP, event +subscriptions, exit codes, and the security model. ## What works today - authenticated Pi model discovery, explicit selection, streamed turns, stop, and redacted public failures; -- separate Guardian, App Agent, and Shaper Pi sessions with independent - policies and lifecycle state; Guardian is tool-free while the interactive - roles receive isolated browser-backed Bash workspaces; +- in-Flect Pi provider discovery, status, login, protected sensitive entry, + safe option selection, refresh, and sign-out, plus model-supported reasoning + controls—without a terminal bootstrap step; +- one protected primary Pi session set for Guardian, accepted App Agent, and + Shaper, plus a separately acquired candidate session set while Preview App + Agent is active; Guardian is tool-free while every active interactive + context receives its own browser-backed Bash workspace; +- Bash-only Shaper proposals through the reserved `flect interface + validate/propose` path, one bounded corrective retry, and actionable + field-level validation diagnostics when candidate output is invalid; - one role-explicit composer that starts centered, moves into an inline, resizable right rail, and becomes an accessible sheet on compact screens; -- separate Edit/Shaper and Run/App Agent conversations, including per-role - drafts, cancellation, shell results, and visible authority before send; +- automatic, deliberate light and dark system appearances with dual-theme + Markdown syntax, forced-colors and reduced-motion support, keyboard focus + restoration, and production WCAG 2.2 A/AA gates across protected states; +- explicit Use/Shape workbench targets with separate accepted App, candidate + App, and Shaper conversations, per-target drafts, candidate-bound context, + warm correction loops, cancellation, rich Markdown, sticky follow, shell + results, and visible authority before send; +- bounded refresh/restart continuity for those three drafts and completed role + conversations, with stale-write rejection and model-free safe-mode + inspection, export, and isolated discard; +- warm Use/Shape switches under the documented 350 ms production-browser + budget without a model router call; +- a typed App Agent `request_interface_edit` transition: questions remain in + Use, while an explicit model tool request can hand a bounded edit instruction + to Shaper without granting acceptance authority; +- live tool instruments with phase, duration, bounded results, preview links, + and validation detail plus a bounded, correlated, redacted operation log; - Effect Schema-validated interface documents and a closed trusted renderer; - propose, preview, keep, reject, rollback, last-known-good recovery, and a compiled `?safe=1` recovery shell; +- a canonical browser-portable Git repository in OPFS, with guarded accepted, + isolated proposal, and last-known-good refs, reactive `flect repository + status`, and complete source/history export that opens in ordinary Git; +- inactive-by-default [portable sharing](docs/sharing.md) from local, HTTPS, + exact public Git, and trusted private sources, with five artifact kinds, + user-owned guarded forks, Shaper-driven sandbox checkpoints, verified clean + two-parent updates, explicit guarded Shape conflict resolution, deterministic + export, and separate remove/delete scope; +- deterministic `.flect` export/import with content-addressed OPFS persistence, + isolated compiled single-HTML previews, and protected provenance, + compatibility, signature-presence, and capability review before Keep; +- a host-defined named product-operation registry shared by capsules, App + Agent, embedded Bash, outside control, JSON, and MCP, with manifest-bound + digest/revision/workspace-scoped decisions; once, session, workspace, + persistent, deny, and revoke lifecycle; atomic rate enforcement; protected + safe-mode inspection; payload-free evidence; agent-visible list/revoke but no + grant command; bounded JSON results; least-privilege HTTPS and fixed-document + GraphQL adapters; and bounded cancellable, sequence-resumable event streams + that never expose raw `fetch`, sockets, documents, or host credentials to the + app. The packable [`@flect/product`](packages/product/) SDK and three + [reference products](examples/product-sdk/) prove offline, browser-direct, + authenticated-broker, inference, extension, update, fork, detach, and event + behavior without importing application internals; +- bounded credential-free HTTPS (plus loopback development) capsule install + through the same integrity and review boundary; +- explicit same-ID version comparison with Keep/Reject rather than silent + update replacement; +- direct bounded import of a plain static site or standard single-entry Vite + JavaScript/TypeScript/React directory with one root `index.html`, without + executing source during inspection, Vite config, or package scripts; +- a constrained `@rolldown/browser` Worker compiler for imported JavaScript, + TypeScript, JSX/TSX, React, and deterministic local CSS, with bounded typed + artifacts, exact guarded-Git inputs, visible build provenance, + content-addressed OPFS recovery, and last-successful fallback; +- integrity-checked browser npm resolution with an exact v3 lock checkpointed + into reviewable source Git, no lifecycle scripts, a content-addressed OPFS + dependency cache, and registry-offline reuse in the integrated React import + path; - a protected composer even when a shaped document omits its own prompt; -- one Pi-visible `bash` tool per interactive role, backed by a role-owned - browser workspace with reserved Bun-compatible run, build, package, preview, +- one Pi-visible `bash` tool per interactive role, backed by a role-owned, + browser-portable working mirror that persists in a separate OPFS namespace + when available, with reserved Bun-compatible run, build, package, preview, and stop commands; +- an unshadowable role-bound `git` surface over the real embedded repository: + inspection for both roles and guarded `add -A`, `commit -m`, and `restore .` + for Shaper. App Agent mutation is denied; patch-producing diff awaits a + browser engine that passes proof; - browser HTTP/SSE and native private-stdio transports behind the same Effect - capabilities; and + capabilities; +- one AXI-style `flect` command with bounded TOON, JSON compatibility, + role-bound browser embedding, optional coding-agent context hooks, explicit + shell-link setup, authenticated JSON/SSE, and MCP stdio mode; and - optional pure extension logic in a disposable QuickJS/Wasm worker that may return inert, schema-decoded intents only. The native app does not start the browser development server. It launches a compiled Bun/Pi sidecar and communicates through private NDJSON stdio exposed -to the webview by one narrow Tauri command. +to the webview by one narrow Tauri command. When the user enables outside +control, that sidecar additionally exposes the authenticated loopback broker. + +## Adopt Flect in a product + +`@flect/product` is the separately versioned, browser-portable Effect SDK for +product teams. It defines strict product metadata, recommended `.flect` +experiences, named unary and event operations, compatibility and declarative +migrations, inference ownership, user-state separation, and deterministic +adoption diagnostics. It also exports policy-fixed HTTP/GraphQL and bounded +event host Layers. + +Build and verify the current `0.1.0` preview tarball locally: + +```bash +bun run product:package +npm install ./dist-product-sdk/flect-product-0.1.0.tgz effect@4.0.0-beta.102 +``` + +This command installs the tarball into a clean temporary consumer, typechecks +it, and runs the smallest offline integration. It does not publish to npm. +Start with the [SDK quickstart](packages/product/README.md), then compare the +[offline, browser-direct, and brokered references](examples/product-sdk/). + +Flect still owns all grants, protected review, capsule activation, workspace +Git, safe mode, and recovery. Products supply only named, bounded closures. +Product denial overrides approval; credentials stay inside trusted host +closures; model-provider or inference-owner choice cannot change product +authorization. Product connection records remain separate from personal forks +and exports, and detach preserves that user-owned work. ## Current security and product boundary @@ -118,10 +250,18 @@ The QuickJS worker and browser agent workspace are defense-in-depth execution realms, not operating-system sandboxes. The source build can execute generated workspace code through its bounded browser shell, but it cannot invoke a host shell, native process, system Bun, ambient filesystem, or ambient network. -Flect does not yet ship portable `.flect` capsules, a component registry, -canonical OPFS/Git workspaces, in-Flect provider login, product/API adapters, -privileged host brokerage, remote runtimes, automatic updates, notarization, a -macOS App Sandbox entitlement, or Intel, Windows, and Linux packages. +Flect now exports and imports verified declarative `.flect` capsules through a +reviewable candidate flow. It also imports, isolates, persists, restores, and +byte-preservingly re-exports compiled HTML capsules with verified local CSS, +classic scripts, images, fonts, and media in supported browsers. +Arbitrary Vite plugins/config transforms, CSS modules/preprocessors and asset +URL rewriting, multi-entry routing, Vue/Svelte adapters, archive/Git import, +updates/forks, a component registry, custom duration/rate editing in the +protected permission UI, database adapters, privileged native +product transport, remote +runtimes, a published signed updater, notarization, a +macOS App Sandbox entitlement, and Intel, Windows, and Linux packages are not +yet shipped. User-shaped documents cannot replace deterministic validation, revision storage, rollback, safe mode, or the compiled recovery path. See the @@ -130,11 +270,39 @@ boundary and the exact [browser Bun compatibility matrix](docs/bun-compatibility.md) for supported commands and deliberate omissions. +## Import an existing interface + +Open the composer’s **Actions** menu and choose **Import app project**. Select +one directory with a root `index.html`. Flect currently recognizes plain static +sites and standard Vite browser entrypoints, including React JSX/TSX. It checks +paths and compatibility without executing source, excludes secret-shaped and +generated dependency files before reading them, checkpoints recognizable +source into embedded Git, and builds the exact isolated proposal locally. +When runtime dependencies need resolution, Flect generates or verifies an npm +v3 lock, checkpoints it into the candidate's ordinary Git source, supersedes +the guarded proposal, and compiles only that locked commit. +The current boundary accepts at most 255 source files (the capsule reserves one +metadata entry), 32 MiB total, and 100 characters per portable relative path. +Named unsupported Vite plugins, `resolve.alias`, and `node:` built-ins are +reported before a candidate exists, with a browser-portable alternative. + +The resulting app opens as a candidate. Review its source revision, artifact +digest, adaptations, ignored files, capabilities, and compatibility; exercise +the isolated UI; then choose **Keep change** or **Reject**. A kept build exports +as a complete `.flect` capsule and runs without Vite, npm, or a Flect-hosted +service. Vite config and package scripts are preserved as source but never run; +only runtime dependencies enter the portable package graph. + ## Architecture and vision - [Vision and intentional non-capabilities](VISION.md) - [Implemented architecture](ARCHITECTURE.md) +- [Local agent control](docs/local-control.md) +- [Product capability adoption](docs/product-capabilities.md) - [Browser Bun compatibility](docs/bun-compatibility.md) +- [Performance and memory budgets](docs/performance.md) +- [Session continuity and recovery](docs/recovery.md) +- [Updates and uninstall](docs/updates-and-uninstall.md) - [Users and product principles](PRODUCT.md) - [Design system](DESIGN.md) - [Contributor guide](CONTRIBUTING.md) @@ -152,10 +320,19 @@ tests, production Chromium workflows, Rust tests, and the native application build. `bun run test:pi-smoke` is separate because it makes one real private turn with the developer’s existing Pi provider login. -Release maintainers can reproduce the screenshots, demo, hero, DMG, checksum, -and MP4 with `bun run media:release` and `bun run release:package`. The media -pipeline additionally needs FFmpeg and the WebP tools `cwebp`, `dwebp`, and -`img2webp`. +Release maintainers can reproduce the screenshots, demo, and hero with +`bun run media:release`, then produce the DMG, checksum, MP4, and a +machine-readable evidence manifest with `bun run release:package`. Public mode +also produces the signed Tauri archive and static `latest.json`. The package +gate verifies the mounted DMG, exact executable inventory and architecture, +deep/strict signing, hardened runtime, and observed Gatekeeper/stapling state. +Public mode (`FLECT_PUBLIC_RELEASE=1`) additionally requires updater signing +material from the release environment and fails closed unless source, Developer +ID, notarization, updater signature verification, and independent +reproducibility proof are all present. Tauri's current Isolation Pattern intentionally generates fresh +per-build security material, so independent native-content reproducibility is +recorded as blocked rather than claimed. The media pipeline additionally needs +FFmpeg and the WebP tools `cwebp`, `dwebp`, and `img2webp`. ## License diff --git a/VISION.md b/VISION.md index e03f6d4..7700dbe 100644 --- a/VISION.md +++ b/VISION.md @@ -76,6 +76,9 @@ Flect will let people: SQL capability; - ask the built-in agent to use approved product capabilities as well as shape their presentation; +- explicitly pair a local outside agent that can inspect, operate, debug, and + subscribe to the same live workspace through the same user-visible command + and recovery boundaries; - use model access they control through Pi or another approved runtime instead of requiring every product to operate an inference service; - begin without an existing product backend and create local, offline-capable @@ -238,12 +241,22 @@ interface, preview the validated result, keep or reject it, then use the accepted experience through its separate App Agent. Recovery remains available through last-known-good rollback or the compiled recovery shell. -Guardian, App Agent, and Shaper use separate in-memory Pi sessions behind one -private runtime boundary. The same protected composer routes a blank workspace -to Edit/Shaper and an accepted experience to Run/App Agent. The desktop app -carries that runtime as a compiled sidecar over stdio instead of exposing it on -localhost. Optional pure extension logic can run in a disposable, +Guardian, accepted App Agent, Shaper, and candidate Preview App Agent use +separate in-memory Pi sessions behind one private runtime boundary. The same +protected composer exposes explicit Use and Shape targets: a blank workspace +starts in Shape, a candidate can be tested immediately in Use, and the accepted +experience uses its own App Agent. The desktop app +carries Pi traffic through a compiled sidecar over private stdio. Optional pure +extension logic can run in a disposable, resource-limited QuickJS WebAssembly worker and return only inert typed intents. +The same Effect workspace controller accepts visible UI actions and explicitly +authorized local commands, JSON/SSE, and MCP requests, publishes their changes +reactively, and retains bounded redacted diagnostic evidence. The native app +ships one public `flect` executable, while App Agent and Shaper receive the +same AXI language as a role-bound reserved command in their browser shell. +Shaper now proposes its workspace file through that command; accepting remains +a protected user decision. Outside control remains off by default and cannot +grant itself authority. This slice intentionally does not run generated React, native extensions, or product API capabilities. Interactive roles may use the bounded diff --git a/assets/agent-integrations/opencode/flect.js b/assets/agent-integrations/opencode/flect.js new file mode 100644 index 0000000..37a30e8 --- /dev/null +++ b/assets/agent-integrations/opencode/flect.js @@ -0,0 +1,82 @@ +// dev.akua.flect-context +const MAX_CONTEXT_BYTES = 1200; + +const boundedContext = async () => { + const process = Bun.spawn(["flect", "context", "--host", "opencode"], { + stdout: "pipe", + stderr: "ignore", + }); + const reader = process.stdout.getReader(); + const chunks = []; + let size = 0; + let truncated = false; + while (size < MAX_CONTEXT_BYTES) { + const next = await reader.read(); + if (next.done) break; + const remaining = MAX_CONTEXT_BYTES - size; + const chunk = next.value.subarray(0, remaining); + chunks.push(chunk); + size += chunk.byteLength; + if (chunk.byteLength < next.value.byteLength) { + truncated = true; + process.kill(); + break; + } + } + const bytes = new Uint8Array(size); + let offset = 0; + for (const chunk of chunks) { + bytes.set(chunk, offset); + offset += chunk.byteLength; + } + const exitCode = await process.exited; + if (exitCode !== 0 && !truncated) return ""; + const text = new TextDecoder().decode(bytes).trim(); + return truncated ? `${text}\n[bounded by Flect]` : text; +}; + +const eventValue = (envelope) => envelope?.event ?? envelope; +const eventSession = (event) => + event?.sessionID ?? event?.properties?.sessionID ?? event?.data?.sessionID; + +export default { + id: "dev.akua.flect-context", + setup: async (ctx) => { + const injected = new Set(); + const controller = new AbortController(); + const eventTask = (async () => { + try { + const subscription = await ctx.event.subscribe({ + signal: controller.signal, + }); + const stream = subscription?.stream ?? subscription; + for await (const envelope of stream) { + const event = eventValue(envelope); + if (event?.type === "session.compacted") { + const sessionID = eventSession(event); + if (typeof sessionID === "string") injected.delete(sessionID); + } + } + } catch { + // OpenCode stops or reloads the plugin by aborting the subscription. + } + })(); + + await ctx.session.hook("request", async (event) => { + const sessionID = eventSession(event) ?? "default"; + if (injected.has(sessionID)) return; + injected.add(sessionID); + const context = await boundedContext(); + if (context.length === 0) { + injected.delete(sessionID); + return; + } + event.system.push({ type: "text", text: context }); + }); + + return async () => { + controller.abort(); + await eventTask; + }; + }, +}; diff --git a/bun.lock b/bun.lock index b246923..2a954a3 100644 --- a/bun.lock +++ b/bun.lock @@ -9,12 +9,17 @@ "@earendil-works/pi-coding-agent": "0.82.1", "@effect/platform-browser": "4.0.0-beta.102", "@effect/platform-bun": "4.0.0-beta.102", + "@flect/product": "workspace:*", "@jitl/quickjs-ng-wasmfile-release-sync": "0.32.0", + "@modelcontextprotocol/server": "2.0.0", "@riftydev/npm-client": "0.2.0", "@riftydev/runtime-js": "0.2.0", "@riftydev/runtime-wasi": "0.2.0", "@riftydev/vfs": "0.2.0", + "@rolldown/browser": "1.2.1", "@tauri-apps/api": "2.11.1", + "@toon-format/toon": "4.1.0", + "@types/semver": "7.7.1", "acorn": "8.18.0", "effect": "4.0.0-beta.102", "esbuild-wasm": "0.28.1", @@ -22,11 +27,23 @@ "quickjs-emscripten-core": "0.32.0", "react": "19.2.8", "react-dom": "19.2.8", + "react-markdown": "10.1.0", + "rehype-raw": "7.0.0", + "rehype-sanitize": "6.0.0", + "remark-breaks": "4.0.0", + "remark-gfm": "4.0.1", + "scheduler": "0.27.0", + "semver": "7.8.5", + "shiki": "4.3.1", "typebox": "1.1.38", + "wasm-git": "0.0.17", + "zod": "4.2.0", }, "devDependencies": { + "@axe-core/playwright": "4.12.1", "@biomejs/biome": "2.5.6", "@effect/vitest": "4.0.0-beta.102", + "@modelcontextprotocol/client": "2.0.0", "@playwright/test": "1.62.0", "@tauri-apps/cli": "2.11.4", "@testing-library/jest-dom": "7.0.0", @@ -44,6 +61,17 @@ "vitest": "4.1.10", }, }, + "packages/product": { + "name": "@flect/product", + "version": "0.1.0", + "devDependencies": { + "effect": "4.0.0-beta.102", + "typescript": "7.0.2", + }, + "peerDependencies": { + "effect": "4.0.0-beta.102", + }, + }, }, "packages": { "@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="], @@ -102,6 +130,8 @@ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], + "@axe-core/playwright": ["@axe-core/playwright@4.12.1", "", { "dependencies": { "axe-core": "~4.12.1" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw=="], + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], @@ -158,14 +188,16 @@ "@effect/vitest": ["@effect/vitest@4.0.0-beta.102", "", { "peerDependencies": { "effect": "^4.0.0-beta.102", "vitest": "^3.0.0 || ^4.0.0" } }, "sha512-4dipFAYG6imOzrY3zy3BgzCJkbb9xESyzUef0WSx8bsK0/SpITqbJpdwKeOyDWLZ+rimjua8IbTFMAde865pIQ=="], - "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + "@emnapi/core": ["@emnapi/core@2.0.0-alpha.3", "", { "dependencies": { "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g=="], - "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + "@emnapi/runtime": ["@emnapi/runtime@2.0.0-alpha.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA=="], - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@2.0.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ=="], "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="], + "@flect/product": ["@flect/product@workspace:packages/product"], + "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="], "@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="], @@ -208,6 +240,12 @@ "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="], + "@modelcontextprotocol/client": ["@modelcontextprotocol/client@2.0.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "jose": "^6.1.3", "pkce-challenge": "^5.0.0", "zod": "^4.2.0" } }, "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw=="], + + "@modelcontextprotocol/core": ["@modelcontextprotocol/core@2.0.0", "", { "dependencies": { "zod": "^4.2.0" } }, "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA=="], + + "@modelcontextprotocol/server": ["@modelcontextprotocol/server@2.0.0", "", { "dependencies": { "@modelcontextprotocol/core": "2.0.0", "zod": "^4.2.0" } }, "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw=="], + "@mongodb-js/zstd": ["@mongodb-js/zstd@7.0.0", "", { "dependencies": { "node-addon-api": "^8.5.0", "prebuild-install": "^7.1.3" } }, "sha512-mQ2s0pYYiav+tzCDR05Zptem8Ey2v8s11lri5RKGhTtL4COVCvVCk5vtyRYNT+9L8qSfyOqqefF9UtnW8mC5jA=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], @@ -296,8 +334,26 @@ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], + "@rolldown/browser": ["@rolldown/browser@1.2.1", "", { "dependencies": { "@emnapi/core": "2.0.0-alpha.3", "@emnapi/runtime": "2.0.0-alpha.3", "@napi-rs/wasm-runtime": "^1.2.0" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-wdWNMUba40WNB7IkqI1UoATBJ0ygLUiosUeQXF1jn0HAjcJtpNjNE5/Qchxi+rEppeHoh1dBwJ06jbWs1RQq0g=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + "@shikijs/core": ["@shikijs/core@4.3.1", "", { "dependencies": { "@shikijs/primitive": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg=="], + + "@shikijs/langs": ["@shikijs/langs@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1" } }, "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ=="], + + "@shikijs/primitive": ["@shikijs/primitive@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A=="], + + "@shikijs/themes": ["@shikijs/themes@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1" } }, "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA=="], + + "@shikijs/types": ["@shikijs/types@4.3.1", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], "@smithy/core": ["@smithy/core@3.31.0", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-sylYk2l9d7CmRv8ts8p0SDQUr3VO+HMeS1nrjL6+UtbO8ktJHTOeQ1McX+aAyvGGccp5aZX9eNtdcXrSwzoZaw=="], @@ -358,6 +414,8 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], + "@toon-format/toon": ["@toon-format/toon@4.1.0", "", {}, "sha512-dBB3pkEx9QYvHnHR6rtkaBAh+7x4W/oA5ONur4G0fh7Ow69PbPuM7OFxzNRABqyxC0t6SZ3RixiGbCuaFjPDAQ=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], @@ -366,10 +424,20 @@ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@26.1.2", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], "@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], @@ -378,6 +446,10 @@ "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="], + "@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], @@ -420,6 +492,8 @@ "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.4", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-XcCQz0TBpBgljhj0gMuuDj49i6Ytqh5q1osT/Gp5uAVJUCTWxyskk/l1jwYYiu2xcNHHipdMz40EGfM1VdamVg=="], "@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="], @@ -450,6 +524,10 @@ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], @@ -470,14 +548,26 @@ "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + "commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="], "concurrently": ["concurrently@10.0.4", "", { "dependencies": { "chalk": "5.6.2", "rxjs": "7.8.2", "shell-quote": "1.9.0", "supports-color": "10.2.2", "tree-kill": "1.2.2", "yargs": "18.0.0" }, "bin": { "conc": "dist/bin/index.js", "concurrently": "dist/bin/index.js" } }, "sha512-trZql+7l/0+WRAsAnEdctr4+iiOS6ZrViI6H8QWcCF9MFS/LT0dKpe8vluB1to6it+OxSI4VospFTIFMW8DJRw=="], @@ -500,6 +590,8 @@ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], @@ -508,6 +600,8 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], @@ -528,8 +622,16 @@ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], @@ -574,12 +676,34 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + + "hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="], "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], @@ -594,6 +718,18 @@ "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], "is-unsafe": ["is-unsafe@2.0.0", "", {}, "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA=="], @@ -602,6 +738,8 @@ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "jose": ["jose@6.2.5", "", {}, "sha512-2E5L2yRp03FnwreJLJX8/r7mHiZICCf8kG7fAsTWkSQTDAcc46NIZoQLKy+EJ8sPoJlxyS4OQR5H70LjIZZlIQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "jsdom": ["jsdom@30.0.1", "", { "dependencies": { "@asamuzakjp/css-color": "^6.0.5", "@asamuzakjp/dom-selector": "^8.3.0", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.7", "@exodus/bytes": "^1.15.1", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.2", "undici": "^8.9.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^17.1.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.2.3" }, "optionalPeers": ["canvas"] }, "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA=="], @@ -644,16 +782,108 @@ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + "marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="], + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-newline-to-break": ["mdast-util-newline-to-break@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-find-and-replace": "^3.0.0" } }, "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], @@ -698,12 +928,18 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + "openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="], "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], "papaparse": ["papaparse@5.5.4", "", {}, "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], @@ -720,6 +956,8 @@ "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + "playwright": ["playwright@1.62.0", "", { "dependencies": { "playwright-core": "1.62.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw=="], "playwright-core": ["playwright-core@1.62.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA=="], @@ -732,6 +970,8 @@ "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="], "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], @@ -754,10 +994,32 @@ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + + "rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="], + + "remark-breaks": ["remark-breaks@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-newline-to-break": "^2.0.0", "unified": "^11.0.0" } }, "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -774,7 +1036,7 @@ "seek-bzip": ["seek-bzip@2.0.0", "", { "dependencies": { "commander": "^6.0.0" }, "bin": { "seek-bunzip": "bin/seek-bunzip", "seek-table": "bin/seek-bzip-table" } }, "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg=="], - "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -782,6 +1044,8 @@ "shell-quote": ["shell-quote@1.9.0", "", {}, "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA=="], + "shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], @@ -794,6 +1058,8 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], "sql.js": ["sql.js@1.14.1", "", {}, "sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A=="], @@ -806,6 +1072,8 @@ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], @@ -816,6 +1084,10 @@ "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], @@ -846,6 +1118,10 @@ "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -864,16 +1140,38 @@ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="], "vitest": ["vitest@4.1.10", "", { "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw=="], "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + "wasm-git": ["wasm-git@0.0.17", "", {}, "sha512-eAWLe2BQrx23t9jjffV5BkwbVW1c9nfUh4KDzPgMMKynNPGGTqztGYuJZY0x60hyO6clIn4eFUNkFnY535XtHw=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], @@ -906,32 +1204,54 @@ "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], - "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "zod": ["zod@4.2.0", "", {}, "sha512-Bd5fw9wlIhtqCCxotZgdTOMwGm1a0u75wARVEY9HMs1X17trvA/lMi4+MGK5EUfYkXVTbX8UDiDKW4OgzHVUZw=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.12", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-dWW5KRt4mnEvjNzbGqGeCuAvgum85Y9ZoyuMQqcTEfapndyVJ1k9BEHK7kdXJZ32enyRmmwcFjMwlB/KgLKI3Q=="], "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1097.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.2", "@aws-sdk/nested-clients": "^3.997.37", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.29.8", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-EIsdmy/f5IGc5r01RjKWNvrbBra6z0xudQM0D6Wf8DeGuPoRlubkLqr7VgWijFucO4kg0mtev9H3RX/ZOubUhg=="], "@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.12", "", { "dependencies": { "@smithy/core": "^3.31.0", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-dWW5KRt4mnEvjNzbGqGeCuAvgum85Y9ZoyuMQqcTEfapndyVJ1k9BEHK7kdXJZ32enyRmmwcFjMwlB/KgLKI3Q=="], + "@earendil-works/pi-coding-agent/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + "@earendil-works/pi-coding-agent/undici": ["undici@8.5.0", "", {}, "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg=="], + "@mistralai/mistralai/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@modelcontextprotocol/client/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@modelcontextprotocol/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@modelcontextprotocol/server/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "@riftydev/runtime-js/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], "data-urls/whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="], + "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + "just-bash/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], "just-bash/undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="], + "node-abi/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="], + "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], @@ -939,5 +1259,9 @@ "strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@rolldown/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "hast-util-raw/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], } } diff --git a/cli/flect-client.test.ts b/cli/flect-client.test.ts new file mode 100644 index 0000000..6b87ba2 --- /dev/null +++ b/cli/flect-client.test.ts @@ -0,0 +1,165 @@ +import { mkdtemp } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { assert, describe, it, vi } from "@effect/vitest"; +import { Effect, Stream } from "effect"; +import { + makeControlToken, + writeControlDescriptor, +} from "../server/control-descriptor"; +import { + FlectWorkspaceEvent, + SetMode, + UserCommandSource, +} from "../shared/control"; +import { ControlDescriptor } from "../shared/control-channel"; +import { + FlectControlClient, + makeFlectControlClientLayer, +} from "./flect-client"; + +describe("FlectControlClient", () => { + it.effect( + "discovers the private descriptor without exposing its bearer", + () => + Effect.gen(function* () { + const directory = yield* Effect.promise(() => + mkdtemp(join(tmpdir(), "flect-client-test-")), + ); + const token = makeControlToken(); + yield* writeControlDescriptor( + ControlDescriptor.make({ + version: 1, + instanceId: "instance-client-test", + workspaceId: "workspace-client-test", + url: "http://127.0.0.1:43127", + token, + pid: process.pid, + createdAt: 1, + }), + directory, + ); + const fetcher = vi.fn().mockImplementation(() => + Promise.resolve( + new Response( + JSON.stringify({ + status: "succeeded", + receipt: { + version: 1, + commandId: "cmd-client-test-response", + workspaceId: "workspace-client-test", + operationId: "operation-client-test", + sequence: 2, + status: "completed", + }, + }), + { + headers: { "content-type": "application/json" }, + }, + ), + ), + ); + + return yield* Effect.gen(function* () { + const client = yield* FlectControlClient; + const receipt = yield* client.command( + SetMode.make({ type: "set-mode", mode: "run" }), + ); + const [input, init] = fetcher.mock.calls[0] ?? []; + const body = JSON.parse(String(init?.body)) as { + readonly source: { + readonly kind: string; + readonly clientName: string; + }; + readonly command: { readonly type: string }; + }; + + assert.strictEqual( + String(input), + "http://127.0.0.1:43127/v1/workspaces/workspace-client-test/commands", + ); + assert.strictEqual( + new Headers(init?.headers).get("authorization"), + `Bearer ${token}`, + ); + assert.strictEqual(body.source.kind, "control"); + assert.strictEqual(body.source.clientName, "Outside test"); + assert.strictEqual(body.command.type, "set-mode"); + assert.strictEqual(receipt.operationId, "operation-client-test"); + assert.isFalse(JSON.stringify(receipt).includes(token)); + + yield* client.disable; + const [, disableInit] = fetcher.mock.calls[1] ?? []; + const disableBody = JSON.parse(String(disableInit?.body)) as { + readonly command: { readonly type: string }; + }; + assert.strictEqual(disableBody.command.type, "disable-control"); + }).pipe( + Effect.provide( + makeFlectControlClientLayer({ + stateDirectory: directory, + clientName: "Outside test", + clientId: "client-client-test", + fetch: fetcher, + }), + ), + ); + }), + ); + + it.effect("strictly decodes the authenticated SSE event stream", () => + Effect.gen(function* () { + const directory = yield* Effect.promise(() => + mkdtemp(join(tmpdir(), "flect-client-events-test-")), + ); + yield* writeControlDescriptor( + ControlDescriptor.make({ + version: 1, + instanceId: "instance-client-events", + workspaceId: "workspace-client-test", + url: "http://127.0.0.1:43129", + token: makeControlToken(), + pid: process.pid, + createdAt: 1, + }), + directory, + ); + const event = FlectWorkspaceEvent.make({ + version: 1, + id: "event-client-test", + sequence: 3, + timestamp: 3, + workspaceId: "workspace-client-test", + source: UserCommandSource.make({ kind: "user" }), + type: "state-changed", + }); + const fetcher = vi.fn().mockResolvedValue( + new Response(`id: 3\ndata: ${JSON.stringify(event)}\n\n`, { + headers: { "content-type": "text/event-stream" }, + }), + ); + + return yield* Effect.gen(function* () { + const client = yield* FlectControlClient; + const events = yield* client + .events(2) + .pipe(Stream.take(1), Stream.runCollect); + + assert.strictEqual(events.length, 1); + assert.strictEqual(events[0]?.sequence, 3); + assert.isTrue( + String(fetcher.mock.calls[0]?.[0]).endsWith( + "/v1/workspaces/workspace-client-test/events?after=2", + ), + ); + }).pipe( + Effect.provide( + makeFlectControlClientLayer({ + stateDirectory: directory, + fetch: fetcher, + }), + ), + ); + }), + ); +}); diff --git a/cli/flect-client.ts b/cli/flect-client.ts new file mode 100644 index 0000000..d8f7e4b --- /dev/null +++ b/cli/flect-client.ts @@ -0,0 +1,299 @@ +import { Context, Effect, Layer, Schema, type SchemaAST, Stream } from "effect"; +import { + type ControlDescriptorError, + readControlDescriptor, +} from "../server/control-descriptor"; +import { + ControlCommandSource, + DisableControl, + type FlectCommand, + FlectCommandEnvelope, + type FlectCommandError, + type FlectCommandReceipt, + FlectWorkspaceEvent, + FlectWorkspaceSnapshot, +} from "../shared/control"; +import { + ControlBrokerStatus, + ControlCommandOutcome, + ControlLogsResponse, +} from "../shared/control-channel"; + +const strictOptions: SchemaAST.ParseOptions = { + errors: "all", + onExcessProperty: "error", +}; + +export class FlectControlClientError extends Schema.TaggedErrorClass()( + "FlectControlClientError", + { + reason: Schema.Literals([ + "unavailable", + "unauthorized", + "invalid-response", + ]), + message: Schema.String.check( + Schema.isMinLength(1), + Schema.isMaxLength(240), + ), + }, +) {} + +const clientError = ( + reason: FlectControlClientError["reason"], + message: string, +) => FlectControlClientError.make({ reason, message }); + +export interface FlectControlClientShape { + readonly status: Effect.Effect< + ControlBrokerStatus, + FlectControlClientError | ControlDescriptorError + >; + readonly inspect: Effect.Effect< + FlectWorkspaceSnapshot, + FlectControlClientError | ControlDescriptorError + >; + readonly logs: Effect.Effect< + ControlLogsResponse, + FlectControlClientError | ControlDescriptorError + >; + readonly events: ( + after?: number, + ) => Stream.Stream< + FlectWorkspaceEvent, + FlectControlClientError | ControlDescriptorError + >; + readonly command: ( + command: FlectCommand, + expectedSequence?: number, + ) => Effect.Effect< + FlectCommandReceipt, + FlectCommandError | FlectControlClientError | ControlDescriptorError + >; + readonly disable: Effect.Effect< + void, + FlectCommandError | FlectControlClientError | ControlDescriptorError + >; +} + +export class FlectControlClient extends Context.Service< + FlectControlClient, + FlectControlClientShape +>()("flect/FlectControlClient") {} + +export interface FlectControlClientOptions { + readonly stateDirectory?: string; + readonly clientName?: string; + readonly clientId?: string; + readonly fetch?: typeof fetch; +} + +const sseData = async function* ( + body: ReadableStream, +): AsyncGenerator { + const reader = body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + let data: Array = []; + try { + while (true) { + const part = await reader.read(); + buffer += decoder.decode(part.value, { stream: !part.done }); + let newline = buffer.indexOf("\n"); + while (newline >= 0) { + const raw = buffer.slice(0, newline); + buffer = buffer.slice(newline + 1); + const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw; + if (line.length === 0) { + if (data.length > 0) { + yield JSON.parse(data.join("\n")) as unknown; + data = []; + } + } else if (line.startsWith("data:")) { + data.push(line.slice(5).trimStart()); + } + newline = buffer.indexOf("\n"); + } + if (part.done) { + break; + } + } + if (data.length > 0) { + yield JSON.parse(data.join("\n")) as unknown; + } + } finally { + reader.releaseLock(); + } +}; + +export const makeFlectControlClientLayer = ( + options: FlectControlClientOptions = {}, +) => + Layer.effect( + FlectControlClient, + Effect.gen(function* () { + const clientName = options.clientName ?? "flect"; + const clientId = options.clientId ?? `client-${crypto.randomUUID()}`; + const fetcher = options.fetch ?? globalThis.fetch; + + const execute = Effect.fn("Flect.ControlClient.execute")(function* ( + path: string, + init: RequestInit = {}, + ) { + const descriptor = yield* readControlDescriptor(options.stateDirectory); + const response = yield* Effect.tryPromise({ + try: (signal) => + fetcher(`${descriptor.url}${path}`, { + ...init, + signal, + headers: { + ...init.headers, + authorization: `Bearer ${descriptor.token}`, + }, + }), + catch: () => + clientError("unavailable", "Flect local control is unavailable."), + }); + if (response.status === 401) { + return yield* Effect.fail( + clientError( + "unauthorized", + "Flect local control authorization was revoked.", + ), + ); + } + return response; + }); + + const decode = ( + schema: Schema.ConstraintDecoder, + response: Response, + ) => + Effect.tryPromise({ + try: () => response.json(), + catch: () => + clientError( + "invalid-response", + "Flect returned an invalid control response.", + ), + }).pipe( + Effect.flatMap((input) => + Schema.decodeUnknownEffect( + schema, + strictOptions, + )(input).pipe( + Effect.mapError(() => + clientError( + "invalid-response", + "Flect returned an invalid control response.", + ), + ), + ), + ), + ); + + const status = execute("/v1/status").pipe( + Effect.flatMap((response) => decode(ControlBrokerStatus, response)), + ); + + const workspacePath = readControlDescriptor(options.stateDirectory).pipe( + Effect.map( + (descriptor) => + `/v1/workspaces/${encodeURIComponent(descriptor.workspaceId)}`, + ), + ); + + const inspect = workspacePath.pipe( + Effect.flatMap(execute), + Effect.flatMap((response) => decode(FlectWorkspaceSnapshot, response)), + ); + + const logs = workspacePath.pipe( + Effect.flatMap((path) => execute(`${path}/logs`)), + Effect.flatMap((response) => decode(ControlLogsResponse, response)), + ); + + const events = (after = 0) => + Stream.unwrap( + workspacePath.pipe( + Effect.flatMap((path) => + execute(`${path}/events?after=${Math.max(0, after)}`), + ), + Effect.flatMap((response) => + response.ok && response.body !== null + ? Effect.succeed( + Stream.fromAsyncIterable(sseData(response.body), () => + clientError( + "invalid-response", + "Flect returned an invalid event stream.", + ), + ).pipe( + Stream.mapEffect((input) => + Schema.decodeUnknownEffect( + FlectWorkspaceEvent, + strictOptions, + )(input).pipe( + Effect.mapError(() => + clientError( + "invalid-response", + "Flect returned an invalid event.", + ), + ), + ), + ), + ), + ) + : Effect.fail( + clientError( + "unavailable", + "Flect event streaming is unavailable.", + ), + ), + ), + ), + ); + + const command = Effect.fn("Flect.ControlClient.command")(function* ( + value: FlectCommand, + expectedSequence?: number, + ) { + if (value.type === "enable-control") { + return yield* Effect.fail( + clientError( + "unauthorized", + "Outside clients cannot enable Flect control.", + ), + ); + } + const descriptor = yield* readControlDescriptor(options.stateDirectory); + const commandPath = `/v1/workspaces/${encodeURIComponent(descriptor.workspaceId)}/commands`; + const envelope = FlectCommandEnvelope.make({ + version: 1, + commandId: `cmd-${crypto.randomUUID()}`, + workspaceId: descriptor.workspaceId, + source: ControlCommandSource.make({ + kind: "control", + clientId, + clientName, + }), + ...(expectedSequence === undefined ? {} : { expectedSequence }), + command: value, + }); + const response = yield* execute(commandPath, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(envelope), + }); + const outcome = yield* decode(ControlCommandOutcome, response); + return outcome.status === "succeeded" + ? outcome.receipt + : yield* Effect.fail(outcome.error); + }); + + const disable = command( + DisableControl.make({ type: "disable-control" }), + ).pipe(Effect.asVoid); + + return { status, inspect, logs, events, command, disable }; + }), + ); diff --git a/cli/flect.test.ts b/cli/flect.test.ts new file mode 100644 index 0000000..414651b --- /dev/null +++ b/cli/flect.test.ts @@ -0,0 +1,96 @@ +import { assert, describe, it, vi } from "@effect/vitest"; +import { Effect, Layer, Stream } from "effect"; +import { ControlUnauthorized } from "../shared/control"; +import { ControlBrokerStatus } from "../shared/control-channel"; +import { + FlectCommandGateway, + type FlectCommandGatewayShape, +} from "../src/axi/gateway"; +import { type FlectCliIo, runFlectCli } from "./flect"; + +const makeHarness = (stdin = "") => { + const stdout: Array = []; + const stderr: Array = []; + const launch = vi.fn(async () => undefined); + const command = vi.fn(() => + Effect.fail( + ControlUnauthorized.make({ message: "Test command boundary reached." }), + ), + ); + const layer = Layer.succeed(FlectCommandGateway)({ + audience: "native", + bin: "flect", + status: Effect.succeed( + ControlBrokerStatus.make({ + version: 1, + enabled: false, + connected: false, + port: 43128, + url: "http://127.0.0.1:43128", + }), + ), + inspect: Effect.die("unused"), + logs: Effect.die("unused"), + events: () => Stream.empty, + command, + }); + const io: FlectCliIo = { + readStdin: async () => stdin, + stdout: (value) => stdout.push(value), + stderr: (value) => stderr.push(value), + launch, + }; + return { command, io, launch, layer, stderr, stdout }; +}; + +describe("flect CLI adapter", () => { + it.effect("writes one TOON home result and no stderr", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const code = yield* runFlectCli([], harness.io); + assert.strictEqual(code, 0); + assert.strictEqual(harness.stdout.length, 1); + assert.include(harness.stdout[0] ?? "", "control: disabled"); + assert.deepStrictEqual(harness.stderr, []); + }).pipe(Effect.provide(harness.layer)); + }); + + it.effect("materializes --stdin before sending a Shaper command", () => { + const harness = makeHarness("Make the dashboard denser\n"); + return Effect.gen(function* () { + const code = yield* runFlectCli(["shape", "--stdin"], harness.io); + assert.strictEqual(code, 1); + assert.strictEqual(harness.command.mock.calls.length, 1); + const sent = harness.command.mock.calls[0]?.[0]; + assert.strictEqual(sent?.type, "submit-shaper-instruction"); + assert.strictEqual( + sent?.type === "submit-shaper-instruction" ? sent.instruction : "", + "Make the dashboard denser", + ); + assert.include(harness.stdout[0] ?? "", "code: unauthorized"); + assert.deepStrictEqual(harness.stderr, []); + }).pipe(Effect.provide(harness.layer)); + }); + + it.effect("opens the graphical app through the native adapter", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const code = yield* runFlectCli(["app"], harness.io); + assert.strictEqual(code, 0); + assert.strictEqual(harness.launch.mock.calls.length, 1); + }).pipe(Effect.provide(harness.layer)); + }); + + it.effect("keeps usage errors structured on stdout", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const code = yield* runFlectCli( + ["--json", "mode", "guardian"], + harness.io, + ); + assert.strictEqual(code, 2); + assert.include(harness.stdout[0] ?? "", '"code":"invalid-argument"'); + assert.deepStrictEqual(harness.stderr, []); + }).pipe(Effect.provide(harness.layer)); + }); +}); diff --git a/cli/flect.ts b/cli/flect.ts new file mode 100644 index 0000000..7e7815e --- /dev/null +++ b/cli/flect.ts @@ -0,0 +1,195 @@ +import { homedir } from "node:os"; +import * as BunFileSystem from "@effect/platform-bun/BunFileSystem"; +import * as BunPath from "@effect/platform-bun/BunPath"; +import { Effect, Layer, Schema } from "effect"; +import { makeBrokerFlectCommandGatewayLayer } from "../src/axi/broker-gateway"; +import { runFlect } from "../src/axi/program"; +import { makeAgentIntegrationLayer } from "../src/lib/agent-integration"; +import { makeShellLinkLayer } from "../src/lib/shell-link"; +import { + flectApplicationPathFromExecutable, + makeUninstallLayer, +} from "../src/lib/uninstall"; +import { makeFlectControlClientLayer } from "./flect-client"; + +export interface FlectCliIo { + readonly readStdin: () => Promise; + readonly stdout: (text: string) => void; + readonly stderr: (text: string) => void; + readonly launch: () => Promise; +} + +export class FlectCliError extends Schema.TaggedErrorClass()( + "FlectCliError", + { + operation: Schema.Literals(["stdin", "launch"]), + message: Schema.String, + }, +) {} + +const defaultIo: FlectCliIo = { + readStdin: () => Bun.stdin.text(), + stdout: (text) => process.stdout.write(text), + stderr: (text) => process.stderr.write(text), + launch: async () => { + const child = Bun.spawn(["open", "-a", "Flect"], { + stdout: "ignore", + stderr: "ignore", + }); + if ((await child.exited) !== 0) { + throw new Error("launch failed"); + } + }, +}; + +const materializeStdin = Effect.fn("Flect.Cli.materializeStdin")(function* ( + argv: ReadonlyArray, + io: FlectCliIo, +) { + const index = argv.indexOf("--stdin"); + if (index < 0) { + return argv; + } + const text = yield* Effect.tryPromise({ + try: io.readStdin, + catch: () => + FlectCliError.make({ + operation: "stdin", + message: "Standard input is unavailable.", + }), + }).pipe(Effect.orElseSucceed(() => "")); + return [...argv.slice(0, index), text.trim(), ...argv.slice(index + 1)]; +}); + +const commandNoun = (argv: ReadonlyArray) => + argv.find((value) => value !== "--json" && value !== "--full"); + +export const runFlectCli = Effect.fn("Flect.Cli.run")(function* ( + argv: ReadonlyArray, + io: FlectCliIo = defaultIo, +) { + const materialized = yield* materializeStdin(argv, io); + if (commandNoun(materialized) === "app") { + yield* Effect.tryPromise({ + try: io.launch, + catch: () => + FlectCliError.make({ + operation: "launch", + message: "The Flect app could not be opened.", + }), + }).pipe(Effect.ignore); + } + const result = yield* runFlect(materialized); + if (result.stdout.length > 0) { + yield* Effect.sync(() => io.stdout(result.stdout)); + } + if (result.stderr.length > 0) { + yield* Effect.sync(() => io.stderr(result.stderr)); + } + return result.exitCode; +}); + +interface PrivateOptions { + readonly argv: ReadonlyArray; + readonly stateDirectory?: string; + readonly clientName?: string; +} + +export interface NativeFlectGatewayOptions { + readonly stateDirectory?: string; + readonly clientName?: string; + readonly bin?: string; + readonly configRoot?: string; + readonly publicExecutable?: string; +} + +export const makeNativeFlectGatewayLayer = ( + options: NativeFlectGatewayOptions = {}, +) => { + const client = makeFlectControlClientLayer({ + stateDirectory: options.stateDirectory, + clientName: options.clientName ?? "flect", + }); + const gateway = makeBrokerFlectCommandGatewayLayer({ + audience: "native", + bin: options.bin ?? "flect", + }).pipe(Layer.provide(client)); + const platform = Layer.merge(BunFileSystem.layer, BunPath.layer); + const integrations = makeAgentIntegrationLayer( + options.configRoot ?? homedir(), + ).pipe(Layer.provide(platform)); + const publicExecutable = + options.publicExecutable ?? process.env.FLECT_PUBLIC_EXECUTABLE; + if (publicExecutable === undefined || !publicExecutable.startsWith("/")) { + return Layer.merge(gateway, integrations); + } + const shell = makeShellLinkLayer({ + home: options.configRoot ?? homedir(), + executable: publicExecutable, + }).pipe(Layer.provide(platform)); + const applicationPath = flectApplicationPathFromExecutable(publicExecutable); + if (applicationPath === undefined) { + return Layer.mergeAll(gateway, integrations, shell); + } + const setup = Layer.merge(integrations, shell); + const uninstall = makeUninstallLayer({ applicationPath }).pipe( + Layer.provide(setup), + ); + return Layer.mergeAll(gateway, setup, uninstall); +}; + +const privateOptions = (argv: ReadonlyArray): PrivateOptions => { + const publicArguments: Array = []; + let stateDirectory: string | undefined; + let clientName: string | undefined; + for (let index = 0; index < argv.length; index += 1) { + const value = argv[index]; + if (value === "--state-dir" || value === "--client-name") { + const option = argv[index + 1]; + if (option !== undefined) { + if (value === "--state-dir") { + stateDirectory = option; + } else { + clientName = option; + } + index += 1; + } else { + publicArguments.push(value); + } + } else if (value !== undefined) { + publicArguments.push(value); + } + } + return { + argv: publicArguments, + ...(stateDirectory === undefined ? {} : { stateDirectory }), + ...(clientName === undefined ? {} : { clientName }), + }; +}; + +export const runFlectCliMain = ( + argv: ReadonlyArray, + io: FlectCliIo = defaultIo, + options: Omit = {}, +) => { + const parsed = privateOptions(argv); + return runFlectCli(parsed.argv, io).pipe( + Effect.provide( + makeNativeFlectGatewayLayer({ + ...options, + stateDirectory: parsed.stateDirectory, + clientName: parsed.clientName ?? options.clientName, + }), + ), + ); +}; + +if (import.meta.main) { + Effect.runPromise( + runFlectCliMain(process.argv.slice(2), defaultIo, { + bin: process.argv[1] ?? "flect", + }), + ).then((code) => { + process.exitCode = code; + }); +} diff --git a/docs/capsule-format.md b/docs/capsule-format.md new file mode 100644 index 0000000..f9067bd --- /dev/null +++ b/docs/capsule-format.md @@ -0,0 +1,264 @@ +# `.flect` capsule format + +Status: version 1 + +A `.flect` file is a deterministic, offline-installable product-interface +artifact. Every Flect host uses this contract. A capsule is data: decoding it +does not execute code, grant capabilities, authenticate a product, or activate +an interface. + +## Archive + +The container is an uncompressed POSIX `ustar` archive. It has normalized +ownership, permissions, and timestamps. `flect.json` is the first entry. Every +payload follows in bytewise lexical path order, then two zero blocks. Identical +manifest input and payload bytes must produce byte-identical archives. + +Version 1 permits at most 256 payload files, 8 MiB per file, and 32 MiB for the +complete archive. Paths are relative slash-separated names of at most 100 UTF-8 +bytes. Absolute paths, empty components, `.`/`..`, `.git`, links, devices, +duplicate entries, extra archive data, and unsupported tar entry types are +invalid. + +## Manifest + +`flect.json` is strict UTF-8 JSON. Unknown fields are invalid. + +- `formatVersion`: `1`. +- `id`, `name`, `version`: stable reverse-domain identifier, display name, and + semantic artifact version. +- `entrypoints`: stable identifiers and payload paths for compiled web entry + documents. +- `files`: path, byte count, and lowercase SHA-256 for every payload, in archive + order. +- `capabilities`: requested capability identifiers and whether the product + considers each required. These are requests, never grants. +- optional `extensions`: bounded portable App Agent and Shaper extension + packages described below. Declarations are requests and inert metadata; + decoding never activates them. +- `compatibility`: supported Flect range, capsule schema version, and platforms. +- `provenance`: publisher label, source reference, source revision, and builder. +- optional `build`: the exact source revision plus SHA-256 input, artifact, and + optional verified dependency-graph digests for a restricted source build. +- `signatures`: reserved detached-signature records (`ed25519`, key identifier, + signature). Presence does not itself establish trust. + +Every entrypoint must name a declared payload. Every payload byte count and hash +is verified before a decoder returns a capsule. + +## Portable extension packages + +Version 1 may declare at most 32 portable packages. A package contains a stable +ID, display name, description, semantic version, verified bundle path, one or +both target roles (`app`, `shaper`), compatible Flect range, portable Extension +API version `1`, supported hosts, requested capabilities, bounded public +instructions, command/tool discovery metadata, host-capped resources, and +publisher/source/revision provenance. Guardian is not a package role. + +Each bundle is a declared capsule payload and its lowercase SHA-256 must match +the package provenance record. An optional source map must likewise name a +declared payload and match its declared digest. Duplicate package, role, +capability, command, or tool IDs are invalid. Missing bundles, mismatched +digests, unsupported API versions, excessive resource requests, and unknown +fields fail capsule decoding before any code can run. + +The package resource declaration cannot exceed 100 ms execution time, 16 MiB +memory, 1 MiB input, 1 MiB output, or 20 inert intents per call. Hosts may apply +stricter limits. The verified executable bundle is capped at 256 KiB, matching +the worker request boundary; a larger bundle is rejected during capsule +verification rather than staged as an unusable package. These values are +ceilings, not authority. + +Packages contain no grants, decisions, credentials, conversations, local paths, +or mutable installation state. Installation review and role activation remain +protected host decisions. Portable bundles execute only through Flect's +QuickJS worker and capability broker; ordinary external Pi extensions remain a +separate explicitly trusted host-code path. The lifecycle and isolation design +is recorded in +[`docs/superpowers/specs/2026-08-02-flect-portable-extension-lifecycle-design.md`](superpowers/specs/2026-08-02-flect-portable-extension-lifecycle-design.md). + +## Review and activation + +Import opens a candidate; it never activates the capsule directly. The +protected Flect shell shows the publisher, artifact version, source revision, +signature count, contents, supported platforms, and every requested capability +before Keep is available. A capability marked `required` is a hard activation +precondition. Until a trusted broker reports that it can grant that capability, +Flect leaves the isolated preview available for inspection but disables Keep at +the UI boundary and rejects acceptance again in the controller. Optional +capabilities remain ungranted unless a later explicit grant flow says otherwise. + +The protected review projects the canonical requested, available, granted, +denied, expired, or revoked lifecycle together with the separate fact of host +availability. A trusted host may register a stable product operation whose +required capability matches the manifest request. Registration and decisions +remain trusted host state; neither is copied into the archive. + +A decision binds the capsule scope ID, exact archive SHA-256, capability-request +digest, workspace when applicable, provenance revision, and the approved +operation/resource/data intersection and decision policy. Changing the archive, +request, workspace-bound context, or registered limits cannot widen an older +decision. See [`docs/product-capabilities.md`](product-capabilities.md) for the +adopter contract and complete lifecycle. + +The current review reports signature presence but does not verify publisher +identity; unsigned and unverified capsules must not be presented as trusted. +The host also evaluates the declared semver range against Flect's package +version and checks the actual browser/native platform. An incompatible range or +unsupported host follows the same inspectable-preview, blocked-activation path. + +Portable packages appear in the same protected review but remain visibly +separate from product capabilities and trusted external Pi extensions. Flect +shows package publisher/source/revision, declared roles, resource ceilings, and +required versus optional authority. App Agent and Shaper start off +independently. Each is enabled only by an explicit protected action, and +optional authority is off by default. An enabled candidate must complete one +bounded worker test before Keep; a failed candidate must be disabled or fixed +before acceptance. The controller repeats this check even if customized UI is +bypassed. + +Pins and local-fork revision markers survive as update conflicts. Because +three-way capsule merging is not implemented in version 1, the honest choices +are to use the upstream package or reject the candidate app update and keep the +current fork. Flect does not silently combine or relabel package bytes. + +## Runtime intents + +Compiled entrypoints have no network authority. Their only host bridge is +`globalThis.flect.post(...)` over an opaque-frame `MessagePort`. Each projected +document receives a new 128-bit cryptographic nonce; the host accepts the +ready/connect handshake only when its protocol version, type, and nonce all +match. The nonce authenticates the frame instance while the transferred port +becomes the bounded capability channel. Unrelated window messages, stale +frames, surplus pending ports, and late traffic are ignored or failed closed. +Version 1 accepts JSON-only intents of this shape: + +```json +{ + "version": 1, + "type": "intent", + "id": "intent-projects1", + "action": "projects.list", + "input": { "limit": 20 } +} +``` + +The action is a stable host-registered operation ID, not a URL or tool name. +Flect constructs a capsule-bound command source, verifies that the current +accepted or candidate capsule declares the operation's capability, applies the +host grant, and invokes the protected adapter through the shared workspace +controller. A matching `flect:host` custom event returns either bounded JSON +output or one sanitized `unavailable`, `denied`, `failed`, or `invalid-result` +failure. The complete message is limited to 64 KiB and must repeat the intent +ID. Malformed, oversized, flooding, stale-frame, and mismatched replies fail +closed. Credentials, transport exceptions, response cookies, and unapproved +headers are not representable in this protocol. A source that misses the +bounded ready deadline is stopped without poisoning a later capsule revision. + +## Compiled asset projection + +For a compiled HTML entrypoint, Flect resolves capsule-relative stylesheet, +classic script, image, poster, font, audio, video, and CSS `url(...)` references +only against verified files in the same archive. Text assets are decoded as +strict UTF-8; binary assets become bounded `data:` URLs. Stylesheets and classic +scripts are inlined into one generated document, so the opaque frame needs no +host filesystem, object URL, package registry, CDN, or network access. Missing, +remote, malformed, and unsupported references remain unavailable under the +frame's deny-by-default CSP. + +Browser hosts load that exact document from a self-contained base64 `data:` +URL. The packaged Tauri host keeps it only in a bounded in-memory registry and +serves the exact token through the local `flect-capsule` custom protocol with +`no-store`, no-referrer, and no-sniff response policy. This is an internal +process transport, not network authority or persisted capsule state. It avoids +weakening the protected host's CSP when WebKit applies the parent policy to +inline scripts in `srcdoc` or `data:` frames. Native registration accepts only +128-bit hexadecimal tokens, at most eight live documents, and at most 16 MiB +per generated document; release removes the exact token. Missing and unknown +tokens return no document. + +Both routes execute the document in an iframe sandbox that grants scripts but +not same-origin authority. The child keeps its own deny-by-default CSP, and the +Tauri shell does not add `unsafe-inline`. The host canvas supplies a readable +light baseline for unstyled portable products without changing authored +styles. The archive limits above remain the canonical product-artifact limits; +the native document limits bound only the generated projection transport. + +This is an artifact runtime, not the source builder. The restricted +`BrowserBuild` service compiles browser-local JavaScript, TypeScript, JSX/TSX, +React, and local CSS in a disposable Worker from an exact guarded Git proposal. +Its package resolver produces and caches an integrity-bearing npm v3 lock. A +successful framework capsule records source revision, input digest, artifact +digest, and optional dependency-graph digest in its `build` receipt, then ships +only the portable HTML shell, verified build outputs, and inert import report. +It does not ship source or require the builder at runtime. Import maps, +`srcset`, CSS asset URLs/modules/preprocessors, arbitrary Vite plugins, and +additional framework adapters remain separate compatibility work. + +## URL installation + +The protected actions menu accepts an HTTPS `.flect` URL; loopback HTTP is +allowed only for local development. Flect sends no credentials, bypasses the +HTTP cache, follows ordinary browser redirects/CORS, aborts after 20 seconds, +and bounds both declared and streamed response bytes to 32 MiB. Downloaded +bytes pass through the same strict decoder and payload-hash checks as a local +file, then open the same reviewable candidate. Browser CORS policy is an honest +host boundary: a publisher must make the artifact readable to the user's +browser or the install fails without changing accepted state. + +When the candidate and installed capsule IDs match, the protected review names +the exact installed-to-candidate version transition. The installed archive and +accepted revision remain authoritative until explicit Keep; Reject restores the +installed presentation without mutation. A different capsule ID is presented +as an explicit replacement rather than an update. Compatible three-way merging +of a personalized fork is not yet implemented. + +## Project directory import + +The protected actions menu can select an ordinary static-site or standard Vite +browser project directory with one root `index.html`. Flect validates every +relative path before reading source, +ignores `.git`, `node_modules`, `.DS_Store`, `.env` variants, package-manager +auth files, private-key names, and key/certificate extensions before reading +their contents, applies the capsule file/byte limits, and never executes +project source, Vite config, package scripts, or development dependencies +during inspection. Static inputs package directly. A local module entrypoint is +checkpointed as recognizable source in embedded Git and compiled only from the +exact guarded proposal before the resulting artifact enters the same review +and candidate flow. A directory import may contribute at most 255 source files +because Flect reserves one of the capsule's 256 payload entries for the import +report; relative paths share the capsule's 100-character limit. Ambiguous +roots, traversal, reserved metadata collisions, named unsupported Vite plugins, +`resolve.alias`, and `node:` built-ins fail before a candidate is created and +name the portable alternative. + +The versioned compatibility report identifies the project class, root +entrypoint, included-file count, ignored paths, adaptations, forms, remote +URLs, storage, and workers. It is carried as inert capsule metadata and shown +in protected review together with the build receipt. +Unsupported authority/runtime assumptions become visible manifest requests and +required ones block activation. Multi-page routing, archive/Git inputs, +content-aware secret scanning and a complete preserved/adapted/unsupported +per-feature matrix remain open. Supported files +are checkpointed first on the isolated `flect/authoring` branch and flow through +the proposal into `flect/accepted` only on Keep; exported ordinary Git contains +the recognizable `project/` source tree. + +## Prohibited state + +Credentials, tokens, cookies, provider sessions, capability grants, user +conversations, local paths, host settings, mutable runtime state, and recovery +metadata have no representation in version 1. Strict decoding rejects attempts +to add them to the manifest. + +## Migration + +Decoders accept only versions for which they implement an explicit migration. +Version 1 is canonical and has no predecessor. A future version must add a +pure, bounded `N -> N+1` manifest migration, retain integrity verification of +the original archive, document semantic changes, and ship golden fixtures. +Unknown versions fail closed; they are never interpreted as the latest version. + +The executable Effect Schema and codec live in `shared/capsule.ts`. This +document describes that source of truth; tests in `shared/capsule.test.ts` +provide determinism, strictness, and integrity evidence. diff --git a/docs/local-control.md b/docs/local-control.md new file mode 100644 index 0000000..4f5f283 --- /dev/null +++ b/docs/local-control.md @@ -0,0 +1,256 @@ +# Flect command and local control + +Flect has one public command surface: `flect`. The installed macOS executable +opens the graphical app when launched by Finder and runs the agent-first +command program when invoked from a terminal. App Agent and Shaper receive the +same language as a reserved command inside their browser-portable Bash +sandboxes. MCP and JSON/SSE are adapters over that same Effect command and +workspace-controller authority. + +## Start here + +From a source checkout: + +```bash +bun run flect -- +bun run flect -- inspect +``` + +From an installed app: + +```bash +/Applications/Flect.app/Contents/MacOS/flect +/Applications/Flect.app/Contents/MacOS/flect inspect +``` + +The no-argument result is content-first discovery for the live workspace, not +a static manual. Use `flect --help` for the command reference and a command's +trailing `--help` for local help. `flect app` opens or focuses the graphical +application. + +Diagnostics can install the fixed user link `~/.local/bin/flect`, or the same +opt-in operation can be requested through the full bundle path: + +```bash +/Applications/Flect.app/Contents/MacOS/flect setup shell install +flect setup shell remove +``` + +Flect never accepts an arbitrary link target, overwrites a regular file or +foreign symlink, copies a second executable, or requests administrator access. +It repairs only a stale link that points into another Flect app bundle. + +## Output contract + +Successful results and expected failures use stdout. Stderr is reserved for +optional progress and debug diagnostics. The default format is bounded TOON: + +```bash +flect inspect +flect --json inspect +flect --full logs --limit 20 +``` + +Global `--json` and `--full` flags precede the command. Without `--full`, long +strings are truncated with their original size and a recovery hint. The total +encoded result is capped. Exit `0` means success or an already-satisfied +idempotent operation, exit `1` means an operational or authorization failure, +and exit `2` means invalid usage. Errors remain structured and carry stable +codes such as `unauthorized`, `conflict`, `rejected`, `unavailable`, and +`unsupported`. + +Commands are non-interactive and set-shaped: `target use`, +`trusted-extensions enable`, and `rail width` state the intended result rather +than toggling hidden state. +`mode set` remains a compatibility alias for older automation. +Use `--stdin` for native `prompt` or `shape` text that should not appear in +process arguments. + +## Command reference + +| Command | Result | +| --- | --- | +| `flect` | Bounded live discovery and relevant next commands. | +| `flect app` | Open or focus the graphical application. | +| `flect status` | Inspect local-control connectivity. | +| `flect inspect [--fields ]` | Read validated workspace state. | +| `flect logs [--limit ] [--role ] [--operation ]` | Read bounded correlated evidence. | +| `flect watch [--after ]` | Wait for one newer workspace event. | +| `flect target ` | Select the visible protected workbench target. | +| `flect mode set ` | Compatibility alias for Shape or Use. | +| `flect prompt \|--stdin` | Ask App Agent to use the accepted product. | +| `flect shape \|--stdin` | Ask Shaper to prepare a proposal. | +| `flect cancel ` | Stop the selected running agent turn. | +| `flect action list` | List actions projected from the visible interface. | +| `flect action inspect ` | Inspect one projected action. | +| `flect action invoke ` | Invoke one currently available action. | +| `flect product invoke [--input ]` | Invoke one registered, granted product operation and return its bounded JSON result. | +| `flect permissions list` | Read the reactive, payload-free product permission lifecycle. | +| `flect permissions revoke ` | Revoke one visible decision; there is deliberately no grant command. | +| `flect interface inspect` | Read the visible interface document. | +| `flect interface schema` | Read the closed interface vocabulary. | +| `flect interface validate ` | Validate a Shaper sandbox file. | +| `flect interface propose ` | Submit a validated Shaper sandbox file. | +| `flect proposal accept\|reject` | Resolve the current preview. | +| `flect revision list\|rollback` | Inspect or roll back revision state. | +| `flect repository status` | Inspect canonical Git refs, candidate isolation, dirtiness, and conflicts. | +| `flect share list\|inspect []` | Inspect inactive review and retained share state. | +| `flect share open-url\|open-git\|reject\|export` | Route supported source and export operations through the protected sharing lifecycle. | +| `flect share checkpoint --at ...` | In Shaper's embedded Bash only, checkpoint bounded sandbox files onto that exact guarded user fork. | +| `flect share resolve --base --upstream --fork ...` | In Shaper's embedded Bash only, resolve every reviewed conflict path against the exact guarded lineage. | +| `flect model list` | List Pi-backed models. | +| `flect model select ` | Select a model explicitly. | +| `flect model favorite ` | Set a favorite explicitly. | +| `flect extensions list` | Discover portable packages visible to the authenticated role and accepted/candidate binding. | +| `flect extensions describe ` | Inspect bounded lifecycle, role, grant, version, digest, and provenance state without bundle source. | +| `flect extensions call [--input ]` | Invoke one enabled portable package through its role-owned QuickJS worker and capability broker. | +| `flect trusted-extensions ` | Set explicitly trusted outside Pi extension loading for one role. The older `extensions enable\|disable` spelling remains a compatibility alias. | +| `flect safe enter\|restore` | Enter recovery or restore last-known-good state. | +| `flect rail collapse\|expand\|width <340-520>` | Set protected rail presentation. | +| `flect control status\|disable` | Inspect or revoke paired outside control. | +| `flect context --host ` | Emit bounded ambient agent guidance. | +| `flect setup status` | Inspect the shell link and agent integrations. | +| `flect setup shell install\|remove` | Manage only `~/.local/bin/flect`. | +| `flect setup agent install\|remove ` | Manage one ownership-marked context integration. | +| `flect mcp` | Serve the compact MCP adapter over stdio. | + +Help visibility is not authorization. The trusted adapter supplies the caller +identity; flags, environment variables, aliases, functions, PATH entries, +workspace files, and model text cannot select or impersonate a role. + +Portable extension discovery is deferred: a model receives no complete package +or tool catalog on every turn. Inside App Agent or Shaper Bash, the embedded +command fixes the role and accepted/candidate binding from the authenticated +tool call. Native callers follow the visible workbench target and binding. +Calls fail closed unless exactly one enabled package record matches. Package +activation, grants, pins, forks, update resolution, and removal remain +protected controller decisions and are not exposed as agent grant commands. + +| Caller | Intended authority | +| --- | --- | +| Outside `flect` | Broad control only after the user enables Local control. It cannot enable control or grant product capabilities; it may inspect and revoke a visible product decision. | +| App Agent | Inspect the accepted product and permission lifecycle, list/inspect/invoke its visible actions, invoke registered product operations, and read bounded evidence. It cannot grant or revoke. | +| Shaper | Inspect schema/state and permission lifecycle, validate its own sandbox file, submit a preview proposal, checkpoint bounded files onto an exact retained share fork, and submit an exact reviewed conflict resolution. It cannot accept that proposal or change permissions. | +| Guardian | No Bash and no `flect` command. | + +## Browser-hosted Flect + +Flect remains usable in a normal browser. App Agent and Shaper run `flect` +inside their role-owned `just-bash` workspace, so no native executable or host +shell is required. The reserved command is registered under a hidden identity +after parsing, which prevents shadowing by an alias, function, PATH change, or +workspace executable. + +The browser command supports workspace inspection, logs, interface shaping, +and role-authorized actions through an in-process bounded command bus. It does +not install shell links or host integrations, read files outside the role's +disposable `/workspace`, expose the control bearer, or provide embedded event +watching. Its default output remains pipeable and redirectable: + +```sh +flect action list | grep available +flect product invoke projects.list --input '{"limit":2}' +flect interface schema | head +flect inspect > /workspace/state.toon +``` + +## Enable and revoke outside control + +Outside control is off by default. Open **Diagnostics** and choose **Enable +local control**. Only the protected Flect shell can create the grant. Enabling +it creates a fresh 256-bit bearer and atomically publishes an owner-private +descriptor (`0700` directory, `0600` file): + +- macOS: `~/Library/Application Support/Flect/control.json` +- Linux: `${XDG_STATE_HOME:-~/.local/state}/flect/control.json` +- tests or custom hosts: `FLECT_CONTROL_STATE_DIR` + +Use **Disable local control** or `flect control disable` to revoke it. Flect +removes the descriptor, invalidates the bearer, closes subscriptions, fails +queued requests, and clears visible client state without a restart. Never copy +the descriptor or put its bearer in a prompt, argument, log, fixture, or +artifact. The public clients discover it privately and never print it. + +Every mutation receives a unique command ID. The underlying JSON API also +supports `expectedSequence`; a stale optimistic request returns a typed +conflict instead of mutating newer state. + +## MCP + +The public executable selects MCP mode explicitly: + +```bash +flect mcp +``` + +Configure an MCP client with +`/Applications/Flect.app/Contents/MacOS/flect` and the single argument `mcp`. +The stdio server exposes four compact tools: + +- `flect_inspect` reads the current non-secret workspace snapshot; +- `flect_command` accepts the exact closed `FlectCommand` schema; +- `flect_wait` waits for a live event and returns fresh state; and +- `flect_logs` returns bounded, redacted operation evidence. + +The small tool count is deliberate. The command tool carries one schema union +instead of permanently loading hundreds of narrow model tools. + +## Optional coding-agent context + +Flect can install an ambient discovery hook for Codex, Claude Code, or +OpenCode: + +```bash +flect setup agent install codex +flect setup agent install claude +flect setup agent install opencode +flect setup status +``` + +Codex and Claude receive bounded output from `flect context --host ...` at +session start/resume/clear/compact. OpenCode receives the same context once per +session and again after compaction through a dependency-free local plugin. +Install and removal are explicit, idempotent, atomic, and ownership-marked; +unrelated hooks and settings are preserved. Invalid JSON or an occupied +OpenCode plugin path is reported as a conflict and is never overwritten. + +## JSON and SSE adapter + +The private descriptor points to an ephemeral server on `127.0.0.1` and a +random port. All public routes require its exact bearer: + +```text +GET /v1/status +GET /v1/instances +GET /v1/workspaces/:workspaceId +GET /v1/workspaces/:workspaceId/logs +GET /v1/workspaces/:workspaceId/events?after=:sequence +POST /v1/workspaces/:workspaceId/commands +``` + +SSE accepts `after` or `Last-Event-ID` and emits schema-decoded monotonic +events. Command bodies are capped at one MiB and decoded as strict +`FlectCommandEnvelope`s. Unknown fields or tags, wrong workspace IDs, +self-enablement, invalid bounds, and missing authorization fail closed. + +Use the public `flect` or MCP surfaces instead of hand-written bearer-bearing +HTTP. The lower-level client module exists for these adapters, not as a public +product surface. + +## Evidence and host boundary + +Flect retains at most 500 operation records and two MiB of encoded evidence in +memory. Records correlate workspace, command, operation, role, session, tool, +revision, and named outside-client IDs. Secret-shaped text is redacted. This +evidence helps users and agents debug the same reactive state; it is not a +second revision store or durable audit database. + +Browser development registers the Vite client with the origin-restricted Bun +runtime. The packaged WebView registers through private Effect RPC and Tauri +with the broker in `flect-runtime`. Both publish through the same controller. +A static page without its approved local runtime cannot expose outside +control. Flect does not bind to a LAN, pair remotely, run an unattended daemon, +or keep a grant after its owning runtime exits. + +See [`ARCHITECTURE.md`](../ARCHITECTURE.md) for implemented topology and +[`docs/trust-model.md`](trust-model.md) for the authority model. diff --git a/docs/performance.md b/docs/performance.md new file mode 100644 index 0000000..b85a8df --- /dev/null +++ b/docs/performance.md @@ -0,0 +1,63 @@ +# Flect performance and memory budgets + +This is the human decision record for `FQ-15.1` through `FQ-15.9`. The exact +release thresholds live once in +[`shared/performance-budgets.ts`](../shared/performance-budgets.ts); tests and +release measurements import or report those values rather than copying them. + +## Product budgets + +On the supported Apple Silicon development/release baseline, production Flect +must stay within these user-visible classes: + +- browser shell interactive in 2,000 ms; +- initial browser transfer at or below 1.5 MB and decoded initial resources at + or below 4 MB; +- model menu and composer input acknowledgement within 250 ms; +- warm Use–Shape switches within 350 ms; +- candidate rebuild within 3,000 ms; +- representative complete Markdown within 2,000 ms; +- browser cancellation acknowledgement within 500 ms; +- garbage-collected Chromium heap at or below 96 MiB, growing by no more than + 24 MiB across the gated repeated product cycles; +- packaged-macOS cold window within 5,000 ms, reopen within 1,000 ms, and first + model-ready state within 5,000 ms; +- packaged cancellation acknowledgement within 1,000 ms; and +- packaged steady RSS at or below 400 MiB, growing by no more than 64 MiB over + the release repetition run. + +The budgets include Flect orchestration and rendering, not provider inference +latency. Model latency is reported separately and never hidden by extending a +spinner budget. + +## Measurement contract + +Mandatory browser gates use a production Vite build, Playwright Chromium, one +worker, deterministic Flect fixtures, a 1180 × 781 desktop viewport unless the +scenario names another size, and no network other than the origin-restricted +test runtime. Resource measurements use browser `PerformanceResourceTiming`. +Heap gates use Chromium CDP after an explicit garbage collection before both +samples. + +Timing diagnostics contain only metric names, durations, byte counts, and +iteration counts. They must not contain prompts, model output, product data, +credentials, URLs, or tool content. Each mandatory interaction is exercised +at least once per gate. Warm target switching uses multiple samples and fails +on the worst sample; release dogfood records three native samples and uses the +median while also rejecting any sample above 150 percent of its budget. + +CI is intentionally tolerant of ordinary scheduler variation but not silent +regression: a mandatory browser metric fails at its exact threshold and prints +the bounded numeric diagnostic. Native measurements complement CI on the +supported macOS release host and are recorded in the dated verification +report. A different supported-device baseline requires a reviewed update to +the one TypeScript budget contract and this rationale. + +## Resource behavior + +Flect keeps the existing deterministic limits on sessions, queues, messages, +activities, output, frames, QuickJS work, auth logins, and auth events. +Performance instrumentation creates no telemetry, identifier, daemon, cache, +or second runtime state. A failed budget is fixed by reducing user-visible +work—such as lazy-loading a large optional surface—rather than by suppressing +the measurement or retaining more hidden work. diff --git a/docs/product-capabilities.md b/docs/product-capabilities.md new file mode 100644 index 0000000..88eebe3 --- /dev/null +++ b/docs/product-capabilities.md @@ -0,0 +1,197 @@ +# Product capability adoption + +Flect products expose named operations, never raw network access. A capsule or +App Agent may request `projects.list`; only trusted host composition knows that +this maps to a particular API, which credentials and policy apply, and which +bounded result is safe to return. + +This document owns the adopter mechanics. The authority model lives in +[`docs/trust-model.md`](trust-model.md), and the implemented topology lives in +[`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Effect services + +An adopter composes the transport services it needs at the trusted runtime +root: + +1. `ProductCapabilityDecisionStore` persists strict version-2 decisions through + protected `InterfaceStorage` and migrates matching legacy boolean state. +2. `ProductCapabilityBroker` owns lifecycle, exact request binding, scope, + lifetime, rate limits, atomic reservation, and revocation. +3. `ProductCapabilityRegistry` maps stable unary operation IDs to one + capability, independent product authorization, and bounded input/output + projections. +4. `ProductHttp` owns HTTPS origin, path prefix, method, header, byte, deadline, + transport, and private credential policy. +5. `ProductGraphql` pins an exact HTTPS endpoint, operation metadata, canonical + document digest, document bytes, variable/result bounds, deadline, and + private credential Effect behind a named operation. +6. `ProductEvents` owns a scoped connector, bounded backpressure queue, strict + event decoding, canonical decimal sequence, finite reconnect policy, cursor + resume, and cancellation. `ProductEventRegistry` binds that stream to the + same broker reservation and live revocation lifecycle. + +All are `Context.Service` contracts with named Layers. Capsules and agents see +only stable operation IDs plus bounded JSON. They never receive an origin, +GraphQL document, credential, cookie, socket, queue, or reconnect primitive. + +## Public SDK boundary + +The separately versioned [`@flect/product`](../packages/product/) package owns +the public contracts and transport-only Layers. Its supported exports are the +root module, `/contracts`, and `/host`; it has an exact Effect peer and no React, +Tauri, Node-runtime, or Flect application dependency. A built tarball contains +only declarations/ESM, package metadata, README, and license and is executed in +a clean consumer by `bun run product:package`. + +`defineProductIntegration` joins strict product metadata, capability manifests, +named unary/event closures, a selected inference owner, and one digest-verified +recommended capsule. `evaluateProductAdoption` compares that integration with +host facts, a prior product connection record, and separate user state. Its +ordered diagnostics cover ready, offline, product update, capability/extension +review, incompatible host/Flect, unavailable authentication, required/blocked +migration, preserved fork, and detach. `detachProduct` clears the product +connection only; Flect remains responsible for any protected grant revocation +and never deletes the user's workspace, Git ref, capsule, or export. + +The package deliberately does not export the decision store, capability broker, +protected permission UI, operation journal, workspace storage, capsule +activation, or safe mode. Those remain Flect-owned. The internal runtime bridge +consumes the same public operation/event types, so SDK and application contracts +cannot drift. + +## Immutable request binding + +Each decoded capsule becomes a `ProductCapabilityRequestContext` containing: + +- capsule scope ID; +- Flect workspace ID; +- SHA-256 of the exact `.flect` archive; +- capsule provenance revision; and +- the exact requested capability IDs and required flags. + +A decision matches all of that binding. Updating archive bytes, changing the +declared request, or replacing the capsule cannot silently widen an older +decision. Two apps requesting the same capability remain independent. + +The host registers a strict `ProductCapabilityManifest` for every capability: +operation IDs, resource IDs, data classes, permitted confirmation policies, and +optional maximum duration and rate. Capsule content and model output cannot +register or expand a manifest. + +## Lifecycle and user choices + +Protected review projects one lifecycle value per request: + +- `requested`: declared by the capsule and awaiting a decision; +- `available`: registered by the host but not requested by this capsule; +- `granted`: currently usable within the approved scope; +- `denied`: explicitly denied by the protected user; +- `expired`: its duration elapsed or its one-use allowance was consumed; or +- `revoked`: explicitly revoked. + +Availability is separate: an unavailable request stays visible and blocks Keep +when required. The protected component shows operation/resource/data scope, +lifetime, rate, and decision identity without showing payloads or credentials. + +An allow decision chooses one of the policies permitted by the manifest: + +- `once`: in memory and atomically consumed by the next attempted operation; +- `session`: in memory until this Flect runtime ends; +- `workspace`: durable and valid only for the matching workspace; or +- `persistent`: durable for the exact capsule request binding. + +Denial is durable. Revocation updates the existing decision and immediately +blocks later capsule and App Agent calls. A failed durable write leaves live +authority unchanged. + +Only the protected user source can create a decision. Paired outside control may +run `flect permissions list` and revoke a visible decision with +`flect permissions revoke `; App Agent and Shaper may inspect but +cannot grant or revoke through their embedded command. There is deliberately no +agent-facing grant command. + +## Invocation order + +For each operation, Flect: + +1. verifies the current accepted/candidate capsule binding and registered + operation; +2. atomically reserves the matching decision, consuming one-use and rate + allowance before product work; +3. runs the product's independent authorization projection; +4. validates its exact operation/resource/data scope against the reservation; +5. invokes the bounded unary adapter or opens the bounded event scope; and +6. decodes the bounded result. + +User approval never overrides product authorization. Product denial and scope +widening stop before transport. Every attempted invocation adds structured, +payload-free capability metadata to the bounded operation journal: capsule +binding, decision, policy, operation, revision, and stable result reason. + +## Host composition + +Define `ProductCapabilityManifest` and operation definitions through +`@flect/product`, then select only the host adapters the product needs. Flect +composes the private decision store, broker, and registry at the protected +runtime root. The executable [`examples/product-sdk/`](../examples/product-sdk/) +directory contains independent offline, browser-direct, and authenticated +brokered integrations; the older combined +[`examples/product-adapter/`](../examples/product-adapter/) remains an +end-to-end adapter exercise. + +For each operation: + +- use a stable dotted operation ID such as `projects.list`; +- validate JSON input before constructing a transport request; +- keep HTTP paths, GraphQL endpoints/documents, and event connectors inside + trusted host code; +- return an exact `AuthorizedProductOperation` resource/data projection; +- project the bounded response into JSON without secrets or raw transport + errors; and +- keep registration and credentials in host-owned code. + +Credential headers come from the host's Effect callback. They are inserted only +after caller input and headers are checked. Resolver defects are sanitized, and +credentials never appear in a response, permission projection, receipt, +journal, workspace snapshot, capsule, Pi prompt, interface document, revision, +or sandbox. A browser product should use its own protected same-origin session +or host endpoint; it must not embed a long-lived bearer secret in client code. + +Fixed GraphQL registration validates the exact document SHA-256 and operation +kind/name before the Layer becomes available. Invocation sends only +`{ operationName, query, variables }` to that registration and treats private +GraphQL errors as `product-denied`. + +Event connectors emit through the supplied Effect callback only. The queue +applies backpressure instead of dropping or growing. Each event is decoded and +byte-bounded before enqueue; duplicate or regressing decimal sequence values +fail closed. Reconnect passes only the last accepted cursor and stops at the +declared attempt count. Caller cancellation, component disposal, grant expiry, +or revocation aborts the same scoped connector. + +## Current platform boundary + +The stock distribution intentionally registers no product operation. The +browser and current desktop WebView execute the same schema-defined HTTP, +GraphQL, and event contracts. Browser-direct HTTP/GraphQL remains subject to +CORS, omits ambient browser credentials, and rejects redirects. OS-keystore +credentials or CORS-independent transport require a protected native adapter +behind the same contract; that adapter is not yet included. Database adapters, +arbitrary transport plugins, SDK registry publication, and stable 1.0 +compatibility remain later work. + +## Verification + +Executable public contracts live in `packages/product/src`; the old `shared/` +and transport import paths are compatibility re-exports rather than duplicate +implementations. Store, broker, transport, authorization, receipt, +protected-UI, persistence, cancellation, resume, and production-Chromium proofs +live beside their implementations, in +`tests/e2e/product-capability.spec.ts`, and in +`tests/e2e/product-adapter.spec.ts`, `tests/e2e/product-adoption.spec.ts`, and +`scripts/product-sdk-package.test.ts`. Run `bun run check:all` and +`bun run product:package` before distributing a product integration. Adapter +evidence is recorded in +[`docs/verification/2026-08-03-product-adapter-verification.md`](verification/2026-08-03-product-adapter-verification.md); +SDK evidence is recorded in the dated verification report beside it. diff --git a/docs/product-quality.md b/docs/product-quality.md new file mode 100644 index 0000000..90d58a1 --- /dev/null +++ b/docs/product-quality.md @@ -0,0 +1,547 @@ +# Flect product-quality contract + +This document is the canonical definition of the outcomes that matter to Flect +users and the evidence required to prove them. It defines the destination. It +does not claim that the current release implements every outcome. + +Current observations belong in dated reports under `docs/verification/`. +Executable work belongs in GitHub issues, and live priority and status belong +in the dedicated Flect organization project. + +## Constituencies + +- **User:** a person using a Flect application or personal workspace. +- **Maker:** a person shaping, importing, testing, or maintaining an interface. +- **Extension author:** a person publishing a component, capsule, workflow, or + role-scoped extension. +- **Product team:** an organization adopting Flect for a product or service. + +## Maturity + +A dated evaluation assigns every criterion exactly one state: + +- `unimplemented`: no observable implementation exists; +- `partial`: some behavior exists but the complete promise does not; +- `implemented`: behavior exists but required proof is missing or stale; +- `proven`: all required evidence is current and passes; +- `regressed`: a previously proven outcome currently fails. + +Source inspection, documentation, compilation, screenshots without behavioral +checks, or an agent assertion cannot establish `proven` alone. + +## Proof classes + +- **Contract:** Effect unit or integration tests through exported contracts and + typed boundaries. +- **Browser:** real Chromium behavior against a production browser build. +- **Native:** packaged supported-host behavior through public UI and AXI + surfaces. +- **Security:** adversarial capability, sandbox, credential, or recovery tests. +- **Accessibility:** automated checks plus keyboard and assistive-technology + walkthroughs where automation is insufficient. +- **Experience:** bounded dogfooding or usability evidence for behavior that + depends on human comprehension, perceived latency, or visual quality. +- **Release:** clean-install, update, artifact, checksum, compatibility, and + reproducibility evidence. +- **Adoption:** an integration fixture or real adopter using only public + contracts. + +Evidence names the date, revision, host, exact command or workflow, result, and +applicable limitations. Sensitive values remain redacted. + +## Release interpretation + +- **Every release:** every behavior currently advertised by that release must + be proven. Security, credential isolation, deterministic recovery, data + ownership, and honest boundaries are non-waivable. +- **Public beta:** additionally requires the protected Shape–Use–recover loop, + trustworthy installation, responsive operation, and explicit compatibility + boundaries. +- **Stable:** additionally requires every advertised portability, extension, + adoption, collaboration, and supported-host promise to be proven. + +An outcome identifier remains stable when its wording improves. Retire an +identifier instead of silently reusing it for a different promise. + +## FQ-01 Installation and first run + +**Constituencies:** User, Maker, Product team +**Gate:** Every advertised host; complete pillar for public beta +**Primary proof:** Browser, Native, Release, Experience + +- **FQ-01.1:** Native installation is trustworthy and simple. +- **FQ-01.2:** Browser Flect works without requiring a desktop application. +- **FQ-01.3:** Provider authentication can be completed inside Flect without a + terminal prerequisite. +- **FQ-01.4:** Existing approved Pi authentication is detected without copying + credentials into Flect state. +- **FQ-01.5:** Users can determine where models, workspaces, and credentials + live. +- **FQ-01.6:** Updating and uninstalling have documented, predictable behavior. +- **FQ-01.7:** A blank installation is immediately useful without an existing + product backend. + +## FQ-02 Immediate comprehension + +**Constituencies:** User, Maker +**Gate:** Public beta +**Primary proof:** Browser, Accessibility, Experience + +- **FQ-02.1:** A first-time user can understand what Flect is and begin a + meaningful task without reading external documentation. +- **FQ-02.2:** Canvas, composer, candidate preview, and accepted interface are + distinguishable. +- **FQ-02.3:** The active **Use** or **Shape** target is visible before send. +- **FQ-02.4:** The user knows whether an action changes a candidate or uses the + accepted product. +- **FQ-02.5:** Model, workspace, capability, and permission state are + discoverable without dominating the work surface. +- **FQ-02.6:** Ordinary use does not require understanding an IDE, agent + harness, runtime, or infrastructure terminology. + +## FQ-03 Conversational shaping + +**Constituencies:** User, Maker +**Gate:** Public beta +**Primary proof:** Contract, Browser, Experience + +- **FQ-03.1:** A user can create a useful interface from a blank workspace by + describing the desired outcome. +- **FQ-03.2:** A user can make a precise change without unrelated interface + changes. +- **FQ-03.3:** Questions are answered without being mistaken for edit + instructions. +- **FQ-03.4:** Shaper can explain its intended or completed change in user + language. +- **FQ-03.5:** Follow-up instructions retain the correct candidate, revision, + and conversation context. +- **FQ-03.6:** Images, files, examples, and supported source projects can supply + shaping context without bypassing validation. +- **FQ-03.7:** Direct manipulation and conversational changes converge on the + same revision model. +- **FQ-03.8:** Explicitly approved Shaper extensions may extend authoring + behavior without extending their own authority. + +## FQ-04 Fast Shape–Use testing + +**Constituencies:** User, Maker, Extension author +**Gate:** Public beta +**Primary proof:** Contract, Browser, Experience, Performance evidence + +- **FQ-04.1:** Edit provides a live candidate product preview. +- **FQ-04.2:** Preview App Agent and Shaper remain warm during an editing + session. +- **FQ-04.3:** Switching **Use ↔ Shape** is immediate and does not require + repeated authorization within the active editing session. +- **FQ-04.4:** Each target preserves its draft, conversation position, and + relevant context. +- **FQ-04.5:** Candidate product extensions can be exercised before acceptance. +- **FQ-04.6:** A request such as “fix what just happened” can attach the bounded + failure, operation trace, candidate revision, and relevant UI state to + Shaper. +- **FQ-04.7:** Automatic target selection is visible and instantly + overridable. +- **FQ-04.8:** An incorrect target prediction cannot accept, publish, or perform + an irreversible action. + +## FQ-05 Product usage + +**Constituencies:** User, Product team +**Gate:** Every advertised product capability; complete pillar for public beta +**Primary proof:** Contract, Browser, Adoption, Security + +- **FQ-05.1:** The accepted interface remains useful without invoking a model. +- **FQ-05.2:** App Agent understands only the product capabilities and bounded + public context exposed to it. +- **FQ-05.3:** App Agent can answer questions and perform approved product + actions through typed capabilities. +- **FQ-05.4:** Tool execution, status, and results appear coherently in the + product experience. +- **FQ-05.5:** A product adopting Flect does not need to ship a separate + assistant surface. +- **FQ-05.6:** Users may select model access they control where product policy + permits. +- **FQ-05.7:** Product authentication and authorization remain authoritative + regardless of model or interface customization. + +## FQ-06 Preview, acceptance, and recovery + +**Constituencies:** All +**Gate:** Every release +**Primary proof:** Contract, Browser, Native, Security + +- **FQ-06.1:** Every unaccepted interface change is visibly a candidate. +- **FQ-06.2:** Users can inspect the meaningful change before acceptance. +- **FQ-06.3:** Keep and Reject are deterministic protected actions. +- **FQ-06.4:** History, comparison, undo, and rollback preserve attribution and + accepted-state integrity. +- **FQ-06.5:** Accepted revisions record sufficient attribution for explanation + and recovery. +- **FQ-06.6:** Last-known-good recovery works without a model provider. +- **FQ-06.7:** Shaped UI and extensions cannot remove or replace safe mode. +- **FQ-06.8:** Broken extensions can be disabled through the protected shell. +- **FQ-06.9:** A crash during shaping cannot destroy or silently replace the + accepted product. + +## FQ-07 Git-backed ownership + +**Constituencies:** Maker, Extension author, Product team +**Gate:** Stable when Git-backed authoring is advertised +**Primary proof:** Contract, Browser, Native, Release + +- **FQ-07.1:** A canonical Flect workspace is a real Git repository. +- **FQ-07.2:** Browser and desktop hosts provide Git behavior without requiring + system Git. +- **FQ-07.3:** Shaper uses ordinary Git concepts rather than proprietary shadow + history. +- **FQ-07.4:** Candidate changes are isolated from the accepted revision. +- **FQ-07.5:** Users can inspect, export, and continue the repository outside + Flect. +- **FQ-07.6:** Conflicts and interrupted operations have understandable, + recoverable states. +- **FQ-07.7:** Protected Flect metadata does not take ownership of or corrupt + the user's repository. + +## FQ-08 Import and export + +**Constituencies:** Maker, Product team +**Gate:** Stable for every advertised framework +**Primary proof:** Browser, Native, Release, Adoption + +- **FQ-08.1:** Advertised React, Vue, Svelte, HTML, and CSS project classes have + versioned import compatibility. +- **FQ-08.2:** Unsupported dependencies are reported before destructive work. +- **FQ-08.3:** Supported assets, routing, styling, and state survive import. +- **FQ-08.4:** Imported source remains recognizable and maintainable. +- **FQ-08.5:** Users can export ordinary source and Git history. +- **FQ-08.6:** Exported projects are not locked to a proprietary Flect service. +- **FQ-08.7:** Failed import leaves the source project and accepted workspace + recoverable. + +## FQ-09 Portable `.flect` applications + +**Constituencies:** User, Maker, Extension author, Product team +**Gate:** Stable when capsules are advertised +**Primary proof:** Contract, Browser, Native, Release, Security + +- **FQ-09.1:** Capsules install without a proprietary hosted service. +- **FQ-09.2:** Installed capsules run without their original build tool or + package registry. +- **FQ-09.3:** Capsule contents, provenance, permissions, and compatibility are + inspectable before activation. +- **FQ-09.4:** A capsule can carry UI, public agent instructions, and + role-scoped extensions under one versioned contract. +- **FQ-09.5:** Users can fork and personalize an installed capsule. +- **FQ-09.6:** Upstream updates do not silently overwrite personal changes. +- **FQ-09.7:** Browser and supported desktop hosts agree on the portable + capsule contract. + +## FQ-10 Extensions + +**Constituencies:** User, Maker, Extension author, Product team +**Gate:** Every release that loads extensions +**Primary proof:** Contract, Security, Browser, Native, Release + +- **FQ-10.1:** Extensions are explicitly enabled per workspace and target role. +- **FQ-10.2:** App Agent and Shaper extensions remain isolated from each + other's private session and authority. +- **FQ-10.3:** Permissions are understandable before activation. +- **FQ-10.4:** Extensions cannot access provider credentials or unrelated + product data. +- **FQ-10.5:** Users can inspect, disable, update, pin, fork, and remove + extensions. +- **FQ-10.6:** A failed extension cannot prevent protected startup or recovery. +- **FQ-10.7:** Components and extensions can be shared without becoming trusted + merely because of publisher or signature. +- **FQ-10.8:** Compatibility and migration failures explain a safe recovery + path. + +## FQ-11 Sandbox and capabilities + +**Constituencies:** All +**Gate:** Every release +**Primary proof:** Contract, Security, Browser, Native + +- **FQ-11.1:** Generated UI and logic cannot escape their declared execution + realm through supported interfaces. +- **FQ-11.2:** Agent Bash is useful inside its role sandbox without exposing a + host shell or native process authority. +- **FQ-11.3:** Browser workspaces use a durable browser-native filesystem where + canonical persistence is required. +- **FQ-11.4:** Filesystem, network, SQL, product, model, and native effects cross + explicit typed capabilities. +- **FQ-11.5:** Capabilities are least-privilege, inspectable, attributable, and + revocable. +- **FQ-11.6:** Capsules, shaped UI, and extensions cannot grant themselves + authority. +- **FQ-11.7:** Credentials never enter prompts, Git, screenshots, logs, or + extension-visible state. +- **FQ-11.8:** CPU, memory, storage, output, and execution limits prevent + unbounded resource use. +- **FQ-11.9:** Security claims distinguish browser or worker isolation from an + operating-system sandbox. + +## FQ-12 Models and authentication + +**Constituencies:** User, Maker, Product team +**Gate:** Public beta +**Primary proof:** Contract, Browser, Native, Security, Experience + +- **FQ-12.1:** Provider login and recovery are available inside Flect. +- **FQ-12.2:** Users can discover and select authenticated compatible models. +- **FQ-12.3:** Missing, expired, or unsupported authentication produces a clear + recovery action. +- **FQ-12.4:** Switching models preserves valid conversation and revision + state. +- **FQ-12.5:** Reasoning effort and other exposed controls use understandable + product language. +- **FQ-12.6:** Provider credentials remain outside shaped applications and + role extensions. +- **FQ-12.7:** Products may provide inference but are not required to do so. +- **FQ-12.8:** Deterministic recovery remains available without any model. + +## FQ-13 Agent transparency + +**Constituencies:** User, Maker +**Gate:** Public beta +**Primary proof:** Browser, Accessibility, Experience + +- **FQ-13.1:** The active agent and its authority are visible before send. +- **FQ-13.2:** Tool calls are distinguishable from assistant prose. +- **FQ-13.3:** Queued, running, completed, failed, and cancelled states are + explicit. +- **FQ-13.4:** Duration and bounded useful output are inspectable. +- **FQ-13.5:** Errors name the failed operation and a safe next action. +- **FQ-13.6:** Ordinary use is not overwhelmed by infrastructure activity. +- **FQ-13.7:** Markdown, code, tables, disclosures, and links render safely and + legibly. +- **FQ-13.8:** Streaming and activity updates do not steal scroll position or + focus. + +## FQ-14 External agent control + +**Constituencies:** Maker, Extension author, Product team +**Gate:** Every release advertising outside control +**Primary proof:** Contract, Browser, Native, Security + +- **FQ-14.1:** Public AXI commands can inspect and operate the same live + workspace as the UI. +- **FQ-14.2:** Embedded role Bash exposes the applicable AXI language without + a separate model-visible tool catalog. +- **FQ-14.3:** JSON, SSE, MCP, native, and browser adapters converge on the same + reactive state and Effect controller. +- **FQ-14.4:** Authorized external actions appear immediately in the visible + interface. +- **FQ-14.5:** External agents can inspect bounded evidence, cancel work, and + debug through public surfaces. +- **FQ-14.6:** External control cannot bypass validation, permissions, + acceptance, or recovery. +- **FQ-14.7:** Outside control is disabled by default and immediately + revocable. +- **FQ-14.8:** Browser Flect offers a portable in-app equivalent without + requiring an additional installed binary. + +## FQ-15 Performance and responsiveness + +**Constituencies:** User, Maker +**Gate:** Public beta +**Primary proof:** Browser, Native, Experience, measured performance evidence + +- **FQ-15.1:** Supported-host startup meets a documented interactive budget. +- **FQ-15.2:** Warm **Use ↔ Shape** targeting meets a documented switch + budget. +- **FQ-15.3:** Typing remains responsive while agents, builds, and previews + run. +- **FQ-15.4:** Streaming remains visually smooth under representative output. +- **FQ-15.5:** Long conversations and bounded tool output remain responsive. +- **FQ-15.6:** Preview rebuilds avoid unnecessary full-workspace work. +- **FQ-15.7:** Background agents and extension hosts remain within explicit + resource budgets. +- **FQ-15.8:** Cancellation gives prompt feedback and terminates owned work. +- **FQ-15.9:** Repeated long-running sessions do not exhibit unbounded memory + growth. + +## FQ-16 Reliability + +**Constituencies:** All +**Gate:** Public beta +**Primary proof:** Contract, Browser, Native, Security + +- **FQ-16.1:** Refreshing or restarting preserves accepted work. +- **FQ-16.2:** Applicable drafts, conversation state, and candidate state + recover consistently. +- **FQ-16.3:** Network interruption does not corrupt revision or conversation + state. +- **FQ-16.4:** Stale candidates cannot overwrite newer accepted work. +- **FQ-16.5:** Concurrent operations are rejected, serialized, or reconciled + explicitly. +- **FQ-16.6:** Runtime and shell version incompatibility is detected before + unsafe operation. +- **FQ-16.7:** Browser-storage exhaustion has a deterministic recovery path. +- **FQ-16.8:** Partial updates and process crashes repair forward or return to + last-known-good state. + +## FQ-17 Privacy and data ownership + +**Constituencies:** All +**Gate:** Every release +**Primary proof:** Contract, Security, Release + +- **FQ-17.1:** Workspaces and personal customizations belong to the user. +- **FQ-17.2:** Users can determine what remains local, is synchronized, or is + sent to a selected model. +- **FQ-17.3:** Telemetry is absent or explicitly disclosed and controlled. +- **FQ-17.4:** Product connections cannot take ownership of personal + customizations. +- **FQ-17.5:** User data can be exported and deleted through documented paths. +- **FQ-17.6:** Credentials remain in the approved credential owner. +- **FQ-17.7:** Diagnostics and operation evidence are bounded and redacted. +- **FQ-17.8:** Local-only and offline-capable workflows remain possible where + their declared capabilities permit. + +## FQ-18 Accessibility + +**Constituencies:** User, Maker +**Gate:** Every user-visible release +**Primary proof:** Accessibility, Browser, Native + +- **FQ-18.1:** The complete protected shell is keyboard operable. +- **FQ-18.2:** Focus is visible, ordered, and restored across sheets, menus, + role changes, and recovery. +- **FQ-18.3:** Assistive technology receives meaningful agent, tool, validation, + and status changes. +- **FQ-18.4:** Color is never the only state signal. +- **FQ-18.5:** Text, placeholders, icons, and controls meet WCAG 2.2 AA contrast + requirements. +- **FQ-18.6:** Reduced-motion preferences preserve complete behavior. +- **FQ-18.7:** Zoom, text scaling, narrow layouts, and reflow remain usable. +- **FQ-18.8:** Touch targets meet the applicable supported-platform guidance. +- **FQ-18.9:** Safe mode remains accessible when shaped UI is inaccessible. + +## FQ-19 Visual and interaction quality + +**Constituencies:** User, Maker, Product team +**Gate:** Public beta +**Primary proof:** Browser, Native, Accessibility, Experience + +- **FQ-19.1:** The protected shell feels calm, professional, coherent, and + first-party on each supported host. +- **FQ-19.2:** The user's work remains more prominent than agent and runtime + infrastructure. +- **FQ-19.3:** Controls use familiar product affordances and complete + interaction states. +- **FQ-19.4:** Information density is space-efficient without becoming cramped. +- **FQ-19.5:** Motion communicates state and does not delay work. +- **FQ-19.6:** Responsive layouts never duplicate, hide, or trap access to the + protected composer and recovery controls. +- **FQ-19.7:** Supported light and dark appearances are deliberate rather than + mechanically inverted. +- **FQ-19.8:** Product interfaces may establish their own design language + without weakening the protected shell. +- **FQ-19.9:** Customization of Flect's own composer and rail has a deterministic + protected fallback. + +## FQ-20 Cross-platform behavior + +**Constituencies:** User, Maker, Product team +**Gate:** Every advertised host +**Primary proof:** Browser, Native, Release, Adoption + +- **FQ-20.1:** The browser is a first-class Flect host rather than a reduced + remote viewer. +- **FQ-20.2:** macOS uses native host behavior where it materially improves the + experience. +- **FQ-20.3:** Shared product behavior comes from one typed application core. +- **FQ-20.4:** Swift, Kotlin, Rust, or another native adapter enters through + narrow reviewed capabilities. +- **FQ-20.5:** Unsupported platform capabilities degrade explicitly. +- **FQ-20.6:** Every advertised Windows, Linux, mobile, and desktop host has a + current compatibility contract. +- **FQ-20.7:** Workspaces and capsules remain portable across compatible hosts. + +## FQ-21 Product-team adoption + +**Constituencies:** Product team, User, Extension author +**Gate:** Stable when product adoption is advertised +**Primary proof:** Adoption, Contract, Security, Release + +- **FQ-21.1:** Products expose approved APIs and capabilities without replacing + the Flect shell. +- **FQ-21.2:** A team can ship an excellent recommended default experience. +- **FQ-21.3:** Users can personalize locally without changing the product for + other users. +- **FQ-21.4:** Product policy remains authoritative over available + capabilities. +- **FQ-21.5:** Product branding cannot obscure or weaken protected recovery. +- **FQ-21.6:** Products can adopt Flect incrementally through public contracts. +- **FQ-21.7:** Teams can test, version, distribute, and update their Flect + experience. +- **FQ-21.8:** Stable compatibility and migration contracts protect adopters + across Flect upgrades. + +## FQ-22 Open-source and ecosystem trust + +**Constituencies:** All +**Gate:** Every public release +**Primary proof:** Release, Contract, Adoption + +- **FQ-22.1:** The protected core remains small enough to inspect and maintain. +- **FQ-22.2:** Published builds are reproducible from the released source and + declared toolchain. +- **FQ-22.3:** Releases are signed or checksummed, attributable, and documented. +- **FQ-22.4:** Capsule, extension, capability, and host contracts are versioned. +- **FQ-22.5:** Breaking changes provide explicit migrations or compatibility + failures. +- **FQ-22.6:** Documentation distinguishes current observable behavior from + future design. +- **FQ-22.7:** Fundamental local and browser workflows require no proprietary + Flect service. +- **FQ-22.8:** Licensing and governance permit understandable community and + commercial adoption. + +## FQ-23 Sharing and collaboration + +**Constituencies:** User, Maker, Extension author, Product team +**Gate:** Stable when sharing is advertised +**Primary proof:** Contract, Security, Release, Adoption + +- **FQ-23.1:** Experiences, components, themes, workflows, and extensions can be + shared independently where their contracts permit. +- **FQ-23.2:** Recipients can inspect provenance, compatibility, and requested + authority before activation. +- **FQ-23.3:** Personal changes survive compatible upstream updates. +- **FQ-23.4:** Teams can review proposed interface and capability changes like + code. +- **FQ-23.5:** Forking preserves attribution without creating authority. +- **FQ-23.6:** Private sharing does not require public publication. +- **FQ-23.7:** Untrusted shared artifacts open in a safe inspectable state before + approval. + +## FQ-24 Honest boundaries + +**Constituencies:** All +**Gate:** Every release +**Primary proof:** Contract, Browser, Native, Release, Experience + +- **FQ-24.1:** Flect clearly reports which source, dependency, and execution + classes it cannot run. +- **FQ-24.2:** Browser compatibility problems are reported before destructive + build or import work. +- **FQ-24.3:** CORS, backend, authorization, and product-data limitations are + explained in user language. +- **FQ-24.4:** Web UI is not falsely represented as a native platform control. +- **FQ-24.5:** Flect does not claim to replace a product backend, database, + business logic, or authorization model. +- **FQ-24.6:** Unsupported packages and capabilities fail with actionable + alternatives where available. +- **FQ-24.7:** Isolation claims name their actual browser, worker, process, or OS + boundary. +- **FQ-24.8:** Marketing, release notes, screenshots, and documentation do not + advertise behavior absent from the shipped release. + +## Ultimate user outcome + +A Flect user can install it, understand it, shape something useful, test it +immediately, trust what it does, recover from mistakes, own and export the +result, and use it anywhere Flect claims to support—without becoming dependent +on Flect or surrendering control of their data. diff --git a/docs/recovery.md b/docs/recovery.md new file mode 100644 index 0000000..a2cfe9f --- /dev/null +++ b/docs/recovery.md @@ -0,0 +1,85 @@ +# Session continuity and recovery + +Flect keeps interface history and session continuity deliberately separate. +The shaping kernel and revision repository are the only authority for the +accepted interface, last-known-good revision, and validated candidate. The +role-continuity record is a bounded convenience projection for unsent drafts +and completed conversations; deleting it never deletes accepted interface +work. + +## What survives a refresh or restart + +- accepted App Agent messages; +- candidate Preview App Agent messages only while the exact candidate revision + still exists; +- Shaper messages; +- separate accepted-Use, candidate-Use, and Shape drafts; and +- the canonical accepted, last-known-good, and candidate interface revisions. + +An in-flight user message may be retained, but a partial assistant stream is +not. Active sessions, tool calls, raw tool output, shell processes, and partial +model output are cancelled and recreated as needed. Provider credentials, Pi +session internals, authentication events, protected-entry references, outside +control grants, connected-client state, and provider/model payloads never +enter the continuity record. + +The continuity record is strict Effect Schema data under +`flect.role-continuity.v1`. It is limited to 512 KiB, 200 messages per role, +16,000 retained characters per projected message, a 100,000-character Use +draft, and a 4,000-character Shape draft. Tool activities are not persisted. + +## Conflicts and storage failures + +Every successful write increments a generation. Flect serializes same-origin +writes with Web Locks where available, rereads under that lock, and rejects a +stale generation instead of merging tabs or replacing newer state. One +`localStorage.setItem` atomically replaces the complete encoded record; quota +or host failures leave the prior record intact. + +Malformed, oversized, unknown-version, and incompatible records are rejected +before runtime mutation. Flect does not opportunistically rewrite them. A +candidate mismatch discards only candidate-use continuity. Accepted App Agent +and Shaper continuity remain separate. + +## Protected recovery + +Open `/?safe=1` or choose **Safe mode** from the protected composer. Safe mode +does not hydrate stored drafts or conversations into the UI. It can show only +bounded generation/revision metadata and a closed recovery reason. + +The `?safe=1` URL is a one-shot protected launcher, not a durable repository +mode. A successful **Restore interface** writes the recovered snapshot through +the guarded Git repository, removes only the `safe=1` query parameter while +preserving other URL state, and remains restored after reload or process +restart. A failed restore keeps the launcher and prior protected refs intact. +If an activation receipt is missing or disagrees with its protected refs, +Flect reconstructs bounded recovery metadata from `flect/last-known-good`, +enters safe mode, and requires the same explicit restore before realigning the +accepted and last-known-good refs. + +Without Pi or a model, safe mode can: + +- restore the last-known-good interface; +- export a valid, schema-decoded continuity record; +- discard session continuity while preserving interface revisions; +- retry strict decoding after the underlying storage condition changes; and +- keep the compiled protected shell available when customized state fails. + +Export is disabled for an invalid continuity record because Flect does not +render or repackage undecoded stored content. Discard remains available. + +## Current proof boundary + +Production Chromium tests cover completed App/Shaper conversation restoration, +three isolated drafts, candidate refresh, rejection cleanup, active Shaper +interruption, safe-mode non-hydration, valid export, isolated discard, a real +same-origin stale second tab, injected browser quota exhaustion, protected Git +commit advancement on safe-mode entry/restore, and restoration after reload. +Unit tests additionally inject malformed, incompatible, oversized, stale, +rejected, missing-receipt, legacy-ref, and receipt/ref-mismatch states. + +The packaged macOS proof in +[`docs/verification/2026-08-02-permission-lifecycle-verification.md`](verification/2026-08-02-permission-lifecycle-verification.md) +repairs a real legacy receipt/ref mismatch, advances the protected commit, and +survives a complete app quit and relaunch. Future interrupted-migration proof +and complete private-state canary evidence remain tracked in GitHub issue #21. diff --git a/docs/sharing.md b/docs/sharing.md new file mode 100644 index 0000000..011b6bb --- /dev/null +++ b/docs/sharing.md @@ -0,0 +1,99 @@ +# Sharing and personal forks + +Flect can review portable `.flect-share` archives from a local file, a +credential-free HTTPS URL, an exact public Git descriptor commit, or a named +private adapter supplied by a trusted host. Every source converges on the same +inactive review. Opening it never replaces the accepted app. + +A share may contain experiences, components, themes, workflows, and portable +extensions. Flect checks the archive, manifest, source paths, compatibility, +provenance, signature state, dependencies, migrations, and requested authority +before any artifact reaches its existing preview or activation boundary. A +signature identifies a claim; it never grants a capability. + +## Retain, personalize, and update + +Retaining a source imports only its exact reachable Git objects and creates +opaque namespaced `base`, `upstream`, and user-owned `fork` refs in Flect's +OPFS-backed repository. It imports no credentials or grants. The accepted app +continues to run until the user explicitly previews and keeps selected parts. + +Shaper can personalize a retained fork from its browser-portable Bash sandbox: + +```sh +flect share inspect dev.flect.weather +flect share checkpoint dev.flect.weather \ + --at \ + --write components/weather/personal-note.md ./personal-note.md \ + --message 'Personalize weather workspace' +``` + +`--write` and `--remove` may repeat. Paths must remain inside the shared source, +the expected commit provides optimistic concurrency, and the command advances +only that share's guarded fork. App Agent cannot run it. The checkpoint does +not activate UI, accept a proposal, import authority, or modify recovery. + +When an exact upstream update arrives, Flect fast-forwards an untouched fork or +computes a bounded three-way merge. A clean personalized merge is a real Git +commit with the personal fork and exact upstream revision as its two parents; +the Worker verifies both the parents and the complete resolved tree before the +candidate ref moves. The user still previews and keeps it through the normal +protected decision. + +Conflicting updates remain inactive and Keep stays disabled. Flect preserves +the base, upstream, fork, and bounded conflict paths. The review offers three +explicit choices: continue with the retained fork, open the conflict in Shape, +or reject the update. Continue still creates an inactive candidate for Preview +and Keep; it does not silently discard the update. + +Open in Shape materializes only the recorded conflict versions below +`/workspace/.flect/share-conflicts/`. Shaper writes exactly one result +or removal for every listed path and submits the exact guarded refs: + +```sh +flect share resolve dev.flect.weather \ + --base \ + --upstream \ + --fork \ + --write components/weather/index.ts \ + /workspace/.flect/share-conflicts/dev.flect.weather/resolved/components/weather/index.ts \ + --message 'Resolve weather conflict' +``` + +Flect's bounded byte-level three-way comparison is the portable conflict +policy. It does not depend on engine-specific merge heuristics: wasm-git may +auto-merge a source file that another Git build reports as conflicted. Exact +ref guards, the reviewed conflict set, the complete resolved tree, and the two +verified commit parents remain mandatory in either case. Restart restores the +inactive resolved candidate. Flect never asks a model to silently choose or +activate a merge result. + +## Export, remove, and delete + +Export emits a deterministic `.flect-share` containing the exact guarded fork +or prepared candidate history. Derived exports clear publisher signatures and +bind provenance to the exported commit. The embedded repository opens in +ordinary Git and is checked by Flect's production workflow with `git fsck`. + +Removing a share detaches its active parts from the app but retains the local +fork and export. Deleting local data is a separate, explicitly confirmed action +available only after removal. It deletes only that namespaced share ref; +unrelated repositories, grants, settings, and accepted interface state remain. + +## Host and browser boundaries + +The browser implementation uses OPFS, a single Wasm/libgit2 Worker, and Web +Locks; it does not require native Git, Bun, or a host shell. Private source +adapters are closure-private host capabilities registered before startup and +return only archive bytes for an opaque reference. Their credentials never +enter DOM, Git, Flect state, logs, prompts, or exports. + +HTTPS sources currently require direct CORS-readable responses and reject +credential-bearing URLs. Public Git requires an exact 40-character commit and +a CORS-capable HTTPS Git server. Real-time collaboration, publisher signature +verification, a public component registry, and automatic conflict resolution +are not implied by the current sharing lifecycle. + +See [the trust model](trust-model.md) for authority boundaries and +[the command reference](local-control.md) for agent and outside-control +surfaces. diff --git a/docs/superpowers/plans/2026-07-31-flect-chat-markdown.md b/docs/superpowers/plans/2026-07-31-flect-chat-markdown.md new file mode 100644 index 0000000..ab80481 --- /dev/null +++ b/docs/superpowers/plans/2026-07-31-flect-chat-markdown.md @@ -0,0 +1,794 @@ +# Flect Chat Markdown Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:executing-plans to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace Flect's plain-text/fence splitter with a complete, +sanitized, T3Code-quality Markdown experience for browser and desktop chat. + +**Architecture:** `react-markdown` owns CommonMark rendering, with GFM and +role-aware hard-break plugins followed by raw-HTML parsing and a closed +sanitizer. Focused Flect components own safe links, native details, contained +tables, and rich code blocks; clipboard access crosses an Effect service, and +syntax highlighting is a lazy, bounded Effect workflow with escaped fallback. + +**Tech Stack:** React 19, Effect 4 beta, react-markdown 10.1.0, remark-gfm +4.0.1, remark-breaks 4.0.0, rehype-raw 7.0.0, rehype-sanitize 6.0.0, Shiki +4.3.1, Vitest/Testing Library, Playwright Chromium, Tauri 2. + +## Global Constraints + +- Preserve all existing uncommitted T3Code UX audit changes. +- Do not commit, push, publish, or release; the repository boundary does not + authorize those operations. +- Treat every message as untrusted text. +- Keep T3Code's file, editor, preview, skill, provider, settings, toast, and + local-API integrations out of Flect. +- Accept link schemes only for `http`, `https`, `mailto`, and local fragments. +- Do not add code execution, filesystem, storage, Pi, native, or network + authority to Markdown. +- Clipboard writes use an Effect `Context.Service`, named `Layer`, and typed + error. +- Keep Shiki outside the initial application chunk through dynamic import. +- Skip highlighting during streaming and preserve escaped code on every + loading or failure path. +- Desktop controls stay dense; compact controls remain at least 44 px. +- Test observable DOM and behavior, not implementation-file strings. +- Run every production change through a witnessed RED/GREEN cycle. + +--- + +### Task 1: Add and verify the Markdown dependency foundation + +**Files:** + +- Modify: `package.json` +- Modify: `bun.lock` + +**Interfaces:** + +- Consumes: current Bun/Vite React application. +- Produces: pinned imports for `react-markdown`, `remark-gfm`, + `remark-breaks`, `rehype-raw`, `rehype-sanitize`, and `shiki`. + +- [ ] **Step 1: Record the current dependency absence** + +Run: + +```bash +rg -n '"(react-markdown|remark-gfm|remark-breaks|rehype-raw|rehype-sanitize|shiki)"' package.json +``` + +Expected: no matches. + +- [ ] **Step 2: Install exact current compatible releases** + +Run: + +```bash +bun add react-markdown@10.1.0 remark-gfm@4.0.1 remark-breaks@4.0.0 rehype-raw@7.0.0 rehype-sanitize@6.0.0 shiki@4.3.1 +``` + +Expected: `package.json` and `bun.lock` contain the six exact versions. + +- [ ] **Step 3: Verify licenses and the dependency graph** + +Run: + +```bash +bun pm ls | rg 'react-markdown|remark-gfm|remark-breaks|rehype-raw|rehype-sanitize|shiki' +npm view react-markdown@10.1.0 license +npm view remark-gfm@4.0.1 license +npm view remark-breaks@4.0.0 license +npm view rehype-raw@7.0.0 license +npm view rehype-sanitize@6.0.0 license +npm view shiki@4.3.1 license +``` + +Expected: all packages resolve and every license is MIT. + +### Task 2: Put clipboard writes behind Effect + +**Files:** + +- Create: `src/lib/clipboard.ts` +- Create: `src/lib/clipboard.test.ts` +- Modify: `src/lib/runtime.ts` + +**Interfaces:** + +- Produces: + +```ts +export class ClipboardWriteError extends Schema.TaggedErrorClass()( + "ClipboardWriteError", + { message: Schema.Literal("Flect could not copy this content.") }, +) {} + +export class Clipboard extends Context.Service< + Clipboard, + { + readonly writeText: ( + value: string, + ) => Effect.Effect; + } +>()("Flect/Clipboard") {} + +export const ClipboardLive: Layer.Layer; +``` + +- [ ] **Step 1: Write failing service tests** + +Create `src/lib/clipboard.test.ts` with tests that: + +```ts +it.effect("writes text through the browser clipboard", () => + Effect.gen(function* () { + const clipboard = yield* Clipboard; + yield* clipboard.writeText("exact Markdown"); + expect(writeText).toHaveBeenCalledWith("exact Markdown"); + }).pipe(Effect.provide(ClipboardLive)), +); + +it.effect("keeps clipboard rejection typed", () => + Effect.gen(function* () { + const clipboard = yield* Clipboard; + const exit = yield* Effect.exit(clipboard.writeText("blocked")); + expect(Exit.isFailure(exit)).toBe(true); + expect(Cause.failureOption(exit.cause).pipe(Option.getOrThrow)._tag).toBe( + "ClipboardWriteError", + ); + }).pipe(Effect.provide(ClipboardLive)), +); +``` + +Use `vi.stubGlobal("navigator", ...)` or a configurable +`navigator.clipboard.writeText` property, restoring it after every test. + +- [ ] **Step 2: Run the tests and witness RED** + +Run: + +```bash +bunx vitest run src/lib/clipboard.test.ts +``` + +Expected: FAIL because `src/lib/clipboard.ts` does not exist. + +- [ ] **Step 3: Implement the service and live layer** + +Implement `ClipboardLive` with: + +```ts +const unavailable = () => + ClipboardWriteError.make({ + message: "Flect could not copy this content.", + }); + +export const ClipboardLive = Layer.succeed(Clipboard)({ + writeText: Effect.fn("Flect.Clipboard.writeText")((value: string) => + Effect.tryPromise({ + try: () => { + if (globalThis.navigator?.clipboard?.writeText === undefined) { + return Promise.reject(new Error("Clipboard API unavailable")); + } + return globalThis.navigator.clipboard.writeText(value); + }, + catch: unavailable, + }), + ), +}); +``` + +Merge `ClipboardLive` into `BrowserLive` and add `Clipboard` to +`FlectBrowserServices`. + +- [ ] **Step 4: Run the service tests and typecheck** + +Run: + +```bash +bunx vitest run src/lib/clipboard.test.ts +bun run typecheck +``` + +Expected: both commands exit 0. + +### Task 3: Implement pure Markdown policies and serializers + +**Files:** + +- Create: `src/components/markdown-policy.ts` +- Create: `src/components/markdown-policy.test.ts` +- Create: `src/components/markdown-table-serialization.ts` +- Create: `src/components/markdown-table-serialization.test.ts` + +**Interfaces:** + +- Produces: + +```ts +export const markdownUrlTransform: UrlTransform; +export const isExternalMarkdownHref: (href: string) => boolean; +export const normalizeSanitizedFragmentId: (id: string) => string; +export const extractFenceLanguage: (className?: string) => string; +export const extractFenceTitle: (meta?: string) => string | undefined; +export const serializeTableToMarkdown: (table: HTMLTableElement) => string; +export const serializeTableToCsv: (table: HTMLTableElement) => string; +``` + +- [ ] **Step 1: Write failing URL and fence-policy tests** + +Cover: + +```ts +expect(markdownUrlTransform("https://example.com")).toBe( + "https://example.com", +); +expect(markdownUrlTransform("mailto:team@example.com")).toBe( + "mailto:team@example.com", +); +expect(markdownUrlTransform("#summary")).toBe("#summary"); +expect(markdownUrlTransform("javascript:alert(1)")).toBe(""); +expect(markdownUrlTransform("data:text/html,boom")).toBe(""); +expect(extractFenceLanguage("language-ts")).toBe("typescript"); +expect(extractFenceTitle('title="src/app.ts"')).toBe("src/app.ts"); +expect(extractFenceTitle("filename=server.ts")).toBe("server.ts"); +``` + +Normalize common aliases: `js -> javascript`, `ts -> typescript`, +`sh -> shell`, `bash -> shell`, `yml -> yaml`, `md -> markdown`, +`text/plaintext/txt -> text`, and `gitignore -> ini`. + +- [ ] **Step 2: Write failing table-serialization tests** + +Create a real DOM table and assert: + +```ts +expect(serializeTableToMarkdown(table)).toBe( + "| Name | Note |\\n| --- | --- |\\n| Flect | A \\\\| B |", +); +expect(serializeTableToCsv(table)).toBe( + "Name,Note\\r\\nFlect,A | B", +); +``` + +Add a separate row containing `A, B` and assert that CSV quotes it. Also cover +quotes, newlines, empty cells, pipes, and header-only tables. + +- [ ] **Step 3: Run policy tests and witness RED** + +Run: + +```bash +bunx vitest run src/components/markdown-policy.test.ts src/components/markdown-table-serialization.test.ts +``` + +Expected: FAIL because both implementation modules are absent. + +- [ ] **Step 4: Implement minimal pure policies** + +Use `react-markdown`'s `defaultUrlTransform` only after the explicit +allow-list has accepted a URL. Return an empty string for relative paths, +protocol-relative URLs, `file:`, `data:`, `javascript:`, and unknown schemes. + +Implement serializers by reading `table.rows` and `row.cells`, normalizing +whitespace, escaping pipes/backslashes for Markdown, and applying RFC-style +double-quote escaping for CSV fields containing comma, quote, CR, or LF. + +- [ ] **Step 5: Run policy tests GREEN** + +Run: + +```bash +bunx vitest run src/components/markdown-policy.test.ts src/components/markdown-table-serialization.test.ts +``` + +Expected: all tests pass. + +### Task 4: Build the sanitized semantic renderer + +**Files:** + +- Replace: `src/components/message-content.tsx` +- Create: `src/components/message-content.test.tsx` + +**Interfaces:** + +- Consumes: Task 3 URL/fence policy. +- Produces: + +```ts +export interface MessageContentProps { + readonly content: string; + readonly messageRole: "user" | "assistant" | "activity"; + readonly streaming?: boolean; +} + +export function MessageContent(props: MessageContentProps): ReactElement; +``` + +- [ ] **Step 1: Write failing semantic rendering tests** + +Render one assistant message containing: + +```md +# Release notes + +**Strong**, *emphasis*, ~~removed~~, and `inline()`. + +> A quoted decision. + +1. First +2. Second + +- [x] Complete +- [ ] Remaining + +A footnote.[^1] + +[^1]: Supporting detail. +``` + +Assert semantic roles/tags: level-one heading, strong/em/del, blockquote, +ordered list, disabled checked/unchecked checkboxes, inline code, footnote +link, and no literal Markdown markers. + +- [ ] **Step 2: Write failing role-aware line-break tests** + +Assert `line one\nline two` produces a `
` for `messageRole="user"` and +remains one normal paragraph for `messageRole="assistant"`. + +- [ ] **Step 3: Write failing sanitizer and safe-link tests** + +Render: + +```md + +Safe text +[unsafe](javascript:alert(1)) +[safe](https://example.com/docs) +
MoreSafe detail
+``` + +Assert: + +- no `script`, `style`, `onclick`, iframe, object, form, media, or image node; +- “Safe text” and “Safe detail” remain; +- unsafe link has no actionable href; +- safe link has `target="_blank"` and `rel="noopener noreferrer"`; +- details and summary remain semantic and keyboard-native. + +- [ ] **Step 4: Run renderer tests and witness RED** + +Run: + +```bash +bunx vitest run src/components/message-content.test.tsx +``` + +Expected: FAIL because the existing fence splitter cannot render semantics. + +- [ ] **Step 5: Implement the Markdown pipeline** + +Use: + +```tsx +const REMARK_PLUGINS = [remarkGfm, remarkPreserveCodeMeta]; +const REMARK_PLUGINS_WITH_BREAKS = [ + remarkGfm, + remarkBreaks, + remarkPreserveCodeMeta, +]; +const REHYPE_PLUGINS = [ + rehypeRaw, + [rehypeSanitize, FLECT_MARKDOWN_SANITIZE_SCHEMA], +]; +``` + +Build `FLECT_MARKDOWN_SANITIZE_SCHEMA` from `defaultSchema`, explicitly adding +only `details`, `summary`, code metadata, `open`, GFM task-list class names, +and footnote identifiers. Remove `img`, form/media/embed tags, `style`, +`title`, and all event-like attributes. + +Use custom `a`, `details`, `code`, `pre`, and `table` components. At this task, +`pre` and `table` may use semantic, contained plain fallbacks; Tasks 5 and 6 +replace them with interactive components. + +Wrap rendering in a class error boundary whose fallback is: + +```tsx +

{content}

+``` + +- [ ] **Step 6: Run semantic tests GREEN** + +Run: + +```bash +bunx vitest run src/components/message-content.test.tsx +bun run typecheck +``` + +Expected: all tests and typecheck pass. + +### Task 5: Add lazy, bounded syntax highlighting and rich code blocks + +**Files:** + +- Create: `src/components/markdown-highlighter.ts` +- Create: `src/components/markdown-highlighter.test.ts` +- Create: `src/components/markdown-code-block.tsx` +- Create: `src/components/markdown-code-block.test.tsx` +- Modify: `src/components/message-content.tsx` +- Modify: `src/components/icons.tsx` + +**Interfaces:** + +- Produces: + +```ts +export class MarkdownHighlightError extends Schema.TaggedErrorClass()( + "MarkdownHighlightError", + { language: Schema.String }, +) {} + +export const highlightMarkdownCode: ( + code: string, + language: string, +) => Effect.Effect; + +export interface MarkdownCodeBlockProps { + readonly code: string; + readonly language: string; + readonly title?: string; + readonly streaming: boolean; +} +``` + +- [ ] **Step 1: Write failing highlighter tests** + +Assert JavaScript output includes a Shiki `
` and escaped highlighted
+source, unsupported languages fall back to `text`, repeated source returns
+the cached result, and more than the configured cache limit evicts the oldest
+entry. Expose only a test reset/introspection helper under an explicit
+`__markdownHighlightTest` export.
+
+- [ ] **Step 2: Write failing code-block interaction tests**
+
+Provide this Markdown fixture:
+
+````md
+```ts title="src/app.ts"
+const answer: number = 42
+```
+````
+
+Assert:
+
+- header text is `src/app.ts`;
+- the block exposes `data-language="typescript"`;
+- source code is visible before highlighting;
+- `Wrap lines` toggles to `Disable line wrap` and `aria-pressed=true`;
+- `Copy code` writes the exact source through `Clipboard`;
+- success becomes `Copied`;
+- failure becomes `Copy failed` in an `aria-live` status;
+- `streaming` keeps plain code and does not render `.shiki`.
+
+- [ ] **Step 3: Run highlighter/code tests and witness RED**
+
+Run:
+
+```bash
+bunx vitest run src/components/markdown-highlighter.test.ts src/components/markdown-code-block.test.tsx
+```
+
+Expected: FAIL because the modules do not exist.
+
+- [ ] **Step 4: Implement the bounded Effect highlighter**
+
+Use `Effect.tryPromise` around a dynamic:
+
+```ts
+const loadShiki = () => import("shiki/bundle/web");
+```
+
+Call the module's cached `codeToHtml` shorthand with
+`theme: "github-dark-default"`. Retry once with `lang: "text"` after an
+unsupported language. Bound Flect's HTML-result cache to 100 entries and
+5 MiB by evicting oldest insertion-order entries.
+
+- [ ] **Step 5: Implement `MarkdownCodeBlock`**
+
+Keep code source in React text while highlighting is loading. Start the
+highlighter Effect only when `streaming === false`; own its fiber and interrupt
+it from the effect cleanup. Insert only locally generated Shiki HTML.
+
+Copy by yielding `Clipboard` inside a named Effect and running it through
+`browserRuntime` at the event boundary. Use local `idle | copied | failed`
+state, reset acknowledgement after 1.2 seconds, and clear timers on unmount.
+
+Add `CopyIcon`, `CheckIcon`, and `WrapIcon` to `icons.tsx`.
+
+- [ ] **Step 6: Run code tests GREEN**
+
+Run:
+
+```bash
+bunx vitest run src/components/markdown-highlighter.test.ts src/components/markdown-code-block.test.tsx src/components/message-content.test.tsx
+bun run typecheck
+```
+
+Expected: all tests and typecheck pass.
+
+### Task 6: Add contained, copyable tables
+
+**Files:**
+
+- Create: `src/components/markdown-table.tsx`
+- Create: `src/components/markdown-table.test.tsx`
+- Modify: `src/components/message-content.tsx`
+- Modify: `src/components/icons.tsx`
+
+**Interfaces:**
+
+- Consumes: Task 2 Clipboard, Task 3 serializers.
+- Produces:
+
+```ts
+export function MarkdownTable(
+  props: React.ComponentProps<"table">,
+): ReactElement;
+```
+
+- [ ] **Step 1: Write failing table interaction tests**
+
+Render a GFM table and assert:
+
+- semantic table, row, columnheader, and cell roles;
+- wrapper starts with `data-expanded="false"`;
+- `Expand table cells` toggles to `Collapse table cells`;
+- Copy Markdown writes the exact normalized Markdown table;
+- Copy CSV writes CRLF CSV with correct quoting;
+- copy success/failure is announced;
+- Escape or focus behavior is not hijacked by the table.
+
+- [ ] **Step 2: Run table tests and witness RED**
+
+Run:
+
+```bash
+bunx vitest run src/components/markdown-table.test.tsx
+```
+
+Expected: FAIL because `MarkdownTable` does not exist.
+
+- [ ] **Step 3: Implement the table surface**
+
+Use one horizontally scrollable viewport containing the table and a footer
+with `Expand table cells`, `Copy table as Markdown`, and
+`Copy table as CSV` buttons. Run clipboard Effects through `browserRuntime`;
+do not introduce a menu dependency or T3Code's toast system.
+
+Add `ExpandIcon` and `CollapseIcon` to `icons.tsx`.
+
+- [ ] **Step 4: Run table and renderer tests GREEN**
+
+Run:
+
+```bash
+bunx vitest run src/components/markdown-table.test.tsx src/components/message-content.test.tsx
+bun run typecheck
+```
+
+Expected: all tests and typecheck pass.
+
+### Task 7: Integrate role and streaming state into the protected rail
+
+**Files:**
+
+- Modify: `src/components/agent-rail.tsx`
+- Modify: `src/components/agent-rail.test.tsx`
+
+**Interfaces:**
+
+- Consumes: `MessageContentProps` from Task 4.
+- Produces: the latest active assistant message receives
+  `streaming={true}`, and every message receives its actual role.
+
+- [ ] **Step 1: Write failing rail integration tests**
+
+Add an assistant fixture with Markdown and assert it renders a semantic
+heading. Render the controller with `status="streaming"` and assert the latest
+assistant code block remains plain; render `status="ready"` and wait for its
+highlighted state.
+
+- [ ] **Step 2: Run rail tests and witness RED**
+
+Run:
+
+```bash
+bunx vitest run src/components/agent-rail.test.tsx
+```
+
+Expected: FAIL because `AgentRail` does not pass role/streaming props.
+
+- [ ] **Step 3: Pass role and streaming state**
+
+Render:
+
+```tsx
+
+```
+
+- [ ] **Step 4: Run integration tests GREEN**
+
+Run:
+
+```bash
+bunx vitest run src/components/agent-rail.test.tsx src/components/message-content.test.tsx
+```
+
+Expected: all tests pass.
+
+### Task 8: Apply T3Code-quality Flect typography and responsive containment
+
+**Files:**
+
+- Modify: `src/styles.css`
+- Modify: `tests/e2e/flect.spec.ts`
+- Modify: `server/test-runtime.ts`
+
+**Interfaces:**
+
+- Consumes: semantic classes from Tasks 4–7.
+- Produces: production-browser visual and geometry evidence.
+
+- [ ] **Step 1: Add the deterministic Markdown showcase**
+
+Make `FlectTestRuntimeLive.prompt(sessionId, text)` return a representative
+Markdown delta when `text === "Show the Markdown showcase"`; preserve the
+existing deterministic response for every other prompt.
+
+The showcase must contain a heading, paragraph emphasis, quote, task list,
+safe link, details, footnote, table, inline code, and titled TypeScript fence.
+
+- [ ] **Step 2: Write failing production-Chromium assertions**
+
+Add an E2E flow that enters Run, sends the showcase prompt, and verifies:
+
+- heading, quote, task checkbox, details, link, table, and code block appear;
+- external link safety attributes are present;
+- code highlighting completes;
+- wrap and table expansion work;
+- copy buttons acknowledge success;
+- at 720 × 780, `.role-shell` and document scroll widths equal viewport width;
+- code/table inner viewports own any horizontal overflow;
+- all Markdown chrome buttons meet 44 px at compact width; and
+- no console errors are emitted.
+
+- [ ] **Step 3: Run the E2E test and witness RED**
+
+Run:
+
+```bash
+bunx playwright test tests/e2e/flect.spec.ts --grep "renders complete Markdown"
+```
+
+Expected: FAIL because the showcase and visual styles are not complete.
+
+- [ ] **Step 4: Replace minimal message CSS with the Flect Markdown system**
+
+Implement the spec's typography with:
+
+- 0.65 rem block rhythm and zero outer first/last margins;
+- compact 1.25/1.125/1/0.875 rem headings;
+- nested list markers and 0.25 rem list-item rhythm;
+- disabled task-checkbox alignment;
+- rose interaction links with visible keyboard focus;
+- two-pixel blockquote rule;
+- quiet bordered inline-code chip;
+- contained code/table surfaces using existing tokens;
+- code header/action styles and Shiki background bridge;
+- table row separators, collapsed ellipsis, expanded wrapping;
+- native details summary with a rotating chevron marker;
+- thin internal scrollbars;
+- 44 px Markdown action targets at `max-width: 760px`; and
+- reduced-motion acknowledgement/disclosure behavior.
+
+- [ ] **Step 5: Run focused browser tests GREEN**
+
+Run:
+
+```bash
+bunx playwright test tests/e2e/flect.spec.ts --grep "renders complete Markdown"
+bunx playwright test tests/e2e/flect.spec.ts --grep "compact breakpoints"
+```
+
+Expected: both tests pass.
+
+### Task 9: Document evidence and run completion verification
+
+**Files:**
+
+- Create: `docs/verification/2026-07-31-chat-markdown-verification.md`
+- Modify if behavior changed: `DESIGN.md`
+
+**Interfaces:**
+
+- Produces: reproducible source comparison, security evidence, test evidence,
+  browser screenshots, and installed-app smoke evidence.
+
+- [ ] **Step 1: Update the visible design-system owner**
+
+Add a concise `Chat Markdown` section to `DESIGN.md` documenting only the
+shipped visual contract: semantic rhythm, compact headings, code/table
+instruments, token usage, and compact targets. Link to the design spec for
+implementation detail rather than duplicating it.
+
+- [ ] **Step 2: Run full static and automated verification**
+
+Run:
+
+```bash
+bun run check:all
+```
+
+Expected: Effect preparation, Rifty verification, Biome, TypeScript, all
+Vitest tests, all Playwright tests, Rust tests, and the macOS app build exit 0.
+
+- [ ] **Step 3: Inspect the production build in real Chromium**
+
+Start the deterministic runtime and Vite production preview, then use Chrome
+to:
+
+- render the Markdown showcase at desktop and compact widths;
+- exercise details, wrap, table expansion, and copy;
+- inspect accessibility roles and focus;
+- confirm unsafe content is absent;
+- capture desktop, code, table, and compact screenshots;
+- inspect console and network errors; and
+- record exact code/table/document overflow geometry.
+
+- [ ] **Step 4: Rebuild, install, and smoke-test macOS**
+
+Use the app bundle produced by `check:all`. Quit only Flect, move the current
+`/Applications/Flect.app` to a timestamped recoverable Trash backup, install
+the new bundle with `ditto`, verify its ad-hoc signature, launch it, confirm
+the 1180 × 781 native window, and confirm the packaged frontend completes
+`GetRuntime` and `ListModels` over the private Effect RPC bridge.
+
+- [ ] **Step 5: Write the verification record**
+
+Document:
+
+- T3Code reference commit and adapted/excluded behavior;
+- dependency versions and licenses;
+- sanitizer and unsafe-link evidence;
+- unit/E2E/Rust test counts;
+- production bundle sizes and lazy Shiki chunk evidence;
+- real-Chromium interaction and geometry results;
+- installed bundle signature/window/RPC results; and
+- any remaining non-blocking performance follow-up.
+
+- [ ] **Step 6: Run final hygiene checks**
+
+Run:
+
+```bash
+bun run lint
+git diff --check
+git status --short
+```
+
+Expected: lint and diff checks exit 0; status contains only intended,
+uncommitted Flect changes.
diff --git a/docs/superpowers/plans/2026-07-31-flect-embedded-axi.md b/docs/superpowers/plans/2026-07-31-flect-embedded-axi.md
new file mode 100644
index 0000000..fa20699
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-31-flect-embedded-axi.md
@@ -0,0 +1,1372 @@
+# Flect Embedded AXI Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use
+> `superpowers:executing-plans` to implement this plan task-by-task. Steps use
+> checkbox (`- [ ]`) syntax for tracking. This repository currently forbids
+> commits without separate user authorization, so each task ends with a diff
+> and test checkpoint instead of a commit.
+
+**Goal:** Replace the separately shipped `flectctl` and `flect-mcp` binaries
+with one AXI-compliant `flect` command surface embedded in the native app and
+the role-owned browser shell, backed by one Effect command/state authority.
+
+**Architecture:** A reusable Effect `FlectAxiProgram` parses commands and
+renders bounded TOON through source-specific gateways. Native invocations use
+the authenticated loopback broker; App Agent and Shaper use a bounded
+in-process Effect queue whose source identity is captured by their sandbox.
+The Tauri executable remains the only public native executable and forwards
+CLI/MCP modes to the existing private Bun runtime.
+
+**Tech Stack:** TypeScript 7, Effect 4 beta, `@effect/vitest`,
+`@toon-format/toon@4.1.0`, Bun 1.4+, just-bash 3.2.0, Pi 0.82.1, React 19,
+Tauri 2/Rust, MCP v2, Vitest, Playwright/Chromium.
+
+## Global Constraints
+
+- Follow
+  [`../specs/2026-07-31-flect-embedded-axi-design.md`](../specs/2026-07-31-flect-embedded-axi-design.md).
+- Preserve all existing uncommitted T3-style composer, Markdown, control,
+  shell, release, and documentation work.
+- Do not commit, push, merge, publish, release, or modify real agent
+  configuration without separate user authorization.
+- Start every behavior change with a failing observable test and verify that it
+  fails for the intended missing behavior before adding production code.
+- Keep application behavior in Effect: strict Effect Schema boundaries,
+  `Context.Service`, named Layers, `Effect.fn`, typed failures, `Queue`,
+  `Deferred`, `Stream`, `Ref`/`SubscriptionRef`, Scope, and interruption.
+- Use the pinned `.repos/effect` checkout and the repository Effect guides for
+  exact APIs; keep all Effect package versions aligned at `4.0.0-beta.102`.
+- Use the official `@toon-format/toon` package pinned exactly to `4.1.0`; TOON
+  is an output encoding, never application state or transport state.
+- Guardian stays tool-free. App and Shaper stay isolated and cannot forge
+  their command source or broaden their role authority.
+- Browser `flect` must run entirely inside the existing just-bash sandbox and
+  must not call localhost, spawn native processes, expose credentials, or gain
+  host filesystem/network access.
+- Outside control remains disabled by default, loopback-only, explicitly
+  user-enabled, authenticated, attributable, bounded, and revocable.
+- Default stdout follows AXI; stderr is diagnostics only; exit codes are `0`
+  success/no-op, `1` operational failure, and `2` usage failure.
+- Test observable exports, protocols, rendered UI, and real artifacts; never
+  assert that implementation source contains a chosen string.
+
+---
+
+## File map
+
+### New files
+
+- `src/axi/contracts.ts` — AXI invocation, audience, output, exit-code, public
+  failure, and result schemas.
+- `src/axi/output.ts` — bounded projections and official TOON/JSON encoding.
+- `src/axi/output.test.ts` — AXI formatting, truncation, empty-state, channel,
+  and byte-limit behavior.
+- `src/axi/gateway.ts` — transport-neutral `FlectCommandGateway` Effect
+  service.
+- `src/axi/broker-gateway.ts` — gateway backed by `FlectControlClient`.
+- `src/axi/command.ts` — strict noun-first parser and command-local help.
+- `src/axi/command.test.ts` — parser, unknown-input, idempotent syntax, and help
+  behavior.
+- `src/axi/program.ts` — content-first home view and command orchestration.
+- `src/axi/program.test.ts` — gateway-level AXI behavior.
+- `src/axi/agent-command-bus.ts` — bounded in-process request queue with exact
+  Deferred responses.
+- `src/axi/agent-command-bus.test.ts` — capacity, interruption, timeout, and
+  shutdown tests.
+- `src/axi/agent-command-bridge.ts` — source-aware controller consumer.
+- `src/axi/agent-command-bridge.test.ts` — controller/gateway parity and role
+  denial tests.
+- `src/axi/agent-gateway.ts` — role-bound gateway used by the virtual command.
+- `src/shell/flect-command.ts` — just-bash custom command adapter.
+- `src/shell/flect-command.test.ts` — argv, output, source binding, and error
+  projection tests.
+- `cli/flect.ts` — unbundled native-development AXI entrypoint.
+- `cli/flect.test.ts` — native broker adapter and process-facing I/O tests.
+- `server/sidecar-mode.ts` — strict private runtime mode selection.
+- `server/sidecar-mode.test.ts` — RPC/AXI/MCP selection tests.
+- `src/lib/agent-integration.ts` — idempotent Codex, Claude Code, and OpenCode
+  integration planning and mutation service.
+- `src/lib/agent-integration.test.ts` — temporary-root install/repair/remove
+  behavior.
+- `assets/agent-integrations/opencode/flect.js` — dependency-free OpenCode V2
+  plugin template.
+- `.agents/skills/flect/SKILL.md` — generated static discovery skill.
+- `scripts/generate-flect-skill.ts` — generates/checks the skill from the AXI
+  static guidance.
+- `scripts/generate-flect-skill.test.ts` — deterministic generation test.
+- `tests/e2e/embedded-axi.spec.ts` — real-browser role and reactive-state proof.
+
+### Renamed or removed files
+
+- Replace `cli/flectctl.ts` and `cli/flectctl.test.ts` with `cli/flect.ts` and
+  `cli/flect.test.ts` after behavioral characterization is migrated.
+- Keep `cli/flect-mcp.ts` as a source module but remove its auto-running public
+  binary entrypoint; `flect mcp` invokes its exported server.
+- Delete generated `src-tauri/binaries/flectctl-aarch64-apple-darwin` and
+  `src-tauri/binaries/flect-mcp-aarch64-apple-darwin` only after packaging no
+  longer references them.
+- Delete `server/pi-proposal-tool.ts` and its test only after Shaper Bash
+  proposal parity passes.
+
+### Existing files modified
+
+- `package.json`, `bun.lock`
+- `shared/control.ts`, `shared/control.test.ts`
+- `shared/contracts.ts`, `shared/contracts.test.ts`
+- `shared/bun-command.ts`, `shared/bun-command.test.ts`
+- `src/lib/workspace-controller.ts`, `src/lib/workspace-controller.test.ts`
+- `src/lib/agent-workspace.ts`, `src/lib/agent-workspace.test.ts`
+- `src/lib/runtime.ts`
+- `src/shell/sandboxed-shell-service.ts`
+- `src/shell/sandboxed-shell.ts`, `src/shell/sandboxed-shell.test.ts`
+- `server/pi-runtime.ts`, `server/pi-runtime.test.ts`
+- `server/sidecar.ts`
+- `cli/flect-client.ts`, `cli/flect-client.test.ts`
+- `cli/flect-mcp.ts`, `cli/flect-mcp.test.ts`
+- `src-tauri/src/main.rs`, `src-tauri/src/lib.rs`
+- `src-tauri/tauri.conf.json`, `src-tauri/capabilities/main.json`
+- `scripts/build-sidecar.ts`
+- `scripts/package-release.ts`, `scripts/package-release.test.ts`
+- `scripts/smoke-pi.ts`
+- `src/components/diagnostics-panel.tsx` and its test
+- `src/lib/tauri-transport.ts` and its test
+- `tests/e2e/flect.spec.ts`, `playwright.config.ts`
+- `AGENTS.md`, `ARCHITECTURE.md`, `CONTRIBUTING.md`, `README.md`, `VISION.md`
+- `docs/local-control.md`, `docs/trust-model.md`
+
+---
+
+### Task 1: AXI result contract and TOON output boundary
+
+**Files:**
+
+- Create: `src/axi/contracts.ts`
+- Create: `src/axi/output.ts`
+- Create: `src/axi/output.test.ts`
+- Modify: `package.json`
+- Modify: `bun.lock`
+
+**Interfaces:**
+
+- Produces `AxiAudience`, `AxiInvocation`, `AxiPublicError`, `AxiRunResult`,
+  `renderAxiSuccess`, and `renderAxiFailure`.
+- `AxiRunResult` is the only value process and just-bash adapters write to
+  their output channels.
+
+- [ ] **Step 1: Write the failing output tests**
+
+Cover these exact cases in `src/axi/output.test.ts` with `@effect/vitest`:
+
+```ts
+it.effect("renders compact TOON with a definitive empty collection", () =>
+  Effect.gen(function* () {
+    const result = yield* renderAxiSuccess({
+      format: "toon",
+      value: { count: 0, actions: [] },
+    })
+    assert.strictEqual(result.exitCode, 0)
+    assert.strictEqual(result.stderr, "")
+    assert.match(result.stdout, /^count: 0\nactions\[0\]:$/)
+  }),
+)
+
+it.effect("puts structured errors on stdout with usage exit code 2", () =>
+  Effect.gen(function* () {
+    const result = yield* renderAxiFailure(
+      AxiPublicError.make({
+        code: "unknown-flag",
+        message: "Unknown flag --stat for action list.",
+        help: ["Run `flect action list --help`"],
+      }),
+      "toon",
+      2,
+    )
+    assert.strictEqual(result.exitCode, 2)
+    assert.strictEqual(result.stderr, "")
+    assert.include(result.stdout, "code: unknown-flag")
+  }),
+)
+```
+
+Also assert JSON compatibility, a 1,000-character default preview with total
+character count, `--full` preservation, newline termination, and rejection of
+encoded output above the shared byte limit.
+
+- [ ] **Step 2: Run the focused test and confirm RED**
+
+Run:
+
+```bash
+bunx vitest run src/axi/output.test.ts
+```
+
+Expected: failure because `src/axi/output.ts` does not exist.
+
+- [ ] **Step 3: Pin the official encoder**
+
+Run:
+
+```bash
+bun add --exact @toon-format/toon@4.1.0
+```
+
+Confirm `package.json` contains exactly `"@toon-format/toon": "4.1.0"`.
+
+- [ ] **Step 4: Implement typed AXI results and formatting**
+
+Use these public shapes in `src/axi/contracts.ts`:
+
+```ts
+export const AxiAudience = Schema.Literals(["native", "app", "shaper"])
+
+export class AxiInvocation extends Schema.Class(
+  "AxiInvocation",
+)({
+  audience: AxiAudience,
+  bin: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(2_048)),
+  format: Schema.Literals(["toon", "json"]),
+  full: Schema.Boolean,
+}) {}
+
+export class AxiPublicError extends Schema.Class(
+  "AxiPublicError",
+)({
+  code: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(80)),
+  message: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(500)),
+  help: Schema.Array(Schema.String.check(Schema.isMaxLength(500))).check(
+    Schema.isMaxLength(4),
+  ),
+}) {}
+
+export class AxiRunResult extends Schema.Class("AxiRunResult")({
+  exitCode: Schema.Literals([0, 1, 2]),
+  stdout: Schema.String,
+  stderr: Schema.String,
+}) {}
+```
+
+In `output.ts`, use `encode` from `@toon-format/toon`, `JSON.stringify` only
+inside `Effect.try`, a recursive bounded projection before encoding, and a
+final UTF-8 byte check. Map encoder defects to one `AxiFormatError` tagged
+error; never leak encoder details.
+
+- [ ] **Step 5: Run GREEN and the existing shared contract tests**
+
+```bash
+bunx vitest run src/axi/output.test.ts shared/control.test.ts
+```
+
+Expected: all selected tests pass without warnings.
+
+- [ ] **Step 6: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- package.json bun.lock src/axi/contracts.ts src/axi/output.ts src/axi/output.test.ts
+```
+
+---
+
+### Task 2: Strict noun-first AXI parser and transport-neutral program
+
+**Files:**
+
+- Create: `src/axi/gateway.ts`
+- Create: `src/axi/command.ts`
+- Create: `src/axi/command.test.ts`
+- Create: `src/axi/program.ts`
+- Create: `src/axi/program.test.ts`
+- Create: `src/axi/broker-gateway.ts`
+- Create: `cli/flect.ts`
+- Create: `cli/flect.test.ts`
+- Modify: `cli/flect-client.ts`
+- Remove after parity: `cli/flectctl.ts`, `cli/flectctl.test.ts`
+
+**Interfaces:**
+
+```ts
+export interface FlectCommandGatewayShape {
+  readonly audience: AxiAudience
+  readonly bin: string
+  readonly status: Effect.Effect
+  readonly inspect: Effect.Effect
+  readonly logs: Effect.Effect
+  readonly events: (
+    after: number,
+  ) => Stream.Stream
+  readonly command: (
+    command: FlectCommand,
+    expectedSequence?: number,
+  ) => Effect.Effect
+}
+```
+
+- `runFlect(argv)` returns `Effect`.
+- `BrokerFlectCommandGatewayLive` adapts the existing authenticated client and
+  translates its private errors into stable gateway errors.
+
+- [ ] **Step 1: Characterize and then replace the old CLI behavior**
+
+Copy the useful existing `flectctl` tests into `cli/flect.test.ts`, then change
+their required behavior:
+
+- no arguments request the live home view rather than help;
+- default output is TOON;
+- `--json` remains valid;
+- errors use stdout and exit `1` or `2`;
+- unknown flags fail before the gateway is called;
+- noun-first commands are required;
+- `favorite add|remove` and `extensions enable|disable` replace toggles; and
+- `--help` works at every noun and leaf.
+
+- [ ] **Step 2: Add parser RED tests**
+
+Use table-driven tests for the complete command catalog from the design. At
+minimum, assert these concrete parses:
+
+```ts
+yield* assertParse(["mode", "set", "run"], {
+  type: "set-mode",
+  mode: "run",
+})
+yield* assertParse(["action", "invoke", "run-report"], {
+  type: "invoke-interface-action",
+  nodeId: "run-report",
+})
+yield* assertParse(["extensions", "enable", "shaper"], {
+  type: "set-external-extensions",
+  role: "shaper",
+  enabled: true,
+})
+yield* assertUsage(["action", "list", "--stat"], "unknown-flag")
+```
+
+- [ ] **Step 3: Run parser/program/CLI tests and confirm RED**
+
+```bash
+bunx vitest run src/axi/command.test.ts src/axi/program.test.ts cli/flect.test.ts
+```
+
+Expected: failure because the AXI parser/program modules do not exist.
+
+- [ ] **Step 4: Implement the parser as closed command definitions**
+
+Represent each leaf with its exact path, allowed flags, usage, examples,
+audiences, and an Effect parser. Do not scan and remove global flags from
+arbitrary positions as `flectctl` currently does. Parse recognized global
+flags first, then reject every unconsumed argument by name.
+
+The only universal flags are `--help`, `--json`, and `--full`. `--stdin`,
+`--fields`, `--limit`, `--after`, `--role`, and `--operation` are leaf-local.
+The removed `raw` escape hatch is not part of AXI; MCP/HTTP retain the strict
+closed command schema for machine clients.
+
+- [ ] **Step 5: Implement content-first program behavior**
+
+The native no-argument output must contain `bin`, `description`, workspace
+mode/phase/sequence, compact role status, proposal summary when present, and
+two or three contextual commands. If control is disabled or no workspace is
+connected, return a successful, definitive state such as:
+
+```text
+bin: /Applications/Flect.app/Contents/MacOS/flect
+description: Inspect and operate the live Flect workspace
+control: disabled
+workspace: unavailable
+help[2]:
+  Open Flect and enable Local control in Diagnostics
+  Run `flect app` to open Flect
+```
+
+Mutations wait for the exact terminal receipt already provided by
+`FlectControlClient.command`, then fetch one relevant post-state projection in
+the same command response.
+
+- [ ] **Step 6: Implement broker and process adapters**
+
+`cli/flect.ts` exports `runFlectCli(argv, io)` and has a guarded
+`import.meta.main` boundary for repository development. `io.stdout` receives
+only `result.stdout`; `io.stderr` receives only `result.stderr`; the process
+exit code is `result.exitCode`.
+
+Keep state-directory and client-name selection as private adapter options, not
+public agent-facing help. Reject missing option values.
+
+- [ ] **Step 7: Run GREEN and remove the old entrypoint**
+
+```bash
+bunx vitest run src/axi/command.test.ts src/axi/program.test.ts cli/flect.test.ts cli/flect-client.test.ts
+```
+
+After the replacement tests pass, remove `cli/flectctl.ts` and
+`cli/flectctl.test.ts`; update imports and the package script from `flectctl`
+to `flect`.
+
+- [ ] **Step 8: Review the task diff without committing**
+
+```bash
+git diff --check
+rg -n "flectctl" cli package.json
+git diff -- cli src/axi package.json
+```
+
+Expected: no executable or script named `flectctl` remains in active CLI code.
+
+---
+
+### Task 3: Idempotent shared commands and agent source authorization
+
+**Files:**
+
+- Modify: `shared/control.ts`
+- Modify: `shared/control.test.ts`
+- Modify: `src/lib/workspace-controller.ts`
+- Modify: `src/lib/workspace-controller.test.ts`
+- Modify: `src/lib/agent-workspace.ts`
+- Modify: `src/lib/agent-workspace.test.ts`
+- Modify: `src/hooks/use-workspace.ts`
+- Modify: UI tests that construct the replaced commands
+
+**Interfaces:**
+
+Add these strict schema values:
+
+```ts
+export class AgentCommandSource extends Schema.Class(
+  "AgentCommandSource",
+)({
+  kind: Schema.Literal("agent"),
+  role: InteractiveAgentRole,
+  sessionId: Identifier,
+  parentOperationId: OperationId,
+  requestId: ToolCallId,
+}) {}
+
+export class SetModelFavorite extends Schema.Class(
+  "SetModelFavorite",
+)({
+  type: Schema.Literal("set-model-favorite"),
+  model: ModelSelection,
+  favorite: Schema.Boolean,
+}) {}
+
+export class SetExternalExtensions extends Schema.Class(
+  "SetExternalExtensions",
+)({
+  type: Schema.Literal("set-external-extensions"),
+  role: InteractiveAgentRole,
+  enabled: Schema.Boolean,
+}) {}
+```
+
+Remove `ToggleModelFavorite` and `ToggleExternalExtensions` only after every UI,
+CLI, MCP, and test caller uses explicit desired state.
+
+- [ ] **Step 1: Write schema and authorization RED tests**
+
+Test strict decoding of `AgentCommandSource`, excess-property rejection, and
+explicit state commands. Add a controller policy matrix with each command as a
+row and `user`, `control`, `app`, and `shaper` as columns.
+
+Mandatory denial assertions:
+
+- App cannot set mode, submit Shaper work, change revisions, safe mode,
+  models/extensions, control, rail, or another role.
+- Shaper cannot accept/reject/rollback, safe mode, product actions,
+  models/extensions, control, rail, or another role.
+- Any agent cannot enable control.
+- Control source cannot enable control.
+- Guardian is not representable as an interactive agent source.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run shared/control.test.ts src/lib/workspace-controller.test.ts
+```
+
+Expected: the new source and set commands are absent and policy tests fail.
+
+- [ ] **Step 3: Implement source and explicit-state schemas**
+
+Extend `FlectCommandSource` and `FlectCommand`. Update operation/message/event
+attribution branches so agent sources retain role and parent operation without
+being mistaken for outside clients.
+
+- [ ] **Step 4: Implement controller authorization before claim**
+
+Keep authorization inside `FlectWorkspaceController.authorize`. Use an
+exhaustive command switch by source kind; do not derive security from parser
+audiences or hidden help. Return `ControlUnauthorized` with bounded public copy.
+
+- [ ] **Step 5: Make state changes idempotent**
+
+Change AgentWorkspace methods to:
+
+```ts
+readonly setModelFavorite: (
+  selection: ModelSelection,
+  favorite: boolean,
+) => Effect.Effect
+readonly setExternalExtensions: (
+  role: InteractiveAgentRole,
+  enabled: boolean,
+) => Effect.Effect
+```
+
+If current state already equals the request, return success without refreshing
+or replacing Pi sessions. Add tests proving no close/create operation occurs
+for an already-satisfied request.
+
+- [ ] **Step 6: Migrate UI and adapters**
+
+Visible toggle controls calculate explicit desired state from the current
+snapshot and dispatch the set command. MCP's raw closed union automatically
+uses the new schema; old toggle commands fail strict decoding.
+
+- [ ] **Step 7: Run GREEN**
+
+```bash
+bunx vitest run shared/control.test.ts src/lib/workspace-controller.test.ts src/lib/agent-workspace.test.ts src/app.test.tsx src/components/model-menu.test.tsx cli/flect-mcp.test.ts
+```
+
+- [ ] **Step 8: Review the task diff without committing**
+
+```bash
+git diff --check
+rg -n "ToggleModelFavorite|ToggleExternalExtensions|toggle-model-favorite|toggle-external-extensions" shared src cli server
+```
+
+Expected: no old toggle schema or call site remains.
+
+---
+
+### Task 4: Bounded in-process agent command bus and controller bridge
+
+**Files:**
+
+- Create: `src/axi/agent-command-bus.ts`
+- Create: `src/axi/agent-command-bus.test.ts`
+- Create: `src/axi/agent-command-bridge.ts`
+- Create: `src/axi/agent-command-bridge.test.ts`
+- Create: `src/axi/agent-gateway.ts`
+- Modify: `src/lib/runtime.ts`
+
+**Interfaces:**
+
+```ts
+export type AgentGatewayOperation =
+  | { readonly type: "inspect" }
+  | { readonly type: "logs" }
+  | { readonly type: "command"; readonly command: FlectCommand }
+
+export interface AgentCommandBusShape {
+  readonly submit: (
+    source: AgentCommandSource,
+    operation: AgentGatewayOperation,
+  ) => Effect.Effect
+  readonly take: Effect.Effect
+  readonly shutdown: Effect.Effect
+}
+```
+
+`AgentCommandRequest` is internal and carries a typed `Deferred`; it is not a
+Schema boundary or persisted value. Queue capacity is 32 and each request has
+a 30-second deadline.
+
+- [ ] **Step 1: Write queue lifecycle RED tests**
+
+Test exact response delivery, interruption removing/failing a pending request,
+capacity rejection, timeout with `TestClock`, and scoped shutdown failing all
+pending requests. Assert no unbounded queue or orphan fiber remains.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run src/axi/agent-command-bus.test.ts
+```
+
+- [ ] **Step 3: Implement the bus with Queue and Deferred**
+
+Use `Queue.bounded(32)`, one `Deferred` per request, `Effect.timeout`, and a
+scoped finalizer that marks the service closed, shuts down the queue, and
+completes pending Deferreds with `AgentCommandBusUnavailable`. Do not use an
+EventEmitter, Promise map, module global, or polling.
+
+- [ ] **Step 4: Write bridge RED tests**
+
+Provide test Layers for controller and bus. Prove:
+
+- inspect and logs are returned without a transport round trip;
+- allowed App action dispatch gets an agent source envelope;
+- forbidden App shaping fails before controller mutation;
+- Shaper proposal-related child operations retain `parentOperationId`; and
+- controller errors arrive on the exact caller's Deferred.
+
+- [ ] **Step 5: Implement the scoped bridge**
+
+The bridge takes the controller's current workspace ID, creates a fresh command
+ID, dispatches the envelope, and completes the exact Deferred. It handles
+requests sequentially by default; controller long work may fork only where an
+existing cancellation requirement demands it. Name the consume and dispatch
+operations with `Effect.fn` and annotate role, parent operation, and command ID
+without logging payloads.
+
+- [ ] **Step 6: Compose the acyclic Layer graph**
+
+Build one shared bus first, provide it to the role-owned shell and
+AgentWorkspace, construct the controller, then start the bridge as a scoped
+consumer. `ManagedRuntime.make` remains the sole browser runtime boundary.
+
+- [ ] **Step 7: Run GREEN and current control bridge tests**
+
+```bash
+bunx vitest run src/axi/agent-command-bus.test.ts src/axi/agent-command-bridge.test.ts src/lib/workspace-control-bridge.test.ts src/lib/runtime.test.ts
+```
+
+If `src/lib/runtime.test.ts` does not exist, cover composition through the
+exported browser runtime in `src/hooks/use-workspace.test.tsx`; do not add a
+source-string test.
+
+- [ ] **Step 8: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- src/axi src/lib/runtime.ts
+```
+
+---
+
+### Task 5: Reserved `flect` command inside role-owned just-bash
+
+**Files:**
+
+- Create: `src/shell/flect-command.ts`
+- Create: `src/shell/flect-command.test.ts`
+- Modify: `src/shell/sandboxed-shell-service.ts`
+- Modify: `src/shell/sandboxed-shell.ts`
+- Modify: `src/shell/sandboxed-shell.test.ts`
+- Modify: `src/lib/agent-workspace.ts`
+
+**Interfaces:**
+
+Extend execution context without trusting environment variables:
+
+```ts
+export interface SandboxedAgentContext {
+  readonly sessionId: string
+  readonly parentOperationId: string
+  readonly requestId: string
+}
+
+export interface SandboxedShellExecuteOptions {
+  readonly signal?: AbortSignal
+  readonly agentContext?: SandboxedAgentContext
+}
+```
+
+The role remains the existing non-user-controlled `execute(role, ...)`
+argument. `makeFlectCommand(role, agentContext)` returns a just-bash custom
+command backed by `FlectAxiProgram` and `AgentFlectCommandGatewayLive`.
+
+- [ ] **Step 1: Write reserved-command RED tests**
+
+Prove all of the following for both roles:
+
+- `flect` prints `runtime: browser-embedded` and the role-specific home state;
+- `flect --help` is concise and role-specific;
+- pipes and redirection work;
+- `alias flect=false`, a shell function, a `/workspace/flect` file, and PATH
+  changes cannot shadow the command;
+- `FLECT_ROLE=shaper flect ...` cannot change an App source;
+- a missing `agentContext` fails safely rather than creating anonymous
+  authority; and
+- cancellation maps to exit `1` without stopping the shell workspace.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run src/shell/flect-command.test.ts src/shell/sandboxed-shell.test.ts
+```
+
+- [ ] **Step 3: Implement the custom command**
+
+Follow the existing reserved `bun` pattern: generate a randomized hidden
+command name, register one custom command, and use an AST transform to rewrite
+the visible `flect` command. Keep separate hidden names per role workspace.
+Pass args as an argv array directly to `FlectAxiProgram`; never reconstruct a
+shell command string.
+
+Return `AxiRunResult.stdout`, `stderr`, and `exitCode` through just-bash. AXI
+errors remain on command stdout; internal diagnostics remain stderr.
+
+- [ ] **Step 4: Supply authenticated shell-call context**
+
+In `AgentWorkspace.executeShellRequest`, pass the current Pi session ID,
+controller operation ID, and Pi shell request ID through
+`SandboxedShellExecuteOptions.agentContext`. Do not add them to shell env or
+workspace files.
+
+- [ ] **Step 5: Run GREEN and browser Bun regression tests**
+
+```bash
+bunx vitest run src/shell/flect-command.test.ts src/shell/sandboxed-shell.test.ts src/shell/bun-command-live.test.ts src/lib/agent-workspace.test.ts
+```
+
+- [ ] **Step 6: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- src/shell src/lib/agent-workspace.ts
+```
+
+---
+
+### Task 6: Shaper proposal completion through Bash
+
+**Files:**
+
+- Modify: `shared/contracts.ts`, `shared/contracts.test.ts`
+- Modify: `shared/bun-command.ts`, `shared/bun-command.test.ts`
+- Modify: `server/pi-runtime.ts`, `server/pi-runtime.test.ts`
+- Modify: `src/lib/agent-workspace.ts`, `src/lib/agent-workspace.test.ts`
+- Modify: `src/lib/workspace-controller.ts`,
+  `src/lib/workspace-controller.test.ts`
+- Modify: `src/shell/flect-command.ts`, `src/shell/flect-command.test.ts`
+- Remove after parity: `server/pi-proposal-tool.ts`,
+  `server/pi-proposal-tool.test.ts`
+
+**Interfaces:**
+
+The browser-only `flect interface validate|propose ` adapter reads from
+the role's existing just-bash `IFileSystem`, parses JSON as unknown, and runs
+the same strict `validateInterfaceDocument` boundary. A successful proposal
+command produces a bounded internal completion marker associated with the
+current Shaper request; only the validated `InterfaceDocument` crosses that
+marker.
+
+- [ ] **Step 1: Write RED tests for Shaper CLI proposal flow**
+
+Test a complete Shaper turn whose only Pi tool is Bash:
+
+1. Pi runs `flect interface schema`.
+2. Pi writes `/workspace/interface.json`.
+3. Pi runs `flect interface validate /workspace/interface.json`.
+4. Pi runs `flect interface propose /workspace/interface.json`.
+5. The existing outer `submit-shaper-instruction` controller operation creates
+   exactly one preview revision.
+6. The command returns revision/name/status in TOON.
+
+Also test invalid JSON, schema issues with field paths, path escape, two
+proposal calls in one turn, no proposal call, and App attempting the command.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run src/shell/flect-command.test.ts src/lib/agent-workspace.test.ts server/pi-runtime.test.ts src/lib/workspace-controller.test.ts
+```
+
+- [ ] **Step 3: Add a typed per-turn proposal latch**
+
+Own the latch in AgentWorkspace, keyed by `parentOperationId` and Shaper shell
+request ID. The first valid proposal wins; a second returns an idempotent no-op
+only when the document is identical and otherwise a conflict. Scope and clear
+it around the Shaper turn with `Effect.acquireUseRelease`; never persist it or
+expose it to App.
+
+- [ ] **Step 4: Change Pi Shaper from a dedicated proposal tool to Bash**
+
+Update the immutable Shaper instructions to name the exact four commands and
+state that `flect interface propose` must be its final action. Build the Shaper
+session with only the existing Bash tool. Keep Guardian tool-free and App Bash
+unchanged.
+
+Change Shape streaming so a normally completed Pi turn is terminal without
+requiring a server-owned proposal tool event. Browser AgentWorkspace requires
+the proposal latch before it considers the turn successful. If the first turn
+finishes without a valid proposal, issue one bounded corrective prompt in the
+same Shaper session; the second miss fails safely.
+
+- [ ] **Step 5: Preserve controller authority**
+
+AgentWorkspace returns the validated latched candidate to the existing outer
+controller command. The controller alone calls `ShapingKernel.propose`, emits
+the revision transition, and completes the parent operation. The latch is not
+revision state and cannot accept or activate its own candidate.
+
+- [ ] **Step 6: Remove the old Pi tool after parity**
+
+When all focused tests pass with Shaper configured as Bash-only, remove
+`pi-proposal-tool.ts`, its tests/imports, TypeBox proposal schema, and old
+`proposal_submitted` private event paths. Keep public validation-failure events
+if the UI still consumes them; now create them from CLI validation failures.
+
+- [ ] **Step 7: Run GREEN and Pi smoke**
+
+```bash
+bunx vitest run server/pi-runtime.test.ts server/pi-shell-bridge.test.ts src/lib/agent-workspace.test.ts src/lib/workspace-controller.test.ts src/shell/flect-command.test.ts shared/contracts.test.ts shared/bun-command.test.ts
+bun run test:pi-smoke
+```
+
+The credential-dependent smoke may report setup-required when Pi is not
+authenticated; that is not a pass. Before final completion, run it with the
+already configured Pi authentication and require the documented success.
+
+- [ ] **Step 8: Review the task diff without committing**
+
+```bash
+git diff --check
+rg -n "propose_interface|pi-proposal-tool" server src shared
+```
+
+Expected after removal: no active Pi tool or instruction references remain.
+
+---
+
+### Task 7: App action invocation through embedded Bash
+
+**Files:**
+
+- Modify: `src/axi/program.ts`, `src/axi/program.test.ts`
+- Modify: `src/axi/agent-command-bridge.ts`,
+  `src/axi/agent-command-bridge.test.ts`
+- Modify: `src/lib/workspace-controller.ts`,
+  `src/lib/workspace-controller.test.ts`
+- Modify: `src/components/interface-renderer.tsx` only if action projection is
+  currently UI-local
+
+**Interfaces:**
+
+- `flect action list` projects visible action node ID, label, action type, and
+  availability from the accepted validated document.
+- `flect action inspect ` returns declaration and current grant state.
+- `flect action invoke ` dispatches the existing
+  `InvokeInterfaceAction` command as an App agent child operation.
+
+- [ ] **Step 1: Write RED tests**
+
+Prove list has definitive zero state, invoke returns combined action outcome
+and post-state, missing node is structured exit `1`, and an App source cannot
+invoke an action absent from the accepted document or lacking its capability
+grant. Shaper invocation must be denied.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run src/axi/program.test.ts src/axi/agent-command-bridge.test.ts src/lib/workspace-controller.test.ts
+```
+
+- [ ] **Step 3: Move action discovery behind controller projection**
+
+Do not parse React DOM. Reuse the controller's validated `InterfaceDocument`
+walk and capability broker state. Expose one typed projection used by React and
+AXI so label/availability cannot drift.
+
+- [ ] **Step 4: Link child operation evidence**
+
+The nested action operation records `AgentCommandSource.parentOperationId`,
+role `app`, session ID, and shell request ID. Do not log prompt, bearer, or raw
+action payload. The parent turn remains interruptible.
+
+- [ ] **Step 5: Run GREEN**
+
+```bash
+bunx vitest run src/axi/program.test.ts src/axi/agent-command-bridge.test.ts src/lib/workspace-controller.test.ts src/components/interface-renderer.test.tsx
+```
+
+- [ ] **Step 6: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- src/axi src/lib/workspace-controller.ts src/components/interface-renderer.tsx
+```
+
+---
+
+### Task 8: One native `flect` executable with AXI and MCP modes
+
+**Files:**
+
+- Create: `server/sidecar-mode.ts`, `server/sidecar-mode.test.ts`
+- Modify: `server/sidecar.ts`
+- Modify: `cli/flect-mcp.ts`, `cli/flect-mcp.test.ts`
+- Modify: `src-tauri/src/main.rs`, `src-tauri/src/lib.rs`
+- Modify: `scripts/build-sidecar.ts`
+- Modify: `src-tauri/tauri.conf.json`
+- Modify: `package.json`
+
+**Interfaces:**
+
+Private runtime modes are exactly `rpc`, `axi`, and `mcp`. The Rust host passes
+one private mode marker before user arguments; unknown private modes fail
+closed.
+
+- [ ] **Step 1: Write sidecar mode RED tests**
+
+Test no args selects RPC, `--flect-private-mode=axi` selects AXI and strips
+only that marker, `mcp` selects MCP, and unknown/duplicate markers fail with a
+typed startup error.
+
+- [ ] **Step 2: Write Rust dispatch RED tests**
+
+Extract a pure `select_launch_mode(args, stdin_is_terminal)` function and test:
+
+- Finder-like no-tty/no-args -> GUI;
+- terminal/no-args -> AXI;
+- `app` -> GUI;
+- `mcp` -> MCP;
+- every other command -> AXI; and
+- private mode flags supplied by users are rejected.
+
+- [ ] **Step 3: Confirm RED**
+
+```bash
+bunx vitest run server/sidecar-mode.test.ts cli/flect-mcp.test.ts
+cargo test --manifest-path src-tauri/Cargo.toml
+```
+
+- [ ] **Step 4: Implement one multi-mode Bun runtime**
+
+Refactor `server/sidecar.ts` so it constructs exactly one selected Layer:
+
+- RPC: existing Effect RPC server, control broker, and Pi runtime;
+- AXI: `runFlectCli` with `BrokerFlectCommandGatewayLive`; or
+- MCP: exported `serveFlectMcp` with the same broker gateway.
+
+Keep `registerBunOAuthFlows()` in modes that need Pi/runtime provider support.
+Do not start RPC or Pi for a broker-only AXI/MCP client.
+
+- [ ] **Step 5: Implement the Rust public dispatcher**
+
+Before `flect_lib::run()`, use `std::io::IsTerminal` to select mode. AXI/MCP
+locate sibling `flect-runtime`, inherit stdio, pass the private marker plus
+arguments, and propagate exit status. Rust recognizes only host lifecycle
+`app`; it does not parse domain flags or render output.
+
+- [ ] **Step 6: Build only the private runtime helper**
+
+Change `scripts/build-sidecar.ts` to one compile target:
+
+```text
+src-tauri/binaries/flect-runtime-aarch64-apple-darwin
+```
+
+Remove `binaries/flectctl` and `binaries/flect-mcp` from Tauri `externalBin`.
+Package scripts expose `flect` and `flect:mcp` for source development, both
+through the shared modules, without compiling public companion executables.
+
+- [ ] **Step 7: Run GREEN and inspect the bundle**
+
+```bash
+bunx vitest run server/sidecar-mode.test.ts cli/flect.test.ts cli/flect-mcp.test.ts
+cargo test --manifest-path src-tauri/Cargo.toml
+bun run build:sidecar
+bun run build:desktop -- --bundles app
+find src-tauri/target/release/bundle/macos/Flect.app/Contents/MacOS -maxdepth 1 -type f -print
+```
+
+Expected files include `flect` and `flect-runtime`; `flectctl` and `flect-mcp`
+must be absent.
+
+- [ ] **Step 8: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- server cli src-tauri scripts/build-sidecar.ts package.json
+```
+
+---
+
+### Task 9: Safe command-line link and opt-in agent integrations
+
+**Files:**
+
+- Create: `src/lib/agent-integration.ts`,
+  `src/lib/agent-integration.test.ts`
+- Create: `assets/agent-integrations/opencode/flect.js`
+- Modify: `src-tauri/src/lib.rs`
+- Modify: `src/lib/tauri-transport.ts`, `src/lib/tauri-transport.test.ts`
+- Modify: `src/components/diagnostics-panel.tsx`,
+  `src/components/diagnostics-panel.test.tsx`
+- Modify: `src/axi/program.ts`, `src/axi/program.test.ts`
+
+**Interfaces:**
+
+- Native host commands inspect/install/remove only the fixed user link
+  `~/.local/bin/flect` targeting the current installed app executable.
+- Agent integrations implement `status`, `install`, and `remove` for `codex`,
+  `claude`, and `opencode` against an injected configuration root.
+
+- [ ] **Step 1: Write Rust link lifecycle RED tests**
+
+Using a temporary directory and explicit current-executable fixture, prove
+install creates the parent and symlink, reinstall is a no-op, repair updates a
+stale Flect-owned link, a regular file or foreign link is never overwritten,
+and removal deletes only a link targeting Flect.
+
+- [ ] **Step 2: Write agent integration RED tests**
+
+Use temporary roots; never inspect or modify the developer's real config.
+Assert exact idempotent merge/remove behavior:
+
+- Codex: `.codex/hooks.json`, `SessionStart` matcher
+  `startup|resume|clear|compact`, command `flect context --host codex`, and
+  `additionalContextLimit: 1200`; preserve unrelated hook groups.
+- Claude Code: `.claude/settings.local.json`, the same SessionStart matcher,
+  command `flect context --host claude`, and preserved unrelated settings.
+- OpenCode: `.opencode/plugins/flect.js`, a dependency-free V2 plugin with ID
+  `dev.akua.flect-context`; its request hook adds the bounded output of
+  `flect context --host opencode` to `event.system` once per session and after
+  a compaction signal; preserve unrelated plugins/config.
+
+Removal must delete only entries carrying Flect's stable ID/description and
+leave user-authored commands untouched even when text is similar.
+
+- [ ] **Step 3: Confirm RED**
+
+```bash
+cargo test --manifest-path src-tauri/Cargo.toml
+bunx vitest run src/lib/agent-integration.test.ts src/lib/tauri-transport.test.ts src/axi/program.test.ts
+```
+
+- [ ] **Step 4: Implement the fixed native link capability**
+
+Use Tauri's home-directory resolver and Rust symlink APIs. Accept no arbitrary
+source/destination from WebView or CLI. Return a typed status containing
+`absent`, `installed`, `stale`, or `conflict` plus the public link path. Expose
+buttons in Diagnostics with exact confirmation copy and browser-unavailable
+state.
+
+- [ ] **Step 5: Implement Effect-owned config mutations**
+
+Define a platform file capability injected in tests. Decode existing JSON as
+unknown through strict schemas that preserve unrelated JSON values while
+validating the owned subtree. Write atomically with private user permissions.
+Do not invoke host agent CLIs or interactive installers.
+
+Use current authoritative formats verified on 2026-07-31:
+
+- Codex hooks documentation in the current Codex manual;
+- Claude Code hooks at ; and
+- OpenCode V2 plugin request hooks at
+  .
+
+- [ ] **Step 6: Implement `context` and setup commands**
+
+`flect context --host ` emits a static discovery line plus bounded live
+workspace state and role/action suggestions; it never emits the control bearer.
+`flect setup agent install|remove ` uses the Effect integration service.
+`flect setup status` reports shell and all agent integrations with definitive
+states.
+
+- [ ] **Step 7: Run GREEN**
+
+```bash
+cargo test --manifest-path src-tauri/Cargo.toml
+bunx vitest run src/lib/agent-integration.test.ts src/lib/tauri-transport.test.ts src/components/diagnostics-panel.test.tsx src/axi/program.test.ts
+```
+
+- [ ] **Step 8: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- src/lib/agent-integration.ts src-tauri/src/lib.rs src/components/diagnostics-panel.tsx assets/agent-integrations
+```
+
+---
+
+### Task 10: Generated Flect skill and durable repository rules
+
+**Files:**
+
+- Create: `scripts/generate-flect-skill.ts`
+- Create: `scripts/generate-flect-skill.test.ts`
+- Create: `.agents/skills/flect/SKILL.md`
+- Modify: `package.json`
+- Modify: `AGENTS.md`
+
+**Interfaces:**
+
+- `generateFlectSkill({ check })` renders static agent guidance from the same
+  command metadata used by AXI help/home output.
+- `bun run check:flect-skill` fails if the committed skill is stale.
+
+- [ ] **Step 1: Write deterministic generation RED tests**
+
+Assert the generated skill has trigger-shaped frontmatter, names the public
+`flect` command, explains content-first discovery and role limits, contains no
+live workspace IDs/models/control state, and exactly matches the checked-in
+file. Mutating command metadata in the fixture must make check mode fail.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run scripts/generate-flect-skill.test.ts
+```
+
+- [ ] **Step 3: Implement generation and check scripts**
+
+Export static command metadata from `src/axi/command.ts`; do not scrape source
+or shell out to the built CLI. Generate through Effect file services and write
+with `apply_patch` during development. Add `check:flect-skill` to `check`.
+
+- [ ] **Step 4: Add the concise permanent rule to AGENTS.md**
+
+State that all agent-facing command surfaces must follow AXI, use the shared
+Effect command/controller authority, default to bounded TOON, preserve stable
+exit/channel behavior, and update the generated skill when command metadata
+changes. Link to `docs/local-control.md` and the design; do not duplicate the
+command catalog.
+
+- [ ] **Step 5: Run GREEN**
+
+```bash
+bunx vitest run scripts/generate-flect-skill.test.ts
+bun run check:flect-skill
+```
+
+- [ ] **Step 6: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- scripts/generate-flect-skill.ts .agents/skills/flect/SKILL.md AGENTS.md package.json
+```
+
+---
+
+### Task 11: Real-browser AXI and reactive UI verification
+
+**Files:**
+
+- Create: `tests/e2e/embedded-axi.spec.ts`
+- Modify: `tests/e2e/flect.spec.ts`
+- Modify: `server/test-runtime.ts`
+- Modify: `playwright.config.ts`
+
+**Interfaces:**
+
+- Test runtime supplies deterministic Pi turns that issue real Bash commands
+  through the same RPC/shell bridge as production.
+- Tests inspect behavior through UI accessibility, public HTTP/SSE, and
+  command results, never private React state or direct storage mutation.
+
+- [ ] **Step 1: Write the browser RED scenarios**
+
+Add Playwright scenarios that prove:
+
+1. Shaper runs embedded `flect`, validates a file, proposes it, and the preview
+   appears reactively without reload.
+2. Shaper's attempt to accept the proposal returns structured unauthorized
+   output and the protected Keep button remains a user decision.
+3. After Keep and Run mode, App lists and invokes a visible action through
+   embedded `flect`; the result appears in both activity and product UI.
+4. App attempts to shape or enter safe mode and is denied.
+5. Role spoofing through env, alias, function, file, and PATH fails.
+6. TOON output passes through `grep`, `head`, and redirection in just-bash.
+7. Tool use is visible with command, phase, bounded result, duration, and
+   failure state.
+8. Manual upward scrolling is not stolen by streamed CLI/tool activity.
+
+- [ ] **Step 2: Confirm RED in a real production Chromium build**
+
+```bash
+bunx playwright test tests/e2e/embedded-axi.spec.ts --project=chromium
+```
+
+Expected: new AXI scenarios fail before the deterministic runtime supports
+their command trajectories.
+
+- [ ] **Step 3: Extend only the deterministic test runtime inputs needed**
+
+Emit the same `AgentShellRequest`, completion, and stream contracts used by
+production. Do not add a DOM test hook or bypass `SandboxedShell`.
+
+- [ ] **Step 4: Run GREEN and full browser suite**
+
+```bash
+bunx playwright test tests/e2e/embedded-axi.spec.ts --project=chromium
+bun run test:e2e
+```
+
+Expected: all Chromium tests pass with zero unexpected console errors, page
+errors, or failed local application requests.
+
+- [ ] **Step 5: Review the task diff without committing**
+
+```bash
+git diff --check
+git diff -- tests/e2e server/test-runtime.ts playwright.config.ts
+```
+
+---
+
+### Task 12: Release packaging, documentation, installed-app dogfood, and completion audit
+
+**Files:**
+
+- Modify: `scripts/package-release.ts`, `scripts/package-release.test.ts`
+- Modify: `scripts/smoke-pi.ts`
+- Modify: `ARCHITECTURE.md`
+- Modify: `CONTRIBUTING.md`
+- Modify: `README.md`
+- Modify: `VISION.md` only if current-slice wording changes
+- Modify: `docs/local-control.md`
+- Modify: `docs/trust-model.md`
+- Modify: `docs/verification/*`
+
+**Interfaces:**
+
+- Release layout requires only app, DMG, demo/checksum assets, and the private
+  runtime helper inside the app.
+- The public smoke path invokes `Flect.app/Contents/MacOS/flect`; no test or
+  documentation invokes `flectctl` or `flect-mcp`.
+
+- [ ] **Step 1: Write release-layout RED tests**
+
+Change `ReleaseLayout` to remove `cli` and `mcp`. Assert validation succeeds
+with `flect` plus `flect-runtime`, fails without either, and explicitly fails
+when forbidden sibling executables `flectctl` or `flect-mcp` are present in
+`Contents/MacOS`.
+
+- [ ] **Step 2: Confirm RED**
+
+```bash
+bunx vitest run scripts/package-release.test.ts
+```
+
+- [ ] **Step 3: Update release and smoke mechanics**
+
+Remove companion-binary paths/copies/checks. Package one app/DMG. Change smoke
+commands to the public executable's AXI and MCP modes. Never print the control
+descriptor or bearer.
+
+- [ ] **Step 4: Update documentation ownership after implementation exists**
+
+- `ARCHITECTURE.md`: verified one-public-executable topology, embedded agent
+  bus, reserved command, Bash proposal, and role policy.
+- `docs/local-control.md`: complete AXI command/reference, TOON/JSON behavior,
+  exit codes, MCP mode, shell link, setup install/remove, and browser limits.
+- `docs/trust-model.md`: external versus captured-agent authority and failure
+  boundaries.
+- `CONTRIBUTING.md`: current build/test/bundle/dogfood commands and exact bundle
+  contents.
+- `README.md`: concise install, enable-control, `flect` home, browser embedded
+  use, and links; do not duplicate the full catalog.
+- `VISION.md`: only update the implemented-slice paragraph, not the durable
+  destination.
+
+- [ ] **Step 5: Run the complete credential-free verification**
+
+```bash
+bun run check:all
+bun run release:verify
+```
+
+Require clean success for Effect pin check, Rifty check, Biome, TypeScript,
+Vitest, Playwright Chromium, Rust tests, and release-mode app build.
+
+- [ ] **Step 6: Build and install the app**
+
+```bash
+bun run build:desktop -- --bundles app
+```
+
+Move the existing `/Applications/Flect.app` to a timestamped backup location
+only if replacement cannot be done atomically; do not delete it. Install the
+new verified bundle at `/Applications/Flect.app`, then open it with macOS
+LaunchServices.
+
+- [ ] **Step 7: Dogfood public native AXI**
+
+After enabling Local control through the protected UI, run:
+
+```bash
+/Applications/Flect.app/Contents/MacOS/flect
+/Applications/Flect.app/Contents/MacOS/flect inspect
+/Applications/Flect.app/Contents/MacOS/flect shape "Create a compact project dashboard"
+/Applications/Flect.app/Contents/MacOS/flect logs --limit 20
+```
+
+Verify TOON, exact terminal outcomes, reactive preview in the open app, visible
+operation/tool evidence, and no bearer/credential output.
+
+- [ ] **Step 8: Dogfood embedded browser AXI and MCP**
+
+Use a live Shaper turn that runs `flect interface propose`, keep it as the user,
+then use a live App turn that runs `flect action list`. Run a real MCP stdio
+initialize/list/call exchange through:
+
+```bash
+/Applications/Flect.app/Contents/MacOS/flect mcp
+```
+
+Capture bounded verification evidence in `docs/verification/` without secrets.
+
+- [ ] **Step 9: Audit every objective requirement**
+
+Create a table mapping each acceptance criterion in the design to authoritative
+evidence: source contract, focused test, full gate output, bundle listing,
+browser trace/screenshot, native command output, or installed-app observation.
+Treat missing or indirect evidence as incomplete and continue implementation.
+
+- [ ] **Step 10: Run stale-name and secret scans**
+
+```bash
+rg -n "flectctl|flect-mcp" --glob '!docs/superpowers/**' --glob '!docs/verification/**' .
+rg -n "Bearer |Authorization:|api[_-]?key|access[_-]?token" docs/verification dist-release 2>/dev/null
+git diff --check
+git status --short
+```
+
+Only deliberate historical migration references may remain outside active
+implementation/user documentation. Inspect every secret-shaped match.
+
+- [ ] **Step 11: Review the full diff without committing**
+
+```bash
+git diff --stat
+git diff --check
+git status --short
+```
+
+Do not commit, push, merge, publish, or create a release until the user gives
+that separate authorization.
+
+---
+
+## Plan self-review
+
+### Spec coverage
+
+- One public native executable: Tasks 8 and 12.
+- No public `flectctl`/`flect-mcp`: Tasks 2, 8, and 12.
+- AXI/TOON semantics: Tasks 1, 2, and 10.
+- One Effect command/state path: Tasks 3 and 4.
+- Browser-reserved command and role source capture: Task 5.
+- Shaper and App drive Flect through Bash: Tasks 6 and 7.
+- Guardian/tool and sandbox boundaries: Tasks 3, 5, and 6.
+- MCP/HTTP compatibility: Tasks 2, 4, and 8.
+- Shell link and Codex/Claude/OpenCode opt-in integrations: Task 9.
+- Generated Agent Skill and durable rule: Task 10.
+- Real-browser, native, package, install, open, and dogfood evidence: Tasks 11
+  and 12.
+- Documentation ownership and completion audit: Task 12.
+
+### Type consistency
+
+- `AxiRunResult` is produced only by the AXI formatter/program and consumed by
+  process/just-bash adapters.
+- `FlectCommandGateway` is the sole command program dependency; broker and
+  agent implementations share its exact shape.
+- `AgentCommandSource` is created by the sandbox adapter and enforced by the
+  controller.
+- `AgentCommandBus` carries typed gateway operations and exact Deferred
+  results; it is not persisted or schema-encoded.
+- Shaper's proposal latch holds only an already validated InterfaceDocument;
+  ShapingKernel remains revision authority.
+
+### Placeholder scan
+
+The plan contains no TBD/TODO/later placeholders. Conditional removal steps
+have explicit parity gates and commands. The only conditional smoke outcome is
+called out as not-a-pass and must be resolved before completion.
diff --git a/docs/superpowers/plans/2026-07-31-flect-observable-control.md b/docs/superpowers/plans/2026-07-31-flect-observable-control.md
new file mode 100644
index 0000000..b017b26
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-31-flect-observable-control.md
@@ -0,0 +1,1351 @@
+# Flect Observable Control Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use
+> `superpowers:executing-plans` to implement this plan task-by-task. Steps use
+> checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make Flect's live workspace reliable, observable, user-respecting,
+and fully controllable by an explicitly authorized local CLI, JSON/SSE API, or
+MCP client through the same Effect command authority used by the UI.
+
+**Architecture:** One schema-defined `FlectWorkspaceController` in the
+connected browser/WebView owns semantic commands and publishes one reactive
+snapshot and event stream. The Bun runtime owns only an authenticated,
+lifecycle-scoped loopback broker that forwards commands to the connected
+workspace; React, HTTP, CLI, and MCP remain adapters rather than alternate
+state machines. Pi emits exact proposal and tool lifecycle events, while a
+bounded in-memory `OperationJournal` keeps safe correlated evidence.
+
+**Tech Stack:** TypeScript 7, Bun, Effect 4 beta, Effect Schema/Layer/Stream/
+SubscriptionRef/Queue/Scope, Pi SDK, TypeBox, React 19, Effect HTTP/RPC,
+Tauri 2, Vitest with `@effect/vitest`, Playwright, Rust tests.
+
+## Global Constraints
+
+- Preserve all existing uncommitted T3 Code UX, Markdown, and verification
+  work.
+- Do not commit, push, merge, publish, or release without separate authority.
+- External control is loopback-only, disabled by default, explicitly enabled
+  in the protected UI, authenticated by a rotating 256-bit capability, and
+  immediately revocable.
+- No outside client may enable or expand its own authority.
+- The connected Flect workspace remains authoritative; the broker stores no
+  shadow revision, conversation, model, or UI state.
+- Every external value crosses an Effect Schema boundary with strict excess
+  property rejection.
+- Use `Schema.Class`, `Schema.TaggedClass`, and `Schema.TaggedErrorClass` for
+  reusable contracts; use `.make(...)`, never unsafe casts or `any`.
+- Define services with `Context.Service`, construct them in named Layers,
+  provide dependencies at composition edges, and use `ManagedRuntime` only at
+  host boundaries.
+- Use `Effect.fn` for named business operations, `SubscriptionRef` for
+  reactive state, `Stream` for events, `Queue`/`Deferred` for command
+  hand-off, and `Effect.acquireRelease`/Scope for broker and session cleanup.
+- Pi provider credentials and bearer capabilities never enter logs, events,
+  screenshots, model context, argv, or API response bodies.
+- App Agent, Shaper, and Guardian remain separate Pi roles; their sandbox and
+  extension policies remain intact.
+- The browser remains a first-class runtime; no feature may require a
+  machine-installed shell, Git, Bun, or native binary.
+- Operation history is in-memory only, bounded to 500 entries and 2 MiB, and
+  contains safe summaries rather than full prompt, output, or credential data.
+- Sticky follow uses a 48 px bottom threshold, suspends after the user moves
+  away, never steals focus, and exposes a keyboard-operable “Jump to latest”
+  control.
+- Test observable behavior through public services, HTTP/RPC contracts, DOM
+  semantics, CLI output, and real browser/native flows.
+
+---
+
+## File Structure
+
+### Shared contracts
+
+- `shared/control.ts` — versioned command envelope, command union, snapshots,
+  events, receipts, operation records, client metadata, and protocol errors.
+- `shared/contracts.ts` — Pi turn/tool/proposal event contracts shared by HTTP,
+  RPC, browser, and desktop.
+- `shared/rpc.ts` — private desktop workspace-channel RPCs in addition to the
+  existing Pi runtime RPCs.
+
+### Pi runtime
+
+- `server/pi-proposal-tool.ts` — exact TypeBox proposal tool and safe Effect
+  validation diagnostic mapping.
+- `server/pi-runtime.ts` — registers proposal and Bash tools, streams their
+  lifecycle, performs one bounded corrective retry, and preserves sessions on
+  validation failure.
+- `server/test-runtime.ts` — deterministic equivalents of the new events.
+
+### Connected workspace
+
+- `src/lib/operation-journal.ts` — bounded `OperationJournal` service.
+- `src/lib/agent-workspace.ts` — Effect-owned App/Shaper session lifecycle and
+  role timelines extracted from the React hook.
+- `src/lib/workspace-controller.ts` — sole semantic command/state authority.
+- `src/lib/workspace-control-bridge.ts` — browser HTTP and desktop RPC bridge
+  adapters that register the workspace, deliver commands, and publish
+  snapshots/events/receipts.
+- `src/hooks/use-workspace.ts` — thin React subscription and dispatch adapter.
+- `src/lib/runtime.ts` — one composed browser application runtime.
+
+### Interface
+
+- `src/components/activity-card.tsx` — live tool and operation instruments.
+- `src/components/diagnostics-panel.tsx` — filters and safe structured detail.
+- `src/hooks/use-sticky-follow.ts` — per-role viewport-follow policy.
+- `src/components/agent-rail.tsx` — consumes controller state and new
+  instruments without re-owning workflow state.
+- `src/components/role-aware-shell.tsx` — protected control toggle and client
+  visibility.
+- `src/app.tsx` — renders controller snapshot and dispatches typed commands.
+- `src/styles.css` — activity, diagnostics, sticky-follow, and attribution
+  styling.
+
+### Local broker and adapters
+
+- `server/control-descriptor.ts` — secure descriptor path, encode/decode,
+  permissions, stale-instance rejection, and cleanup.
+- `server/control-broker.ts` — scoped token, queue, state cache, waiters,
+  external loopback listener, and workspace registration.
+- `server/control-http.ts` — authenticated JSON/SSE external API.
+- `server/app.ts` — protected browser workspace-channel routes.
+- `server/rpc-handlers.ts` — private desktop workspace-channel handlers.
+- `server/index.ts` and `server/sidecar.ts` — compose one shared broker layer.
+- `cli/flect-client.ts` — descriptor discovery and schema-safe HTTP/SSE client.
+- `cli/flectctl.ts` — human and JSON CLI.
+- `cli/flect-mcp.ts` — MCP stdio adapter over the same client.
+- `scripts/build-sidecar.ts`, `src-tauri/tauri.conf.json`, and release
+  packaging files — build and bundle `flectctl`.
+
+### Verification and documentation
+
+- Unit tests adjacent to every new module.
+- `tests/e2e/flect.spec.ts` — real browser UI behavior.
+- `tests/e2e/control-plane.spec.ts` — browser plus real `flectctl`.
+- `tests/e2e/native-control.spec.ts` or the existing native smoke harness —
+  packaged-app control.
+- `ARCHITECTURE.md`, `DESIGN.md`, `PRODUCT.md`, `README.md`, `VISION.md`, and
+  `AGENTS.md` — only the information owned by each document.
+- `docs/verification/flect-observable-control/` — commands, screenshots, safe
+  logs, and final evidence.
+
+---
+
+### Task 1: Define the versioned command, snapshot, event, and error contracts
+
+**Files:**
+- Create: `shared/control.ts`
+- Create: `shared/control.test.ts`
+- Modify: `shared/contracts.ts`
+- Modify: `shared/contracts.test.ts`
+
+**Interfaces:**
+- Produces:
+  - `FlectCommandEnvelope`
+  - `FlectCommand`
+  - `FlectWorkspaceSnapshot`
+  - `FlectWorkspaceEvent`
+  - `FlectCommandReceipt`
+  - `FlectCommandError`
+  - `OperationRecord`
+  - `ToolActivity`
+  - `ControlClientSummary`
+- Consumes: existing `InterfaceDocument`, `InteractiveAgentRole`,
+  `ModelSummary`, `ShapingSnapshot`, and shell result summaries.
+
+- [ ] **Step 1: Write failing schema round-trip and rejection tests**
+
+```ts
+it.effect("round-trips every command and rejects unknown fields", () =>
+  Effect.gen(function* () {
+    const value = FlectCommandEnvelope.make({
+      version: 1,
+      commandId: "cmd-00000001",
+      workspaceId: "workspace-00000001",
+      source: UserCommandSource.make({ kind: "user" }),
+      command: SubmitShaperInstruction.make({
+        type: "submit-shaper-instruction",
+        instruction: "Make the headline quieter"
+      })
+    })
+    const encoded = yield* Schema.encodeEffect(FlectCommandEnvelope)(value)
+    const decoded = yield* decodeFlectCommandEnvelope(encoded)
+    assert.deepStrictEqual(decoded, value)
+    const exit = yield* Effect.exit(
+      decodeFlectCommandEnvelope({ ...encoded, invented: true })
+    )
+    assert.isTrue(Exit.isFailure(exit))
+  })
+)
+```
+
+Add table-driven cases for all commands and prove:
+
+- control sources require `clientId` and `clientName`;
+- `enable-control` decodes but is later authorizable only for user sources;
+- instruction, prompt, width, identifier, and log-detail bounds hold;
+- unknown command tags, button actions, and excess keys fail closed.
+
+- [ ] **Step 2: Run the focused tests and confirm red**
+
+Run:
+
+```bash
+bunx vitest run shared/control.test.ts shared/contracts.test.ts
+```
+
+Expected: FAIL because the new schemas and event variants do not exist.
+
+- [ ] **Step 3: Implement reusable named schemas and tagged unions**
+
+Use this public shape:
+
+```ts
+export class FlectCommandEnvelope extends Schema.Class(
+  "FlectCommandEnvelope"
+)({
+  version: Schema.Literal(1),
+  commandId: CommandId,
+  workspaceId: WorkspaceId,
+  source: FlectCommandSource,
+  expectedSequence: Schema.optionalKey(
+    Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
+  ),
+  command: FlectCommand
+}) {}
+
+export const decodeFlectCommandEnvelope = Schema.decodeUnknownEffect(
+  FlectCommandEnvelope,
+  { errors: "all", onExcessProperty: "error" }
+)
+```
+
+Define each command as a `Schema.TaggedClass` with its literal `type`.
+Represent expected protocol failures as `Schema.TaggedErrorClass` variants:
+`InvalidControlCommand`, `ControlUnauthorized`, `WorkspaceUnavailable`,
+`CommandConflict`, `CommandRejected`, and `OperationFailed`.
+
+Extend Pi events with exact, bounded variants:
+
+```ts
+ToolExecutionStarted
+ToolExecutionUpdated
+ToolExecutionCompleted
+ProposalValidationFailed
+```
+
+Tool records carry role, call ID, tool name, phase, started/completed
+timestamps, duration, bounded command/result summaries, exit code, and optional
+preview URL; no raw credentials or unlimited output.
+
+- [ ] **Step 4: Run focused tests and typecheck**
+
+```bash
+bunx vitest run shared/control.test.ts shared/contracts.test.ts
+bun run typecheck
+```
+
+Expected: all focused tests PASS and TypeScript reports no errors.
+
+- [ ] **Step 5: Inspect the diff for duplicate contracts**
+
+```bash
+rg -n "FlectCommandEnvelope|OperationRecord|ToolActivity" shared src server
+git diff --check
+```
+
+Expected: each reusable contract has one shared source of truth and
+`git diff --check` prints nothing.
+
+### Task 2: Replace free-form Shaper JSON with a terminating proposal tool
+
+**Files:**
+- Create: `server/pi-proposal-tool.ts`
+- Create: `server/pi-proposal-tool.test.ts`
+- Modify: `server/pi-runtime.ts`
+- Modify: `server/pi-runtime.test.ts`
+- Modify: `server/test-runtime.ts`
+- Modify: `server/test-runtime.test.ts` if present
+
+**Interfaces:**
+- Consumes: `InterfaceDocument`, `validateInterfaceDocument`,
+  `ProposalValidationFailed`, and Pi `defineTool`.
+- Produces:
+  - `makePiProposalTool(onEvent)`
+  - `InterfaceProposalValidationError`
+  - shape streams that terminate only after a validated proposal or one
+    bounded corrective retry.
+
+- [ ] **Step 1: Write failing exact-schema and diagnostic tests**
+
+```ts
+it.effect("accepts only an exact InterfaceDocument proposal", () =>
+  Effect.gen(function* () {
+    const capture = yield* makeProposalCapture()
+    const tool = yield* makePiProposalTool(capture.emit)
+    const result = yield* Effect.promise(() =>
+      tool.execute("tool-1", { document: validDocument }, undefined)
+    )
+    assert.strictEqual(result.details.status, "accepted")
+    assert.deepStrictEqual(yield* capture.proposal, validDocument)
+  })
+)
+```
+
+Also prove that missing `text.style`, invented button actions,
+`agent-panel.children`, missing `agent-panel.title`, excess properties,
+oversized trees, and reused node IDs produce safe issues such as:
+
+```ts
+{
+  path: ["root", "children", 0, "style"],
+  code: "required",
+  message: "Required field is missing."
+}
+```
+
+- [ ] **Step 2: Run focused tests and confirm red**
+
+```bash
+bunx vitest run server/pi-proposal-tool.test.ts server/pi-runtime.test.ts
+```
+
+Expected: FAIL because `propose_interface` and validation issue events are
+absent.
+
+- [ ] **Step 3: Implement the TypeBox tool from the exact closed UI schema**
+
+Define `propose_interface` with:
+
+```ts
+defineTool({
+  name: "propose_interface",
+  label: "Propose interface",
+  description: "Submit one complete Flect InterfaceDocument.",
+  parameters: Type.Object(
+    { document: interfaceDocumentTypeBox },
+    { additionalProperties: false }
+  ),
+  executionMode: "sequential",
+  terminate: true,
+  promptSnippet: "Use propose_interface for the final complete interface.",
+  promptGuidelines: [
+    "Never invent node fields, node types, or button actions.",
+    "Every text node requires style.",
+    "Only stack nodes have children."
+  ],
+  execute
+})
+```
+
+Validate again with Effect Schema inside `execute`; TypeBox is model guidance,
+not the application trust boundary. Convert `SchemaError` issues to bounded
+safe paths and reasons.
+
+- [ ] **Step 4: Register tool lifecycle events and one corrective retry**
+
+In `server/pi-runtime.ts`:
+
+- subscribe to Pi `tool_execution_start`, `tool_execution_update`, and
+  `tool_execution_end`;
+- emit the shared lifecycle variants for Bash and proposal tools;
+- retain the Shaper Pi session when proposal validation fails;
+- append the safe validation issues as the single retry instruction;
+- permit exactly one retry per shape command;
+- return `ProposalValidationFailed` after the second invalid proposal;
+- remove the broad first-`{`/last-`}` JSON extraction path.
+
+- [ ] **Step 5: Update deterministic runtime fixtures**
+
+Make the test runtime emit the same event order:
+
+```text
+turn/tool started -> proposal tool started -> proposal tool completed
+-> shape completed
+```
+
+Add an instruction keyword used only by tests that yields a controlled
+validation failure event without weakening production validation.
+
+- [ ] **Step 6: Run focused runtime tests and the real Pi smoke**
+
+```bash
+bunx vitest run server/pi-proposal-tool.test.ts server/pi-runtime.test.ts server/app.test.ts server/rpc-handlers.test.ts
+bun run test:pi-smoke
+```
+
+Expected: unit tests PASS; real Pi smoke returns one validated document and no
+generic parse failure.
+
+### Task 3: Add the bounded structured operation journal
+
+**Files:**
+- Create: `src/lib/operation-journal.ts`
+- Create: `src/lib/operation-journal.test.ts`
+
+**Interfaces:**
+- Consumes: `OperationRecord`, `FlectWorkspaceEvent`, `ToolActivity`.
+- Produces:
+
+```ts
+interface OperationJournalShape {
+  readonly snapshot: Effect.Effect>
+  readonly changes: Stream.Stream>
+  readonly append: (
+    event: OperationJournalInput
+  ) => Effect.Effect
+  readonly query: (
+    filter: OperationFilter
+  ) => Effect.Effect>
+}
+```
+
+- [ ] **Step 1: Write failing retention, correlation, and redaction tests**
+
+Test:
+
+- lifecycle order and monotonic sequence;
+- `operationId`, `commandId`, `sessionId`, `toolCallId`, and revision
+  correlation;
+- eviction at 500 records;
+- eviction before encoded size exceeds 2 MiB;
+- query filters for role, status, operation, tool, revision, client, and
+  failures;
+- rejection/redaction of bearer tokens, authorization headers, provider
+  secrets, and full prompts.
+
+- [ ] **Step 2: Run and confirm red**
+
+```bash
+bunx vitest run src/lib/operation-journal.test.ts
+```
+
+Expected: FAIL because the service does not exist.
+
+- [ ] **Step 3: Implement the service with `SubscriptionRef`**
+
+Construct one named layer with:
+
+```ts
+export class OperationJournal extends Context.Service<
+  OperationJournal,
+  OperationJournalShape
+>()("flect/OperationJournal") {}
+
+export const OperationJournalLive = Layer.effect(
+  OperationJournal,
+  Effect.gen(function* () {
+    const state = yield* SubscriptionRef.make(JournalState.empty())
+    // append/query/snapshot/changes
+    return implementation
+  })
+)
+```
+
+Use `Schema.encodeEffect(OperationRecord)` to count encoded bytes. Redact at
+the append boundary, not in renderers.
+
+- [ ] **Step 4: Run focused tests**
+
+```bash
+bunx vitest run src/lib/operation-journal.test.ts
+bun run typecheck
+```
+
+Expected: PASS.
+
+### Task 4: Extract Pi session and role state into an Effect service
+
+**Files:**
+- Create: `src/lib/agent-workspace.ts`
+- Create: `src/lib/agent-workspace.test.ts`
+- Modify: `src/hooks/use-agent-session.ts`
+- Modify: `src/hooks/use-agent-session.test.tsx`
+
+**Interfaces:**
+- Consumes: `FlectClient`, `SandboxedShell`, `OperationJournal`,
+  `SessionSelection`, and the new Pi events.
+- Produces:
+
+```ts
+interface AgentWorkspaceShape {
+  readonly snapshot: Effect.Effect
+  readonly changes: Stream.Stream
+  readonly refresh: Effect.Effect
+  readonly selectModel: (
+    selection: ModelSelection | undefined
+  ) => Effect.Effect
+  readonly toggleExternalExtensions: (
+    role: InteractiveAgentRole
+  ) => Effect.Effect
+  readonly submitAppPrompt: (
+    operation: OperationContext,
+    text: string
+  ) => Effect.Effect
+  readonly submitShaperInstruction: (
+    operation: OperationContext,
+    instruction: string,
+    document: InterfaceDocument
+  ) => Effect.Effect
+  readonly cancel: (
+    role: InteractiveAgentRole
+  ) => Effect.Effect
+}
+```
+
+- [ ] **Step 1: Port current hook behavior into failing service tests**
+
+Cover session reuse, model-change session replacement, per-role busy guards,
+App/Shaper concurrency, cancellation, shell completion, extension selection,
+setup-required/unavailable states, tool activity updates, validation failure
+without session closure, and scoped finalization.
+
+- [ ] **Step 2: Run focused tests and confirm red**
+
+```bash
+bunx vitest run src/lib/agent-workspace.test.ts
+```
+
+Expected: FAIL because `AgentWorkspace` is absent.
+
+- [ ] **Step 3: Implement `AgentWorkspaceLive`**
+
+Move session handles, role fibers, cancellation flags, models, selections,
+messages, and tool activity into one `SubscriptionRef`. Use:
+
+```ts
+Effect.acquireRelease(createOrReuseSession, closeSession)
+Fiber.interrupt
+Effect.onInterrupt
+Stream.runForEach
+```
+
+Tool start creates an activity; update changes the same call ID; end records
+duration and bounded result. A proposal validation error becomes a structured
+Shaper timeline item and does not release an otherwise healthy Pi session.
+
+- [ ] **Step 4: Reduce the React hook to a compatibility adapter**
+
+Until Task 6 removes it from `App`, make `useAgentSession` subscribe to
+`AgentWorkspace.changes` and invoke service methods. It must not retain its
+own session, model, fiber, or message refs.
+
+- [ ] **Step 5: Run service and existing hook tests**
+
+```bash
+bunx vitest run src/lib/agent-workspace.test.ts src/hooks/use-agent-session.test.tsx
+bun run typecheck
+```
+
+Expected: PASS with the previous observable hook contract preserved.
+
+### Task 5: Build the sole Effect workspace command/state authority
+
+**Files:**
+- Create: `src/lib/workspace-controller.ts`
+- Create: `src/lib/workspace-controller.test.ts`
+- Modify: `src/lib/runtime.ts`
+
+**Interfaces:**
+- Consumes: `AgentWorkspace`, `ShapingKernel`, `ShellPreferences`,
+  `ExtensionExecution`, `OperationJournal`, `FlectCommandEnvelope`.
+- Produces:
+
+```ts
+interface FlectWorkspaceControllerShape {
+  readonly snapshot: Effect.Effect
+  readonly changes: Stream.Stream
+  readonly events: Stream.Stream
+  readonly dispatch: (
+    envelope: FlectCommandEnvelope
+  ) => Effect.Effect
+}
+```
+
+- [ ] **Step 1: Write failing state-machine tests**
+
+Use test layers and exercise only `dispatch`. Prove:
+
+- every command tag has a deterministic receipt and event;
+- commands from user and control sources reach the same transition;
+- model favorites and App/Shaper external-extension toggles update the same
+  reactive state for either source;
+- duplicate `commandId` returns the prior receipt without rerunning;
+- the recent command window is bounded;
+- stale `expectedSequence` returns `CommandConflict`;
+- `enable-control` with `source.kind === "control"` returns
+  `ControlUnauthorized`;
+- shape creates a proposal preview; accept/reject/rollback/safe-mode preserve
+  ShapingKernel invariants;
+- interface action invocation resolves a node ID from the current validated
+  document and rejects stale/unknown IDs;
+- long-running operations are cancellable and publish terminal events;
+- each transition appears in `OperationJournal`.
+
+- [ ] **Step 2: Run focused tests and confirm red**
+
+```bash
+bunx vitest run src/lib/workspace-controller.test.ts
+```
+
+Expected: FAIL because the controller does not exist.
+
+- [ ] **Step 3: Implement controller state and dispatch**
+
+Use one `SubscriptionRef`, one bounded result cache, and one
+broadcast event source. Keep command authorization separate:
+
+```ts
+const authorize = Effect.fn("Flect.Workspace.authorize")(function* (
+  envelope: FlectCommandEnvelope
+) {
+  if (
+    envelope.command.type === "enable-control" &&
+    envelope.source.kind !== "user"
+  ) {
+    return yield* Effect.fail(ControlUnauthorized.make({
+      message: "Outside clients cannot enable control."
+    }))
+  }
+})
+```
+
+Dispatch the closed union with exhaustive `Match.value(...).pipe(...)` or
+equivalent typed branching. Do not accept arbitrary callbacks or action
+strings.
+
+- [ ] **Step 4: Compose one browser application runtime**
+
+Replace the split browser/shaping ownership with one named top-level layer:
+
+```ts
+const FlectApplicationLive = WorkspaceControllerLive.pipe(
+  Layer.provideMerge(WorkspaceDependenciesLive)
+)
+
+export const flectRuntime = ManagedRuntime.make(FlectApplicationLive)
+```
+
+Keep compatibility exports temporarily if existing tests need them, but make
+them aliases over the same managed runtime rather than separate service
+instances.
+
+- [ ] **Step 5: Run controller, kernel, and repository tests**
+
+```bash
+bunx vitest run src/lib/workspace-controller.test.ts src/lib/shaping-kernel.test.ts src/lib/interface-repository.test.ts
+bun run typecheck
+```
+
+Expected: PASS.
+
+### Task 6: Make React a reactive adapter and render visible activity
+
+**Files:**
+- Create: `src/hooks/use-workspace.ts`
+- Create: `src/hooks/use-workspace.test.tsx`
+- Create: `src/components/activity-card.tsx`
+- Create: `src/components/activity-card.test.tsx`
+- Create: `src/components/diagnostics-panel.tsx`
+- Create: `src/components/diagnostics-panel.test.tsx`
+- Modify: `src/app.tsx`
+- Modify: `src/app.test.tsx`
+- Modify: `src/components/agent-rail.tsx`
+- Modify: `src/components/agent-rail.test.tsx`
+- Modify: `src/components/role-aware-shell.tsx`
+- Modify: `src/components/role-aware-shell.test.tsx`
+- Modify: `src/styles.css`
+
+**Interfaces:**
+- Consumes: `FlectWorkspaceController.snapshot/changes/events/dispatch`.
+- Produces: semantic UI controls that dispatch command envelopes and display
+  the controller's exact reactive state.
+
+- [ ] **Step 1: Write failing adapter and activity UI tests**
+
+Prove:
+
+- external-source events update the rendered mode, messages, proposal, model,
+  and attribution without reload;
+- Bash and proposal tool cards show queued/running/succeeded/failed status,
+  duration, command summary, exit code, output disclosure, and preview action;
+- failed proposal shows exact safe paths and a retry action;
+- operations can be filtered and correlation IDs copied;
+- connected clients and a single protected enable/disable control are visible;
+- toggling enable dispatches a user-source command;
+- tool disclosures are keyboard and screen-reader operable.
+
+- [ ] **Step 2: Run focused component tests and confirm red**
+
+```bash
+bunx vitest run src/hooks/use-workspace.test.tsx src/components/activity-card.test.tsx src/components/diagnostics-panel.test.tsx src/app.test.tsx
+```
+
+Expected: FAIL because the reactive adapter and instruments do not exist.
+
+- [ ] **Step 3: Implement the thin React adapter**
+
+`useWorkspace`:
+
+- reads the initial snapshot through `flectRuntime`;
+- subscribes to `changes` in a scoped fiber;
+- interrupts that fiber on unmount;
+- builds user-source envelopes with `crypto.randomUUID()`;
+- exposes `dispatch(command, expectedSequence?)`;
+- owns no parallel session, revision, operation, or model state.
+
+- [ ] **Step 4: Implement compact T3-like activity instruments**
+
+Use a quiet one-line collapsed card:
+
+```text
+● Bash · bun test · 1.2s
+```
+
+Expanded content uses labelled sections for input, bounded output, validation
+issues, preview URL, operation/correlation IDs, source attribution, and retry.
+Use existing typography and icon primitives rather than adding a second
+visual system.
+
+- [ ] **Step 5: Refactor `App` and shell controls onto typed commands**
+
+Remove direct `useState` workflow ownership and direct calls to
+`ShapingKernel`, `useAgentSession`, or shell preferences. `App` renders the
+controller snapshot; buttons dispatch exact command classes. Preserve only
+ephemeral draft, focus, disclosure, and scroll state in React.
+
+- [ ] **Step 6: Run component tests and accessibility checks**
+
+```bash
+bunx vitest run src/hooks/use-workspace.test.tsx src/components/activity-card.test.tsx src/components/diagnostics-panel.test.tsx src/app.test.tsx src/components/agent-rail.test.tsx src/components/role-aware-shell.test.tsx
+bun run typecheck
+```
+
+Expected: PASS with no inaccessible-name or focus-order regressions.
+
+### Task 7: Add per-role sticky follow and jump-to-latest
+
+**Files:**
+- Create: `src/hooks/use-sticky-follow.ts`
+- Create: `src/hooks/use-sticky-follow.test.tsx`
+- Modify: `src/components/agent-rail.tsx`
+- Modify: `src/components/agent-rail.test.tsx`
+- Modify: `src/styles.css`
+
+**Interfaces:**
+- Produces:
+
+```ts
+interface StickyFollow {
+  readonly containerRef: RefObject
+  readonly following: boolean
+  readonly unreadCount: number
+  readonly jumpToLatest: () => void
+}
+```
+
+- [ ] **Step 1: Write failing scroll-policy tests**
+
+With a controllable scroll element prove:
+
+- initial and already-near-bottom updates follow;
+- distance greater than 48 px suspends follow;
+- streaming updates while suspended do not change `scrollTop`;
+- unread count increments by message/tool update;
+- jump uses non-focus-stealing scroll and resumes follow;
+- switching roles restores each role's own follow state;
+- reduced motion selects instant behavior.
+
+- [ ] **Step 2: Run focused tests and confirm red**
+
+```bash
+bunx vitest run src/hooks/use-sticky-follow.test.tsx src/components/agent-rail.test.tsx
+```
+
+Expected: FAIL.
+
+- [ ] **Step 3: Implement the hook and disable native anchoring**
+
+Observe `scroll` and content size, compute:
+
+```ts
+const distance = scrollHeight - scrollTop - clientHeight
+const nearBottom = distance <= 48
+```
+
+Set `overflow-anchor: none` on the timeline. Only set `scrollTop` when
+following or after explicit jump. Do not call `.focus()`.
+
+- [ ] **Step 4: Render the accessible jump control**
+
+Render `Jump to latest` with optional unread count only while suspended.
+Place it inside the timeline footer so it does not cover composer controls.
+
+- [ ] **Step 5: Run focused tests**
+
+```bash
+bunx vitest run src/hooks/use-sticky-follow.test.tsx src/components/agent-rail.test.tsx
+```
+
+Expected: PASS.
+
+### Task 8: Build the scoped local control broker and protected descriptor
+
+**Files:**
+- Create: `server/control-descriptor.ts`
+- Create: `server/control-descriptor.test.ts`
+- Create: `server/control-broker.ts`
+- Create: `server/control-broker.test.ts`
+- Create: `server/control-http.ts`
+- Create: `server/control-http.test.ts`
+
+**Interfaces:**
+- Consumes: shared control schemas.
+- Produces:
+
+```ts
+interface FlectControlBrokerShape {
+  readonly status: Effect.Effect
+  readonly enable: (
+    workspace: WorkspaceRegistration
+  ) => Effect.Effect
+  readonly disable: Effect.Effect
+  readonly nextCommand: (
+    workspaceId: string
+  ) => Effect.Effect
+  readonly complete: (
+    receipt: FlectCommandReceipt
+  ) => Effect.Effect
+  readonly publishSnapshot: (
+    snapshot: FlectWorkspaceSnapshot
+  ) => Effect.Effect
+  readonly publishEvent: (
+    event: FlectWorkspaceEvent
+  ) => Effect.Effect
+}
+```
+
+- [ ] **Step 1: Write failing descriptor and lifecycle tests**
+
+Test in a temporary explicit directory:
+
+- 256-bit token generation;
+- random `127.0.0.1` port only;
+- directory `0700`, descriptor `0600`;
+- exact version/url/token/pid/instance/workspace schema;
+- stale PID or instance rejection and cleanup;
+- disable interrupts queue waiters, removes descriptor, rotates token, and
+  rejects the prior bearer;
+- scope release performs the same cleanup.
+
+- [ ] **Step 2: Write failing external API security tests**
+
+Prove:
+
+- missing, malformed, and stale bearer returns 401 without detail leakage;
+- non-loopback bind is rejected at construction;
+- body and header limits apply before decode;
+- `enable-control` is rejected externally;
+- inspect, commands, logs, and SSE events use the shared schemas;
+- command POST waits for the workspace receipt;
+- disconnected workspace returns typed 409/503 behavior;
+- no API response includes the bearer.
+
+- [ ] **Step 3: Run focused tests and confirm red**
+
+```bash
+bunx vitest run server/control-descriptor.test.ts server/control-broker.test.ts server/control-http.test.ts
+```
+
+Expected: FAIL.
+
+- [ ] **Step 4: Implement secure descriptor handling**
+
+Use `Effect.tryPromise` around filesystem boundaries with typed errors. Write
+to a temporary file in the same directory, chmod, then rename atomically.
+Resolve paths from `XDG_STATE_HOME` or the platform user state directory; test
+layers inject an explicit directory and never target `HOME`, `~`, or `/`.
+
+- [ ] **Step 5: Implement the scoped broker**
+
+Use `Queue`, `Deferred`, `SubscriptionRef`, and `Effect.acquireRelease`.
+Maintain only:
+
+- active grant metadata;
+- connected workspace metadata;
+- latest published snapshot;
+- bounded published events;
+- pending command receipts.
+
+Never reconstruct or mutate workspace state in the broker.
+
+- [ ] **Step 6: Implement the authenticated Effect HTTP/SSE listener**
+
+Expose versioned routes:
+
+```text
+GET  /v1/status
+GET  /v1/instances
+GET  /v1/workspaces/:id
+GET  /v1/workspaces/:id/events
+GET  /v1/workspaces/:id/logs
+POST /v1/workspaces/:id/commands
+POST /v1/control/disable
+```
+
+Start on `127.0.0.1:0`, discover the assigned port, and write the descriptor
+only after the server is accepting connections.
+
+- [ ] **Step 7: Run focused tests and leak scans**
+
+```bash
+bunx vitest run server/control-descriptor.test.ts server/control-broker.test.ts server/control-http.test.ts
+rg -n "authorization|bearer|token" server/control-*.ts shared/control.ts
+```
+
+Expected: tests PASS; every sensitive-value use is confined to authentication
+or descriptor code and never logging.
+
+### Task 9: Connect browser and packaged desktop workspaces to the broker
+
+**Files:**
+- Create: `src/lib/workspace-control-bridge.ts`
+- Create: `src/lib/workspace-control-bridge.test.ts`
+- Modify: `server/app.ts`
+- Modify: `server/app.test.ts`
+- Modify: `shared/rpc.ts`
+- Modify: `server/rpc-handlers.ts`
+- Modify: `server/rpc-handlers.test.ts`
+- Modify: `src/lib/tauri-transport.ts`
+- Modify: `src/lib/tauri-transport.test.ts`
+- Modify: `server/index.ts`
+- Modify: `server/sidecar.ts`
+- Modify: `src/lib/runtime.ts`
+
+**Interfaces:**
+- Consumes: `FlectControlBroker`,
+  `FlectWorkspaceController.dispatch/changes/events`.
+- Produces: one `WorkspaceControlBridge` service whose browser and desktop
+  layers have identical semantics.
+
+- [ ] **Step 1: Write failing browser channel tests**
+
+Test:
+
+- enable/register requires an allowed browser `Origin`;
+- no-origin and cross-origin callers cannot impersonate the UI;
+- the UI receives queued commands, posts typed receipts, and publishes state
+  and events;
+- disable closes the command stream;
+- reconnect uses the same active workspace but a new channel instance;
+- control commands appear immediately in the controller snapshot.
+
+- [ ] **Step 2: Write failing desktop RPC channel tests**
+
+Add streaming/private RPCs:
+
+```text
+ControlEnable
+ControlDisable
+ControlStatus
+ControlRegisterWorkspace
+ControlCommands (stream)
+ControlComplete
+ControlPublishSnapshot
+ControlPublishEvent
+```
+
+Prove schema round-trip, stream interruption, broker sharing, and parity with
+browser receipts.
+
+- [ ] **Step 3: Run focused tests and confirm red**
+
+```bash
+bunx vitest run src/lib/workspace-control-bridge.test.ts server/app.test.ts server/rpc-handlers.test.ts src/lib/tauri-transport.test.ts
+```
+
+Expected: FAIL.
+
+- [ ] **Step 4: Implement browser and desktop bridge layers**
+
+The bridge must:
+
+- subscribe once to controller changes/events;
+- publish them to the broker;
+- consume broker commands;
+- call the same `controller.dispatch`;
+- return the exact receipt/error;
+- use scoped fibers and interrupt all streams on disable/unmount.
+
+Do not decode or branch on command tags in transport adapters beyond schema
+validation.
+
+- [ ] **Step 5: Compose exactly one broker instance per host**
+
+In `server/index.ts` and `server/sidecar.ts`, bind `ControlBrokerLive` once and
+provide the same layer reference to HTTP/RPC handlers and lifecycle startup.
+Ensure sidecar exit and browser runtime shutdown release its scope.
+
+- [ ] **Step 6: Run focused transport tests**
+
+```bash
+bunx vitest run src/lib/workspace-control-bridge.test.ts server/app.test.ts server/rpc-handlers.test.ts src/lib/tauri-transport.test.ts
+bun run typecheck
+```
+
+Expected: PASS.
+
+### Task 10: Ship the user-equivalent CLI, JSON mode, and MCP stdio adapter
+
+**Files:**
+- Create: `cli/flect-client.ts`
+- Create: `cli/flect-client.test.ts`
+- Create: `cli/flectctl.ts`
+- Create: `cli/flectctl.test.ts`
+- Create: `cli/flect-mcp.ts`
+- Create: `cli/flect-mcp.test.ts`
+- Modify: `package.json`
+- Modify: `scripts/build-sidecar.ts`
+- Modify: `scripts/build-sidecar.test.ts` if present
+- Modify: `src-tauri/tauri.conf.json`
+- Modify: release packaging tests and scripts that enumerate binaries
+
+**Interfaces:**
+- Consumes: protected descriptor and external JSON/SSE API.
+- Produces:
+  - compiled `flectctl`;
+  - stable exit codes;
+  - four MCP tools: `flect_inspect`, `flect_command`, `flect_wait`,
+    `flect_logs`.
+
+- [ ] **Step 1: Write failing client and CLI tests**
+
+Cover:
+
+- descriptor discovery and stale-instance failure;
+- authorization header injection without exposing the bearer;
+- `--stdin` for prompt/shape;
+- human versus `--json` output;
+- inspect/watch/logs, mode, prompt, shape, invoke, cancel, models/model,
+  model favorites, App/Shaper external-extension toggles,
+  accept/reject/rollback, safe/restore, rail, control status/disable, and raw
+  schema command;
+- `launch` opens the installed app or documented browser URL;
+- no CLI `control enable`;
+- exit codes for use error, unavailable, auth, conflict, operation failure,
+  and interruption;
+- stdout contains only schema JSON in JSON mode and diagnostics use stderr.
+
+- [ ] **Step 2: Write failing MCP protocol tests**
+
+Feed JSON-RPC frames over in-memory stdio and verify initialize, tool listing,
+tool calls, schema validation, cancellation, and safe error responses. Confirm
+the bearer never appears in tool schemas/results.
+
+- [ ] **Step 3: Run focused tests and confirm red**
+
+```bash
+bunx vitest run cli/flect-client.test.ts cli/flectctl.test.ts cli/flect-mcp.test.ts
+```
+
+Expected: FAIL.
+
+- [ ] **Step 4: Implement the Effect CLI client and commands**
+
+Use `Effect.fn` operations and one boundary runtime. Decode every response with
+shared schemas. Generate command envelopes client-side with the workspace ID
+from inspection and a named control source. Wait for terminal operation events
+by default; `--no-wait` returns the accepted receipt.
+
+Arguments support human convenience, but documentation and agent tests use:
+
+```bash
+printf '%s' 'Make the dashboard denser' | flectctl shape --stdin --json
+```
+
+- [ ] **Step 5: Implement the compact MCP adapter**
+
+Use an official installed MCP SDK if already present; otherwise add the
+smallest current official SDK package and inspect its local docs/source before
+coding. Map all authority through the same client:
+
+```text
+flect_inspect -> snapshot
+flect_command -> closed FlectCommand
+flect_wait    -> event cursor / terminal operation
+flect_logs    -> bounded OperationFilter
+```
+
+Do not create one MCP tool per Flect command.
+
+- [ ] **Step 6: Compile and bundle `flectctl`**
+
+Extend the build script to compile:
+
+```text
+src-tauri/binaries/flect-runtime-aarch64-apple-darwin
+src-tauri/binaries/flectctl-aarch64-apple-darwin
+```
+
+Add `flectctl` to Tauri `externalBin` and release artifact verification.
+`install-shell` may create a user-approved symlink in `~/.local/bin`; no
+automatic PATH or shell-profile mutation.
+
+- [ ] **Step 7: Run focused tests and binary smoke**
+
+```bash
+bunx vitest run cli/flect-client.test.ts cli/flectctl.test.ts cli/flect-mcp.test.ts scripts/package-release.test.ts
+bun run build:sidecar
+src-tauri/binaries/flectctl-aarch64-apple-darwin --help
+```
+
+Expected: tests PASS; help lists the documented commands and no enable command.
+
+### Task 11: Verify real browser behavior through the public control surface
+
+**Files:**
+- Modify: `tests/e2e/flect.spec.ts`
+- Create: `tests/e2e/control-plane.spec.ts`
+- Modify: `playwright.config.ts` if a second worker/service fixture is needed
+- Create: `docs/verification/flect-observable-control/browser.md`
+- Create screenshots under:
+  `docs/verification/flect-observable-control/screenshots/`
+
+**Interfaces:**
+- Consumes: running test runtime, UI, real `flectctl`, JSON/SSE API.
+- Produces: public-surface browser evidence.
+
+- [ ] **Step 1: Add a failing end-to-end Shaper reliability scenario**
+
+Drive Edit mode through the visible UI, submit the prior reproduction
+instruction `make a demo ui and show me`, and assert:
+
+- proposal tool activity becomes visible;
+- a valid preview appears;
+- accept makes it active;
+- no generic “could not produce” error appears;
+- diagnostics contain correlated validation/tool/revision events.
+
+- [ ] **Step 2: Add failing sticky-follow and activity scenarios**
+
+Fill the timeline, scroll more than 48 px away, stream additional deltas/tool
+updates, and assert viewport stability. Assert `Jump to latest` and unread
+count, then jump and verify following resumes.
+
+- [ ] **Step 3: Add failing CLI-to-live-UI scenarios**
+
+Enable control via the protected UI, then invoke the compiled CLI from the
+test:
+
+```text
+inspect -> mode edit -> shape -> accept -> mode run -> prompt
+-> invoke a visible node -> logs -> safe-mode -> restore -> disable
+```
+
+Assert each external command changes the open page reactively and displays
+client attribution. Assert the old token fails immediately after disable.
+
+- [ ] **Step 4: Run the scenarios red, then implement only missing seams**
+
+```bash
+bunx playwright test tests/e2e/flect.spec.ts tests/e2e/control-plane.spec.ts
+```
+
+Expected before final fixes: at least one new scenario FAILS for the missing
+observable behavior. Fix the smallest owning module for each failure.
+
+- [ ] **Step 5: Run Playwright green and capture evidence**
+
+```bash
+bunx playwright test tests/e2e/flect.spec.ts tests/e2e/control-plane.spec.ts
+```
+
+Expected: PASS. Save light/dark relevant UI screenshots showing activity,
+validation diagnostics, suspended scrolling, external attribution, and
+connected control state.
+
+### Task 12: Document ownership, security, adoption, and agent usage
+
+**Files:**
+- Modify: `README.md`
+- Modify: `VISION.md`
+- Modify: `PRODUCT.md`
+- Modify: `DESIGN.md`
+- Modify: `ARCHITECTURE.md`
+- Modify: `AGENTS.md`
+- Create: `docs/control-api.md`
+- Create: `docs/verification/flect-observable-control/README.md`
+
+**Interfaces:**
+- Consumes: verified implemented behavior only.
+- Produces: one authoritative home for each contract and workflow.
+
+- [ ] **Step 1: Update architecture and security ownership**
+
+In `ARCHITECTURE.md`, document:
+
+- workspace-authoritative topology;
+- App/Shaper/Guardian separation;
+- browser versus desktop channel parity;
+- broker lifecycle and descriptor;
+- token and origin threat model;
+- Effect services/layers/streams/scopes;
+- journal retention and redaction;
+- sandbox boundary unchanged by outside control.
+
+- [ ] **Step 2: Update product and design ownership**
+
+Put user outcomes and limits in `PRODUCT.md`, long-term self-shaping intent in
+`VISION.md`, and activity/sticky-follow/control UI behavior in `DESIGN.md`.
+Avoid copying protocol steps into those documents.
+
+- [ ] **Step 3: Add adoption and quickstart material**
+
+In `README.md`, show:
+
+```bash
+open /Applications/Flect.app
+/Applications/Flect.app/Contents/MacOS/flectctl inspect
+printf '%s' 'Create a project dashboard' |
+  /Applications/Flect.app/Contents/MacOS/flectctl shape --stdin
+```
+
+Explain that control must first be enabled inside Flect and that Flect works
+without outside control in both browser and desktop modes.
+
+- [ ] **Step 4: Add the exact API/CLI/MCP reference**
+
+`docs/control-api.md` owns endpoint routes, auth, schemas, commands, exit
+codes, SSE cursors, MCP tools, examples, disable/revoke behavior, and safe
+debugging. Generate examples from actual schema-encoded fixtures where
+practical.
+
+- [ ] **Step 5: Update `AGENTS.md` with codebase invariants only**
+
+Record that:
+
+- all semantic actions enter `FlectWorkspaceController`;
+- React and control adapters cannot create alternate state;
+- Effect is mandatory for UI shaping and orchestration;
+- control boundaries use shared schemas;
+- secrets never enter logs/model context;
+- real-browser tests drive the public control surface.
+
+Do not duplicate setup commands or API reference detail into `AGENTS.md`.
+
+- [ ] **Step 6: Run documentation and formatting checks**
+
+```bash
+rg -n 'FIXME|PLACEHOLDER|control '"'