@north-light/crouter 0.3.233 → 0.3.234
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.js +16 -15
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
- package/dist/clients/attach/chrome/ticket-panel.js +15 -2
- package/dist/clients/attach/overlays/graph.js +0 -3
- package/dist/clients/attach/overlays/mcp.js +1 -2
- package/dist/clients/attach/render/chat-view.js +0 -12
- package/dist/clients/attach/render/crtr-output.js +0 -2
- package/dist/clients/attach/render/tool-calls.js +0 -6
- package/dist/clients/attach/slash/dispatch.js +8 -11
- package/dist/clients/attach/viewer.js +532 -534
- package/dist/clients/conversation/projection.js +0 -2
- package/dist/clients/inbox/controller.js +22 -3
- package/dist/clients/inbox/review/companion-pane.js +0 -4
- package/dist/clients/inbox/review-adapter.js +0 -7
- package/dist/clients/inbox/tui/slots.d.ts +3 -3
- package/dist/clients/inbox/tui/types.d.ts +2 -2
- package/dist/commands/api-client.js +0 -8
- package/dist/commands/attention.js +0 -8
- package/dist/commands/canvas-browse.js +2 -1
- package/dist/commands/canvas-config.js +0 -10
- package/dist/commands/canvas-history.js +2 -1
- package/dist/commands/cron.js +5 -18
- package/dist/commands/dashboard.js +0 -2
- package/dist/commands/human/feedback.js +2 -1
- package/dist/commands/human/prompts.js +2 -5
- package/dist/commands/human/queue.js +2 -7
- package/dist/commands/human/request.js +0 -14
- package/dist/commands/human/review.js +2 -1
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
- package/dist/commands/memory/find.js +2 -2
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/read.d.ts +2 -7
- package/dist/commands/memory/read.js +5 -16
- package/dist/commands/memory/shared.js +4 -7
- package/dist/commands/memory/write.js +4 -3
- package/dist/commands/node/bash.js +2 -1
- package/dist/commands/node/create.js +2 -1
- package/dist/commands/node/inspect.js +0 -6
- package/dist/commands/node/lifecycle.js +4 -13
- package/dist/commands/node/message.js +3 -2
- package/dist/commands/node/subscription.js +2 -1
- package/dist/commands/node/wait.js +4 -3
- package/dist/commands/node-context.js +2 -1
- package/dist/commands/node-lifecycle-revive.js +0 -2
- package/dist/commands/node-worktree.js +2 -1
- package/dist/commands/pkg/browse/actions.d.ts +1 -1
- package/dist/commands/pkg/browse/actions.js +4 -4
- package/dist/commands/pkg/browse/catalog.d.ts +1 -1
- package/dist/commands/pkg/browse/catalog.js +15 -15
- package/dist/commands/pkg/browse/command-view.d.ts +1 -1
- package/dist/commands/pkg/browse/command-view.js +3 -3
- package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
- package/dist/commands/pkg/browse/detail-panel.js +8 -8
- package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
- package/dist/commands/pkg/browse/doc-view.js +3 -3
- package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
- package/dist/commands/pkg/browse/list-panel.js +3 -3
- package/dist/commands/pkg/browse/model.d.ts +3 -3
- package/dist/commands/pkg/browse/shell.d.ts +2 -2
- package/dist/commands/pkg/browse/shell.js +13 -13
- package/dist/commands/pkg/plugin-manage.js +2 -1
- package/dist/commands/pkg/shared.js +0 -2
- package/dist/commands/profile/delete.js +2 -1
- package/dist/commands/push.js +2 -3
- package/dist/commands/revive.js +0 -2
- package/dist/commands/surface/node/placement.js +2 -1
- package/dist/commands/surface-edit.js +2 -1
- package/dist/commands/sys/config.js +0 -4
- package/dist/commands/sys/daemon.js +0 -10
- package/dist/commands/sys/doctor.js +3 -2
- package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
- package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
- package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
- package/dist/commands/sys/panels/dependencies-panel.js +2 -2
- package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
- package/dist/commands/sys/panels/keybindings-panel.js +5 -5
- package/dist/commands/sys/panels/models-panel.d.ts +4 -4
- package/dist/commands/sys/panels/models-panel.js +11 -10
- package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
- package/dist/commands/sys/panels/plugins-panel.js +2 -2
- package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
- package/dist/commands/sys/panels/prefs-panel.js +4 -4
- package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
- package/dist/commands/sys/panels/profiles-panel.js +14 -13
- package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
- package/dist/commands/sys/panels/provider-panel.js +8 -8
- package/dist/commands/sys/settings-shell.d.ts +3 -3
- package/dist/commands/sys/settings-shell.js +15 -15
- package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
- package/dist/commands/sys/setup-settings-runner.js +5 -5
- package/dist/commands/sys/setup-wizard.d.ts +4 -4
- package/dist/commands/sys/setup-wizard.js +8 -8
- package/dist/commands/sys/support/prepare.js +4 -4
- package/dist/commands/sys/support/submit.js +2 -2
- package/dist/commands/sys/sync-deps.js +0 -2
- package/dist/commands/sys/sync-project-guidance.js +2 -1
- package/dist/commands/sys/sysprompt.js +2 -1
- package/dist/core/__tests__/boot.test.js +0 -8
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
- package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
- package/dist/core/__tests__/canvas.test.js +1 -1
- package/dist/core/__tests__/child-death-wake.test.js +0 -2
- package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
- package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
- package/dist/core/__tests__/daemon-wedge.test.js +0 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
- package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
- package/dist/core/__tests__/focuses.test.js +0 -6
- package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
- package/dist/core/__tests__/fs-utils.test.js +50 -0
- package/dist/core/__tests__/helpers/harness.js +1 -1
- package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
- package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
- package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
- package/dist/core/__tests__/integration/command-plugins.test.js +0 -24
- package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
- package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
- package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
- package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
- package/dist/core/__tests__/integration/revive.test.js +0 -12
- package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
- package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
- package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
- package/dist/core/__tests__/lifecycle.test.js +0 -18
- package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
- package/dist/core/__tests__/migration.test.js +2 -2
- package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
- package/dist/core/__tests__/pid-identity-match.test.js +1 -1
- package/dist/core/__tests__/plugin-kinds.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +1 -1
- package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
- package/dist/core/__tests__/respawn-throttle.test.js +0 -4
- package/dist/core/__tests__/revive-all.test.js +5 -5
- package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
- package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
- package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
- package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
- package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
- package/dist/core/activity-signal.d.ts +7 -0
- package/dist/core/activity-signal.js +38 -0
- package/dist/core/auto-update.js +2 -1
- package/dist/core/bootstrap.js +2 -1
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
- package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
- package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
- package/dist/core/canvas/browse/app.js +3 -2
- package/dist/core/canvas/browse/model.d.ts +1 -1
- package/dist/core/canvas/browse/model.js +0 -4
- package/dist/core/canvas/canvas.d.ts +22 -0
- package/dist/core/canvas/canvas.js +50 -18
- package/dist/core/canvas/crons.js +0 -6
- package/dist/core/canvas/db.d.ts +14 -20
- package/dist/core/canvas/db.js +30 -1332
- package/dist/core/canvas/focuses.js +0 -4
- package/dist/core/canvas/history.js +2 -1
- package/dist/core/canvas/migrations.d.ts +20 -0
- package/dist/core/canvas/migrations.js +1339 -0
- package/dist/core/canvas/nav-model.js +1 -11
- package/dist/core/canvas/nav-render.js +0 -8
- package/dist/core/canvas/remote-canvas-source.js +0 -2
- package/dist/core/canvas/render-source.js +31 -13
- package/dist/core/command-manifests/manifest.js +0 -10
- package/dist/core/command-manifests/schema.js +0 -4
- package/dist/core/command-plugins/transport/http-invoke.js +0 -14
- package/dist/core/command.js +0 -4
- package/dist/core/config.js +2 -1
- package/dist/core/feed/inbox.d.ts +10 -0
- package/dist/core/feed/inbox.js +18 -12
- package/dist/core/fs-utils.d.ts +9 -2
- package/dist/core/fs-utils.js +7 -15
- package/dist/core/help.js +0 -8
- package/dist/core/host-exports/export.d.ts +2 -2
- package/dist/core/host-exports/export.js +9 -12
- package/dist/core/human/answer.d.ts +2 -2
- package/dist/core/human/convention.d.ts +2 -5
- package/dist/core/human/convention.js +3 -13
- package/dist/core/human/page-schema.d.ts +2 -2
- package/dist/core/human/page-schema.js +2 -2
- package/dist/core/human/root.js +6 -23
- package/dist/core/human/scan.js +24 -7
- package/dist/core/human/tickets.d.ts +4 -1
- package/dist/core/human/tickets.js +20 -6
- package/dist/core/io.js +0 -12
- package/dist/core/memory/identity.js +0 -4
- package/dist/core/memory/inline-ref-inventory.js +3 -2
- package/dist/core/memory/lint.js +3 -6
- package/dist/core/memory-resolver.d.ts +5 -0
- package/dist/core/memory-resolver.js +14 -12
- package/dist/core/pagination.js +0 -2
- package/dist/core/preview-registry.d.ts +14 -0
- package/dist/core/preview-registry.js +2 -4
- package/dist/core/preview-result-path.js +2 -1
- package/dist/core/profiles/manifest.d.ts +37 -5
- package/dist/core/profiles/manifest.js +44 -24
- package/dist/core/profiles/select.js +0 -4
- package/dist/core/review/signal.js +4 -23
- package/dist/core/review/ticket-filter.d.ts +3 -3
- package/dist/core/review/ticket-filter.js +4 -9
- package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
- package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
- package/dist/core/runtime/bearings-render.d.ts +56 -0
- package/dist/core/runtime/bearings-render.js +241 -0
- package/dist/core/runtime/bearings.d.ts +2 -17
- package/dist/core/runtime/bearings.js +12 -198
- package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
- package/dist/core/runtime/broker/auth-reload.js +149 -0
- package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
- package/dist/core/runtime/broker/engine-drive.js +257 -0
- package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
- package/dist/core/runtime/broker/engine-routing.js +211 -0
- package/dist/core/runtime/broker/event-projection.js +3 -2
- package/dist/core/runtime/broker/fault-retry.js +5 -4
- package/dist/core/runtime/broker/frame-client.d.ts +38 -0
- package/dist/core/runtime/broker/frame-client.js +129 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
- package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
- package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
- package/dist/core/runtime/broker/frame-engine-control.js +231 -0
- package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
- package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
- package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
- package/dist/core/runtime/broker/frame-model-query.js +69 -0
- package/dist/core/runtime/broker/frame-read.d.ts +19 -0
- package/dist/core/runtime/broker/frame-read.js +116 -0
- package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
- package/dist/core/runtime/broker/frame-replies.js +64 -0
- package/dist/core/runtime/broker/frame-session.d.ts +34 -0
- package/dist/core/runtime/broker/frame-session.js +203 -0
- package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
- package/dist/core/runtime/broker/turn-ignition.js +74 -0
- package/dist/core/runtime/broker-extension-render.js +9 -85
- package/dist/core/runtime/broker-protocol.js +0 -4
- package/dist/core/runtime/broker-sdk.js +2 -1
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +5 -23
- package/dist/core/runtime/fault.js +3 -2
- package/dist/core/runtime/headless-pi.js +3 -2
- package/dist/core/runtime/host.js +2 -3
- package/dist/core/runtime/kickoff.js +0 -4
- package/dist/core/runtime/launch-target.js +2 -1
- package/dist/core/runtime/launch.js +3 -6
- package/dist/core/runtime/lifecycle.js +17 -26
- package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
- package/dist/core/runtime/naming.js +0 -2
- package/dist/core/runtime/nodes.js +2 -1
- package/dist/core/runtime/persona.js +0 -6
- package/dist/core/runtime/placement-tmux.js +2 -5
- package/dist/core/runtime/placement.js +4 -11
- package/dist/core/runtime/recap.js +4 -3
- package/dist/core/runtime/reopen.js +4 -7
- package/dist/core/runtime/reset.js +0 -4
- package/dist/core/runtime/resume-root.js +0 -2
- package/dist/core/runtime/revive.js +17 -13
- package/dist/core/runtime/session-list-cache.js +2 -2
- package/dist/core/runtime/shell-expansion.js +0 -2
- package/dist/core/runtime/spawn.js +0 -2
- package/dist/core/runtime/stream-watchdog.d.ts +0 -1
- package/dist/core/runtime/stream-watchdog.js +13 -18
- package/dist/core/runtime/tmux-bindings.js +0 -2
- package/dist/core/runtime/tmux-driver.d.ts +12 -0
- package/dist/core/runtime/tmux-driver.js +18 -13
- package/dist/core/runtime/warm-pool.js +0 -4
- package/dist/core/scope.js +4 -3
- package/dist/core/spawn.js +2 -3
- package/dist/core/subscription-state.js +10 -17
- package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
- package/dist/core/substrate/render.js +0 -12
- package/dist/core/substrate/schema.js +0 -12
- package/dist/core/substrate/surface-match.js +0 -2
- package/dist/core/termrender/display.js +2 -1
- package/dist/core/tui/page-host.d.ts +3 -3
- package/dist/core/tui/page-host.js +4 -4
- package/dist/core/tui/panel.js +3 -3
- package/dist/core/wake.js +0 -12
- package/dist/core/worktree.js +0 -2
- package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
- package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
- package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
- package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
- package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
- package/dist/daemon/api/handlers/attach.js +2 -1
- package/dist/daemon/api/handlers/broker-ops.js +3 -6
- package/dist/daemon/api/handlers/canvas.js +12 -18
- package/dist/daemon/api/handlers/feedback-comments.js +0 -8
- package/dist/daemon/api/handlers/health.js +3 -2
- package/dist/daemon/api/handlers/human-requests.js +0 -10
- package/dist/daemon/api/handlers/human.js +1 -5
- package/dist/daemon/api/handlers/inbox.js +0 -18
- package/dist/daemon/api/handlers/memory.js +1 -1
- package/dist/daemon/api/handlers/messages.js +4 -8
- package/dist/daemon/api/handlers/nodes.js +10 -21
- package/dist/daemon/api/handlers/reports.js +1 -1
- package/dist/daemon/api/handlers/reviews.js +1 -6
- package/dist/daemon/api/map.js +0 -6
- package/dist/daemon/api/router.js +0 -4
- package/dist/daemon/api/server.js +2 -5
- package/dist/daemon/boot-migrations.d.ts +14 -0
- package/dist/daemon/boot-migrations.js +31 -0
- package/dist/daemon/cron/armed-context.d.ts +21 -0
- package/dist/daemon/cron/armed-context.js +100 -0
- package/dist/daemon/cron/capture.d.ts +20 -0
- package/dist/daemon/cron/capture.js +45 -0
- package/dist/daemon/cron/disposition.d.ts +19 -0
- package/dist/daemon/cron/disposition.js +52 -0
- package/dist/daemon/cron/escalation.d.ts +12 -0
- package/dist/daemon/cron/escalation.js +124 -0
- package/dist/daemon/cron/held.d.ts +4 -0
- package/dist/daemon/cron/held.js +20 -0
- package/dist/daemon/cron/lease-recovery.d.ts +3 -0
- package/dist/daemon/cron/lease-recovery.js +119 -0
- package/dist/daemon/cron/live-runs.d.ts +11 -0
- package/dist/daemon/cron/live-runs.js +23 -0
- package/dist/daemon/cron/passes.d.ts +11 -0
- package/dist/daemon/cron/passes.js +78 -0
- package/dist/daemon/cron/settle.d.ts +28 -0
- package/dist/daemon/cron/settle.js +37 -0
- package/dist/daemon/cron/sinks.d.ts +2 -0
- package/dist/daemon/cron/sinks.js +100 -0
- package/dist/daemon/cron-run.d.ts +5 -41
- package/dist/daemon/cron-run.js +193 -882
- package/dist/daemon/crtrd-cli.js +2 -1
- package/dist/daemon/crtrd.js +22 -22
- package/dist/daemon/fleet.js +0 -4
- package/dist/daemon/human/deliver-action.js +2 -1
- package/dist/daemon/human/sweep.js +15 -4
- package/dist/daemon/manage.d.ts +2 -2
- package/dist/daemon/manage.js +4 -13
- package/dist/daemon/pidfile.js +2 -2
- package/dist/daemon/reconcilers/broker-supervision.js +26 -29
- package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
- package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
- package/dist/daemon/reconcilers/live-obligation.js +2 -2
- package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
- package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
- package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
- package/dist/daemon/review/companion.js +2 -2
- package/dist/daemon/review/sweep.js +8 -3
- package/dist/migrations/001-surfaces-frontmatter.js +0 -6
- package/dist/migrations/corpus.js +2 -5
- package/dist/migrations/registry.js +15 -2
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
- package/dist/pi-extensions/broker-local.js +2 -1
- package/dist/pi-extensions/canvas-bash-valve.js +3 -17
- package/dist/pi-extensions/canvas-context-intro.js +2 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
- package/dist/pi-extensions/canvas-goal-capture.js +2 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
- package/dist/pi-extensions/canvas-passive-context.js +2 -5
- package/dist/pi-extensions/canvas-preview-result.js +2 -1
- package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
- package/dist/pi-extensions/canvas-recap.js +3 -16
- package/dist/pi-extensions/canvas-review-boundary.js +2 -5
- package/dist/pi-extensions/canvas-stophook.js +2 -26
- package/dist/pi-extensions/canvas-structured-output.js +2 -1
- package/dist/pi-extensions/canvas-tool-guide.js +2 -1
- package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
- package/dist/shared/__tests__/env-boundary.test.js +93 -0
- package/dist/shared/env.d.ts +68 -0
- package/dist/shared/env.js +171 -0
- package/package.json +3 -1
- package/runtime.lock.json +2 -2
- package/dist/core/canvas/render.d.ts +0 -47
- package/dist/core/canvas/render.js +0 -245
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
// map.ts carries no projector — it is frozen under concurrency).
|
|
7
7
|
//
|
|
8
8
|
// `{id}` is the target node.
|
|
9
|
+
import { envProfileId } from '../../../shared/env.js';
|
|
9
10
|
import { readdirSync } from 'node:fs';
|
|
10
11
|
import { basename } from 'node:path';
|
|
11
|
-
import { appendSituationalContext,
|
|
12
|
-
import {
|
|
12
|
+
import { appendSituationalContext, formatSituationalProse } from '../../../core/runtime/situational-context.js';
|
|
13
|
+
import { closeNode } from '../../../core/runtime/close.js';
|
|
14
|
+
import { reviveNode } from '../../../core/runtime/revive.js';
|
|
15
|
+
import { nowIso } from '../../../core/fs-utils.js';
|
|
13
16
|
import { assertLaunchModelRegistered, forkNode, spawnChild } from '../../../core/runtime/spawn.js';
|
|
14
|
-
import { childrenOf, subscribersOf, subscriptionsOf, setMessageWait, updateNode } from '../../../core/canvas/canvas.js';
|
|
17
|
+
import { childrenOf, getNode, listNodes, subscribersOf, subscriptionsOf, setMessageWait, updateNode } from '../../../core/canvas/canvas.js';
|
|
15
18
|
import { subtreeIds } from '../../../core/canvas/nav-model.js';
|
|
16
19
|
import { ticketCountsForNodes } from '../../../core/canvas/attention.js';
|
|
17
20
|
import { contextDir, reportsDir } from '../../../core/canvas/paths.js';
|
|
@@ -40,9 +43,7 @@ import { cronWakeOrigin } from '../../../core/runtime/bearings.js';
|
|
|
40
43
|
import { notFound, usage } from '../../../core/errors.js';
|
|
41
44
|
import { toCloseResultDTO, toNodeDetailDTO, toNodeSummaryDTO, toReviveResultDTO } from '../map.js';
|
|
42
45
|
import { ApiError } from '../../../api/index.js';
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
46
|
// Shared helpers
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
47
|
function requireMeta(id) {
|
|
47
48
|
const meta = getNode(id);
|
|
48
49
|
if (meta === null)
|
|
@@ -76,9 +77,7 @@ function countFiles(id) {
|
|
|
76
77
|
}
|
|
77
78
|
return n;
|
|
78
79
|
}
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
80
|
// POST /v1/nodes — create immediately (201)
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
81
|
/** Validate + narrow the create body (spec §6.2). */
|
|
83
82
|
function parseCreateBody(body) {
|
|
84
83
|
if (typeof body !== 'object' || body === null) {
|
|
@@ -192,7 +191,7 @@ export async function handleCreate(ctx, deps = {}) {
|
|
|
192
191
|
// buildRecipe already defaults `prompt` to ''. The CLI keeps every no_kickoff
|
|
193
192
|
// guards (root-only / no-prompt / no-orchestrator).
|
|
194
193
|
const parent = req.parent ?? undefined;
|
|
195
|
-
//
|
|
194
|
+
// Immediate spawn (201). A managed child needs a parent; a root does not.
|
|
196
195
|
if (parent == null && req.root !== true) {
|
|
197
196
|
throw usage('a node create requires a parent (or root: true for an independent root)');
|
|
198
197
|
}
|
|
@@ -233,9 +232,7 @@ export async function handleCreate(ctx, deps = {}) {
|
|
|
233
232
|
writeOutputSchema(result.node.node_id, 'terminal', schema);
|
|
234
233
|
return { status: 201, body: detailOf(result.node.node_id) };
|
|
235
234
|
}
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
235
|
// GET /v1/nodes — list (filters) ; GET /v1/nodes/{id} — detail
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
236
|
function singleQueryValue(ctx, name) {
|
|
240
237
|
const values = ctx.query.getAll(name);
|
|
241
238
|
if (values.length > 1)
|
|
@@ -343,9 +340,7 @@ function handleDetail(ctx) {
|
|
|
343
340
|
requireMeta(id);
|
|
344
341
|
return { status: 200, body: detailOf(id) };
|
|
345
342
|
}
|
|
346
|
-
// ---------------------------------------------------------------------------
|
|
347
343
|
// Read endpoints (snapshot / transcript / context / artifacts)
|
|
348
|
-
// ---------------------------------------------------------------------------
|
|
349
344
|
async function handleSnapshot(ctx) {
|
|
350
345
|
const id = ctx.params['id'];
|
|
351
346
|
requireMeta(id);
|
|
@@ -458,9 +453,7 @@ function handleArtifacts(ctx) {
|
|
|
458
453
|
const body = { node_id: id, artifacts };
|
|
459
454
|
return { status: 200, body };
|
|
460
455
|
}
|
|
461
|
-
// ---------------------------------------------------------------------------
|
|
462
456
|
// Lifecycle verbs
|
|
463
|
-
// ---------------------------------------------------------------------------
|
|
464
457
|
async function handleFork(ctx) {
|
|
465
458
|
const id = ctx.params['id'];
|
|
466
459
|
requireMeta(id);
|
|
@@ -601,9 +594,7 @@ function handleReviveAll() {
|
|
|
601
594
|
const body = { revived: [...result.revived], failed: [...result.failed] };
|
|
602
595
|
return { status: 200, body };
|
|
603
596
|
}
|
|
604
|
-
// ---------------------------------------------------------------------------
|
|
605
597
|
// PATCH /v1/nodes/{id}/config — model / situational-context (spec §6.2)
|
|
606
|
-
// ---------------------------------------------------------------------------
|
|
607
598
|
export async function handleConfig(ctx, deps = {}) {
|
|
608
599
|
const id = ctx.params['id'];
|
|
609
600
|
let meta = requireMeta(id);
|
|
@@ -646,7 +637,7 @@ export async function handleConfig(ctx, deps = {}) {
|
|
|
646
637
|
// Re-read so the launch-spec rebuild below sees the fresh model pin.
|
|
647
638
|
meta = requireMeta(id);
|
|
648
639
|
}
|
|
649
|
-
//
|
|
640
|
+
// kind / mode / lifecycle: reshape via canonical primitive (CLI parity).
|
|
650
641
|
if (patch.kind !== undefined || patch.mode !== undefined || patch.lifecycle !== undefined) {
|
|
651
642
|
if (patch.kind !== undefined)
|
|
652
643
|
await assertKindServer(patch.kind);
|
|
@@ -671,7 +662,7 @@ export async function handleConfig(ctx, deps = {}) {
|
|
|
671
662
|
...(patch.lifecycle !== undefined ? { lifecycle: newLifecycle } : {}),
|
|
672
663
|
}, beforePersist);
|
|
673
664
|
}
|
|
674
|
-
//
|
|
665
|
+
// name: rename the row and, when it has a live viewer window, that window.
|
|
675
666
|
if (patch.name !== undefined) {
|
|
676
667
|
meta = updateNode(id, { name: patch.name });
|
|
677
668
|
if (meta.window)
|
|
@@ -685,15 +676,13 @@ export async function handleConfig(ctx, deps = {}) {
|
|
|
685
676
|
* ambient configuration; prospective reads validate their resolved target. The
|
|
686
677
|
* read is subprocess-isolated — crtrd must never resolve config on its own loop. */
|
|
687
678
|
async function assertKindServer(kind) {
|
|
688
|
-
const kinds = Object.keys((await readMergedLaunchConfigAsync(process.cwd(),
|
|
679
|
+
const kinds = Object.keys((await readMergedLaunchConfigAsync(process.cwd(), envProfileId() || null)).kinds);
|
|
689
680
|
if (!kinds.includes(kind)) {
|
|
690
681
|
throw usage(`unknown kind: ${kind}`, { received: kind, next: `Valid kinds: ${kinds.sort().join(', ')}.` });
|
|
691
682
|
}
|
|
692
683
|
}
|
|
693
|
-
// ---------------------------------------------------------------------------
|
|
694
684
|
// Route table (revive-all registered before `:id` collisions are impossible —
|
|
695
685
|
// different methods — but ordered defensively).
|
|
696
|
-
// ---------------------------------------------------------------------------
|
|
697
686
|
export const nodeRoutes = [
|
|
698
687
|
{ method: 'POST', pattern: '/v1/nodes', handler: handleCreate },
|
|
699
688
|
{ method: 'GET', pattern: '/v1/nodes', handler: handleList },
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `{id}` is the REPORTING node (client-side = CRTR_NODE_ID).
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import { getNode } from '../../../
|
|
9
|
+
import { getNode } from '../../../core/canvas/canvas.js';
|
|
10
10
|
import { push, FinalizationError } from '../../../core/feed/feed.js';
|
|
11
11
|
import { autoDropCleanManagedWorktree, hasOpenManagedWorktree, missingWorktreeRecoveryHint } from '../../../core/worktree.js';
|
|
12
12
|
import { reportsDir } from '../../../core/canvas/paths.js';
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// Canonical daemon-owned review lifecycle routes. Handlers validate the full
|
|
2
2
|
// wire request, delegate lifecycle work to review services, and project only
|
|
3
3
|
// committed daemon state.
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
5
|
-
import { ApiError } from '../../../api/index.js';
|
|
6
4
|
import { isSafeNodeId, reviewResultPath } from '../../../core/canvas/paths.js';
|
|
7
|
-
import { responsePath
|
|
5
|
+
import { responsePath } from '../../../core/human/convention.js';
|
|
8
6
|
import { ticketDir } from '../../../core/human/root.js';
|
|
9
7
|
import { createReview } from '../../../core/review/birth.js';
|
|
10
8
|
import { readAnchorBase, getReviewByBridge, listReviews, requireVisibleReview } from '../../../core/review/store.js';
|
|
@@ -168,9 +166,6 @@ function handleByBridge(ctx) {
|
|
|
168
166
|
const review = getReviewByBridge(bridgeNodeId);
|
|
169
167
|
if (review !== null)
|
|
170
168
|
return { status: 200, body: toReviewResponseDTO(review) };
|
|
171
|
-
if (existsSync(reviewPath(ticketDir(bridgeNodeId)))) {
|
|
172
|
-
throw new ApiError(409, 'review_predates_system', 'this review predates canonical review records; cancel it and re-file the review', { bridge_node_id: bridgeNodeId });
|
|
173
|
-
}
|
|
174
169
|
throw new ReviewOperationError('unknown_review', 'review was not found', { received: bridgeNodeId });
|
|
175
170
|
}
|
|
176
171
|
async function handleSubmit(ctx) {
|
package/dist/daemon/api/map.js
CHANGED
|
@@ -20,9 +20,7 @@ import { ExitCode } from '../../types.js';
|
|
|
20
20
|
import { sinkDisplay } from '../cron-sink.js';
|
|
21
21
|
import { responsePath } from '../../core/human/convention.js';
|
|
22
22
|
import { ticketDir } from '../../core/human/root.js';
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
23
|
// Node projections
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
24
|
/** Project the queryable row (what `listNodes` yields) into a summary DTO. */
|
|
27
25
|
export function toNodeSummaryDTO(row) {
|
|
28
26
|
return {
|
|
@@ -144,9 +142,7 @@ export function toNodeDetailDTO(meta, edges) {
|
|
|
144
142
|
dto.worktree = toNodeWorktreeDTO(meta.managed_worktree);
|
|
145
143
|
return dto;
|
|
146
144
|
}
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
145
|
// Action-result projections
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
146
|
/** Project a `reviveNode` result. `revived` is true only when a fresh launch
|
|
151
147
|
* actually happened — the double-revive no-op (already-alive) returns without a
|
|
152
148
|
* `launch` handle, so its absence is the "nothing relaunched" signal. `status`
|
|
@@ -398,9 +394,7 @@ export function toFeedbackResultDTO(result) {
|
|
|
398
394
|
}
|
|
399
395
|
const _exitIntentParity = true;
|
|
400
396
|
void _exitIntentParity;
|
|
401
|
-
// ---------------------------------------------------------------------------
|
|
402
397
|
// Error → HTTP status mapping (spec §8)
|
|
403
|
-
// ---------------------------------------------------------------------------
|
|
404
398
|
/** The only internal review-error to public HTTP projection. Every internal
|
|
405
399
|
* review failure is listed explicitly so handlers never need to invent a status
|
|
406
400
|
* or public code. */
|
|
@@ -102,9 +102,7 @@ export class Router {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
105
|
// Matching
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
106
|
/** Split a path/pattern into non-empty segments (leading/trailing '/' ignored). */
|
|
109
107
|
function splitPath(p) {
|
|
110
108
|
return p.split('/').filter((s) => s.length > 0);
|
|
@@ -127,9 +125,7 @@ function matchSegments(pattern, actual) {
|
|
|
127
125
|
}
|
|
128
126
|
return params;
|
|
129
127
|
}
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
128
|
// Body + response I/O
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
129
|
class BodyTooLargeError extends Error {
|
|
134
130
|
}
|
|
135
131
|
/** Read and JSON-parse the request body. Returns `undefined` for an empty body.
|
|
@@ -16,6 +16,7 @@ import { createServer } from 'node:http';
|
|
|
16
16
|
import { chmodSync, existsSync, unlinkSync } from 'node:fs';
|
|
17
17
|
import { apiSocketPath } from '../../core/canvas/paths.js';
|
|
18
18
|
import { emitEvent } from '../../core/events/emit.js';
|
|
19
|
+
import { envCrtrdTcp } from '../../shared/env.js';
|
|
19
20
|
import { Router } from './router.js';
|
|
20
21
|
import { handleAttachUpgrade } from './bridge.js';
|
|
21
22
|
import { attachRoutes } from './handlers/attach.js';
|
|
@@ -204,10 +205,8 @@ export function createApiServer(opts = {}) {
|
|
|
204
205
|
emitEvent({ event: 'api.server.failed', level: 'error', error: err });
|
|
205
206
|
});
|
|
206
207
|
};
|
|
207
|
-
// -------------------------------------------------------------------------
|
|
208
208
|
// Unix socket — ALWAYS bound. Unlink any stale socket first (mirror
|
|
209
209
|
// broker.ts's listener setup), listen, then chmod 0600 once the file exists.
|
|
210
|
-
// -------------------------------------------------------------------------
|
|
211
210
|
const socketServer = createServer(onRequest);
|
|
212
211
|
socketServer.on('upgrade', onUpgrade);
|
|
213
212
|
bindErrorHandler(socketServer);
|
|
@@ -230,11 +229,9 @@ export function createApiServer(opts = {}) {
|
|
|
230
229
|
});
|
|
231
230
|
}
|
|
232
231
|
});
|
|
233
|
-
// -------------------------------------------------------------------------
|
|
234
232
|
// TCP — opt-in only. A SECOND http.Server sharing the same handlers.
|
|
235
|
-
// -------------------------------------------------------------------------
|
|
236
233
|
let tcpServer;
|
|
237
|
-
const tcpSpec = opts.tcp ??
|
|
234
|
+
const tcpSpec = opts.tcp ?? envCrtrdTcp();
|
|
238
235
|
if (tcpSpec !== undefined && tcpSpec !== '') {
|
|
239
236
|
const parsed = parseTcp(tcpSpec);
|
|
240
237
|
if (parsed === null) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface BootMigrationResult {
|
|
2
|
+
changed: boolean;
|
|
3
|
+
summary: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface BootMigration {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly description: string;
|
|
8
|
+
/** Runs unconditionally at every daemon boot. Must be safe to re-run every
|
|
9
|
+
* time (idempotent via its own convergence, not a version gate) and must
|
|
10
|
+
* isolate its own per-item failures — a thrown error here is NOT caught by
|
|
11
|
+
* the boot sequence and would abort daemon startup. */
|
|
12
|
+
apply(): BootMigrationResult;
|
|
13
|
+
}
|
|
14
|
+
export declare const BOOT_MIGRATIONS: readonly BootMigration[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The ordered boot-migration list — the THIRD of this repo's three migration
|
|
2
|
+
// homes, and the one for filesystem repairs that must run unconditionally at
|
|
3
|
+
// every daemon boot, before supervision can revive anything. No version gate
|
|
4
|
+
// (there is no single database to hold a `user_version`, and no `crtr sys
|
|
5
|
+
// migrate` invocation to drive it): each entry converges on its own, the same
|
|
6
|
+
// way `migrateLegacyInboxIdentities` already does — a re-run over
|
|
7
|
+
// already-migrated state rewrites nothing.
|
|
8
|
+
//
|
|
9
|
+
// The other two homes:
|
|
10
|
+
// - `../core/canvas/migrations.ts` — canvas.db's versioned schema chain,
|
|
11
|
+
// gated by `PRAGMA user_version`, applied once per database inside
|
|
12
|
+
// `migrate()`.
|
|
13
|
+
// - `../migrations/registry.ts` — the `STATE_MIGRATIONS` registry for
|
|
14
|
+
// on-disk memory documents, profile manifests, and the corpus lane,
|
|
15
|
+
// driven by `crtr sys migrate`.
|
|
16
|
+
//
|
|
17
|
+
// Authoring contract: one entry per boot-time filesystem repair, appended
|
|
18
|
+
// here in order. `crtrd.ts` iterates this list once at boot and emits one
|
|
19
|
+
// `daemon.boot_migration.applied` event per entry that reports a change —
|
|
20
|
+
// add a step here instead of a bespoke call in the boot sequence.
|
|
21
|
+
import { migrateLegacyInboxIdentities } from '../core/feed/inbox-migrate.js';
|
|
22
|
+
export const BOOT_MIGRATIONS = [
|
|
23
|
+
{
|
|
24
|
+
id: 'legacy-inbox-identities',
|
|
25
|
+
description: 'normalize pre-cutover inbox entry/operation ids (UUID-spelled or missing) and cursors (ISO-timestamp boundaries) into canonical 32-lowercase-hex identities the strict inbox reader requires.',
|
|
26
|
+
apply: () => {
|
|
27
|
+
const summary = migrateLegacyInboxIdentities();
|
|
28
|
+
return { changed: summary.nodes > 0 || summary.failures > 0, summary: { ...summary } };
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Cron } from '../../core/canvas/crons.js';
|
|
2
|
+
/** The cron's profile, re-validated now and returned in canonical id form.
|
|
3
|
+
*
|
|
4
|
+
* A DELETED profile is the spec's root fallback: null. An UNREADABLE one is
|
|
5
|
+
* not — the profile is still there, so running as root would execute the
|
|
6
|
+
* command under an identity nobody armed, with the profile's own env store and
|
|
7
|
+
* purview silently replaced. That throws, and the run fails loudly once
|
|
8
|
+
* instead of running wrong on every tick. */
|
|
9
|
+
export declare function resolveLiveProfile(c: Cron): string | null;
|
|
10
|
+
export declare function resolveLiveProfileOrRoot(c: Cron): string | null;
|
|
11
|
+
export declare function resolveLiveProfileName(c: Cron): string | undefined;
|
|
12
|
+
/** A stored context cannot be rebuilt; the run fails rather than running under a context nobody armed. */
|
|
13
|
+
export declare class ArmedContextError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
export interface ArmedRunContext {
|
|
17
|
+
profileId: string | null;
|
|
18
|
+
env: NodeJS.ProcessEnv;
|
|
19
|
+
}
|
|
20
|
+
/** Rebuild the armed context. Corrupt snapshots and unreadable profiles fail rather than silently changing its identity or environment. */
|
|
21
|
+
export declare function buildArmedRunContext(c: Cron, runId: string): ArmedRunContext;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { envHomeOverride } from '../../shared/env.js';
|
|
2
|
+
import { loadProfileManifest, lookupProfile } from '../../core/profiles/manifest.js';
|
|
3
|
+
import { readProfileEnvVars } from '../../core/profiles/env-store.js';
|
|
4
|
+
import { ADMITTED_HOST_PATH_ENV, buildOperationalEnvBase } from '../../core/runtime/spawn-env.js';
|
|
5
|
+
/** The cron's profile, re-validated now and returned in canonical id form.
|
|
6
|
+
*
|
|
7
|
+
* A DELETED profile is the spec's root fallback: null. An UNREADABLE one is
|
|
8
|
+
* not — the profile is still there, so running as root would execute the
|
|
9
|
+
* command under an identity nobody armed, with the profile's own env store and
|
|
10
|
+
* purview silently replaced. That throws, and the run fails loudly once
|
|
11
|
+
* instead of running wrong on every tick. */
|
|
12
|
+
export function resolveLiveProfile(c) {
|
|
13
|
+
if (c.profile == null)
|
|
14
|
+
return null;
|
|
15
|
+
let lookup;
|
|
16
|
+
try {
|
|
17
|
+
lookup = lookupProfile(c.profile);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null; // an ambiguous NAME resolves no better than a deleted profile
|
|
21
|
+
}
|
|
22
|
+
if (lookup.kind === 'resolved')
|
|
23
|
+
return lookup.entry.profileId;
|
|
24
|
+
if (lookup.kind === 'absent')
|
|
25
|
+
return null;
|
|
26
|
+
throw new ArmedContextError(`the armed profile "${c.profile}" is still on this host but its manifest could not be read (${lookup.manifestPath}) — running as root would silently give this cron a different identity, env store, and purview. Repair the manifest, or re-arm the cron under a profile that resolves.`);
|
|
27
|
+
}
|
|
28
|
+
// The escalation node must still report an unreadable manifest, so its own spawn falls back to root.
|
|
29
|
+
export function resolveLiveProfileOrRoot(c) {
|
|
30
|
+
try {
|
|
31
|
+
return resolveLiveProfile(c);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function resolveLiveProfileName(c) {
|
|
38
|
+
if (c.profile == null)
|
|
39
|
+
return undefined;
|
|
40
|
+
try {
|
|
41
|
+
return loadProfileManifest(c.profile).manifest.name;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** A stored context cannot be rebuilt; the run fails rather than running under a context nobody armed. */
|
|
48
|
+
export class ArmedContextError extends Error {
|
|
49
|
+
constructor(message) {
|
|
50
|
+
super(message);
|
|
51
|
+
this.name = 'ArmedContextError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Invalid snapshots are corruption: dropping `--env` would run under an environment nobody armed, so fail instead.
|
|
55
|
+
function parseArmedEnv(envJson) {
|
|
56
|
+
if (envJson == null)
|
|
57
|
+
return {};
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(envJson);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
throw new ArmedContextError(`the armed env snapshot is not valid JSON (${err.message}) — this cron's stored env_json is corrupt, so the run would silently drop every armed variable. Re-arm the cron with a valid --env.`);
|
|
64
|
+
}
|
|
65
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
66
|
+
throw new ArmedContextError("the armed env snapshot is not a JSON object — this cron's stored env_json is corrupt. Re-arm the cron with a valid --env.");
|
|
67
|
+
}
|
|
68
|
+
const extraEnv = {};
|
|
69
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
70
|
+
if (typeof v !== 'string') {
|
|
71
|
+
throw new ArmedContextError(`the armed env snapshot's value for "${k}" is not a string — this cron's stored env_json is corrupt, so the run would silently drop that variable. Re-arm the cron with a valid --env.`);
|
|
72
|
+
}
|
|
73
|
+
extraEnv[k] = v;
|
|
74
|
+
}
|
|
75
|
+
return extraEnv;
|
|
76
|
+
}
|
|
77
|
+
/** Rebuild the armed context. Corrupt snapshots and unreadable profiles fail rather than silently changing its identity or environment. */
|
|
78
|
+
export function buildArmedRunContext(c, runId) {
|
|
79
|
+
const extraEnv = parseArmedEnv(c.env_json);
|
|
80
|
+
const profileId = resolveLiveProfile(c);
|
|
81
|
+
const operationalEnv = buildOperationalEnvBase({ targetCwd: c.cwd, targetProfileId: profileId });
|
|
82
|
+
const env = {
|
|
83
|
+
...operationalEnv,
|
|
84
|
+
...readProfileEnvVars(profileId),
|
|
85
|
+
...extraEnv,
|
|
86
|
+
...(profileId !== null ? { CRTR_PROFILE_ID: profileId } : {}),
|
|
87
|
+
CRTR_CRON_ID: c.cron_id,
|
|
88
|
+
CRTR_CRON_NAME: c.name,
|
|
89
|
+
CRTR_RUN_ID: runId,
|
|
90
|
+
};
|
|
91
|
+
// Preserve the daemon identity: otherwise child `crtr` resolves the default canvas.
|
|
92
|
+
const canvasHomeOverride = envHomeOverride();
|
|
93
|
+
if (canvasHomeOverride !== undefined && canvasHomeOverride !== '') {
|
|
94
|
+
env['CRTR_HOME'] = canvasHomeOverride;
|
|
95
|
+
}
|
|
96
|
+
if (c.anchor_node != null)
|
|
97
|
+
env['CRTR_ANCHOR'] = c.anchor_node;
|
|
98
|
+
env[ADMITTED_HOST_PATH_ENV] = operationalEnv[ADMITTED_HOST_PATH_ENV];
|
|
99
|
+
return { profileId, env };
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Output head caps for the run log (spec: "capped, e.g. 4KB"). */
|
|
2
|
+
export declare const CRON_OUTPUT_HEAD_BYTES: number;
|
|
3
|
+
/** Bound between the run-timeout SIGTERM and a forced SIGKILL escalation, so
|
|
4
|
+
* a signal-trapping command cannot stall teardown's detached drain. */
|
|
5
|
+
export declare const CRON_KILL_GRACE_MS = 2000;
|
|
6
|
+
/** Poll interval while waiting for a SIGTERMed stale-lease run to actually die
|
|
7
|
+
* (each probe is a `ps` call, so this is deliberately coarse). */
|
|
8
|
+
export declare const CRON_DEATH_POLL_MS = 250;
|
|
9
|
+
/** sha256 of empty stdout — the change-detection hash a run that produced no
|
|
10
|
+
* output carries (a failed spawn, a recovered stale lease). */
|
|
11
|
+
export declare const EMPTY_STDOUT_SHA256: string;
|
|
12
|
+
/** A byte-capped capture of one output stream's head. Bytes are buffered and
|
|
13
|
+
* decoded exactly once, at the end — decoding per chunk both mis-measured the
|
|
14
|
+
* cap (UTF-16 code units, not bytes) and corrupted any multi-byte sequence
|
|
15
|
+
* that straddled a chunk boundary. Truncation is MARKED, never silent: a
|
|
16
|
+
* clipped body reads as complete output to whoever receives the delivery. */
|
|
17
|
+
export declare function newHeadCapture(): {
|
|
18
|
+
take(chunk: Buffer): void;
|
|
19
|
+
decode(): string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/daemon/cron/capture.ts — output capture for a cron run's child process,
|
|
2
|
+
// plus the timing/hash constants the capture and kill mechanics share.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
/** Output head caps for the run log (spec: "capped, e.g. 4KB"). */
|
|
5
|
+
export const CRON_OUTPUT_HEAD_BYTES = 4 * 1024;
|
|
6
|
+
/** Bound between the run-timeout SIGTERM and a forced SIGKILL escalation, so
|
|
7
|
+
* a signal-trapping command cannot stall teardown's detached drain. */
|
|
8
|
+
export const CRON_KILL_GRACE_MS = 2000;
|
|
9
|
+
/** Poll interval while waiting for a SIGTERMed stale-lease run to actually die
|
|
10
|
+
* (each probe is a `ps` call, so this is deliberately coarse). */
|
|
11
|
+
export const CRON_DEATH_POLL_MS = 250;
|
|
12
|
+
/** sha256 of empty stdout — the change-detection hash a run that produced no
|
|
13
|
+
* output carries (a failed spawn, a recovered stale lease). */
|
|
14
|
+
export const EMPTY_STDOUT_SHA256 = createHash('sha256').update('').digest('hex');
|
|
15
|
+
/** A byte-capped capture of one output stream's head. Bytes are buffered and
|
|
16
|
+
* decoded exactly once, at the end — decoding per chunk both mis-measured the
|
|
17
|
+
* cap (UTF-16 code units, not bytes) and corrupted any multi-byte sequence
|
|
18
|
+
* that straddled a chunk boundary. Truncation is MARKED, never silent: a
|
|
19
|
+
* clipped body reads as complete output to whoever receives the delivery. */
|
|
20
|
+
export function newHeadCapture() {
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let bytes = 0;
|
|
23
|
+
let truncated = false;
|
|
24
|
+
return {
|
|
25
|
+
take(chunk) {
|
|
26
|
+
const room = CRON_OUTPUT_HEAD_BYTES - bytes;
|
|
27
|
+
if (room <= 0) {
|
|
28
|
+
truncated = true;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (chunk.length > room) {
|
|
32
|
+
chunks.push(chunk.subarray(0, room));
|
|
33
|
+
bytes = CRON_OUTPUT_HEAD_BYTES;
|
|
34
|
+
truncated = true;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
chunks.push(chunk);
|
|
38
|
+
bytes += chunk.length;
|
|
39
|
+
},
|
|
40
|
+
decode() {
|
|
41
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
42
|
+
return truncated ? `${text}\n[… output truncated at ${CRON_OUTPUT_HEAD_BYTES} bytes]` : text;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Cron } from '../../core/canvas/crons.js';
|
|
2
|
+
export interface RunSettleContext {
|
|
3
|
+
runId: string;
|
|
4
|
+
startedAtIso: string;
|
|
5
|
+
exitCode: number | null;
|
|
6
|
+
timedOut: boolean;
|
|
7
|
+
runTimeoutS: number;
|
|
8
|
+
processErrorMessage: string | null;
|
|
9
|
+
stdoutHead: string;
|
|
10
|
+
/** Hash the full output: a capped head can hide a change. */
|
|
11
|
+
stdoutSha256: string;
|
|
12
|
+
stderrHead: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Disposition {
|
|
15
|
+
delivered: string;
|
|
16
|
+
/** A paused row is retained so `cron resume` can re-arm it. */
|
|
17
|
+
paused: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function disposeSettledRun(c: Cron, ctx: RunSettleContext, outOfBand: boolean, selfCanceled?: boolean): Promise<Disposition>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getCron, setCronLastOutputHash } from '../../core/canvas/crons.js';
|
|
2
|
+
import { escalateFailure, failureSummary } from './escalation.js';
|
|
3
|
+
import { deliverToSink } from './sinks.js';
|
|
4
|
+
export async function disposeSettledRun(c, ctx, outOfBand, selfCanceled = false) {
|
|
5
|
+
const prefix = outOfBand ? 'out-of-band: ' : '';
|
|
6
|
+
// A self-cancel still delivers its already-produced output; an external cancellation does not.
|
|
7
|
+
const fresh = getCron(c.cron_id);
|
|
8
|
+
if (fresh === null && !selfCanceled)
|
|
9
|
+
return { delivered: `${prefix}cron canceled mid-run — nothing delivered`, paused: false };
|
|
10
|
+
const effective = fresh ?? c;
|
|
11
|
+
const failed = ctx.timedOut || ctx.processErrorMessage !== null || ctx.exitCode !== 0;
|
|
12
|
+
// `cron run` compares but never rewrites the scheduled baseline.
|
|
13
|
+
let changed = true;
|
|
14
|
+
if (effective.on_output === 'on-change') {
|
|
15
|
+
changed = effective.last_output_hash !== ctx.stdoutSha256;
|
|
16
|
+
if (!outOfBand && fresh !== null)
|
|
17
|
+
setCronLastOutputHash(c.cron_id, ctx.stdoutSha256);
|
|
18
|
+
}
|
|
19
|
+
if (failed) {
|
|
20
|
+
if (effective.on_output === 'silent')
|
|
21
|
+
return { delivered: `${prefix}silent`, paused: false };
|
|
22
|
+
if (outOfBand)
|
|
23
|
+
return { delivered: `out-of-band: ${failureSummary(ctx, null)} — not escalated`, paused: false };
|
|
24
|
+
return { delivered: await escalateFailure(effective, ctx, null), paused: true };
|
|
25
|
+
}
|
|
26
|
+
switch (effective.on_output) {
|
|
27
|
+
case 'silent':
|
|
28
|
+
return { delivered: `${prefix}silent`, paused: false };
|
|
29
|
+
case 'on-failure':
|
|
30
|
+
return { delivered: `${prefix}ok — nothing to deliver (on-failure)`, paused: false };
|
|
31
|
+
case 'on-change':
|
|
32
|
+
if (!changed)
|
|
33
|
+
return { delivered: `${prefix}unchanged — not delivered (on-change)`, paused: false };
|
|
34
|
+
return deliverOrEscalate(effective, ctx, outOfBand, prefix);
|
|
35
|
+
case 'always':
|
|
36
|
+
return deliverOrEscalate(effective, ctx, outOfBand, prefix);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function deliverOrEscalate(c, ctx, outOfBand, prefix) {
|
|
40
|
+
if (ctx.stdoutHead.trim() === '') {
|
|
41
|
+
return { delivered: `${prefix}empty stdout — nothing delivered`, paused: false };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return { delivered: `${prefix}${await deliverToSink(c, ctx.stdoutHead)}`, paused: false };
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const why = `delivery failed: ${err.message}`;
|
|
48
|
+
if (outOfBand)
|
|
49
|
+
return { delivered: `out-of-band: ${why} — not escalated`, paused: false };
|
|
50
|
+
return { delivered: await escalateFailure(c, ctx, why), paused: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Cron } from '../../core/canvas/crons.js';
|
|
2
|
+
import type { RunSettleContext } from './disposition.js';
|
|
3
|
+
export declare function failureSummary(ctx: RunSettleContext, deliveryFailure: string | null): string;
|
|
4
|
+
/** Pause the cron and spawn the escalation node in the cron's originating
|
|
5
|
+
* profile (root fallback). Returns the run-log `delivered` string. Never
|
|
6
|
+
* throws: an escalation spawn failure is recorded and logged, the pause
|
|
7
|
+
* stands (the row must not keep refiring a broken command). */
|
|
8
|
+
export declare function escalateFailure(c: Cron, ctx: RunSettleContext, deliveryFailure: string | null): Promise<string>;
|
|
9
|
+
/** The delivery-failure line an escalated crashed ONE-SHOT carries: its side
|
|
10
|
+
* effects may already have landed in full, so a human/agent decides whether to
|
|
11
|
+
* redo it rather than the daemon re-firing it. */
|
|
12
|
+
export declare function crashedOneShotSummary(why: string): string;
|