@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
|
@@ -13,23 +13,18 @@
|
|
|
13
13
|
// generation so settlement records an automatic pi→provider connection fault;
|
|
14
14
|
// the existing fault-retry machinery owns re-driving the turn. The broker
|
|
15
15
|
// process — and its in-memory queues — survives instead of being killed.
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (raw === undefined)
|
|
29
|
-
return DEFAULT_STREAM_WATCHDOG_MS;
|
|
30
|
-
const n = Number(raw);
|
|
31
|
-
return Number.isFinite(n) && n > 0 ? n : DEFAULT_STREAM_WATCHDOG_MS;
|
|
32
|
-
}
|
|
16
|
+
// The default is 5 minutes, comfortably under the daemon's 20-minute
|
|
17
|
+
// WEDGE_QUIET_MS hard kick (crtrd.ts), so this is the common-case recovery
|
|
18
|
+
// path and the daemon kick becomes the rare backstop. Generous enough that a
|
|
19
|
+
// live, healthy stream never trips it: a single LLM turn's own network
|
|
20
|
+
// round-trips (message_update deltas, tool_execution_start/update/end) land
|
|
21
|
+
// well inside 5 minutes. The watchdog covers ONLY time awaiting the provider:
|
|
22
|
+
// the broker disarms it while a tool is executing (a legitimately silent tool
|
|
23
|
+
// can run longer than the timeout — see broker.ts relayEvent) and rearms it
|
|
24
|
+
// once the last in-flight tool ends, so a slow-but-live turn is never mistaken
|
|
25
|
+
// for a dead one. See `envStreamWatchdogMs()` (`shared/env.ts`) for the parsed
|
|
26
|
+
// `CRTR_STREAM_WATCHDOG_MS` accessor and default value.
|
|
27
|
+
import { envStreamWatchdogMs } from '../../shared/env.js';
|
|
33
28
|
/**
|
|
34
29
|
* A single-timer dead-man's switch: `poke(token)` (re)arms it, `clear()`
|
|
35
30
|
* disarms it, and `onStall(token)` fires exactly once if `poke()` isn't called
|
|
@@ -50,7 +45,7 @@ export class StreamWatchdog {
|
|
|
50
45
|
ms;
|
|
51
46
|
constructor(onStall, ms) {
|
|
52
47
|
this.onStall = onStall;
|
|
53
|
-
this.ms = ms ??
|
|
48
|
+
this.ms = ms ?? envStreamWatchdogMs();
|
|
54
49
|
}
|
|
55
50
|
/** Proof of progress — (re)arm the timer for `token`, canceling any prior
|
|
56
51
|
* countdown. The fired `onStall` receives THIS arm's token. Inert while
|
|
@@ -10,9 +10,7 @@ import { normalizeTmuxPassthrough } from '../config.js';
|
|
|
10
10
|
import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
|
|
11
11
|
import { surfaceTmuxStyleArgs } from './surface-bg.js';
|
|
12
12
|
import { runTmux as tmux, shellQuote } from './tmux-driver.js';
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
13
|
// Crouter-owned tmux bindings
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
14
|
const TMUX_BINDING_MANIFEST = '@crtr_keybindings_manifest';
|
|
17
15
|
const ROOT_TABLE = 'root';
|
|
18
16
|
const LEAF_MENU_ACTIONS = {
|
|
@@ -7,6 +7,18 @@ declare function tmux(args: string[]): {
|
|
|
7
7
|
stderr: string;
|
|
8
8
|
};
|
|
9
9
|
export declare function inTmux(): boolean;
|
|
10
|
+
/** Is any tmux SESSION currently up? `tmux list-sessions` exits 0 iff a server
|
|
11
|
+
* is running with at least one session, non-zero otherwise (including "no
|
|
12
|
+
* server"). This is the boot-time/bash-tooling probe — distinct from
|
|
13
|
+
* `tmuxClientsReachable`, which asks whether a server is reachable at all
|
|
14
|
+
* regardless of session count and is the human-surface gate. */
|
|
15
|
+
export declare function tmuxSessionsReachable(): boolean;
|
|
16
|
+
/** Is the tmux SERVER reachable at all? `tmux list-clients` exits 0 whenever a
|
|
17
|
+
* server is up (even with zero attached clients), non-zero when no server is
|
|
18
|
+
* running. This is the human-surface gate (core/spawn.ts's
|
|
19
|
+
* `tmuxServerReachable`) — distinct from `tmuxSessionsReachable`, which
|
|
20
|
+
* additionally requires a session to exist. */
|
|
21
|
+
export declare function tmuxClientsReachable(): boolean;
|
|
10
22
|
/** The pane currently displaying `nodeId`, found by the authoritative `@crtr_node`
|
|
11
23
|
* pane tag — set by EVERY viewer (a bare `surface attach`, a focus split, a
|
|
12
24
|
* spawned viewer) on connect. Scans all panes (`list-panes -a`); the reverse of
|
|
@@ -7,9 +7,8 @@ import { execFile, spawnSync } from 'node:child_process';
|
|
|
7
7
|
import { encodeAttachControlInput, } from '../keybindings/index.js';
|
|
8
8
|
import { bundledPiCommandForShell } from './pi-cli.js';
|
|
9
9
|
import { shellQuote } from '../../shared/shell-quote.js';
|
|
10
|
-
|
|
10
|
+
import { envPiBinaryOverride } from '../../shared/env.js';
|
|
11
11
|
// tmux invocation
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
12
|
export { shellQuote };
|
|
14
13
|
function tmux(args) {
|
|
15
14
|
const r = spawnSync('tmux', args, { encoding: 'utf8' });
|
|
@@ -25,6 +24,22 @@ function tmuxOnSocket(socket, args) {
|
|
|
25
24
|
export function inTmux() {
|
|
26
25
|
return process.env['TMUX'] !== undefined && process.env['TMUX'] !== '';
|
|
27
26
|
}
|
|
27
|
+
/** Is any tmux SESSION currently up? `tmux list-sessions` exits 0 iff a server
|
|
28
|
+
* is running with at least one session, non-zero otherwise (including "no
|
|
29
|
+
* server"). This is the boot-time/bash-tooling probe — distinct from
|
|
30
|
+
* `tmuxClientsReachable`, which asks whether a server is reachable at all
|
|
31
|
+
* regardless of session count and is the human-surface gate. */
|
|
32
|
+
export function tmuxSessionsReachable() {
|
|
33
|
+
return spawnSync('tmux', ['list-sessions'], { encoding: 'utf8', stdio: 'ignore' }).status === 0;
|
|
34
|
+
}
|
|
35
|
+
/** Is the tmux SERVER reachable at all? `tmux list-clients` exits 0 whenever a
|
|
36
|
+
* server is up (even with zero attached clients), non-zero when no server is
|
|
37
|
+
* running. This is the human-surface gate (core/spawn.ts's
|
|
38
|
+
* `tmuxServerReachable`) — distinct from `tmuxSessionsReachable`, which
|
|
39
|
+
* additionally requires a session to exist. */
|
|
40
|
+
export function tmuxClientsReachable() {
|
|
41
|
+
return spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], { encoding: 'utf8' }).status === 0;
|
|
42
|
+
}
|
|
28
43
|
/** The pane currently displaying `nodeId`, found by the authoritative `@crtr_node`
|
|
29
44
|
* pane tag — set by EVERY viewer (a bare `surface attach`, a focus split, a
|
|
30
45
|
* spawned viewer) on connect. Scans all panes (`list-panes -a`); the reverse of
|
|
@@ -63,9 +78,7 @@ export function currentTmux() {
|
|
|
63
78
|
const [session, window, pane] = r.stdout.split('\t');
|
|
64
79
|
return { session, window, pane };
|
|
65
80
|
}
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
81
|
// Sessions + windows
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
82
|
export function sessionExists(name) {
|
|
70
83
|
return tmux(['has-session', '-t', name]).ok;
|
|
71
84
|
}
|
|
@@ -517,9 +530,7 @@ export function panePid(pane) {
|
|
|
517
530
|
export function respawnPaneInPlace(pane) {
|
|
518
531
|
return tmux(['respawn-pane', '-k', '-t', pane]).ok;
|
|
519
532
|
}
|
|
520
|
-
// ---------------------------------------------------------------------------
|
|
521
533
|
// pi command assembly
|
|
522
|
-
// ---------------------------------------------------------------------------
|
|
523
534
|
/** Turn a pi argv array into a single shell command string.
|
|
524
535
|
*
|
|
525
536
|
* The binary defaults to `CRTR_PI_BINARY` when that env var is set, else the
|
|
@@ -530,12 +541,10 @@ export function respawnPaneInPlace(pane) {
|
|
|
530
541
|
* explicit `binary` arg still overrides the env. The value may be a multi-word
|
|
531
542
|
* launcher (e.g. `node --import tsx/esm host.ts`); only argv entries are
|
|
532
543
|
* shell-quoted, so a multi-word binary is spliced verbatim ahead of them. */
|
|
533
|
-
export function piCommand(argv, binary =
|
|
544
|
+
export function piCommand(argv, binary = envPiBinaryOverride() ?? bundledPiCommandForShell(shellQuote)) {
|
|
534
545
|
return [binary, ...argv.map(shellQuote)].join(' ');
|
|
535
546
|
}
|
|
536
|
-
// ---------------------------------------------------------------------------
|
|
537
547
|
// Window liveness helpers (used by the supervisor daemon)
|
|
538
|
-
// ---------------------------------------------------------------------------
|
|
539
548
|
/** List all window ids present in `session`. Returns [] if the session does
|
|
540
549
|
* not exist or tmux fails for any reason. Each entry is the raw window id
|
|
541
550
|
* string reported by tmux (e.g. `@1`, `@2`, …). */
|
|
@@ -553,9 +562,7 @@ export function windowAlive(session, window) {
|
|
|
553
562
|
return false;
|
|
554
563
|
return listWindowIds(session).includes(window);
|
|
555
564
|
}
|
|
556
|
-
// ---------------------------------------------------------------------------
|
|
557
565
|
// Focus helpers (used by the placement layer)
|
|
558
|
-
// ---------------------------------------------------------------------------
|
|
559
566
|
/** Activate a window within its session (same-session navigation). Equivalent
|
|
560
567
|
* to `tmux select-window -t <session>:<window>`. Best-effort; never throws. */
|
|
561
568
|
export function selectWindow(session, window) {
|
|
@@ -582,9 +589,7 @@ export function selectPane(pane) {
|
|
|
582
589
|
export function switchClient(session) {
|
|
583
590
|
return tmux(['switch-client', '-t', session]).ok;
|
|
584
591
|
}
|
|
585
|
-
// ---------------------------------------------------------------------------
|
|
586
592
|
// send-keys (chrome — used by the prefix menu + nav bindings)
|
|
587
|
-
// ---------------------------------------------------------------------------
|
|
588
593
|
/** Type a literal (e.g. a `/graph` slash command) into a pane and press Enter
|
|
589
594
|
* (`tmux send-keys -t <pane> '<text>' Enter`). Requires the pane's editor be
|
|
590
595
|
* empty, same limitation as the menu's `/promote` item. Best-effort. */
|
|
@@ -182,9 +182,7 @@ function abandonSpare(nodeId, what, err) {
|
|
|
182
182
|
catch { /* best-effort */ }
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
185
|
// Reaping
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
186
|
/** How long an UNCLAIMED spare is worth its resident broker (~150-300 MB).
|
|
189
187
|
* A spare exists to make the NEXT create instant; one nobody has claimed in
|
|
190
188
|
* this long is memory rented for nothing, so it is returned. Over-reaping
|
|
@@ -308,9 +306,7 @@ function mint(recipe) {
|
|
|
308
306
|
}
|
|
309
307
|
})();
|
|
310
308
|
}
|
|
311
|
-
// ---------------------------------------------------------------------------
|
|
312
309
|
// Daemon-start pre-warm
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
310
|
/** How many profiles get a spare at daemon start. Each spare is a resident
|
|
315
311
|
* broker at ~150-300 MB, so this is deliberately tiny. */
|
|
316
312
|
const PREWARM_PROFILES = 2;
|
package/dist/core/scope.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { envProfileId } from '../shared/env.js';
|
|
1
2
|
import { homedir } from 'node:os';
|
|
2
3
|
import { existsSync, statSync } from 'node:fs';
|
|
3
4
|
import { join, resolve, dirname } from 'node:path';
|
|
@@ -105,7 +106,7 @@ function collectAncestorScopeRoots(startDir, userRoot) {
|
|
|
105
106
|
* (`findProjectScopeRootsWithMemory`), a projection layered onto this order
|
|
106
107
|
* rather than part of it: discovery provenance decides precedence, the
|
|
107
108
|
* relationship decides the ceiling, and the two never influence each other. */
|
|
108
|
-
export function findProjectScopeRoots(startDir = process.cwd(), profileId =
|
|
109
|
+
export function findProjectScopeRoots(startDir = process.cwd(), profileId = envProfileId() || null) {
|
|
109
110
|
return resolveProjectScopeRoots(startDir, profileId).roots;
|
|
110
111
|
}
|
|
111
112
|
/** `findProjectScopeRoots` with each root's profile memory ceiling attached —
|
|
@@ -113,7 +114,7 @@ export function findProjectScopeRoots(startDir = process.cwd(), profileId = proc
|
|
|
113
114
|
* walk takes that entry's `memory` (first entry in manifest order wins, even
|
|
114
115
|
* when cwd also reaches the root); every other root takes the neutral
|
|
115
116
|
* `content` ceiling. */
|
|
116
|
-
export function findProjectScopeRootsWithMemory(startDir = process.cwd(), profileId =
|
|
117
|
+
export function findProjectScopeRootsWithMemory(startDir = process.cwd(), profileId = envProfileId() || null) {
|
|
117
118
|
return resolveProjectScopeRoots(startDir, profileId).annotated;
|
|
118
119
|
}
|
|
119
120
|
function resolveProjectScopeRoots(startDir, profileId) {
|
|
@@ -195,7 +196,7 @@ export function ensureProjectScopeRoot(startDir = process.cwd()) {
|
|
|
195
196
|
// cache hit.
|
|
196
197
|
const resolvedStart = resolve(startDir);
|
|
197
198
|
const root = join(resolvedStart, CRTR_DIR_NAME);
|
|
198
|
-
const profileId =
|
|
199
|
+
const profileId = envProfileId() || null;
|
|
199
200
|
// Neutral ceiling: the empty cached stack proves no profile entry's ancestor
|
|
200
201
|
// walk reached this root, since it did not exist to be walked into.
|
|
201
202
|
scopeRootsCache.set(scopeRootsCacheKey(resolvedStart, profileId), {
|
package/dist/core/spawn.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (tmuxServerReachable) so it can decide whether to surface a prompt or degrade
|
|
5
5
|
// to the inbox-drain follow-up. The canvas runtime has its own
|
|
6
6
|
// one-window-per-node machinery in core/runtime/placement-tmux.ts.
|
|
7
|
-
import {
|
|
7
|
+
import { tmuxClientsReachable } from './runtime/tmux-driver.js';
|
|
8
8
|
/** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
|
|
9
9
|
* gate for the human surface — see tmuxServerReachable. A headless-broker child
|
|
10
10
|
* strips TMUX/TMUX_PANE (broker.ts, anti-stophook-hijack) yet can still drive
|
|
@@ -22,6 +22,5 @@ export function isInTmux() {
|
|
|
22
22
|
* no server is running — the true "no tmux to surface into" case that should
|
|
23
23
|
* degrade to the inbox-drain follow-up. */
|
|
24
24
|
export function tmuxServerReachable() {
|
|
25
|
-
|
|
26
|
-
return r.status === 0;
|
|
25
|
+
return tmuxClientsReachable();
|
|
27
26
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createHash } from
|
|
3
|
-
import { homedir } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { atomicWriteJson } from './fs-utils.js';
|
|
6
|
+
import { LockedJsonFile } from './locked-file.js';
|
|
6
7
|
export const ANTHROPIC_PROVIDER_ID = "anthropic";
|
|
7
8
|
export const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
|
8
9
|
export const INVALID_REFRESH_TOKEN_BACKOFF_MS = 30 * 24 * 60 * 60 * 1000;
|
|
@@ -123,14 +124,6 @@ function parsePoolUnderLock(path, current) {
|
|
|
123
124
|
throw new Error(`Subscription pool ${path} is corrupt: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
|
-
function writeJsonFile(path, value, mode) {
|
|
127
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
128
|
-
const tmp = `${path}.${process.pid}.tmp`;
|
|
129
|
-
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, mode === undefined ? "utf8" : { encoding: "utf8", mode });
|
|
130
|
-
if (mode !== undefined)
|
|
131
|
-
chmodSync(tmp, mode);
|
|
132
|
-
renameSync(tmp, path);
|
|
133
|
-
}
|
|
134
127
|
function normalizeStrength(value) {
|
|
135
128
|
return value === "ultra" || value === "strong" || value === "medium" || value === "light" ? value : undefined;
|
|
136
129
|
}
|
|
@@ -212,13 +205,13 @@ function assertCandidateRowsStorable(providerId, candidate) {
|
|
|
212
205
|
// The ONE door every write to a pool file goes through. Both pool invariants -- one account
|
|
213
206
|
// identity per pool, and every candidate row carrying a storable credential -- are properties
|
|
214
207
|
// of the FILE, not of whichever code path happens to be writing, so they are asserted here
|
|
215
|
-
// against the state this write replaces, under the caller's already-held lock. `
|
|
216
|
-
// is reached for the pool NOWHERE ELSE: that makes the invariants unbypassable.
|
|
208
|
+
// against the state this write replaces, under the caller's already-held lock. `atomicWriteJson`
|
|
209
|
+
// is reached for the pool NOWHERE ELSE in this file: that makes the invariants unbypassable.
|
|
217
210
|
function persistPool(providerId, poolFile, base, candidate) {
|
|
218
211
|
assertCandidateRowsStorable(providerId, candidate);
|
|
219
212
|
const next = normalizeCredentials(candidate);
|
|
220
213
|
logCredentialSetChange(providerId, base, next);
|
|
221
|
-
|
|
214
|
+
atomicWriteJson(poolFile, next, { mode: 0o600 });
|
|
222
215
|
return next;
|
|
223
216
|
}
|
|
224
217
|
// Accounts vanishing from a pool is the failure users actually feel ("I keep having to log in
|
|
@@ -721,7 +714,7 @@ export function writeRotationConfig(next) {
|
|
|
721
714
|
defaultFallbackStrength: normalizeStrength(next.defaultFallbackStrength) ?? current.defaultFallbackStrength ?? DEFAULT_FALLBACK_STRENGTH,
|
|
722
715
|
revertWhenAvailable: next.revertWhenAvailable ?? current.revertWhenAvailable ?? true,
|
|
723
716
|
};
|
|
724
|
-
|
|
717
|
+
atomicWriteJson(rotationConfigFile(), merged);
|
|
725
718
|
return merged;
|
|
726
719
|
}
|
|
727
720
|
export function formatStatusLine(providerId, now = Date.now()) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Regression: `pre-command` matching holds a command BEFORE it runs, so it
|
|
2
|
+
// costs the agent a whole turn when it fires. Two ways it can be wrong:
|
|
3
|
+
//
|
|
4
|
+
// • It fires on a mention rather than an invocation. A doc-writing command
|
|
5
|
+
// whose heredoc body merely quotes a matched invocation must run untouched
|
|
6
|
+
// — only the heredoc's opening line is a real command.
|
|
7
|
+
// • It ignores the entry's own `gate`. A doc whose entry excludes this node
|
|
8
|
+
// must never hold that node's commands.
|
|
9
|
+
//
|
|
10
|
+
// It also pins the shared-routing invariant: the pre-command matcher is the
|
|
11
|
+
// post-execution `command` matcher plus heredoc stripping, and nothing else.
|
|
12
|
+
// One command-glob semantics, not two.
|
|
13
|
+
//
|
|
14
|
+
// Run: node --conditions=crtr-src --import tsx/esm --test src/core/substrate/__tests__/surface-match-pre-command.test.ts
|
|
15
|
+
import { test } from 'node:test';
|
|
16
|
+
import assert from 'node:assert/strict';
|
|
17
|
+
import { matchesCommandEntry, matchesPreCommandEntry, preCommandDeliveryRung } from '../surface-match.js';
|
|
18
|
+
const SUBJECT = {
|
|
19
|
+
kind: 'developer',
|
|
20
|
+
mode: 'base',
|
|
21
|
+
lifecycle: 'terminal',
|
|
22
|
+
hasManager: true,
|
|
23
|
+
cwd: '/tmp/project',
|
|
24
|
+
scope: 'project',
|
|
25
|
+
orchestration: { depth: 1 },
|
|
26
|
+
profile: 'northlight',
|
|
27
|
+
};
|
|
28
|
+
function entry(match, gate) {
|
|
29
|
+
return { on: 'pre-command', at: 'content', match, ...(gate !== undefined ? { gate } : {}) };
|
|
30
|
+
}
|
|
31
|
+
// the behaviour this event adds
|
|
32
|
+
test('a matched invocation inside a heredoc BODY does not hold the command', () => {
|
|
33
|
+
const command = [
|
|
34
|
+
"crtr memory write approvals --body <<'EOF'",
|
|
35
|
+
'Before you run `crtr integration run --action send`, get approval.',
|
|
36
|
+
'EOF',
|
|
37
|
+
].join('\n');
|
|
38
|
+
assert.equal(matchesPreCommandEntry(entry(['crtr integration run*']), SUBJECT, command), false);
|
|
39
|
+
});
|
|
40
|
+
test('the same invocation on the heredoc OPENING line still holds the command', () => {
|
|
41
|
+
const command = ["crtr integration run --action send --body <<'EOF'", 'hello', 'EOF'].join('\n');
|
|
42
|
+
assert.equal(matchesPreCommandEntry(entry(['crtr integration run*']), SUBJECT, command), true);
|
|
43
|
+
});
|
|
44
|
+
test('a `<<-` heredoc body is stripped too, tabs and all', () => {
|
|
45
|
+
const command = ['cat <<-EOF', '\tcrtr integration run --action send', '\tEOF'].join('\n');
|
|
46
|
+
assert.equal(matchesPreCommandEntry(entry(['crtr integration run*']), SUBJECT, command), false);
|
|
47
|
+
});
|
|
48
|
+
test('an unterminated heredoc drops its body rather than leaking it back in', () => {
|
|
49
|
+
const command = ["crtr memory write d --body <<'EOF'", 'crtr integration run --action send'].join('\n');
|
|
50
|
+
assert.equal(matchesPreCommandEntry(entry(['crtr integration run*']), SUBJECT, command), false);
|
|
51
|
+
});
|
|
52
|
+
test('an entry whose gate excludes the subject never holds a command', () => {
|
|
53
|
+
const excluded = entry(['crtr integration run*'], { lifecycle: 'resident' });
|
|
54
|
+
assert.equal(matchesPreCommandEntry(excluded, SUBJECT, 'crtr integration run --action send'), false);
|
|
55
|
+
const included = entry(['crtr integration run*'], { lifecycle: 'terminal' });
|
|
56
|
+
assert.equal(matchesPreCommandEntry(included, SUBJECT, 'crtr integration run --action send'), true);
|
|
57
|
+
});
|
|
58
|
+
test('a `command` entry is never consumed by the pre-command matcher, nor the reverse', () => {
|
|
59
|
+
const post = { on: 'command', at: 'content', match: ['crtr integration run*'] };
|
|
60
|
+
assert.equal(matchesPreCommandEntry(post, SUBJECT, 'crtr integration run --action send'), false);
|
|
61
|
+
assert.equal(matchesCommandEntry(entry(['crtr integration run*']), SUBJECT, 'crtr integration run --action send'), false);
|
|
62
|
+
});
|
|
63
|
+
// the rung fold
|
|
64
|
+
test('the rung fold returns the highest `at` across matching entries, and none when nothing matches', () => {
|
|
65
|
+
const doc = {
|
|
66
|
+
surfaces: [
|
|
67
|
+
entry(['crtr crustdata person search*']),
|
|
68
|
+
entry(['crtr integration run*']),
|
|
69
|
+
{ on: 'command', at: 'content', match: ['*'] },
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
assert.equal(preCommandDeliveryRung(doc, SUBJECT, 'crtr integration run --action send'), 'content');
|
|
73
|
+
assert.equal(preCommandDeliveryRung(doc, SUBJECT, 'ls -la'), 'none');
|
|
74
|
+
});
|
|
75
|
+
// shared routing: one command-glob semantics, not two
|
|
76
|
+
test('outside a heredoc, pre-command matching is exactly the `command` matcher', () => {
|
|
77
|
+
// The guard against a second command tokenizer drifting away from the first.
|
|
78
|
+
// Whatever `command` globs mean today, `pre-command` means the same thing —
|
|
79
|
+
// so a sharpening of command-position matching reaches both events at once.
|
|
80
|
+
const glob = 'crtr integration run*';
|
|
81
|
+
for (const command of [
|
|
82
|
+
'crtr integration run --action send',
|
|
83
|
+
'cd /tmp && crtr integration run --action send',
|
|
84
|
+
'echo hi\ncrtr integration run --action send',
|
|
85
|
+
'NL_ENV=1 crtr integration run --action send',
|
|
86
|
+
'crtr integration list',
|
|
87
|
+
'ls -la',
|
|
88
|
+
'echo "crtr integration run --action send"',
|
|
89
|
+
]) {
|
|
90
|
+
assert.equal(matchesPreCommandEntry(entry([glob]), SUBJECT, command), matchesCommandEntry({ on: 'command', at: 'content', match: [glob] }, SUBJECT, command), `pre-command and command disagreed on: ${JSON.stringify(command)}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
@@ -47,7 +47,7 @@ export function lintSubstrateSurfaces(value) {
|
|
|
47
47
|
return `invalid surfaces entry \`match-frontmatter\`: ${JSON.stringify(matchFrontmatter)} (expected a field→matcher object)`;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
if ((on === 'read' || on === 'memory-read' || on === 'command') && !hasMatch && matchFrontmatter === undefined) {
|
|
50
|
+
if ((on === 'read' || on === 'memory-read' || on === 'command' || on === 'pre-command') && !hasMatch && matchFrontmatter === undefined) {
|
|
51
51
|
return `invalid surfaces entry: a \`${on}\` entry requires \`match\`${on === 'read' ? ' or `match-frontmatter`' : ''}`;
|
|
52
52
|
}
|
|
53
53
|
const gate = entry['gate'];
|
|
@@ -24,6 +24,12 @@ export declare function contentExposureIdentity(body: string): string | null;
|
|
|
24
24
|
export declare function documentExposedAtOrAbove(state: ContextExposureState, path: string, body: string, rung: Rung): boolean;
|
|
25
25
|
/** Register both the physical document and its non-empty content alias. */
|
|
26
26
|
export declare function registerDocumentExposure(target: ExposureTarget, path: string, body: string, rung: Rung): void;
|
|
27
|
+
/** Forget that a document was delivered into the TRANSCRIPT — both its physical
|
|
28
|
+
* identity and its content alias — while leaving any `system` rank intact,
|
|
29
|
+
* because the system prompt is rebuilt on every agent run and survives
|
|
30
|
+
* compaction. This is what re-arms a `pre-command` hold for guidance that
|
|
31
|
+
* compaction dropped out of the transcript. */
|
|
32
|
+
export declare function demoteDocumentTranscriptExposure(state: ContextExposureState, path: string, body: string): void;
|
|
27
33
|
export declare function freezePreferenceSnapshot(state: ContextExposureState, rendered: string): string;
|
|
28
34
|
export declare function cloneContextExposureState(source: ContextExposureState): ContextExposureState;
|
|
29
35
|
export declare function hasTranscriptExposure(state: ContextExposureState): boolean;
|
|
@@ -49,6 +49,30 @@ export function registerDocumentExposure(target, path, body, rung) {
|
|
|
49
49
|
if (contentIdentity !== null)
|
|
50
50
|
registerExposure(target, contentIdentity, rung);
|
|
51
51
|
}
|
|
52
|
+
/** Drop one identity's transcript rank, deleting the entry outright when no
|
|
53
|
+
* `system` rank remains. The v3 loader rejects an entry carrying neither rank,
|
|
54
|
+
* and the writer serializes the map as-is, so leaving `{}` behind would crash
|
|
55
|
+
* the node's substrate on its next load. */
|
|
56
|
+
function dropTranscriptExposure(state, identity) {
|
|
57
|
+
const ranks = state.exposures.get(identity);
|
|
58
|
+
if (ranks === undefined)
|
|
59
|
+
return;
|
|
60
|
+
if (validRank(ranks.system))
|
|
61
|
+
state.exposures.set(identity, { system: ranks.system });
|
|
62
|
+
else
|
|
63
|
+
state.exposures.delete(identity);
|
|
64
|
+
}
|
|
65
|
+
/** Forget that a document was delivered into the TRANSCRIPT — both its physical
|
|
66
|
+
* identity and its content alias — while leaving any `system` rank intact,
|
|
67
|
+
* because the system prompt is rebuilt on every agent run and survives
|
|
68
|
+
* compaction. This is what re-arms a `pre-command` hold for guidance that
|
|
69
|
+
* compaction dropped out of the transcript. */
|
|
70
|
+
export function demoteDocumentTranscriptExposure(state, path, body) {
|
|
71
|
+
dropTranscriptExposure(state, realpathOrSelf(path));
|
|
72
|
+
const contentIdentity = contentExposureIdentity(body);
|
|
73
|
+
if (contentIdentity !== null)
|
|
74
|
+
dropTranscriptExposure(state, contentIdentity);
|
|
75
|
+
}
|
|
52
76
|
export function freezePreferenceSnapshot(state, rendered) {
|
|
53
77
|
if (state.preferenceSnapshot === null)
|
|
54
78
|
state.preferenceSnapshot = rendered;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ExposureTarget } from './injected-store.js';
|
|
1
|
+
import { type ContextExposureState, type ExposureTarget } from './injected-store.js';
|
|
2
2
|
import { type Rung, type SubstrateDoc } from './schema.js';
|
|
3
3
|
import type { NodeConfigSubject } from './subject-fields.js';
|
|
4
4
|
interface EventCandidate {
|
|
@@ -30,4 +30,20 @@ export declare function memoryReadDocBlocks(subject: NodeConfigSubject | null, e
|
|
|
30
30
|
* The corpus is the resolved cwd/profile set; a command has no file from which
|
|
31
31
|
* to discover enclosing project stores. */
|
|
32
32
|
export declare function renderOnCommandDocsForSubject(subject: NodeConfigSubject, command: string, target?: ExposureTarget): string;
|
|
33
|
+
/** Surface docs whose `pre-command` entries match a bash command that has NOT
|
|
34
|
+
* run yet. The exact twin of the post-execution renderer above, and its empty
|
|
35
|
+
* string is load-bearing: `''` means every matching doc is already exposed at
|
|
36
|
+
* its matching rung or above, which is the release — the caller lets the
|
|
37
|
+
* command through. */
|
|
38
|
+
export declare function renderPreCommandDocsForSubject(subject: NodeConfigSubject, command: string, target?: ExposureTarget): string;
|
|
39
|
+
/** Does ANY doc in this session's corpus carry a `pre-command` entry? The
|
|
40
|
+
* short-circuit a pre-execution caller checks first: with no such doc — the
|
|
41
|
+
* common case — no command need ever pay for a subject lookup. */
|
|
42
|
+
export declare function corpusHasPreCommandSurfaces(): boolean;
|
|
43
|
+
/** Forget the transcript exposure of every `pre-command` doc, re-arming their
|
|
44
|
+
* holds. Compaction drops delivered guidance out of the transcript while the
|
|
45
|
+
* ledger still claims it was delivered, so without this pass a doc could be
|
|
46
|
+
* held once and then silently absent for the rest of the session. Lives here
|
|
47
|
+
* rather than in the extension because the corpus accessor is private. */
|
|
48
|
+
export declare function demotePreCommandExposures(state: ContextExposureState): void;
|
|
33
49
|
export {};
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// • `command` — a `bash` tool call evaluates the executed command string
|
|
23
23
|
// against every doc's `command` entries (per shell segment, wildcards at
|
|
24
24
|
// token granularity — see surface-match.ts).
|
|
25
|
+
// • `pre-command` — a `bash` tool call ABOUT to run evaluates the same way
|
|
26
|
+
// against every doc's `pre-command` entries, and the call is held until
|
|
27
|
+
// the matching docs have been delivered. Because delivery registers
|
|
28
|
+
// exposure, the agent's re-issue of the same command finds nothing left to
|
|
29
|
+
// deliver and runs.
|
|
25
30
|
//
|
|
26
31
|
// All channels register through one context exposure target, so a document
|
|
27
32
|
// already loaded at the same or a higher rung does not repeat. Each candidate
|
|
@@ -37,8 +42,8 @@ import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
|
37
42
|
import { listAllMemoryDocs, listProjectMemoryDocs, loadStoreMemoryDocs, openProjectMemoryStore } from '../memory-resolver.js';
|
|
38
43
|
import { userScopeRoot } from '../scope.js';
|
|
39
44
|
import { gatePasses } from './gate.js';
|
|
40
|
-
import { commandDeliveryRung, memoryReadDeliveryRung, owningRootOf, readDeliveryRung, workspaceOpenRung } from './surface-match.js';
|
|
41
|
-
import { documentExposedAtOrAbove, emptyContextExposureState, exposureTarget, registerDocumentExposure, } from './injected-store.js';
|
|
45
|
+
import { commandDeliveryRung, memoryReadDeliveryRung, owningRootOf, preCommandDeliveryRung, readDeliveryRung, workspaceOpenRung } from './surface-match.js';
|
|
46
|
+
import { demoteDocumentTranscriptExposure, documentExposedAtOrAbove, emptyContextExposureState, exposureTarget, registerDocumentExposure, } from './injected-store.js';
|
|
42
47
|
import { minRung, parseSubstrateDoc, previewLine } from './schema.js';
|
|
43
48
|
import { cachedEventCorpusInclusive } from './session-cache.js';
|
|
44
49
|
const JUNK_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.cache', '.yalc']);
|
|
@@ -243,3 +248,32 @@ export function renderOnCommandDocsForSubject(subject, command, target = transie
|
|
|
243
248
|
const candidates = resolvedDocs().map(({ doc }) => ({ doc, rung: commandDeliveryRung(doc, subject, command) }));
|
|
244
249
|
return renderCandidates(subject, candidates, target);
|
|
245
250
|
}
|
|
251
|
+
/** Surface docs whose `pre-command` entries match a bash command that has NOT
|
|
252
|
+
* run yet. The exact twin of the post-execution renderer above, and its empty
|
|
253
|
+
* string is load-bearing: `''` means every matching doc is already exposed at
|
|
254
|
+
* its matching rung or above, which is the release — the caller lets the
|
|
255
|
+
* command through. */
|
|
256
|
+
export function renderPreCommandDocsForSubject(subject, command, target = transientTranscriptTarget()) {
|
|
257
|
+
const candidates = resolvedDocs().map(({ doc }) => ({ doc, rung: preCommandDeliveryRung(doc, subject, command) }));
|
|
258
|
+
return renderCandidates(subject, candidates, target);
|
|
259
|
+
}
|
|
260
|
+
function carriesPreCommandEntry(doc) {
|
|
261
|
+
return doc.surfaces.some((entry) => entry.on === 'pre-command');
|
|
262
|
+
}
|
|
263
|
+
/** Does ANY doc in this session's corpus carry a `pre-command` entry? The
|
|
264
|
+
* short-circuit a pre-execution caller checks first: with no such doc — the
|
|
265
|
+
* common case — no command need ever pay for a subject lookup. */
|
|
266
|
+
export function corpusHasPreCommandSurfaces() {
|
|
267
|
+
return resolvedDocs().some(({ doc }) => carriesPreCommandEntry(doc));
|
|
268
|
+
}
|
|
269
|
+
/** Forget the transcript exposure of every `pre-command` doc, re-arming their
|
|
270
|
+
* holds. Compaction drops delivered guidance out of the transcript while the
|
|
271
|
+
* ledger still claims it was delivered, so without this pass a doc could be
|
|
272
|
+
* held once and then silently absent for the rest of the session. Lives here
|
|
273
|
+
* rather than in the extension because the corpus accessor is private. */
|
|
274
|
+
export function demotePreCommandExposures(state) {
|
|
275
|
+
for (const { doc } of resolvedDocs()) {
|
|
276
|
+
if (carriesPreCommandEntry(doc))
|
|
277
|
+
demoteDocumentTranscriptExposure(state, doc.path, doc.body);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -140,9 +140,7 @@ function bootCorpus() {
|
|
|
140
140
|
}
|
|
141
141
|
return { docs, sources };
|
|
142
142
|
}
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
143
|
// Exposure registration
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
144
|
function rendersAtBoot(doc) {
|
|
147
145
|
return isVisible(doc.bootRung) && (doc.bootRung !== 'content' || doc.body.trim() !== '');
|
|
148
146
|
}
|
|
@@ -156,9 +154,7 @@ function recordRendered(target, docs) {
|
|
|
156
154
|
registerDocumentExposure(target, d.path, d.body, d.bootRung);
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
157
|
// Step 2 — display ordering + grouped render.
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
158
|
/** Content-prose ordering rank: general → specific. builtin=0, user=1,
|
|
163
159
|
* profile=2, project ranks start at 3 and climb by root nearness — the
|
|
164
160
|
* OUTERMOST ancestor `.crouter/` root ranks lowest (renders first), the
|
|
@@ -412,9 +408,7 @@ function buildTree(docs, rootLabel) {
|
|
|
412
408
|
renderChildren(root, '', lines);
|
|
413
409
|
return lines.join('\n');
|
|
414
410
|
}
|
|
415
|
-
// ---------------------------------------------------------------------------
|
|
416
411
|
// Block prose — the intro legend + the update directive for each kind.
|
|
417
|
-
// ---------------------------------------------------------------------------
|
|
418
412
|
const READ_LEGEND = 'Each %s below shows either its full content (indented beneath its name), a ' +
|
|
419
413
|
'`# read when:` line telling you when to read the full document, or simply its name.';
|
|
420
414
|
const PREFERENCES_INTRO = 'Preferences are how the user wants you to behave — standing directives and corrections. ' +
|
|
@@ -487,9 +481,7 @@ export function buildSubPersonaMenu(kind) {
|
|
|
487
481
|
'dragging unrelated context into it.\n\n' +
|
|
488
482
|
lines.join('\n'));
|
|
489
483
|
}
|
|
490
|
-
// ---------------------------------------------------------------------------
|
|
491
484
|
// 1. Preferences — protocol prose + `<memory kind="preference">` (system prompt).
|
|
492
|
-
// ---------------------------------------------------------------------------
|
|
493
485
|
/** The system-prompt substrate splice, TWO parts in one string.
|
|
494
486
|
*
|
|
495
487
|
* 1. The operating protocol: shipped (builtin/plugin) content-rung bodies as
|
|
@@ -548,9 +540,7 @@ function buildPreferencesBlock(subject, target) {
|
|
|
548
540
|
export function renderPreferencesForSubject(subject, target) {
|
|
549
541
|
return buildPreferencesBlock(subject, target);
|
|
550
542
|
}
|
|
551
|
-
// ---------------------------------------------------------------------------
|
|
552
543
|
// 2. Knowledge — `<memory kind="knowledge">` (the first-message bearings).
|
|
553
|
-
// ---------------------------------------------------------------------------
|
|
554
544
|
/** STEP 1 for the knowledge block, shared by the boot render and the
|
|
555
545
|
* config-change delta: the resolver's knowledge winners ∪ the node's own store
|
|
556
546
|
* (any kind), first-wins deduped resolver-first so a same-named node doc
|
|
@@ -589,9 +579,7 @@ export function renderKnowledgeForSubject(subject, nodeId, target) {
|
|
|
589
579
|
// Surfaced INTO this node: swap literal `$CRTR_CONTEXT_DIR` for the real path.
|
|
590
580
|
return interpolateNodePaths(`<memory kind="knowledge">\n${out}\n</memory>`, nodeId);
|
|
591
581
|
}
|
|
592
|
-
// ---------------------------------------------------------------------------
|
|
593
582
|
// 3. The config-change delta — the memory a node's NEW configuration exposes.
|
|
594
|
-
// ---------------------------------------------------------------------------
|
|
595
583
|
const DELTA_PROTOCOL_INTRO = 'Your node configuration changed. This operating protocol is in force for you now:';
|
|
596
584
|
const DELTA_PREFERENCES_INTRO = 'Your new configuration puts these preferences in force — standing directives and corrections ' +
|
|
597
585
|
'from the user. To read one, run `crtr memory read <name>`. ' +
|
|
@@ -15,7 +15,7 @@ export declare function rungAtLeast(r: Rung, min: Rung): boolean;
|
|
|
15
15
|
* authored rung against its project relationship's cap. */
|
|
16
16
|
export declare function minRung(a: Rung, b: Rung): Rung;
|
|
17
17
|
export { normalizeNameSegment, normalizeDocName, resolveLocalName as resolveDocName } from '../memory/identity.js';
|
|
18
|
-
export declare const SURFACE_EVENTS: readonly ["boot", "workspace-open", "read", "memory-read", "command"];
|
|
18
|
+
export declare const SURFACE_EVENTS: readonly ["boot", "workspace-open", "read", "memory-read", "command", "pre-command"];
|
|
19
19
|
export type SurfaceEvent = (typeof SURFACE_EVENTS)[number];
|
|
20
20
|
/** The rungs an entry may deliver at. There is no `none` — silence is the
|
|
21
21
|
* absence of an entry. (`Rung`'s `none` survives only as the internal
|
|
@@ -30,8 +30,8 @@ export type SurfaceRung = (typeof SURFACE_RUNGS)[number];
|
|
|
30
30
|
export interface SurfaceEntry {
|
|
31
31
|
on: SurfaceEvent;
|
|
32
32
|
/** Globs vs the event's subject namespace. Required on read/memory-read/
|
|
33
|
-
* command (a read entry may carry `matchFrontmatter` instead);
|
|
34
|
-
* on boot/workspace-open (presence of the entry is the match). */
|
|
33
|
+
* command/pre-command (a read entry may carry `matchFrontmatter` instead);
|
|
34
|
+
* meaningless on boot/workspace-open (presence of the entry is the match). */
|
|
35
35
|
match?: string[];
|
|
36
36
|
/** Predicate over the read file's own YAML frontmatter — `read` event only.
|
|
37
37
|
* Frontmatter key `match-frontmatter`. */
|