@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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// source's parse() job.
|
|
9
9
|
import { execFile } from 'node:child_process';
|
|
10
10
|
import { readFile } from 'node:fs/promises';
|
|
11
|
+
import { openLocalStream } from './stream-local.js';
|
|
11
12
|
const MAX_BUFFER = 10 * 1024 * 1024;
|
|
12
13
|
export function createLocalTransport(opts = {}) {
|
|
13
14
|
const baseCwd = opts.cwd ?? process.cwd();
|
|
@@ -19,6 +20,9 @@ export function createLocalTransport(opts = {}) {
|
|
|
19
20
|
case 'http': return sendHttp(req);
|
|
20
21
|
}
|
|
21
22
|
},
|
|
23
|
+
open(req, handlers) {
|
|
24
|
+
return openLocalStream(req, handlers);
|
|
25
|
+
},
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
function sendExec(req, baseCwd) {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type { SourceRequest, RawResponse } from './contract.js';
|
|
1
|
+
import type { SourceRequest, RawResponse, StreamRequest, StreamChannel, SourceError } from './contract.js';
|
|
2
|
+
export interface StreamHandlers {
|
|
3
|
+
onFrame(frame: unknown): void;
|
|
4
|
+
onClose(reason: string): void;
|
|
5
|
+
onError(err: SourceError): void;
|
|
6
|
+
}
|
|
2
7
|
export interface Transport {
|
|
3
8
|
send(req: SourceRequest): Promise<RawResponse>;
|
|
9
|
+
open(req: StreamRequest, handlers: StreamHandlers): StreamChannel;
|
|
4
10
|
}
|
package/dist/daemon/crtrd.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type NodeRow } from '../core/canvas/index.js';
|
|
2
|
+
import { FAULT_QUIET_MS } from '../core/runtime/fault.js';
|
|
3
|
+
export { FAULT_QUIET_MS };
|
|
2
4
|
import { isPidAlive } from '../core/canvas/pid.js';
|
|
3
5
|
export declare const YIELD_STALL_GRACE_MS: number;
|
|
4
6
|
/** Refresh-stall decision (§H refresh-authority; audit 2026-06-09, Bug 1 — node
|
|
@@ -17,25 +19,6 @@ export declare const YIELD_STALL_GRACE_MS: number;
|
|
|
17
19
|
* after running `node yield` mid-turn, and a working engine must never be
|
|
18
20
|
* killed), and the state has persisted past YIELD_STALL_GRACE_MS. */
|
|
19
21
|
export declare function yieldStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, stalledFor: number | null): 'leave' | 'pending' | 'kill';
|
|
20
|
-
export declare const ERROR_STALL_QUIET_MS: number;
|
|
21
|
-
/** The §I wedge signature, from a session-jsonl tail: the LAST assistant
|
|
22
|
-
* message stopped with 'error' (retry budget exhausted — pi appends the error
|
|
23
|
-
* turn and parks). Returns its errorMessage for the daemon log (the "why it
|
|
24
|
-
* wedged" trace), or null when the last assistant turn ended any other way —
|
|
25
|
-
* 'stop'/'length' is a healthy park, 'aborted' is a human Esc, and trailing
|
|
26
|
-
* non-assistant lines (toolResults, an injected user message) are walked past.
|
|
27
|
-
* The first line of a mid-file tail cut fails JSON.parse and is skipped. */
|
|
28
|
-
export declare function trailingEngineError(tail: string | null): string | null;
|
|
29
|
-
/** Error-stall decision (§I; gh issue #4 — node mqaeqzdu-0746b3b7, a 9h zombie
|
|
30
|
-
* after an Anthropic outage). Pure, mirroring yieldStallVerdict; the kill side
|
|
31
|
-
* effects live in handleErrorStall.
|
|
32
|
-
*
|
|
33
|
-
* 'kill' ONLY when ALL of: engine alive, NO pending intent (refresh is §H's
|
|
34
|
-
* domain; idle-release chose dormancy), the turn is over (busy absent — a node
|
|
35
|
-
* wedged mid-bash must be recovered by killing the subprocess, never the node),
|
|
36
|
-
* the session file has been quiet past ERROR_STALL_QUIET_MS, and its last
|
|
37
|
-
* assistant message stopped with 'error'. */
|
|
38
|
-
export declare function errorStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, quietForMs: number | null, trailingError: boolean): 'leave' | 'kill';
|
|
39
22
|
export type LivenessVerdict = 'leave' | 'pending' | 'revive';
|
|
40
23
|
/** Decide what to do with a node whose engine pid is DEAD, from how long it's
|
|
41
24
|
* been dead. Pure — the time/revive side effects live in handleNodeLiveness;
|
|
@@ -50,6 +33,7 @@ export declare function readPidfile(): number | null;
|
|
|
50
33
|
export { isPidAlive };
|
|
51
34
|
/** True when a crtrd process is already running (pidfile exists + pid alive). */
|
|
52
35
|
export declare function isDaemonRunning(): boolean;
|
|
36
|
+
/** Supervision tick over the daemon's current live set. */
|
|
53
37
|
export declare function superviseTick(now?: number): Promise<void>;
|
|
54
38
|
export interface DaemonOpts {
|
|
55
39
|
/** Milliseconds between supervision polls. Default 2000. */
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// reaps a node on pane/window state. All node liveness routes through
|
|
11
11
|
// handleNodeLiveness:
|
|
12
12
|
// • Poll every intervalMs (default 2000ms).
|
|
13
|
-
// • pid alive → leave; but enforce a stuck refresh-yield
|
|
14
|
-
//
|
|
13
|
+
// • pid alive → leave; but enforce a stuck refresh-yield and active-fault
|
|
14
|
+
// recovery when a parked broker is due.
|
|
15
15
|
// • pid null + pi_session_id set → relaunch in flight; leave.
|
|
16
16
|
// • pid null + pi_session_id null → after a boot grace, crash + surface boot
|
|
17
17
|
// failure (the broker exited before session_start).
|
|
@@ -28,30 +28,39 @@
|
|
|
28
28
|
// grace, it force-kills the engine itself (handleYieldStall) so the dead-pid
|
|
29
29
|
// refresh path then revives it fresh.
|
|
30
30
|
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
31
|
+
// An engine whose turn ends with stopReason 'error' (e.g. a provider outage
|
|
32
|
+
// exhausted the SDK retry budget) is left parked by the stophook ("stay alive
|
|
33
|
+
// for re-steering") — but a headless node has no human to re-steer it, so the
|
|
34
|
+
// broker stays alive, status stays 'active', and the pid-only liveness check
|
|
35
|
+
// reads it healthy forever. The daemon is the authority here too: when a live,
|
|
36
|
+
// not-busy node with no pending intent has a session file that is quiet past
|
|
37
|
+
// the recovery window and whose last assistant message stops with 'error', the
|
|
38
|
+
// daemon keeps the live broker parked and retries it via the inbox-based
|
|
39
|
+
// fault-recovery pass so the ordinary dead-pid crash path never needs an
|
|
40
|
+
// out-of-band launch.
|
|
41
|
+
// dead-pid crash path grace-revives RESUME on the saved session — the manual
|
|
42
|
+
// recovery (kill broker + canvas revive) that fixes these by hand.
|
|
41
43
|
//
|
|
42
44
|
// Single-instance guarantee
|
|
43
|
-
// A PID file
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
import {
|
|
45
|
+
// A PID file prevents double-runs. On start, if the file exists and the
|
|
46
|
+
// recorded pid is alive, we refuse to start (exit 0). Hearth guests set
|
|
47
|
+
// CRTR_PIDFILE to keep this process-local on recreate; otherwise it defaults
|
|
48
|
+
// to crtrHome()/crtrd.pid. On stop (SIGINT/SIGTERM/exit) we remove the file.
|
|
49
|
+
import { writeFileSync, readFileSync, rmSync, existsSync, mkdirSync, statSync, } from 'node:fs';
|
|
50
|
+
import { dirname, join } from 'node:path';
|
|
51
|
+
import { homedir } from 'node:os';
|
|
48
52
|
import { crtrHome, nodeDir, jobDir } from '../core/canvas/paths.js';
|
|
49
53
|
import { listNodes, getRow, getNode, dueWakes, consumeWake, advanceWake, listFocuses, closeFocusRow, } from '../core/canvas/index.js';
|
|
50
54
|
import { transition } from '../core/runtime/lifecycle.js';
|
|
51
55
|
import { isBusy } from '../core/runtime/busy.js';
|
|
56
|
+
import { FAULT_QUIET_MS } from '../core/runtime/fault.js';
|
|
57
|
+
import { logger } from '../core/log.js';
|
|
58
|
+
export { FAULT_QUIET_MS };
|
|
52
59
|
import { isPidAlive } from '../core/canvas/pid.js';
|
|
53
60
|
import { listLivePanes, tearDownNode } from '../core/runtime/placement.js';
|
|
54
61
|
import { reviveNode } from '../core/runtime/revive.js';
|
|
62
|
+
import { isBrokerLive } from '../core/runtime/model-swap.js';
|
|
63
|
+
import { reloadAuthLive } from '../core/runtime/auth-reload.js';
|
|
55
64
|
import { spawnChild } from '../core/runtime/spawn.js';
|
|
56
65
|
import { wakeOriginFrom } from '../core/runtime/bearings.js';
|
|
57
66
|
import { pushUrgent } from '../core/feed/feed.js';
|
|
@@ -78,6 +87,7 @@ async function surfaceBootFailure(meta) {
|
|
|
78
87
|
await pushUrgent(meta.node_id, body, { from: meta.node_id });
|
|
79
88
|
}
|
|
80
89
|
const DEFAULT_INTERVAL_MS = 2000;
|
|
90
|
+
const log = logger('crtrd');
|
|
81
91
|
// How long a node's broker pid may be observed dead before the daemon revives
|
|
82
92
|
// it. MUST exceed worst-case broker boot time: a refresh / crash-revive
|
|
83
93
|
// transiently shows a dead OLD pid for the gap between the old broker exiting
|
|
@@ -184,151 +194,77 @@ function handleYieldStall(row, pid, now) {
|
|
|
184
194
|
}
|
|
185
195
|
}
|
|
186
196
|
}
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const TAIL_BYTES = 64 * 1024;
|
|
200
|
-
/** Read the last TAIL_BYTES of a file. null on any failure (absent file reads
|
|
201
|
-
* as "nothing to inspect", never as evidence of a wedge). */
|
|
202
|
-
function readTail(path) {
|
|
203
|
-
try {
|
|
204
|
-
const size = statSync(path).size;
|
|
205
|
-
const len = Math.min(TAIL_BYTES, size);
|
|
206
|
-
if (len === 0)
|
|
207
|
-
return null;
|
|
208
|
-
const fd = openSync(path, 'r');
|
|
209
|
-
try {
|
|
210
|
-
const buf = Buffer.alloc(len);
|
|
211
|
-
readSync(fd, buf, 0, len, size - len);
|
|
212
|
-
return buf.toString('utf8');
|
|
213
|
-
}
|
|
214
|
-
finally {
|
|
215
|
-
closeSync(fd);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
/** The §I wedge signature, from a session-jsonl tail: the LAST assistant
|
|
223
|
-
* message stopped with 'error' (retry budget exhausted — pi appends the error
|
|
224
|
-
* turn and parks). Returns its errorMessage for the daemon log (the "why it
|
|
225
|
-
* wedged" trace), or null when the last assistant turn ended any other way —
|
|
226
|
-
* 'stop'/'length' is a healthy park, 'aborted' is a human Esc, and trailing
|
|
227
|
-
* non-assistant lines (toolResults, an injected user message) are walked past.
|
|
228
|
-
* The first line of a mid-file tail cut fails JSON.parse and is skipped. */
|
|
229
|
-
export function trailingEngineError(tail) {
|
|
230
|
-
if (tail === null)
|
|
231
|
-
return null;
|
|
232
|
-
const lines = tail.split('\n');
|
|
233
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
234
|
-
const line = lines[i].trim();
|
|
235
|
-
if (line === '')
|
|
236
|
-
continue;
|
|
237
|
-
let msg;
|
|
238
|
-
try {
|
|
239
|
-
msg = JSON.parse(line)?.message;
|
|
240
|
-
}
|
|
241
|
-
catch {
|
|
242
|
-
continue; // a cut fragment (tail boundary) or non-JSON line
|
|
243
|
-
}
|
|
244
|
-
if (msg?.role !== 'assistant')
|
|
245
|
-
continue;
|
|
246
|
-
if (msg.stopReason !== 'error')
|
|
247
|
-
return null;
|
|
248
|
-
return typeof msg.errorMessage === 'string' && msg.errorMessage !== ''
|
|
249
|
-
? msg.errorMessage
|
|
250
|
-
: 'engine error (no errorMessage recorded)';
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Auth-reload fan (one /login → every live broker follows)
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
/** Path of the shared pi credentials file every broker reads. VENDORED from pi
|
|
201
|
+
* `config.getAuthPath()` (= `<agentDir>/auth.json`) the same way spawn.ts
|
|
202
|
+
* vendors the sessions root: a ROOT import of pi's `getAgentDir` would eager-
|
|
203
|
+
* load the heavy pi SDK index, so we mirror its resolution — `PI_CODING_AGENT_DIR`
|
|
204
|
+
* env, else `~/.pi/agent`. Re-sync on a pi SDK bump that moves auth.json. */
|
|
205
|
+
function piAuthPath() {
|
|
206
|
+
const env = process.env['PI_CODING_AGENT_DIR'];
|
|
207
|
+
const agentDir = env !== undefined && env !== '' ? env : join(homedir(), '.pi', 'agent');
|
|
208
|
+
return join(agentDir, 'auth.json');
|
|
253
209
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
210
|
+
// Last-observed auth.json mtime, for the daemon's change-watch. Lazily seeded on
|
|
211
|
+
// the first poll that sees the file (so daemon start never fans a spurious
|
|
212
|
+
// reload), then updated on every detected change. In-memory only — a daemon
|
|
213
|
+
// restart re-seeds from the current file, so at most one missed change spans the
|
|
214
|
+
// restart gap (re-login during a ~1s restart), which a human just redoes.
|
|
215
|
+
let knownAuthMtimeMs = null;
|
|
216
|
+
/** Watch the shared auth.json mtime and, on a change, fan a live `reload_auth`
|
|
217
|
+
* to every live broker so one `/login` propagates across the whole canvas. The
|
|
218
|
+
* broker that performed the login already self-reloaded; re-reloading it is
|
|
219
|
+
* harmless (authStorage.reload() is idempotent), so we don't exclude it.
|
|
257
220
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
|
|
263
|
-
export function errorStallVerdict(piPidAlive, intent, busy, quietForMs, trailingError) {
|
|
264
|
-
if (piPidAlive !== true)
|
|
265
|
-
return 'leave';
|
|
266
|
-
if (intent !== null)
|
|
267
|
-
return 'leave';
|
|
268
|
-
if (busy)
|
|
269
|
-
return 'leave';
|
|
270
|
-
if (quietForMs === null || quietForMs < ERROR_STALL_QUIET_MS)
|
|
271
|
-
return 'leave';
|
|
272
|
-
if (!trailingError)
|
|
273
|
-
return 'leave';
|
|
274
|
-
return 'kill';
|
|
275
|
-
}
|
|
276
|
-
/** Enact an error-stall (§I): when the verdict is 'kill', SIGTERM the parked
|
|
277
|
-
* engine (escalating to SIGKILL after KILL_ESCALATE_MS, like §H — a broker
|
|
278
|
-
* wedged after an outage may not run graceful handlers). intent is null, so
|
|
279
|
-
* once the pid dies the ordinary crash branch in handleNodeLiveness grace-
|
|
280
|
-
* revives RESUME on the saved session — the conversation is preserved and the
|
|
281
|
-
* revive kickoff prompts the node to continue. No grace clock of our own: the
|
|
282
|
-
* session file's mtime IS the clock (any append — a re-steer, an inbox-watcher
|
|
283
|
-
* wake, a new turn — resets it). */
|
|
284
|
-
function handleErrorStall(row, pid, now) {
|
|
285
|
-
const id = row.node_id;
|
|
286
|
-
if (row.intent !== null || isBusy(id)) {
|
|
287
|
-
errorTermAt.delete(id);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const file = getNode(id)?.pi_session_file;
|
|
291
|
-
if (file == null)
|
|
292
|
-
return;
|
|
221
|
+
* Rides the existing ~2s poll, so several rapid writes from one login collapse
|
|
222
|
+
* to a single observed mtime change → a single fan. Each broker is reloaded
|
|
223
|
+
* under its own try/catch (Promise.allSettled): a broker mid-shutdown or whose
|
|
224
|
+
* socket refuses connect is logged and skipped, never aborting the others. */
|
|
225
|
+
async function handleAuthReload(now) {
|
|
293
226
|
let mtimeMs;
|
|
294
227
|
try {
|
|
295
|
-
mtimeMs = statSync(
|
|
228
|
+
mtimeMs = statSync(piAuthPath()).mtimeMs;
|
|
296
229
|
}
|
|
297
230
|
catch {
|
|
298
|
-
return; //
|
|
231
|
+
return; // no auth.json yet (never logged in) — nothing to watch
|
|
299
232
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
errorTermAt.delete(id);
|
|
233
|
+
if (knownAuthMtimeMs === null) {
|
|
234
|
+
knownAuthMtimeMs = mtimeMs; // seed on first sight; never fan on daemon start
|
|
303
235
|
return;
|
|
304
236
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
237
|
+
if (mtimeMs === knownAuthMtimeMs)
|
|
238
|
+
return;
|
|
239
|
+
knownAuthMtimeMs = mtimeMs;
|
|
240
|
+
// Enumerate live brokers (pid alive + view.sock present) only when auth
|
|
241
|
+
// actually changed — the rare path, so the extra list/stat cost is paid only
|
|
242
|
+
// on a real login, never every tick.
|
|
243
|
+
let live;
|
|
244
|
+
try {
|
|
245
|
+
live = listNodes({ status: ['active', 'idle'] })
|
|
246
|
+
.filter((r) => r.kind !== 'human')
|
|
247
|
+
.map((r) => getNode(r.node_id))
|
|
248
|
+
.filter((m) => m !== null && isBrokerLive(m))
|
|
249
|
+
.map((m) => m.node_id);
|
|
309
250
|
}
|
|
310
|
-
|
|
251
|
+
catch (err) {
|
|
252
|
+
process.stderr.write(`[crtrd] auth-reload enumerate error: ${err.message}\n`);
|
|
311
253
|
return;
|
|
312
|
-
const termed = errorTermAt.get(id);
|
|
313
|
-
if (termed === undefined) {
|
|
314
|
-
process.stderr.write(`[crtrd] kill ${id} (error-stall: engine parked on a trailing error turn, session quiet ${Math.round(quietFor / 1000)}s — SIGTERM; last error: ${String(memo.error).slice(0, 200)})\n`);
|
|
315
|
-
try {
|
|
316
|
-
process.kill(pid, 'SIGTERM');
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
/* already gone */
|
|
320
|
-
}
|
|
321
|
-
errorTermAt.set(id, now);
|
|
322
254
|
}
|
|
323
|
-
|
|
324
|
-
|
|
255
|
+
if (live.length === 0)
|
|
256
|
+
return;
|
|
257
|
+
const results = await Promise.allSettled(live.map(async (id) => {
|
|
325
258
|
try {
|
|
326
|
-
|
|
259
|
+
await reloadAuthLive(id);
|
|
327
260
|
}
|
|
328
|
-
catch {
|
|
329
|
-
|
|
261
|
+
catch (err) {
|
|
262
|
+
process.stderr.write(`[crtrd] auth-reload ${id} failed: ${err.message}\n`);
|
|
263
|
+
throw err;
|
|
330
264
|
}
|
|
331
|
-
}
|
|
265
|
+
}));
|
|
266
|
+
const ok = results.filter((r) => r.status === 'fulfilled').length;
|
|
267
|
+
process.stderr.write(`[crtrd] auth-reload: auth.json changed — fanned to ${live.length} live broker(s) (${ok} ok, ${live.length - ok} failed)\n`);
|
|
332
268
|
}
|
|
333
269
|
/** Decide what to do with a node whose engine pid is DEAD, from how long it's
|
|
334
270
|
* been dead. Pure — the time/revive side effects live in handleNodeLiveness;
|
|
@@ -354,12 +290,14 @@ export function livenessVerdict(piPidAlive, deadFor) {
|
|
|
354
290
|
* with the turn over) and, once the engine has sat alive past the grace,
|
|
355
291
|
* kills it — so the daemon, not the (possibly stale) in-process stophook, is
|
|
356
292
|
* the authority on refresh, and the dead-pid refresh branch below then
|
|
357
|
-
* revives it fresh.
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* crash branch below grace-revives it RESUME.
|
|
293
|
+
* revives it fresh. Fault recovery now runs in a separate pass: live,
|
|
294
|
+
* daemon-owned provider faults are retried by inbox nudge, while dead-pid
|
|
295
|
+
* crashes still fall through the ordinary resume path below.
|
|
361
296
|
* • pid null + pi_session_id set (a relaunch in flight — reviveNode clears
|
|
362
|
-
* pi_pid right after launch) → leave
|
|
297
|
+
* pi_pid right after launch) → leave through a boot grace; a healthy engine
|
|
298
|
+
* re-records its pid well within it. If the grace ELAPSES with still no pid,
|
|
299
|
+
* the relaunch died after the pid-clear → grace-revive RESUME on the saved
|
|
300
|
+
* session (else the node strands 'active' with no engine forever).
|
|
363
301
|
* • pid null + pi_session_id null (NEVER booted) → normally the sub-second SDK
|
|
364
302
|
* boot gap, but a broker that throws BEFORE session_start records no pid and
|
|
365
303
|
* no session ever — so after a boot grace with STILL nothing, crash +
|
|
@@ -380,37 +318,50 @@ async function handleNodeLiveness(row, now, revivedThisTick) {
|
|
|
380
318
|
if (pid != null && isPidAlive(pid)) {
|
|
381
319
|
unhealthySince.delete(id);
|
|
382
320
|
handleYieldStall(row, pid, now);
|
|
383
|
-
handleErrorStall(row, pid, now);
|
|
384
321
|
return;
|
|
385
322
|
}
|
|
386
323
|
if (pid == null) {
|
|
387
|
-
// No supervised pid recorded.
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
// engine re-records
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
324
|
+
// No supervised pid recorded. BOTH sub-cases run the SAME boot grace clock and
|
|
325
|
+
// differ only in how it ENDS, keyed on pi_session_id:
|
|
326
|
+
// • session SET — either a relaunch in flight (reviveNode clears pi_pid right
|
|
327
|
+
// after launch; the fresh engine re-records within a tick or two, well
|
|
328
|
+
// inside the grace, and the pid-alive branch above clears the clock) OR a
|
|
329
|
+
// relaunch whose broker DIED after the pid-clear and before it re-recorded.
|
|
330
|
+
// The healthy case never reaches the grace; if the grace DOES elapse with
|
|
331
|
+
// still no pid, the relaunch is dead → grace-revive RESUME on the saved
|
|
332
|
+
// session. Returning unconditionally here (the old behavior) read null+
|
|
333
|
+
// session as "in flight" FOREVER and stranded such a node 'active' with no
|
|
334
|
+
// engine — its parent waiting on a dead child indefinitely.
|
|
335
|
+
// • session NULL — normally the sub-second SDK boot gap, BUT a broker that
|
|
336
|
+
// THROWS before session_start (malformed broker-launch.json, SessionManager
|
|
337
|
+
// .open on a missing .jsonl, a loader/registry/createAgentSession failure,
|
|
338
|
+
// the fork / bare-id guards, or broker-cli's own fatal catch) records NO
|
|
339
|
+
// pid and NO session — EVER. After the grace with STILL nothing, crash +
|
|
399
340
|
// surfaceBootFailure up the spine (M-1).
|
|
341
|
+
// The 20s grace doubles as the double-spawn guard: a healthy relaunch always
|
|
342
|
+
// re-records its pid well within it, so neither branch can fire on one.
|
|
400
343
|
const meta = getNode(id);
|
|
401
|
-
if (meta === null
|
|
402
|
-
unhealthySince.delete(id); //
|
|
344
|
+
if (meta === null) {
|
|
345
|
+
unhealthySince.delete(id); // row gone from meta — nothing to supervise
|
|
403
346
|
return;
|
|
404
347
|
}
|
|
405
348
|
const since = unhealthySince.get(id);
|
|
406
349
|
if (since === undefined) {
|
|
407
|
-
unhealthySince.set(id, now); // start the boot
|
|
350
|
+
unhealthySince.set(id, now); // start the boot grace clock
|
|
408
351
|
return;
|
|
409
352
|
}
|
|
410
353
|
if (now - since < REVIVE_GRACE_MS)
|
|
411
354
|
return; // still inside the boot grace
|
|
412
|
-
// Boot grace elapsed, still no pid and no session → the broker never booted.
|
|
413
355
|
unhealthySince.delete(id);
|
|
356
|
+
if (meta.pi_session_id != null) {
|
|
357
|
+
// Grace elapsed with a session but still no pid → a relaunch that died after
|
|
358
|
+
// the pid-clear, before re-record. Resume it on the saved session.
|
|
359
|
+
process.stderr.write(`[crtrd] revive ${id} (stranded relaunch — pid never re-recorded)\n`);
|
|
360
|
+
reviveNode(id, { resume: true });
|
|
361
|
+
revivedThisTick.add(id); // third-pass bare double-spawn guard (Maj-4)
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// Grace elapsed with no pid AND no session → the broker never booted.
|
|
414
365
|
process.stderr.write(`[crtrd] boot-failed ${id} (broker exited before session_start)\n`);
|
|
415
366
|
transition(id, 'crash');
|
|
416
367
|
try {
|
|
@@ -421,12 +372,10 @@ async function handleNodeLiveness(row, now, revivedThisTick) {
|
|
|
421
372
|
}
|
|
422
373
|
return;
|
|
423
374
|
}
|
|
424
|
-
// The engine pid is DEAD —
|
|
425
|
-
//
|
|
375
|
+
// The engine pid is DEAD — the dead-pid paths below own recovery now. Branch
|
|
376
|
+
// on intent.
|
|
426
377
|
yieldStallSince.delete(id);
|
|
427
378
|
yieldTermAt.delete(id);
|
|
428
|
-
errorTermAt.delete(id);
|
|
429
|
-
errorTailMemo.delete(id);
|
|
430
379
|
if (row.intent === 'refresh') {
|
|
431
380
|
// Clean yield — or the §H force-kill above just landed — → respawn FRESH,
|
|
432
381
|
// immediately (no grace wait).
|
|
@@ -578,11 +527,12 @@ function gcStaleFocuses() {
|
|
|
578
527
|
// Pidfile
|
|
579
528
|
// ---------------------------------------------------------------------------
|
|
580
529
|
function pidfilePath() {
|
|
581
|
-
|
|
530
|
+
const override = process.env['CRTR_PIDFILE'];
|
|
531
|
+
return override !== undefined && override !== '' ? override : join(crtrHome(), 'crtrd.pid');
|
|
582
532
|
}
|
|
583
533
|
function writePidfile() {
|
|
584
|
-
// Ensure the
|
|
585
|
-
mkdirSync(
|
|
534
|
+
// Ensure the pidfile parent exists before writing.
|
|
535
|
+
mkdirSync(dirname(pidfilePath()), { recursive: true });
|
|
586
536
|
writeFileSync(pidfilePath(), String(process.pid), 'utf8');
|
|
587
537
|
}
|
|
588
538
|
function removePidfile() {
|
|
@@ -615,7 +565,18 @@ export function isDaemonRunning() {
|
|
|
615
565
|
// ---------------------------------------------------------------------------
|
|
616
566
|
// Supervisor tick
|
|
617
567
|
// ---------------------------------------------------------------------------
|
|
568
|
+
/** Supervision tick over the daemon's current live set. */
|
|
618
569
|
export async function superviseTick(now = Date.now()) {
|
|
570
|
+
// Auth-reload fan FIRST, independent of node supervision: a change to the
|
|
571
|
+
// shared auth.json propagates a live credential reload to every broker. Kept
|
|
572
|
+
// ahead of the listNodes guard below so a transient list error in the
|
|
573
|
+
// supervision passes never skips an auth fan (it does its own enumeration).
|
|
574
|
+
try {
|
|
575
|
+
await handleAuthReload(now);
|
|
576
|
+
}
|
|
577
|
+
catch (err) {
|
|
578
|
+
process.stderr.write(`[crtrd] handleAuthReload error: ${err.message}\n`);
|
|
579
|
+
}
|
|
619
580
|
let rows;
|
|
620
581
|
try {
|
|
621
582
|
rows = listNodes({ status: ['active', 'idle'] });
|
|
@@ -801,7 +762,7 @@ export async function superviseTick(now = Date.now()) {
|
|
|
801
762
|
// stored recipe) so the born node's kickoff leads with a <crtr-wake>
|
|
802
763
|
// block — it learns, by construction, that a timer birthed it (and, for
|
|
803
764
|
// a spawn-cron, that it is one run of a standing job).
|
|
804
|
-
spawnChild({ ...recipe, wakeOrigin: wakeOriginFrom(w) });
|
|
765
|
+
await spawnChild({ ...recipe, wakeOrigin: wakeOriginFrom(w) });
|
|
805
766
|
notifiedWakeFailures.delete(w.wakeup_id); // success clears the dedup latch
|
|
806
767
|
}
|
|
807
768
|
catch (err) {
|
|
@@ -842,7 +803,7 @@ export function runDaemon(opts = {}) {
|
|
|
842
803
|
}
|
|
843
804
|
const interval = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
844
805
|
writePidfile();
|
|
845
|
-
|
|
806
|
+
log.info(`started (pid ${process.pid}, interval ${interval}ms)`);
|
|
846
807
|
let running = true;
|
|
847
808
|
// Cleanup — idempotent.
|
|
848
809
|
const cleanup = () => {
|
package/dist/daemon/manage.d.ts
CHANGED
|
@@ -6,11 +6,28 @@ export interface SpawnDaemonResult {
|
|
|
6
6
|
/** PID of the already-running daemon, if it was already up. */
|
|
7
7
|
existing_pid?: number;
|
|
8
8
|
}
|
|
9
|
+
export interface DaemonStartupDeps {
|
|
10
|
+
readPidfile?: () => number | null;
|
|
11
|
+
isPidAlive?: (pid: number) => boolean;
|
|
12
|
+
childExited?: () => {
|
|
13
|
+
code: number | null;
|
|
14
|
+
signal: NodeJS.Signals | null;
|
|
15
|
+
} | null;
|
|
16
|
+
sleepMs?: (ms: number) => Promise<void> | void;
|
|
17
|
+
now?: () => number;
|
|
18
|
+
}
|
|
19
|
+
/** Tiny bounded post-spawn guard: wait for the daemon pidfile and live pid to
|
|
20
|
+
* appear before reporting success. This catches the "started:true but not yet
|
|
21
|
+
* plausibly alive" race without turning startup into a retry loop.
|
|
22
|
+
*
|
|
23
|
+
* Returns null when the spawned pid owns the pidfile; returns a different live
|
|
24
|
+
* daemon pid when startup lost the race to an already-running daemon. */
|
|
25
|
+
export declare function verifyDaemonStartup(pid: number, timeoutMs?: number, deps?: DaemonStartupDeps): Promise<number | null>;
|
|
9
26
|
/** Spawn crtrd detached. Returns immediately; the child outlives this process.
|
|
10
27
|
*
|
|
11
28
|
* If the daemon is already running, returns {started:false, existing_pid}.
|
|
12
29
|
* If spawning fails (e.g. missing dist — run `npm run build` first), throws. */
|
|
13
|
-
export declare function spawnDaemon(): SpawnDaemonResult
|
|
30
|
+
export declare function spawnDaemon(): Promise<SpawnDaemonResult>;
|
|
14
31
|
/** Start the daemon if it is not already running. No-op if already up.
|
|
15
32
|
* Silently swallows spawn errors (the canvas still works without the daemon;
|
|
16
33
|
* nodes just won't be auto-revived). */
|
package/dist/daemon/manage.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { mkdirSync } from 'node:fs';
|
|
10
10
|
import { crtrHome } from '../core/canvas/paths.js';
|
|
11
11
|
import { hostExecPath } from '../core/runtime/branded-host.js';
|
|
12
|
-
import { isDaemonRunning, readPidfile } from './crtrd.js';
|
|
12
|
+
import { isDaemonRunning, readPidfile, isPidAlive } from './crtrd.js';
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Entry point resolution
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -21,11 +21,45 @@ function resolveCrtrdEntry() {
|
|
|
21
21
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
return join(here, 'crtrd-cli.js');
|
|
23
23
|
}
|
|
24
|
+
// Cold guests can take a few seconds to settle their pidfile + liveness probe.
|
|
25
|
+
const STARTUP_VERIFY_WINDOW_MS = 5_000;
|
|
26
|
+
const STARTUP_VERIFY_POLL_MS = 10;
|
|
27
|
+
function sleepMs(ms) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
setTimeout(resolve, ms);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Tiny bounded post-spawn guard: wait for the daemon pidfile and live pid to
|
|
33
|
+
* appear before reporting success. This catches the "started:true but not yet
|
|
34
|
+
* plausibly alive" race without turning startup into a retry loop.
|
|
35
|
+
*
|
|
36
|
+
* Returns null when the spawned pid owns the pidfile; returns a different live
|
|
37
|
+
* daemon pid when startup lost the race to an already-running daemon. */
|
|
38
|
+
export async function verifyDaemonStartup(pid, timeoutMs = STARTUP_VERIFY_WINDOW_MS, deps = {}) {
|
|
39
|
+
const read = deps.readPidfile ?? readPidfile;
|
|
40
|
+
const alive = deps.isPidAlive ?? isPidAlive;
|
|
41
|
+
const exited = deps.childExited;
|
|
42
|
+
const sleep = deps.sleepMs ?? sleepMs;
|
|
43
|
+
const now = deps.now ?? Date.now;
|
|
44
|
+
const deadline = now() + timeoutMs;
|
|
45
|
+
while (now() <= deadline) {
|
|
46
|
+
const owner = read();
|
|
47
|
+
if (owner !== null && alive(owner))
|
|
48
|
+
return owner === pid ? null : owner;
|
|
49
|
+
const exitState = exited?.();
|
|
50
|
+
if (exitState !== null && exitState !== undefined) {
|
|
51
|
+
const exitDetail = exitState.signal !== null ? `by ${exitState.signal}` : `with exit code ${exitState.code ?? '?'}`;
|
|
52
|
+
throw new Error(`daemon ${pid} exited before becoming ready (${exitDetail})`);
|
|
53
|
+
}
|
|
54
|
+
await sleep(STARTUP_VERIFY_POLL_MS);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`daemon ${pid} did not become ready within ${timeoutMs}ms`);
|
|
57
|
+
}
|
|
24
58
|
/** Spawn crtrd detached. Returns immediately; the child outlives this process.
|
|
25
59
|
*
|
|
26
60
|
* If the daemon is already running, returns {started:false, existing_pid}.
|
|
27
61
|
* If spawning fails (e.g. missing dist — run `npm run build` first), throws. */
|
|
28
|
-
export function spawnDaemon() {
|
|
62
|
+
export async function spawnDaemon() {
|
|
29
63
|
if (isDaemonRunning()) {
|
|
30
64
|
return { started: false, existing_pid: readPidfile() ?? undefined };
|
|
31
65
|
}
|
|
@@ -41,7 +75,20 @@ export function spawnDaemon() {
|
|
|
41
75
|
stdio: 'ignore',
|
|
42
76
|
});
|
|
43
77
|
const pid = child.pid;
|
|
78
|
+
if (pid === undefined) {
|
|
79
|
+
throw new Error('daemon spawn did not return a pid');
|
|
80
|
+
}
|
|
81
|
+
let exitState = null;
|
|
82
|
+
child.once('exit', (code, signal) => {
|
|
83
|
+
exitState = { code, signal };
|
|
84
|
+
});
|
|
44
85
|
child.unref();
|
|
86
|
+
const existingPid = await verifyDaemonStartup(pid, STARTUP_VERIFY_WINDOW_MS, {
|
|
87
|
+
childExited: () => exitState,
|
|
88
|
+
});
|
|
89
|
+
if (existingPid !== null) {
|
|
90
|
+
return { started: false, existing_pid: existingPid };
|
|
91
|
+
}
|
|
45
92
|
return { started: true, pid };
|
|
46
93
|
}
|
|
47
94
|
// ---------------------------------------------------------------------------
|
|
@@ -51,12 +98,10 @@ export function spawnDaemon() {
|
|
|
51
98
|
* Silently swallows spawn errors (the canvas still works without the daemon;
|
|
52
99
|
* nodes just won't be auto-revived). */
|
|
53
100
|
export function ensureDaemon() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Intentionally silent — a missing dist/daemon/crtrd-cli.js (dev mode,
|
|
60
|
-
// pre-build) must not break the calling command.
|
|
101
|
+
if (!isDaemonRunning()) {
|
|
102
|
+
void spawnDaemon().catch(() => {
|
|
103
|
+
// Intentionally silent — a missing dist/daemon/crtrd-cli.js (dev mode,
|
|
104
|
+
// pre-build) must not break the calling command.
|
|
105
|
+
});
|
|
61
106
|
}
|
|
62
107
|
}
|