@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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Run: node --import tsx/esm --test src/core/__tests__/model-ladders.test.ts
|
|
2
|
+
//
|
|
3
|
+
// The runtime model ladder is user-configurable: config.json can override the
|
|
4
|
+
// provider default, the per-provider ladder cells, and persona-strength picks.
|
|
5
|
+
// This test verifies both the merge shape and the cached sync read invalidation
|
|
6
|
+
// on config edits.
|
|
7
|
+
import { test, before, after, beforeEach } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { readConfig } from '../config.js';
|
|
13
|
+
import { buildLaunchSpec, equivalentOtherProviderModel, normalizeModel } from '../runtime/launch.js';
|
|
14
|
+
let home;
|
|
15
|
+
const crtrRoot = () => join(home, '.crouter');
|
|
16
|
+
const configFile = () => join(crtrRoot(), 'config.json');
|
|
17
|
+
function writeConfig(config) {
|
|
18
|
+
mkdirSync(crtrRoot(), { recursive: true });
|
|
19
|
+
writeFileSync(configFile(), `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
before(() => {
|
|
22
|
+
home = mkdtempSync(join(tmpdir(), 'crtr-model-ladders-'));
|
|
23
|
+
process.env['HOME'] = home;
|
|
24
|
+
});
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
rmSync(crtrRoot(), { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
after(() => {
|
|
29
|
+
rmSync(home, { recursive: true, force: true });
|
|
30
|
+
delete process.env['HOME'];
|
|
31
|
+
});
|
|
32
|
+
test('config merge keeps ladder defaults while honoring overrides', () => {
|
|
33
|
+
writeConfig({
|
|
34
|
+
modelLadders: {
|
|
35
|
+
defaultProvider: 'openai',
|
|
36
|
+
anthropic: {
|
|
37
|
+
ultra: 'anthropic/custom-ultra-1',
|
|
38
|
+
strong: 'anthropic/custom-strong-1',
|
|
39
|
+
medium: 'anthropic/custom-medium-1',
|
|
40
|
+
light: 'anthropic/custom-light-1',
|
|
41
|
+
},
|
|
42
|
+
openai: {
|
|
43
|
+
ultra: 'openai/custom-ultra-1',
|
|
44
|
+
strong: 'openai/custom-strong-1',
|
|
45
|
+
medium: 'openai/custom-medium-1',
|
|
46
|
+
light: 'openai/custom-light-1',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
personaStrengths: {
|
|
50
|
+
developer: 'light',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const cfg = readConfig('user');
|
|
54
|
+
assert.equal(cfg.modelLadders.defaultProvider, 'openai');
|
|
55
|
+
assert.equal(cfg.modelLadders.openai.strong, 'openai/custom-strong-1');
|
|
56
|
+
assert.equal(cfg.modelLadders.anthropic.light, 'anthropic/custom-light-1');
|
|
57
|
+
assert.equal(cfg.personaStrengths.developer, 'light');
|
|
58
|
+
});
|
|
59
|
+
test('normalizeModel and buildLaunchSpec honor config overrides, and edits invalidate the cache', () => {
|
|
60
|
+
writeConfig({
|
|
61
|
+
modelLadders: {
|
|
62
|
+
defaultProvider: 'openai',
|
|
63
|
+
anthropic: {
|
|
64
|
+
ultra: 'anthropic/custom-ultra-1',
|
|
65
|
+
strong: 'anthropic/custom-strong-1',
|
|
66
|
+
medium: 'anthropic/custom-medium-1',
|
|
67
|
+
light: 'anthropic/custom-light-1',
|
|
68
|
+
},
|
|
69
|
+
openai: {
|
|
70
|
+
ultra: 'openai/custom-ultra-1',
|
|
71
|
+
strong: 'openai/custom-strong-1',
|
|
72
|
+
medium: 'openai/custom-medium-1',
|
|
73
|
+
light: 'openai/custom-light-1',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
personaStrengths: {
|
|
77
|
+
developer: 'light',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
assert.equal(normalizeModel('strong'), 'openai/custom-strong-1', 'config default provider wins over env/default');
|
|
81
|
+
assert.equal(normalizeModel('opus'), 'anthropic/custom-strong-1', 'family aliases still map to anthropic');
|
|
82
|
+
const override = buildLaunchSpec('developer', 'base', {
|
|
83
|
+
lifecycle: 'terminal',
|
|
84
|
+
hasManager: true,
|
|
85
|
+
model: 'medium',
|
|
86
|
+
});
|
|
87
|
+
assert.equal(override.launch.model, 'openai/custom-medium-1', 'meta.model_override wins over personaStrengths');
|
|
88
|
+
const persona = buildLaunchSpec('developer', 'base', {
|
|
89
|
+
lifecycle: 'terminal',
|
|
90
|
+
hasManager: true,
|
|
91
|
+
});
|
|
92
|
+
assert.equal(persona.launch.model, 'openai/custom-light-1', 'personaStrengths wins over persona frontmatter');
|
|
93
|
+
writeConfig({
|
|
94
|
+
modelLadders: {
|
|
95
|
+
defaultProvider: 'anthropic',
|
|
96
|
+
anthropic: {
|
|
97
|
+
ultra: 'anthropic/custom-ultra-2',
|
|
98
|
+
strong: 'anthropic/custom-strong-2',
|
|
99
|
+
medium: 'anthropic/custom-medium-2',
|
|
100
|
+
light: 'anthropic/custom-light-2',
|
|
101
|
+
},
|
|
102
|
+
openai: {
|
|
103
|
+
ultra: 'openai/custom-ultra-2',
|
|
104
|
+
strong: 'openai/custom-strong-2',
|
|
105
|
+
medium: 'openai/custom-medium-2',
|
|
106
|
+
light: 'openai/custom-light-2',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
personaStrengths: {
|
|
110
|
+
developer: 'strong',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
assert.equal(normalizeModel('strong'), 'anthropic/custom-strong-2', 'a config edit invalidates the cached sync read');
|
|
114
|
+
const edited = buildLaunchSpec('developer', 'base', {
|
|
115
|
+
lifecycle: 'terminal',
|
|
116
|
+
hasManager: true,
|
|
117
|
+
});
|
|
118
|
+
assert.equal(edited.launch.model, 'anthropic/custom-strong-2', 'edited personaStrengths are picked up on the next launch');
|
|
119
|
+
});
|
|
120
|
+
test('equivalentOtherProviderModel maps a failed ladder model to the other provider at the same strength', () => {
|
|
121
|
+
writeConfig({
|
|
122
|
+
modelLadders: {
|
|
123
|
+
defaultProvider: 'anthropic',
|
|
124
|
+
anthropic: {
|
|
125
|
+
ultra: 'anthropic/custom-ultra',
|
|
126
|
+
strong: 'anthropic/custom-strong:high',
|
|
127
|
+
medium: 'anthropic/custom-medium',
|
|
128
|
+
light: 'anthropic/custom-light',
|
|
129
|
+
},
|
|
130
|
+
openai: {
|
|
131
|
+
ultra: 'openai/custom-ultra',
|
|
132
|
+
strong: 'openai/custom-strong:xhigh',
|
|
133
|
+
medium: 'openai/custom-medium',
|
|
134
|
+
light: 'openai/custom-light',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
assert.deepEqual(equivalentOtherProviderModel('anthropic/custom-strong:medium'), {
|
|
139
|
+
fromProvider: 'anthropic',
|
|
140
|
+
toProvider: 'openai',
|
|
141
|
+
strength: 'strong',
|
|
142
|
+
model: 'openai/custom-strong:xhigh',
|
|
143
|
+
});
|
|
144
|
+
assert.deepEqual(equivalentOtherProviderModel('openai/custom-light'), {
|
|
145
|
+
fromProvider: 'openai',
|
|
146
|
+
toProvider: 'anthropic',
|
|
147
|
+
strength: 'light',
|
|
148
|
+
model: 'anthropic/custom-light',
|
|
149
|
+
});
|
|
150
|
+
assert.equal(equivalentOtherProviderModel('anthropic/custom-strong:medium', new Set(['anthropic'])), null);
|
|
151
|
+
assert.equal(equivalentOtherProviderModel('some-provider/not-in-ladder'), null);
|
|
152
|
+
});
|
|
153
|
+
test('malformed existing user config surfaces instead of falling back to defaults', () => {
|
|
154
|
+
mkdirSync(crtrRoot(), { recursive: true });
|
|
155
|
+
writeFileSync(configFile(), '{\n', 'utf8');
|
|
156
|
+
assert.throws(() => buildLaunchSpec('developer', 'base', {
|
|
157
|
+
lifecycle: 'terminal',
|
|
158
|
+
hasManager: true,
|
|
159
|
+
}), /JSON|Unexpected token|Unexpected end/);
|
|
160
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { nodeEnv } from '../runtime/nodes.js';
|
|
4
|
+
function meta(over = {}) {
|
|
5
|
+
return {
|
|
6
|
+
node_id: 'n',
|
|
7
|
+
name: 'alpha-beta',
|
|
8
|
+
created: '2026-01-01T00:00:00.000Z',
|
|
9
|
+
cwd: '/tmp/work',
|
|
10
|
+
kind: 'developer',
|
|
11
|
+
mode: 'base',
|
|
12
|
+
lifecycle: 'terminal',
|
|
13
|
+
status: 'active',
|
|
14
|
+
...over,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
test('nodeEnv exports CRTR_CYCLES from meta.cycles', () => {
|
|
18
|
+
const env = nodeEnv(meta({ cycles: 9 }));
|
|
19
|
+
assert.equal(env.CRTR_CYCLES, '9');
|
|
20
|
+
});
|
|
21
|
+
test('nodeEnv defaults CRTR_CYCLES to 0 when meta.cycles is absent', () => {
|
|
22
|
+
const withoutCycles = meta();
|
|
23
|
+
delete withoutCycles.cycles;
|
|
24
|
+
const env = nodeEnv(withoutCycles);
|
|
25
|
+
assert.equal(env.CRTR_CYCLES, '0');
|
|
26
|
+
});
|
|
@@ -11,7 +11,7 @@ import { tmpdir } from 'node:os';
|
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { createNode, setStatus } from '../canvas/canvas.js';
|
|
13
13
|
import { closeDb } from '../canvas/db.js';
|
|
14
|
-
import { registerPush } from '../../commands/push.js';
|
|
14
|
+
import { registerPush, mapFinalizeTransitionRaceError } from '../../commands/push.js';
|
|
15
15
|
import { InputError } from '../io.js';
|
|
16
16
|
let home;
|
|
17
17
|
const prevNodeId = process.env['CRTR_NODE_ID'];
|
|
@@ -69,3 +69,9 @@ test('push final on a dead node is also caught cleanly', async () => {
|
|
|
69
69
|
process.env['CRTR_NODE_ID'] = id;
|
|
70
70
|
await assert.rejects(() => finalLeaf().run({ body: 'x' }), (e) => e instanceof InputError && e.payload.error === 'already_finalized');
|
|
71
71
|
});
|
|
72
|
+
test('a raw finalize-transition race is normalized to already_finalized', () => {
|
|
73
|
+
const mapped = mapFinalizeTransitionRaceError('canceled', new Error("illegal lifecycle transition: 'finalize' from status='canceled' (node raceNode)"));
|
|
74
|
+
assert.ok(mapped instanceof InputError, 'the raw transition error is converted');
|
|
75
|
+
assert.equal(mapped?.payload.error, 'already_finalized');
|
|
76
|
+
assert.equal(mapped?.payload.message, 'This node is already canceled — `push final` finishes a node once and cannot finalize it again.');
|
|
77
|
+
});
|
|
@@ -36,7 +36,7 @@ function makeDeps(pid) {
|
|
|
36
36
|
const deps = {
|
|
37
37
|
launchBroker: (nodeId) => {
|
|
38
38
|
launched.push(nodeId);
|
|
39
|
-
return { pid };
|
|
39
|
+
return { pid, exited: new Promise(() => { }) };
|
|
40
40
|
},
|
|
41
41
|
waitForViewSocket: () => true,
|
|
42
42
|
respawnViewer: () => true,
|
|
@@ -57,7 +57,7 @@ after(() => {
|
|
|
57
57
|
rmSync(home, { recursive: true, force: true });
|
|
58
58
|
delete process.env['CRTR_HOME'];
|
|
59
59
|
});
|
|
60
|
-
test('relaunchRoot parks the old root done and mints a fresh active root, reaping descendants', () => {
|
|
60
|
+
test('relaunchRoot parks the old root done and mints a fresh active root, reaping descendants', async () => {
|
|
61
61
|
// root → child → grandchild (a parent that subscribes to its workers).
|
|
62
62
|
createNode(node('root', { parent: null, lifecycle: 'resident', cwd: '/tmp/proj' }));
|
|
63
63
|
createNode(node('child', { parent: 'root' }));
|
|
@@ -65,7 +65,7 @@ test('relaunchRoot parks the old root done and mints a fresh active root, reapin
|
|
|
65
65
|
subscribe('root', 'child', true);
|
|
66
66
|
subscribe('child', 'grand', true);
|
|
67
67
|
const { deps, launched } = makeDeps(4242);
|
|
68
|
-
const res = relaunchRoot('root', deps);
|
|
68
|
+
const res = await relaunchRoot('root', deps);
|
|
69
69
|
// A fresh, DIFFERENT node id was minted and booted.
|
|
70
70
|
assert.ok(res, 'relaunchRoot returns the new node id');
|
|
71
71
|
const newId = res.newNodeId;
|
|
@@ -86,14 +86,14 @@ test('relaunchRoot parks the old root done and mints a fresh active root, reapin
|
|
|
86
86
|
assert.equal(getNode('child')?.status, 'canceled', 'descendant child canceled');
|
|
87
87
|
assert.equal(getNode('grand')?.status, 'canceled', 'descendant grandchild canceled');
|
|
88
88
|
});
|
|
89
|
-
test('relaunchRoot on a boot failure leaves the old root + descendants fully intact', () => {
|
|
89
|
+
test('relaunchRoot on a boot failure leaves the old root + descendants fully intact', async () => {
|
|
90
90
|
// The mint-and-boot-FIRST ordering: a launch failure (pid=null) must occur
|
|
91
91
|
// BEFORE the old root is parked or its descendants reaped.
|
|
92
92
|
createNode(node('root', { parent: null, lifecycle: 'resident' }));
|
|
93
93
|
createNode(node('child', { parent: 'root' }));
|
|
94
94
|
subscribe('root', 'child', true);
|
|
95
95
|
const { deps, launched } = makeDeps(null); // broker never starts
|
|
96
|
-
const res = relaunchRoot('root', deps);
|
|
96
|
+
const res = await relaunchRoot('root', deps);
|
|
97
97
|
assert.equal(res, null, 'a boot failure returns null');
|
|
98
98
|
// Old root + descendant untouched: still live, ready to keep running.
|
|
99
99
|
assert.equal(getNode('root')?.status, 'active', 'old root left active + untouched');
|
|
@@ -102,13 +102,13 @@ test('relaunchRoot on a boot failure leaves the old root + descendants fully int
|
|
|
102
102
|
assert.equal(launched.length, 1, 'one broker launch was attempted');
|
|
103
103
|
assert.equal(getNode(launched[0])?.status, 'dead', 'the half-born new node is crashed (dead)');
|
|
104
104
|
});
|
|
105
|
-
test('relaunchRoot is a no-op for a non-root child and an already-parked root', () => {
|
|
105
|
+
test('relaunchRoot is a no-op for a non-root child and an already-parked root', async () => {
|
|
106
106
|
createNode(node('root', { parent: null, lifecycle: 'resident' }));
|
|
107
107
|
createNode(node('child', { parent: 'root' }));
|
|
108
108
|
createNode(node('done-root', { parent: null, status: 'done' }));
|
|
109
109
|
const { deps, launched } = makeDeps(1);
|
|
110
|
-
assert.equal(relaunchRoot('child', deps), null, 'a child is not a relaunchable root');
|
|
111
|
-
assert.equal(relaunchRoot('done-root', deps), null, 'a done root is not relaunched again (double /new)');
|
|
112
|
-
assert.equal(relaunchRoot('ghost', deps), null, 'an unknown node is a no-op');
|
|
110
|
+
assert.equal(await relaunchRoot('child', deps), null, 'a child is not a relaunchable root');
|
|
111
|
+
assert.equal(await relaunchRoot('done-root', deps), null, 'a done root is not relaunched again (double /new)');
|
|
112
|
+
assert.equal(await relaunchRoot('ghost', deps), null, 'an unknown node is a no-op');
|
|
113
113
|
assert.deepEqual(launched, [], 'no broker was booted for any of the no-op cases');
|
|
114
114
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Run with: node --import tsx/esm --test src/core/__tests__/revive.test.ts
|
|
2
2
|
//
|
|
3
3
|
// HEADLESS RETARGET (foundation-spec §C.13 + §E). Covers the session-picker fix
|
|
4
|
-
// (resume by ABSOLUTE session-file path, cwd-immune
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// (resume by ABSOLUTE session-file path, cwd-immune) and the double-revive guard
|
|
5
|
+
// that keeps two pi processes off one session file. The argv selection is
|
|
6
|
+
// unit-tested via the pure `resumeArgs` + `buildPiArgv`; the guard + the
|
|
7
|
+
// cycle-counter bump are now driven against a fabricated BROKER-hosted node —
|
|
8
|
+
// ZERO real tmux, ZERO real boot.
|
|
9
9
|
//
|
|
10
10
|
// (1) BUG LOCKED — the double-revive guard (revive.ts:114): a node already up
|
|
11
11
|
// (its engine container alive AND its pi pid live) must NOT be re-launched —
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
// goes RED.
|
|
31
31
|
import { test, before, after, beforeEach } from 'node:test';
|
|
32
32
|
import assert from 'node:assert/strict';
|
|
33
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
33
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
34
34
|
import { tmpdir } from 'node:os';
|
|
35
35
|
import { join } from 'node:path';
|
|
36
36
|
import { spawn, spawnSync } from 'node:child_process';
|
|
37
37
|
import { createNode } from '../canvas/canvas.js';
|
|
38
38
|
import { closeDb } from '../canvas/db.js';
|
|
39
39
|
import { buildPiArgv } from '../runtime/launch.js';
|
|
40
|
+
import { headlessBrokerHost } from '../runtime/host.js';
|
|
40
41
|
import { resumeArgs, reviveNode } from '../runtime/revive.js';
|
|
41
42
|
import { createHarness } from './helpers/harness.js';
|
|
42
43
|
let home;
|
|
@@ -211,11 +212,15 @@ test('reviveNode PROCEEDS when the broker engine is DOWN (dead pid) — cycle co
|
|
|
211
212
|
// circuit → reviveNode proceeds. It bumps cycles BEFORE the detached (fake-
|
|
212
213
|
// engine) launch, so the bump is observable instantly; the throwaway broker is
|
|
213
214
|
// killed by dispose().
|
|
215
|
+
const sessionFile = join(home, 'sessions', 'crtr-revive-proceed.jsonl');
|
|
216
|
+
mkdirSync(join(home, 'sessions'), { recursive: true });
|
|
217
|
+
writeFileSync(sessionFile, '{}\n', 'utf8');
|
|
214
218
|
const M = h.fabricateBrokerNode({
|
|
215
219
|
status: 'active',
|
|
216
220
|
intent: null,
|
|
217
221
|
pi_pid: deadPid(),
|
|
218
222
|
pi_session_id: 'uuid-1',
|
|
223
|
+
pi_session_file: sessionFile,
|
|
219
224
|
});
|
|
220
225
|
assert.equal(h.node(M).cycles ?? 0, 0, 'fabricated at cycle 0 (no revive yet)');
|
|
221
226
|
reviveNode(M, { resume: true });
|
|
@@ -225,3 +230,28 @@ test('reviveNode PROCEEDS when the broker engine is DOWN (dead pid) — cycle co
|
|
|
225
230
|
await h.dispose();
|
|
226
231
|
}
|
|
227
232
|
});
|
|
233
|
+
test('reviveNode crashes and clears pid when launch/preflight throws', async () => {
|
|
234
|
+
const h = await createHarness({ headless: true, sessionPrefix: 'crtr-revive-fail' });
|
|
235
|
+
const originalLaunch = headlessBrokerHost.launch;
|
|
236
|
+
try {
|
|
237
|
+
const sessionFile = join(home, 'sessions', 'crtr-revive-fail.jsonl');
|
|
238
|
+
mkdirSync(join(home, 'sessions'), { recursive: true });
|
|
239
|
+
writeFileSync(sessionFile, '{}\n', 'utf8');
|
|
240
|
+
const M = h.fabricateBrokerNode({
|
|
241
|
+
status: 'active',
|
|
242
|
+
intent: null,
|
|
243
|
+
pi_pid: deadPid(),
|
|
244
|
+
pi_session_id: 'uuid-2',
|
|
245
|
+
pi_session_file: sessionFile,
|
|
246
|
+
});
|
|
247
|
+
headlessBrokerHost.launch = (_nodeId, _inv, _opts) => { throw new Error('boom'); };
|
|
248
|
+
assert.throws(() => reviveNode(M, { resume: true }), /refusing to revive/i);
|
|
249
|
+
const after = h.node(M);
|
|
250
|
+
assert.equal(after.status, 'dead', 'failed revive marks the node dead');
|
|
251
|
+
assert.equal(after.pi_pid, null, 'failed revive clears the stale pid');
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
headlessBrokerHost.launch = originalLaunch;
|
|
255
|
+
await h.dispose();
|
|
256
|
+
}
|
|
257
|
+
});
|
|
@@ -10,7 +10,8 @@ import { createNode, getNode, getRow, subscribersOf } from '../canvas/canvas.js'
|
|
|
10
10
|
import { closeDb } from '../canvas/db.js';
|
|
11
11
|
import { nodeDir } from '../canvas/paths.js';
|
|
12
12
|
import { spawnNode } from '../runtime/nodes.js';
|
|
13
|
-
import { resolveSpawner } from '../runtime/spawn.js';
|
|
13
|
+
import { spawnChild, resolveSpawner } from '../runtime/spawn.js';
|
|
14
|
+
import { headlessBrokerHost } from '../runtime/host.js';
|
|
14
15
|
let home;
|
|
15
16
|
function spawner(id) {
|
|
16
17
|
return {
|
|
@@ -81,3 +82,25 @@ test('unknown parent: spawnNode throws and mints NO node dir / row (validate bef
|
|
|
81
82
|
assert.equal(getRow(orphanId), null, 'no index row written for the orphan');
|
|
82
83
|
assert.equal(existsSync(nodeDir(orphanId)), false, 'no node dir scaffolded for the orphan');
|
|
83
84
|
});
|
|
85
|
+
test('spawnChild crashes the node when launch/preflight throws', async () => {
|
|
86
|
+
const parentId = 'A';
|
|
87
|
+
createNode(spawner(parentId));
|
|
88
|
+
const originalLaunch = headlessBrokerHost.launch;
|
|
89
|
+
let launchedNodeId = null;
|
|
90
|
+
try {
|
|
91
|
+
headlessBrokerHost.launch = (nodeId, _inv, _opts) => {
|
|
92
|
+
launchedNodeId = nodeId;
|
|
93
|
+
throw new Error('boom');
|
|
94
|
+
};
|
|
95
|
+
await assert.rejects(async () => spawnChild({ kind: 'developer', cwd: '/tmp/work', parent: parentId, prompt: 'task' }), /refusing to launch/i);
|
|
96
|
+
assert.ok(launchedNodeId !== null, 'the launch hook saw the spawned node id');
|
|
97
|
+
const after = getNode(launchedNodeId);
|
|
98
|
+
assert.equal(after.status, 'dead', 'failed spawn marks the node dead');
|
|
99
|
+
assert.equal(after.pi_pid, null, 'failed spawn clears the stale pid');
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
headlessBrokerHost.launch = originalLaunch;
|
|
103
|
+
closeDb();
|
|
104
|
+
rmSync(home, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
@@ -80,3 +80,13 @@ test('pushedFinal → finished; askedHuman → escalated (both short-circuit bef
|
|
|
80
80
|
assert.deepEqual(evaluateStop('t', { pushedFinal: true, askedHuman: false }), { action: 'allow', reason: 'finished' });
|
|
81
81
|
assert.deepEqual(evaluateStop('t', { pushedFinal: false, askedHuman: true }), { action: 'allow', reason: 'escalated' });
|
|
82
82
|
});
|
|
83
|
+
test('already-terminal stored statuses are allowed without a stall reprompt', () => {
|
|
84
|
+
for (const [id, status] of [
|
|
85
|
+
['doneNode', 'done'],
|
|
86
|
+
['deadNode', 'dead'],
|
|
87
|
+
['canceledNode', 'canceled'],
|
|
88
|
+
]) {
|
|
89
|
+
createNode(node(id, { parent: 'mgr', lifecycle: 'terminal', status }));
|
|
90
|
+
assert.deepEqual(evaluateStop(id, noSignals), { action: 'allow', reason: 'finished' });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/stranded-relaunch.test.ts
|
|
2
|
+
//
|
|
3
|
+
// BUG-REGRESSION (observed 2026-06-15 on the live canvas). A broker-hosted node
|
|
4
|
+
// whose row carries pi_pid=NULL but pi_session_id SET was read by
|
|
5
|
+
// handleNodeLiveness as "a relaunch in flight — leave it" UNCONDITIONALLY, with
|
|
6
|
+
// no timeout. reviveNode clears pi_pid right after launch and the fresh engine
|
|
7
|
+
// re-records it within a tick or two — but a relaunch whose broker DIED in that
|
|
8
|
+
// gap (after the pid-clear, before re-record) never re-records, so the node sat
|
|
9
|
+
// 'active' with no engine FOREVER and its parent waited on a dead child. Six such
|
|
10
|
+
// stranded resident roots had accumulated and the daemon would never recover or
|
|
11
|
+
// reap any of them.
|
|
12
|
+
//
|
|
13
|
+
// THE FIX — give the pid==null + session-set case the SAME REVIVE_GRACE_MS boot
|
|
14
|
+
// grace as the never-booted case: a healthy relaunch always re-records its pid
|
|
15
|
+
// well inside the grace (and the pid-alive branch clears the clock); if the grace
|
|
16
|
+
// ELAPSES with still no pid, the relaunch is dead → grace-revive RESUME on the
|
|
17
|
+
// saved session. The 20s grace doubles as the double-spawn guard.
|
|
18
|
+
//
|
|
19
|
+
// WHY MODEL-LEVEL, NOT TMUX CHROME — the stranding lives entirely in pure daemon
|
|
20
|
+
// logic (the pid==null branch of handleNodeLiveness + the unhealthySince clock).
|
|
21
|
+
// A fabricated broker row carrying pi_pid=null + a recorded pi_session_id
|
|
22
|
+
// reproduces the exact stranded state with no process at all; h.tick(now) walks a
|
|
23
|
+
// deterministic clock across the grace boundary and getNode().cycles (reviveNode
|
|
24
|
+
// bumps cycles + transition('revive') BEFORE the detached spawn) makes the revive
|
|
25
|
+
// observable instantly.
|
|
26
|
+
//
|
|
27
|
+
// HOW IT FAILS IF THE BUG REGRESSES — restore the unconditional
|
|
28
|
+
// `if (meta.pi_session_id != null) return;` and the PAST-grace tick never revives
|
|
29
|
+
// → cycles stays 0 → the final assert goes RED.
|
|
30
|
+
import { test } from 'node:test';
|
|
31
|
+
import assert from 'node:assert/strict';
|
|
32
|
+
import { createHarness } from './helpers/harness.js';
|
|
33
|
+
// REVIVE_GRACE_MS is 20_000 (crtrd.ts). Offsets well inside / well past it keep
|
|
34
|
+
// the test robust to the exact value without importing it.
|
|
35
|
+
const NOW = 7_000_000;
|
|
36
|
+
const WITHIN_GRACE = NOW + 10_000; // < 20s after first-observed-null → still pending
|
|
37
|
+
const PAST_GRACE = NOW + 25_000; // > 20s after first-observed-null → revive
|
|
38
|
+
test('stranded relaunch: pid=null + session set is revived once the boot grace elapses, not left active forever', { timeout: 30_000 }, async () => {
|
|
39
|
+
const h = await createHarness({ headless: true, sessionPrefix: 'crtr-stranded' });
|
|
40
|
+
try {
|
|
41
|
+
// The EXACT stranded state: a broker row that booted once (pi_session_id
|
|
42
|
+
// captured) whose relaunch then died after the pid-clear — pi_pid NULL,
|
|
43
|
+
// intent null (so it is neither the refresh nor the idle-release path).
|
|
44
|
+
const B = h.fabricateBrokerNode({
|
|
45
|
+
kind: 'developer',
|
|
46
|
+
status: 'active',
|
|
47
|
+
intent: null,
|
|
48
|
+
pi_pid: null,
|
|
49
|
+
pi_session_id: 'sess-stranded-B',
|
|
50
|
+
});
|
|
51
|
+
const b0 = h.node(B);
|
|
52
|
+
assert.equal(b0.host_kind, 'broker', 'B is broker-hosted (paneless)');
|
|
53
|
+
assert.equal(b0.pi_pid ?? null, null, 'row pi_pid is null (relaunch never re-recorded)');
|
|
54
|
+
assert.equal(b0.pi_session_id, 'sess-stranded-B', 'session is set (booted once)');
|
|
55
|
+
assert.equal(b0.cycles ?? 0, 0, 'no revive yet — cycles at 0');
|
|
56
|
+
// --- TICK 1 @ NOW: first observation of null pid → start the boot grace
|
|
57
|
+
// clock, do NOT revive (a healthy relaunch re-records its pid here). ---
|
|
58
|
+
await h.tick(NOW);
|
|
59
|
+
assert.equal(h.node(B).cycles ?? 0, 0, 'first tick: no revive — grace clock started');
|
|
60
|
+
// --- TICK 2 @ NOW+10s (WITHIN grace): still pending. This is the
|
|
61
|
+
// double-spawn guard — a healthy relaunch would have re-recorded by now. ---
|
|
62
|
+
await h.tick(WITHIN_GRACE);
|
|
63
|
+
assert.equal(h.node(B).cycles ?? 0, 0, 'within REVIVE_GRACE_MS: still no revive (healthy relaunch grace, not a strand yet)');
|
|
64
|
+
// --- TICK 3 @ NOW+25s (PAST grace): the relaunch is dead → grace-revive
|
|
65
|
+
// RESUME on the saved session; reviveNode bumps cycles immediately. ---
|
|
66
|
+
await h.tick(PAST_GRACE);
|
|
67
|
+
assert.equal(h.node(B).cycles ?? 0, 1, 'past REVIVE_GRACE_MS: the stranded relaunch is revived (cycles bumped) — NOT left active forever');
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await h.dispose();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -68,7 +68,11 @@ test('§2.2 driver invariant: every placement verb in tmux.ts passes an explicit
|
|
|
68
68
|
found++;
|
|
69
69
|
const verb = m[1];
|
|
70
70
|
const arr = enclosingArray(src, m.index);
|
|
71
|
-
|
|
71
|
+
// The invariant is "name a specific tmux object, never fall back to the
|
|
72
|
+
// GLOBAL current one". `-t` targets a destination; `break-pane` (and the
|
|
73
|
+
// deleted swap/join/move-pane) instead name their SOURCE with `-s` — which
|
|
74
|
+
// satisfies the invariant identically. Accept either explicit target flag.
|
|
75
|
+
assert.ok(arr.includes(`'-t'`) || arr.includes(`'-s'`), `tmux verb '${verb}' is invoked WITHOUT an explicit -t/-s target — a latent ` +
|
|
72
76
|
`instance of the unbidden-window bug (§2.2). Offending args array:\n${arr}`);
|
|
73
77
|
}
|
|
74
78
|
// Sanity: the driver really does contain placement verbs (so a refactor that
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// The `next` road sign must name a command that actually exists: the FULL path
|
|
5
5
|
// to the deepest matched node, not just its local name. A prior bug emitted
|
|
6
|
-
// `crtr
|
|
6
|
+
// `crtr inspect -h` (dropping the `pkg` parent) when `crtr pkg inspect bogus` was
|
|
7
7
|
// invoked, sending the caller to a nonexistent top-level command.
|
|
8
8
|
import { test, describe } from 'node:test';
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
@@ -15,25 +15,25 @@ const leaf = defineLeaf({
|
|
|
15
15
|
help: { name: 'search', summary: 'search', output: [], outputKind: 'object', effects: ['None. Read-only.'] },
|
|
16
16
|
run: async () => ({}),
|
|
17
17
|
});
|
|
18
|
-
const
|
|
19
|
-
name: '
|
|
20
|
-
description: '
|
|
18
|
+
const inspectBranch = defineBranch({
|
|
19
|
+
name: 'inspect',
|
|
20
|
+
description: 'inspect',
|
|
21
21
|
whenToUse: 'x',
|
|
22
|
-
help: { name: '
|
|
22
|
+
help: { name: 'inspect', summary: 'inspect' },
|
|
23
23
|
children: [leaf],
|
|
24
24
|
});
|
|
25
|
-
const
|
|
26
|
-
name: '
|
|
27
|
-
description: '
|
|
25
|
+
const pkgBranch = defineBranch({
|
|
26
|
+
name: 'pkg',
|
|
27
|
+
description: 'pkg',
|
|
28
28
|
whenToUse: 'x',
|
|
29
|
-
help: { name: '
|
|
30
|
-
rootEntry: { concept: '
|
|
31
|
-
children: [
|
|
29
|
+
help: { name: 'pkg', summary: 'pkg' },
|
|
30
|
+
rootEntry: { concept: 'pkg', desc: 'pkg', useWhen: 'x' },
|
|
31
|
+
children: [inspectBranch],
|
|
32
32
|
});
|
|
33
33
|
const root = defineRoot({
|
|
34
34
|
tagline: 'test runtime',
|
|
35
35
|
globals: [],
|
|
36
|
-
subtrees: [
|
|
36
|
+
subtrees: [pkgBranch],
|
|
37
37
|
});
|
|
38
38
|
function nextHint(...tokens) {
|
|
39
39
|
const { node, path, remaining } = walk(root, tokens);
|
|
@@ -44,15 +44,15 @@ describe('unknown-path error: recovery hint names the full valid path', () => {
|
|
|
44
44
|
test('root-level unknown points at `crtr -h`', () => {
|
|
45
45
|
assert.match(nextHint('bogus'), /Run `crtr -h`/);
|
|
46
46
|
});
|
|
47
|
-
test('one-level unknown points at `crtr
|
|
48
|
-
assert.match(nextHint('
|
|
47
|
+
test('one-level unknown points at `crtr pkg -h`', () => {
|
|
48
|
+
assert.match(nextHint('pkg', 'bogus'), /Run `crtr pkg -h`/);
|
|
49
49
|
});
|
|
50
|
-
test('two-level unknown points at `crtr
|
|
51
|
-
const hint = nextHint('
|
|
52
|
-
assert.match(hint, /Run `crtr
|
|
53
|
-
assert.doesNotMatch(hint, /Run `crtr
|
|
50
|
+
test('two-level unknown points at `crtr pkg inspect -h`, not `crtr inspect -h`', () => {
|
|
51
|
+
const hint = nextHint('pkg', 'inspect', 'bogus');
|
|
52
|
+
assert.match(hint, /Run `crtr pkg inspect -h`/);
|
|
53
|
+
assert.doesNotMatch(hint, /Run `crtr inspect -h`/);
|
|
54
54
|
});
|
|
55
55
|
test('valid children of the matched node are listed', () => {
|
|
56
|
-
assert.match(nextHint('
|
|
56
|
+
assert.match(nextHint('pkg', 'inspect', 'bogus'), /Valid children: search\./);
|
|
57
57
|
});
|
|
58
58
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/yield-ensures-daemon.test.ts
|
|
2
|
+
//
|
|
3
|
+
// Regression: `crtr node yield` timed out into "broker gone" instead of reviving
|
|
4
|
+
// in the same pane (observed 2026-06-18). The yield arms intent='refresh' and
|
|
5
|
+
// the broker shuts down expecting the DAEMON to relaunch it — but requestYield
|
|
6
|
+
// never ensured the daemon was running, unlike the spawn/recycle paths. With a
|
|
7
|
+
// dead daemon, the broker exited and nothing revived it; the viewer redialed its
|
|
8
|
+
// view.sock for ~30s and reported "broker gone". This locks in that requestYield
|
|
9
|
+
// ensures the supervisor before arming the refresh.
|
|
10
|
+
import { test, before, beforeEach, after } from 'node:test';
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { createNode, getNode } from '../canvas/canvas.js';
|
|
16
|
+
import { closeDb } from '../canvas/db.js';
|
|
17
|
+
import { requestYield } from '../runtime/promote.js';
|
|
18
|
+
let home;
|
|
19
|
+
function mkOrchestrator(id) {
|
|
20
|
+
const meta = {
|
|
21
|
+
node_id: id,
|
|
22
|
+
name: id,
|
|
23
|
+
created: new Date().toISOString(),
|
|
24
|
+
cwd: '/tmp/work',
|
|
25
|
+
kind: 'general',
|
|
26
|
+
mode: 'orchestrator', // already an orchestrator → yield skips the promote path
|
|
27
|
+
lifecycle: 'terminal',
|
|
28
|
+
status: 'active', // LIVE, so transition('yield') is legal
|
|
29
|
+
};
|
|
30
|
+
createNode(meta);
|
|
31
|
+
}
|
|
32
|
+
before(() => {
|
|
33
|
+
home = mkdtempSync(join(tmpdir(), 'crtr-yield-ensure-'));
|
|
34
|
+
process.env['CRTR_HOME'] = home;
|
|
35
|
+
});
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
closeDb();
|
|
38
|
+
rmSync(home, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
after(() => {
|
|
41
|
+
closeDb();
|
|
42
|
+
rmSync(home, { recursive: true, force: true });
|
|
43
|
+
delete process.env['CRTR_HOME'];
|
|
44
|
+
});
|
|
45
|
+
test('requestYield ensures the daemon before arming intent=refresh', () => {
|
|
46
|
+
mkOrchestrator('n1');
|
|
47
|
+
let ensured = 0;
|
|
48
|
+
const res = requestYield('n1', {}, { ensure: () => { ensured += 1; } });
|
|
49
|
+
// The supervisor that performs the refresh-revive must have been ensured...
|
|
50
|
+
assert.equal(ensured, 1, 'requestYield must ensure the daemon is running');
|
|
51
|
+
// ...and the yield must still arm the refresh.
|
|
52
|
+
assert.equal(res.willRefresh, true);
|
|
53
|
+
assert.equal(getNode('n1')?.intent, 'refresh');
|
|
54
|
+
});
|