@north-light/crouter 0.3.232 → 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-memory/internal/memory-loading.md +4 -3
- 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.d.ts +1 -1
- package/dist/commands/memory/shared.js +7 -10
- 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.d.ts +1 -0
- package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +92 -0
- package/dist/core/substrate/frontmatter-validation.js +1 -1
- package/dist/core/substrate/injected-store.d.ts +6 -0
- package/dist/core/substrate/injected-store.js +24 -0
- package/dist/core/substrate/on-read.d.ts +17 -1
- package/dist/core/substrate/on-read.js +36 -2
- package/dist/core/substrate/render.js +0 -12
- package/dist/core/substrate/schema.d.ts +3 -3
- package/dist/core/substrate/schema.js +4 -16
- package/dist/core/substrate/surface-match.d.ts +19 -0
- package/dist/core/substrate/surface-match.js +60 -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/__tests__/pre-command-gate.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/pre-command-gate.test.js +220 -0
- 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.d.ts +14 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +77 -5
- 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
|
@@ -9,21 +9,17 @@
|
|
|
9
9
|
// render, and `crtr memory lint` owns strict authoring-time enforcement.
|
|
10
10
|
import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
|
|
11
11
|
import { surfaceEntryGatePasses } from './gate.js';
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
12
|
// Kinds — the two semantic kinds (design §3): `knowledge` (consult — procedural
|
|
14
13
|
// playbooks + factual references merged) vs `preference` (behave — standing
|
|
15
14
|
// directives). `kind` is a load-bearing binary, not a content taxonomy.
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
15
|
export const KINDS = ['knowledge', 'preference'];
|
|
18
16
|
/** Is `v` one of the two valid document kinds? */
|
|
19
17
|
export function isDocKind(v) {
|
|
20
18
|
return typeof v === 'string' && KINDS.includes(v);
|
|
21
19
|
}
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
20
|
// The 4-rung visibility ladder (design §4). A strict, monotone ordering where
|
|
24
21
|
// each rung is a superset of disclosure over the one before:
|
|
25
22
|
// none < name < preview < content
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
23
|
// The ladder is ONE union, owned by the public API DTO (`ProfileProjectMemory`)
|
|
28
24
|
// so a profile relationship's cap and a doc's authored rung are the same values
|
|
29
25
|
// and clamp against each other without a conversion.
|
|
@@ -41,22 +37,18 @@ export function rungAtLeast(r, min) {
|
|
|
41
37
|
export function minRung(a, b) {
|
|
42
38
|
return rungRank(a) <= rungRank(b) ? a : b;
|
|
43
39
|
}
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
40
|
// Identity — owned entirely by core/memory/identity.ts (ordering-prefix
|
|
46
41
|
// normalization, INDEX collapse, explicit-name-then-path local identity,
|
|
47
42
|
// namespace composition). Re-exported here so document parsing and identity
|
|
48
43
|
// derivation stay one rule with one implementation.
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
44
|
export { normalizeNameSegment, normalizeDocName, resolveLocalName as resolveDocName } from '../memory/identity.js';
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
45
|
// Surfaces — explicit event routing (design: surfaces-design.md). A doc
|
|
53
46
|
// declares WHEN it delivers as (event, match, rung) entries; matches are
|
|
54
47
|
// always globs, always absolute in the event's namespace, with `./` as the
|
|
55
48
|
// one relative spelling (anchoring to the doc's own container). A doc with
|
|
56
49
|
// no `surfaces` at all does exactly one thing: appears in its directory's
|
|
57
50
|
// listing.
|
|
58
|
-
|
|
59
|
-
export const SURFACE_EVENTS = ['boot', 'workspace-open', 'read', 'memory-read', 'command'];
|
|
51
|
+
export const SURFACE_EVENTS = ['boot', 'workspace-open', 'read', 'memory-read', 'command', 'pre-command'];
|
|
60
52
|
/** The rungs an entry may deliver at. There is no `none` — silence is the
|
|
61
53
|
* absence of an entry. (`Rung`'s `none` survives only as the internal
|
|
62
54
|
* ranking floor, e.g. `bootRung` of a doc with no boot entry.) */
|
|
@@ -74,9 +66,7 @@ export function bootRung(doc, subject) {
|
|
|
74
66
|
}
|
|
75
67
|
return r;
|
|
76
68
|
}
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
69
|
// Parse / validate.
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
70
|
/** Parse a raw frontmatter record (from `parseFrontmatterGeneric`, via the
|
|
81
71
|
* resolver) into a typed schema with defaults applied. Returns `null` when the
|
|
82
72
|
* record is absent or carries no valid `kind` — i.e. it is not a substrate
|
|
@@ -135,9 +125,7 @@ export function previewLine(doc) {
|
|
|
135
125
|
const line = doc.whenAndWhyToRead.trim().replace(/\.+$/, '');
|
|
136
126
|
return line === '' ? '' : `${line}.`;
|
|
137
127
|
}
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
128
|
// Field coercion helpers (private).
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
129
|
function strField(v) {
|
|
142
130
|
return typeof v === 'string' ? v : '';
|
|
143
131
|
}
|
|
@@ -158,8 +146,8 @@ function parseGate(v) {
|
|
|
158
146
|
: undefined;
|
|
159
147
|
}
|
|
160
148
|
/** Tolerant `surfaces` parse: a non-array yields `[]`; invalid entries are
|
|
161
|
-
* dropped (bad `on`, bad `at`, a read/memory-read/command entry
|
|
162
|
-
* neither `match` nor — read only — `match-frontmatter`). `match-frontmatter`
|
|
149
|
+
* dropped (bad `on`, bad `at`, a read/memory-read/command/pre-command entry
|
|
150
|
+
* left with neither `match` nor — read only — `match-frontmatter`). `match-frontmatter`
|
|
163
151
|
* on a non-read event is dropped from the entry, which survives iff it still
|
|
164
152
|
* carries a `match`; an invalid entry `gate` is dropped. Lint owns strict
|
|
165
153
|
* enforcement; the runtime parser maps over many docs and must never throw. */
|
|
@@ -180,7 +168,7 @@ function parseSurfaces(v) {
|
|
|
180
168
|
const match = parseMatchGlobs(rec['match']);
|
|
181
169
|
const matchFrontmatter = on === 'read' ? parseGate(rec['match-frontmatter']) : undefined;
|
|
182
170
|
const gate = parseGate(rec['gate']);
|
|
183
|
-
if ((on === 'read' || on === 'memory-read' || on === 'command') &&
|
|
171
|
+
if ((on === 'read' || on === 'memory-read' || on === 'command' || on === 'pre-command') &&
|
|
184
172
|
match === undefined &&
|
|
185
173
|
matchFrontmatter === undefined) {
|
|
186
174
|
continue;
|
|
@@ -20,6 +20,21 @@ export declare function matchesReadEntry(entry: SurfaceEntry, doc: Pick<Substrat
|
|
|
20
20
|
export declare function matchesMemoryReadEntry(entry: SurfaceEntry, routingAnchor: string, subject: NodeConfigSubject | null, name: string): boolean;
|
|
21
21
|
/** Does a `command` entry fit the executed command string? */
|
|
22
22
|
export declare function matchesCommandEntry(entry: SurfaceEntry, subject: NodeConfigSubject, command: string): boolean;
|
|
23
|
+
/** Remove heredoc BODIES (the stdin data between `<<DELIM` and its closing
|
|
24
|
+
* `DELIM` line) from a command before it is matched. The opening line is kept
|
|
25
|
+
* — `crtr push final <<'EOF'` is still a real `push final` invocation — but the
|
|
26
|
+
* body lines, which are data fed on stdin and never executed as commands, are
|
|
27
|
+
* dropped. Without this a doc-writing command whose body merely MENTIONS a
|
|
28
|
+
* matched invocation would have its whole call held. Post-execution `command`
|
|
29
|
+
* delivery does not need this (a spurious late injection costs a paragraph);
|
|
30
|
+
* a `pre-command` block costs the agent a turn, so it does. */
|
|
31
|
+
export declare function stripHeredocs(command: string): string;
|
|
32
|
+
/** Does a `pre-command` entry fit a command that is ABOUT to run? The same
|
|
33
|
+
* glob machinery `command` uses — one matcher, one semantics — applied to the
|
|
34
|
+
* command with its heredoc bodies stripped. Subshell parentheses, backticks,
|
|
35
|
+
* and path-prefixed binaries stay unhandled: this is a guardrail against the
|
|
36
|
+
* faithful-but-uninformed action, not an enforcement boundary. */
|
|
37
|
+
export declare function matchesPreCommandEntry(entry: SurfaceEntry, subject: NodeConfigSubject, command: string): boolean;
|
|
23
38
|
/** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
|
|
24
39
|
export declare function readDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'path' | 'scope'>, subject: NodeConfigSubject, absReadFile: string, readFrontmatter: Record<string, unknown>): Rung;
|
|
25
40
|
/** The doc's workspace-open rung. Presence of an entry is the match; callers
|
|
@@ -33,3 +48,7 @@ export declare function workspaceOpenRung(doc: Pick<SubstrateDoc, 'surfaces'>, s
|
|
|
33
48
|
export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, routingAnchor: string, subject: NodeConfigSubject | null, name: string): Rung;
|
|
34
49
|
/** The doc's delivery rung for an executed command string. */
|
|
35
50
|
export declare function commandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject, command: string): Rung;
|
|
51
|
+
/** The doc's delivery rung for a command about to run — the highest `at`
|
|
52
|
+
* over the doc's matching `pre-command` entries, folded the same way as every
|
|
53
|
+
* other event. */
|
|
54
|
+
export declare function preCommandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject, command: string): Rung;
|
|
@@ -276,12 +276,57 @@ export function matchesCommandEntry(entry, subject, command) {
|
|
|
276
276
|
return false;
|
|
277
277
|
return entry.match.some((g) => commandGlobMatches(g, command));
|
|
278
278
|
}
|
|
279
|
-
|
|
279
|
+
const HEREDOC_OPEN = /<<(-?)\s*(["']?)([A-Za-z_][A-Za-z0-9_]*)\2/g;
|
|
280
|
+
/** Remove heredoc BODIES (the stdin data between `<<DELIM` and its closing
|
|
281
|
+
* `DELIM` line) from a command before it is matched. The opening line is kept
|
|
282
|
+
* — `crtr push final <<'EOF'` is still a real `push final` invocation — but the
|
|
283
|
+
* body lines, which are data fed on stdin and never executed as commands, are
|
|
284
|
+
* dropped. Without this a doc-writing command whose body merely MENTIONS a
|
|
285
|
+
* matched invocation would have its whole call held. Post-execution `command`
|
|
286
|
+
* delivery does not need this (a spurious late injection costs a paragraph);
|
|
287
|
+
* a `pre-command` block costs the agent a turn, so it does. */
|
|
288
|
+
export function stripHeredocs(command) {
|
|
289
|
+
const lines = command.split('\n');
|
|
290
|
+
const out = [];
|
|
291
|
+
let i = 0;
|
|
292
|
+
while (i < lines.length) {
|
|
293
|
+
const line = lines[i];
|
|
294
|
+
out.push(line);
|
|
295
|
+
i += 1;
|
|
296
|
+
// Every heredoc opened on this line, left to right; their bodies stack and
|
|
297
|
+
// are consumed in that order.
|
|
298
|
+
const delims = [];
|
|
299
|
+
HEREDOC_OPEN.lastIndex = 0;
|
|
300
|
+
let m;
|
|
301
|
+
while ((m = HEREDOC_OPEN.exec(line)) !== null)
|
|
302
|
+
delims.push({ name: m[3], dash: m[1] === '-' });
|
|
303
|
+
for (const d of delims) {
|
|
304
|
+
while (i < lines.length) {
|
|
305
|
+
// `<<-` strips leading TABS from the body and the closing delimiter.
|
|
306
|
+
const probe = d.dash ? lines[i].replace(/^\t+/, '') : lines[i];
|
|
307
|
+
i += 1;
|
|
308
|
+
if (probe === d.name)
|
|
309
|
+
break; // closing delimiter line — drop it, stop
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return out.join('\n');
|
|
314
|
+
}
|
|
315
|
+
/** Does a `pre-command` entry fit a command that is ABOUT to run? The same
|
|
316
|
+
* glob machinery `command` uses — one matcher, one semantics — applied to the
|
|
317
|
+
* command with its heredoc bodies stripped. Subshell parentheses, backticks,
|
|
318
|
+
* and path-prefixed binaries stay unhandled: this is a guardrail against the
|
|
319
|
+
* faithful-but-uninformed action, not an enforcement boundary. */
|
|
320
|
+
export function matchesPreCommandEntry(entry, subject, command) {
|
|
321
|
+
if (entry.on !== 'pre-command' || entry.match === undefined || !surfaceEntryGatePasses(entry, subject))
|
|
322
|
+
return false;
|
|
323
|
+
const stripped = stripHeredocs(command);
|
|
324
|
+
return entry.match.some((g) => commandGlobMatches(g, stripped));
|
|
325
|
+
}
|
|
280
326
|
// Per-doc rung folds — a doc's delivery rung for one event occurrence is the
|
|
281
327
|
// highest `at` over its matching entries of that event, `none` when none
|
|
282
328
|
// match (entries OR together; `none` is the internal "does not deliver"
|
|
283
329
|
// floor, never an authored rung).
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
330
|
/** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
|
|
286
331
|
export function readDeliveryRung(doc, subject, absReadFile, readFrontmatter) {
|
|
287
332
|
let r = 'none';
|
|
@@ -331,3 +376,16 @@ export function commandDeliveryRung(doc, subject, command) {
|
|
|
331
376
|
}
|
|
332
377
|
return r;
|
|
333
378
|
}
|
|
379
|
+
/** The doc's delivery rung for a command about to run — the highest `at`
|
|
380
|
+
* over the doc's matching `pre-command` entries, folded the same way as every
|
|
381
|
+
* other event. */
|
|
382
|
+
export function preCommandDeliveryRung(doc, subject, command) {
|
|
383
|
+
let r = 'none';
|
|
384
|
+
for (const e of doc.surfaces) {
|
|
385
|
+
if (!matchesPreCommandEntry(e, subject, command))
|
|
386
|
+
continue;
|
|
387
|
+
if (rungRank(e.at) > rungRank(r))
|
|
388
|
+
r = e.at;
|
|
389
|
+
}
|
|
390
|
+
return r;
|
|
391
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { envNodeId } from '../../shared/env.js';
|
|
1
2
|
import { spawnSync } from 'node:child_process';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { breakPane, closePane, currentTmux, paneOfNode, splitWindow, } from '../runtime/placement-tmux.js';
|
|
@@ -6,7 +7,7 @@ function targetPane() {
|
|
|
6
7
|
const callerPane = process.env['TMUX_PANE'];
|
|
7
8
|
if (callerPane !== undefined && callerPane !== '')
|
|
8
9
|
return callerPane;
|
|
9
|
-
const nodeId =
|
|
10
|
+
const nodeId = envNodeId();
|
|
10
11
|
if (nodeId !== undefined && nodeId !== '') {
|
|
11
12
|
const viewerPane = paneOfNode(nodeId);
|
|
12
13
|
if (viewerPane !== null)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ProcessTerminal, type Component, type Focusable, type TUI } from
|
|
2
|
-
import { readRawScopeConfig } from
|
|
3
|
-
import { type BindingId, type BindingResolution } from
|
|
1
|
+
import { ProcessTerminal, type Component, type Focusable, type TUI } from '@earendil-works/pi-tui';
|
|
2
|
+
import { readRawScopeConfig } from '../config.js';
|
|
3
|
+
import { type BindingId, type BindingResolution } from '../keybindings/index.js';
|
|
4
4
|
/** A full-screen page: a focusable TUI component that cleans up after itself. */
|
|
5
5
|
export type TuiPageSurface = Component & Focusable & {
|
|
6
6
|
dispose(): void;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
// restore path that survives an exception, a signal, and process exit. Anything
|
|
7
7
|
// a single page needs beyond that — pi's model runtime, a canvas client — the
|
|
8
8
|
// page's own runner builds and closes over before calling in here.
|
|
9
|
-
import { ProcessTerminal, TuiMainScreen, } from
|
|
10
|
-
import { applyTheme } from
|
|
11
|
-
import { readRawScopeConfig } from
|
|
12
|
-
import { resolveKeybindings, } from
|
|
9
|
+
import { ProcessTerminal, TuiMainScreen, } from '@earendil-works/pi-tui';
|
|
10
|
+
import { applyTheme } from '../../clients/attach/config.js';
|
|
11
|
+
import { readRawScopeConfig } from '../config.js';
|
|
12
|
+
import { resolveKeybindings, } from '../keybindings/index.js';
|
|
13
13
|
/** Mount one page as the TUI's only child and resolve when it closes.
|
|
14
14
|
*
|
|
15
15
|
* `interceptCancel` is a process-level escape hatch for surfaces that must be
|
package/dist/core/tui/panel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getMarkdownTheme, getSelectListTheme, getSettingsListTheme, } from
|
|
2
|
-
import { truncateToWidth, visibleWidth } from
|
|
1
|
+
import { getMarkdownTheme, getSelectListTheme, getSettingsListTheme, } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
3
3
|
const ansiGreen = (text) => `\x1b[32m${text}\x1b[39m`;
|
|
4
4
|
const ansiYellow = (text) => `\x1b[33m${text}\x1b[39m`;
|
|
5
5
|
export const theme = {
|
|
@@ -95,7 +95,7 @@ export function framed(lines, width) {
|
|
|
95
95
|
export function fitTail(lines, budget) {
|
|
96
96
|
return budget >= lines.length ? lines : lines.slice(lines.length - budget);
|
|
97
97
|
}
|
|
98
|
-
//
|
|
98
|
+
// Page chrome shared by the settings shell and the setup wizard
|
|
99
99
|
/** Rows the page frame spends on its own title, rules and footer. */
|
|
100
100
|
export const PAGE_CHROME_ROWS = 4;
|
|
101
101
|
/** Title row with right-aligned scope metadata, a rule under it, the body
|
package/dist/core/wake.js
CHANGED
|
@@ -10,9 +10,7 @@
|
|
|
10
10
|
// design §5.3 + D7 (engine evaluates UTC; the surface bakes the IANA zone
|
|
11
11
|
// into a calendar `recur`). Tick-scale only — no sub-second precision.
|
|
12
12
|
import { CronExpressionParser } from 'cron-parser';
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
13
|
// Constants
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
14
|
/** Cadence floor: reject any interval / cron min-spacing below this (AC-N4). */
|
|
17
15
|
export const CADENCE_FLOOR_MS = 60_000;
|
|
18
16
|
const DURATION_UNIT_MS = {
|
|
@@ -38,9 +36,7 @@ const CRON_ALIASES = {
|
|
|
38
36
|
'@midnight': '0 0 * * *',
|
|
39
37
|
'@hourly': '0 * * * *',
|
|
40
38
|
};
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
39
|
// Internal helpers
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
40
|
function whenError(code, message, received) {
|
|
45
41
|
return { error: { code, message, received } };
|
|
46
42
|
}
|
|
@@ -127,9 +123,7 @@ function classifyIso(s) {
|
|
|
127
123
|
return { zoned: true };
|
|
128
124
|
return null; // trailing garbage -> not a valid ISO instant
|
|
129
125
|
}
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
126
|
// parseWhen — resolve a one-shot <when> to an absolute UTC instant
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
127
|
/**
|
|
134
128
|
* Resolve a `<when>` to a single UTC `fireAt`:
|
|
135
129
|
* - relative duration (`90s`, `1h30m`) -> now + Σ
|
|
@@ -182,9 +176,7 @@ export function parseWhen(when, opts) {
|
|
|
182
176
|
}
|
|
183
177
|
return whenError('bad_when', `Could not parse "${received}". Use a duration ("5m", "1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00").`, received);
|
|
184
178
|
}
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
179
|
// parseCadence — resolve --every to a stored recur + first fire
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
180
|
/**
|
|
189
181
|
* Resolve a `<cadence>` (the `--every` value) to a stored `recur` JSON string
|
|
190
182
|
* plus the first `fireAt`:
|
|
@@ -240,9 +232,7 @@ export function parseCadence(every, opts) {
|
|
|
240
232
|
firstFireAt,
|
|
241
233
|
};
|
|
242
234
|
}
|
|
243
|
-
// ---------------------------------------------------------------------------
|
|
244
235
|
// cadenceDisplay — a recur JSON rendered for humans + agents
|
|
245
|
-
// ---------------------------------------------------------------------------
|
|
246
236
|
/**
|
|
247
237
|
* Render a stored `recur` JSON as a compact, human-readable cadence:
|
|
248
238
|
* - {"every":"6h"} -> `every 6h`
|
|
@@ -270,9 +260,7 @@ export function cadenceDisplay(recur) {
|
|
|
270
260
|
}
|
|
271
261
|
return 'none';
|
|
272
262
|
}
|
|
273
|
-
// ---------------------------------------------------------------------------
|
|
274
263
|
// nextSlotAfter — the coalescing primitive (daemon advance)
|
|
275
|
-
// ---------------------------------------------------------------------------
|
|
276
264
|
/**
|
|
277
265
|
* Earliest occurrence of `recur` strictly greater than `now`, as a UTC ISO
|
|
278
266
|
* string. Anchors on `now` (not the stored `fire_at`), so it structurally
|
package/dist/core/worktree.js
CHANGED
|
@@ -357,9 +357,7 @@ export function closeManagedWorktree(nodeId) {
|
|
|
357
357
|
return closeManagedWorktreeLocked(nodeId);
|
|
358
358
|
return withRepositoryWorktreeLock(wt.repo_root, () => closeManagedWorktreeLocked(nodeId));
|
|
359
359
|
}
|
|
360
|
-
// ---------------------------------------------------------------------------
|
|
361
360
|
// Push-final auto-drop of a clean, zero-commit managed worktree
|
|
362
|
-
// ---------------------------------------------------------------------------
|
|
363
361
|
/** Delete a source branch only while the ref that proved it already delivered
|
|
364
362
|
* is still at the exact inspected SHA. `containingRef` can be the recorded
|
|
365
363
|
* local base or that base's remote-tracking upstream. */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Every reconciler in crtrd's tick loop conforms to one shape:
|
|
2
|
+
// `run(now: number, ctx: TCtx): void | Promise<unknown>`. The call loop treats
|
|
3
|
+
// every reconciler identically — a 1-arg or discard-shaped signature is exactly
|
|
4
|
+
// the drift this test catches (a runtime arity check; a compile-time
|
|
5
|
+
// `satisfies` pass over the same five/seven classes would need a shared
|
|
6
|
+
// Reconciler<TCtx> interface that adds no real safety this doesn't already
|
|
7
|
+
// give, since `tsc --noEmit` already fails hard on a wrong-shaped ctx object
|
|
8
|
+
// at each call site in crtrd.ts).
|
|
9
|
+
import { test } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { BrokerSupervisionReconciler } from '../reconcilers/broker-supervision.js';
|
|
12
|
+
import { ControllerDeathReconciler, DormantInboxReconciler } from '../reconcilers/dormant-inbox.js';
|
|
13
|
+
import { CronLaneReconciler } from '../reconcilers/cron-lane.js';
|
|
14
|
+
import { HumanDeliveryLaneReconciler } from '../reconcilers/human-delivery-lane.js';
|
|
15
|
+
import { PendingReviewSubmitReconciler } from '../reconcilers/pending-review-submit.js';
|
|
16
|
+
import { StorageMaintenanceReconciler } from '../reconcilers/storage-maintenance.js';
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
const RECONCILERS = [
|
|
19
|
+
['BrokerSupervisionReconciler', BrokerSupervisionReconciler],
|
|
20
|
+
['ControllerDeathReconciler', ControllerDeathReconciler],
|
|
21
|
+
['DormantInboxReconciler', DormantInboxReconciler],
|
|
22
|
+
['CronLaneReconciler', CronLaneReconciler],
|
|
23
|
+
['HumanDeliveryLaneReconciler', HumanDeliveryLaneReconciler],
|
|
24
|
+
['PendingReviewSubmitReconciler', PendingReviewSubmitReconciler],
|
|
25
|
+
['StorageMaintenanceReconciler', StorageMaintenanceReconciler],
|
|
26
|
+
];
|
|
27
|
+
test('every crtrd reconciler declares run(now, ctx) — arity 2', () => {
|
|
28
|
+
const offenders = [];
|
|
29
|
+
for (const [name, Ctor] of RECONCILERS) {
|
|
30
|
+
const instance = new Ctor();
|
|
31
|
+
if (instance.run.length !== 2) {
|
|
32
|
+
offenders.push(`${name}.run has arity ${instance.run.length}, expected 2`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
assert.deepEqual(offenders, [], `reconciler(s) not shaped run(now, ctx):\n${offenders.join('\n')}`);
|
|
36
|
+
});
|
|
@@ -72,9 +72,7 @@ function countLines(path) {
|
|
|
72
72
|
return 0;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
75
|
// A1 — /healthz ok + socket mode 0600
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
76
|
test('A1: /healthz is ok and the unix socket is bound mode 0600', async () => {
|
|
79
77
|
const health = await client.healthz();
|
|
80
78
|
assert.equal(health.ok, true, 'GET /healthz → { ok: true }');
|
|
@@ -86,10 +84,8 @@ test('attention counts reads only the requested node slice', async () => {
|
|
|
86
84
|
const result = await client.attentionCounts([root, 'unknown-node']);
|
|
87
85
|
assert.deepEqual(result.counts, { [root]: 0, 'unknown-node': 0 });
|
|
88
86
|
});
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
87
|
// A2 — POST /nodes → live node (row + broker + recorded pid), GET shows active,
|
|
91
88
|
// view.sock exists
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
89
|
test('A2: POST /nodes boots a live broker; GET /nodes/{id} shows active + live pid + view.sock', async () => {
|
|
94
90
|
const root = h.spawnRoot('a2 root'); // in-process resident, pi_pid = harness pid (alive)
|
|
95
91
|
const detail = await client.createNode({ kind: 'developer', parent: root, prompt: 'a2 worker' });
|
|
@@ -103,31 +99,27 @@ test('A2: POST /nodes boots a live broker; GET /nodes/{id} shows active + live p
|
|
|
103
99
|
assert.ok(meta?.pi_pid != null && isPidAlive(meta.pi_pid), 'row records a live broker pid');
|
|
104
100
|
assert.ok(existsSync(h.brokerSock(id)), 'view.sock exists for the live broker');
|
|
105
101
|
});
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
102
|
// A3 — POST .../messages appends inbox.jsonl; POST .../reports {tier:'final'}
|
|
108
103
|
// writes the report, notifies subscribers, transitions the row
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
104
|
test('A3: messages append inbox.jsonl; final report writes + notifies subscriber + transitions row', async () => {
|
|
111
105
|
const A = h.spawnRoot('a3 mgr');
|
|
112
106
|
const B = (await client.createNode({ kind: 'developer', parent: A, prompt: 'a3 worker' })).node_id;
|
|
113
107
|
await h.awaitBoot(B);
|
|
114
|
-
//
|
|
108
|
+
// messages append inbox.jsonl (raw line count is cursor-independent)
|
|
115
109
|
const inboxFile = inboxPath(B);
|
|
116
110
|
const before = countLines(inboxFile);
|
|
117
111
|
const msg = await client.sendMessage(B, { body: 'hello inbox' });
|
|
118
112
|
assert.equal(msg.delivered, true, 'message reports delivered');
|
|
119
113
|
await h.waitFor(() => countLines(inboxFile) > before, { label: 'inbox.jsonl appended', timeoutMs: 10_000 });
|
|
120
|
-
//
|
|
114
|
+
// final report: file written + subscriber notified + row transitioned
|
|
121
115
|
const res = await client.pushReport(B, { tier: 'final', body: 'a3 done result' });
|
|
122
116
|
assert.ok(existsSync(res.report_path), `report file written at ${res.report_path}`);
|
|
123
117
|
assert.ok(res.notified.includes(A), `final push notifies the spawn-subscribed manager A (notified=${JSON.stringify(res.notified)})`);
|
|
124
118
|
assert.deepEqual(res.transitioned, { from: 'active', to: 'done' }, 'row transitioned active→done');
|
|
125
119
|
assert.equal(h.node(B)?.status, 'done', 'row status is done after final');
|
|
126
120
|
});
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
121
|
// A4 — WS attach reconstructs the same settled broker snapshot as a direct
|
|
129
122
|
// ViewSocketClient; ?revive=0 on a dormant node → 409 without launching a broker
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
123
|
test('A4a: WS attach welcome.snapshot is byte-identical to a direct ViewSocketClient snapshot', async () => {
|
|
132
124
|
const root = h.spawnRoot('a4 root');
|
|
133
125
|
const id = (await client.createNode({ kind: 'developer', parent: root, prompt: 'a4 worker' })).node_id;
|
|
@@ -229,9 +221,7 @@ test('A4b: ?revive=0 on a dormant node refuses 409 without launching a broker',
|
|
|
229
221
|
assert.equal(h.bootCount(dorm), bootsBefore, 'no broker was launched (boot count unchanged)');
|
|
230
222
|
assert.equal(h.node(dorm)?.pi_pid ?? null, null, 'no pid recorded — the node stayed dormant');
|
|
231
223
|
});
|
|
232
|
-
// ---------------------------------------------------------------------------
|
|
233
224
|
// A5 — POST .../close tears down the broker + cascades per edges
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
225
|
test('A5: close tears down the target broker and cascades its subtree, leaving the ancestor untouched', async () => {
|
|
236
226
|
const A = h.spawnRoot('a5 root');
|
|
237
227
|
const B = (await client.createNode({ kind: 'developer', parent: A, prompt: 'a5 mid' })).node_id;
|
|
@@ -259,12 +249,10 @@ test('A5: close tears down the target broker and cascades its subtree, leaving t
|
|
|
259
249
|
assert.equal(h.node(C)?.status, 'canceled', 'cascaded child C is canceled');
|
|
260
250
|
assert.equal(h.node(A)?.status, 'active', 'ancestor A is untouched (still active)');
|
|
261
251
|
});
|
|
262
|
-
// ---------------------------------------------------------------------------
|
|
263
252
|
// A6 — PUT /profiles/{name} idempotent; PUT /model-auth/{provider} writes
|
|
264
253
|
// auth.json (sandboxed PI_CODING_AGENT_DIR).
|
|
265
254
|
// HONEST PROXY: reload fan is asserted as the file write only (the mtime
|
|
266
255
|
// watcher lives in runDaemon, not createApiServer).
|
|
267
|
-
// ---------------------------------------------------------------------------
|
|
268
256
|
test('A6a: PUT /profiles/{name} is idempotent (twice → same profile id)', async () => {
|
|
269
257
|
// Profiles write under `join(homedir(), '.crouter', 'profiles')` — homedir()
|
|
270
258
|
// honors HOME, which the harness only overrides for SUBPROCESS children. For
|
|
@@ -312,11 +300,9 @@ test('A6b: PUT /model-auth/{provider} writes auth.json (reload fan asserted as f
|
|
|
312
300
|
rmSync(piDir, { recursive: true, force: true });
|
|
313
301
|
}
|
|
314
302
|
});
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
303
|
// /v1/files/peek — host file read (capped → `truncated`), plus the
|
|
317
304
|
// usage/relative/404 error mapping. Covers the handler's non-trivial
|
|
318
305
|
// branching (usage / notFound / directory / truncation boundary).
|
|
319
|
-
// ---------------------------------------------------------------------------
|
|
320
306
|
const PEEK_CAP = 512 * 1024; // MUST mirror PEEK_MAX_BYTES in handlers/files.ts
|
|
321
307
|
test('files/peek: 200 returns full content, untruncated, for a small file', async () => {
|
|
322
308
|
const dir = mkdtempSync(join(tmpdir(), 'crtr-peek-'));
|
|
@@ -60,10 +60,8 @@ after(async () => {
|
|
|
60
60
|
await server?.close();
|
|
61
61
|
await h?.dispose();
|
|
62
62
|
});
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
63
|
// B2 (attach hybrid) — ensureAttach revives a DORMANT node server-side; the
|
|
65
64
|
// direct view.sock snapshot streams; DETACH LEAVES THE BROKER RUNNING.
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
65
|
test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach leaves the broker running', async () => {
|
|
68
66
|
const root = h.spawnRoot('b10 attach root'); // in-process resident (alive pid)
|
|
69
67
|
const id = (await client.createNode({ kind: 'developer', parent: root, prompt: 'b10 attach worker' })).node_id;
|
|
@@ -87,7 +85,7 @@ test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach l
|
|
|
87
85
|
// reviveNode's double-launch guard reads the fleet entry, so attaching in the
|
|
88
86
|
// window between the kill and the child-exit delivery is correctly a no-op.
|
|
89
87
|
await h.awaitFleetExit(id);
|
|
90
|
-
//
|
|
88
|
+
// ensureAttach (the exact call viewer.ts makes) revives it SERVER-SIDE
|
|
91
89
|
const res = await client.ensureAttach(id, { resume: true, revive: true });
|
|
92
90
|
assert.equal(res.node_id, id, 'ensureAttach echoes the node id');
|
|
93
91
|
assert.equal(res.revived, true, 'server launched a fresh broker (revived=true) for the dormant node');
|
|
@@ -101,7 +99,7 @@ test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach l
|
|
|
101
99
|
// hop viewer.ts keeps local). ---
|
|
102
100
|
const attached = await kit.attach(id, 'observer', 'b10-attach-observer');
|
|
103
101
|
assert.ok(attached.welcome.snapshot != null, 'welcome frame carries a reconstructed snapshot (snapshot streams)');
|
|
104
|
-
//
|
|
102
|
+
// DETACH LEAVES THE BROKER RUNNING (reap-on-detach removed this batch)
|
|
105
103
|
attached.close();
|
|
106
104
|
// Give any (now-absent) reap path a beat to have fired, then assert the broker
|
|
107
105
|
// is unmistakably still alive.
|
|
@@ -111,11 +109,9 @@ test('B2-attach: ensureAttach revives a dormant node, snapshot streams, detach l
|
|
|
111
109
|
assert.ok(isPidAlive(afterDetachPid), 'detach left the broker RUNNING (no reap-on-detach)');
|
|
112
110
|
assert.notEqual(h.node(id)?.status, 'done', 'detach did not finalize the node');
|
|
113
111
|
});
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
112
|
// B2 (model-auth) — installCredential writes auth.json (content asserted), the
|
|
116
113
|
// reload FAN acks against a live broker, and the API path is byte-identical to
|
|
117
114
|
// the old direct-write path.
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
115
|
test('B2-modelauth: installCredential writes auth.json and the reload fan acks a live broker', async () => {
|
|
120
116
|
// Sandbox pi's credential store so the write lands in a throwaway dir, never the
|
|
121
117
|
// developer's real ~/.pi/agent/auth.json. authPath() honors PI_CODING_AGENT_DIR.
|
|
@@ -128,7 +124,7 @@ test('B2-modelauth: installCredential writes auth.json and the reload fan acks a
|
|
|
128
124
|
const root = h.spawnRoot('b10 modelauth root');
|
|
129
125
|
liveId = (await client.createNode({ kind: 'developer', parent: root, prompt: 'b10 fan target' })).node_id;
|
|
130
126
|
await h.awaitBoot(liveId);
|
|
131
|
-
//
|
|
127
|
+
// installCredential (the exact call setup.ts makes) writes auth.json
|
|
132
128
|
const res = await client.installCredential('anthropic', { kind: 'api_key', api_key: 'sk-b10-test-key' });
|
|
133
129
|
assert.equal(res.installed, true, 'credential reports installed');
|
|
134
130
|
assert.equal(res.managed, false, 'api_key install is not a managed-pool account');
|
|
@@ -150,14 +146,12 @@ test('B2-modelauth: installCredential writes auth.json and the reload fan acks a
|
|
|
150
146
|
rmSync(apiDir, { recursive: true, force: true });
|
|
151
147
|
}
|
|
152
148
|
});
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
149
|
// B2 (model-auth, oauth) — non-managed OAuth install must PRESERVE
|
|
155
150
|
// provider-specific extra credential fields (e.g. github-copilot's
|
|
156
151
|
// `enterpriseUrl`, load-bearing for enterprise routing/refresh). The client
|
|
157
152
|
// forwards non-standard keys through `extra` and the handler re-spreads them.
|
|
158
153
|
// Regression guard for review M1 (a dropped enterpriseUrl breaks
|
|
159
154
|
// GitHub-Enterprise Copilot).
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
155
|
test('B2-modelauth-oauth: installCredential(oauth) preserves extra fields (enterpriseUrl)', async () => {
|
|
162
156
|
const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
|
|
163
157
|
const apiDir = mkdtempSync(join(tmpdir(), 'crtr-b10-oauth-api-'));
|
|
@@ -192,10 +186,8 @@ test('B2-modelauth-oauth: installCredential(oauth) preserves extra fields (enter
|
|
|
192
186
|
rmSync(apiDir, { recursive: true, force: true });
|
|
193
187
|
}
|
|
194
188
|
});
|
|
195
|
-
// ---------------------------------------------------------------------------
|
|
196
189
|
// Credential truth/removal — GET exposes only connection state and DELETE
|
|
197
190
|
// clears exactly its provider's durable store.
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
191
|
test('B2-modelauth-status: list is sanitized and delete clears only the selected provider', async () => {
|
|
200
192
|
const savedPiDir = process.env['PI_CODING_AGENT_DIR'];
|
|
201
193
|
const apiDir = mkdtempSync(join(tmpdir(), 'crtr-b10-status-'));
|
|
@@ -127,12 +127,10 @@ after(async () => {
|
|
|
127
127
|
else
|
|
128
128
|
process.env['CRTR_NODE_ID'] = savedNodeId;
|
|
129
129
|
});
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
130
|
// push final — the report write, the subscriber fan, and the lifecycle
|
|
132
131
|
// transition all happen server-side, driven through the real push.ts leaf. A
|
|
133
132
|
// parent spawns a child (so the parent subscribes to the child); the child
|
|
134
133
|
// pushes.
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
134
|
test('`crtr push final` from a child writes a report, notifies the subscribed parent, AND transitions the child row active→done', async () => {
|
|
137
135
|
const parent = h.spawnRoot('leaf-api-parity final parent');
|
|
138
136
|
const child = (await client.createNode({ kind: 'developer', parent, prompt: 'final worker' })).node_id;
|
|
@@ -153,11 +151,9 @@ test('`crtr push final` from a child writes a report, notifies the subscribed pa
|
|
|
153
151
|
// — the re-homed guard only engages for a managed-worktree owner.
|
|
154
152
|
assert.equal('worktree_auto_dropped' in out, false, 'a normal node final is unaffected by the worktree guard');
|
|
155
153
|
});
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
154
|
// Re-homed worktree guard/auto-drop (ambiguity-2 follow-up): the pre-B-5 CLI
|
|
158
155
|
// `push final` worktree behavior now lives SERVER-side in handlers/reports.ts,
|
|
159
156
|
// exercised end-to-end through the real push.ts leaf.
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
157
|
test('re-homed guard: `crtr push final` on a node with a managed worktree holding REAL unlanded commits blocks with the parity open_managed_worktree InputError (round-tripped from the server), and does NOT finalize', async () => {
|
|
162
158
|
const repo = initRepo();
|
|
163
159
|
try {
|
|
@@ -255,13 +251,11 @@ test('`crtr push final` keeps an absent managed worktree open when its branch is
|
|
|
255
251
|
rmSync(repo, { recursive: true, force: true });
|
|
256
252
|
}
|
|
257
253
|
});
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
254
|
// `node worktree close` error parity — the SPECIFIC WorktreeError code + its
|
|
260
255
|
// `next` remediation round-trip verbatim through the server → ApiError → the
|
|
261
256
|
// bespoke CLI mapper in node-worktree.ts, instead of collapsing to a generic
|
|
262
257
|
// status bucket. That mapper exists for exactly this contract, so this is the
|
|
263
258
|
// load-bearing regression to lock.
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
259
|
test('`crtr node worktree close` on a node with NO managed worktree round-trips the specific `no_managed_worktree` code + its exact next-hint (not a generic status bucket)', async () => {
|
|
266
260
|
const id = 'wt-close-none';
|
|
267
261
|
createNode({ node_id: id, name: id, created: new Date().toISOString(), cwd: process.cwd(), kind: 'developer', mode: 'base', lifecycle: 'terminal', status: 'active' });
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// is bridge.ts's (A-8) — NOT routed here.
|
|
6
6
|
//
|
|
7
7
|
// `{id}` is the target node being attached to.
|
|
8
|
-
import { getNode
|
|
8
|
+
import { getNode } from '../../../core/canvas/canvas.js';
|
|
9
|
+
import { reviveNode } from '../../../core/runtime/revive.js';
|
|
9
10
|
import { viewSocketPath } from '../../../core/canvas/paths.js';
|
|
10
11
|
import { waitForBrokerViewSocket } from '../../../core/runtime/placement-tmux.js';
|
|
11
12
|
import { notFound, usage } from '../../../core/errors.js';
|
|
@@ -6,7 +6,7 @@ import { editorLabel } from '../../../core/canvas/labels.js';
|
|
|
6
6
|
import { assembleNodeSubject } from '../../../core/substrate/subject.js';
|
|
7
7
|
import { withCanvasWrite } from '../../../core/canvas/db.js';
|
|
8
8
|
import { notFound, usage } from '../../../core/errors.js';
|
|
9
|
-
import { readCursor, readInboxSince, writeCursor } from '../../../core/feed/inbox.js';
|
|
9
|
+
import { hasUnseenWakeMail, readCursor, readInboxSince, writeCursor } from '../../../core/feed/inbox.js';
|
|
10
10
|
import { clearYieldMessage } from '../../../core/runtime/kickoff.js';
|
|
11
11
|
import { transition } from '../../../core/runtime/lifecycle.js';
|
|
12
12
|
import { focusOf, tearDownNode } from '../../../core/runtime/placement.js';
|
|
@@ -224,9 +224,6 @@ function handleSessionBound(ctx) {
|
|
|
224
224
|
});
|
|
225
225
|
return { status: 200, body: result };
|
|
226
226
|
}
|
|
227
|
-
function hasUnseenNonDeferredInbox(nodeId) {
|
|
228
|
-
return readInboxSince(nodeId, readCursor(nodeId)).some((entry) => entry.tier !== 'deferred');
|
|
229
|
-
}
|
|
230
227
|
/** Commit the complete broker-settlement policy while holding one canvas write
|
|
231
228
|
* boundary. This handler intentionally never invokes the broker that called it:
|
|
232
229
|
* the returned directive is the only Pi-side consequence. */
|
|
@@ -300,14 +297,14 @@ function settleBroker(nodeId, request) {
|
|
|
300
297
|
if (decision.action === 'reprompt')
|
|
301
298
|
return { action: 'reprompt', prompt: decision.message };
|
|
302
299
|
if (decision.reason === 'finished') {
|
|
303
|
-
return
|
|
300
|
+
return hasUnseenWakeMail(nodeId) ? { action: 'stay_dormant' } : { action: 'shutdown' };
|
|
304
301
|
}
|
|
305
302
|
if (decision.reason !== 'awaiting' && decision.reason !== 'scheduled') {
|
|
306
303
|
return { action: 'stay_dormant' };
|
|
307
304
|
}
|
|
308
305
|
if (focusOf(nodeId) !== null
|
|
309
306
|
|| hasAttachedBrokerViewers(nodeId)
|
|
310
|
-
||
|
|
307
|
+
|| hasUnseenWakeMail(nodeId))
|
|
311
308
|
return { action: 'stay_dormant' };
|
|
312
309
|
transition(nodeId, 'release');
|
|
313
310
|
return { action: 'shutdown' };
|