@crouton-kit/crouter 0.3.32 → 0.3.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/build-root.js +1 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
- package/dist/builtin-memory/crouter-development/personas.md +3 -4
- package/dist/builtin-memory/crouter-development/plugins.md +18 -18
- package/dist/builtin-memory/design.md +4 -1
- package/dist/builtin-memory/development.md +4 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +6 -6
- package/dist/builtin-memory/internal/storage-tiers.md +5 -5
- package/dist/builtin-memory/planning.md +4 -1
- package/dist/builtin-memory/product.md +80 -0
- package/dist/builtin-memory/spec.md +4 -1
- package/dist/builtin-personas/advisor/PERSONA.md +10 -0
- package/dist/builtin-personas/design/PERSONA.md +2 -2
- package/dist/builtin-personas/design/orchestrator.md +3 -3
- package/dist/builtin-personas/developer/PERSONA.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +2 -2
- package/dist/builtin-personas/explore/PERSONA.md +3 -3
- package/dist/builtin-personas/explore/orchestrator.md +4 -2
- package/dist/builtin-personas/general/PERSONA.md +1 -1
- package/dist/builtin-personas/general/orchestrator.md +1 -1
- package/dist/builtin-personas/orchestration-kernel.md +7 -14
- package/dist/builtin-personas/plan/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/orchestrator.md +2 -2
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
- package/dist/builtin-personas/product/PERSONA.md +18 -0
- package/dist/builtin-personas/product/orchestrator.md +14 -0
- package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -1
- package/dist/builtin-personas/review/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +5 -0
- package/dist/builtin-personas/spec/PERSONA.md +1 -1
- package/dist/builtin-personas/spec/orchestrator.md +2 -2
- package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
- package/dist/builtin-views/canvas/core.mjs +82 -1
- package/dist/builtin-views/canvas/tui.mjs +9 -5
- package/dist/builtin-views/canvas/web.jsx +3 -2
- package/dist/builtin-views/chat/core.mjs +725 -0
- package/dist/builtin-views/chat/text.mjs +101 -0
- package/dist/builtin-views/chat/tui.mjs +368 -0
- package/dist/builtin-views/chat/web.jsx +367 -0
- package/dist/builtin-views/prompt-review/core.mjs +863 -0
- package/dist/builtin-views/prompt-review/text.mjs +15 -0
- package/dist/builtin-views/prompt-review/tui.mjs +196 -0
- package/dist/builtin-views/prompt-review/web.jsx +484 -0
- package/dist/builtin-views/settings/core.mjs +397 -0
- package/dist/builtin-views/settings/text.mjs +40 -0
- package/dist/builtin-views/settings/tui.mjs +95 -0
- package/dist/builtin-views/settings/web.jsx +167 -0
- package/dist/cli.js +16 -3
- package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +96 -0
- package/dist/clients/attach/__tests__/bash-bang-routing.test.js +58 -0
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +56 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +22 -1
- package/dist/clients/attach/attach-cmd.d.ts +18 -0
- package/dist/clients/attach/attach-cmd.js +1723 -795
- package/dist/clients/attach/canvas-panels.js +2 -2
- package/dist/clients/attach/chat-view.d.ts +10 -0
- package/dist/clients/attach/chat-view.js +23 -0
- package/dist/clients/attach/config-load.d.ts +8 -4
- package/dist/clients/attach/config-load.js +2 -0
- package/dist/clients/attach/extension-dialogs.d.ts +2 -3
- package/dist/clients/attach/extension-dialogs.js +7 -9
- package/dist/clients/attach/graph-overlay.js +3 -2
- package/dist/clients/attach/input-controller.d.ts +4 -3
- package/dist/clients/attach/input-controller.js +24 -3
- package/dist/clients/attach/slash-commands.d.ts +7 -7
- package/dist/clients/attach/slash-commands.js +38 -14
- package/dist/clients/attach/titled-editor.js +18 -14
- package/dist/clients/attach/view-socket.d.ts +2 -1
- package/dist/clients/attach/view-socket.js +27 -8
- package/dist/clients/web/__tests__/source-cache.test.d.ts +1 -0
- package/dist/clients/web/__tests__/source-cache.test.js +32 -0
- package/dist/clients/web/dev-server.js +1 -0
- package/dist/clients/web/events.js +9 -11
- package/dist/clients/web/server.d.ts +4 -3
- package/dist/clients/web/server.js +138 -31
- package/dist/clients/web/source-cache.d.ts +10 -0
- package/dist/clients/web/source-cache.js +57 -0
- package/dist/clients/web/web-cmd.js +28 -9
- package/dist/commands/__tests__/human.test.js +30 -28
- package/dist/commands/canvas-history/search.js +1 -1
- package/dist/commands/canvas-history.js +5 -8
- package/dist/commands/canvas-issue.d.ts +2 -0
- package/dist/commands/canvas-issue.js +148 -0
- package/dist/commands/canvas-prune.js +19 -0
- package/dist/commands/canvas-rebuild-index.d.ts +2 -0
- package/dist/commands/canvas-rebuild-index.js +57 -0
- package/dist/commands/canvas-snapshot.d.ts +2 -0
- package/dist/commands/canvas-snapshot.js +48 -0
- package/dist/commands/canvas-tmux-spread.d.ts +2 -0
- package/dist/commands/canvas-tmux-spread.js +188 -0
- package/dist/commands/canvas.d.ts +1 -0
- package/dist/commands/canvas.js +16 -1
- package/dist/commands/chord.js +143 -48
- package/dist/commands/daemon.js +3 -3
- package/dist/commands/human/prompts.d.ts +0 -1
- package/dist/commands/human/prompts.js +39 -54
- package/dist/commands/human/queue.d.ts +12 -1
- package/dist/commands/human/queue.js +468 -73
- package/dist/commands/human/shared.d.ts +3 -4
- package/dist/commands/human/shared.js +3 -3
- package/dist/commands/human.js +11 -10
- package/dist/commands/memory/find.js +3 -2
- package/dist/commands/memory/list.js +4 -3
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +1 -4
- package/dist/commands/memory/shared.js +6 -9
- package/dist/commands/memory/write.js +3 -3
- package/dist/commands/memory.js +4 -7
- package/dist/commands/node-context.d.ts +2 -0
- package/dist/commands/node-context.js +172 -0
- package/dist/commands/node-snapshot.d.ts +2 -0
- package/dist/commands/node-snapshot.js +123 -0
- package/dist/commands/node.js +228 -52
- package/dist/commands/pkg/plugin-inspect.js +6 -6
- package/dist/commands/pkg/plugin-manage.js +1 -1
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +2 -2
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +79 -11
- package/dist/commands/revive.js +74 -1
- package/dist/commands/search/answer.d.ts +1 -0
- package/dist/commands/search/answer.js +50 -0
- package/dist/commands/search/contents.d.ts +1 -0
- package/dist/commands/search/contents.js +96 -0
- package/dist/commands/search/exa.d.ts +53 -0
- package/dist/commands/search/exa.js +155 -0
- package/dist/commands/search/puremd.d.ts +11 -0
- package/dist/commands/search/puremd.js +53 -0
- package/dist/commands/search/web.d.ts +1 -0
- package/dist/commands/search/web.js +65 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +24 -0
- package/dist/commands/sys/__tests__/sync-import.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
- package/dist/commands/sys/config.js +59 -10
- package/dist/commands/sys/doctor.js +56 -4
- package/dist/commands/sys/prompt-review.d.ts +1 -0
- package/dist/commands/sys/prompt-review.js +178 -0
- package/dist/commands/sys/promptstudio.d.ts +2 -0
- package/dist/commands/sys/promptstudio.js +65 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +16 -0
- package/dist/commands/sys/sync.js +311 -159
- package/dist/commands/sys/sysprompt.d.ts +1 -0
- package/dist/commands/sys/sysprompt.js +86 -0
- package/dist/commands/sys.js +9 -5
- package/dist/commands/view-new.js +2 -2
- package/dist/core/__tests__/artifact-paths.test.d.ts +1 -0
- package/dist/core/__tests__/artifact-paths.test.js +44 -0
- package/dist/core/__tests__/broker-preflight.test.d.ts +1 -0
- package/dist/core/__tests__/broker-preflight.test.js +85 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +15 -0
- package/dist/core/__tests__/canvas.test.js +37 -1
- package/dist/core/__tests__/chat-view-reconnect.test.d.ts +1 -0
- package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
- package/dist/core/__tests__/child-death-wake.test.js +11 -2
- package/dist/core/__tests__/close.test.js +39 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +1 -0
- package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
- package/dist/core/__tests__/context-intro.test.js +48 -14
- package/dist/core/__tests__/daemon-boot.test.js +182 -9
- package/dist/core/__tests__/editor-label.test.d.ts +1 -0
- package/dist/core/__tests__/editor-label.test.js +26 -0
- package/dist/core/__tests__/fault-marker.test.d.ts +1 -0
- package/dist/core/__tests__/fault-marker.test.js +112 -0
- package/dist/core/__tests__/fault-retry-rewind.test.d.ts +1 -0
- package/dist/core/__tests__/fault-retry-rewind.test.js +123 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +156 -17
- package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
- package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +2 -2
- package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
- package/dist/core/__tests__/human-surface-target.test.js +1 -1
- package/dist/core/__tests__/kickoff.test.js +18 -19
- package/dist/core/__tests__/memory-resolver.test.js +12 -1
- package/dist/core/__tests__/migration.test.js +49 -14
- package/dist/core/__tests__/model-ladders.test.d.ts +1 -0
- package/dist/core/__tests__/model-ladders.test.js +160 -0
- package/dist/core/__tests__/node-env.test.d.ts +1 -0
- package/dist/core/__tests__/node-env.test.js +26 -0
- package/dist/core/__tests__/push-final-guard.test.js +7 -1
- package/dist/core/__tests__/relaunch-root.test.js +9 -9
- package/dist/core/__tests__/revive.test.js +36 -6
- package/dist/core/__tests__/spawn-root.test.js +24 -1
- package/dist/core/__tests__/stop-guard.test.js +10 -0
- package/dist/core/__tests__/stranded-relaunch.test.d.ts +1 -0
- package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
- package/dist/core/__tests__/tmux-surface.test.js +5 -1
- package/dist/core/__tests__/unknown-path.test.js +19 -19
- package/dist/core/__tests__/yield-ensures-daemon.test.d.ts +1 -0
- package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
- package/dist/core/artifact.d.ts +2 -33
- package/dist/core/artifact.js +27 -87
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
- package/dist/core/canvas/browse/app.js +87 -17
- package/dist/core/canvas/browse/model.d.ts +10 -1
- package/dist/core/canvas/browse/model.js +37 -1
- package/dist/core/canvas/browse/render.js +35 -33
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +54 -5
- package/dist/core/canvas/db.js +35 -8
- package/dist/core/canvas/labels.d.ts +6 -8
- package/dist/core/canvas/labels.js +8 -11
- package/dist/core/canvas/nav-model.d.ts +5 -4
- package/dist/core/canvas/nav-model.js +31 -17
- package/dist/core/canvas/paths.d.ts +18 -1
- package/dist/core/canvas/paths.js +31 -2
- package/dist/core/canvas/render.d.ts +25 -0
- package/dist/core/canvas/render.js +96 -13
- package/dist/core/canvas/status-glyph.d.ts +35 -0
- package/dist/core/canvas/status-glyph.js +104 -0
- package/dist/core/canvas/types.d.ts +2 -2
- package/dist/core/config.js +114 -16
- package/dist/core/fault-classifier.d.ts +41 -0
- package/dist/core/fault-classifier.js +110 -0
- package/dist/core/feed/inbox.d.ts +15 -0
- package/dist/core/feed/inbox.js +40 -14
- package/dist/core/frontmatter.d.ts +1 -11
- package/dist/core/frontmatter.js +1 -55
- package/dist/core/hearth/config.d.ts +2 -0
- package/dist/core/hearth/config.js +75 -0
- package/dist/core/hearth/guest-env.d.ts +2 -0
- package/dist/core/hearth/guest-env.js +11 -0
- package/dist/core/hearth/index.d.ts +5 -0
- package/dist/core/hearth/index.js +5 -0
- package/dist/core/hearth/provider.d.ts +21 -0
- package/dist/core/hearth/provider.js +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
- package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
- package/dist/core/hearth/providers/blaxel-home.js +126 -0
- package/dist/core/hearth/providers/blaxel.d.ts +26 -0
- package/dist/core/hearth/providers/blaxel.js +208 -0
- package/dist/core/hearth/providers/types.d.ts +72 -0
- package/dist/core/hearth/providers/types.js +1 -0
- package/dist/core/hearth/registry.d.ts +15 -0
- package/dist/core/hearth/registry.js +179 -0
- package/dist/core/hearth/types.d.ts +121 -0
- package/dist/core/hearth/types.js +1 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
- package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
- package/dist/core/host-exports/builtins.js +68 -0
- package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
- package/dist/core/{skill-sync → host-exports}/export.js +37 -14
- package/dist/core/io.js +1 -1
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +113 -0
- package/dist/core/memory-resolver.d.ts +8 -8
- package/dist/core/memory-resolver.js +15 -28
- package/dist/core/personas/index.d.ts +4 -4
- package/dist/core/personas/index.js +2 -2
- package/dist/core/personas/loader.d.ts +51 -0
- package/dist/core/personas/loader.js +54 -50
- package/dist/core/personas/resolve.d.ts +43 -27
- package/dist/core/personas/resolve.js +336 -94
- package/dist/core/runtime/auth-reload.d.ts +7 -0
- package/dist/core/runtime/auth-reload.js +69 -0
- package/dist/core/runtime/bearings.d.ts +35 -15
- package/dist/core/runtime/bearings.js +305 -44
- package/dist/core/runtime/branded-host.d.ts +4 -2
- package/dist/core/runtime/branded-host.js +66 -4
- package/dist/core/runtime/broker-protocol.d.ts +38 -3
- package/dist/core/runtime/broker.js +343 -18
- package/dist/core/runtime/close.js +37 -29
- package/dist/core/runtime/connectivity.d.ts +12 -0
- package/dist/core/runtime/connectivity.js +73 -0
- package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
- package/dist/core/runtime/fault-recovery-nudge.js +2 -0
- package/dist/core/runtime/fault-recovery.d.ts +23 -0
- package/dist/core/runtime/fault-recovery.js +74 -0
- package/dist/core/runtime/fault.d.ts +25 -0
- package/dist/core/runtime/fault.js +176 -0
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/host.d.ts +10 -0
- package/dist/core/runtime/host.js +71 -9
- package/dist/core/runtime/kickoff.js +31 -45
- package/dist/core/runtime/launch.d.ts +32 -18
- package/dist/core/runtime/launch.js +148 -43
- package/dist/core/runtime/model-swap.d.ts +18 -0
- package/dist/core/runtime/model-swap.js +95 -0
- package/dist/core/runtime/naming.d.ts +7 -0
- package/dist/core/runtime/naming.js +61 -9
- package/dist/core/runtime/nodes.js +6 -1
- package/dist/core/runtime/persona.js +16 -17
- package/dist/core/runtime/placement.d.ts +36 -31
- package/dist/core/runtime/placement.js +149 -64
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +23 -3
- package/dist/core/runtime/recycle.js +6 -4
- package/dist/core/runtime/reset.d.ts +5 -2
- package/dist/core/runtime/reset.js +2 -2
- package/dist/core/runtime/revive.d.ts +4 -0
- package/dist/core/runtime/revive.js +27 -9
- package/dist/core/runtime/roadmap.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +2 -2
- package/dist/core/runtime/spawn.js +30 -6
- package/dist/core/runtime/stop-guard.js +6 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +29 -6
- package/dist/core/runtime/tmux.js +134 -80
- package/dist/core/runtime/view-socket-client.d.ts +46 -0
- package/dist/core/runtime/view-socket-client.js +203 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/on-read.js +16 -4
- package/dist/core/substrate/render.d.ts +10 -13
- package/dist/core/substrate/render.js +56 -51
- package/dist/core/tui/host.js +83 -18
- package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
- package/dist/core/view/__tests__/transport-cache.test.js +62 -0
- package/dist/core/view/contract.d.ts +20 -0
- package/dist/core/view/stream-local.d.ts +3 -0
- package/dist/core/view/stream-local.js +184 -0
- package/dist/core/view/transport-cache.d.ts +8 -0
- package/dist/core/view/transport-cache.js +38 -0
- package/dist/core/view/transport-local.js +4 -0
- package/dist/core/view/transport.d.ts +7 -1
- package/dist/daemon/crtrd.d.ts +3 -19
- package/dist/daemon/crtrd.js +139 -178
- package/dist/daemon/manage.d.ts +18 -1
- package/dist/daemon/manage.js +54 -9
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
- package/dist/hearth/wake-proxy/auth.d.ts +22 -0
- package/dist/hearth/wake-proxy/auth.js +128 -0
- package/dist/hearth/wake-proxy/config.d.ts +2 -0
- package/dist/hearth/wake-proxy/config.js +151 -0
- package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
- package/dist/hearth/wake-proxy/guest-source.js +130 -0
- package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
- package/dist/hearth/wake-proxy/hearth-status.js +267 -0
- package/dist/hearth/wake-proxy/home.d.ts +67 -0
- package/dist/hearth/wake-proxy/home.js +297 -0
- package/dist/hearth/wake-proxy/main.d.ts +1 -0
- package/dist/hearth/wake-proxy/main.js +134 -0
- package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
- package/dist/hearth/wake-proxy/model-auth.js +345 -0
- package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
- package/dist/hearth/wake-proxy/proxy.js +716 -0
- package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
- package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
- package/dist/hearth/wake-proxy/redact.d.ts +1 -0
- package/dist/hearth/wake-proxy/redact.js +17 -0
- package/dist/hearth/wake-proxy/server.d.ts +11 -0
- package/dist/hearth/wake-proxy/server.js +142 -0
- package/dist/hearth/wake-proxy/state.d.ts +18 -0
- package/dist/hearth/wake-proxy/state.js +342 -0
- package/dist/hearth/wake-proxy/types.d.ts +76 -0
- package/dist/hearth/wake-proxy/types.js +1 -0
- package/dist/index.js +3 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
- package/dist/pi-extensions/canvas-context-intro.js +7 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
- package/dist/pi-extensions/canvas-nav.js +15 -8
- package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
- package/dist/pi-extensions/canvas-stophook.js +78 -11
- package/dist/types.d.ts +39 -20
- package/dist/types.js +38 -13
- package/dist/web/runtime.js +141 -42
- package/dist/web/transport-http.js +7 -5
- package/dist/web/transport-stream.d.ts +3 -0
- package/dist/web/transport-stream.js +204 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
- package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
- package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
- package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
- package/dist/web-client/assets/index-D36PNBj4.js +80 -0
- package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
- package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/index.html +3 -2
- package/package.json +32 -10
- package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
- package/dist/core/skill-sync/builtins.js +0 -112
- package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
- package/dist/core/skill-sync/claude-plugins.js +0 -71
- package/dist/core/skill-sync/engine.d.ts +0 -42
- package/dist/core/skill-sync/engine.js +0 -633
- package/dist/core/skill-sync/manifest.d.ts +0 -64
- package/dist/core/skill-sync/manifest.js +0 -181
- package/dist/core/skill-sync/profile.d.ts +0 -76
- package/dist/core/skill-sync/profile.js +0 -173
- package/dist/core/skill-sync/snapshot.d.ts +0 -57
- package/dist/core/skill-sync/snapshot.js +0 -120
- package/dist/pi-extensions/canvas-commands.d.ts +0 -37
- package/dist/pi-extensions/canvas-commands.js +0 -113
- package/dist/pi-extensions/canvas-resume.d.ts +0 -21
- package/dist/pi-extensions/canvas-resume.js +0 -83
- package/dist/pi-extensions/canvas-view.d.ts +0 -21
- package/dist/pi-extensions/canvas-view.js +0 -76
- package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
- package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
- /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
- /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// server.ts — the unified `crtr web serve` host: the crouter web UI server.
|
|
2
2
|
//
|
|
3
|
-
// ONE long-running server process on
|
|
4
|
-
// origin (the §2 unified server):
|
|
3
|
+
// ONE long-running server process on the bound host that serves THREE concerns
|
|
4
|
+
// on one origin (the §2 unified server):
|
|
5
5
|
// • GET /* → the shell SPA (static dist/web-client/, or Vite
|
|
6
6
|
// middleware in --dev) + SPA fallback.
|
|
7
7
|
// • POST /__crtr/source → the source + command bridge: decode a SourceRequest
|
|
@@ -21,23 +21,28 @@
|
|
|
21
21
|
// ALREADY-running broker's `view.sock`; if the broker is not running the WS is
|
|
22
22
|
// closed with a clear reason. The relay NEVER launches an engine. ALL writes go
|
|
23
23
|
// through the bridge running `crtr` SUBPROCESSES — this process is never the
|
|
24
|
-
// sanctioned writer; the CLI it shells out to is.
|
|
24
|
+
// sanctioned writer; the CLI it shells out to is. The sanctioned exception is a
|
|
25
|
+
// relay fault record written directly to the node when the broker socket fails.
|
|
25
26
|
//
|
|
26
27
|
// One `view.sock` connection per browser-WS (N browsers of one node = N broker
|
|
27
28
|
// clients) — the broker fans out natively, so there is no bridge-side fan-out.
|
|
28
29
|
import { createServer as createHttpServer } from 'node:http';
|
|
29
|
-
import { createConnection } from 'node:net';
|
|
30
|
+
import { createConnection, isIP } from 'node:net';
|
|
30
31
|
import { createReadStream, existsSync, statSync } from 'node:fs';
|
|
31
32
|
import { fileURLToPath } from 'node:url';
|
|
32
33
|
import { dirname, extname, join, normalize, resolve } from 'node:path';
|
|
33
34
|
import { WebSocketServer } from 'ws';
|
|
34
|
-
import {
|
|
35
|
+
import { viewSocketPath } from '../../core/canvas/paths.js';
|
|
35
36
|
import { getNode } from '../../core/canvas/index.js';
|
|
36
37
|
import { BROKER_READ_CAPS, CLIENT_READ_CAPS, FrameDecoder, FrameOverflowError, } from '../../core/runtime/broker-protocol.js';
|
|
38
|
+
import { classify } from '../../core/fault-classifier.js';
|
|
39
|
+
import { clearFault, recordFault } from '../../core/runtime/fault.js';
|
|
37
40
|
import { createLocalTransport } from '../../core/view/transport-local.js';
|
|
41
|
+
import { createBurstCoalescingTransport } from '../../core/view/transport-cache.js';
|
|
38
42
|
import { runSourceRequest } from '../../core/view/bridge.js';
|
|
39
43
|
import { startEventHub } from './events.js';
|
|
40
44
|
import { createDevServer } from './dev-server.js';
|
|
45
|
+
import { SOURCE_CACHE_TTL_MS, isCoalescableSourceRequest } from './source-cache.js';
|
|
41
46
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
42
47
|
/** Candidate dirs to serve the shell SPA bundle from, in priority order: an
|
|
43
48
|
* explicit override, the compiled-module-relative `dist/web-client/` (what
|
|
@@ -94,11 +99,46 @@ function nodeIdFromPath(pathname) {
|
|
|
94
99
|
return null; // malformed percent-encoding — treat as no match
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
|
-
/** A node id is used to build `
|
|
98
|
-
* path separator or `..` (e.g. a decoded `..%2f..`
|
|
102
|
+
/** A node id is used to build the broker socket path via `viewSocketPath(id)`, so
|
|
103
|
+
* it MUST NOT contain a path separator or `..` (e.g. a decoded `..%2f..`). */
|
|
99
104
|
function isSafeNodeId(id) {
|
|
100
105
|
return id !== '' && !/[/\\]/.test(id) && !id.includes('..');
|
|
101
106
|
}
|
|
107
|
+
function isLoopbackHost(host) {
|
|
108
|
+
const lower = host.toLowerCase();
|
|
109
|
+
const ipVersion = isIP(host);
|
|
110
|
+
if (lower === 'localhost' || lower === '::1')
|
|
111
|
+
return true;
|
|
112
|
+
if (ipVersion === 4 && host.startsWith('127.'))
|
|
113
|
+
return true;
|
|
114
|
+
if (lower === '::ffff:127.0.0.1')
|
|
115
|
+
return true;
|
|
116
|
+
if (ipVersion === 6 && lower.startsWith('::ffff:127.'))
|
|
117
|
+
return true;
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
function hostForUrl(host) {
|
|
121
|
+
return host.includes(':') ? `[${host}]` : host;
|
|
122
|
+
}
|
|
123
|
+
function requestTokens(req) {
|
|
124
|
+
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
125
|
+
const tokens = [];
|
|
126
|
+
const queryToken = url.searchParams.get('token');
|
|
127
|
+
if (queryToken !== null && queryToken !== '')
|
|
128
|
+
tokens.push(queryToken);
|
|
129
|
+
const authorization = req.headers.authorization;
|
|
130
|
+
if (typeof authorization === 'string') {
|
|
131
|
+
const match = /^Bearer\s+(.+)$/.exec(authorization);
|
|
132
|
+
if (match !== null && match[1] !== '')
|
|
133
|
+
tokens.push(match[1]);
|
|
134
|
+
}
|
|
135
|
+
return tokens;
|
|
136
|
+
}
|
|
137
|
+
function requestTokenMatches(req, secret) {
|
|
138
|
+
if (secret === undefined)
|
|
139
|
+
return false;
|
|
140
|
+
return requestTokens(req).some((token) => token === secret);
|
|
141
|
+
}
|
|
102
142
|
/** A WS close reason is capped at 123 UTF-8 bytes by the protocol. */
|
|
103
143
|
function clampReason(reason) {
|
|
104
144
|
const buf = Buffer.from(reason, 'utf8');
|
|
@@ -192,10 +232,15 @@ function bridgeConnection(ws, nodeId) {
|
|
|
192
232
|
}
|
|
193
233
|
return;
|
|
194
234
|
}
|
|
195
|
-
|
|
235
|
+
clearFault(nodeId, { link: 'browser↔relay' });
|
|
236
|
+
const sockPath = viewSocketPath(nodeId);
|
|
196
237
|
const socket = createConnection(sockPath);
|
|
197
238
|
const decoder = new FrameDecoder(CLIENT_READ_CAPS);
|
|
198
239
|
let closed = false;
|
|
240
|
+
let browserFaultRecorded = false;
|
|
241
|
+
socket.on('connect', () => {
|
|
242
|
+
clearFault(nodeId, { link: 'relay↔broker' });
|
|
243
|
+
});
|
|
199
244
|
const teardown = (wsCode, reason) => {
|
|
200
245
|
if (closed)
|
|
201
246
|
return;
|
|
@@ -234,12 +279,62 @@ function bridgeConnection(ws, nodeId) {
|
|
|
234
279
|
}
|
|
235
280
|
});
|
|
236
281
|
socket.on('error', (err) => {
|
|
282
|
+
const classification = classify('relay↔broker', err);
|
|
283
|
+
recordFault(nodeId, {
|
|
284
|
+
link: 'relay↔broker',
|
|
285
|
+
op: 'bridge socket',
|
|
286
|
+
kind: classification.kind,
|
|
287
|
+
retry: { disposition: 'manual' },
|
|
288
|
+
message: err.message,
|
|
289
|
+
});
|
|
237
290
|
const reason = err.code === 'ECONNREFUSED' || err.code === 'ENOENT'
|
|
238
291
|
? `no running broker for ${nodeId}`
|
|
239
292
|
: `view socket error for ${nodeId}: ${err.message}`;
|
|
240
293
|
teardown(1011, reason);
|
|
241
294
|
});
|
|
242
|
-
socket.on('close', () =>
|
|
295
|
+
socket.on('close', (hadError) => {
|
|
296
|
+
if (closed || hadError)
|
|
297
|
+
return;
|
|
298
|
+
clearFault(nodeId, { link: 'relay↔broker' });
|
|
299
|
+
teardown(1000, 'broker socket closed');
|
|
300
|
+
});
|
|
301
|
+
ws.on('close', (code, reason) => {
|
|
302
|
+
const reasonText = reason.toString('utf8');
|
|
303
|
+
if (closed) {
|
|
304
|
+
if (code === 1000)
|
|
305
|
+
clearFault(nodeId, { link: 'browser↔relay' });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (code === 1000) {
|
|
309
|
+
clearFault(nodeId, { link: 'browser↔relay' });
|
|
310
|
+
}
|
|
311
|
+
else if (!browserFaultRecorded) {
|
|
312
|
+
browserFaultRecorded = true;
|
|
313
|
+
const classification = classify('browser↔relay', { code, reason: reasonText });
|
|
314
|
+
recordFault(nodeId, {
|
|
315
|
+
link: 'browser↔relay',
|
|
316
|
+
op: 'browser websocket',
|
|
317
|
+
kind: classification.kind,
|
|
318
|
+
retry: { disposition: 'manual' },
|
|
319
|
+
message: reasonText || `browser relay closed (${code})`,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
teardown(code, reasonText || `browser relay closed (${code})`);
|
|
323
|
+
});
|
|
324
|
+
ws.on('error', () => {
|
|
325
|
+
if (!browserFaultRecorded) {
|
|
326
|
+
browserFaultRecorded = true;
|
|
327
|
+
const classification = classify('browser↔relay', { code: 1006, reason: 'browser ws error' });
|
|
328
|
+
recordFault(nodeId, {
|
|
329
|
+
link: 'browser↔relay',
|
|
330
|
+
op: 'browser websocket',
|
|
331
|
+
kind: classification.kind,
|
|
332
|
+
retry: { disposition: 'manual' },
|
|
333
|
+
message: 'browser ws error',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
teardown(1011, 'browser ws error');
|
|
337
|
+
});
|
|
243
338
|
// WS → socket: re-frame each message as one newline-terminated JSON line.
|
|
244
339
|
// VERBATIM (no parse) — the broker validates and bounds its own reads.
|
|
245
340
|
ws.on('message', (data) => {
|
|
@@ -259,34 +354,43 @@ function bridgeConnection(ws, nodeId) {
|
|
|
259
354
|
/* dead socket — its 'close' drives teardown */
|
|
260
355
|
}
|
|
261
356
|
});
|
|
262
|
-
ws.on('close', () => teardown(1000, 'browser closed'));
|
|
263
|
-
ws.on('error', () => teardown(1011, 'browser ws error'));
|
|
264
357
|
}
|
|
265
|
-
/** Build + start the unified web server. Binds
|
|
266
|
-
*
|
|
267
|
-
*
|
|
358
|
+
/** Build + start the unified web server. Binds the requested host/port and
|
|
359
|
+
* resolves once the server is listening. Async because --dev awaits a Vite
|
|
360
|
+
* middleware server before listening. */
|
|
268
361
|
export async function startWebServer(opts) {
|
|
269
362
|
const host = opts.host ?? '127.0.0.1';
|
|
363
|
+
const token = opts.token !== undefined && opts.token !== '' ? opts.token : undefined;
|
|
270
364
|
const dev = opts.dev ?? false;
|
|
365
|
+
const tokenRequired = !isLoopbackHost(host);
|
|
366
|
+
if (tokenRequired && (token === undefined || token === '')) {
|
|
367
|
+
throw new Error(`crtr web serve --host ${host} requires --token for remote-capable endpoints`);
|
|
368
|
+
}
|
|
271
369
|
const clientDir = resolveClientDir();
|
|
272
370
|
// The bridge runs view sources + command verbs locally, in the cwd crtr was
|
|
273
371
|
// invoked from (a git-pr view inspects THIS repo; a `crtr node new` lands here).
|
|
274
|
-
const transport = createLocalTransport({ cwd: process.cwd() })
|
|
372
|
+
const transport = createBurstCoalescingTransport(createLocalTransport({ cwd: process.cwd() }), {
|
|
373
|
+
ttlMs: SOURCE_CACHE_TTL_MS,
|
|
374
|
+
shouldCache: isCoalescableSourceRequest,
|
|
375
|
+
});
|
|
275
376
|
// The SSE change lane — one hub, fanned to every connected EventSource.
|
|
276
377
|
const eventHub = startEventHub();
|
|
277
|
-
// Same-origin allowlist
|
|
278
|
-
//
|
|
279
|
-
// otherwise
|
|
280
|
-
// /__crtr/source to run ARBITRARY exec. The same gate guards all three write/
|
|
281
|
-
// drive surfaces (WS upgrade, bridge POST, SSE GET). Populated once we know the
|
|
282
|
-
// bound port. A request with NO Origin (a CLI/curl/test client, not a browser)
|
|
283
|
-
// is allowed — the threat model is foreign browser pages, which always send it.
|
|
378
|
+
// Same-origin allowlist: tokenless localhost pages keep the legacy guard,
|
|
379
|
+
// while a valid relay token can authorize remote Hearth route origins that would
|
|
380
|
+
// otherwise be rejected by originAllowed().
|
|
284
381
|
let allowedOrigins = new Set();
|
|
285
382
|
const originAllowed = (origin) => {
|
|
286
383
|
if (origin === undefined || origin === '')
|
|
287
384
|
return true;
|
|
288
385
|
return allowedOrigins.has(origin);
|
|
289
386
|
};
|
|
387
|
+
const requestAllowed = (req) => {
|
|
388
|
+
if (requestTokenMatches(req, token))
|
|
389
|
+
return true;
|
|
390
|
+
if (tokenRequired)
|
|
391
|
+
return false;
|
|
392
|
+
return originAllowed(req.headers.origin);
|
|
393
|
+
};
|
|
290
394
|
// Assigned before listen() when --dev; the request handler closes over it.
|
|
291
395
|
let vite;
|
|
292
396
|
const httpServer = createHttpServer((req, res) => {
|
|
@@ -294,15 +398,16 @@ export async function startWebServer(opts) {
|
|
|
294
398
|
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
295
399
|
const pathname = url.pathname;
|
|
296
400
|
// The source + command bridge: POST /__crtr/source → run the SourceRequest
|
|
297
|
-
// through the local transport.
|
|
298
|
-
// arbitrary exec from a foreign browser page is exactly what
|
|
401
|
+
// through the local transport. Token-or-origin gated (same guard as the WS
|
|
402
|
+
// relay): arbitrary exec from a foreign browser page is exactly what the
|
|
403
|
+
// remote binding guard prevents.
|
|
299
404
|
if (pathname === '/__crtr/source') {
|
|
300
405
|
if (req.method !== 'POST') {
|
|
301
406
|
res.writeHead(405, { 'content-type': 'text/plain; charset=utf-8', allow: 'POST' });
|
|
302
407
|
res.end('method not allowed\n');
|
|
303
408
|
return;
|
|
304
409
|
}
|
|
305
|
-
if (!
|
|
410
|
+
if (!requestAllowed(req)) {
|
|
306
411
|
res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' });
|
|
307
412
|
res.end('forbidden\n');
|
|
308
413
|
return;
|
|
@@ -319,15 +424,16 @@ export async function startWebServer(opts) {
|
|
|
319
424
|
});
|
|
320
425
|
return;
|
|
321
426
|
}
|
|
322
|
-
// The SSE change-invalidation lane: GET /__crtr/events.
|
|
323
|
-
// a foreign page should not even learn the user's graph is
|
|
427
|
+
// The SSE change-invalidation lane: GET /__crtr/events. Token-or-origin
|
|
428
|
+
// gated too — a foreign page should not even learn the user's graph is
|
|
429
|
+
// mutating.
|
|
324
430
|
if (pathname === '/__crtr/events') {
|
|
325
431
|
if (req.method !== 'GET') {
|
|
326
432
|
res.writeHead(405, { 'content-type': 'text/plain; charset=utf-8', allow: 'GET' });
|
|
327
433
|
res.end('method not allowed\n');
|
|
328
434
|
return;
|
|
329
435
|
}
|
|
330
|
-
if (!
|
|
436
|
+
if (!requestAllowed(req)) {
|
|
331
437
|
res.writeHead(403, { 'content-type': 'text/plain; charset=utf-8' });
|
|
332
438
|
res.end('forbidden\n');
|
|
333
439
|
return;
|
|
@@ -383,7 +489,7 @@ export async function startWebServer(opts) {
|
|
|
383
489
|
socket.destroy();
|
|
384
490
|
return;
|
|
385
491
|
}
|
|
386
|
-
if (!
|
|
492
|
+
if (!requestAllowed(req)) {
|
|
387
493
|
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
388
494
|
socket.destroy();
|
|
389
495
|
return;
|
|
@@ -418,9 +524,10 @@ export async function startWebServer(opts) {
|
|
|
418
524
|
});
|
|
419
525
|
const addr = httpServer.address();
|
|
420
526
|
const port = typeof addr === 'object' && addr !== null ? addr.port : opts.port;
|
|
421
|
-
const
|
|
527
|
+
const hostUrl = hostForUrl(host);
|
|
528
|
+
const url = `http://${hostUrl}:${port}`;
|
|
422
529
|
allowedOrigins = new Set([
|
|
423
|
-
`http://${
|
|
530
|
+
`http://${hostUrl}:${port}`,
|
|
424
531
|
`http://127.0.0.1:${port}`,
|
|
425
532
|
`http://localhost:${port}`,
|
|
426
533
|
]);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SourceRequest } from '../../core/view/contract.js';
|
|
2
|
+
/** TTL for coalesced source reads. Must comfortably exceed the SSE debounce
|
|
3
|
+
* (events.ts, 150 ms) so a single mutation's fan-out collapses to one
|
|
4
|
+
* subprocess per distinct read, while staying short enough that the next
|
|
5
|
+
* poll/SSE refreshes promptly. */
|
|
6
|
+
export declare const SOURCE_CACHE_TTL_MS = 500;
|
|
7
|
+
/** True iff `req` is an idempotent `crtr … --json` read safe to burst-coalesce.
|
|
8
|
+
* Anything carrying stdin or a cwd override, any non-exec request, and any
|
|
9
|
+
* command not on the {@link CACHEABLE_READS} allowlist is rejected. */
|
|
10
|
+
export declare function isCoalescableSourceRequest(req: SourceRequest): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// source-cache.ts — which `/__crtr/source` requests the host may burst-coalesce.
|
|
2
|
+
//
|
|
3
|
+
// The web client has no data API: every read is a `crtr … --json` subprocess
|
|
4
|
+
// POSTed to /__crtr/source, and one SSE invalidation fans out to N independent
|
|
5
|
+
// React subscribers that each re-issue their own read. On NFS-backed canvas
|
|
6
|
+
// state each such subprocess is expensive, so an SSE burst spawns a storm of
|
|
7
|
+
// duplicate reads. The host wraps the transport in a burst-coalescing decorator
|
|
8
|
+
// (transport-cache.ts) keyed on JSON.stringify(req); this predicate decides
|
|
9
|
+
// which requests are safe to share/cache within a short TTL.
|
|
10
|
+
//
|
|
11
|
+
// ONLY idempotent, side-effect-free `crtr` reads belong here. Writes
|
|
12
|
+
// (node new / canvas revive / node msg), file reads, http requests, and any
|
|
13
|
+
// request carrying stdin or an overridden cwd are NEVER cacheable — caching
|
|
14
|
+
// those would drop a mutation or serve a stale cross-cwd read. Target-dependent
|
|
15
|
+
// reads (node snapshot/inspect for a specific id) ARE cacheable because the id
|
|
16
|
+
// is part of the request and therefore part of the coalescing key, so distinct
|
|
17
|
+
// ids never collide.
|
|
18
|
+
/** TTL for coalesced source reads. Must comfortably exceed the SSE debounce
|
|
19
|
+
* (events.ts, 150 ms) so a single mutation's fan-out collapses to one
|
|
20
|
+
* subprocess per distinct read, while staying short enough that the next
|
|
21
|
+
* poll/SSE refreshes promptly. */
|
|
22
|
+
export const SOURCE_CACHE_TTL_MS = 500;
|
|
23
|
+
/** The literal `crtr … --json` read commands the web client re-issues on every
|
|
24
|
+
* SSE burst. Each entry is matched positionally against the exec args; an
|
|
25
|
+
* `'*'` placeholder matches exactly one arbitrary arg (a target id). */
|
|
26
|
+
const CACHEABLE_READS = [
|
|
27
|
+
['canvas', 'snapshot', '--json'],
|
|
28
|
+
['human', 'list', '--json'],
|
|
29
|
+
['view', 'list', '--json'],
|
|
30
|
+
['node', 'snapshot', '*', '--json'],
|
|
31
|
+
['node', 'inspect', 'show', '*', '--json'],
|
|
32
|
+
];
|
|
33
|
+
function argsMatch(pattern, args) {
|
|
34
|
+
if (pattern.length !== args.length)
|
|
35
|
+
return false;
|
|
36
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
37
|
+
if (pattern[i] === '*')
|
|
38
|
+
continue;
|
|
39
|
+
if (pattern[i] !== args[i])
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/** True iff `req` is an idempotent `crtr … --json` read safe to burst-coalesce.
|
|
45
|
+
* Anything carrying stdin or a cwd override, any non-exec request, and any
|
|
46
|
+
* command not on the {@link CACHEABLE_READS} allowlist is rejected. */
|
|
47
|
+
export function isCoalescableSourceRequest(req) {
|
|
48
|
+
if (req.kind !== 'exec')
|
|
49
|
+
return false;
|
|
50
|
+
if (req.bin !== 'crtr')
|
|
51
|
+
return false;
|
|
52
|
+
if (req.stdin !== undefined)
|
|
53
|
+
return false;
|
|
54
|
+
if (req.cwd !== undefined)
|
|
55
|
+
return false;
|
|
56
|
+
return CACHEABLE_READS.some((pattern) => argsMatch(pattern, req.args));
|
|
57
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Starts the ONE long-running unified web server (server.ts): the crouter web UI
|
|
4
4
|
// host. It serves the shell SPA, the source + command bridge (POST
|
|
5
5
|
// /__crtr/source), the SSE change lane (GET /__crtr/events), and a VERBATIM
|
|
6
|
-
// browser ⇄ broker relay on `ws://
|
|
6
|
+
// browser ⇄ broker relay on `ws://HOST:PORT/node/<id>` (the browser becomes
|
|
7
7
|
// the SAME protocol peer as `crtr attach`). The node is selected by the URL, so
|
|
8
8
|
// there is NO positional node here.
|
|
9
9
|
//
|
|
@@ -37,10 +37,10 @@ function openBrowser(url) {
|
|
|
37
37
|
const webServeLeaf = defineLeaf({
|
|
38
38
|
name: 'serve',
|
|
39
39
|
description: 'start the crouter web UI server — the shell, the source/command bridge, and a browser ⇄ broker relay',
|
|
40
|
-
whenToUse: 'you want the crouter web UI: a browser shell to watch the canvas and drive headless nodes, instead of a terminal pane. It starts ONE long-running
|
|
40
|
+
whenToUse: 'you want the crouter web UI: a browser shell to watch the canvas and drive headless nodes, instead of a terminal pane. It starts ONE long-running server (default 127.0.0.1) that serves the shell SPA, the source + command bridge (POST /__crtr/source — views\' reads AND `crtr` command writes), an SSE change-invalidation lane (GET /__crtr/events), and a VERBATIM browser ⇄ broker relay on /node/<id> (the browser becomes the same protocol peer as `crtr attach`: one controller drives, extra viewers follow read-only). If you bind a non-loopback host, pass --token so the remote-capable bridge, SSE, and relay endpoints can authorize the page. The relay NEVER starts an engine — each node must already have a running headless broker (focus or revive it first); all writes flow through `crtr` subprocesses the bridge runs. Pass --dev for Vite middleware (HMR) while iterating on the shell. The server runs until ctrl+c; the node is picked in the browser via the URL, not on the command line.',
|
|
41
41
|
help: {
|
|
42
42
|
name: 'web serve',
|
|
43
|
-
summary: 'start the long-running crouter web UI server
|
|
43
|
+
summary: 'start the long-running crouter web UI server: serves the shell SPA, the POST /__crtr/source bridge, the GET /__crtr/events SSE lane, and a VERBATIM browser ⇄ node-broker relay (browser picks the node via /node/<id>)',
|
|
44
44
|
params: [
|
|
45
45
|
{
|
|
46
46
|
kind: 'flag',
|
|
@@ -50,6 +50,21 @@ const webServeLeaf = defineLeaf({
|
|
|
50
50
|
default: DEFAULT_PORT,
|
|
51
51
|
constraint: `TCP port to bind on 127.0.0.1. Default ${DEFAULT_PORT}.`,
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
kind: 'flag',
|
|
55
|
+
name: 'host',
|
|
56
|
+
type: 'string',
|
|
57
|
+
required: false,
|
|
58
|
+
default: '127.0.0.1',
|
|
59
|
+
constraint: 'TCP host to bind. Default 127.0.0.1. Non-loopback binds require --token.',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
kind: 'flag',
|
|
63
|
+
name: 'token',
|
|
64
|
+
type: 'string',
|
|
65
|
+
required: false,
|
|
66
|
+
constraint: 'Bearer token for remote-capable bridge, SSE, and broker relay requests. Required for non-loopback binds.',
|
|
67
|
+
},
|
|
53
68
|
{
|
|
54
69
|
kind: 'flag',
|
|
55
70
|
name: 'open',
|
|
@@ -70,17 +85,21 @@ const webServeLeaf = defineLeaf({
|
|
|
70
85
|
output: [],
|
|
71
86
|
outputKind: 'object',
|
|
72
87
|
effects: [
|
|
73
|
-
'Binds a long-running HTTP+WebSocket server on
|
|
74
|
-
'Serves the shell SPA (static dist/web-client/, or Vite middleware under --dev), runs the POST /__crtr/source bridge (exec/file/http in this cwd), streams the GET /__crtr/events SSE change lane, and on a browser ws://
|
|
88
|
+
'Binds a long-running HTTP+WebSocket server on the requested host and port and keeps serving until interrupted (ctrl+c / SIGTERM). Safe to run non-interactively (daemon-like): it prints the URL and serves.',
|
|
89
|
+
'Serves the shell SPA (static dist/web-client/, or Vite middleware under --dev), runs the POST /__crtr/source bridge (exec/file/http in this cwd), streams the GET /__crtr/events SSE change lane, and on a browser ws://HOST:<port>/node/<id> connection opens that node\'s running broker view.sock and relays frames VERBATIM both directions. Non-loopback binds require a token so the remote Hearth route is not an unauthenticated command surface.',
|
|
75
90
|
'The relay NEVER spawns pi and NEVER writes any session — it only opens a socket to an already-running broker; if the broker is not running, the browser connection is closed with a clear reason. All graph mutations go through `crtr` subprocesses the bridge runs.',
|
|
76
91
|
],
|
|
77
92
|
},
|
|
78
93
|
run: async (input) => {
|
|
79
94
|
const port = input['port'] ?? DEFAULT_PORT;
|
|
95
|
+
const host = input['host'] ?? '127.0.0.1';
|
|
96
|
+
const tokenRaw = input['token'];
|
|
97
|
+
const token = tokenRaw !== undefined && tokenRaw !== '' ? tokenRaw : undefined;
|
|
80
98
|
const open = input['open'] ?? false;
|
|
81
99
|
const dev = input['dev'] ?? false;
|
|
82
|
-
const server = await startWebServer({ port, dev });
|
|
100
|
+
const server = await startWebServer({ port, host, token, dev });
|
|
83
101
|
const nodeUrlShape = `${server.url}/node/<id>`;
|
|
102
|
+
const browserUrl = token !== undefined ? `${server.url}?token=${encodeURIComponent(token)}` : server.url;
|
|
84
103
|
process.stdout.write(`crtr web serving on ${server.url}${dev ? ' (--dev: Vite middleware)' : ''}\n` +
|
|
85
104
|
` open a node: ${nodeUrlShape}\n` +
|
|
86
105
|
` bridge: POST ${server.url}/__crtr/source\n` +
|
|
@@ -88,7 +107,7 @@ const webServeLeaf = defineLeaf({
|
|
|
88
107
|
` shell assets: ${dev ? 'Vite middleware (HMR)' : server.clientDir}\n` +
|
|
89
108
|
` (ctrl+c to stop)\n`);
|
|
90
109
|
if (open)
|
|
91
|
-
openBrowser(
|
|
110
|
+
openBrowser(browserUrl);
|
|
92
111
|
// Long-running: resolve only on a shutdown signal, tearing down cleanly.
|
|
93
112
|
await new Promise((resolveShutdown) => {
|
|
94
113
|
const shutdown = () => {
|
|
@@ -108,12 +127,12 @@ export function registerWeb() {
|
|
|
108
127
|
rootEntry: {
|
|
109
128
|
concept: 'the crouter web UI server — one host serving the browser shell, the source/command bridge, an SSE change lane, and a VERBATIM relay to nodes\' running brokers',
|
|
110
129
|
desc: 'serve the crouter web UI for the canvas and headless nodes',
|
|
111
|
-
useWhen: 'you want the crouter web UI in a browser instead of a tmux pane: watch the canvas and drive headless nodes. It runs one long-lived
|
|
130
|
+
useWhen: 'you want the crouter web UI in a browser instead of a tmux pane: watch the canvas and drive headless nodes. It runs one long-lived server that serves the shell SPA, runs view sources + `crtr` command writes over POST /__crtr/source, streams canvas-change invalidations over GET /__crtr/events (SSE), and relays a browser connection VERBATIM to a node\'s already-running broker on /node/<id> — same protocol peer as `crtr attach`. The node is selected in the browser via the URL; if the server is bound off-loopback, the page must carry a token so the relay and bridge can authorize.',
|
|
112
131
|
},
|
|
113
132
|
help: {
|
|
114
133
|
name: 'web',
|
|
115
134
|
summary: 'serve the crouter web UI: the shell SPA, the source/command bridge, the SSE change lane, and a VERBATIM relay to headless nodes\' brokers',
|
|
116
|
-
model: '`serve` starts the ONE long-running unified server on 127.0.0.1
|
|
135
|
+
model: '`serve` starts the ONE long-running unified server on the bound host (--port, default 7878; --host defaults to 127.0.0.1; --token is required for non-loopback binds; --open to launch the browser; --dev for Vite middleware/HMR). It serves the shell SPA (static dist/web-client/ shipped, Vite middleware in --dev), the POST /__crtr/source bridge (views\' reads AND `crtr` command writes via the local exec/file/http transport), the GET /__crtr/events SSE change-invalidation lane, and on a browser ws://HOST:PORT/node/<id> connection opens that node\'s broker view.sock and relays frames VERBATIM both ways — the browser is the same protocol peer as `crtr attach` (one controller drives, extra viewers are read-only; arbitration + dialogs ride the same frames). The relay NEVER spawns pi or writes a session: the node must already have a running headless broker (focus or revive it first); all graph mutations flow through `crtr` subprocesses the bridge runs. The server keeps running until ctrl+c; the node is chosen in the browser via the URL, not on the command line.',
|
|
117
136
|
},
|
|
118
137
|
children: [webServeLeaf],
|
|
119
138
|
});
|
|
@@ -10,6 +10,8 @@ import { tmpdir } from 'node:os';
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { randomBytes } from 'node:crypto';
|
|
12
12
|
import { parseArgv } from '../../core/command.js';
|
|
13
|
+
import { renderLeafArgv } from '../../core/help.js';
|
|
14
|
+
import { humanAsk } from '../human/prompts.js';
|
|
13
15
|
import { humanCancel } from '../human/queue.js';
|
|
14
16
|
import { createNode, getNode, closeDb } from '../../core/canvas/index.js';
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
@@ -23,34 +25,6 @@ function tmpJson(obj) {
|
|
|
23
25
|
return p;
|
|
24
26
|
}
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
26
|
-
// human approve — positional title + optional flags
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
describe('human approve: params', () => {
|
|
29
|
-
const params = [
|
|
30
|
-
{ kind: 'positional', name: 'title', type: 'string', required: true, constraint: 'The question.' },
|
|
31
|
-
{ kind: 'flag', name: 'subtitle', type: 'string', required: false, constraint: 'One-line context.' },
|
|
32
|
-
{ kind: 'flag', name: 'body', type: 'string', required: false, constraint: 'Markdown body.' },
|
|
33
|
-
];
|
|
34
|
-
test('parses positional title', async () => {
|
|
35
|
-
const result = await parseArgv(params, ['Deploy to prod?']);
|
|
36
|
-
assert.equal(result['title'], 'Deploy to prod?');
|
|
37
|
-
});
|
|
38
|
-
test('parses title with optional flags', async () => {
|
|
39
|
-
const result = await parseArgv(params, ['Deploy?', '--subtitle', 'v1.2.3', '--body', 'LGTM']);
|
|
40
|
-
assert.equal(result['title'], 'Deploy?');
|
|
41
|
-
assert.equal(result['subtitle'], 'v1.2.3');
|
|
42
|
-
assert.equal(result['body'], 'LGTM');
|
|
43
|
-
});
|
|
44
|
-
test('flags absent → undefined', async () => {
|
|
45
|
-
const result = await parseArgv(params, ['Deploy?']);
|
|
46
|
-
assert.equal(result['subtitle'], undefined);
|
|
47
|
-
assert.equal(result['body'], undefined);
|
|
48
|
-
});
|
|
49
|
-
test('missing title throws missing_parameter', async () => {
|
|
50
|
-
await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
28
|
// human review — positional file (path) + optional --output
|
|
55
29
|
// ---------------------------------------------------------------------------
|
|
56
30
|
describe('human review: params', () => {
|
|
@@ -172,6 +146,34 @@ describe('human ask: params', () => {
|
|
|
172
146
|
});
|
|
173
147
|
});
|
|
174
148
|
// ---------------------------------------------------------------------------
|
|
149
|
+
// human ask — shorthand question + strict --context-file deck
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
describe('human ask: shorthand', () => {
|
|
152
|
+
test('help documents both the bare question and --context-file forms', () => {
|
|
153
|
+
const out = renderLeafArgv(humanAsk.help);
|
|
154
|
+
assert.match(out, /QUESTION/);
|
|
155
|
+
assert.match(out, /--context-file PATH/);
|
|
156
|
+
assert.match(out, /crtr human ask "<question>"/);
|
|
157
|
+
assert.match(out, /crtr human ask --context-file deck\.json/);
|
|
158
|
+
});
|
|
159
|
+
test('parses a positional question into input.question', async () => {
|
|
160
|
+
const result = await parseArgv(humanAsk.help.params ?? [], ['Should we ship today?']);
|
|
161
|
+
assert.equal(result['question'], 'Should we ship today?');
|
|
162
|
+
assert.equal(result['wait'], false);
|
|
163
|
+
});
|
|
164
|
+
test('parses --context-file into input.deck while keeping the strict deck path', async () => {
|
|
165
|
+
const deck = {
|
|
166
|
+
title: 'Choose a path',
|
|
167
|
+
interactions: [
|
|
168
|
+
{ id: 'q', title: 'Pick one', options: [{ id: 'yes', label: 'Yes' }] },
|
|
169
|
+
],
|
|
170
|
+
};
|
|
171
|
+
const path = tmpJson(deck);
|
|
172
|
+
const result = await parseArgv(humanAsk.help.params ?? [], ['--context-file', path]);
|
|
173
|
+
assert.deepEqual(result['deck'], deck);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
175
177
|
// human cancel — positional job_id + optional --reason
|
|
176
178
|
// ---------------------------------------------------------------------------
|
|
177
179
|
describe('human cancel: params', () => {
|
|
@@ -2,7 +2,7 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
2
2
|
import { usage } from '../../core/errors.js';
|
|
3
3
|
import { buildCorpus, } from '../../core/canvas/index.js';
|
|
4
4
|
const TYPES = ['report', 'doc', 'roadmap', 'meta'];
|
|
5
|
-
const NODE_KINDS = ['developer', 'spec', 'design', 'review', 'explore', 'plan', 'general'];
|
|
5
|
+
const NODE_KINDS = ['advisor', 'developer', 'spec', 'design', 'review', 'explore', 'plan', 'general'];
|
|
6
6
|
const STATUSES = ['active', 'idle', 'done', 'dead', 'canceled'];
|
|
7
7
|
const SORTS = ['relevance', 'recency', 'oldest'];
|
|
8
8
|
const REL_UNITS = {
|
|
@@ -9,15 +9,12 @@ import { defineBranch } from '../core/command.js';
|
|
|
9
9
|
import { searchLeaf } from './canvas-history/search.js';
|
|
10
10
|
import { readLeaf } from './canvas-history/read.js';
|
|
11
11
|
import { showLeaf } from './canvas-history/show.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
* enumerate). Soft-fails to omission if the db is unreachable. */
|
|
12
|
+
/** A tiny per-cwd `<corpus>` marker for branch -h — just enough context to
|
|
13
|
+
* anchor the help text without scanning nodes or files. Uses only the injected
|
|
14
|
+
* node cwd env (or the process cwd outside a node), so help stays cheap. */
|
|
16
15
|
function corpusBlock() {
|
|
17
|
-
const cwd =
|
|
18
|
-
|
|
19
|
-
const span = s.span !== null ? `${s.span.from} → ${s.span.to}` : 'n/a';
|
|
20
|
-
return `<corpus cwd="${cwd}" nodes="${s.nodes}" reports="${s.reports}" docs="${s.docs}" span="${span}"/>`;
|
|
16
|
+
const cwd = process.env['CRTR_NODE_CWD'] ?? process.cwd();
|
|
17
|
+
return `<corpus cwd="${cwd}"/>`;
|
|
21
18
|
}
|
|
22
19
|
export const historyBranch = defineBranch({
|
|
23
20
|
name: 'history',
|