@north-light/crouter 0.3.170 → 0.3.172
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.d.ts +37 -11
- package/dist/api/client.js +84 -15
- package/dist/api/dto/human.d.ts +53 -39
- package/dist/api/dto/human.js +2 -3
- package/dist/api/dto/inbox.d.ts +0 -1
- package/dist/api/dto/nodes.d.ts +12 -0
- package/dist/api/dto/review-comments.d.ts +137 -0
- package/dist/api/dto/review-comments.js +5 -0
- package/dist/api/dto/reviews.d.ts +109 -0
- package/dist/api/dto/reviews.js +5 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/routes.d.ts +17 -4
- package/dist/api/routes.js +21 -5
- package/dist/build-root.d.ts +1 -16
- package/dist/build-root.js +1 -25
- package/dist/builtin-memory/00-runtime-base.md +7 -5
- package/dist/builtin-memory/04-base-worker.md +1 -1
- package/dist/builtin-memory/04-orchestration-kernel.md +7 -7
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/review/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +20 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -7
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -3
- package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -3
- package/dist/builtin-memory/design.md +2 -2
- package/dist/builtin-memory/insights/capture.md +50 -0
- package/dist/builtin-memory/insights/init.md +45 -0
- package/dist/builtin-memory/insights/listen.md +9 -0
- package/dist/builtin-memory/internal/agent-shaping.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/plugins.md +12 -11
- package/dist/builtin-memory/internal/storage-tiers.md +2 -2
- package/dist/builtin-memory/{planning.md → plan/roadmap.md} +3 -3
- package/dist/builtin-memory/plan.md +19 -0
- package/dist/builtin-memory/spec/guide.md +38 -0
- package/dist/builtin-memory/spec/requirements.md +31 -0
- package/dist/builtin-memory/spec/roadmap.md +35 -0
- package/dist/builtin-memory/spec.md +10 -86
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +3 -4
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +49 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +56 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +920 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +221 -156
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +4 -5
- package/dist/cli.js +2 -6
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +7 -35
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +32 -16
- package/dist/clients/attach/__tests__/context-message.test.js +142 -6
- package/dist/clients/attach/__tests__/crtr-output.test.js +29 -45
- package/dist/clients/attach/__tests__/diagram.test.js +1 -1
- package/dist/clients/attach/__tests__/file-review-focus.test.js +48 -0
- package/dist/clients/attach/__tests__/group-activity.test.js +266 -0
- package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +32 -0
- package/dist/clients/attach/__tests__/session-identity-refresh.test.js +32 -0
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -2
- package/dist/clients/attach/chrome/header.d.ts +10 -0
- package/dist/clients/attach/chrome/header.js +30 -0
- package/dist/clients/attach/chrome/recap.d.ts +7 -0
- package/dist/clients/attach/chrome/recap.js +19 -0
- package/dist/clients/attach/chrome/status-line.d.ts +3 -0
- package/dist/clients/attach/chrome/status-line.js +5 -0
- package/dist/clients/attach/chrome/widgets.js +11 -21
- package/dist/clients/attach/command.js +11 -7
- package/dist/clients/attach/config.d.ts +4 -0
- package/dist/clients/attach/config.js +3 -0
- package/dist/clients/attach/input/controller.d.ts +19 -0
- package/dist/clients/attach/input/controller.js +62 -28
- package/dist/clients/attach/input/titled-editor.d.ts +0 -4
- package/dist/clients/attach/input/titled-editor.js +2 -10
- package/dist/clients/attach/overlays/file-review.d.ts +9 -5
- package/dist/clients/attach/overlays/file-review.js +9 -70
- package/dist/clients/attach/overlays/help.d.ts +33 -0
- package/dist/clients/attach/overlays/help.js +204 -0
- package/dist/clients/attach/render/activity-slot.d.ts +4 -0
- package/dist/clients/attach/render/activity-slot.js +13 -0
- package/dist/clients/attach/render/chat-view.d.ts +117 -12
- package/dist/clients/attach/render/chat-view.js +604 -135
- package/dist/clients/attach/render/context-message.d.ts +2 -1
- package/dist/clients/attach/render/context-message.js +17 -13
- package/dist/clients/attach/render/crtr-output.js +1 -3
- package/dist/clients/attach/render/diagram.js +2 -2
- package/dist/clients/attach/render/edit-diff.d.ts +4 -1
- package/dist/clients/attach/render/edit-diff.js +7 -11
- package/dist/clients/attach/render/group-activity.d.ts +32 -0
- package/dist/clients/attach/render/group-activity.js +206 -0
- package/dist/clients/attach/render/group-recap.d.ts +10 -0
- package/dist/clients/attach/render/group-recap.js +93 -0
- package/dist/clients/attach/render/html-markdown.d.ts +17 -0
- package/dist/clients/attach/render/html-markdown.js +228 -0
- package/dist/clients/attach/render/measured-container.d.ts +96 -0
- package/dist/clients/attach/render/measured-container.js +322 -0
- package/dist/clients/attach/render/scroll-animation.d.ts +8 -0
- package/dist/clients/attach/render/scroll-animation.js +80 -0
- package/dist/clients/attach/render/tool-calls.js +6 -6
- package/dist/clients/attach/render/transcript-copy.d.ts +9 -0
- package/dist/clients/attach/render/transcript-copy.js +81 -0
- package/dist/clients/attach/render/viewport.d.ts +29 -0
- package/dist/clients/attach/render/viewport.js +159 -0
- package/dist/clients/attach/session/bindings.d.ts +10 -7
- package/dist/clients/attach/session/bindings.js +23 -8
- package/dist/clients/attach/session/connection.js +4 -5
- package/dist/clients/attach/session/context.d.ts +6 -4
- package/dist/clients/attach/session/frame.d.ts +34 -0
- package/dist/clients/attach/session/frame.js +176 -0
- package/dist/clients/attach/session/frames.d.ts +4 -1
- package/dist/clients/attach/session/frames.js +14 -1
- package/dist/clients/attach/session/identity.js +4 -3
- package/dist/clients/attach/session/input-wiring.d.ts +14 -4
- package/dist/clients/attach/session/input-wiring.js +15 -13
- package/dist/clients/attach/session/invariant.d.ts +9 -0
- package/dist/clients/attach/session/invariant.js +15 -0
- package/dist/clients/attach/session/keys.d.ts +10 -2
- package/dist/clients/attach/session/keys.js +13 -5
- package/dist/clients/attach/session/layout.d.ts +10 -8
- package/dist/clients/attach/session/layout.js +33 -34
- package/dist/clients/attach/session/mouse.d.ts +18 -0
- package/dist/clients/attach/session/mouse.js +64 -0
- package/dist/clients/attach/session/pane-tag.d.ts +3 -0
- package/dist/clients/attach/session/pane-tag.js +51 -8
- package/dist/clients/attach/session/reconnect.d.ts +3 -0
- package/dist/clients/attach/session/reconnect.js +42 -5
- package/dist/clients/attach/session/surface-frame.d.ts +20 -0
- package/dist/clients/attach/session/surface-frame.js +73 -0
- package/dist/clients/attach/session/surface-host.d.ts +64 -0
- package/dist/clients/attach/session/surface-host.js +255 -0
- package/dist/clients/attach/session/whip.d.ts +17 -0
- package/dist/clients/attach/session/whip.js +334 -0
- package/dist/clients/attach/slash/dispatch.d.ts +13 -1
- package/dist/clients/attach/slash/dispatch.js +51 -29
- package/dist/clients/attach/viewer.js +907 -819
- package/dist/clients/conversation/conversation.d.ts +65 -0
- package/dist/clients/conversation/conversation.js +318 -0
- package/dist/clients/conversation/frames.d.ts +30 -0
- package/dist/clients/conversation/frames.js +52 -0
- package/dist/clients/conversation/region.d.ts +51 -0
- package/dist/clients/conversation/region.js +265 -0
- package/dist/clients/conversation/submit.d.ts +38 -0
- package/dist/clients/conversation/submit.js +51 -0
- package/dist/clients/inbox/__tests__/inbox-controller.test.js +247 -0
- package/dist/clients/inbox/__tests__/mount-panel.test.js +776 -0
- package/dist/clients/inbox/controller.d.ts +79 -0
- package/dist/clients/inbox/controller.js +448 -0
- package/dist/clients/inbox/deck-adapter.d.ts +29 -0
- package/dist/clients/inbox/deck-adapter.js +62 -0
- package/dist/clients/inbox/layout.d.ts +8 -0
- package/dist/clients/inbox/layout.js +9 -0
- package/dist/clients/inbox/resolve.d.ts +10 -0
- package/dist/clients/inbox/resolve.js +34 -0
- package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +29 -0
- package/dist/clients/inbox/review/__tests__/remap.test.js +196 -0
- package/dist/clients/inbox/review/__tests__/review-core.test.js +341 -0
- package/dist/clients/inbox/review/anchors.d.ts +41 -0
- package/dist/clients/inbox/review/anchors.js +89 -0
- package/dist/clients/inbox/review/columns.d.ts +22 -0
- package/dist/clients/inbox/review/columns.js +40 -0
- package/dist/clients/inbox/review/comments-client.d.ts +66 -0
- package/dist/clients/inbox/review/comments-client.js +98 -0
- package/dist/clients/inbox/review/composition.d.ts +60 -0
- package/dist/clients/inbox/review/composition.js +278 -0
- package/dist/clients/inbox/review/document-region.d.ts +120 -0
- package/dist/clients/inbox/review/document-region.js +902 -0
- package/dist/clients/inbox/review/frame.d.ts +45 -0
- package/dist/clients/inbox/review/frame.js +215 -0
- package/dist/clients/inbox/review/keys.d.ts +67 -0
- package/dist/clients/inbox/review/keys.js +130 -0
- package/dist/clients/inbox/review/launch.d.ts +10 -0
- package/dist/clients/inbox/review/launch.js +129 -0
- package/dist/clients/inbox/review/live-source.d.ts +24 -0
- package/dist/clients/inbox/review/live-source.js +160 -0
- package/dist/clients/inbox/review/nvim.d.ts +13 -0
- package/dist/clients/inbox/review/nvim.js +42 -0
- package/dist/clients/inbox/review/remap.d.ts +51 -0
- package/dist/clients/inbox/review/remap.js +157 -0
- package/dist/clients/inbox/review/review-client.d.ts +30 -0
- package/dist/clients/inbox/review/review-client.js +107 -0
- package/dist/clients/inbox/review/roundtrip.d.ts +5 -0
- package/dist/clients/inbox/review/roundtrip.js +36 -0
- package/dist/clients/inbox/review/state.d.ts +143 -0
- package/dist/clients/inbox/review/state.js +282 -0
- package/dist/clients/inbox/review-adapter.d.ts +29 -0
- package/dist/clients/inbox/review-adapter.js +92 -0
- package/dist/clients/inbox/review-visibility.d.ts +5 -0
- package/dist/clients/inbox/review-visibility.js +19 -0
- package/dist/clients/inbox/surface.d.ts +19 -0
- package/dist/clients/inbox/surface.js +128 -0
- package/dist/clients/inbox/tui/ansi.d.ts +66 -0
- package/dist/clients/inbox/tui/ansi.js +317 -0
- package/dist/clients/inbox/tui/clipboard.d.ts +2 -0
- package/dist/clients/inbox/tui/clipboard.js +26 -0
- package/dist/clients/inbox/tui/input.d.ts +7 -0
- package/dist/clients/inbox/tui/input.js +653 -0
- package/dist/clients/inbox/tui/keys.d.ts +28 -0
- package/dist/clients/inbox/tui/keys.js +111 -0
- package/dist/clients/inbox/tui/panel.d.ts +2 -0
- package/dist/clients/inbox/tui/panel.js +217 -0
- package/dist/clients/inbox/tui/render.d.ts +37 -0
- package/dist/clients/inbox/tui/render.js +476 -0
- package/dist/clients/inbox/tui/types.d.ts +57 -0
- package/dist/clients/inbox/tui/types.js +1 -0
- package/dist/clients/inbox/tui.d.ts +6 -0
- package/dist/clients/inbox/tui.js +74 -0
- package/dist/clients/surfaces/host.d.ts +66 -0
- package/dist/clients/surfaces/host.js +1 -0
- package/dist/clients/surfaces/region.d.ts +16 -0
- package/dist/clients/surfaces/region.js +1 -0
- package/dist/clients/web/__tests__/push-engine.test.js +14 -158
- package/dist/clients/web/dev-server.d.ts +3 -3
- package/dist/clients/web/dev-server.js +9 -23
- package/dist/clients/web/events.js +4 -6
- package/dist/clients/web/push-engine.d.ts +2 -66
- package/dist/clients/web/push-engine.js +30 -161
- package/dist/clients/web/push-registry.d.ts +0 -4
- package/dist/clients/web/push-registry.js +2 -7
- package/dist/clients/web/server.js +3 -35
- package/dist/clients/web/web-client/shared/protocol.d.ts +0 -108
- package/dist/clients/web/web-cmd.js +2 -2
- package/dist/commands/__tests__/human.test.js +27 -15
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/canvas-issue.js +1 -1
- package/dist/commands/canvas-use.js +1 -1
- package/dist/commands/human/doc.d.ts +2 -0
- package/dist/commands/human/doc.js +98 -0
- package/dist/commands/human/inbox.d.ts +2 -0
- package/dist/commands/human/inbox.js +101 -0
- package/dist/commands/human/prompts.d.ts +2 -3
- package/dist/commands/human/prompts.js +27 -180
- package/dist/commands/human/queue.d.ts +1 -5
- package/dist/commands/human/queue.js +54 -216
- package/dist/commands/human/review.d.ts +2 -0
- package/dist/commands/human/review.js +298 -0
- package/dist/commands/human/shared.d.ts +2 -69
- package/dist/commands/human/shared.js +1 -181
- package/dist/commands/human.js +15 -20
- package/dist/commands/memory/__tests__/lint.test.d.ts +1 -0
- package/dist/commands/memory/__tests__/lint.test.js +15 -0
- package/dist/commands/memory/delete.js +2 -2
- package/dist/commands/memory/lint.d.ts +1 -0
- package/dist/commands/memory/lint.js +21 -3
- package/dist/commands/memory/read.js +4 -4
- package/dist/commands/node.js +8 -7
- package/dist/commands/pkg/plugin-manage.js +143 -68
- package/dist/commands/surface-inspect.js +2 -2
- package/dist/commands/surface.js +6 -12
- package/dist/commands/sys/__tests__/setup-core.test.js +9 -5
- package/dist/commands/sys/__tests__/setup-front-door.test.js +7 -42
- package/dist/commands/sys/config.js +29 -3
- package/dist/commands/sys/feedback.js +1 -1
- package/dist/commands/sys/setup-core.d.ts +12 -11
- package/dist/commands/sys/setup-core.js +55 -46
- package/dist/commands/sys/setup-wizard.d.ts +4 -1
- package/dist/commands/sys/setup-wizard.js +50 -9
- package/dist/commands/sys/sync-deps.js +1 -1
- package/dist/commands/sys.js +4 -6
- package/dist/core/__tests__/broker-sdk-wiring.test.js +6 -1
- package/dist/core/__tests__/broker-snapshot-history.test.js +9 -1
- package/dist/core/__tests__/dead-node-policy-table.test.js +3 -4
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +8 -5
- package/dist/core/__tests__/fixtures/fake-engine.js +28 -2
- package/dist/core/__tests__/full/broker-provider-retry.test.js +87 -2
- package/dist/core/__tests__/full/broker-whip-interrupt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-whip-interrupt.test.js +39 -0
- package/dist/core/__tests__/full/detach-focus.test.js +1 -1
- package/dist/core/__tests__/human-deliver-e2e.test.js +64 -64
- package/dist/core/__tests__/human-deliver.test.js +71 -345
- package/dist/core/__tests__/inline-memory-refs.test.js +2 -12
- package/dist/core/__tests__/memory-resolver-precedence.test.js +12 -15
- package/dist/core/__tests__/migration.test.js +10 -0
- package/dist/core/__tests__/model-pin-durability.test.js +14 -0
- package/dist/core/__tests__/phase4-review-migration.test.d.ts +1 -0
- package/dist/core/__tests__/phase4-review-migration.test.js +100 -0
- package/dist/core/__tests__/phase4-review-store.test.d.ts +1 -0
- package/dist/core/__tests__/phase4-review-store.test.js +143 -0
- package/dist/core/__tests__/remote-canvas-target.test.js +2 -2
- package/dist/core/__tests__/revive.test.js +22 -53
- package/dist/core/__tests__/session-cycles.test.js +48 -18
- package/dist/core/__tests__/spawn-no-kickoff.test.js +8 -12
- package/dist/core/__tests__/stop-guard.test.js +16 -1
- package/dist/core/__tests__/tmux-surface.test.js +21 -25
- package/dist/core/asset-root.d.ts +7 -0
- package/dist/core/asset-root.js +18 -0
- package/dist/core/broker-client/client.d.ts +0 -7
- package/dist/core/broker-client/client.js +4 -10
- package/dist/core/broker-client/transport-relay.d.ts +3 -4
- package/dist/core/broker-client/transport-relay.js +3 -4
- package/dist/core/canvas/__tests__/attention.test.js +1 -1
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
- package/dist/core/canvas/__tests__/remote-transport.test.d.ts +1 -0
- package/dist/core/{view/__tests__/transport-remote.test.js → canvas/__tests__/remote-transport.test.js} +2 -2
- package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
- package/dist/core/canvas/attention.js +4 -4
- package/dist/core/canvas/canvas.d.ts +6 -11
- package/dist/core/canvas/canvas.js +11 -33
- package/dist/core/canvas/db.js +207 -38
- package/dist/core/canvas/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +8 -1
- package/dist/core/canvas/index.d.ts +0 -1
- package/dist/core/canvas/index.js +0 -1
- package/dist/core/canvas/nav-model.d.ts +1 -1
- package/dist/core/canvas/nav-model.js +1 -1
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +16 -0
- package/dist/core/canvas/remote-canvas-source.js +2 -2
- package/dist/core/canvas/remote-transport.d.ts +7 -0
- package/dist/core/{view/transport-remote.js → canvas/remote-transport.js} +3 -4
- package/dist/core/canvas/source.js +1 -1
- package/dist/core/canvas/status-glyph.js +4 -12
- package/dist/core/canvas/types.d.ts +10 -35
- package/dist/core/clipboard-text.d.ts +8 -0
- package/dist/core/clipboard-text.js +18 -0
- package/dist/core/command-manifests/registry.d.ts +1 -3
- package/dist/core/command-plugins/bundle.d.ts +29 -0
- package/dist/core/command-plugins/bundle.js +198 -0
- package/dist/core/command-plugins/discovery.d.ts +0 -8
- package/dist/core/command-plugins/discovery.js +2 -39
- package/dist/core/command-plugins/transport/http-fetch.d.ts +4 -30
- package/dist/core/command-plugins/transport/http-fetch.js +17 -95
- package/dist/core/command.d.ts +1 -13
- package/dist/core/command.js +1 -16
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +28 -3
- package/dist/core/document-lines.d.ts +4 -0
- package/dist/core/document-lines.js +6 -0
- package/dist/core/feed/feed.d.ts +2 -0
- package/dist/core/feed/feed.js +11 -7
- package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +1 -0
- package/dist/core/human/__tests__/inbox-cancel-worker.js +7 -0
- package/dist/core/human/__tests__/inbox-claim-worker.d.ts +1 -0
- package/dist/core/human/__tests__/inbox-claim-worker.js +17 -0
- package/dist/core/human/__tests__/inbox-core.test.d.ts +1 -0
- package/dist/core/human/__tests__/inbox-core.test.js +144 -0
- package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +1 -0
- package/dist/core/human/__tests__/inbox-stale-claim-worker.js +8 -0
- package/dist/core/human/__tests__/visible-paths.test.d.ts +1 -0
- package/dist/{clients/attach → core/human}/__tests__/visible-paths.test.js +9 -0
- package/dist/core/human/claim.d.ts +23 -0
- package/dist/core/human/claim.js +67 -0
- package/dist/core/human/convention.d.ts +30 -0
- package/dist/core/human/convention.js +202 -0
- package/dist/core/human/deck-factories.d.ts +8 -0
- package/dist/core/human/deck-factories.js +15 -0
- package/dist/core/human/deck-schema.d.ts +70 -0
- package/dist/core/human/deck-schema.js +92 -0
- package/dist/core/human/root.d.ts +7 -0
- package/dist/core/human/root.js +37 -0
- package/dist/core/human/scan.d.ts +3 -0
- package/dist/core/human/scan.js +76 -0
- package/dist/core/human/summary.d.ts +8 -0
- package/dist/core/human/summary.js +44 -0
- package/dist/core/human/tickets.d.ts +49 -0
- package/dist/core/human/tickets.js +190 -0
- package/dist/core/human/types.d.ts +136 -0
- package/dist/core/human/types.js +5 -0
- package/dist/{clients/attach → core/human}/visible-paths.d.ts +1 -1
- package/dist/{clients/attach → core/human}/visible-paths.js +3 -2
- package/dist/core/inspector/chrome.d.ts +6 -0
- package/dist/core/inspector/chrome.js +14 -0
- package/dist/core/inspector/contract.d.ts +105 -0
- package/dist/core/inspector/contract.js +1 -0
- package/dist/core/inspector/core.d.ts +4 -5
- package/dist/core/inspector/core.js +3 -5
- package/dist/core/inspector/host.d.ts +46 -0
- package/dist/core/{tui → inspector}/host.js +42 -208
- package/dist/core/inspector/text.d.ts +2 -2
- package/dist/core/inspector/transport.d.ts +6 -0
- package/dist/core/inspector/transport.js +36 -0
- package/dist/core/inspector/tui.d.ts +2 -2
- package/dist/core/inspector/tui.js +15 -15
- package/dist/core/keybindings/__tests__/inbox-affordance.test.js +11 -11
- package/dist/core/keybindings/__tests__/resolve.test.js +10 -19
- package/dist/core/keybindings/attach-control.d.ts +9 -3
- package/dist/core/keybindings/attach-control.js +3 -1
- package/dist/core/keybindings/catalog.d.ts +6 -5
- package/dist/core/keybindings/catalog.js +61 -133
- package/dist/core/keybindings/inbox.d.ts +5 -6
- package/dist/core/keybindings/inbox.js +9 -10
- package/dist/core/keybindings/index.d.ts +3 -3
- package/dist/core/keybindings/index.js +3 -3
- package/dist/core/keybindings/persistence.d.ts +5 -1
- package/dist/core/keybindings/persistence.js +24 -6
- package/dist/core/keybindings/resolve.d.ts +6 -0
- package/dist/core/keybindings/resolve.js +10 -3
- package/dist/core/memory/inline-ref-grammar.js +2 -3
- package/dist/core/memory-resolver.d.ts +27 -29
- package/dist/core/memory-resolver.js +45 -39
- package/dist/core/model-routes.d.ts +33 -0
- package/dist/core/model-routes.js +42 -3
- package/dist/core/preview-registry.js +17 -55
- package/dist/core/profiles/select.js +3 -3
- package/dist/core/{view/remote-canvas-target.d.ts → remote-canvas-target.d.ts} +2 -2
- package/dist/core/{view/remote-canvas-target.js → remote-canvas-target.js} +4 -4
- package/dist/core/review/__tests__/capture-origin.test.d.ts +1 -0
- package/dist/core/review/__tests__/capture-origin.test.js +139 -0
- package/dist/core/review/__tests__/comments.test.d.ts +1 -0
- package/dist/core/review/__tests__/comments.test.js +98 -0
- package/dist/core/review/__tests__/project.test.d.ts +1 -0
- package/dist/core/review/__tests__/project.test.js +71 -0
- package/dist/core/review/__tests__/remap.test.d.ts +1 -0
- package/dist/core/review/__tests__/remap.test.js +159 -0
- package/dist/core/review/__tests__/stage-identity.test.d.ts +1 -0
- package/dist/core/review/__tests__/stage-identity.test.js +140 -0
- package/dist/core/review/birth.d.ts +7 -0
- package/dist/core/review/birth.js +23 -0
- package/dist/core/review/comments.d.ts +69 -0
- package/dist/core/review/comments.js +295 -0
- package/dist/core/review/companion.d.ts +35 -0
- package/dist/core/review/companion.js +205 -0
- package/dist/core/review/document.d.ts +27 -0
- package/dist/core/review/document.js +138 -0
- package/dist/core/review/project.d.ts +13 -0
- package/dist/core/review/project.js +58 -0
- package/dist/core/review/realize.d.ts +19 -0
- package/dist/core/review/realize.js +217 -0
- package/dist/core/review/remap.d.ts +24 -0
- package/dist/core/review/remap.js +156 -0
- package/dist/core/review/signal.d.ts +5 -0
- package/dist/core/review/signal.js +31 -0
- package/dist/core/review/stage.d.ts +26 -0
- package/dist/core/review/stage.js +184 -0
- package/dist/core/review/store.d.ts +46 -0
- package/dist/core/review/store.js +163 -0
- package/dist/core/review/ticket-filter.d.ts +17 -0
- package/dist/core/review/ticket-filter.js +32 -0
- package/dist/core/review/types.d.ts +130 -0
- package/dist/core/review/types.js +23 -0
- package/dist/core/runtime/__tests__/session-visibility.test.d.ts +1 -0
- package/dist/core/runtime/__tests__/session-visibility.test.js +119 -0
- package/dist/core/runtime/bearings.d.ts +5 -17
- package/dist/core/runtime/bearings.js +88 -71
- package/dist/core/runtime/broker-protocol.d.ts +31 -8
- package/dist/core/runtime/broker.d.ts +15 -1
- package/dist/core/runtime/broker.js +573 -141
- package/dist/core/runtime/canvas-extensions.d.ts +4 -5
- package/dist/core/runtime/canvas-extensions.js +5 -5
- package/dist/core/runtime/front-door.js +4 -4
- package/dist/core/runtime/headless-pi.d.ts +24 -0
- package/dist/core/runtime/headless-pi.js +152 -0
- package/dist/core/runtime/kickoff.d.ts +1 -5
- package/dist/core/runtime/kickoff.js +1 -5
- package/dist/core/runtime/model-registry.d.ts +3 -0
- package/dist/core/runtime/model-registry.js +13 -0
- package/dist/core/runtime/naming-persist.d.ts +4 -0
- package/dist/core/runtime/naming-persist.js +27 -1
- package/dist/core/runtime/naming.d.ts +17 -12
- package/dist/core/runtime/naming.js +91 -140
- package/dist/core/runtime/node-read.js +19 -10
- package/dist/core/runtime/nodes.d.ts +9 -14
- package/dist/core/runtime/nodes.js +3 -53
- package/dist/core/runtime/package-health.d.ts +6 -21
- package/dist/core/runtime/package-health.js +12 -123
- package/dist/core/runtime/placement-tmux.d.ts +2 -1
- package/dist/core/runtime/placement-tmux.js +6 -4
- package/dist/core/runtime/placement.d.ts +1 -0
- package/dist/core/runtime/placement.js +4 -8
- package/dist/core/runtime/promote.js +3 -0
- package/dist/core/runtime/recap.d.ts +2 -7
- package/dist/core/runtime/recap.js +35 -69
- package/dist/core/runtime/revive.js +34 -77
- package/dist/core/runtime/session-cycles.d.ts +4 -0
- package/dist/core/runtime/session-cycles.js +14 -1
- package/dist/core/runtime/session-visibility.d.ts +37 -0
- package/dist/core/runtime/session-visibility.js +83 -0
- package/dist/core/runtime/spawn.js +10 -22
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +18 -13
- package/dist/core/runtime/surface-bg.js +1 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +35 -7
- package/dist/core/runtime/tmux.js +439 -62
- package/dist/core/runtime/tool-group-summary.d.ts +19 -0
- package/dist/core/runtime/tool-group-summary.js +88 -0
- package/dist/core/scope.d.ts +2 -7
- package/dist/core/scope.js +9 -35
- package/dist/core/substrate/schema.d.ts +2 -2
- package/dist/core/termrender/code-doc.d.ts +12 -0
- package/dist/core/termrender/code-doc.js +92 -0
- package/dist/core/termrender/display.d.ts +12 -0
- package/dist/core/termrender/display.js +19 -0
- package/dist/core/termrender/termrender.d.ts +73 -0
- package/dist/core/termrender/termrender.js +795 -0
- package/dist/core/termrender/version.d.ts +1 -0
- package/dist/core/termrender/version.js +1 -0
- package/dist/core/tui/terminal.js +1 -2
- package/dist/core/user-settings.d.ts +71 -3
- package/dist/core/user-settings.js +83 -1
- package/dist/daemon/api/__tests__/inbox.test.js +44 -133
- package/dist/daemon/api/handlers/human.d.ts +0 -10
- package/dist/daemon/api/handlers/human.js +104 -702
- package/dist/daemon/api/handlers/inbox.js +113 -142
- package/dist/daemon/api/handlers/review-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/review-comments.js +500 -0
- package/dist/daemon/api/handlers/reviews.d.ts +2 -0
- package/dist/daemon/api/handlers/reviews.js +287 -0
- package/dist/daemon/api/map.d.ts +22 -7
- package/dist/daemon/api/map.js +159 -11
- package/dist/daemon/api/server.js +4 -0
- package/dist/daemon/cron-run.js +6 -9
- package/dist/daemon/crtrd.js +17 -33
- package/dist/daemon/fleet.d.ts +1 -8
- package/dist/daemon/fleet.js +4 -30
- package/dist/daemon/human/finish.d.ts +14 -0
- package/dist/daemon/human/finish.js +115 -0
- package/dist/daemon/human/sweep.d.ts +6 -0
- package/dist/daemon/human/sweep.js +49 -0
- package/dist/daemon/messaging/node-message.d.ts +18 -0
- package/dist/daemon/messaging/node-message.js +70 -0
- package/dist/daemon/review/comment-notify.d.ts +26 -0
- package/dist/daemon/review/comment-notify.js +107 -0
- package/dist/daemon/review/deliver.d.ts +7 -0
- package/dist/daemon/review/deliver.js +56 -0
- package/dist/daemon/review/finish.d.ts +22 -0
- package/dist/daemon/review/finish.js +254 -0
- package/dist/daemon/review/sweep.d.ts +11 -0
- package/dist/daemon/review/sweep.js +120 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +42 -64
- package/dist/pi-extensions/__tests__/canvas-recap.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-recap.test.js +159 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +16 -38
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +63 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +8 -11
- package/dist/pi-extensions/canvas-context-intro.js +50 -104
- package/dist/pi-extensions/canvas-recap.d.ts +15 -2
- package/dist/pi-extensions/canvas-recap.js +192 -69
- package/dist/pi-extensions/canvas-review-boundary.d.ts +59 -0
- package/dist/pi-extensions/canvas-review-boundary.js +81 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +3 -3
- package/dist/pi-extensions/canvas-stophook.js +78 -128
- package/dist/pi-extensions/canvas-structured-output.js +52 -8
- package/dist/shared/generated-context.d.ts +29 -4
- package/dist/shared/generated-context.js +122 -7
- package/dist/shared/tool-groups.d.ts +23 -0
- package/dist/shared/tool-groups.js +60 -0
- package/dist/shared/working-activity.d.ts +8 -0
- package/dist/shared/working-activity.js +26 -0
- package/dist/types.d.ts +36 -2
- package/dist/types.js +51 -0
- package/dist/web-client/assets/index-CCPaLeW7.css +2 -0
- package/dist/web-client/assets/index-kyOWsnZK.js +86 -0
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/compat/hearth-crtr-v4.md +2 -2
- package/docs/compat/hearth-crtr-v5.md +4 -2
- package/docs/public-api.md +3 -3
- package/package.json +21 -14
- package/runtime.lock.json +4661 -1453
- package/scripts/postinstall.mjs +15 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +0 -36
- package/dist/builtin-pi-packages/pi-mode-switch/bin/mode +0 -36
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +0 -449
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +0 -14
- package/dist/builtin-pi-packages/pi-mode-switch/tsconfig.json +0 -9
- package/dist/builtin-views/_lib/states.mjs +0 -161
- package/dist/builtin-views/canvas/core.mjs +0 -657
- package/dist/builtin-views/canvas/text.mjs +0 -58
- package/dist/builtin-views/canvas/tui.mjs +0 -168
- package/dist/builtin-views/canvas/web.jsx +0 -121
- package/dist/builtin-views/chat/core.mjs +0 -684
- package/dist/builtin-views/chat/text.mjs +0 -101
- package/dist/builtin-views/chat/tui.mjs +0 -360
- package/dist/builtin-views/chat/web.jsx +0 -362
- package/dist/builtin-views/git-pr/core.mjs +0 -673
- package/dist/builtin-views/git-pr/text.mjs +0 -84
- package/dist/builtin-views/git-pr/tui.mjs +0 -301
- package/dist/builtin-views/git-pr/web.jsx +0 -216
- package/dist/builtin-views/inbox/_lib/render.mjs +0 -175
- package/dist/builtin-views/inbox/core.mjs +0 -1273
- package/dist/builtin-views/inbox/text.mjs +0 -73
- package/dist/builtin-views/inbox/tui.mjs +0 -314
- package/dist/builtin-views/inbox/web.jsx +0 -188
- package/dist/builtin-views/linkedin/core.mjs +0 -906
- package/dist/builtin-views/linkedin/text.mjs +0 -69
- package/dist/builtin-views/linkedin/tui.mjs +0 -413
- package/dist/builtin-views/linkedin/web.jsx +0 -206
- package/dist/builtin-views/prompt-review/core.mjs +0 -735
- package/dist/builtin-views/prompt-review/text.mjs +0 -15
- package/dist/builtin-views/prompt-review/tui.mjs +0 -196
- package/dist/builtin-views/prompt-review/web.jsx +0 -484
- package/dist/builtin-views/workspace-sidebar/__tests__/core.test.js +0 -50
- package/dist/builtin-views/workspace-sidebar/__tests__/core.test.ts +0 -53
- package/dist/builtin-views/workspace-sidebar/core.mjs +0 -670
- package/dist/builtin-views/workspace-sidebar/text.mjs +0 -53
- package/dist/builtin-views/workspace-sidebar/tui.mjs +0 -141
- package/dist/builtin-views/workspace-sidebar/web.jsx +0 -109
- package/dist/clients/attach/session/mode.d.ts +0 -25
- package/dist/clients/attach/session/mode.js +0 -97
- package/dist/commands/sys/promptstudio.d.ts +0 -2
- package/dist/commands/sys/promptstudio.js +0 -65
- package/dist/commands/view-cycle.d.ts +0 -2
- package/dist/commands/view-cycle.js +0 -130
- package/dist/commands/view-list.d.ts +0 -2
- package/dist/commands/view-list.js +0 -66
- package/dist/commands/view-new.d.ts +0 -2
- package/dist/commands/view-new.js +0 -74
- package/dist/commands/view-pick.d.ts +0 -6
- package/dist/commands/view-pick.js +0 -129
- package/dist/commands/view-run.d.ts +0 -2
- package/dist/commands/view-run.js +0 -214
- package/dist/commands/view.d.ts +0 -2
- package/dist/commands/view.js +0 -31
- package/dist/commands/workspace.d.ts +0 -2
- package/dist/commands/workspace.js +0 -165
- package/dist/core/__tests__/chat-view-reconnect.test.js +0 -105
- package/dist/core/__tests__/full/consult-startup-failure.test.js +0 -124
- package/dist/core/canvas/__tests__/human-work-outbox.test.js +0 -123
- package/dist/core/canvas/human-work-outbox.d.ts +0 -73
- package/dist/core/canvas/human-work-outbox.js +0 -261
- package/dist/core/command-plugins/store.d.ts +0 -16
- package/dist/core/command-plugins/store.js +0 -64
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +0 -40
- package/dist/core/runtime/fault-recovery-nudge.d.ts +0 -3
- package/dist/core/runtime/fault-recovery-nudge.js +0 -3
- package/dist/core/tui/__tests__/host-keybindings.test.js +0 -113
- package/dist/core/tui/host.d.ts +0 -66
- package/dist/core/view/__tests__/transport-cache.test.js +0 -62
- package/dist/core/view/bridge.d.ts +0 -10
- package/dist/core/view/bridge.js +0 -31
- package/dist/core/view/chrome.d.ts +0 -9
- package/dist/core/view/chrome.js +0 -22
- package/dist/core/view/contract.d.ts +0 -205
- package/dist/core/view/contract.js +0 -23
- package/dist/core/view/loader.d.ts +0 -31
- package/dist/core/view/loader.js +0 -188
- package/dist/core/view/stream-local.d.ts +0 -3
- package/dist/core/view/stream-local.js +0 -231
- package/dist/core/view/transport-cache.d.ts +0 -8
- package/dist/core/view/transport-cache.js +0 -38
- package/dist/core/view/transport-local.d.ts +0 -7
- package/dist/core/view/transport-local.js +0 -83
- package/dist/core/view/transport-remote.d.ts +0 -2
- package/dist/core/view/transport.d.ts +0 -10
- package/dist/core/view/transport.js +0 -15
- package/dist/pi-extensions/naming-tool.d.ts +0 -24
- package/dist/pi-extensions/naming-tool.js +0 -67
- package/dist/prompts/view.d.ts +0 -7
- package/dist/prompts/view.js +0 -172
- package/dist/web/ViewChrome.d.ts +0 -7
- package/dist/web/ViewChrome.js +0 -28
- package/dist/web/ViewPane.d.ts +0 -39
- package/dist/web/ViewPane.js +0 -48
- package/dist/web/index.d.ts +0 -8
- package/dist/web/index.js +0 -22
- package/dist/web/runtime.d.ts +0 -39
- package/dist/web/runtime.js +0 -232
- package/dist/web/states.d.ts +0 -24
- package/dist/web/states.js +0 -24
- package/dist/web/transport-http.d.ts +0 -5
- package/dist/web/transport-http.js +0 -34
- package/dist/web/transport-stream.d.ts +0 -3
- package/dist/web/transport-stream.js +0 -204
- package/dist/web-client/assets/index-U_NZ66VE.js +0 -79
- package/dist/web-client/assets/index-aaeu4adv.css +0 -2
- /package/dist/{builtin-views/workspace-sidebar/__tests__/core.test.d.ts → clients/attach/__tests__/file-review-focus.test.d.ts} +0 -0
- /package/dist/clients/attach/__tests__/{visible-paths.test.d.ts → group-activity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/chat-view-reconnect.test.d.ts → clients/attach/__tests__/input-controller-extension-command.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/consult-startup-failure.test.d.ts → clients/attach/__tests__/session-identity-refresh.test.d.ts} +0 -0
- /package/dist/{core/canvas/__tests__/human-work-outbox.test.d.ts → clients/inbox/__tests__/inbox-controller.test.d.ts} +0 -0
- /package/dist/{core/keybindings/__tests__/bespoke-consumers.test.d.ts → clients/inbox/__tests__/mount-panel.test.d.ts} +0 -0
- /package/dist/{core/tui/__tests__/host-keybindings.test.d.ts → clients/inbox/review/__tests__/editor-roundtrip.test.d.ts} +0 -0
- /package/dist/{core/view/__tests__/transport-cache.test.d.ts → clients/inbox/review/__tests__/remap.test.d.ts} +0 -0
- /package/dist/{core/view/__tests__/transport-remote.test.d.ts → clients/inbox/review/__tests__/review-core.test.d.ts} +0 -0
|
@@ -1,741 +1,143 @@
|
|
|
1
|
-
// Human
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// canvas state (spec §1/§10). Two families live here:
|
|
6
|
-
//
|
|
7
|
-
// POST /v1/canvas/consults arm a consult-outbox receipt (idempotent)
|
|
8
|
-
// GET /v1/canvas/consults list active consult-outbox entries
|
|
9
|
-
//
|
|
10
|
-
// POST /v1/human/bridge create a kind:'human' bridge node (spawnNode, no broker)
|
|
11
|
-
// POST /v1/human/deliver run the registered completion handler (_deliver)
|
|
12
|
-
// POST /v1/human/consult run the registered follow-up handler (_consult)
|
|
13
|
-
// POST /v1/human/visual run the registered visual handler (_visual)
|
|
14
|
-
//
|
|
15
|
-
// The completion-handler bodies (`_deliver`/`_consult`/`_visual`) were lifted
|
|
16
|
-
// VERBATIM from `src/commands/human/queue.ts`: humanloop still invokes
|
|
17
|
-
// `node dist/cli.js human _deliver|_consult|_visual` with the event on stdin,
|
|
18
|
-
// but that CLI leaf is now a thin forwarder to the endpoints below. crtrd
|
|
19
|
-
// RE-VERIFIES the full trust binding (event schema, realpath containment, root
|
|
20
|
-
// registered to `crouter`, and — for consult/visual — the registered handler
|
|
21
|
-
// identity matching crouter's own argv) before performing any canvas mutation,
|
|
22
|
-
// so relocating the caller preserves the security boundary intact. A Visual
|
|
23
|
-
// request may outlive the immutable runtime generation that registered it, so
|
|
24
|
-
// its handler is accepted when it is either the daemon's exact current argv or
|
|
25
|
-
// a Node invocation of the same `_visual` leaf from a sealed sibling generation
|
|
26
|
-
// under the daemon's runtime root. This keeps live old brokers working across
|
|
27
|
-
// generation rolls without accepting an arbitrary executable or CLI path.
|
|
28
|
-
import { realpathSync, accessSync, constants as fsConstants } from 'node:fs';
|
|
29
|
-
import { basename, join, resolve } from 'node:path';
|
|
1
|
+
// Human bridge and ticket-work routes owned by crtrd. Ticket directories are
|
|
2
|
+
// derived from bridge node ids; callers never supply a filesystem path, root,
|
|
3
|
+
// handler, or claim binding.
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
30
5
|
import { ApiError } from '../../../api/index.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* longer exists on disk, so the `_deliver` containment/registration checks
|
|
47
|
-
* compare like against like regardless of symlinked path components. */
|
|
48
|
-
function canonicalPath(p) {
|
|
49
|
-
try {
|
|
50
|
-
return realpathSync(p);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return resolve(p);
|
|
54
|
-
}
|
|
6
|
+
import { isSafeNodeId } from '../../../core/canvas/paths.js';
|
|
7
|
+
import { deckPath, reviewPath } from '../../../core/human/convention.js';
|
|
8
|
+
import { ticketDir } from '../../../core/human/root.js';
|
|
9
|
+
import { readTicketResult, validateDeckTicketResponses } from '../../../core/human/tickets.js';
|
|
10
|
+
import { getReviewByBridge } from '../../../core/review/store.js';
|
|
11
|
+
import { ReviewOperationError } from '../../../core/review/types.js';
|
|
12
|
+
import { spawnNode } from '../../../core/runtime/nodes.js';
|
|
13
|
+
import { cancelHumanTicket, resolveDeckTicket } from '../../human/finish.js';
|
|
14
|
+
import { cancelReview } from '../../review/finish.js';
|
|
15
|
+
function requireTicketNodeId(ctx) {
|
|
16
|
+
const nodeId = ctx.params['node_id'];
|
|
17
|
+
if (nodeId === undefined || !isSafeNodeId(nodeId)) {
|
|
18
|
+
throw new ApiError(400, 'invalid_request', 'invalid ticket node id');
|
|
19
|
+
}
|
|
20
|
+
return nodeId;
|
|
55
21
|
}
|
|
56
22
|
// ===========================================================================
|
|
57
|
-
// POST
|
|
23
|
+
// POST /v1/human/bridge — create a terminal bridge without a broker
|
|
58
24
|
// ===========================================================================
|
|
59
|
-
/** `POST /v1/canvas/consults` — arm a consult-outbox receipt. Idempotent for a
|
|
60
|
-
* follow-up: an already-armed/pending request for the same dir+request_id is
|
|
61
|
-
* returned with `created:false` rather than double-armed.
|
|
62
|
-
*
|
|
63
|
-
* Visual consults carry an immutable correlation binding (interaction /
|
|
64
|
-
* generation / snapshot / handler / claim token) that `CreateConsultRequest`
|
|
65
|
-
* does not model, so a `visual` kind is refused (422) rather than half-written
|
|
66
|
-
* here. Since the `_visual` lift below arms visual work with its full binding
|
|
67
|
-
* server-side (`handleVisual`), no crouter caller ever reaches this route with
|
|
68
|
-
* a visual kind — the 422 is retained only as a defensive guard on the bare
|
|
69
|
-
* consult primitive. */
|
|
70
|
-
function handleCreate(ctx) {
|
|
71
|
-
const req = ctx.body;
|
|
72
|
-
if (req === undefined || typeof req !== 'object') {
|
|
73
|
-
throw new ApiError(400, 'invalid_request', 'consult body is required');
|
|
74
|
-
}
|
|
75
|
-
const { node_id, kind, request_id, dir, root } = req;
|
|
76
|
-
for (const [field, value] of Object.entries({ node_id, kind, request_id, dir, root })) {
|
|
77
|
-
if (typeof value !== 'string' || value === '') {
|
|
78
|
-
throw new ApiError(400, 'invalid_request', `consult body requires \`${field}\``);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (kind === 'visual') {
|
|
82
|
-
throw new ApiError(422, 'unprocessable', 'visual consults carry an immutable correlation binding not modeled by this route');
|
|
83
|
-
}
|
|
84
|
-
if (kind !== 'follow_up') {
|
|
85
|
-
throw new ApiError(400, 'invalid_request', `unknown consult kind: ${String(kind)}`);
|
|
86
|
-
}
|
|
87
|
-
if (getActiveFollowUpWorkByRequest(dir, request_id) !== null) {
|
|
88
|
-
return { status: 200, body: { request_id, created: false } };
|
|
89
|
-
}
|
|
90
|
-
armFollowUpWork({ nodeId: node_id, dir, root, requestId: request_id });
|
|
91
|
-
return { status: 200, body: { request_id, created: true } };
|
|
92
|
-
}
|
|
93
|
-
/** `GET /v1/canvas/consults` — active consult-outbox entries. With `?dir=<abs>`
|
|
94
|
-
* the list is scoped to that interaction dir (both follow-up and visual work);
|
|
95
|
-
* without it, every active follow-up receipt on the canvas (armed + pending). */
|
|
96
|
-
function handleList(ctx) {
|
|
97
|
-
const dir = ctx.query.get('dir');
|
|
98
|
-
const rows = dir !== null && dir !== ''
|
|
99
|
-
? [...listActiveFollowUpWorkByDir(dir), ...listActiveVisualWorkByDir(dir)]
|
|
100
|
-
: [...listArmedFollowUpWork(), ...listPendingFollowUpWork()];
|
|
101
|
-
return { status: 200, body: { entries: rows.map(toConsultOutboxEntryDTO) } };
|
|
102
|
-
}
|
|
103
|
-
// ===========================================================================
|
|
104
|
-
// POST /v1/human/bridge — create a kind:'human' bridge node (no broker)
|
|
105
|
-
// ===========================================================================
|
|
106
|
-
/** Create the terminal `kind:'human'` bridge node that later receives the
|
|
107
|
-
* human's answer via `pushFinal`. Uses `spawnNode` (a pure canvas row + meta,
|
|
108
|
-
* NO broker engine) — NOT `spawnChild`/`POST /nodes`, which always launches a
|
|
109
|
-
* broker the bridge must never have (spec §6.5's "spawnChild" is corrected to
|
|
110
|
-
* this dedicated no-broker endpoint). */
|
|
111
25
|
function handleCreateBridge(ctx) {
|
|
112
26
|
const req = ctx.body;
|
|
113
27
|
if (req === undefined || typeof req !== 'object') {
|
|
114
28
|
throw new ApiError(400, 'invalid_request', 'bridge body is required');
|
|
115
29
|
}
|
|
116
30
|
const { kind, cwd, name } = req;
|
|
117
|
-
if (kind !== 'human')
|
|
31
|
+
if (kind !== 'human')
|
|
118
32
|
throw new ApiError(400, 'invalid_request', 'bridge kind must be "human"');
|
|
119
|
-
|
|
120
|
-
if (typeof cwd !== 'string' || cwd === '') {
|
|
33
|
+
if (typeof cwd !== 'string' || cwd === '')
|
|
121
34
|
throw new ApiError(400, 'invalid_request', 'bridge body requires `cwd`');
|
|
122
|
-
|
|
123
|
-
if (typeof name !== 'string' || name === '') {
|
|
35
|
+
if (typeof name !== 'string' || name === '')
|
|
124
36
|
throw new ApiError(400, 'invalid_request', 'bridge body requires `name`');
|
|
125
|
-
}
|
|
126
37
|
const parent = req.parent ?? null;
|
|
127
38
|
if (parent !== null && (typeof parent !== 'string' || parent === '')) {
|
|
128
39
|
throw new ApiError(400, 'invalid_request', 'bridge `parent` must be a node id or null');
|
|
129
40
|
}
|
|
130
|
-
let node;
|
|
131
|
-
try {
|
|
132
|
-
node = spawnNode({ kind: 'human', parent, cwd, name, lifecycle: 'terminal' });
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
// The only expected failure is an unknown parent (spawnNode validates it
|
|
136
|
-
// before minting) — surface it as a 400, not an internal error.
|
|
137
|
-
throw new ApiError(400, 'invalid_request', String(e));
|
|
138
|
-
}
|
|
139
|
-
return { status: 200, body: { node_id: node.node_id, name: node.name } };
|
|
140
|
-
}
|
|
141
|
-
// ===========================================================================
|
|
142
|
-
// POST /v1/human/deliver — the registered humanloop completion handler
|
|
143
|
-
// ===========================================================================
|
|
144
|
-
/** Build the ResolutionEnvelope crouter delivers back for an answered deck,
|
|
145
|
-
* from the canonical deck result. */
|
|
146
|
-
function envelopeFromDeckResult(responses, summary, completedAt, respPath) {
|
|
147
|
-
return { summary, responsePath: respPath, schema: 'humanloop.response/v2', responses, completedAt };
|
|
148
|
-
}
|
|
149
|
-
/** The answer report a subscriber actually reads. The asking agent wants the
|
|
150
|
-
* human's words, not a serialized envelope: the deterministic per-question
|
|
151
|
-
* summary IS the answer, and the canonical response.json stays one path away
|
|
152
|
-
* for the rare case that option ids or timestamps matter. */
|
|
153
|
-
function renderDeckAnswer(env) {
|
|
154
|
-
const summary = env.summary.trim();
|
|
155
|
-
const body = summary === '' ? '(the human submitted no answer text)' : summary;
|
|
156
|
-
return `${body}\n\nResponse metadata (question ids, selected options, timestamp): ${env.responsePath}`;
|
|
157
|
-
}
|
|
158
|
-
/** Same contract for a review ticket: readable anchored comments, with the
|
|
159
|
-
* full feedback record left as a path rather than inlined JSON. */
|
|
160
|
-
function renderReviewAnswer(feedback, respPath) {
|
|
161
|
-
const n = feedback.comments.length;
|
|
162
|
-
const out = [];
|
|
163
|
-
if (n === 0) {
|
|
164
|
-
out.push(feedback.approved
|
|
165
|
-
? `Review approved — 0 comments on ${feedback.file} (human signalled looks-good; proceed).`
|
|
166
|
-
: `Review returned with 0 comments on ${feedback.file}.`);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
out.push(`Review — ${n} comment${n === 1 ? '' : 's'} on ${feedback.file}:`);
|
|
170
|
-
out.push('');
|
|
171
|
-
feedback.comments.forEach((c, i) => {
|
|
172
|
-
const range = c.endLine > c.line ? `L${c.line}-${c.endLine}` : `L${c.line}`;
|
|
173
|
-
const quoted = c.quote !== undefined && c.quote !== '' ? c.quote : c.lineText;
|
|
174
|
-
out.push(` ${i + 1}. ${range}`);
|
|
175
|
-
quoted.split('\n').forEach((ln, k) => out.push(k === 0 ? ` text: ${ln}` : ` ${ln}`));
|
|
176
|
-
c.comment.split('\n').forEach((ln, k) => out.push(k === 0 ? ` comment: ${ln}` : ` ${ln}`));
|
|
177
|
-
out.push('');
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
out.push(`Full feedback record (anchors, columns, timestamps): ${respPath}`);
|
|
181
|
-
return out.join('\n');
|
|
182
|
-
}
|
|
183
|
-
/** Follow-up retirement, invoked before the bridge is retired on ANY
|
|
184
|
-
* resolution/cancellation outcome (deck, review, canceled) —
|
|
185
|
-
* design-followup-consult-v2.md §B.5 "Bridge retirement" + §6 "Cancellation
|
|
186
|
-
* & supersede" (c). Ordering matters (M1): invalidate the current follow-up
|
|
187
|
-
* request under humanloop's `.followup-lock` FIRST (`cancelFollowUp`), so a
|
|
188
|
-
* fork that is mid-launch sees a non-running request on its post-launch
|
|
189
|
-
* recheck (`handleConsultStart`) and self-cancels, THEN cancel any
|
|
190
|
-
* request-bound fork still armed or pending for this ticket. Best-effort and
|
|
191
|
-
* idempotent: a follow-up consult must never block or fail the primary
|
|
192
|
-
* delivery it rides in on — reconcileFollowUpWork is the crash-safety net
|
|
193
|
-
* for anything this misses. */
|
|
194
|
-
async function retireVisualsForTicket(root, dir) {
|
|
195
|
-
try {
|
|
196
|
-
await cancelVisualRequestsForTicket(root, dir);
|
|
197
|
-
}
|
|
198
|
-
catch { /* primary delivery remains authoritative */ }
|
|
199
|
-
for (const row of listActiveVisualWorkByDir(dir)) {
|
|
200
|
-
try {
|
|
201
|
-
transition(row.node_id, 'cancel');
|
|
202
|
-
}
|
|
203
|
-
catch { /* already terminal */ }
|
|
204
|
-
try {
|
|
205
|
-
headlessBrokerHost.teardown(row.node_id);
|
|
206
|
-
}
|
|
207
|
-
catch { /* best-effort, mirrors close.ts */ }
|
|
208
|
-
retireVisualWork(dir, row.request_id);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function retireConsultsForTicket(root, dir) {
|
|
212
41
|
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
catch {
|
|
216
|
-
// No live followUpHandler registration, or no running request to
|
|
217
|
-
// invalidate — nothing to do.
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
for (const row of listActiveFollowUpWorkByDir(dir)) {
|
|
221
|
-
transition(row.node_id, 'cancel');
|
|
222
|
-
cancelFollowUpWork(row.node_id);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
// Best-effort cleanup; reconcileFollowUpWork is the crash-safety net.
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
/** `POST /v1/human/deliver` — turns a resolved/canceled ticket into the
|
|
230
|
-
* existing pushFinal delivery and bridge finalization. The body is a
|
|
231
|
-
* `humanloop.completion/v1` event (already JSON-parsed by the router).
|
|
232
|
-
* Idempotent: replay after a crash is safe. */
|
|
233
|
-
async function handleDeliver(ctx) {
|
|
234
|
-
const event = ctx.body;
|
|
235
|
-
if (event?.schema !== 'humanloop.completion/v1') {
|
|
236
|
-
throw new ApiError(400, 'invalid_request', 'not a humanloop.completion/v1 event');
|
|
237
|
-
}
|
|
238
|
-
// Canonicalize both paths through realpathSync before comparing. Humanloop's
|
|
239
|
-
// registry stores every root REALPATH-resolved, but its dispatchCompletion
|
|
240
|
-
// only `resolve()`s the dir into the event (not realpath) and crouter's own
|
|
241
|
-
// `human cancel` hands it a CRTR_HOME-derived dir that is likewise not
|
|
242
|
-
// realpath'd. So on any host whose interaction path has a symlinked component
|
|
243
|
-
// (macOS `/tmp`→`/private/tmp`, `/var`→`/private/var`), a plain `resolve()`
|
|
244
|
-
// comparison mismatches the registry's realpath and the containment/registration
|
|
245
|
-
// checks below spuriously reject a legitimate completion. Realpath'ing both
|
|
246
|
-
// sides makes the trust boundary robust to whichever caller produced the event.
|
|
247
|
-
const root = canonicalPath(event.root);
|
|
248
|
-
const dir = canonicalPath(event.dir);
|
|
249
|
-
// Containment: the interaction dir must be a direct child of the event root,
|
|
250
|
-
// and that root must be registered to crouter.
|
|
251
|
-
if (resolve(dir, '..') !== root) {
|
|
252
|
-
throw new ApiError(400, 'invalid_request', 'interaction dir is not a direct child of the event root');
|
|
253
|
-
}
|
|
254
|
-
const registered = listInboxRoots().some((r) => r.root === root && r.owner === 'crouter');
|
|
255
|
-
if (!registered) {
|
|
256
|
-
throw new ApiError(400, 'invalid_request', `root is not registered to crouter: ${root}`);
|
|
257
|
-
}
|
|
258
|
-
// Prove event.responsePath is THIS ticket's canonical response before
|
|
259
|
-
// trusting anything about it (Major-3): dispatchCompletion always sets it
|
|
260
|
-
// to `responsePath(dir)`, so a mismatch means a malformed/stale/forged
|
|
261
|
-
// event whose kind/payload cannot be trusted — reject it here, before any
|
|
262
|
-
// consult is invalidated and before the (unread) result could downgrade a
|
|
263
|
-
// live, valid consultation.
|
|
264
|
-
if (canonicalPath(event.responsePath) !== canonicalPath(responsePath(dir))) {
|
|
265
|
-
throw new ApiError(400, 'invalid_request', "responsePath is not this ticket's canonical response");
|
|
266
|
-
}
|
|
267
|
-
const jobId = basename(dir);
|
|
268
|
-
const rc = readJson(join(dir, 'run.json'));
|
|
269
|
-
// A notification has no bridge and no answer delivery.
|
|
270
|
-
if (rc?.mode === 'notify')
|
|
271
|
-
return { status: 200, body: { delivered: true } };
|
|
272
|
-
// Read + validate the canonical primary result BEFORE invalidating or
|
|
273
|
-
// canceling any live follow-up consult (Major-3): a malformed, stale, or
|
|
274
|
-
// transiently unreadable event must fail HERE and leave a live consult
|
|
275
|
-
// untouched, rather than permanently canceling valid consultation work
|
|
276
|
-
// ahead of a delivery that then turns out not to have a real result.
|
|
277
|
-
const result = readTicketResult(event.responsePath);
|
|
278
|
-
if (result === null) {
|
|
279
|
-
throw new ApiError(404, 'not_found', 'no canonical result to deliver');
|
|
42
|
+
const node = spawnNode({ kind: 'human', parent, cwd, name, lifecycle: 'terminal' });
|
|
43
|
+
return { status: 200, body: { node_id: node.node_id, name: node.name } };
|
|
280
44
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// crash-safety-net sweep for every OTHER ticket, and perform bridge
|
|
284
|
-
// retirement (below) — §B.5 ordering (M1): invalidate the request, then
|
|
285
|
-
// cancel the fork, THEN the general reconcile pass (Major 6): a pending
|
|
286
|
-
// consult crash-left for THIS exact ticket must never get a chance to
|
|
287
|
-
// publish while this ticket's request is still 'running' — retiring this
|
|
288
|
-
// ticket first (which cancels its own outbox rows) makes the reconcile
|
|
289
|
-
// that follows a true no-op for it, leaving reconcile to do its
|
|
290
|
-
// crash-safety-net job for every OTHER ticket only.
|
|
291
|
-
await retireVisualsForTicket(root, dir);
|
|
292
|
-
retireConsultsForTicket(root, dir);
|
|
293
|
-
reconcileFollowUpWork();
|
|
294
|
-
reconcileVisualWork();
|
|
295
|
-
const node = getNode(jobId);
|
|
296
|
-
// Replay-safe: an already-terminal (or reaped) bridge needs no further work.
|
|
297
|
-
if (node === null || node.status === 'dead' || node.status === 'canceled' || (node.status === 'done' && node.final_report != null)) {
|
|
298
|
-
return { status: 200, body: { delivered: true } };
|
|
299
|
-
}
|
|
300
|
-
if (result.kind === 'canceled') {
|
|
301
|
-
transition(jobId, 'finish');
|
|
302
|
-
const note = result.reason !== undefined && result.reason !== '' ? ` — ${result.reason}` : '';
|
|
303
|
-
const actor = result.actor;
|
|
304
|
-
for (const sub of subscribersOf(jobId)) {
|
|
305
|
-
if (actor !== undefined && sub.node_id === actor)
|
|
306
|
-
continue;
|
|
307
|
-
appendInbox(sub.node_id, {
|
|
308
|
-
from: jobId,
|
|
309
|
-
tier: 'deferred',
|
|
310
|
-
kind: 'message',
|
|
311
|
-
label: `human interaction ${jobId} canceled — no answer is coming${note}`,
|
|
312
|
-
data: { body: `The human interaction ${jobId} was canceled${note}. No response will arrive.` },
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
return { status: 200, body: { delivered: true } };
|
|
316
|
-
}
|
|
317
|
-
if (result.kind === 'review') {
|
|
318
|
-
const feedback = result.result;
|
|
319
|
-
await pushFinal(jobId, renderReviewAnswer(feedback, event.responsePath));
|
|
320
|
-
return { status: 200, body: { delivered: true } };
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new ApiError(400, 'invalid_request', String(error));
|
|
321
47
|
}
|
|
322
|
-
// deck
|
|
323
|
-
const env = envelopeFromDeckResult(result.responses, result.summary, result.completedAt, event.responsePath);
|
|
324
|
-
await pushFinal(jobId, renderDeckAnswer(env));
|
|
325
|
-
return { status: 200, body: { delivered: true } };
|
|
326
48
|
}
|
|
327
49
|
// ===========================================================================
|
|
328
|
-
// POST /v1/human/
|
|
329
|
-
//
|
|
330
|
-
// design-followup-consult-v2.md §B.5 — the registered `followUpHandler` for
|
|
331
|
-
// the crouter root (§B.7). Verifies capability + a live running follow-up
|
|
332
|
-
// request (M3), then persists the receipted outbox + lifecycle-owned private
|
|
333
|
-
// fork claim and asks reviveNode to birth it (action:'start'), or cancels the
|
|
334
|
-
// request-bound child (action:'cancel'). Every branch resolves without
|
|
335
|
-
// throwing — `handled` communicates whether an action was actually taken;
|
|
336
|
-
// humanloop's handler contract is a fast kickoff ack, never a blocking wait on
|
|
337
|
-
// the answer (a rejection here just means a cancel/supersede already raced
|
|
338
|
-
// ahead of this event, which is expected, not exceptional).
|
|
50
|
+
// POST /v1/human/tickets/:node_id/resolve and /cancel
|
|
339
51
|
// ===========================================================================
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
* explicit capability constant, `owner==='crouter'`, the dir is a direct
|
|
372
|
-
* child of the registered root, that registration's `followUpHandler`
|
|
373
|
-
* identity matches ours, and the ticket is an `ask` run — the only kind with
|
|
374
|
-
* a branch worth consulting, and the trust anchor a cancel needs just as
|
|
375
|
-
* much as a start. Returns the canonicalized root/dir plus the parsed
|
|
376
|
-
* run.json once every check passes; returns null (decline, never act) on the
|
|
377
|
-
* first failure. */
|
|
378
|
-
function verifyConsultTrust(event) {
|
|
379
|
-
if (event.capability !== FOLLOW_UP_CAPABILITY || event.owner !== 'crouter')
|
|
380
|
-
return null;
|
|
381
|
-
const root = canonicalPath(event.root);
|
|
382
|
-
const dir = canonicalPath(event.dir);
|
|
383
|
-
if (resolve(dir, '..') !== root)
|
|
384
|
-
return null;
|
|
385
|
-
const registration = listInboxRoots().find((r) => r.root === root && r.owner === 'crouter');
|
|
386
|
-
if (registration === undefined || !followUpHandlerMatches(registration.followUpHandler))
|
|
387
|
-
return null;
|
|
388
|
-
const rc = readJson(join(dir, 'run.json'));
|
|
389
|
-
if (rc?.mode !== 'ask')
|
|
390
|
-
return null;
|
|
391
|
-
return { root, dir, rc };
|
|
392
|
-
}
|
|
393
|
-
/** True when `path` exists and this process can read it. Used to settle
|
|
394
|
-
* (not silently decline) a consult whose captured branch is missing OR
|
|
395
|
-
* present-but-unreadable (Major 1) — both are the same terminal outcome for
|
|
396
|
-
* the human: no conversation context to consult against. */
|
|
397
|
-
function isReadableFile(path) {
|
|
52
|
+
function reviewSubmitGuidance() {
|
|
53
|
+
return new ApiError(400, 'invalid_request', 'review approvals use POST /v1/human/reviews/:review_id/submit');
|
|
54
|
+
}
|
|
55
|
+
/** The ticket resolve route accepts only a deck response body. */
|
|
56
|
+
function requireResolveRequest(ctx) {
|
|
57
|
+
const request = ctx.body;
|
|
58
|
+
if (request === undefined || request === null || typeof request !== 'object' || Array.isArray(request)) {
|
|
59
|
+
throw new ApiError(400, 'invalid_request', 'ticket resolve body is required');
|
|
60
|
+
}
|
|
61
|
+
const body = request;
|
|
62
|
+
const fields = Object.keys(body);
|
|
63
|
+
if (fields.length === 1 && fields[0] === 'responses')
|
|
64
|
+
return { responses: body['responses'] };
|
|
65
|
+
if ('feedback' in body)
|
|
66
|
+
throw reviewSubmitGuidance();
|
|
67
|
+
throw new ApiError(400, 'invalid_request', 'ticket resolve body requires exactly `responses`');
|
|
68
|
+
}
|
|
69
|
+
function requireDeckTicket(dir) {
|
|
70
|
+
const deck = existsSync(deckPath(dir));
|
|
71
|
+
const review = existsSync(reviewPath(dir));
|
|
72
|
+
if (review && !deck)
|
|
73
|
+
throw reviewSubmitGuidance();
|
|
74
|
+
if (!deck || review) {
|
|
75
|
+
throw new ApiError(400, 'invalid_request', 'ticket has no unambiguous deck kind');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function invalidResolveRequest(error) {
|
|
79
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
80
|
+
return new ApiError(400, 'invalid_request', `invalid ticket resolve body: ${detail}`);
|
|
81
|
+
}
|
|
82
|
+
function preflightResolve(validate) {
|
|
398
83
|
try {
|
|
399
|
-
|
|
400
|
-
return true;
|
|
401
|
-
}
|
|
402
|
-
catch {
|
|
403
|
-
return false;
|
|
84
|
+
return validate();
|
|
404
85
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
* request, THEN arm the receipted outbox and launch, then a post-launch
|
|
408
|
-
* currency recheck (M1). Once BOTH trust and request currency are
|
|
409
|
-
* established, every further failure (missing/unreadable branch, no
|
|
410
|
-
* captured model, a launch fault) settles the request with a published
|
|
411
|
-
* error result rather than silently declining — nothing else will ever
|
|
412
|
-
* publish an answer for a ticket stuck on "consulting…" (Major 1). */
|
|
413
|
-
function handleConsultStart(event) {
|
|
414
|
-
const trust = verifyConsultTrust(event);
|
|
415
|
-
if (trust === null)
|
|
416
|
-
return { handled: false };
|
|
417
|
-
const { root, dir, rc } = trust;
|
|
418
|
-
const { request } = readFollowUp(dir);
|
|
419
|
-
if (request === null || request.state !== 'running' || request.requestId !== event.requestId) {
|
|
420
|
-
// A cancel may have already raced in, or this event is already stale
|
|
421
|
-
// (superseded by a newer '?'). Nothing to do — no error is owed here,
|
|
422
|
-
// since this exact request is no longer the one anything is waiting on.
|
|
423
|
-
return { handled: false };
|
|
424
|
-
}
|
|
425
|
-
const jobId = basename(dir);
|
|
426
|
-
const node = getNode(jobId);
|
|
427
|
-
if (node === null) {
|
|
428
|
-
// Trust + request currency are both established, but the bridge node
|
|
429
|
-
// itself is gone (pruned/reaped) — there is no cwd/profile context left
|
|
430
|
-
// to fork from. Settle with a published error rather than silently
|
|
431
|
-
// declining (Major-4): a bare `handled:false` here left Lane A publishing
|
|
432
|
-
// nothing and the UI stuck on "consulting…" forever, matching the
|
|
433
|
-
// missing-branch/missing-model error paths below.
|
|
434
|
-
submitFollowUpResult(root, dir, {
|
|
435
|
-
requestId: event.requestId,
|
|
436
|
-
status: 'error',
|
|
437
|
-
error: 'the asking conversation no longer exists; follow-up unavailable',
|
|
438
|
-
});
|
|
439
|
-
return { handled: true };
|
|
440
|
-
}
|
|
441
|
-
// Full trust + current-request validation are both established from here.
|
|
442
|
-
const provenance = rc.conversation_fork;
|
|
443
|
-
if (provenance?.status !== 'ready') {
|
|
444
|
-
submitFollowUpResult(root, dir, {
|
|
445
|
-
requestId: event.requestId,
|
|
446
|
-
status: 'error',
|
|
447
|
-
error: 'no captured conversation branch for this ask; follow-up unavailable',
|
|
448
|
-
});
|
|
449
|
-
return { handled: true };
|
|
450
|
-
}
|
|
451
|
-
const branchFile = join(dir, provenance.branch_point);
|
|
452
|
-
if (!isReadableFile(branchFile)) {
|
|
453
|
-
submitFollowUpResult(root, dir, {
|
|
454
|
-
requestId: event.requestId,
|
|
455
|
-
status: 'error',
|
|
456
|
-
error: 'captured conversation branch is unavailable; follow-up unavailable',
|
|
457
|
-
});
|
|
458
|
-
return { handled: true };
|
|
459
|
-
}
|
|
460
|
-
const exactModel = provenance.model_spec;
|
|
461
|
-
const originProfileId = getNode(provenance.source_node_id)?.profile_id ?? null;
|
|
462
|
-
const forkId = newNodeId();
|
|
463
|
-
try {
|
|
464
|
-
// Persist the delivery receipt and every immutable birth fact before the
|
|
465
|
-
// lifecycle is allowed to create an engine. The node is parent-null (no
|
|
466
|
-
// subscription/viewer), exact-model locked, and explicitly fork_pending;
|
|
467
|
-
// reviveNode is the only operation below that can turn the claim into a
|
|
468
|
-
// broker process.
|
|
469
|
-
armFollowUpWork({ nodeId: forkId, dir, root, requestId: event.requestId });
|
|
470
|
-
const { launch } = buildLaunchSpec('general', 'base', {
|
|
471
|
-
lifecycle: 'terminal',
|
|
472
|
-
hasManager: false,
|
|
473
|
-
model: exactModel,
|
|
474
|
-
modelExact: true,
|
|
475
|
-
cwd: node.cwd,
|
|
476
|
-
profileId: originProfileId,
|
|
477
|
-
});
|
|
478
|
-
const kickoff = `${buildIdentityAssertionFor({ node_id: forkId, kind: 'general', mode: 'base', fork_from: branchFile })}\n\n` +
|
|
479
|
-
`${PRIVATE_FOLLOW_UP_FRAMING}\n\n${event.question}`;
|
|
480
|
-
spawnNode({
|
|
481
|
-
nodeId: forkId,
|
|
482
|
-
kind: 'general',
|
|
483
|
-
mode: 'base',
|
|
484
|
-
lifecycle: 'terminal',
|
|
485
|
-
cwd: node.cwd,
|
|
486
|
-
parent: null,
|
|
487
|
-
spawnedBy: node.node_id,
|
|
488
|
-
forkFrom: branchFile,
|
|
489
|
-
modelOverride: exactModel,
|
|
490
|
-
humanWorkTarget: { work_kind: 'follow_up', dir, request_id: event.requestId, root },
|
|
491
|
-
privateForkPhase: 'fork_pending',
|
|
492
|
-
privateForkRecipe: { kickoff },
|
|
493
|
-
profile_id: originProfileId,
|
|
494
|
-
launch,
|
|
495
|
-
});
|
|
496
|
-
reviveNode(forkId, { resume: false });
|
|
497
|
-
}
|
|
498
|
-
catch (e) {
|
|
499
|
-
// A synchronous claim/birth failure leaves any armed receipt in place so
|
|
500
|
-
// publication remains retryable. Publish the user-visible error first and
|
|
501
|
-
// mark the receipt terminal only after Humanloop confirms that publish.
|
|
502
|
-
submitFollowUpResult(root, dir, {
|
|
503
|
-
requestId: event.requestId,
|
|
504
|
-
status: 'error',
|
|
505
|
-
error: `consultation failed to launch: ${String(e)}`,
|
|
506
|
-
});
|
|
507
|
-
const armedForkId = getActiveFollowUpWorkByRequest(dir, event.requestId)?.node_id ?? null;
|
|
508
|
-
if (armedForkId !== null)
|
|
509
|
-
errorFollowUpWork(armedForkId);
|
|
510
|
-
return { handled: true };
|
|
511
|
-
}
|
|
512
|
-
// Post-launch currency recheck (M1): a supersede/cancel that raced the
|
|
513
|
-
// launch means this fork is already wasted work — cancel it early. The
|
|
514
|
-
// authoritative safety net is submitFollowUpResult's own conditional
|
|
515
|
-
// publish (A.4); this just avoids burning a turn on a fork nobody will read.
|
|
516
|
-
const recheck = readFollowUp(dir);
|
|
517
|
-
if (recheck.request === null || recheck.request.requestId !== event.requestId || recheck.request.state !== 'running') {
|
|
518
|
-
transition(forkId, 'cancel');
|
|
519
|
-
cancelFollowUpWork(forkId);
|
|
520
|
-
}
|
|
521
|
-
return { handled: true };
|
|
522
|
-
}
|
|
523
|
-
/** action:'cancel' — the SAME full trust binding as start (Major 5), then
|
|
524
|
-
* declines ONLY the narrow untrusted case: this exact requestId still
|
|
525
|
-
* `running` at `dir` (humanloop's `cancelFollowUp` always settles it out of
|
|
526
|
-
* `running` BEFORE invoking `action:'cancel'`, design §A.4, so a legitimate
|
|
527
|
-
* event never observes that). It does NOT require `dir`'s CURRENT request to
|
|
528
|
-
* still equal this requestId (Minor-5) — a delayed cancel legitimately
|
|
529
|
-
* arrives after a newer `?` has already superseded it, and `findConsultForkId`
|
|
530
|
-
* keys on `(dir, requestId)` directly, so it can only ever find/cancel the
|
|
531
|
-
* fork bound to THIS requestId, never a newer one's. `transition(...,
|
|
532
|
-
* 'cancel')` refuses to overwrite a committed final, so a fork that already
|
|
533
|
-
* delivered is left untouched. */
|
|
534
|
-
function handleConsultCancel(event) {
|
|
535
|
-
const trust = verifyConsultTrust(event);
|
|
536
|
-
if (trust === null)
|
|
537
|
-
return { handled: false };
|
|
538
|
-
const { dir } = trust;
|
|
539
|
-
// Trust binding only (Minor-5): humanloop's cancelFollowUp settles the
|
|
540
|
-
// CURRENT request out of 'running' under `.followup-lock` BEFORE ever
|
|
541
|
-
// invoking action:'cancel' (design §A.4), so a legitimate cancel event's
|
|
542
|
-
// OWN requestId is never still the live running one — decline only that
|
|
543
|
-
// exact case (a stray/forged cancel racing ahead of the lock). Do NOT also
|
|
544
|
-
// require dir's CURRENT request to still equal this event's requestId: a
|
|
545
|
-
// delayed cancel legitimately arrives after a newer '?' has already
|
|
546
|
-
// superseded it, and `findConsultForkId` below keys on `(dir, requestId)`
|
|
547
|
-
// directly, so it can only ever find/cancel the fork actually bound to
|
|
548
|
-
// THIS requestId — never a newer one's.
|
|
549
|
-
const { request } = readFollowUp(dir);
|
|
550
|
-
if (request !== null && request.requestId === event.requestId && request.state === 'running') {
|
|
551
|
-
return { handled: false };
|
|
552
|
-
}
|
|
553
|
-
const forkId = findConsultForkId(dir, event.requestId);
|
|
554
|
-
if (forkId === null)
|
|
555
|
-
return { handled: false };
|
|
556
|
-
transition(forkId, 'cancel');
|
|
557
|
-
cancelFollowUpWork(forkId);
|
|
558
|
-
return { handled: true };
|
|
559
|
-
}
|
|
560
|
-
/** `POST /v1/human/consult` — the follow-up handler body. The event is a
|
|
561
|
-
* `humanloop.followup-request/v1` (already JSON-parsed by the router). */
|
|
562
|
-
function handleConsult(ctx) {
|
|
563
|
-
const event = ctx.body;
|
|
564
|
-
if (event?.schema !== 'humanloop.followup-request/v1') {
|
|
565
|
-
throw new ApiError(400, 'invalid_request', 'not a humanloop.followup-request/v1 event');
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw invalidResolveRequest(error);
|
|
566
88
|
}
|
|
567
|
-
// Crash-safety net (§B.4): reconcile on every touchpoint, never a timer.
|
|
568
|
-
reconcileFollowUpWork();
|
|
569
|
-
// Explicit action union (Major 5) — anything other than the literal
|
|
570
|
-
// 'start'/'cancel' is declined, never silently treated as a start.
|
|
571
|
-
if (event.action === 'start')
|
|
572
|
-
return { status: 200, body: handleConsultStart(event) };
|
|
573
|
-
if (event.action === 'cancel')
|
|
574
|
-
return { status: 200, body: handleConsultCancel(event) };
|
|
575
|
-
return { status: 200, body: { handled: false } };
|
|
576
|
-
}
|
|
577
|
-
// ===========================================================================
|
|
578
|
-
// POST /v1/human/visual — the registered visual handler
|
|
579
|
-
// ===========================================================================
|
|
580
|
-
const VISUAL_CAPABILITY = 'humanloop.visual/v1';
|
|
581
|
-
const PRIVATE_VISUAL_FRAMING = 'Create concise, self-contained Markdown visual context for the supplied interaction. Use inherited history only for facts. Return exactly one Markdown result with `crtr push final`; it returns only to Humanloop Visual.';
|
|
582
|
-
function sameHandler(left, right) {
|
|
583
|
-
return left !== undefined && JSON.stringify(left) === JSON.stringify(right);
|
|
584
|
-
}
|
|
585
|
-
/** A broker retains its sealed runtime generation across installs. Humanloop
|
|
586
|
-
* freezes that generation's `_visual` argv into the request, while crtrd may
|
|
587
|
-
* already run a newer sibling generation when the human opens the ticket.
|
|
588
|
-
* Accept only a Node invocation of the same leaf from an immutable 64-hex
|
|
589
|
-
* generation beneath the current handler's exact generations directory. The
|
|
590
|
-
* command differs legitimately: CLI registrations use the system Node path,
|
|
591
|
-
* while the daemon runs through crouter's stable copied-Node host. */
|
|
592
|
-
export function trustedCrouterHandler(candidate, current, leaf) {
|
|
593
|
-
if (sameHandler(candidate, current))
|
|
594
|
-
return true;
|
|
595
|
-
if (candidate === undefined)
|
|
596
|
-
return false;
|
|
597
|
-
if (basename(candidate.command) !== 'node' && basename(candidate.command) !== basename(current.command))
|
|
598
|
-
return false;
|
|
599
|
-
if (candidate.args.length !== 3 || current.args.length !== 3)
|
|
600
|
-
return false;
|
|
601
|
-
if (candidate.args[1] !== 'human' || candidate.args[2] !== leaf)
|
|
602
|
-
return false;
|
|
603
|
-
if (current.args[1] !== 'human' || current.args[2] !== leaf)
|
|
604
|
-
return false;
|
|
605
|
-
const currentEntry = resolve(current.args[0]);
|
|
606
|
-
const candidateEntry = resolve(candidate.args[0]);
|
|
607
|
-
const generationsDir = resolve(currentEntry, '..', '..', '..');
|
|
608
|
-
const currentGeneration = basename(resolve(currentEntry, '..', '..'));
|
|
609
|
-
const candidateGeneration = basename(resolve(candidateEntry, '..', '..'));
|
|
610
|
-
const generationId = /^[a-f0-9]{64}$/;
|
|
611
|
-
if (!generationId.test(currentGeneration) || !generationId.test(candidateGeneration))
|
|
612
|
-
return false;
|
|
613
|
-
return candidateEntry === join(generationsDir, candidateGeneration, 'dist', 'cli.js');
|
|
614
|
-
}
|
|
615
|
-
export function trustedCrouterVisualHandler(candidate, current = crouterVisualHandler()) {
|
|
616
|
-
return trustedCrouterHandler(candidate, current, '_visual');
|
|
617
89
|
}
|
|
618
|
-
function
|
|
619
|
-
|
|
90
|
+
async function handleResolve(ctx) {
|
|
91
|
+
const nodeId = requireTicketNodeId(ctx);
|
|
92
|
+
const request = requireResolveRequest(ctx);
|
|
93
|
+
const dir = ticketDir(nodeId);
|
|
94
|
+
requireDeckTicket(dir);
|
|
95
|
+
const responses = preflightResolve(() => validateDeckTicketResponses(dir, request.responses));
|
|
96
|
+
await resolveDeckTicket(nodeId, responses);
|
|
97
|
+
return { status: 200, body: { delivered: true, kind: 'deck' } };
|
|
620
98
|
}
|
|
621
|
-
function
|
|
622
|
-
|
|
623
|
-
return null;
|
|
624
|
-
const root = canonicalPath(event.root), dir = canonicalPath(event.dir);
|
|
625
|
-
if (resolve(dir, '..') !== root || event.ticketId !== basename(dir))
|
|
626
|
-
return null;
|
|
627
|
-
if (!listInboxRoots().some((entry) => entry.root === root && entry.owner === 'crouter'))
|
|
628
|
-
return null;
|
|
629
|
-
const request = readVisualRequestForEvent(event);
|
|
630
|
-
if (request === null || !trustedCrouterVisualHandler(request.handler))
|
|
631
|
-
return null;
|
|
632
|
-
const rc = readJson(join(dir, 'run.json'));
|
|
633
|
-
return rc?.mode === 'ask' ? { root, dir, rc, request } : null;
|
|
99
|
+
function hasPublishedTicket(dir) {
|
|
100
|
+
return existsSync(deckPath(dir)) || existsSync(reviewPath(dir)) || readTicketResult(dir) !== null;
|
|
634
101
|
}
|
|
635
|
-
function
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
catch {
|
|
642
|
-
return false;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
function handleVisualStart(event) {
|
|
646
|
-
const trust = trustedVisual(event);
|
|
647
|
-
if (trust === null)
|
|
648
|
-
return { handled: false };
|
|
649
|
-
const { root, dir, rc, request } = trust;
|
|
650
|
-
if (!currentVisualInteraction(dir, request) || isResolved(dir))
|
|
651
|
-
return { handled: false };
|
|
652
|
-
const provenance = rc.conversation_fork;
|
|
653
|
-
if (provenance?.status !== 'ready') {
|
|
654
|
-
submitVisualError(root, dir, request, 'Visual context is unavailable for this ask');
|
|
655
|
-
return { handled: true };
|
|
656
|
-
}
|
|
657
|
-
const branchFile = join(dir, provenance.branch_point);
|
|
658
|
-
if (!isReadableFile(branchFile)) {
|
|
659
|
-
submitVisualError(root, dir, request, 'Captured Visual context is unavailable');
|
|
660
|
-
return { handled: true };
|
|
661
|
-
}
|
|
662
|
-
const bridge = getNode(basename(dir));
|
|
663
|
-
if (bridge === null) {
|
|
664
|
-
submitVisualError(root, dir, request, 'The asking conversation no longer exists; Visual is unavailable');
|
|
665
|
-
return { handled: true };
|
|
102
|
+
async function handleCancel(ctx) {
|
|
103
|
+
const nodeId = requireTicketNodeId(ctx);
|
|
104
|
+
const request = ctx.body;
|
|
105
|
+
if (request !== undefined && (typeof request !== 'object' || request === null)) {
|
|
106
|
+
throw new ApiError(400, 'invalid_request', 'ticket cancel body must be an object');
|
|
666
107
|
}
|
|
667
|
-
const
|
|
108
|
+
const dir = ticketDir(nodeId);
|
|
109
|
+
const review = getReviewByBridge(nodeId);
|
|
668
110
|
try {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
return { handled: true };
|
|
111
|
+
if (review !== null) {
|
|
112
|
+
if (review.state === 'approved') {
|
|
113
|
+
throw new ApiError(409, 'ticket_already_resolved', 'ticket already resolved');
|
|
114
|
+
}
|
|
115
|
+
const finished = await cancelReview(review.review_id, request ?? {});
|
|
116
|
+
if (finished.outcome === 'already_settled') {
|
|
117
|
+
throw new ApiError(409, 'ticket_already_resolved', 'ticket already resolved');
|
|
118
|
+
}
|
|
119
|
+
return { status: 200, body: { canceled: true } };
|
|
679
120
|
}
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
121
|
+
// A binding row has no published ticket. It is intentionally
|
|
122
|
+
// indistinguishable from an unknown node id on this ticket surface.
|
|
123
|
+
if (!hasPublishedTicket(dir)) {
|
|
124
|
+
throw new ApiError(404, 'ticket_not_found', 'no matching ticket');
|
|
125
|
+
}
|
|
126
|
+
await cancelHumanTicket(nodeId, request ?? {});
|
|
127
|
+
return { status: 200, body: { canceled: true } };
|
|
687
128
|
}
|
|
688
129
|
catch (error) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
function handleVisualCancel(event) {
|
|
697
|
-
const trust = trustedVisual(event);
|
|
698
|
-
if (trust === null)
|
|
699
|
-
return { handled: false };
|
|
700
|
-
const { dir, request } = trust;
|
|
701
|
-
const row = getVisualWorkByRequest(dir, request.requestId);
|
|
702
|
-
if (row === null)
|
|
703
|
-
return { handled: true };
|
|
704
|
-
const exact = row.root === request.root && row.generation_id === request.generationId && row.interaction_id === request.interactionId && row.interaction_snapshot === canonicalInteractionJson(request.interaction) && row.handler_command === request.handler.command && row.handler_args_json === JSON.stringify(request.handler.args) && row.claim_token === request.claim.token;
|
|
705
|
-
if (!exact)
|
|
706
|
-
return { handled: false };
|
|
707
|
-
// Retire before canceling the deliberately inert fork so reconciliation
|
|
708
|
-
// cannot classify this exact cancellation as a pre-launch claim.
|
|
709
|
-
retireVisualWork(dir, request.requestId);
|
|
710
|
-
try {
|
|
711
|
-
transition(row.node_id, 'cancel');
|
|
712
|
-
}
|
|
713
|
-
catch { /* absent or terminal is an acknowledgement */ }
|
|
714
|
-
try {
|
|
715
|
-
headlessBrokerHost.teardown(row.node_id);
|
|
716
|
-
}
|
|
717
|
-
catch { /* best-effort, mirrors close.ts */ }
|
|
718
|
-
return { handled: true };
|
|
719
|
-
}
|
|
720
|
-
/** `POST /v1/human/visual` — validates and launches or retires one exact
|
|
721
|
-
* Humanloop Visual fork. The event is a `humanloop.visual-request-event/v1`
|
|
722
|
-
* (already JSON-parsed by the router). */
|
|
723
|
-
function handleVisual(ctx) {
|
|
724
|
-
let event;
|
|
725
|
-
try {
|
|
726
|
-
event = parseVisualRequestEvent(ctx.body);
|
|
727
|
-
}
|
|
728
|
-
catch {
|
|
729
|
-
throw new ApiError(400, 'invalid_request', 'not a humanloop.visual-request-event/v1 event');
|
|
130
|
+
if (error instanceof ApiError)
|
|
131
|
+
throw error;
|
|
132
|
+
// Review errors are not part of the retained ticket route's vocabulary.
|
|
133
|
+
if (error instanceof ReviewOperationError) {
|
|
134
|
+
throw new ApiError(409, 'ticket_already_resolved', 'ticket already resolved');
|
|
135
|
+
}
|
|
136
|
+
throw new ApiError(500, 'internal', 'the ticket could not be canceled');
|
|
730
137
|
}
|
|
731
|
-
reconcileVisualWork();
|
|
732
|
-
return { status: 200, body: event.action === 'start' ? handleVisualStart(event) : handleVisualCancel(event) };
|
|
733
138
|
}
|
|
734
139
|
export const humanRoutes = [
|
|
735
|
-
{ method: 'POST', pattern: '/v1/canvas/consults', handler: handleCreate },
|
|
736
|
-
{ method: 'GET', pattern: '/v1/canvas/consults', handler: handleList },
|
|
737
140
|
{ method: 'POST', pattern: '/v1/human/bridge', handler: handleCreateBridge },
|
|
738
|
-
{ method: 'POST', pattern: '/v1/human/
|
|
739
|
-
{ method: 'POST', pattern: '/v1/human/
|
|
740
|
-
{ method: 'POST', pattern: '/v1/human/visual', handler: handleVisual },
|
|
141
|
+
{ method: 'POST', pattern: '/v1/human/tickets/:node_id/resolve', handler: handleResolve },
|
|
142
|
+
{ method: 'POST', pattern: '/v1/human/tickets/:node_id/cancel', handler: handleCancel },
|
|
741
143
|
];
|