@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,10 +1,10 @@
|
|
|
1
1
|
// Session naming — turn a node's first prompt into a short, human-readable
|
|
2
2
|
// handle for the editor label.
|
|
3
3
|
//
|
|
4
|
-
// A node's editor label is `<kind> (<mode>)
|
|
5
|
-
//
|
|
4
|
+
// A node's editor label is `<name> <kind> (<mode>)` (see editorLabel in
|
|
5
|
+
// labels.ts). The `<name>` is a 3-8 word kebab-case "description" derived from
|
|
6
6
|
// the first prompt by asking pi headlessly (`pi -p`), persisted on the node's
|
|
7
|
-
// meta so it survives revives and
|
|
7
|
+
// meta so it survives revives and updates the live session name.
|
|
8
8
|
//
|
|
9
9
|
// One entry point: generateAndPersistName — async (execFile, non-blocking).
|
|
10
10
|
// Naming happens INSIDE the named node's own pi process, off the first real
|
|
@@ -73,10 +73,10 @@ const DEFAULT_NAME_MODEL = 'anthropic/claude-haiku-4-5';
|
|
|
73
73
|
* side-effect free. Pinned to Haiku with thinking off — naming is a trivial
|
|
74
74
|
* classification that never needs a reasoning budget. Override the model with
|
|
75
75
|
* CRTR_NAME_MODEL. */
|
|
76
|
-
function
|
|
76
|
+
function piTextArgs(systemPrompt, userPrompt) {
|
|
77
77
|
const override = process.env['CRTR_NAME_MODEL'];
|
|
78
78
|
const model = override !== undefined && override.trim() !== '' ? override.trim() : DEFAULT_NAME_MODEL;
|
|
79
|
-
|
|
79
|
+
return [
|
|
80
80
|
'-p',
|
|
81
81
|
'--no-session',
|
|
82
82
|
'--no-context-files',
|
|
@@ -86,13 +86,15 @@ function nameArgs(prompt) {
|
|
|
86
86
|
'--no-themes',
|
|
87
87
|
'--no-tools',
|
|
88
88
|
'--mode', 'text',
|
|
89
|
-
//
|
|
89
|
+
// A trivial one-line classification — no thinking budget, ever.
|
|
90
90
|
'--thinking', 'off',
|
|
91
91
|
'--model', model,
|
|
92
|
+
'--system-prompt', systemPrompt,
|
|
93
|
+
userPrompt,
|
|
92
94
|
];
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return
|
|
95
|
+
}
|
|
96
|
+
function nameArgs(prompt) {
|
|
97
|
+
return piTextArgs(NAME_SYSTEM_PROMPT, nameUserPrompt(prompt));
|
|
96
98
|
}
|
|
97
99
|
/** Ask pi headlessly for a kebab-case name for `body`, async. Resolves to the
|
|
98
100
|
* sanitized name, or '' on any failure (non-zero exit, timeout, empty/garbled
|
|
@@ -148,3 +150,53 @@ export function generateAndPersistName(nodeId, prompt, onNamed) {
|
|
|
148
150
|
persist(name !== '' ? name : slugFromPrompt(body));
|
|
149
151
|
});
|
|
150
152
|
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Issue titles — turn a free-text todo/idea/bug note into a short, human-readable
|
|
155
|
+
// GitHub-issue title (sentence case, NOT kebab). Used by `crtr canvas issue new`.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
const TITLE_SYSTEM_PROMPT = 'You write GitHub issue titles. Given a free-text note (a todo, idea, or bug ' +
|
|
158
|
+
'report), reply with ONLY a concise issue title: sentence case, 4-10 words, no ' +
|
|
159
|
+
'trailing period, no quotes, no prefix like "Bug:" or "Idea:", no markdown. ' +
|
|
160
|
+
'Capture the gist as an actionable title. Output JUST the title, nothing else.';
|
|
161
|
+
function titleUserPrompt(note) {
|
|
162
|
+
return `<note>\n${note.slice(0, PROMPT_CAP)}\n</note>\n\nWrite a concise GitHub issue title for the note above. Output JUST the title, nothing else.`;
|
|
163
|
+
}
|
|
164
|
+
/** First non-empty line of `note`, trimmed and clamped to ~80 chars — the local
|
|
165
|
+
* fallback title when the headless call fails. */
|
|
166
|
+
function firstLineTitle(note) {
|
|
167
|
+
const line = (note ?? '').split('\n').map((l) => l.trim()).find((l) => l !== '') ?? '';
|
|
168
|
+
const clamped = line.length > 80 ? `${line.slice(0, 77).trimEnd()}…` : line;
|
|
169
|
+
return clamped !== '' ? clamped : 'untitled';
|
|
170
|
+
}
|
|
171
|
+
/** A filesystem-safe slug for the `.todos/<slug>.md` fallback path: lowercase,
|
|
172
|
+
* [a-z0-9] kept, everything else → single hyphen, clamped to 8 words. */
|
|
173
|
+
export function titleSlug(title) {
|
|
174
|
+
const words = (title ?? '')
|
|
175
|
+
.toLowerCase()
|
|
176
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
177
|
+
.split('-')
|
|
178
|
+
.filter((w) => w !== '');
|
|
179
|
+
return words.slice(0, 8).join('-') || 'untitled';
|
|
180
|
+
}
|
|
181
|
+
/** Ask pi headlessly for a one-line issue title for `note`. Resolves to the
|
|
182
|
+
* generated title, or a local first-line fallback on any failure (non-zero
|
|
183
|
+
* exit, timeout, empty output). Async; never throws. */
|
|
184
|
+
export function generateIssueTitle(note) {
|
|
185
|
+
const body = (note ?? '').trim();
|
|
186
|
+
if (body === '')
|
|
187
|
+
return Promise.resolve('untitled');
|
|
188
|
+
return new Promise((resolve) => {
|
|
189
|
+
try {
|
|
190
|
+
const child = execFile('pi', piTextArgs(TITLE_SYSTEM_PROMPT, titleUserPrompt(body)), { encoding: 'utf8', timeout: NAME_TIMEOUT_MS }, (err, stdout) => {
|
|
191
|
+
if (err || typeof stdout !== 'string')
|
|
192
|
+
return resolve(firstLineTitle(body));
|
|
193
|
+
const t = stdout.trim().split('\n').map((l) => l.trim()).find((l) => l !== '') ?? '';
|
|
194
|
+
resolve(t !== '' ? t.replace(/^["']|["']$/g, '').replace(/\.$/, '') : firstLineTitle(body));
|
|
195
|
+
});
|
|
196
|
+
child.stdin?.end(); // immediate EOF — pi -p reads stdin (see headlessName)
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
resolve(firstLineTitle(body));
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// is also recorded.
|
|
15
15
|
import { randomBytes } from 'node:crypto';
|
|
16
16
|
import { mkdirSync } from 'node:fs';
|
|
17
|
-
import { createNode, getNode, getRow, subscribe, recordSpawn, } from '../canvas/index.js';
|
|
17
|
+
import { createNode, getNode, getRow, subscribe, recordSpawn, contextDir, } from '../canvas/index.js';
|
|
18
18
|
import { memoryDir } from './memory.js';
|
|
19
19
|
/** Generate a node id in the same shape as job ids (time-sortable + random). */
|
|
20
20
|
export function newNodeId() {
|
|
@@ -59,6 +59,11 @@ export function nodeEnv(meta) {
|
|
|
59
59
|
CRTR_MODE: meta.mode,
|
|
60
60
|
CRTR_LIFECYCLE: meta.lifecycle,
|
|
61
61
|
CRTR_NODE_CWD: meta.cwd,
|
|
62
|
+
// The node's own context dir (canvas home), as an ABSOLUTE path. The engine
|
|
63
|
+
// runs with cwd = CRTR_NODE_CWD (the project), so a relative `context/`
|
|
64
|
+
// resolves into the REPO, not here — agents must address artifacts via this.
|
|
65
|
+
CRTR_CONTEXT_DIR: contextDir(meta.node_id),
|
|
66
|
+
CRTR_CYCLES: (meta.cycles ?? 0).toString(),
|
|
62
67
|
};
|
|
63
68
|
if (meta.parent)
|
|
64
69
|
env['CRTR_PARENT_NODE_ID'] = meta.parent;
|
|
@@ -28,11 +28,10 @@ import { orchestratorContextNote } from './bearings.js';
|
|
|
28
28
|
// base→orchestrator guidance (the roadmap-shaping dump) — MOVED here from
|
|
29
29
|
// promote.ts so the injector is the one place that builds it.
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
|
-
/** Coerce a frontmatter scalar to its string form
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `roadmapSkill` be silently dropped by a `typeof === 'string'` guard. */
|
|
31
|
+
/** Coerce a frontmatter scalar to its string form. Strings pass through;
|
|
32
|
+
* number/boolean coerce via String(); null/undefined and non-scalars are
|
|
33
|
+
* dropped (→ null). This lets numeric/boolean `roadmapKnowledge` knowledge-doc
|
|
34
|
+
* pointers resolve the same way string pointers do. */
|
|
36
35
|
function scalarToString(v) {
|
|
37
36
|
if (typeof v === 'string')
|
|
38
37
|
return v;
|
|
@@ -40,10 +39,10 @@ function scalarToString(v) {
|
|
|
40
39
|
return String(v);
|
|
41
40
|
return null;
|
|
42
41
|
}
|
|
43
|
-
/** Load a
|
|
44
|
-
* Used to inline a kind's roadmap-shaping
|
|
45
|
-
* guidance
|
|
46
|
-
function
|
|
42
|
+
/** Load a knowledge doc's body text by name, or null if it can't be resolved.
|
|
43
|
+
* Used to inline a kind's roadmap-shaping knowledge doc into the orchestration
|
|
44
|
+
* guidance. */
|
|
45
|
+
function loadRoadmapKnowledgeBody(name) {
|
|
47
46
|
try {
|
|
48
47
|
return resolveMemoryDoc(name).body.trim();
|
|
49
48
|
}
|
|
@@ -52,8 +51,8 @@ function loadSkillBody(name) {
|
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
/** The base→orchestrator guidance dump, specialized to the node's kind: the
|
|
55
|
-
* shared kernel + that kind's roadmap-shaping
|
|
56
|
-
* node must author + the orchestrator context-dir framing + a short pointer at
|
|
54
|
+
* shared kernel + that kind's roadmap-shaping memory doc + the roadmap scaffold
|
|
55
|
+
* the node must author + the orchestrator context-dir framing + a short pointer at
|
|
57
56
|
* the substrate memory flow (promotion re-informs of nothing about the stores,
|
|
58
57
|
* it only adds the across-cycles relevance). The node is now a delegator whose
|
|
59
58
|
* scarce resource is its own context window. (Lifecycle is left to its own
|
|
@@ -61,8 +60,8 @@ function loadSkillBody(name) {
|
|
|
61
60
|
function orchestrationGuidance(nodeId, kind) {
|
|
62
61
|
const kernel = loadKernel();
|
|
63
62
|
const orch = loadPersona(kind, 'orchestrator');
|
|
64
|
-
const
|
|
65
|
-
const
|
|
63
|
+
const roadmapKnowledge = scalarToString(orch?.frontmatter?.['roadmapKnowledge']) ?? undefined;
|
|
64
|
+
const roadmapKnowledgeBody = roadmapKnowledge ? loadRoadmapKnowledgeBody(roadmapKnowledge) : null;
|
|
66
65
|
const roadmap = readRoadmap(nodeId) ?? '(no roadmap yet)';
|
|
67
66
|
const rmPath = roadmapPath(nodeId);
|
|
68
67
|
const parts = [
|
|
@@ -71,14 +70,14 @@ function orchestrationGuidance(nodeId, kind) {
|
|
|
71
70
|
'',
|
|
72
71
|
kernel,
|
|
73
72
|
];
|
|
74
|
-
if (
|
|
75
|
-
parts.push('', `--- How to shape a ${kind} roadmap (
|
|
73
|
+
if (roadmapKnowledgeBody) {
|
|
74
|
+
parts.push('', `--- How to shape a ${kind} roadmap (knowledge: ${roadmapKnowledge}) ---`, '', roadmapKnowledgeBody);
|
|
76
75
|
}
|
|
77
76
|
parts.push('', `Your roadmap scaffold (\`${rmPath}\`) — author it now: state the goal, exit criteria, and the phase skeleton, using the approach above. Current contents:`, '', roadmap, '',
|
|
78
77
|
// The orchestrator framing for the context dir — the missing guidance a
|
|
79
78
|
// promoted node never got at spawn (it spawned as a base worker). Same note
|
|
80
|
-
// a born-orchestrator gets in its <crtr-
|
|
81
|
-
orchestratorContextNote(nodeId), '', 'Your three long-term memory scopes (user-global, project, node-local) already surface in your
|
|
79
|
+
// a born-orchestrator gets in its <crtr-bearings> block.
|
|
80
|
+
orchestratorContextNote(nodeId), '', 'Your three long-term memory scopes (user-global, project, node-local) already surface in your bearings at boot. What promotion changes is USE, not access: you now persist across refresh cycles, so node-local carries what THIS goal needs between your refreshes, and durable facts you commit to user-global or project outlive this node. Use `crtr memory write` to save a document; `crtr memory list` to browse; `crtr memory find` to search (see "Your long-term memory" above for the full substrate flow).', '', 'Then delegate each phase with `crtr node new --kind <kind>`. When your context fills, run `crtr node yield` to refresh against this roadmap.');
|
|
82
81
|
return parts.join('\n');
|
|
83
82
|
}
|
|
84
83
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FocusRow } from '../canvas/index.js';
|
|
2
2
|
export type { FocusRow };
|
|
3
|
-
export { splitWindow, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
|
|
3
|
+
export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
|
|
4
4
|
export type { RespawnPaneOpts } from './tmux.js';
|
|
5
5
|
/** The viewer a node has on screen, or null. UNIQUE(node_id) ⇒ at most one. */
|
|
6
6
|
export declare function focusOf(nodeId: string): FocusRow | null;
|
|
@@ -22,17 +22,26 @@ export declare function listFocuses(): FocusRow[];
|
|
|
22
22
|
* tmux probe, so the pane may be stale; the caller liveness-checks before
|
|
23
23
|
* targeting it. */
|
|
24
24
|
export declare function graphSurfaceTarget(nodeId: string): FocusRow | null;
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
/** Wait until a broker's view.sock accepts a connection. The launching caller
|
|
26
|
+
* stays async, so the broker's real exit signal can fail fast the wait instead
|
|
27
|
+
* of being masked by a zombie pid. Success proves more than file existence: it
|
|
28
|
+
* is robust to a stale leftover socket the broker has not unlinked yet,
|
|
29
|
+
* because only an accepting listener yields a `connect`. */
|
|
30
|
+
export declare function waitForBrokerViewSocket(nodeId: string, exited?: Promise<{
|
|
31
|
+
code: number | null;
|
|
32
|
+
signal: NodeJS.Signals | null;
|
|
33
|
+
}>): Promise<boolean>;
|
|
32
34
|
/** A reviver: ensure a node's broker ENGINE is alive (idempotent → reviveNode →
|
|
33
35
|
* headlessBrokerHost.launch). Injected so placement.ts need not import revive.ts
|
|
34
36
|
* (which imports placement.ts — a cycle). */
|
|
35
|
-
export type Reviver = (nodeId: string) =>
|
|
37
|
+
export type Reviver = (nodeId: string) => {
|
|
38
|
+
launch?: {
|
|
39
|
+
exited: Promise<{
|
|
40
|
+
code: number | null;
|
|
41
|
+
signal: NodeJS.Signals | null;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
} | void;
|
|
36
45
|
/** Result of a focus op. `inPlace` = navigated to an existing viewer (no new
|
|
37
46
|
* pane); false = a fresh viewer pane was opened/moved. `revived` = this call
|
|
38
47
|
* had to (re)launch the broker engine. */
|
|
@@ -72,32 +81,11 @@ export declare function openViewerWindow(nodeId: string, session: string, opts?:
|
|
|
72
81
|
cwd?: string;
|
|
73
82
|
besidePane?: string;
|
|
74
83
|
}): FocusRow | null;
|
|
75
|
-
/** Bring a node's ONE viewer pane forefront (§A.4). Every node is a detached
|
|
76
|
-
* broker, so there is no engine pane to swap into the caller's viewport — focus
|
|
77
|
-
* instead ensures the node's single `crtr attach` viewer is on screen beside (or
|
|
78
|
-
* navigated to) the caller:
|
|
79
|
-
* (a) ensure the broker ENGINE is alive (idempotent injected reviver) and its
|
|
80
|
-
* view.sock is ACCEPTING (closes the cold-start race against attach's
|
|
81
|
-
* single connect) — without this the freshly-opened viewer would exit
|
|
82
|
-
* "no broker".
|
|
83
|
-
* (b) `--new-pane` → always a fresh viewer beside the caller (e.g. two
|
|
84
|
-
* different nodes side by side); drop any prior viewer first (UNIQUE).
|
|
85
|
-
* (c) the node already has a live viewer:
|
|
86
|
-
* - in the caller's session ⇒ navigate to it (switchClient+selectWindow),
|
|
87
|
-
* no new pane (inPlace).
|
|
88
|
-
* - elsewhere (e.g. a --root's viewer in another session) ⇒ MOVE it: close the old
|
|
89
|
-
* viewer pane (the broker runs on; the fresh viewer replays full
|
|
90
|
-
* scrollback from the broker `welcome` snapshot), drop its row, reopen
|
|
91
|
-
* beside the caller.
|
|
92
|
-
* (d) no live viewer ⇒ open one beside the caller and register the row.
|
|
93
|
-
*
|
|
94
|
-
* crtr is tmux-only: with no caller pane there is no viewport to open and no
|
|
95
|
-
* non-tmux fallback — report not-focused. */
|
|
96
84
|
export declare function focus(nodeId: string, opts: {
|
|
97
85
|
pane?: string;
|
|
98
86
|
newPane?: boolean;
|
|
99
87
|
revive: Reviver;
|
|
100
|
-
}): FocusResult
|
|
88
|
+
}): Promise<FocusResult>;
|
|
101
89
|
/** Tear a node off its viewer (close/reset/cancel teardown). The broker engine
|
|
102
90
|
* is killed by its own host teardown over view.sock — this only closes the
|
|
103
91
|
* on-screen viewer: kill its pane (the window collapses once its last pane goes)
|
|
@@ -112,3 +100,20 @@ export declare function tearDownNode(nodeId: string): void;
|
|
|
112
100
|
* acts on (the Alt+C menu's `#{pane_id}`); falls back to the node's registered
|
|
113
101
|
* viewer pane. No-op (false) when there is no live viewer pane to close. */
|
|
114
102
|
export declare function detachToBackground(nodeId: string, pane?: string): boolean;
|
|
103
|
+
/** Reap `nodeId` IFF it is an empty shell (see {@link isEmptyNode}): tear down its
|
|
104
|
+
* broker engine, close any viewer, and hard-delete its row (edges cascade) + dir.
|
|
105
|
+
* The user closing or detaching from a node that never said anything wants it
|
|
106
|
+
* GONE, not parked as a canceled husk. Returns true when it reaped. No-op (false)
|
|
107
|
+
* on a node that produced output, is mid-first-turn, or no longer exists. */
|
|
108
|
+
export declare function reapIfEmpty(nodeId: string): boolean;
|
|
109
|
+
/** Sweep the whole canvas for empty shells and reap them — the bulk cleanup
|
|
110
|
+
* behind `crtr canvas prune --empty`, for the husks that accumulated before
|
|
111
|
+
* close/detach learned to reap. Skips: the caller ($CRTR_NODE_ID), nodes
|
|
112
|
+
* mid-first-turn, and any node with a LIVE broker (pi_pid alive) — a running
|
|
113
|
+
* engine is the daemon's domain and may have output not yet persisted, so bulk
|
|
114
|
+
* GC never kills one (the per-action {@link reapIfEmpty} does, since the user
|
|
115
|
+
* explicitly closed/detached that node). `dryRun` reports candidates without
|
|
116
|
+
* deleting. Returns the reaped (or, under dryRun, reapable) node ids. */
|
|
117
|
+
export declare function reapEmptyNodes(opts?: {
|
|
118
|
+
dryRun?: boolean;
|
|
119
|
+
}): string[];
|
|
@@ -23,25 +23,29 @@
|
|
|
23
23
|
// model-over-driver so the §5.1 import-lint ("only placement.ts /
|
|
24
24
|
// tmux-chrome.ts import tmux.ts") holds — every other module reaches the
|
|
25
25
|
// driver verbs through here.
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
26
|
+
import { createConnection } from 'node:net';
|
|
27
|
+
import { getNode, deleteNode, listNodes, openFocusRow, closeFocusRow, getFocusByNode, getFocusByPane, getFocusById, listFocuses as listFocusRows, view, } from '../canvas/index.js';
|
|
28
|
+
import { nodeHasAssistantMessage, isNodeStreaming } from '../canvas/render.js';
|
|
29
|
+
import { headlessBrokerHost } from './host.js';
|
|
30
|
+
import { paneExists, paneLocation, ensureSession, openNodeWindow, splitWindow, closePane, currentTmux, switchClient, selectWindow, selectLayout, selectPane, getPaneOption, } from './tmux.js';
|
|
30
31
|
import { newNodeId, rootOfSpine } from './nodes.js';
|
|
31
|
-
import {
|
|
32
|
+
import { viewSocketPath } from '../canvas/paths.js';
|
|
32
33
|
import { isPidAlive } from '../canvas/pid.js';
|
|
33
34
|
// Placement is the sanctioned model-over-driver (§2.1): non-placement runtime /
|
|
34
35
|
// command modules that legitimately need a raw driver verb get it from here, so
|
|
35
36
|
// the §5.1 lint can hold "only placement.ts / tmux-chrome.ts import tmux.ts".
|
|
36
37
|
// These are the chrome + viewer verbs that survive the cut (the engine-in-pane
|
|
37
38
|
// verbs — respawnPane, respawnPaneDetached, swapPaneInPlace, breakPaneToSession,
|
|
38
|
-
// joinPane,
|
|
39
|
+
// joinPane, setWindowOption, setRemainOnExit — are deleted). `breakPane` +
|
|
40
|
+
// `selectLayout` are FRESH minimal viewer-chrome helpers (not the deleted
|
|
41
|
+
// engine-in-pane `breakPaneToSession`): `canvas tmux-spread` breaks the caller's
|
|
42
|
+
// viewer pane into its own window and tiles sibling viewers beside it.
|
|
39
43
|
// `piCommand` + `paneCurrentPath` are KEPT: the sessionless `crtr view` system
|
|
40
44
|
// (view-cycle.ts / view-run.ts) builds its pane commands with them, and the §5.1
|
|
41
45
|
// import-lint forbids those commands importing tmux.ts directly — they must reach
|
|
42
46
|
// the driver through placement. (They are NOT engine-in-pane; the design's
|
|
43
47
|
// "nothing keeps" classification missed the view consumers.)
|
|
44
|
-
export { splitWindow, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
|
|
48
|
+
export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
|
|
45
49
|
// ---------------------------------------------------------------------------
|
|
46
50
|
// Viewer registry reads — COMPOSE over the canvas focuses table (§2.3/§4).
|
|
47
51
|
//
|
|
@@ -115,61 +119,68 @@ export function graphSurfaceTarget(nodeId) {
|
|
|
115
119
|
// ---------------------------------------------------------------------------
|
|
116
120
|
const BROKER_FOCUS_SOCKET_WAIT_MS = 30_000;
|
|
117
121
|
const BROKER_FOCUS_SOCKET_RETRY_MS = 100;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const VIEW_SOCKET_PROBE_WORKER = `
|
|
123
|
-
const net = require('node:net');
|
|
124
|
-
const { workerData, parentPort } = require('node:worker_threads');
|
|
125
|
-
const { buffer, sockPath, waitMs, retryMs } = workerData;
|
|
126
|
-
const result = new Int32Array(buffer);
|
|
127
|
-
const deadline = Date.now() + waitMs;
|
|
128
|
-
const report = (code) => { Atomics.store(result, 0, code); Atomics.notify(result, 0); };
|
|
129
|
-
function attempt() {
|
|
130
|
-
let socket;
|
|
131
|
-
let settled = false;
|
|
132
|
-
const finish = (ok) => {
|
|
133
|
-
if (settled) return;
|
|
134
|
-
settled = true;
|
|
135
|
-
if (socket !== undefined) socket.destroy();
|
|
136
|
-
if (ok) { report(1); return; }
|
|
137
|
-
if (Date.now() >= deadline) { report(2); return; }
|
|
138
|
-
setTimeout(attempt, retryMs);
|
|
139
|
-
};
|
|
140
|
-
try {
|
|
141
|
-
socket = net.createConnection(sockPath);
|
|
142
|
-
} catch {
|
|
143
|
-
finish(false);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
socket.once('connect', () => finish(true));
|
|
147
|
-
socket.once('error', () => finish(false));
|
|
148
|
-
socket.setTimeout(retryMs, () => finish(false));
|
|
122
|
+
function sleep(ms) {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
setTimeout(resolve, ms);
|
|
125
|
+
});
|
|
149
126
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
127
|
+
function probeBrokerViewSocket(sockPath, timeoutMs) {
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
let settled = false;
|
|
130
|
+
let socket = null;
|
|
131
|
+
const finish = (ok) => {
|
|
132
|
+
if (settled)
|
|
133
|
+
return;
|
|
134
|
+
settled = true;
|
|
135
|
+
if (socket !== null)
|
|
136
|
+
socket.destroy();
|
|
137
|
+
resolve(ok);
|
|
138
|
+
};
|
|
139
|
+
try {
|
|
140
|
+
socket = createConnection(sockPath);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
finish(false);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
socket.once('connect', () => finish(true));
|
|
147
|
+
socket.once('error', () => finish(false));
|
|
148
|
+
socket.setTimeout(timeoutMs, () => finish(false));
|
|
166
149
|
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
150
|
+
}
|
|
151
|
+
/** Wait until a broker's view.sock accepts a connection. The launching caller
|
|
152
|
+
* stays async, so the broker's real exit signal can fail fast the wait instead
|
|
153
|
+
* of being masked by a zombie pid. Success proves more than file existence: it
|
|
154
|
+
* is robust to a stale leftover socket the broker has not unlinked yet,
|
|
155
|
+
* because only an accepting listener yields a `connect`. */
|
|
156
|
+
export async function waitForBrokerViewSocket(nodeId, exited) {
|
|
157
|
+
const sockPath = viewSocketPath(nodeId);
|
|
158
|
+
const deadline = Date.now() + BROKER_FOCUS_SOCKET_WAIT_MS;
|
|
159
|
+
const exitSeen = exited?.then(() => 'exit');
|
|
160
|
+
while (Date.now() <= deadline) {
|
|
161
|
+
const remaining = Math.max(0, deadline - Date.now());
|
|
162
|
+
const probe = probeBrokerViewSocket(sockPath, Math.min(BROKER_FOCUS_SOCKET_RETRY_MS, remaining)).then((ok) => (ok ? 'ready' : 'retry'));
|
|
163
|
+
// Race the probe against the broker's real exit ONLY when we have one. With
|
|
164
|
+
// no exit promise, an `?? Promise.resolve('retry')` would settle instantly
|
|
165
|
+
// and always beat the probe, so readiness could never observe a `connect`.
|
|
166
|
+
const outcome = exitSeen === undefined ? await probe : await Promise.race([probe, exitSeen]);
|
|
167
|
+
if (outcome === 'ready')
|
|
168
|
+
return true;
|
|
169
|
+
if (outcome === 'exit')
|
|
170
|
+
return false;
|
|
171
|
+
if (exitSeen !== undefined) {
|
|
172
|
+
const sleptOrExit = await Promise.race([
|
|
173
|
+
sleep(BROKER_FOCUS_SOCKET_RETRY_MS).then(() => 'sleep'),
|
|
174
|
+
exitSeen,
|
|
175
|
+
]);
|
|
176
|
+
if (sleptOrExit === 'exit')
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await sleep(BROKER_FOCUS_SOCKET_RETRY_MS);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
173
184
|
}
|
|
174
185
|
function newFocusId() {
|
|
175
186
|
return `f-${newNodeId()}`;
|
|
@@ -263,7 +274,17 @@ export function openViewerWindow(nodeId, session, opts = {}) {
|
|
|
263
274
|
*
|
|
264
275
|
* crtr is tmux-only: with no caller pane there is no viewport to open and no
|
|
265
276
|
* non-tmux fallback — report not-focused. */
|
|
266
|
-
|
|
277
|
+
/** After a viewer pane is opened/moved beside the caller, make it the ACTIVE
|
|
278
|
+
* pane and rebalance its window's panes to an even side-by-side layout — so
|
|
279
|
+
* picking a node (e.g. from the alt+g graph) lands the keyboard on the fresh
|
|
280
|
+
* viewer and the panes are evenly sized again, not halved. Best-effort. */
|
|
281
|
+
function surfaceNewPane(pane) {
|
|
282
|
+
const win = paneLocation(pane)?.window ?? null;
|
|
283
|
+
if (win !== null)
|
|
284
|
+
selectLayout(win, 'even-horizontal');
|
|
285
|
+
selectPane(pane);
|
|
286
|
+
}
|
|
287
|
+
export async function focus(nodeId, opts) {
|
|
267
288
|
const meta = getNode(nodeId);
|
|
268
289
|
if (meta === null)
|
|
269
290
|
return { focused: false, session: null, inPlace: false, revived: false };
|
|
@@ -274,9 +295,9 @@ export function focus(nodeId, opts) {
|
|
|
274
295
|
// (a) Ensure the broker engine is alive + its view.sock accepts. Capture prior
|
|
275
296
|
// liveness so the result reports whether THIS focus had to launch it.
|
|
276
297
|
const wasAlive = isPidAlive(meta.pi_pid ?? null);
|
|
277
|
-
opts.revive(nodeId);
|
|
298
|
+
const revivedLaunch = opts.revive(nodeId);
|
|
278
299
|
const revived = !wasAlive;
|
|
279
|
-
if (!waitForBrokerViewSocket(nodeId)) {
|
|
300
|
+
if (!(await waitForBrokerViewSocket(nodeId, revivedLaunch?.launch?.exited))) {
|
|
280
301
|
return { focused: false, session: null, inPlace: false, revived };
|
|
281
302
|
}
|
|
282
303
|
// focus always opens BESIDE the caller pane, so `fallbackSession` is only the
|
|
@@ -296,6 +317,8 @@ export function focus(nodeId, opts) {
|
|
|
296
317
|
const opened = openViewerWindow(nodeId, fallbackSession, { cwd: meta.cwd, besidePane: callerPane });
|
|
297
318
|
if (opened === null)
|
|
298
319
|
return { focused: false, session: null, inPlace: false, revived };
|
|
320
|
+
if (opened.pane !== null)
|
|
321
|
+
surfaceNewPane(opened.pane);
|
|
299
322
|
return { focused: true, session: opened.session, inPlace: false, revived };
|
|
300
323
|
}
|
|
301
324
|
// (c) Reuse / move the node's one live viewer — but ONLY a pane that still
|
|
@@ -315,6 +338,8 @@ export function focus(nodeId, opts) {
|
|
|
315
338
|
// Viewer is in the caller's session — just navigate to it.
|
|
316
339
|
switchClient(loc.session);
|
|
317
340
|
selectWindow(loc.session, loc.window);
|
|
341
|
+
if (existing.pane !== null)
|
|
342
|
+
selectPane(existing.pane);
|
|
318
343
|
return { focused: true, session: loc.session, inPlace: true, revived };
|
|
319
344
|
}
|
|
320
345
|
// Viewer lives elsewhere — MOVE it: close the old pane (broker runs on; the
|
|
@@ -334,6 +359,8 @@ export function focus(nodeId, opts) {
|
|
|
334
359
|
const opened = openViewerWindow(nodeId, fallbackSession, { cwd: meta.cwd, besidePane: callerPane });
|
|
335
360
|
if (opened === null)
|
|
336
361
|
return { focused: false, session: null, inPlace: false, revived };
|
|
362
|
+
if (opened.pane !== null)
|
|
363
|
+
surfaceNewPane(opened.pane);
|
|
337
364
|
return { focused: true, session: opened.session, inPlace: false, revived };
|
|
338
365
|
}
|
|
339
366
|
// ---------------------------------------------------------------------------
|
|
@@ -366,5 +393,63 @@ export function detachToBackground(nodeId, pane) {
|
|
|
366
393
|
const f = focusByPane(viewer);
|
|
367
394
|
if (f !== null)
|
|
368
395
|
closeFocusRow(f.focus_id);
|
|
369
|
-
|
|
396
|
+
const closed = closePane(viewer);
|
|
397
|
+
// A node the user detached from that never produced any AI output is a useless
|
|
398
|
+
// shell taking up graph + disk space — reap it (engine + row + dir) outright
|
|
399
|
+
// rather than leaving an empty broker running headless off-screen.
|
|
400
|
+
reapIfEmpty(nodeId);
|
|
401
|
+
return closed;
|
|
402
|
+
}
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
// Reaping empty nodes (no AI output) on close / detach
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
/** Is this node an EMPTY SHELL right now — a node whose engine never produced an
|
|
407
|
+
* assistant message? Such nodes are pure dead weight on the graph. A node
|
|
408
|
+
* actively generating its FIRST turn is NOT empty (its output just hasn't landed
|
|
409
|
+
* on disk yet), so streaming nodes are spared. */
|
|
410
|
+
function isEmptyNode(nodeId) {
|
|
411
|
+
const m = getNode(nodeId);
|
|
412
|
+
if (m === null)
|
|
413
|
+
return false;
|
|
414
|
+
if (isNodeStreaming(nodeId))
|
|
415
|
+
return false;
|
|
416
|
+
return !nodeHasAssistantMessage(m.pi_session_file);
|
|
417
|
+
}
|
|
418
|
+
/** Reap `nodeId` IFF it is an empty shell (see {@link isEmptyNode}): tear down its
|
|
419
|
+
* broker engine, close any viewer, and hard-delete its row (edges cascade) + dir.
|
|
420
|
+
* The user closing or detaching from a node that never said anything wants it
|
|
421
|
+
* GONE, not parked as a canceled husk. Returns true when it reaped. No-op (false)
|
|
422
|
+
* on a node that produced output, is mid-first-turn, or no longer exists. */
|
|
423
|
+
export function reapIfEmpty(nodeId) {
|
|
424
|
+
if (!isEmptyNode(nodeId))
|
|
425
|
+
return false;
|
|
426
|
+
headlessBrokerHost.teardown(nodeId);
|
|
427
|
+
tearDownNode(nodeId);
|
|
428
|
+
deleteNode(nodeId);
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
/** Sweep the whole canvas for empty shells and reap them — the bulk cleanup
|
|
432
|
+
* behind `crtr canvas prune --empty`, for the husks that accumulated before
|
|
433
|
+
* close/detach learned to reap. Skips: the caller ($CRTR_NODE_ID), nodes
|
|
434
|
+
* mid-first-turn, and any node with a LIVE broker (pi_pid alive) — a running
|
|
435
|
+
* engine is the daemon's domain and may have output not yet persisted, so bulk
|
|
436
|
+
* GC never kills one (the per-action {@link reapIfEmpty} does, since the user
|
|
437
|
+
* explicitly closed/detached that node). `dryRun` reports candidates without
|
|
438
|
+
* deleting. Returns the reaped (or, under dryRun, reapable) node ids. */
|
|
439
|
+
export function reapEmptyNodes(opts = {}) {
|
|
440
|
+
const dryRun = opts.dryRun ?? false;
|
|
441
|
+
const selfId = process.env['CRTR_NODE_ID'] ?? '';
|
|
442
|
+
const reaped = [];
|
|
443
|
+
for (const row of listNodes()) {
|
|
444
|
+
const id = row.node_id;
|
|
445
|
+
if (id === selfId || isPidAlive(row.pi_pid) || !isEmptyNode(id))
|
|
446
|
+
continue;
|
|
447
|
+
if (!dryRun) {
|
|
448
|
+
headlessBrokerHost.teardown(id);
|
|
449
|
+
tearDownNode(id);
|
|
450
|
+
deleteNode(id);
|
|
451
|
+
}
|
|
452
|
+
reaped.push(id);
|
|
453
|
+
}
|
|
454
|
+
return reaped;
|
|
370
455
|
}
|