@north-light/crouter 0.3.233 → 0.3.234
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/dist/api/client.js +16 -15
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
- package/dist/clients/attach/chrome/ticket-panel.js +15 -2
- package/dist/clients/attach/overlays/graph.js +0 -3
- package/dist/clients/attach/overlays/mcp.js +1 -2
- package/dist/clients/attach/render/chat-view.js +0 -12
- package/dist/clients/attach/render/crtr-output.js +0 -2
- package/dist/clients/attach/render/tool-calls.js +0 -6
- package/dist/clients/attach/slash/dispatch.js +8 -11
- package/dist/clients/attach/viewer.js +532 -534
- package/dist/clients/conversation/projection.js +0 -2
- package/dist/clients/inbox/controller.js +22 -3
- package/dist/clients/inbox/review/companion-pane.js +0 -4
- package/dist/clients/inbox/review-adapter.js +0 -7
- package/dist/clients/inbox/tui/slots.d.ts +3 -3
- package/dist/clients/inbox/tui/types.d.ts +2 -2
- package/dist/commands/api-client.js +0 -8
- package/dist/commands/attention.js +0 -8
- package/dist/commands/canvas-browse.js +2 -1
- package/dist/commands/canvas-config.js +0 -10
- package/dist/commands/canvas-history.js +2 -1
- package/dist/commands/cron.js +5 -18
- package/dist/commands/dashboard.js +0 -2
- package/dist/commands/human/feedback.js +2 -1
- package/dist/commands/human/prompts.js +2 -5
- package/dist/commands/human/queue.js +2 -7
- package/dist/commands/human/request.js +0 -14
- package/dist/commands/human/review.js +2 -1
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
- package/dist/commands/memory/find.js +2 -2
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/read.d.ts +2 -7
- package/dist/commands/memory/read.js +5 -16
- package/dist/commands/memory/shared.js +4 -7
- package/dist/commands/memory/write.js +4 -3
- package/dist/commands/node/bash.js +2 -1
- package/dist/commands/node/create.js +2 -1
- package/dist/commands/node/inspect.js +0 -6
- package/dist/commands/node/lifecycle.js +4 -13
- package/dist/commands/node/message.js +3 -2
- package/dist/commands/node/subscription.js +2 -1
- package/dist/commands/node/wait.js +4 -3
- package/dist/commands/node-context.js +2 -1
- package/dist/commands/node-lifecycle-revive.js +0 -2
- package/dist/commands/node-worktree.js +2 -1
- package/dist/commands/pkg/browse/actions.d.ts +1 -1
- package/dist/commands/pkg/browse/actions.js +4 -4
- package/dist/commands/pkg/browse/catalog.d.ts +1 -1
- package/dist/commands/pkg/browse/catalog.js +15 -15
- package/dist/commands/pkg/browse/command-view.d.ts +1 -1
- package/dist/commands/pkg/browse/command-view.js +3 -3
- package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
- package/dist/commands/pkg/browse/detail-panel.js +8 -8
- package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
- package/dist/commands/pkg/browse/doc-view.js +3 -3
- package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
- package/dist/commands/pkg/browse/list-panel.js +3 -3
- package/dist/commands/pkg/browse/model.d.ts +3 -3
- package/dist/commands/pkg/browse/shell.d.ts +2 -2
- package/dist/commands/pkg/browse/shell.js +13 -13
- package/dist/commands/pkg/plugin-manage.js +2 -1
- package/dist/commands/pkg/shared.js +0 -2
- package/dist/commands/profile/delete.js +2 -1
- package/dist/commands/push.js +2 -3
- package/dist/commands/revive.js +0 -2
- package/dist/commands/surface/node/placement.js +2 -1
- package/dist/commands/surface-edit.js +2 -1
- package/dist/commands/sys/config.js +0 -4
- package/dist/commands/sys/daemon.js +0 -10
- package/dist/commands/sys/doctor.js +3 -2
- package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
- package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
- package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
- package/dist/commands/sys/panels/dependencies-panel.js +2 -2
- package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
- package/dist/commands/sys/panels/keybindings-panel.js +5 -5
- package/dist/commands/sys/panels/models-panel.d.ts +4 -4
- package/dist/commands/sys/panels/models-panel.js +11 -10
- package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
- package/dist/commands/sys/panels/plugins-panel.js +2 -2
- package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
- package/dist/commands/sys/panels/prefs-panel.js +4 -4
- package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
- package/dist/commands/sys/panels/profiles-panel.js +14 -13
- package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
- package/dist/commands/sys/panels/provider-panel.js +8 -8
- package/dist/commands/sys/settings-shell.d.ts +3 -3
- package/dist/commands/sys/settings-shell.js +15 -15
- package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
- package/dist/commands/sys/setup-settings-runner.js +5 -5
- package/dist/commands/sys/setup-wizard.d.ts +4 -4
- package/dist/commands/sys/setup-wizard.js +8 -8
- package/dist/commands/sys/support/prepare.js +4 -4
- package/dist/commands/sys/support/submit.js +2 -2
- package/dist/commands/sys/sync-deps.js +0 -2
- package/dist/commands/sys/sync-project-guidance.js +2 -1
- package/dist/commands/sys/sysprompt.js +2 -1
- package/dist/core/__tests__/boot.test.js +0 -8
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
- package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
- package/dist/core/__tests__/canvas.test.js +1 -1
- package/dist/core/__tests__/child-death-wake.test.js +0 -2
- package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
- package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
- package/dist/core/__tests__/daemon-wedge.test.js +0 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
- package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
- package/dist/core/__tests__/focuses.test.js +0 -6
- package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
- package/dist/core/__tests__/fs-utils.test.js +50 -0
- package/dist/core/__tests__/helpers/harness.js +1 -1
- package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
- package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
- package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
- package/dist/core/__tests__/integration/command-plugins.test.js +0 -24
- package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
- package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
- package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
- package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
- package/dist/core/__tests__/integration/revive.test.js +0 -12
- package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
- package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
- package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
- package/dist/core/__tests__/lifecycle.test.js +0 -18
- package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
- package/dist/core/__tests__/migration.test.js +2 -2
- package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
- package/dist/core/__tests__/pid-identity-match.test.js +1 -1
- package/dist/core/__tests__/plugin-kinds.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +1 -1
- package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
- package/dist/core/__tests__/respawn-throttle.test.js +0 -4
- package/dist/core/__tests__/revive-all.test.js +5 -5
- package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
- package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
- package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
- package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
- package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
- package/dist/core/activity-signal.d.ts +7 -0
- package/dist/core/activity-signal.js +38 -0
- package/dist/core/auto-update.js +2 -1
- package/dist/core/bootstrap.js +2 -1
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
- package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
- package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
- package/dist/core/canvas/browse/app.js +3 -2
- package/dist/core/canvas/browse/model.d.ts +1 -1
- package/dist/core/canvas/browse/model.js +0 -4
- package/dist/core/canvas/canvas.d.ts +22 -0
- package/dist/core/canvas/canvas.js +50 -18
- package/dist/core/canvas/crons.js +0 -6
- package/dist/core/canvas/db.d.ts +14 -20
- package/dist/core/canvas/db.js +30 -1332
- package/dist/core/canvas/focuses.js +0 -4
- package/dist/core/canvas/history.js +2 -1
- package/dist/core/canvas/migrations.d.ts +20 -0
- package/dist/core/canvas/migrations.js +1339 -0
- package/dist/core/canvas/nav-model.js +1 -11
- package/dist/core/canvas/nav-render.js +0 -8
- package/dist/core/canvas/remote-canvas-source.js +0 -2
- package/dist/core/canvas/render-source.js +31 -13
- package/dist/core/command-manifests/manifest.js +0 -10
- package/dist/core/command-manifests/schema.js +0 -4
- package/dist/core/command-plugins/transport/http-invoke.js +0 -14
- package/dist/core/command.js +0 -4
- package/dist/core/config.js +2 -1
- package/dist/core/feed/inbox.d.ts +10 -0
- package/dist/core/feed/inbox.js +18 -12
- package/dist/core/fs-utils.d.ts +9 -2
- package/dist/core/fs-utils.js +7 -15
- package/dist/core/help.js +0 -8
- package/dist/core/host-exports/export.d.ts +2 -2
- package/dist/core/host-exports/export.js +9 -12
- package/dist/core/human/answer.d.ts +2 -2
- package/dist/core/human/convention.d.ts +2 -5
- package/dist/core/human/convention.js +3 -13
- package/dist/core/human/page-schema.d.ts +2 -2
- package/dist/core/human/page-schema.js +2 -2
- package/dist/core/human/root.js +6 -23
- package/dist/core/human/scan.js +24 -7
- package/dist/core/human/tickets.d.ts +4 -1
- package/dist/core/human/tickets.js +20 -6
- package/dist/core/io.js +0 -12
- package/dist/core/memory/identity.js +0 -4
- package/dist/core/memory/inline-ref-inventory.js +3 -2
- package/dist/core/memory/lint.js +3 -6
- package/dist/core/memory-resolver.d.ts +5 -0
- package/dist/core/memory-resolver.js +14 -12
- package/dist/core/pagination.js +0 -2
- package/dist/core/preview-registry.d.ts +14 -0
- package/dist/core/preview-registry.js +2 -4
- package/dist/core/preview-result-path.js +2 -1
- package/dist/core/profiles/manifest.d.ts +37 -5
- package/dist/core/profiles/manifest.js +44 -24
- package/dist/core/profiles/select.js +0 -4
- package/dist/core/review/signal.js +4 -23
- package/dist/core/review/ticket-filter.d.ts +3 -3
- package/dist/core/review/ticket-filter.js +4 -9
- package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
- package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
- package/dist/core/runtime/bearings-render.d.ts +56 -0
- package/dist/core/runtime/bearings-render.js +241 -0
- package/dist/core/runtime/bearings.d.ts +2 -17
- package/dist/core/runtime/bearings.js +12 -198
- package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
- package/dist/core/runtime/broker/auth-reload.js +149 -0
- package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
- package/dist/core/runtime/broker/engine-drive.js +257 -0
- package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
- package/dist/core/runtime/broker/engine-routing.js +211 -0
- package/dist/core/runtime/broker/event-projection.js +3 -2
- package/dist/core/runtime/broker/fault-retry.js +5 -4
- package/dist/core/runtime/broker/frame-client.d.ts +38 -0
- package/dist/core/runtime/broker/frame-client.js +129 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
- package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
- package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
- package/dist/core/runtime/broker/frame-engine-control.js +231 -0
- package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
- package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
- package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
- package/dist/core/runtime/broker/frame-model-query.js +69 -0
- package/dist/core/runtime/broker/frame-read.d.ts +19 -0
- package/dist/core/runtime/broker/frame-read.js +116 -0
- package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
- package/dist/core/runtime/broker/frame-replies.js +64 -0
- package/dist/core/runtime/broker/frame-session.d.ts +34 -0
- package/dist/core/runtime/broker/frame-session.js +203 -0
- package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
- package/dist/core/runtime/broker/turn-ignition.js +74 -0
- package/dist/core/runtime/broker-extension-render.js +9 -85
- package/dist/core/runtime/broker-protocol.js +0 -4
- package/dist/core/runtime/broker-sdk.js +2 -1
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +5 -23
- package/dist/core/runtime/fault.js +3 -2
- package/dist/core/runtime/headless-pi.js +3 -2
- package/dist/core/runtime/host.js +2 -3
- package/dist/core/runtime/kickoff.js +0 -4
- package/dist/core/runtime/launch-target.js +2 -1
- package/dist/core/runtime/launch.js +3 -6
- package/dist/core/runtime/lifecycle.js +17 -26
- package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
- package/dist/core/runtime/naming.js +0 -2
- package/dist/core/runtime/nodes.js +2 -1
- package/dist/core/runtime/persona.js +0 -6
- package/dist/core/runtime/placement-tmux.js +2 -5
- package/dist/core/runtime/placement.js +4 -11
- package/dist/core/runtime/recap.js +4 -3
- package/dist/core/runtime/reopen.js +4 -7
- package/dist/core/runtime/reset.js +0 -4
- package/dist/core/runtime/resume-root.js +0 -2
- package/dist/core/runtime/revive.js +17 -13
- package/dist/core/runtime/session-list-cache.js +2 -2
- package/dist/core/runtime/shell-expansion.js +0 -2
- package/dist/core/runtime/spawn.js +0 -2
- package/dist/core/runtime/stream-watchdog.d.ts +0 -1
- package/dist/core/runtime/stream-watchdog.js +13 -18
- package/dist/core/runtime/tmux-bindings.js +0 -2
- package/dist/core/runtime/tmux-driver.d.ts +12 -0
- package/dist/core/runtime/tmux-driver.js +18 -13
- package/dist/core/runtime/warm-pool.js +0 -4
- package/dist/core/scope.js +4 -3
- package/dist/core/spawn.js +2 -3
- package/dist/core/subscription-state.js +10 -17
- package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
- package/dist/core/substrate/render.js +0 -12
- package/dist/core/substrate/schema.js +0 -12
- package/dist/core/substrate/surface-match.js +0 -2
- package/dist/core/termrender/display.js +2 -1
- package/dist/core/tui/page-host.d.ts +3 -3
- package/dist/core/tui/page-host.js +4 -4
- package/dist/core/tui/panel.js +3 -3
- package/dist/core/wake.js +0 -12
- package/dist/core/worktree.js +0 -2
- package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
- package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
- package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
- package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
- package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
- package/dist/daemon/api/handlers/attach.js +2 -1
- package/dist/daemon/api/handlers/broker-ops.js +3 -6
- package/dist/daemon/api/handlers/canvas.js +12 -18
- package/dist/daemon/api/handlers/feedback-comments.js +0 -8
- package/dist/daemon/api/handlers/health.js +3 -2
- package/dist/daemon/api/handlers/human-requests.js +0 -10
- package/dist/daemon/api/handlers/human.js +1 -5
- package/dist/daemon/api/handlers/inbox.js +0 -18
- package/dist/daemon/api/handlers/memory.js +1 -1
- package/dist/daemon/api/handlers/messages.js +4 -8
- package/dist/daemon/api/handlers/nodes.js +10 -21
- package/dist/daemon/api/handlers/reports.js +1 -1
- package/dist/daemon/api/handlers/reviews.js +1 -6
- package/dist/daemon/api/map.js +0 -6
- package/dist/daemon/api/router.js +0 -4
- package/dist/daemon/api/server.js +2 -5
- package/dist/daemon/boot-migrations.d.ts +14 -0
- package/dist/daemon/boot-migrations.js +31 -0
- package/dist/daemon/cron/armed-context.d.ts +21 -0
- package/dist/daemon/cron/armed-context.js +100 -0
- package/dist/daemon/cron/capture.d.ts +20 -0
- package/dist/daemon/cron/capture.js +45 -0
- package/dist/daemon/cron/disposition.d.ts +19 -0
- package/dist/daemon/cron/disposition.js +52 -0
- package/dist/daemon/cron/escalation.d.ts +12 -0
- package/dist/daemon/cron/escalation.js +124 -0
- package/dist/daemon/cron/held.d.ts +4 -0
- package/dist/daemon/cron/held.js +20 -0
- package/dist/daemon/cron/lease-recovery.d.ts +3 -0
- package/dist/daemon/cron/lease-recovery.js +119 -0
- package/dist/daemon/cron/live-runs.d.ts +11 -0
- package/dist/daemon/cron/live-runs.js +23 -0
- package/dist/daemon/cron/passes.d.ts +11 -0
- package/dist/daemon/cron/passes.js +78 -0
- package/dist/daemon/cron/settle.d.ts +28 -0
- package/dist/daemon/cron/settle.js +37 -0
- package/dist/daemon/cron/sinks.d.ts +2 -0
- package/dist/daemon/cron/sinks.js +100 -0
- package/dist/daemon/cron-run.d.ts +5 -41
- package/dist/daemon/cron-run.js +193 -882
- package/dist/daemon/crtrd-cli.js +2 -1
- package/dist/daemon/crtrd.js +22 -22
- package/dist/daemon/fleet.js +0 -4
- package/dist/daemon/human/deliver-action.js +2 -1
- package/dist/daemon/human/sweep.js +15 -4
- package/dist/daemon/manage.d.ts +2 -2
- package/dist/daemon/manage.js +4 -13
- package/dist/daemon/pidfile.js +2 -2
- package/dist/daemon/reconcilers/broker-supervision.js +26 -29
- package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
- package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
- package/dist/daemon/reconcilers/live-obligation.js +2 -2
- package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
- package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
- package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
- package/dist/daemon/review/companion.js +2 -2
- package/dist/daemon/review/sweep.js +8 -3
- package/dist/migrations/001-surfaces-frontmatter.js +0 -6
- package/dist/migrations/corpus.js +2 -5
- package/dist/migrations/registry.js +15 -2
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
- package/dist/pi-extensions/broker-local.js +2 -1
- package/dist/pi-extensions/canvas-bash-valve.js +3 -17
- package/dist/pi-extensions/canvas-context-intro.js +2 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
- package/dist/pi-extensions/canvas-goal-capture.js +2 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
- package/dist/pi-extensions/canvas-passive-context.js +2 -5
- package/dist/pi-extensions/canvas-preview-result.js +2 -1
- package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
- package/dist/pi-extensions/canvas-recap.js +3 -16
- package/dist/pi-extensions/canvas-review-boundary.js +2 -5
- package/dist/pi-extensions/canvas-stophook.js +2 -26
- package/dist/pi-extensions/canvas-structured-output.js +2 -1
- package/dist/pi-extensions/canvas-tool-guide.js +2 -1
- package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
- package/dist/shared/__tests__/env-boundary.test.js +93 -0
- package/dist/shared/env.d.ts +68 -0
- package/dist/shared/env.js +171 -0
- package/package.json +3 -1
- package/runtime.lock.json +2 -2
- package/dist/core/canvas/render.d.ts +0 -47
- package/dist/core/canvas/render.js +0 -245
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This is the persistent settings editor. The first-run installer lives in
|
|
4
4
|
// setup-wizard.ts; both surfaces reuse only the panels they actually share.
|
|
5
|
-
import { matchesKey, truncateToWidth, visibleWidth, } from
|
|
6
|
-
import { configPath } from
|
|
7
|
-
import { formatGesture, matchesPiTuiInput, } from
|
|
8
|
-
import { reconcileInboxFrontDoor } from
|
|
9
|
-
import { runSetupSettingsProcess } from
|
|
10
|
-
import { BrokerLimitsPanel } from
|
|
11
|
-
import { KeybindingsPanel } from
|
|
12
|
-
import { ModelsPanel } from
|
|
13
|
-
import { PAGE_CHROME_ROWS, columns, padAnsi, pageFrame, theme, } from
|
|
14
|
-
import { PrefsPanel } from
|
|
15
|
-
import { ProfilesPanel } from
|
|
16
|
-
import { ProviderPanel } from
|
|
5
|
+
import { matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
|
|
6
|
+
import { configPath } from '../../core/config.js';
|
|
7
|
+
import { formatGesture, matchesPiTuiInput, } from '../../core/keybindings/index.js';
|
|
8
|
+
import { reconcileInboxFrontDoor } from './setup-core.js';
|
|
9
|
+
import { runSetupSettingsProcess } from './setup-settings-runner.js';
|
|
10
|
+
import { BrokerLimitsPanel } from './panels/broker-limits-panel.js';
|
|
11
|
+
import { KeybindingsPanel } from './panels/keybindings-panel.js';
|
|
12
|
+
import { ModelsPanel } from './panels/models-panel.js';
|
|
13
|
+
import { PAGE_CHROME_ROWS, columns, padAnsi, pageFrame, theme, } from '../../core/tui/panel.js';
|
|
14
|
+
import { PrefsPanel } from './panels/prefs-panel.js';
|
|
15
|
+
import { ProfilesPanel } from './panels/profiles-panel.js';
|
|
16
|
+
import { ProviderPanel } from './panels/provider-panel.js';
|
|
17
17
|
/** Rail column width, including its badge gutter. */
|
|
18
18
|
const RAIL_WIDTH = 22;
|
|
19
19
|
/** Below this the rail costs more than it explains; the page falls back to a
|
|
@@ -114,7 +114,7 @@ export class SettingsShell {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
//
|
|
117
|
+
// focus and navigation
|
|
118
118
|
set focused(value) {
|
|
119
119
|
this._focused = value;
|
|
120
120
|
this.syncFocus();
|
|
@@ -164,7 +164,7 @@ export class SettingsShell {
|
|
|
164
164
|
? "disabled"
|
|
165
165
|
: formatGesture(gestures[0].split(" > ").pop());
|
|
166
166
|
}
|
|
167
|
-
//
|
|
167
|
+
// input
|
|
168
168
|
/** Panels that have taken over the keyboard — a live text field, a modal, an
|
|
169
169
|
* open picker — keep every key including Escape until they release it. */
|
|
170
170
|
panelOwnsKeyboard(data) {
|
|
@@ -270,7 +270,7 @@ export class SettingsShell {
|
|
|
270
270
|
this.keybindings.focused = false;
|
|
271
271
|
this.brokerLimits.focused = false;
|
|
272
272
|
}
|
|
273
|
-
//
|
|
273
|
+
// rendering
|
|
274
274
|
renderRail(height) {
|
|
275
275
|
const out = [];
|
|
276
276
|
let group;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ModelRegistry, ModelRuntime } from
|
|
2
|
-
import type { BindingId, BindingResolution } from
|
|
3
|
-
import { type TuiPageContext, type TuiPageControl, type TuiPageSurface } from
|
|
1
|
+
import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { BindingId, BindingResolution } from '../../core/keybindings/index.js';
|
|
3
|
+
import { type TuiPageContext, type TuiPageControl, type TuiPageSurface } from '../../core/tui/page-host.js';
|
|
4
4
|
/** Setup and settings both edit pi's model configuration, so their host adds
|
|
5
5
|
* the model runtime to the shared page context. Every other page concern —
|
|
6
6
|
* theme, keybindings, terminal lifecycle — belongs to `runTuiPage`. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ModelRegistry, ModelRuntime } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { defaultAgentDir } from
|
|
4
|
-
import { PoolCredentialStore } from
|
|
5
|
-
import { runTuiPage, } from
|
|
1
|
+
import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { defaultAgentDir } from '../../clients/attach/config.js';
|
|
4
|
+
import { PoolCredentialStore } from '../../core/pool-credential-store.js';
|
|
5
|
+
import { runTuiPage, } from '../../core/tui/page-host.js';
|
|
6
6
|
export async function runSetupSettingsProcess(createSurface, interceptCancel) {
|
|
7
7
|
const agentDir = defaultAgentDir();
|
|
8
8
|
const runtime = await ModelRuntime.create({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ModelRegistry, ModelRuntime } from
|
|
2
|
-
import { TUI, type Component, type Focusable } from
|
|
3
|
-
import { type BindingId, type BindingResolution } from
|
|
4
|
-
import { type SetupPackageId, type SetupPackageManifestEntry, type SetupSubmission, type SetupSystemInstallPlan } from
|
|
1
|
+
import { ModelRegistry, ModelRuntime } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { TUI, type Component, type Focusable } from '@earendil-works/pi-tui';
|
|
3
|
+
import { type BindingId, type BindingResolution } from '../../core/keybindings/index.js';
|
|
4
|
+
import { type SetupPackageId, type SetupPackageManifestEntry, type SetupSubmission, type SetupSystemInstallPlan } from './setup-core.js';
|
|
5
5
|
/** The host owns step navigation, focus, submission and composition. Each step
|
|
6
6
|
* owns its own mutable UI state. */
|
|
7
7
|
export declare class SetupWizard implements Component, Focusable {
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
// broker limits, updates — lives in settings-shell.ts. Both hosts share the
|
|
6
6
|
// panels in ./panels; this one is a linear stepper that ends in a single
|
|
7
7
|
// confirmed install, that one is a persistent editor that saves as you go.
|
|
8
|
-
import { matchesKey, truncateToWidth, visibleWidth, } from
|
|
9
|
-
import { formatGesture, matchesPiTuiInput, } from
|
|
10
|
-
import { buildSetupInitialState, executeSetupSubmission, formatSetupExecutionSummary, packagePlanAction, reconcileInboxFrontDoor, renderSetupStaticInstructions, } from
|
|
11
|
-
import { runSetupSettingsProcess } from
|
|
12
|
-
import { DependenciesPanel } from
|
|
13
|
-
import { PAGE_CHROME_ROWS, pageFrame, theme, } from
|
|
14
|
-
import { PluginsPanel } from
|
|
15
|
-
import { ProviderPanel } from
|
|
8
|
+
import { matchesKey, truncateToWidth, visibleWidth, } from '@earendil-works/pi-tui';
|
|
9
|
+
import { formatGesture, matchesPiTuiInput, } from '../../core/keybindings/index.js';
|
|
10
|
+
import { buildSetupInitialState, executeSetupSubmission, formatSetupExecutionSummary, packagePlanAction, reconcileInboxFrontDoor, renderSetupStaticInstructions, } from './setup-core.js';
|
|
11
|
+
import { runSetupSettingsProcess } from './setup-settings-runner.js';
|
|
12
|
+
import { DependenciesPanel } from './panels/dependencies-panel.js';
|
|
13
|
+
import { PAGE_CHROME_ROWS, pageFrame, theme, } from '../../core/tui/panel.js';
|
|
14
|
+
import { PluginsPanel } from './panels/plugins-panel.js';
|
|
15
|
+
import { ProviderPanel } from './panels/provider-panel.js';
|
|
16
16
|
const STEPS = ["Providers", "Plugins", "Dependencies"];
|
|
17
17
|
/** The host owns step navigation, focus, submission and composition. Each step
|
|
18
18
|
* owns its own mutable UI state. */
|
|
@@ -16,7 +16,7 @@ const BUNDLE_NAME = 'bundle.jsonl';
|
|
|
16
16
|
const CAP_BYTES = 2_097_152;
|
|
17
17
|
const MAX_RUNTIME_STATE_BYTES = MAX_EVENT_RECORD_BYTES - 1;
|
|
18
18
|
const CATEGORIES = ['events', 'runtime_state', 'raw_residue'];
|
|
19
|
-
//
|
|
19
|
+
// reference generation
|
|
20
20
|
function pad(value, length) {
|
|
21
21
|
return String(value).padStart(length, '0');
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@ function generateReference(now) {
|
|
|
26
26
|
const time = `${pad(now.getUTCHours(), 2)}${pad(now.getUTCMinutes(), 2)}${pad(now.getUTCSeconds(), 2)}${pad(now.getUTCMilliseconds(), 3)}`;
|
|
27
27
|
return `${date}T${time}Z-${randomBytes(4).toString('hex')}`;
|
|
28
28
|
}
|
|
29
|
-
//
|
|
29
|
+
// compact bundle records
|
|
30
30
|
function compactLine(record) {
|
|
31
31
|
return Buffer.from(`${JSON.stringify(record)}\n`, 'utf8');
|
|
32
32
|
}
|
|
@@ -123,7 +123,7 @@ function collectRuntimeState(nodeId, ts, sources) {
|
|
|
123
123
|
}
|
|
124
124
|
return records;
|
|
125
125
|
}
|
|
126
|
-
//
|
|
126
|
+
// manifest + cap fitting
|
|
127
127
|
function renderManifestBuffer(base, sources) {
|
|
128
128
|
const manifest = {
|
|
129
129
|
reference: base.reference,
|
|
@@ -157,7 +157,7 @@ function fitToCap(records, sources, base) {
|
|
|
157
157
|
state.truncated = true;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
|
-
//
|
|
160
|
+
// core
|
|
161
161
|
/** Output-free: collects and writes the prepared artifact and returns a
|
|
162
162
|
* small result. Never prints; the leaf below owns argv/rendering. */
|
|
163
163
|
export function prepareSupportBundle(nodeId) {
|
|
@@ -43,7 +43,7 @@ export function defaultGhRunner(args, input) {
|
|
|
43
43
|
const included = args.includes('--include') ? splitIncludedResponse(raw) : { body: raw, httpStatus: undefined };
|
|
44
44
|
return { code: result.status, stdout: included.body, stderr, httpStatus: included.httpStatus };
|
|
45
45
|
}
|
|
46
|
-
//
|
|
46
|
+
// symlink-free, TOCTOU-safe filesystem access
|
|
47
47
|
// `~/.crouter/support/<reference>/` is user-writable, so every open here rejects symlinks
|
|
48
48
|
// and confirms the thing opened is the thing later read/uploaded — never an arbitrary path.
|
|
49
49
|
function closeSafe(fd) {
|
|
@@ -194,7 +194,7 @@ function openReference(reference) {
|
|
|
194
194
|
throw error;
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
-
//
|
|
197
|
+
// manifest and bundle validation
|
|
198
198
|
// An ordinary manifest: every field is either fixed input (the reference) or independently
|
|
199
199
|
// checked against its own declared shape. Nothing here is self-referential — the cap is
|
|
200
200
|
// enforced by directly measuring the two files, not by trusting a declared byte count.
|
|
@@ -449,9 +449,7 @@ export function runDepsSync(opts) {
|
|
|
449
449
|
writeText(join(depsDir, '.gitignore'), '*\n');
|
|
450
450
|
return rows;
|
|
451
451
|
}
|
|
452
|
-
// ---------------------------------------------------------------------------
|
|
453
452
|
// The `sys sync deps` leaf.
|
|
454
|
-
// ---------------------------------------------------------------------------
|
|
455
453
|
function renderDepsSummary(rows) {
|
|
456
454
|
const imported = rows.filter((r) => r.status === 'imported').length;
|
|
457
455
|
const wouldImport = rows.filter((r) => r.status === 'would-import').length;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// CLAUDE.md/AGENTS.md/.claude/rules into repository-root and nested
|
|
3
3
|
// `.crouter/memory/` stores. Only the repository root owns the namespace and
|
|
4
4
|
// workspace front door; nested stores derive their prefix from their path.
|
|
5
|
+
import { envProfileId } from '../../shared/env.js';
|
|
5
6
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
6
7
|
import { basename, join, resolve } from 'node:path';
|
|
7
8
|
import { defineLeaf } from '../../core/command.js';
|
|
@@ -33,7 +34,7 @@ function projectDocRoots(sourceArg) {
|
|
|
33
34
|
if (sourceArg !== undefined)
|
|
34
35
|
return [resolve(sourceArg)];
|
|
35
36
|
const roots = [resolve(process.cwd())];
|
|
36
|
-
const profileId =
|
|
37
|
+
const profileId = envProfileId();
|
|
37
38
|
if (profileId !== undefined && profileId !== '') {
|
|
38
39
|
try {
|
|
39
40
|
const { manifest } = loadProfileManifest(profileId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { envNodeId } from '../../shared/env.js';
|
|
1
2
|
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { tmpdir } from 'node:os';
|
|
3
4
|
import { join } from 'node:path';
|
|
@@ -34,7 +35,7 @@ export const sysSyspromptLeaf = defineLeaf({
|
|
|
34
35
|
preview: { suppressOutput: true },
|
|
35
36
|
},
|
|
36
37
|
run: async (input) => {
|
|
37
|
-
const nodeId = input['node']?.trim() ||
|
|
38
|
+
const nodeId = input['node']?.trim() || envNodeId();
|
|
38
39
|
if (nodeId === undefined || nodeId === '') {
|
|
39
40
|
throw new InputError({ error: 'empty_node', message: 'a node id is required', field: 'node', next: 'Pass a node id or run from inside a node.' });
|
|
40
41
|
}
|
|
@@ -59,9 +59,7 @@ after(() => {
|
|
|
59
59
|
rmSync(home, { recursive: true, force: true });
|
|
60
60
|
delete process.env['CRTR_HOME'];
|
|
61
61
|
});
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
62
|
// bootChanged — pure core
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
63
|
test('bootChanged: no persisted history is never a change (never clears a genuinely live pid)', () => {
|
|
66
64
|
assert.equal(bootChanged(ident('boot-A', 1_000_000), null), false);
|
|
67
65
|
});
|
|
@@ -84,12 +82,8 @@ test('bootChanged: wall-clock fallback (no boot_id) — epochs within tolerance
|
|
|
84
82
|
test('bootChanged: wall-clock fallback (no boot_id) — epochs far apart are a DIFFERENT boot', () => {
|
|
85
83
|
assert.equal(bootChanged(ident(null, 1_000_000 + 10 * 60_000), ident(null, 1_000_000)), true, '10min apart — different boot');
|
|
86
84
|
});
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
85
|
// reconcileBootLiveness — the impure sweep against a real canvas.db
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
86
|
// migrateLegacyPidIdentities — one-time in-place format migration at startup
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
87
|
test('migrateLegacyPidIdentities: a live legacy lstart#ticks broker is re-recorded to <bootId>#<ticks>', () => {
|
|
94
88
|
createNode(node('n1'));
|
|
95
89
|
setPidIdentity('n1', 111, 'Sat Jul 11 17:51:45 2026#53459'); // legacy baseline
|
|
@@ -193,7 +187,6 @@ test('reconcileBootLiveness: after reconciling, the SAME boot no longer re-trigg
|
|
|
193
187
|
assert.deepEqual(result, { changed: false, cleared: [], sameBootProven: true });
|
|
194
188
|
assert.equal(getRow('n1')?.pi_pid, 999, 'a pid recorded AFTER reconciliation, within the same boot, is never touched');
|
|
195
189
|
});
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
190
|
// bootProvenSame + the daemon migration gate (Major-1 regression)
|
|
198
191
|
//
|
|
199
192
|
// The legacy identity migration blesses a recorded pid as a current-boot
|
|
@@ -202,7 +195,6 @@ test('reconcileBootLiveness: after reconciling, the SAME boot no longer re-trigg
|
|
|
202
195
|
// NOT that proof — it is also true with no/malformed marker or the wall-clock
|
|
203
196
|
// epoch fallback, where an unrelated cross-boot process can collide on
|
|
204
197
|
// pid+ticks and be permanently baptized.
|
|
205
|
-
// ---------------------------------------------------------------------------
|
|
206
198
|
test('bootProvenSame: exact equal boot_id on both sides is proof', () => {
|
|
207
199
|
assert.equal(bootProvenSame(ident('boot-A', 2_000), ident('boot-A', 1_000)), true);
|
|
208
200
|
});
|
|
@@ -3,12 +3,33 @@
|
|
|
3
3
|
// and rejects any reach to the single DB accessor module.
|
|
4
4
|
import { test } from 'node:test';
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, join, normalize, resolve } from 'node:path';
|
|
8
8
|
import ts from 'typescript';
|
|
9
9
|
import { CANVAS_EXTENSION_NAMES } from '../canvas/extensions.js';
|
|
10
10
|
const SOURCE_ROOT = resolve(process.cwd(), 'src');
|
|
11
11
|
const DB_MODULE = normalize(join(SOURCE_ROOT, 'core/canvas/db.ts'));
|
|
12
|
+
// The builtin pi-crtr-extensions package loads into the SAME broker process
|
|
13
|
+
// as the shipped pi-extensions/ roots below (its own package.json declares
|
|
14
|
+
// `pi.extensions: ["extensions/*.ts", "extensions/*/index.ts"]`), so it must
|
|
15
|
+
// clear the identical canvas.db-free bar. Read the glob's two shapes directly
|
|
16
|
+
// off disk rather than hardcoding a name list, so a future extension added to
|
|
17
|
+
// the package is covered automatically.
|
|
18
|
+
function builtinPiCrtrExtensionRoots() {
|
|
19
|
+
const extDir = join(SOURCE_ROOT, 'builtin-pi-packages/pi-crtr-extensions/extensions');
|
|
20
|
+
const roots = [];
|
|
21
|
+
for (const entry of readdirSync(extDir, { withFileTypes: true })) {
|
|
22
|
+
if (entry.isFile() && entry.name.endsWith('.ts') && !entry.name.includes('.test.')) {
|
|
23
|
+
roots.push(join(extDir, entry.name));
|
|
24
|
+
}
|
|
25
|
+
else if (entry.isDirectory()) {
|
|
26
|
+
const indexFile = join(extDir, entry.name, 'index.ts');
|
|
27
|
+
if (existsSync(indexFile))
|
|
28
|
+
roots.push(indexFile);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return roots;
|
|
32
|
+
}
|
|
12
33
|
function resolveLocalImport(from, specifier) {
|
|
13
34
|
if (!specifier.startsWith('.'))
|
|
14
35
|
return null;
|
|
@@ -45,6 +66,7 @@ test('every shipped broker extension and the prospective preflight are transitiv
|
|
|
45
66
|
const roots = [
|
|
46
67
|
...CANVAS_EXTENSION_NAMES.map((name) => join(SOURCE_ROOT, 'pi-extensions', `${name}.ts`)),
|
|
47
68
|
join(SOURCE_ROOT, 'core/runtime/prospective-inventory-cli.ts'),
|
|
69
|
+
...builtinPiCrtrExtensionRoots(),
|
|
48
70
|
];
|
|
49
71
|
for (const root of roots) {
|
|
50
72
|
const reach = dbReach(root);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// canvas.ts declares itself "the one place that reads/writes the node+edge
|
|
2
|
+
// model" for the runtime layer (spawn, push, lifecycle, daemon) that builds on
|
|
3
|
+
// it — every node/edge write from `runtime/` goes through a named canvas.ts
|
|
4
|
+
// accessor, never a raw SQL statement of its own. This grep-enforces that
|
|
5
|
+
// boundary at the one layer the convention actually claims: `core/runtime/`.
|
|
6
|
+
// (Other canvas.db tables — reviews, review_comments — are a SEPARATE
|
|
7
|
+
// subsystem's tables with their own owner in `core/review/`, and `feed.ts`'s
|
|
8
|
+
// `commitCanonicalFinal` is the one documented sanctioned exception outside
|
|
9
|
+
// `runtime/` entirely — see `core/canvas/.crouter/memory/INDEX.md` — so
|
|
10
|
+
// neither is in scope here.)
|
|
11
|
+
import { test } from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
14
|
+
import { join, resolve } from 'node:path';
|
|
15
|
+
const RUNTIME_ROOT = resolve(process.cwd(), 'src/core/runtime');
|
|
16
|
+
function tsFilesUnder(dir) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const entry of readdirSync(dir)) {
|
|
19
|
+
const path = join(dir, entry);
|
|
20
|
+
const stat = statSync(path);
|
|
21
|
+
if (stat.isDirectory()) {
|
|
22
|
+
if (entry === '__tests__')
|
|
23
|
+
continue;
|
|
24
|
+
out.push(...tsFilesUnder(path));
|
|
25
|
+
}
|
|
26
|
+
else if (entry.endsWith('.ts') && !entry.endsWith('.test.ts')) {
|
|
27
|
+
out.push(path);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
test('no file under core/runtime/ prepares a raw SQL statement — canvas.ts is the one node+edge DAO', () => {
|
|
33
|
+
const offenders = [];
|
|
34
|
+
for (const file of tsFilesUnder(RUNTIME_ROOT)) {
|
|
35
|
+
const source = readFileSync(file, 'utf8');
|
|
36
|
+
const lines = source.split('\n');
|
|
37
|
+
lines.forEach((line, index) => {
|
|
38
|
+
// Skip comment lines referencing `.prepare(` in prose (backtick-quoted).
|
|
39
|
+
if (/`\.prepare\(`/.test(line))
|
|
40
|
+
return;
|
|
41
|
+
if (line.includes('.prepare('))
|
|
42
|
+
offenders.push(`${file}:${index + 1}`);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
assert.deepEqual(offenders, [], `raw .prepare( call(s) outside core/canvas/:\n${offenders.join('\n')}`);
|
|
46
|
+
});
|
|
@@ -118,7 +118,7 @@ test('recordSpawn writes the audit-only spawned_by edge', () => {
|
|
|
118
118
|
assert.equal(subscriptionsOf('child').length, 0);
|
|
119
119
|
assert.equal(subscribersOf('parent').length, 0);
|
|
120
120
|
});
|
|
121
|
-
//
|
|
121
|
+
// Durability contract: meta.json is durable and never truncated
|
|
122
122
|
// writeMeta uses atomic rename with fsync on both the temp file and parent
|
|
123
123
|
// directory to ensure atomicity and durability. readMeta/rebuildIndex tolerate
|
|
124
124
|
// an empty/corrupt meta.json as MISSING (returning null) rather than throwing,
|
|
@@ -110,7 +110,6 @@ after(() => {
|
|
|
110
110
|
delete process.env['CRTR_HOME'];
|
|
111
111
|
resetFleetForTests();
|
|
112
112
|
});
|
|
113
|
-
// ===========================================================================
|
|
114
113
|
// BROKER CUT — exit-event authority REPLACES daemon pane-gone reaping. A booted
|
|
115
114
|
// child whose engine pid is dead (and whose viewer pane is gone) is REVIVABLE,
|
|
116
115
|
// not reaped, and the daemon's tick fans NO wake to the dormant parent. This
|
|
@@ -120,7 +119,6 @@ after(() => {
|
|
|
120
119
|
// node recovery rides real exit events), the daemon treats them all identically
|
|
121
120
|
// — no reap, no liveness wake — so the old CRUX (no spurious wake on healthy
|
|
122
121
|
// dormancy) holds by construction.
|
|
123
|
-
// ===========================================================================
|
|
124
122
|
test('exit-event authority: a dead-engine booted child is revivable, NOT reaped, and the tick fans NO wake to its dormant parent', async () => {
|
|
125
123
|
inboxWaitingParent('PARENT');
|
|
126
124
|
deadEngineChild('CHILD');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Run with: npm test -- --test-name-pattern='armed'
|
|
2
|
+
//
|
|
3
|
+
// A cron run executes under the context it was ARMED under: the `--env`
|
|
4
|
+
// snapshot and the profile stored on the row. When that stored context cannot
|
|
5
|
+
// be rebuilt, the run must FAIL LOUDLY rather than execute the command under a
|
|
6
|
+
// context nobody armed — the class of defect that otherwise runs wrong, every
|
|
7
|
+
// tick, forever, with a clean run log.
|
|
8
|
+
import { after, afterEach, before, beforeEach, test } from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { existsSync, mkdtempSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { listNodes } from '../canvas/canvas.js';
|
|
14
|
+
import { armCron, getCron, listCronRuns } from '../canvas/crons.js';
|
|
15
|
+
import { closeDb } from '../canvas/db.js';
|
|
16
|
+
import { createProfile, profileManifestPath, profileRoot } from '../profiles/manifest.js';
|
|
17
|
+
import { resetScopeCache } from '../scope.js';
|
|
18
|
+
import { headlessBrokerHost } from '../runtime/host.js';
|
|
19
|
+
import { resetFleetForTests } from '../runtime/fleet.js';
|
|
20
|
+
import { executeCron } from '../../daemon/cron-run.js';
|
|
21
|
+
import { bindTestFleet } from './helpers/fleet.js';
|
|
22
|
+
let userHome;
|
|
23
|
+
let canvasHome;
|
|
24
|
+
let priorHome;
|
|
25
|
+
let priorCanvasHome;
|
|
26
|
+
let fleet;
|
|
27
|
+
let originalLaunch;
|
|
28
|
+
/** The marker a run writes if the command actually executed. */
|
|
29
|
+
function marker(id) {
|
|
30
|
+
return join(canvasHome, `${id}.ran`);
|
|
31
|
+
}
|
|
32
|
+
function arm({ cron_id, ...overrides }) {
|
|
33
|
+
armCron({
|
|
34
|
+
cron_id,
|
|
35
|
+
name: cron_id,
|
|
36
|
+
created_by: null,
|
|
37
|
+
// Records the profile the command actually ran under, so a root fallback is
|
|
38
|
+
// observable rather than inferred.
|
|
39
|
+
command: `printf "%s" "$CRTR_PROFILE_ID" > ${marker(cron_id)}`,
|
|
40
|
+
fire_at: new Date(Date.now() - 1_000).toISOString(),
|
|
41
|
+
recur: null, // a one-shot: retention is observable
|
|
42
|
+
tz: null,
|
|
43
|
+
expires_at: null,
|
|
44
|
+
anchor_node: null,
|
|
45
|
+
cancel_on_wake: false,
|
|
46
|
+
cwd: canvasHome,
|
|
47
|
+
env_json: null,
|
|
48
|
+
profile: null,
|
|
49
|
+
scope: 'profile',
|
|
50
|
+
run_timeout_s: 30,
|
|
51
|
+
overlap: 'skip',
|
|
52
|
+
on_output: 'on-failure',
|
|
53
|
+
sink: '{}',
|
|
54
|
+
tier: 'normal',
|
|
55
|
+
...overrides,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
before(() => {
|
|
59
|
+
// realpath: the profile store persists realpath-resolved paths, and macOS
|
|
60
|
+
// hands back a symlinked /var/folders prefix.
|
|
61
|
+
userHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-cron-armed-user-')));
|
|
62
|
+
canvasHome = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-cron-armed-canvas-')));
|
|
63
|
+
priorHome = process.env['HOME'];
|
|
64
|
+
priorCanvasHome = process.env['CRTR_HOME'];
|
|
65
|
+
process.env['HOME'] = userHome; // the profile store lives under the USER root
|
|
66
|
+
process.env['CRTR_HOME'] = canvasHome;
|
|
67
|
+
resetScopeCache();
|
|
68
|
+
});
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
closeDb();
|
|
71
|
+
rmSync(canvasHome, { recursive: true, force: true });
|
|
72
|
+
mkdirSync(canvasHome, { recursive: true });
|
|
73
|
+
resetFleetForTests();
|
|
74
|
+
fleet = bindTestFleet();
|
|
75
|
+
originalLaunch = headlessBrokerHost.launch;
|
|
76
|
+
headlessBrokerHost.launch = ((nodeId) => {
|
|
77
|
+
const handle = { pid: 0x7ffffffe, exited: new Promise(() => { }) };
|
|
78
|
+
fleet.fleet.register(nodeId, handle);
|
|
79
|
+
return handle;
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
headlessBrokerHost.launch = originalLaunch;
|
|
84
|
+
closeDb();
|
|
85
|
+
resetFleetForTests();
|
|
86
|
+
});
|
|
87
|
+
after(() => {
|
|
88
|
+
rmSync(canvasHome, { recursive: true, force: true });
|
|
89
|
+
rmSync(userHome, { recursive: true, force: true });
|
|
90
|
+
if (priorHome === undefined)
|
|
91
|
+
delete process.env['HOME'];
|
|
92
|
+
else
|
|
93
|
+
process.env['HOME'] = priorHome;
|
|
94
|
+
if (priorCanvasHome === undefined)
|
|
95
|
+
delete process.env['CRTR_HOME'];
|
|
96
|
+
else
|
|
97
|
+
process.env['CRTR_HOME'] = priorCanvasHome;
|
|
98
|
+
resetScopeCache();
|
|
99
|
+
});
|
|
100
|
+
/** Every way an armed context fails settles identically: no child, a record
|
|
101
|
+
* naming the cause, a paused row that keeps its one-shot, and one escalation
|
|
102
|
+
* node. */
|
|
103
|
+
function assertSettledAsArmingFailure(id, record, cause, nodesBefore) {
|
|
104
|
+
assert.equal(existsSync(marker(id)), false, `${id}: the command never ran`);
|
|
105
|
+
assert.equal(record?.exit_code, null, `${id}: no process existed to exit`);
|
|
106
|
+
assert.match(record?.stderr_head ?? '', cause, `${id}: the record names the cause`);
|
|
107
|
+
assert.match(record?.delivered ?? '', /^failure: cron paused, escalation node /, `${id}: the failure is escalated`);
|
|
108
|
+
assert.equal(listNodes().length, nodesBefore + 1, `${id}: exactly one escalation node was created`);
|
|
109
|
+
const row = getCron(id);
|
|
110
|
+
assert.notEqual(row, null, `${id}: the one-shot is retained, not consumed`);
|
|
111
|
+
assert.equal(row.state, 'paused', `${id}: the row pauses, so it does not refire every tick`);
|
|
112
|
+
assert.equal(row.run_state, 'idle', `${id}: no lease was ever taken`);
|
|
113
|
+
assert.deepEqual(listCronRuns(id).map((r) => r.run_id), [record.run_id], `${id}: the failure left exactly one record`);
|
|
114
|
+
}
|
|
115
|
+
test('a corrupt armed env snapshot fails the run instead of silently dropping the operator variables', async () => {
|
|
116
|
+
// Both shapes the arm-time validator rejects, so neither can reach a row
|
|
117
|
+
// through the supported path: unparseable JSON, and a non-string value.
|
|
118
|
+
const corrupt = [
|
|
119
|
+
['env-unparseable', '{"API_BASE": '],
|
|
120
|
+
['env-nonstring', '{"API_BASE": 42}'],
|
|
121
|
+
];
|
|
122
|
+
for (const [id, envJson] of corrupt) {
|
|
123
|
+
arm({ cron_id: id, env_json: envJson });
|
|
124
|
+
const nodesBefore = listNodes().length;
|
|
125
|
+
const record = await executeCron(getCron(id), { outOfBand: false });
|
|
126
|
+
assertSettledAsArmingFailure(id, record, /run could not be armed: the armed env snapshot/, nodesBefore);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
test('an unreadable profile manifest fails the run, while a deleted profile still runs as root', async () => {
|
|
130
|
+
const unreadable = createProfile('cron-unreadable').profileId;
|
|
131
|
+
const deleted = createProfile('cron-deleted').profileId;
|
|
132
|
+
// Corrupt one manifest in place and delete the other profile outright: the
|
|
133
|
+
// two states the old resolver could not tell apart.
|
|
134
|
+
writeFileSync(profileManifestPath(unreadable), '{ not json', 'utf8');
|
|
135
|
+
rmSync(profileRoot(deleted), { recursive: true, force: true });
|
|
136
|
+
arm({ cron_id: 'profile-unreadable', profile: unreadable });
|
|
137
|
+
const nodesBefore = listNodes().length;
|
|
138
|
+
const failed = await executeCron(getCron('profile-unreadable'), { outOfBand: false });
|
|
139
|
+
assertSettledAsArmingFailure('profile-unreadable', failed, /run could not be armed: the armed profile .* manifest could not be read/, nodesBefore);
|
|
140
|
+
arm({ cron_id: 'profile-deleted', profile: deleted });
|
|
141
|
+
const ran = await executeCron(getCron('profile-deleted'), { outOfBand: false });
|
|
142
|
+
assert.equal(ran?.exit_code, 0, 'a deleted profile is the spec root fallback — the run proceeds');
|
|
143
|
+
assert.equal(readFileSync(marker('profile-deleted'), 'utf8'), '', 'the run carries no profile id: it ran as root');
|
|
144
|
+
assert.equal(getCron('profile-deleted'), null, 'an ordinary settled one-shot is consumed');
|
|
145
|
+
});
|
|
@@ -37,12 +37,10 @@ test('wedgeVerdict: fires ONLY when busy + quiet-past-grace + near-zero cpu all
|
|
|
37
37
|
assert.equal(wedgeVerdict({ busy: true, quietForMs: WEDGE_QUIET_MS, cpuPercent: 0 }), 'wedged');
|
|
38
38
|
assert.equal(wedgeVerdict({ busy: true, quietForMs: WEDGE_QUIET_MS + 60_000, cpuPercent: WEDGE_CPU_MAX_PERCENT }), 'wedged', 'at the cpu ceiling (inclusive)');
|
|
39
39
|
});
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
40
|
// sumTreeCpu (pure) — process-tree CPU sampling. Review fix for issue #110:
|
|
42
41
|
// sampling only the broker pid false-fired on a broker awaiting a busy CHILD
|
|
43
42
|
// subprocess. These lock the tree-sum behavior directly, independent of any
|
|
44
43
|
// `ps` shell-out.
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
44
|
test('sumTreeCpu: a busy CHILD subprocess exonerates an idle broker (the false-positive this fixes)', () => {
|
|
47
45
|
const ps = [
|
|
48
46
|
' 100 1 0.0', // broker, idle
|
|
@@ -85,11 +83,9 @@ test('sumTreeCpu: malformed/blank lines are skipped; empty output returns null',
|
|
|
85
83
|
assert.equal(sumTreeCpu('', 100), null);
|
|
86
84
|
assert.equal(sumTreeCpu(' \n \n', 100), null);
|
|
87
85
|
});
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
86
|
// treeHasDescendants (pure) — issue #119: the broker-alone case has no
|
|
90
87
|
// subprocess to kill, so remediation must branch on this instead of assuming
|
|
91
88
|
// a runaway child always exists.
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
89
|
test('treeHasDescendants: broker alone in its own tree — nothing to kill', () => {
|
|
94
90
|
const ps = [' 100 1 0.0'].join('\n');
|
|
95
91
|
assert.equal(treeHasDescendants(ps, 100), false);
|
|
@@ -20,9 +20,7 @@ function row(over = {}) {
|
|
|
20
20
|
...over,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
// ===========================================================================
|
|
24
23
|
// The eight table rows, in evaluation order (design flow B).
|
|
25
|
-
// ===========================================================================
|
|
26
24
|
test('row 1: a deleted row (orphan of a pruned/closed node) is forgotten', () => {
|
|
27
25
|
assert.equal(classifyDeadNode(null, false), 'forget');
|
|
28
26
|
assert.equal(classifyDeadNode(null, true), 'forget', 'a stale busy marker cannot resurrect a deleted row');
|
|
@@ -68,11 +66,9 @@ test('row 7: an ordinary fork dead before binding its child session retries the
|
|
|
68
66
|
test('row 8: sessionless with no fork claim — a fresh launch died before session_start — is a boot failure', () => {
|
|
69
67
|
assert.equal(classifyDeadNode(row(), false), 'boot-failure');
|
|
70
68
|
});
|
|
71
|
-
// ===========================================================================
|
|
72
69
|
// retryResumeMode (pure): only an unconfirmed cycling launch must cycle again.
|
|
73
70
|
// An interrupted ordinary turn resumes strictly; `#respawn` adds the runtime
|
|
74
71
|
// continuation that re-drives the stopped work in the same conversation.
|
|
75
|
-
// ===========================================================================
|
|
76
72
|
test('retryResumeMode: pending cycles recover as cycles; every confirmed saved session resumes strictly', () => {
|
|
77
73
|
assert.equal(retryResumeMode({ cycle_pending: true }), false, 'a pending cycling attempt retries AS a cycle');
|
|
78
74
|
assert.equal(retryResumeMode({ cycle_pending: false }), true, 'an ordinary saved session resumes strictly');
|