@north-light/crouter 0.3.233 → 0.3.235
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/api/dto/nodes.d.ts +11 -0
- 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 +535 -537
- 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 +1 -9
- 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 +10 -23
- 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/human.js +1 -1
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
- package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.d.ts +1 -0
- package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +96 -0
- package/dist/commands/memory/delete.js +1 -1
- package/dist/commands/memory/edit.js +6 -3
- package/dist/commands/memory/find.js +2 -2
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/list.d.ts +4 -0
- package/dist/commands/memory/list.js +20 -14
- package/dist/commands/memory/move.js +1 -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 -0
- package/dist/commands/memory/shared.js +17 -8
- package/dist/commands/memory/write.js +8 -4
- package/dist/commands/node/bash.js +2 -1
- package/dist/commands/node/create.js +2 -1
- package/dist/commands/node/inspect.js +11 -10
- package/dist/commands/node/lifecycle.js +5 -13
- package/dist/commands/node/message.js +11 -6
- package/dist/commands/node/subscription.js +7 -2
- package/dist/commands/node/wait.js +9 -4
- 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 +3 -2
- package/dist/commands/pkg/shared.js +0 -2
- package/dist/commands/pkg.js +1 -0
- package/dist/commands/profile/delete.js +2 -1
- package/dist/commands/profile/rename.js +1 -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 +1 -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/commands/sys.js +1 -0
- 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__/cli-help-contract.test.d.ts +1 -0
- package/dist/core/__tests__/cli-help-contract.test.js +150 -0
- 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 +21 -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/__tests__/watchdog-abort-arms-retry.test.js +11 -6
- 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 +61 -19
- 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/meta-file.js +18 -27
- 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.d.ts +8 -0
- package/dist/core/canvas/remote-canvas-source.js +9 -3
- package/dist/core/canvas/render-source.js +31 -13
- package/dist/core/command-manifests/manifest.js +0 -10
- package/dist/core/command-manifests/registry.d.ts +2 -0
- package/dist/core/command-manifests/schema.js +0 -4
- package/dist/core/command-plugins/compose.js +4 -2
- package/dist/core/command-plugins/discovery.d.ts +1 -0
- package/dist/core/command-plugins/discovery.js +1 -1
- package/dist/core/command-plugins/transport/http-invoke.js +0 -14
- package/dist/core/command.js +2 -6
- package/dist/core/config.js +2 -1
- package/dist/core/fault-classifier.js +8 -0
- 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.d.ts +2 -0
- package/dist/core/pagination.js +2 -3
- 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 +11 -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/rebind.js +50 -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.d.ts +2 -2
- package/dist/core/runtime/launch.js +20 -10
- 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/stop-guard.js +28 -16
- 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/frontmatter-validation.d.ts +1 -0
- package/dist/core/substrate/frontmatter-validation.js +1 -1
- 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__/error-seam.test.js +27 -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 +16 -20
- package/dist/daemon/api/handlers/nodes.js +23 -22
- package/dist/daemon/api/handlers/reports.js +1 -1
- package/dist/daemon/api/handlers/reviews.js +1 -6
- package/dist/daemon/api/handlers/worktree.js +6 -22
- package/dist/daemon/api/map.d.ts +4 -2
- package/dist/daemon/api/map.js +22 -8
- 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 +27 -30
- 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 +7 -20
- 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
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { emitEvent } from '../../events/emit.js';
|
|
3
|
+
/**
|
|
4
|
+
* Route a writable client's `prompt`/`follow_up` frame against the LIVE session
|
|
5
|
+
* state. The client picks its frame type off a possibly-STALE `isStreaming`
|
|
6
|
+
* snapshot, so the broker is authoritative and the client's choice is a HINT:
|
|
7
|
+
*
|
|
8
|
+
* - m-B (streaming-safe prompt): a `prompt` arriving mid-stream needs
|
|
9
|
+
* PromptOptions.streamingBehavior ("Required if streaming"; prompt() throws
|
|
10
|
+
* without it). "steer" mirrors pi interactive-mode's own
|
|
11
|
+
* Enter-while-streaming submit (`prompt(text, { streamingBehavior: "steer" })`).
|
|
12
|
+
* - m-C (idle-safe follow_up): pi's followUp() only enqueues into a RUNNING
|
|
13
|
+
* agent loop — on an idle session nothing ever drains the queue and the text
|
|
14
|
+
* is silently lost. An idle `follow_up` therefore runs as a prompt now.
|
|
15
|
+
*
|
|
16
|
+
* driveEngine calls this synchronously with `session.isStreaming`, so the
|
|
17
|
+
* routed state cannot go stale between the check and the engine call.
|
|
18
|
+
* C1: images ride as a field on PromptOptions (pi: `prompt(text, { images })`).
|
|
19
|
+
*/
|
|
20
|
+
export function resolveEngineRoute(frame, isStreaming) {
|
|
21
|
+
if (frame.type === 'follow_up' && isStreaming)
|
|
22
|
+
return { call: 'followUp' };
|
|
23
|
+
const options = {};
|
|
24
|
+
if (frame.images !== undefined)
|
|
25
|
+
options.images = frame.images;
|
|
26
|
+
if (isStreaming)
|
|
27
|
+
options.streamingBehavior = 'steer';
|
|
28
|
+
return { call: 'prompt', options };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* True iff `text` would be consumed by pi's OWN leading-name preflight before
|
|
32
|
+
* `session.prompt/steer/followUp` ever sees it as prose — an extension command
|
|
33
|
+
* (`session.extensionRunner.getCommand`), a `/skill:name` skill, or a `/name`
|
|
34
|
+
* prompt template (mirrors `_tryExecuteExtensionCommand` / `_expandSkillCommand`
|
|
35
|
+
* / `expandPromptTemplate` in the pinned SDK verbatim, including the exact
|
|
36
|
+
* name-parsing substrings). Inline memory-reference guidance must NEVER fire
|
|
37
|
+
* for these — the leading token routes to the engine's own command surface,
|
|
38
|
+
* not prose referencing a memory doc. Each lookup is independently wrapped so
|
|
39
|
+
* an engine missing one accessor (the fake, or a degraded session) degrades
|
|
40
|
+
* that check to "not a match" rather than aborting the whole classification —
|
|
41
|
+
* matching `buildCommandList`'s existing "no live extensionRunner" tolerance.
|
|
42
|
+
*/
|
|
43
|
+
export function isLeadingEngineCommand(text, session) {
|
|
44
|
+
if (text.startsWith('/')) {
|
|
45
|
+
const spaceIndex = text.indexOf(' ');
|
|
46
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
47
|
+
try {
|
|
48
|
+
if (session.extensionRunner?.getCommand(commandName))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* engine without a live extensionRunner (e.g. the fake) — not a match */
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (text.startsWith('/skill:')) {
|
|
56
|
+
const spaceIndex = text.indexOf(' ');
|
|
57
|
+
const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
|
|
58
|
+
try {
|
|
59
|
+
if (session.resourceLoader?.getSkills().skills.some((s) => s.name === skillName))
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* engine without a live resourceLoader — not a match */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (text.startsWith('/')) {
|
|
67
|
+
const match = text.match(/^\/([^\s]+)(?:\s+([\s\S]*))?$/);
|
|
68
|
+
const templateName = match?.[1];
|
|
69
|
+
if (templateName !== undefined) {
|
|
70
|
+
try {
|
|
71
|
+
if (session.promptTemplates?.some((t) => t.name === templateName))
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* engine without a live promptTemplates getter — not a match */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
/** A `steer` frame normally maps directly to pi's steer queue. Engine commands,
|
|
82
|
+
* skills, and templates must instead pass through prompt()'s leading-command
|
|
83
|
+
* preflight; with streamingBehavior:'steer', ordinary expanded text still lands
|
|
84
|
+
* in the same queue. */
|
|
85
|
+
export function resolveSteerCall(text, session) {
|
|
86
|
+
return isLeadingEngineCommand(text, session) ? 'prompt' : 'steer';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Enumerate every leading-token STRING this session's engine would
|
|
90
|
+
* independently consume as a leading command/skill/template dispatch BEFORE
|
|
91
|
+
* inline memory-reference guidance ever applies — the read-op sibling of
|
|
92
|
+
* `isLeadingEngineCommand`'s point-lookup, gathered as a full enumeration for
|
|
93
|
+
* viewer-side suppression instead of a per-submission membership check. Same
|
|
94
|
+
* three sources, same shape of the leading match `isLeadingEngineCommand`
|
|
95
|
+
* checks (bare extension-command invocation names and prompt-template names,
|
|
96
|
+
* `skill:<name>` for skills), so `/${token}` round-trips through
|
|
97
|
+
* `isLeadingEngineCommand(text, session)` for every emitted token (locked by
|
|
98
|
+
* a test). Kept STRICTLY separate from `buildCommandList` (command
|
|
99
|
+
* completion) and the ref inventory (`RefMeta` rows) — this is neither.
|
|
100
|
+
*/
|
|
101
|
+
export function engineLeadingCommandTokens(session) {
|
|
102
|
+
const out = [];
|
|
103
|
+
try {
|
|
104
|
+
for (const c of session.extensionRunner?.getRegisteredCommands() ?? [])
|
|
105
|
+
out.push(c.invocationName);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* engine without a live extensionRunner (e.g. the fake) — none */
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
for (const s of session.resourceLoader?.getSkills().skills ?? [])
|
|
112
|
+
out.push(`skill:${s.name}`);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* engine without a live resourceLoader — none */
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
for (const t of session.promptTemplates ?? [])
|
|
119
|
+
out.push(t.name);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* engine without a live promptTemplates getter — none */
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Choose how the hidden `crtr-memory-refs` guidance custom message rides
|
|
128
|
+
* alongside a human frame's own engine call (design Flow B). Pure over the
|
|
129
|
+
* frame's routing outcome — never reads live session state itself, so a
|
|
130
|
+
* caller can decide the delivery mode from the exact same `route`
|
|
131
|
+
* `resolveEngineRoute` already computed for the human call.
|
|
132
|
+
*
|
|
133
|
+
* - `steer` frame -> 'steer' (always; no route call exists).
|
|
134
|
+
* - a TRUE mid-stream follow_up (`routeCall === 'followUp'`) -> 'followUp'.
|
|
135
|
+
* - every other `prompt`/`follow_up` routed as `routeCall === 'prompt'`:
|
|
136
|
+
* idle -> 'nextTurn' (the ONLY case guidance precedes the human call —
|
|
137
|
+
* pi's `prompt()` reads+clears its pending-nextTurn queue synchronously
|
|
138
|
+
* into the SAME turn as the new user message); streaming (m-B
|
|
139
|
+
* steer-while-streaming) -> 'steer', never 'nextTurn' (that would attach
|
|
140
|
+
* to a later, unrelated turn).
|
|
141
|
+
*/
|
|
142
|
+
export function chooseGuidanceDeliveryMode(frameType, routeCall, isStreaming) {
|
|
143
|
+
if (frameType === 'steer')
|
|
144
|
+
return 'steer';
|
|
145
|
+
if (routeCall === 'followUp')
|
|
146
|
+
return 'followUp';
|
|
147
|
+
return isStreaming ? 'steer' : 'nextTurn';
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Deliver a `nextTurn` guidance custom message ATOMICALLY around the idle-
|
|
151
|
+
* prompt call it's meant to ride alongside. Pi's `_pendingNextTurnMessages`
|
|
152
|
+
* queue (the only public seam for `deliverAs:'nextTurn'`) is drained ONLY deep
|
|
153
|
+
* inside `session.prompt()`, well after several await points — extension-
|
|
154
|
+
* command interception, the `input` extension transform, a streaming reroute,
|
|
155
|
+
* model/auth validation, pre-turn compaction — that can return or throw BEFORE
|
|
156
|
+
* the drain ever runs. `sendCustomMessage`'s nextTurn branch pushes
|
|
157
|
+
* synchronously and unconditionally, so any of those early exits strands the
|
|
158
|
+
* pushed guidance in the queue to attach itself to whichever LATER, unrelated
|
|
159
|
+
* prompt happens to reach the drain first.
|
|
160
|
+
*
|
|
161
|
+
* There is no public SDK seam to observe or cancel a pending nextTurn message
|
|
162
|
+
* (`_pendingNextTurnMessages` is TS-`private` only — a plain runtime field, not
|
|
163
|
+
* `#private`), so this reaches into that one field and removes EXACTLY the
|
|
164
|
+
* entry this call pushed, identified by object identity via a locally-
|
|
165
|
+
* generated marker riding the message's own `details` field (never surfaced to
|
|
166
|
+
* the model, and irrelevant if the message WAS consumed normally — `details`
|
|
167
|
+
* is just carried through like any other field once folded into a real turn).
|
|
168
|
+
* Runs UNCONDITIONALLY after the human call settles, success or failure/early-
|
|
169
|
+
* return alike — a "successful" `prompt()` promise can just as easily be an
|
|
170
|
+
* early return that skipped the drain (an intercepted extension command, an
|
|
171
|
+
* `input` transform, a streaming reroute) as a genuine consumed turn, so only
|
|
172
|
+
* checking `.catch` is not enough.
|
|
173
|
+
*/
|
|
174
|
+
export async function promptWithNextTurnGuidance(session, guidance, runPrompt) {
|
|
175
|
+
// Must survive Pi's structured-clone step when the queued custom message is
|
|
176
|
+
// folded into the turn. A Symbol reaches that step in `details` and aborts
|
|
177
|
+
// the real prompt with "could not be cloned" before the provider runs.
|
|
178
|
+
const marker = `crtr-memory-refs-pending:${randomUUID()}`;
|
|
179
|
+
let queued = false;
|
|
180
|
+
try {
|
|
181
|
+
await session.sendCustomMessage({ customType: 'crtr-memory-refs', content: guidance, display: false, details: marker }, { deliverAs: 'nextTurn' });
|
|
182
|
+
queued = true;
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
emitEvent({
|
|
186
|
+
level: 'warn',
|
|
187
|
+
event: 'broker.memory_refs.guidance_send_failed',
|
|
188
|
+
error: err,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
await runPrompt();
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
if (queued)
|
|
196
|
+
clearOwnPendingNextTurnGuidance(session, marker);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Splice out exactly the pending-nextTurn entry `marker` identifies (object-
|
|
200
|
+
* identity match on `details`), if it is still present — a no-op (and safe)
|
|
201
|
+
* if `session.prompt()` already drained it normally, or if the session/field
|
|
202
|
+
* shape doesn't match (defensive against an engine without the field, e.g. a
|
|
203
|
+
* future SDK bump or a structurally-different test double). */
|
|
204
|
+
function clearOwnPendingNextTurnGuidance(session, marker) {
|
|
205
|
+
const pending = session._pendingNextTurnMessages;
|
|
206
|
+
if (!Array.isArray(pending))
|
|
207
|
+
return;
|
|
208
|
+
const idx = pending.findIndex((m) => m?.details === marker);
|
|
209
|
+
if (idx !== -1)
|
|
210
|
+
pending.splice(idx, 1);
|
|
211
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readConfig } from '../../config.js';
|
|
2
|
+
import { envStreamWatchdogMs } from '../../../shared/env.js';
|
|
2
3
|
import { emitEvent } from '../../events/emit.js';
|
|
3
|
-
import { releaseKnownStreamWait,
|
|
4
|
+
import { releaseKnownStreamWait, StreamWatchdog } from '../stream-watchdog.js';
|
|
4
5
|
import { withoutYieldAbort } from '../session-cycles.js';
|
|
5
6
|
import { DEFAULT_WORKING_ACTIVITY, randomWorkingActivity } from '../../../shared/working-activity.js';
|
|
6
7
|
import { hydrateLiveAdvertisedCommandMessage, prepareAdvertisedCommandPersistence, } from '../advertised-command-invocation.js';
|
|
@@ -12,7 +13,7 @@ export class EventProjection {
|
|
|
12
13
|
pendingUpdateTimer = null;
|
|
13
14
|
workingActivity = DEFAULT_WORKING_ACTIVITY;
|
|
14
15
|
streamWatchdog;
|
|
15
|
-
streamWatchdogMs =
|
|
16
|
+
streamWatchdogMs = envStreamWatchdogMs();
|
|
16
17
|
pendingBroadcast = null;
|
|
17
18
|
broadcastGeneration = 0;
|
|
18
19
|
constructor(deps) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { envModelIntent, envNodeCwd, envProfileId } from '../../../shared/env.js';
|
|
1
2
|
import { isContextOverflow } from '@earendil-works/pi-ai';
|
|
2
3
|
import { classify } from '../../fault-classifier.js';
|
|
3
4
|
import { clearFault, isModelNotFoundError, isProviderUnconfiguredError, readFault, recordFault } from '../fault.js';
|
|
@@ -8,6 +9,7 @@ import { emitEvent } from '../../events/emit.js';
|
|
|
8
9
|
import { CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY, formatCard, formatModelFallbackRecovery } from '../../../shared/generated-context.js';
|
|
9
10
|
import { markFatalOnExhaust, nextFaultRetry, policyFor } from '../fault-recovery.js';
|
|
10
11
|
import { parseModelSpec } from '../model-selection.js';
|
|
12
|
+
import { endedByAbort } from '../session-cycles.js';
|
|
11
13
|
import { resolveProviderCandidates } from '../../subscription-state.js';
|
|
12
14
|
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
13
15
|
/** Owns provider fallback and daemon-driven fault retry state for one broker session. */
|
|
@@ -89,6 +91,11 @@ export class FaultRetry {
|
|
|
89
91
|
if (refreshAbort)
|
|
90
92
|
return;
|
|
91
93
|
const raw = typeof last.errorMessage === 'string' ? last.errorMessage : 'engine error (no errorMessage recorded)';
|
|
94
|
+
// An abort landing between requests reaches pi's catch as a bare AbortError
|
|
95
|
+
// and settles as an error rather than `aborted`. Something asked that turn
|
|
96
|
+
// to stop, so there is no provider fault to record.
|
|
97
|
+
if (endedByAbort({ stopReason: 'error', errorMessage: raw }))
|
|
98
|
+
return;
|
|
92
99
|
const uncompactedOverflow = isContextOverflow(last, settledSession.model?.contextWindow);
|
|
93
100
|
const classified = classify('pi→provider', last);
|
|
94
101
|
const coolingDeadline = extractCoolingDeadline(last);
|
|
@@ -140,10 +147,10 @@ export class FaultRetry {
|
|
|
140
147
|
const currentSpec = this.deps.formatModelSpec(candidateSession.model, candidateSession.thinkingLevel);
|
|
141
148
|
if (currentSpec === null)
|
|
142
149
|
return;
|
|
143
|
-
const routeRequest = modelRequestFromConfig(this.deps.cfg.model,
|
|
150
|
+
const routeRequest = modelRequestFromConfig(this.deps.cfg.model, envModelIntent(), false);
|
|
144
151
|
if (routeRequest === undefined)
|
|
145
152
|
return;
|
|
146
|
-
const routes = expandModelCandidates(routeRequest,
|
|
153
|
+
const routes = expandModelCandidates(routeRequest, envNodeCwd() ?? this.deps.cfg.cwd, envProfileId());
|
|
147
154
|
const current = parseModelSpec(currentSpec);
|
|
148
155
|
const liveRank = routes.find((candidate) => `${candidate.providerId}/${candidate.modelId}` === current.modelSpec && candidate.thinkingLevel === current.thinkingLevel)?.rank;
|
|
149
156
|
const nextRoute = liveRank === undefined ? undefined : routes.slice(liveRank + 1).find((candidate) => !this.failedRetryRoutes.has(candidate.routeId) && resolveProviderCandidates([{ ...candidate, ...probeRouteAvailability(candidate, this.deps.registryOf(candidateServices)), automaticFallback: true }]).candidates.length > 0);
|
|
@@ -183,8 +190,8 @@ export class FaultRetry {
|
|
|
183
190
|
return;
|
|
184
191
|
const current = parseModelSpec(currentSpec);
|
|
185
192
|
this.notFoundModels.add(current.modelSpec);
|
|
186
|
-
const routeRequest = modelRequestFromConfig(this.deps.cfg.model,
|
|
187
|
-
const routes = routeRequest ? expandModelCandidates(routeRequest,
|
|
193
|
+
const routeRequest = modelRequestFromConfig(this.deps.cfg.model, envModelIntent(), false);
|
|
194
|
+
const routes = routeRequest ? expandModelCandidates(routeRequest, envNodeCwd() ?? this.deps.cfg.cwd, envProfileId()) : [];
|
|
188
195
|
const liveRank = routes.find((candidate) => `${candidate.providerId}/${candidate.modelId}` === current.modelSpec && candidate.thinkingLevel === current.thinkingLevel)?.rank;
|
|
189
196
|
const nextRoute = liveRank === undefined ? undefined : routes.slice(liveRank + 1).find((candidate) => !this.notFoundModels.has(`${candidate.providerId}/${candidate.modelId}`) && resolveProviderCandidates([{ ...candidate, ...probeRouteAvailability(candidate, this.deps.registryOf(candidateServices)), automaticFallback: true }]).candidates.length > 0);
|
|
190
197
|
if (!nextRoute)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ClientToBroker } from '../broker-protocol.js';
|
|
2
|
+
import type { BrokerClient, BrokerClientRegistry } from './client-registry.js';
|
|
3
|
+
import type { EngineDriver } from './engine-drive.js';
|
|
4
|
+
import type { FrameReplies } from './frame-replies.js';
|
|
5
|
+
import type { BrokerSession } from './read-ops.js';
|
|
6
|
+
import type { TurnAcceptanceGate } from './turn-ignition.js';
|
|
7
|
+
/** An extension dialog fanned out to writable clients, awaiting the first answer. */
|
|
8
|
+
export type PendingDialog = {
|
|
9
|
+
resolve: (response: Extract<ClientToBroker, {
|
|
10
|
+
type: 'extension_ui_response';
|
|
11
|
+
}>) => void;
|
|
12
|
+
};
|
|
13
|
+
/** The frames about a client's own connection — admission, one-shot delivery,
|
|
14
|
+
* dialog answers, and the two ways a socket or the broker ends. */
|
|
15
|
+
export type ClientFrameHandlers = {
|
|
16
|
+
hello(client: BrokerClient, frame: Extract<ClientToBroker, {
|
|
17
|
+
type: 'hello';
|
|
18
|
+
}>): void;
|
|
19
|
+
deliver(client: BrokerClient, frame: Extract<ClientToBroker, {
|
|
20
|
+
type: 'deliver';
|
|
21
|
+
}>): void;
|
|
22
|
+
dialog(client: BrokerClient, frame: Extract<ClientToBroker, {
|
|
23
|
+
type: 'extension_ui_response';
|
|
24
|
+
}>): void;
|
|
25
|
+
bye(client: BrokerClient): void;
|
|
26
|
+
shutdown(): void;
|
|
27
|
+
};
|
|
28
|
+
export declare function createClientHandlers(deps: {
|
|
29
|
+
registry: BrokerClientRegistry;
|
|
30
|
+
replies: FrameReplies;
|
|
31
|
+
session: () => BrokerSession;
|
|
32
|
+
driveEngine: EngineDriver;
|
|
33
|
+
turnAcceptance: TurnAcceptanceGate;
|
|
34
|
+
pendingDialogs: Map<string, PendingDialog>;
|
|
35
|
+
sendWelcome: (client: BrokerClient) => void;
|
|
36
|
+
replayExtraPendingDialogsTo: (client: BrokerClient) => void;
|
|
37
|
+
disposeAndExit: (reason: string, status?: number) => void;
|
|
38
|
+
}): ClientFrameHandlers;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { SITUATIONAL_CONTEXT_CUSTOM_TYPE } from '../../../shared/generated-context.js';
|
|
2
|
+
import { emitEvent } from '../../events/emit.js';
|
|
3
|
+
import { normalizeSnapshotTail } from '../broker-protocol.js';
|
|
4
|
+
export function createClientHandlers(deps) {
|
|
5
|
+
const { registry, replies, session, driveEngine, turnAcceptance, pendingDialogs, sendWelcome, replayExtraPendingDialogsTo, disposeAndExit, } = deps;
|
|
6
|
+
const { notWritable, ackTo } = replies;
|
|
7
|
+
return {
|
|
8
|
+
hello(client, frame) {
|
|
9
|
+
// Admit at the REQUESTED role — unconditionally. `controller` is a
|
|
10
|
+
// per-client write capability, not a singleton slot, so an arriving
|
|
11
|
+
// writable client never demotes (or is demoted by) an already-attached
|
|
12
|
+
// one: attached terminal panes are all writable at once.
|
|
13
|
+
//
|
|
14
|
+
// A repeated hello on an already-helloed socket must not RE-role it: the
|
|
15
|
+
// role is fixed for the socket's lifetime, so every downstream gate and
|
|
16
|
+
// every already-fanned-out dialog keep the authority the first welcome
|
|
17
|
+
// stated. A duplicate hello still re-sends the catch-up snapshot.
|
|
18
|
+
const firstHello = !client.helloed;
|
|
19
|
+
client.id = frame.client_id;
|
|
20
|
+
client.snapshotTail = normalizeSnapshotTail(frame.snapshot_tail);
|
|
21
|
+
if (firstHello) {
|
|
22
|
+
client.helloed = true;
|
|
23
|
+
client.role = frame.role === 'controller' ? 'controller' : 'observer';
|
|
24
|
+
client.attends = frame.attends !== false;
|
|
25
|
+
}
|
|
26
|
+
sendWelcome(client);
|
|
27
|
+
if (firstHello)
|
|
28
|
+
registry.persistAttachState(); // a helloed viewer arrived
|
|
29
|
+
// welcome carried the FIRST pending dialog; replay any extras so a writable
|
|
30
|
+
// client attaching mid-dialog can answer every in-flight dialog.
|
|
31
|
+
if (client.role === 'controller')
|
|
32
|
+
replayExtraPendingDialogsTo(client);
|
|
33
|
+
},
|
|
34
|
+
deliver(client, frame) {
|
|
35
|
+
// Interactive delivery from a one-shot server client (crtrd's
|
|
36
|
+
// `POST /messages` `delivery:'interactive'` path). Routed HERE,
|
|
37
|
+
// broker-side, because only the broker authoritatively knows streaming
|
|
38
|
+
// state: the synthesized `prompt` frame reuses resolveEngineRoute's m-B
|
|
39
|
+
// routing (idle → prompt(); mid-stream → prompt({streamingBehavior:
|
|
40
|
+
// 'steer'}), pi's own Enter-while-streaming submit) plus the memory-refs
|
|
41
|
+
// guidance path — byte-identical behavior to a viewer's Enter. Acked
|
|
42
|
+
// once ACCEPTED (correlated by the frame's id) so the daemon returns
|
|
43
|
+
// deterministically; `detail` reports which route was taken.
|
|
44
|
+
//
|
|
45
|
+
// The ack reports the engine call's OUTCOME, never merely the fact that
|
|
46
|
+
// a call was made: the session's prompt() rejects outright when the
|
|
47
|
+
// model's provider has no credential, and acking unconditionally reported
|
|
48
|
+
// `delivered:true` for a message the engine had refused — the caller's
|
|
49
|
+
// one-shot client then closed before the error frame arrived and the
|
|
50
|
+
// message vanished with no trace. A prompt route therefore acks on the
|
|
51
|
+
// race between acceptance (agent_start) and rejection; a steer route
|
|
52
|
+
// joins a turn already running, so no new agent_start is coming and its
|
|
53
|
+
// ack stays at routing time.
|
|
54
|
+
if (notWritable(client, 'drive the engine', frame.id))
|
|
55
|
+
return;
|
|
56
|
+
const via = session().isStreaming ? 'steer' : 'prompt';
|
|
57
|
+
// Ordered context cards ahead of the body, inside this ONE frame handling,
|
|
58
|
+
// so no other client's send can interleave between a card and its body.
|
|
59
|
+
// Nothing is awaited: `sendCustomMessage` completes synchronously on both
|
|
60
|
+
// branches that apply here — idle appends to the session and starts NO turn
|
|
61
|
+
// (the body is the sole trigger), streaming queues on the same steer queue
|
|
62
|
+
// the body's `prompt({streamingBehavior:'steer'})` uses, preserving order.
|
|
63
|
+
for (const card of frame.cards ?? []) {
|
|
64
|
+
void session()
|
|
65
|
+
.sendCustomMessage({
|
|
66
|
+
customType: SITUATIONAL_CONTEXT_CUSTOM_TYPE,
|
|
67
|
+
content: card,
|
|
68
|
+
display: true,
|
|
69
|
+
}, via === 'steer' ? { deliverAs: 'steer' } : undefined)
|
|
70
|
+
.catch((error) => emitEvent({
|
|
71
|
+
level: 'error',
|
|
72
|
+
event: 'broker.deliver.card_failed',
|
|
73
|
+
error,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
let delivered = false;
|
|
77
|
+
const ackAccepted = () => {
|
|
78
|
+
if (delivered)
|
|
79
|
+
return;
|
|
80
|
+
delivered = true;
|
|
81
|
+
ackTo(client, 'deliver', true, via, frame.id);
|
|
82
|
+
};
|
|
83
|
+
if (via === 'prompt')
|
|
84
|
+
turnAcceptance.waitFor(ackAccepted);
|
|
85
|
+
const outcome = driveEngine(client, {
|
|
86
|
+
type: 'prompt',
|
|
87
|
+
text: frame.text,
|
|
88
|
+
...(frame.images !== undefined ? { images: frame.images } : {}),
|
|
89
|
+
});
|
|
90
|
+
if (via === 'steer' || outcome === undefined) {
|
|
91
|
+
turnAcceptance.cancel(ackAccepted);
|
|
92
|
+
ackAccepted();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
void outcome.then((failure) => {
|
|
96
|
+
turnAcceptance.cancel(ackAccepted);
|
|
97
|
+
if (failure === undefined) {
|
|
98
|
+
ackAccepted();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// The error frame already went to the client, but a one-shot deliver
|
|
102
|
+
// client is gone by then — record it where it survives.
|
|
103
|
+
emitEvent({
|
|
104
|
+
level: 'error',
|
|
105
|
+
event: 'broker.prompt.deliver_failed',
|
|
106
|
+
error: failure,
|
|
107
|
+
});
|
|
108
|
+
if (delivered)
|
|
109
|
+
return;
|
|
110
|
+
delivered = true;
|
|
111
|
+
ackTo(client, 'deliver', false, String(failure), frame.id);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
dialog(client, frame) {
|
|
115
|
+
if (notWritable(client, 'answer dialogs'))
|
|
116
|
+
return;
|
|
117
|
+
// First response for this id wins: `resolve` settles the entry and deletes
|
|
118
|
+
// it, so every later response from a peer that was also fanned this dialog
|
|
119
|
+
// finds nothing here and is a silent no-op.
|
|
120
|
+
pendingDialogs.get(frame.id)?.resolve(frame);
|
|
121
|
+
},
|
|
122
|
+
bye(client) {
|
|
123
|
+
client.socket.end();
|
|
124
|
+
},
|
|
125
|
+
shutdown() {
|
|
126
|
+
disposeAndExit('shutdown-frame');
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|