@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
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// so node target metadata comes from each node's durable meta.json — the source
|
|
18
18
|
// of truth the database index is rebuilt from — through its existing read-only
|
|
19
19
|
// helper. Missing or corrupt metadata remains a global blocker.
|
|
20
|
+
import { envProfileId } from '../shared/env.js';
|
|
20
21
|
import { readdirSync } from 'node:fs';
|
|
21
22
|
import { dirname, join } from 'node:path';
|
|
22
23
|
import { readMetaObject } from '../core/canvas/meta-file.js';
|
|
@@ -79,7 +80,7 @@ function nodeStoresWithDocs() {
|
|
|
79
80
|
* so discovery must run after that lane, not against `listProfiles()`. */
|
|
80
81
|
export async function discoverCorpus(opts) {
|
|
81
82
|
const cwd = realpathOrSelf(opts.cwd ?? process.cwd());
|
|
82
|
-
const selectedProfileId =
|
|
83
|
+
const selectedProfileId = envProfileId() || null;
|
|
83
84
|
const explicitDirs = opts.explicitDirs ?? [];
|
|
84
85
|
const roots = [];
|
|
85
86
|
const rootSeen = new Set();
|
|
@@ -336,9 +337,7 @@ export async function discoverCorpus(opts) {
|
|
|
336
337
|
nodeMetadata,
|
|
337
338
|
};
|
|
338
339
|
}
|
|
339
|
-
// ---------------------------------------------------------------------------
|
|
340
340
|
// Snapshots
|
|
341
|
-
// ---------------------------------------------------------------------------
|
|
342
341
|
/** Read every discovered store's documents. `preloaded` supplies the per-store
|
|
343
342
|
* convergent lane's FOLDED snapshots so the corpus plans against the bytes a
|
|
344
343
|
* write run leaves behind — identical under `--dry-run`, where the same fold
|
|
@@ -387,9 +386,7 @@ export function buildCorpusSnapshot(discovery, snapshots) {
|
|
|
387
386
|
}));
|
|
388
387
|
return makeCorpusSnapshot(stores, discovery.targets, discovery.roots);
|
|
389
388
|
}
|
|
390
|
-
// ---------------------------------------------------------------------------
|
|
391
389
|
// The runner
|
|
392
|
-
// ---------------------------------------------------------------------------
|
|
393
390
|
function cloneCorpus(corpus) {
|
|
394
391
|
const stores = corpus.stores.map((store) => ({
|
|
395
392
|
...store,
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
// The ordered state-migration registry —
|
|
2
|
-
//
|
|
1
|
+
// The ordered state-migration registry — the SECOND of this repo's three
|
|
2
|
+
// migration homes, and the one for on-disk memory documents, profile
|
|
3
|
+
// manifests, and the corpus lane, driven by `crtr sys migrate`. ONE list for
|
|
4
|
+
// both non-journaled lanes here, so a cut that spans canvas-db and document
|
|
5
|
+
// stores ships as adjacent entries (the journaled lane's entries splice into
|
|
6
|
+
// `../core/canvas/migrations.ts`'s `MIGRATIONS` chain at its tail).
|
|
7
|
+
//
|
|
8
|
+
// The other two homes:
|
|
9
|
+
// - `../core/canvas/migrations.ts` — canvas.db's own versioned schema chain,
|
|
10
|
+
// gated by `PRAGMA user_version`. Steps there may freely read node meta,
|
|
11
|
+
// query other tables mid-step, and build shell commands from canvas data
|
|
12
|
+
// — exactly what this registry's import contract (below) forbids, which
|
|
13
|
+
// is why that chain is not expressed as registrants here.
|
|
14
|
+
// - `../daemon/boot-migrations.ts` — filesystem repairs that run
|
|
15
|
+
// unconditionally at every daemon boot, outside any version gate.
|
|
3
16
|
//
|
|
4
17
|
// Authoring contract (each migration is one file, `NNN-<desc>.ts`, or one
|
|
5
18
|
// directory, `NNN-<desc>/index.ts`, appended here in order):
|
|
@@ -221,7 +221,6 @@ test('queued continuation finishes before natural-stop routing commits', { timeo
|
|
|
221
221
|
assert.equal(shutdown, true, 'settlement routes from the completed continuation');
|
|
222
222
|
assert.equal(getNode('leaf')?.intent ?? null, null, 'the intermediate natural stop never idle-released the node');
|
|
223
223
|
});
|
|
224
|
-
// ---------------------------------------------------------------------------
|
|
225
224
|
// §5.1 — the §1.7 settlement branch map on the focuses table. Every assertion is
|
|
226
225
|
// on the canvas focuses/runtime rows after firing agent_settled (TMUX_PANE is
|
|
227
226
|
// cleared in beforeEach). NOTE (broker-host cut): the placement read `focusOf`
|
|
@@ -232,7 +231,6 @@ test('queued continuation finishes before natural-stop routing commits', { timeo
|
|
|
232
231
|
// but-not-yet-realized viewer, which liveOrPrune passes through). status='done'
|
|
233
232
|
// is reached by setting the runtime row directly (the branch reads
|
|
234
233
|
// getNode(nodeId).status).
|
|
235
|
-
// ---------------------------------------------------------------------------
|
|
236
234
|
test('§5.1.2 truly-done + focused → focus row CLOSED, own presence nulled (Q1)', async () => {
|
|
237
235
|
// R carries a LOCATION so the close-path presence-null is observable.
|
|
238
236
|
createNode(node('R', { parent: null, lifecycle: 'terminal', pane: '%r', tmux_session: 'Suser', window: '@wr' }));
|
|
@@ -323,10 +321,8 @@ test('refresh-yield (intent=refresh) writes NO push — silent to subscribers',
|
|
|
323
321
|
assert.equal(readInboxSince('root').length, 0, 'a yield is silent: NO inbox pointer');
|
|
324
322
|
assert.equal(pi.injected.length, 0, 'no reprompt on a refresh-yield');
|
|
325
323
|
});
|
|
326
|
-
// ---------------------------------------------------------------------------
|
|
327
324
|
// turn_end — stale-⚠ fix: reaching turn_end proves the current provider
|
|
328
325
|
// round-trip and any wedge episode are both over, so both fault links clear.
|
|
329
|
-
// ---------------------------------------------------------------------------
|
|
330
326
|
/** A turn_end event carrying one assistant text block. */
|
|
331
327
|
function turnEndEvent(text) {
|
|
332
328
|
return { message: { role: 'assistant', content: [{ type: 'text', text }] } };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Regression: the `pre-command` gate holds a bash command BEFORE it runs and
|
|
2
|
+
// releases it once its memory is in the transcript. The release is not a stored
|
|
3
|
+
// decision — it is the exposure ledger, and that is exactly what makes it
|
|
4
|
+
// fragile:
|
|
5
|
+
//
|
|
6
|
+
// • Compaction drops the delivered guidance out of the transcript while the
|
|
7
|
+
// ledger still claims it was delivered. Without the demotion pass the doc is
|
|
8
|
+
// held once and then silently absent for the rest of the session. Worse, a
|
|
9
|
+
// demotion that left a rankless entry behind would crash the node's whole
|
|
10
|
+
// substrate on its next load — so the ledger is reloaded FROM DISK here.
|
|
11
|
+
// • pi preflights the siblings of one assistant message sequentially and then
|
|
12
|
+
// executes them concurrently, so without a per-turn flag the second sibling
|
|
13
|
+
// would find the doc already exposed and slip past the hold its sibling just
|
|
14
|
+
// raised.
|
|
15
|
+
// • A session whose corpus carries no `pre-command` doc at all is the common
|
|
16
|
+
// case and must never pay a daemon round-trip. Proven by closing the daemon:
|
|
17
|
+
// if the short-circuit stopped working the call would fail loud.
|
|
18
|
+
//
|
|
19
|
+
// The gate runs against the real canvas api server, so the subject the matcher
|
|
20
|
+
// gates on is the one crtrd actually serves.
|
|
21
|
+
//
|
|
22
|
+
// Run: node --conditions=crtr-src --import tsx/esm --test src/pi-extensions/__tests__/pre-command-gate.test.ts
|
|
23
|
+
import { test, before, beforeEach, afterEach, after } from 'node:test';
|
|
24
|
+
import assert from 'node:assert/strict';
|
|
25
|
+
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { tmpdir } from 'node:os';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { registerCanvasDocSubstrate } from '../canvas-doc-substrate.js';
|
|
29
|
+
import { createNode } from '../../core/canvas/canvas.js';
|
|
30
|
+
import { closeDb } from '../../core/canvas/db.js';
|
|
31
|
+
import { apiSocketPath } from '../../core/canvas/paths.js';
|
|
32
|
+
import { resetScopeCache } from '../../core/scope.js';
|
|
33
|
+
import { loadContextExposureState } from '../../core/substrate/injected-store.js';
|
|
34
|
+
import { clearSessionCache } from '../../core/substrate/session-cache.js';
|
|
35
|
+
import { createApiServer } from '../../daemon/api/server.js';
|
|
36
|
+
const DOCTRINE_BODY = 'PRE-COMMAND-DOCTRINE-BODY';
|
|
37
|
+
let home;
|
|
38
|
+
let cwd;
|
|
39
|
+
let server = null;
|
|
40
|
+
let origCwd;
|
|
41
|
+
let origHome;
|
|
42
|
+
let origNode;
|
|
43
|
+
let origProfile;
|
|
44
|
+
async function waitForSocket(path) {
|
|
45
|
+
for (let i = 0; i < 200; i++) {
|
|
46
|
+
if (existsSync(path))
|
|
47
|
+
return;
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`api socket never bound at ${path}`);
|
|
51
|
+
}
|
|
52
|
+
async function closeServer() {
|
|
53
|
+
const running = server;
|
|
54
|
+
server = null;
|
|
55
|
+
if (running !== null)
|
|
56
|
+
await running.close();
|
|
57
|
+
}
|
|
58
|
+
/** A user-scope doc that holds every `deploy …` invocation until it is read. */
|
|
59
|
+
function writeDoctrineDoc(event) {
|
|
60
|
+
const store = join(home, 'user', '.crouter', 'memory');
|
|
61
|
+
mkdirSync(store, { recursive: true });
|
|
62
|
+
const path = join(store, 'deploy-doctrine.md');
|
|
63
|
+
writeFileSync(path, '---\nkind: knowledge\n' +
|
|
64
|
+
'when-and-why-to-read: When deploying, this knowledge should be read because it carries the approval boundary.\n' +
|
|
65
|
+
`surfaces:\n - {on: ${event}, at: content, match: "deploy*"}\n---\n` +
|
|
66
|
+
`${DOCTRINE_BODY}\n`);
|
|
67
|
+
clearSessionCache();
|
|
68
|
+
return realpathSync(path);
|
|
69
|
+
}
|
|
70
|
+
/** A canvas node the api server can assemble a subject for, selected as the
|
|
71
|
+
* session's node. Each test uses its own id: the extension's exposure state is
|
|
72
|
+
* a per-node process singleton. */
|
|
73
|
+
function seedNode(id) {
|
|
74
|
+
createNode({
|
|
75
|
+
node_id: id,
|
|
76
|
+
name: id,
|
|
77
|
+
created: new Date().toISOString(),
|
|
78
|
+
cwd,
|
|
79
|
+
kind: 'general',
|
|
80
|
+
mode: 'base',
|
|
81
|
+
lifecycle: 'terminal',
|
|
82
|
+
status: 'active',
|
|
83
|
+
});
|
|
84
|
+
mkdirSync(join(home, 'nodes', id), { recursive: true });
|
|
85
|
+
process.env['CRTR_NODE_ID'] = id;
|
|
86
|
+
}
|
|
87
|
+
/** The registered extension, driven through the pi events it actually binds. */
|
|
88
|
+
function registerSubstrate() {
|
|
89
|
+
const handlers = new Map();
|
|
90
|
+
registerCanvasDocSubstrate({
|
|
91
|
+
on(event, handler) {
|
|
92
|
+
const bound = handlers.get(event) ?? [];
|
|
93
|
+
bound.push(handler);
|
|
94
|
+
handlers.set(event, bound);
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
async function fire(event, payload) {
|
|
98
|
+
const bound = handlers.get(event) ?? [];
|
|
99
|
+
assert.equal(bound.length, 1, `the substrate binds exactly one ${event} handler`);
|
|
100
|
+
return await bound[0](payload, {});
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
toolCall: (toolName, input) => fire('tool_call', { toolName, input }),
|
|
104
|
+
bash: (command) => fire('tool_call', { toolName: 'bash', input: { command } }),
|
|
105
|
+
endTurn: async () => void (await fire('turn_end', {})),
|
|
106
|
+
compact: async () => void (await fire('session_compact', {})),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function assertHeld(result, what) {
|
|
110
|
+
assert.ok(result !== undefined, `${what} is held`);
|
|
111
|
+
assert.equal(result.block, true);
|
|
112
|
+
assert.ok(result.reason.startsWith('HELD — nothing ran.'), `the held result opens with the notice; got: ${result.reason}`);
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
before(() => {
|
|
116
|
+
origCwd = process.cwd();
|
|
117
|
+
origHome = process.env['HOME'];
|
|
118
|
+
origNode = process.env['CRTR_NODE_ID'];
|
|
119
|
+
origProfile = process.env['CRTR_PROFILE_ID'];
|
|
120
|
+
});
|
|
121
|
+
beforeEach(async () => {
|
|
122
|
+
closeDb();
|
|
123
|
+
if (home)
|
|
124
|
+
rmSync(home, { recursive: true, force: true });
|
|
125
|
+
home = mkdtempSync(join(tmpdir(), 'crtr-pre-command-'));
|
|
126
|
+
cwd = join(home, 'user', 'work');
|
|
127
|
+
mkdirSync(cwd, { recursive: true });
|
|
128
|
+
process.chdir(cwd);
|
|
129
|
+
process.env['HOME'] = join(home, 'user');
|
|
130
|
+
process.env['CRTR_HOME'] = home;
|
|
131
|
+
delete process.env['CRTR_NODE_ID'];
|
|
132
|
+
delete process.env['CRTR_PROFILE_ID'];
|
|
133
|
+
resetScopeCache();
|
|
134
|
+
clearSessionCache();
|
|
135
|
+
server = createApiServer();
|
|
136
|
+
await waitForSocket(apiSocketPath());
|
|
137
|
+
});
|
|
138
|
+
afterEach(async () => {
|
|
139
|
+
try {
|
|
140
|
+
await closeServer();
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
process.chdir(origCwd);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
after(() => {
|
|
147
|
+
process.chdir(origCwd);
|
|
148
|
+
closeDb();
|
|
149
|
+
if (home)
|
|
150
|
+
rmSync(home, { recursive: true, force: true });
|
|
151
|
+
resetScopeCache();
|
|
152
|
+
clearSessionCache();
|
|
153
|
+
delete process.env['CRTR_HOME'];
|
|
154
|
+
if (origHome === undefined)
|
|
155
|
+
delete process.env['HOME'];
|
|
156
|
+
else
|
|
157
|
+
process.env['HOME'] = origHome;
|
|
158
|
+
if (origNode === undefined)
|
|
159
|
+
delete process.env['CRTR_NODE_ID'];
|
|
160
|
+
else
|
|
161
|
+
process.env['CRTR_NODE_ID'] = origNode;
|
|
162
|
+
if (origProfile === undefined)
|
|
163
|
+
delete process.env['CRTR_PROFILE_ID'];
|
|
164
|
+
else
|
|
165
|
+
process.env['CRTR_PROFILE_ID'] = origProfile;
|
|
166
|
+
});
|
|
167
|
+
test('a matching command is held with its doc, every sibling in the message with it, and the re-issue runs', async () => {
|
|
168
|
+
const docPath = writeDoctrineDoc('pre-command');
|
|
169
|
+
seedNode('gate-hold');
|
|
170
|
+
const pi = registerSubstrate();
|
|
171
|
+
const held = assertHeld(await pi.bash('deploy prod'), 'the first matching command');
|
|
172
|
+
assert.ok(held.reason.includes(DOCTRINE_BODY), `the doc rides back as the blocked call's result; got: ${held.reason}`);
|
|
173
|
+
assert.match(held.reason, /re-issue your WHOLE command/, 'the copy steers a whole-command re-issue, never one clause');
|
|
174
|
+
// Exposure is registered AT BLOCK TIME — physical identity and content alias
|
|
175
|
+
// both — and that single fact is what releases the re-issue below.
|
|
176
|
+
const afterBlock = loadContextExposureState('gate-hold');
|
|
177
|
+
assert.deepEqual(afterBlock.exposures.get(docPath), { transcript: 3 }, 'the held doc is recorded as delivered');
|
|
178
|
+
assert.equal(afterBlock.exposures.size, 2, 'the content alias is recorded alongside the path');
|
|
179
|
+
// Siblings of the same assistant message. The doc is already exposed, so
|
|
180
|
+
// without the per-turn flag the render would come back empty and let them
|
|
181
|
+
// through. The flag covers the whole message, matching or not: every command
|
|
182
|
+
// in it was authored under the worldview this doc just corrected.
|
|
183
|
+
const sibling = assertHeld(await pi.bash('deploy staging'), 'a matching sibling');
|
|
184
|
+
assert.match(sibling.reason, /Another command in this same message/);
|
|
185
|
+
assertHeld(await pi.bash('ls -la'), 'a non-matching sibling of a held command');
|
|
186
|
+
await pi.endTurn();
|
|
187
|
+
// The release: the doc is in the transcript, so there is nothing left to
|
|
188
|
+
// deliver and nothing left to hold.
|
|
189
|
+
assert.equal(await pi.bash('deploy prod'), undefined, 'the identical command re-issued in a later turn runs');
|
|
190
|
+
assert.equal(await pi.bash('ls -la'), undefined, 'a command matching nothing is untouched');
|
|
191
|
+
});
|
|
192
|
+
test('compaction re-arms the hold and leaves an exposure ledger that reloads', async () => {
|
|
193
|
+
const docPath = writeDoctrineDoc('pre-command');
|
|
194
|
+
seedNode('gate-compact');
|
|
195
|
+
const pi = registerSubstrate();
|
|
196
|
+
assertHeld(await pi.bash('deploy prod'), 'the first matching command');
|
|
197
|
+
await pi.endTurn();
|
|
198
|
+
assert.equal(await pi.bash('deploy prod'), undefined, 'the doc is in the transcript, so the command runs');
|
|
199
|
+
await pi.endTurn();
|
|
200
|
+
await pi.compact();
|
|
201
|
+
// Reloaded from disk: the demotion must DELETE the rankless entry rather than
|
|
202
|
+
// leave `{}` behind, which the v3 loader rejects outright — this call is the
|
|
203
|
+
// proof it did.
|
|
204
|
+
const afterCompact = loadContextExposureState('gate-compact');
|
|
205
|
+
assert.equal(afterCompact.exposures.has(docPath), false, 'compaction forgets the delivery that compaction dropped');
|
|
206
|
+
assert.equal(afterCompact.exposures.size, 0, 'the content alias is forgotten too, and no rankless entry survives');
|
|
207
|
+
const reheld = assertHeld(await pi.bash('deploy prod'), 'the same command after compaction');
|
|
208
|
+
assert.ok(reheld.reason.includes(DOCTRINE_BODY), 'guidance the transcript no longer carries is delivered again');
|
|
209
|
+
});
|
|
210
|
+
test('a corpus with no pre-command doc never reaches the daemon', async () => {
|
|
211
|
+
writeDoctrineDoc('command');
|
|
212
|
+
seedNode('gate-quiet');
|
|
213
|
+
const pi = registerSubstrate();
|
|
214
|
+
// No daemon. Every path below must return before the subject lookup; one that
|
|
215
|
+
// does not fails loud with `daemon_unavailable` instead of passing quietly.
|
|
216
|
+
await closeServer();
|
|
217
|
+
assert.equal(await pi.toolCall('read', { file: '/tmp/x' }), undefined, 'a non-bash call is not the gate’s business');
|
|
218
|
+
assert.equal(await pi.toolCall('bash', { command: ' ' }), undefined, 'a bash call carrying no command is not held');
|
|
219
|
+
assert.equal(await pi.bash('deploy prod'), undefined, 'a post-execution `command` doc never holds anything');
|
|
220
|
+
});
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// These helpers are intentionally independent of the canvas state store. A
|
|
4
4
|
// broker may read its own context files directly, but all node metadata reads
|
|
5
5
|
// and generated-name writes go through crtrd's broker operation projection.
|
|
6
|
+
import { envHomeOverride } from '../shared/env.js';
|
|
6
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
8
|
import { homedir } from 'node:os';
|
|
8
9
|
import { join } from 'node:path';
|
|
@@ -14,7 +15,7 @@ function contextDir(nodeId) {
|
|
|
14
15
|
if (nodeId === '' || nodeId === '.' || nodeId === '..' || /[\\/\0]/u.test(nodeId)) {
|
|
15
16
|
throw new TypeError('node_id must be a non-empty path segment');
|
|
16
17
|
}
|
|
17
|
-
const canvasHome =
|
|
18
|
+
const canvasHome = envHomeOverride()?.trim() || join(homedir(), CRTR_DIR_NAME, 'canvas');
|
|
18
19
|
return join(canvasHome, 'nodes', nodeId, 'context');
|
|
19
20
|
}
|
|
20
21
|
/** Persist a bare root's first real interactive message without clobbering an
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
// Deviation from upstream: this backend pins `bash` as the shell (upstream
|
|
25
25
|
// consults user shell config via non-exported helpers). Acceptable — crtr's
|
|
26
26
|
// agents are bash-contract anyway.
|
|
27
|
+
import { envBashValveMs, envNodeId } from '../shared/env.js';
|
|
27
28
|
import { spawn } from 'node:child_process';
|
|
28
29
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
29
30
|
import { homedir } from 'node:os';
|
|
@@ -34,20 +35,12 @@ import { PREVIEW_RESULT_PATH_ENV, previewResultPath } from '../core/preview-resu
|
|
|
34
35
|
import { resolveAdmittedHostCommand } from '../core/runtime/spawn-env.js';
|
|
35
36
|
import { Type } from 'typebox';
|
|
36
37
|
import { createBashToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
38
|
// Deadline — 5 minutes by default, overridable via CRTR_BASH_VALVE_MS
|
|
39
39
|
// (undocumented; test-only seam so the E2E valve check doesn't sleep 5m).
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
const DEFAULT_VALVE_MS = 5 * 60 * 1000;
|
|
42
40
|
const POLL_MS = 200;
|
|
43
41
|
function resolveValveMs() {
|
|
44
|
-
|
|
45
|
-
if (raw === undefined)
|
|
46
|
-
return DEFAULT_VALVE_MS;
|
|
47
|
-
const n = Number(raw);
|
|
48
|
-
return Number.isFinite(n) && n > 0 ? n : DEFAULT_VALVE_MS;
|
|
42
|
+
return envBashValveMs();
|
|
49
43
|
}
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
44
|
// The detached supervisor. Spawned with `detached: true` so it becomes its
|
|
52
45
|
// own process-group leader (pgid === its pid) — killing `-pgid` reaches the
|
|
53
46
|
// real command and anything it forked, even after this pi process exits.
|
|
@@ -59,7 +52,6 @@ function resolveValveMs() {
|
|
|
59
52
|
//
|
|
60
53
|
// `--` before the paths is a dummy $0 (a standard `bash -c script -- args`
|
|
61
54
|
// idiom) so the real payload lands at $1 onward.
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
55
|
// The elapsed rendering mirrors formatBashElapsed() in core/bash-jobs.ts
|
|
64
56
|
// (`12s` / `3m 40s` / `1h 04m`) so the completion message reads the same as the
|
|
65
57
|
// handoff notice; it measures TOTAL runtime, foreground portion included.
|
|
@@ -166,12 +158,10 @@ function readExitCode(path) {
|
|
|
166
158
|
return null;
|
|
167
159
|
}
|
|
168
160
|
}
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
161
|
// Long-sleep guard. A command that opens with `sleep <long>` is a node burning
|
|
171
162
|
// a live window to wait on a clock — the runtime already has waiting primitives
|
|
172
163
|
// that cost nothing (`crtr cron` for a scheduled wake, `crtr node wait deadline`
|
|
173
164
|
// for an inbox-vs-deadline race). Anything over 10s is refused before it runs.
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
165
|
const MAX_INLINE_SLEEP_SECONDS = 10;
|
|
176
166
|
const SLEEP_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
177
167
|
/** Seconds a leading `sleep ...` will block for, or null when the command does
|
|
@@ -371,10 +361,7 @@ export function createValveOperations(nodeId, contextDir, takePurpose = () => nu
|
|
|
371
361
|
},
|
|
372
362
|
};
|
|
373
363
|
}
|
|
374
|
-
// ---------------------------------------------------------------------------
|
|
375
364
|
// Extension
|
|
376
|
-
// ---------------------------------------------------------------------------
|
|
377
|
-
// ---------------------------------------------------------------------------
|
|
378
365
|
// `purpose` — the one line a person reads while a command runs
|
|
379
366
|
//
|
|
380
367
|
// An optional deviation from taking pi's definition verbatim, and the only one
|
|
@@ -396,7 +383,6 @@ export function createValveOperations(nodeId, contextDir, takePurpose = () => nu
|
|
|
396
383
|
// Upstream ask: pi's own bash tool should carry this (Claude Code's bash tool
|
|
397
384
|
// has shipped an equivalent `description` field for years). Until it does, the
|
|
398
385
|
// field is re-declared here on top of pi's schema rather than forked into it.
|
|
399
|
-
// ---------------------------------------------------------------------------
|
|
400
386
|
const PURPOSE_DESCRIPTION = [
|
|
401
387
|
'One short clause naming what this step is FOR, in the words of the person you',
|
|
402
388
|
'are working for — "Reading the Acme thread", not "Fetching emails" and never',
|
|
@@ -437,7 +423,7 @@ export function createValveToolDefinition(nodeId, contextDir, cwd, exposePurpose
|
|
|
437
423
|
return { ...schemaDefinition, execute };
|
|
438
424
|
}
|
|
439
425
|
export default function (pi) {
|
|
440
|
-
const nodeId =
|
|
426
|
+
const nodeId = envNodeId();
|
|
441
427
|
if (nodeId === undefined || nodeId.trim() === '')
|
|
442
428
|
return; // inert — builtin bash stays
|
|
443
429
|
const contextDir = process.env['CRTR_CONTEXT_DIR'];
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
//
|
|
23
23
|
// Plain TS-with-types — no imports from @earendil-works/* so this compiles inside
|
|
24
24
|
// crouter's own tsc build without a dep on the pi packages.
|
|
25
|
+
import { envNodeId } from '../shared/env.js';
|
|
25
26
|
import { CONTEXT_INTRO_CUSTOM_TYPE, formatCard, parseCard } from '../shared/generated-context.js';
|
|
26
27
|
import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
|
|
27
28
|
import { buildContextBearingsFromState, buildForkBearingsFromState, buildProjectContextBlockForBroker, renderPreferencesFromState, } from '../core/runtime/broker-extension-render.js';
|
|
@@ -35,9 +36,7 @@ import { truncateToWidth } from './truncate.js';
|
|
|
35
36
|
* the block builder in `core/runtime/bearings.ts` because the warm-pool claim's
|
|
36
37
|
* live delivery needs it too; re-exported here as this module's own surface. */
|
|
37
38
|
export { CONTEXT_INTRO_CUSTOM_TYPE };
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
39
|
// Block builder
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
40
|
/** Build the full bearings baseline for a conversation without inherited
|
|
42
41
|
* crouter context. Exported for testing. */
|
|
43
42
|
export async function buildContextIntro(nodeId, target) {
|
|
@@ -50,9 +49,7 @@ export async function buildForkContextIntro(nodeId) {
|
|
|
50
49
|
const state = await brokerExtensionState(nodeId);
|
|
51
50
|
return buildForkBearingsFromState(state, situationalContextEnvelope(nodeId) ?? '');
|
|
52
51
|
}
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
52
|
// Collapsed-by-default rendering
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
53
|
/** Pull the plain text out of a custom message's content (string or blocks). */
|
|
57
54
|
function messageText(message) {
|
|
58
55
|
if (typeof message.content === 'string')
|
|
@@ -127,16 +124,14 @@ export function renderContextMessage(message, options, theme) {
|
|
|
127
124
|
},
|
|
128
125
|
};
|
|
129
126
|
}
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
127
|
// Extension
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
128
|
/** Register new-context baseline assembly; a strict resume changes nothing. */
|
|
134
129
|
export function registerCanvasContextIntro(pi) {
|
|
135
130
|
// Collapse the block to a one-liner until the user expands tool output (Ctrl+O).
|
|
136
131
|
// Harmless to register outside TUI mode (it's only consulted while rendering).
|
|
137
132
|
pi.registerMessageRenderer(CONTEXT_INTRO_CUSTOM_TYPE, renderContextMessage);
|
|
138
133
|
pi.on('session_start', async (event, ctx) => {
|
|
139
|
-
const nodeId =
|
|
134
|
+
const nodeId = envNodeId();
|
|
140
135
|
if (nodeId === undefined || nodeId.trim() === '')
|
|
141
136
|
return;
|
|
142
137
|
const reason = event?.reason;
|
|
@@ -29,9 +29,23 @@ interface ToolResultCtxLike {
|
|
|
29
29
|
type ToolResultResultLike = {
|
|
30
30
|
content: ContentBlockLike[];
|
|
31
31
|
} | void;
|
|
32
|
+
/** tool_call: fired BEFORE the tool runs. Returning `{block: true, reason}`
|
|
33
|
+
* stops it — nothing executes, no `tool_result` fires, and `reason` becomes the
|
|
34
|
+
* call's synthesized error result, carrying arbitrary-length text. */
|
|
35
|
+
interface ToolCallEventLike {
|
|
36
|
+
toolName: string;
|
|
37
|
+
input?: unknown;
|
|
38
|
+
}
|
|
39
|
+
type ToolCallResultLike = {
|
|
40
|
+
block: true;
|
|
41
|
+
reason: string;
|
|
42
|
+
} | void;
|
|
32
43
|
interface PiLike {
|
|
33
44
|
on(event: 'before_agent_start', handler: (event: BeforeAgentStartEventLike) => BeforeAgentStartResultLike | void | Promise<BeforeAgentStartResultLike | void>): void;
|
|
34
45
|
on(event: 'session_start', handler: (event: unknown, ctx: unknown) => void | Promise<void>): void;
|
|
46
|
+
on(event: 'session_compact', handler: (event: unknown, ctx: unknown) => void | Promise<void>): void;
|
|
47
|
+
on(event: 'turn_end', handler: (event: unknown, ctx: unknown) => void | Promise<void>): void;
|
|
48
|
+
on(event: 'tool_call', handler: (event: ToolCallEventLike, ctx: unknown) => ToolCallResultLike | Promise<ToolCallResultLike>): void;
|
|
35
49
|
on(event: 'tool_result', handler: (event: ToolResultEventLike, ctx: ToolResultCtxLike) => ToolResultResultLike | Promise<ToolResultResultLike>): void;
|
|
36
50
|
}
|
|
37
51
|
export declare function registerCanvasDocSubstrate(pi: PiLike): void;
|
|
@@ -7,29 +7,62 @@
|
|
|
7
7
|
//
|
|
8
8
|
// before_agent_start splices the context's frozen preference snapshot after the
|
|
9
9
|
// native tool list. Successful read/bash results append matching memory through
|
|
10
|
-
// the shared context exposure state.
|
|
10
|
+
// the shared context exposure state. A bash call matching a `pre-command` doc is
|
|
11
|
+
// BLOCKED before it runs and the doc comes back as its synthesized error result.
|
|
12
|
+
// Pure rendering lives in core/substrate/.
|
|
11
13
|
//
|
|
12
14
|
// Plain TS-with-types — NO imports from @earendil-works/* (a local structural
|
|
13
15
|
// PiLike interface stands in), so it compiles inside crouter's own tsc build
|
|
14
16
|
// with no dep on the pi packages. Mirrors canvas-context-intro.ts.
|
|
17
|
+
import { envNodeId } from '../shared/env.js';
|
|
15
18
|
import { homedir } from 'node:os';
|
|
16
19
|
import { join, resolve } from 'node:path';
|
|
17
20
|
import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
|
|
18
21
|
import { renderPreferencesFromState } from '../core/runtime/broker-extension-render.js';
|
|
19
|
-
import { renderOnCommandDocsForSubject, renderOnReadDocsForSubject } from '../core/substrate/on-read.js';
|
|
22
|
+
import { corpusHasPreCommandSurfaces, demotePreCommandExposures, renderOnCommandDocsForSubject, renderOnReadDocsForSubject, renderPreCommandDocsForSubject, } from '../core/substrate/on-read.js';
|
|
20
23
|
import { clearSessionCache } from '../core/substrate/session-cache.js';
|
|
21
24
|
import { cloneContextExposureState, exposureTarget, freezePreferenceSnapshot, loadContextExposureState, mergeContextExposureState, replaceContextExposureState, saveContextExposureState, sharedContextExposureState, } from '../core/substrate/injected-store.js';
|
|
22
25
|
import { autoLoadedContextInner, mergeAutoLoadedContext } from './envelope-merge.js';
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
26
|
// Extension
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
27
|
/** The stable seam that closes pi's native "Available tools" list. We splice the
|
|
27
28
|
* substrate's boot sections right before it — the agent decides which capability
|
|
28
29
|
* to reach for while reading the tools, so memory docs/preferences must sit
|
|
29
30
|
* there, not far below. Falls back to appending if absent. */
|
|
30
31
|
const TOOLS_ANCHOR = '\n\nGuidelines:';
|
|
32
|
+
/** The bash command string a tool call carries, or `null` when there is none. */
|
|
33
|
+
function bashCommandOf(input) {
|
|
34
|
+
const command = input?.command;
|
|
35
|
+
return typeof command === 'string' && command.trim() !== '' ? command : null;
|
|
36
|
+
}
|
|
37
|
+
/** The blocked call's synthesized error result. A blocked command is discarded
|
|
38
|
+
* IN FULL — pi executes none of it — so the copy must steer a re-issue of the
|
|
39
|
+
* WHOLE command, never of one clause: when the same command also built its own
|
|
40
|
+
* input (a heredoc, a `>` redirect, a `$(…)`), that setup did not run either,
|
|
41
|
+
* and re-issuing only the tail would act on stale or missing data. */
|
|
42
|
+
function heldReason(rendered) {
|
|
43
|
+
return [
|
|
44
|
+
'HELD — nothing ran.',
|
|
45
|
+
'',
|
|
46
|
+
'Your bash command was NOT executed. It matched memory you had not yet read, and that memory is below.',
|
|
47
|
+
'',
|
|
48
|
+
'Read it, then re-issue your WHOLE command unchanged — every clause of it, including any heredoc, redirect, or `$(…)` that built its input, because none of that ran either. The re-issue executes: this memory is now in your transcript, so there is nothing left to deliver and nothing left to hold.',
|
|
49
|
+
'',
|
|
50
|
+
rendered,
|
|
51
|
+
].join('\n');
|
|
52
|
+
}
|
|
53
|
+
/** The follow-on notice for a SIBLING command in the same assistant message.
|
|
54
|
+
* pi preflights siblings sequentially and then executes them concurrently, so
|
|
55
|
+
* without one flag per turn the second sibling would find the doc already
|
|
56
|
+
* exposed and slip past the hold its sibling just raised. */
|
|
57
|
+
const HELD_SIBLING_REASON = [
|
|
58
|
+
'HELD — nothing ran.',
|
|
59
|
+
'',
|
|
60
|
+
'Your bash command was NOT executed. Another command in this same message was held against memory you had not read, and that memory was returned with it.',
|
|
61
|
+
'',
|
|
62
|
+
'Read it, then re-issue your WHOLE command unchanged. The re-issue executes.',
|
|
63
|
+
].join('\n');
|
|
31
64
|
export function registerCanvasDocSubstrate(pi) {
|
|
32
|
-
const nodeId =
|
|
65
|
+
const nodeId = envNodeId();
|
|
33
66
|
if (nodeId === undefined || nodeId.trim() === '')
|
|
34
67
|
return; // not a canvas node — inert
|
|
35
68
|
// One in-place state object is shared with context-intro and rehydrated on a
|
|
@@ -57,6 +90,45 @@ export function registerCanvasDocSubstrate(pi) {
|
|
|
57
90
|
systemPrompt: `${event.systemPrompt.slice(0, idx)}\n\n${block}${event.systemPrompt.slice(idx)}`,
|
|
58
91
|
};
|
|
59
92
|
});
|
|
93
|
+
// One flag per assistant turn: the first hold covers every matching command
|
|
94
|
+
// in the same message.
|
|
95
|
+
let blockedThisTurn = false;
|
|
96
|
+
pi.on('turn_end', () => {
|
|
97
|
+
blockedThisTurn = false;
|
|
98
|
+
});
|
|
99
|
+
// Compaction drops delivered guidance out of the transcript while the ledger
|
|
100
|
+
// still records it as delivered. Forget those deliveries so the holds re-arm.
|
|
101
|
+
pi.on('session_compact', () => {
|
|
102
|
+
mergeContextExposureState(contextExposure, loadContextExposureState(nodeId));
|
|
103
|
+
demotePreCommandExposures(contextExposure);
|
|
104
|
+
saveContextExposureState(nodeId, contextExposure);
|
|
105
|
+
blockedThisTurn = false;
|
|
106
|
+
});
|
|
107
|
+
pi.on('tool_call', async (event) => {
|
|
108
|
+
if (event.toolName !== 'bash')
|
|
109
|
+
return;
|
|
110
|
+
const command = bashCommandOf(event.input);
|
|
111
|
+
if (command === null)
|
|
112
|
+
return;
|
|
113
|
+
// The common case is a corpus with no pre-command doc at all, and it must
|
|
114
|
+
// cost nothing — short-circuit before the daemon round-trip below.
|
|
115
|
+
if (!corpusHasPreCommandSurfaces())
|
|
116
|
+
return;
|
|
117
|
+
if (blockedThisTurn)
|
|
118
|
+
return { block: true, reason: HELD_SIBLING_REASON };
|
|
119
|
+
const state = await brokerExtensionState(nodeId);
|
|
120
|
+
mergeContextExposureState(contextExposure, loadContextExposureState(nodeId));
|
|
121
|
+
const next = cloneContextExposureState(contextExposure);
|
|
122
|
+
const rendered = renderPreCommandDocsForSubject(state.subject, command, exposureTarget(next, 'transcript'));
|
|
123
|
+
// Everything this command matches is already in the transcript: the hold is
|
|
124
|
+
// released and the command runs.
|
|
125
|
+
if (rendered === '')
|
|
126
|
+
return;
|
|
127
|
+
blockedThisTurn = true;
|
|
128
|
+
saveContextExposureState(nodeId, next);
|
|
129
|
+
replaceContextExposureState(contextExposure, next);
|
|
130
|
+
return { block: true, reason: heldReason(rendered) };
|
|
131
|
+
});
|
|
60
132
|
pi.on('tool_result', async (event, ctx) => {
|
|
61
133
|
if (event.isError === true)
|
|
62
134
|
return;
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
//
|
|
30
30
|
// Plain TS-with-types — no imports from @earendil-works/* so this compiles inside
|
|
31
31
|
// crouter's own tsc build without a dep on the pi packages.
|
|
32
|
+
import { envNodeId } from '../shared/env.js';
|
|
32
33
|
import { captureGoalIfAbsent, generateAndCommitInitialName, } from './broker-local.js';
|
|
33
34
|
import { parseCard } from '../shared/generated-context.js';
|
|
34
35
|
/**
|
|
@@ -41,7 +42,7 @@ import { parseCard } from '../shared/generated-context.js';
|
|
|
41
42
|
export function registerCanvasGoalCapture(pi) {
|
|
42
43
|
pi.on('input', (event) => {
|
|
43
44
|
try {
|
|
44
|
-
const nodeId =
|
|
45
|
+
const nodeId = envNodeId();
|
|
45
46
|
if (nodeId === undefined || nodeId.trim() === '')
|
|
46
47
|
return; // not a canvas node
|
|
47
48
|
const text = (event.text ?? '').trim();
|