@crouton-kit/crouter 0.3.31 → 0.3.33
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/README.md +8 -0
- package/dist/build-root.js +1 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
- package/dist/builtin-memory/crouter-development/personas.md +3 -4
- package/dist/builtin-memory/crouter-development/plugins.md +18 -18
- package/dist/builtin-memory/design.md +4 -1
- package/dist/builtin-memory/development.md +4 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +6 -6
- package/dist/builtin-memory/internal/storage-tiers.md +5 -5
- package/dist/builtin-memory/planning.md +4 -1
- package/dist/builtin-memory/product.md +80 -0
- package/dist/builtin-memory/spec.md +4 -1
- package/dist/builtin-personas/advisor/PERSONA.md +10 -0
- package/dist/builtin-personas/design/PERSONA.md +2 -2
- package/dist/builtin-personas/design/orchestrator.md +3 -3
- package/dist/builtin-personas/developer/PERSONA.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +2 -2
- package/dist/builtin-personas/explore/PERSONA.md +3 -3
- package/dist/builtin-personas/explore/orchestrator.md +4 -2
- package/dist/builtin-personas/general/PERSONA.md +1 -1
- package/dist/builtin-personas/general/orchestrator.md +1 -1
- package/dist/builtin-personas/orchestration-kernel.md +7 -14
- package/dist/builtin-personas/plan/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/orchestrator.md +2 -2
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
- package/dist/builtin-personas/product/PERSONA.md +18 -0
- package/dist/builtin-personas/product/orchestrator.md +14 -0
- package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -1
- package/dist/builtin-personas/review/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +5 -0
- package/dist/builtin-personas/spec/PERSONA.md +1 -1
- package/dist/builtin-personas/spec/orchestrator.md +2 -2
- package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
- package/dist/builtin-views/canvas/core.mjs +82 -1
- package/dist/builtin-views/canvas/tui.mjs +9 -5
- package/dist/builtin-views/canvas/web.jsx +3 -2
- package/dist/builtin-views/chat/core.mjs +725 -0
- package/dist/builtin-views/chat/text.mjs +101 -0
- package/dist/builtin-views/chat/tui.mjs +368 -0
- package/dist/builtin-views/chat/web.jsx +367 -0
- package/dist/builtin-views/prompt-review/core.mjs +863 -0
- package/dist/builtin-views/prompt-review/text.mjs +15 -0
- package/dist/builtin-views/prompt-review/tui.mjs +196 -0
- package/dist/builtin-views/prompt-review/web.jsx +484 -0
- package/dist/builtin-views/settings/core.mjs +397 -0
- package/dist/builtin-views/settings/text.mjs +40 -0
- package/dist/builtin-views/settings/tui.mjs +95 -0
- package/dist/builtin-views/settings/web.jsx +167 -0
- package/dist/cli.js +16 -3
- package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +96 -0
- package/dist/clients/attach/__tests__/bash-bang-routing.test.js +58 -0
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +56 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +22 -1
- package/dist/clients/attach/attach-cmd.d.ts +18 -0
- package/dist/clients/attach/attach-cmd.js +1723 -795
- package/dist/clients/attach/canvas-panels.js +2 -2
- package/dist/clients/attach/chat-view.d.ts +10 -0
- package/dist/clients/attach/chat-view.js +23 -0
- package/dist/clients/attach/config-load.d.ts +8 -4
- package/dist/clients/attach/config-load.js +2 -0
- package/dist/clients/attach/extension-dialogs.d.ts +2 -3
- package/dist/clients/attach/extension-dialogs.js +7 -9
- package/dist/clients/attach/graph-overlay.js +3 -2
- package/dist/clients/attach/input-controller.d.ts +4 -3
- package/dist/clients/attach/input-controller.js +24 -3
- package/dist/clients/attach/slash-commands.d.ts +7 -7
- package/dist/clients/attach/slash-commands.js +38 -14
- package/dist/clients/attach/titled-editor.js +18 -14
- package/dist/clients/attach/view-socket.d.ts +2 -1
- package/dist/clients/attach/view-socket.js +27 -8
- package/dist/clients/web/__tests__/source-cache.test.d.ts +1 -0
- package/dist/clients/web/__tests__/source-cache.test.js +32 -0
- package/dist/clients/web/dev-server.js +1 -0
- package/dist/clients/web/events.js +9 -11
- package/dist/clients/web/server.d.ts +4 -3
- package/dist/clients/web/server.js +138 -31
- package/dist/clients/web/source-cache.d.ts +10 -0
- package/dist/clients/web/source-cache.js +57 -0
- package/dist/clients/web/web-cmd.js +28 -9
- package/dist/commands/__tests__/human.test.js +30 -28
- package/dist/commands/canvas-history/search.js +1 -1
- package/dist/commands/canvas-history.js +5 -8
- package/dist/commands/canvas-issue.d.ts +2 -0
- package/dist/commands/canvas-issue.js +148 -0
- package/dist/commands/canvas-prune.js +19 -0
- package/dist/commands/canvas-rebuild-index.d.ts +2 -0
- package/dist/commands/canvas-rebuild-index.js +57 -0
- package/dist/commands/canvas-snapshot.d.ts +2 -0
- package/dist/commands/canvas-snapshot.js +48 -0
- package/dist/commands/canvas-tmux-spread.d.ts +2 -0
- package/dist/commands/canvas-tmux-spread.js +188 -0
- package/dist/commands/canvas.d.ts +1 -0
- package/dist/commands/canvas.js +16 -1
- package/dist/commands/chord.js +143 -48
- package/dist/commands/daemon.js +3 -3
- package/dist/commands/human/prompts.d.ts +0 -1
- package/dist/commands/human/prompts.js +39 -54
- package/dist/commands/human/queue.d.ts +12 -1
- package/dist/commands/human/queue.js +468 -73
- package/dist/commands/human/shared.d.ts +3 -4
- package/dist/commands/human/shared.js +3 -3
- package/dist/commands/human.js +11 -10
- package/dist/commands/memory/find.js +3 -2
- package/dist/commands/memory/list.js +4 -3
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +1 -4
- package/dist/commands/memory/shared.js +6 -9
- package/dist/commands/memory/write.js +3 -3
- package/dist/commands/memory.js +4 -7
- package/dist/commands/node-context.d.ts +2 -0
- package/dist/commands/node-context.js +172 -0
- package/dist/commands/node-snapshot.d.ts +2 -0
- package/dist/commands/node-snapshot.js +123 -0
- package/dist/commands/node.js +228 -52
- package/dist/commands/pkg/plugin-inspect.js +6 -6
- package/dist/commands/pkg/plugin-manage.js +1 -1
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +2 -2
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +79 -11
- package/dist/commands/revive.js +74 -1
- package/dist/commands/search/answer.d.ts +1 -0
- package/dist/commands/search/answer.js +50 -0
- package/dist/commands/search/contents.d.ts +1 -0
- package/dist/commands/search/contents.js +96 -0
- package/dist/commands/search/exa.d.ts +53 -0
- package/dist/commands/search/exa.js +155 -0
- package/dist/commands/search/puremd.d.ts +11 -0
- package/dist/commands/search/puremd.js +53 -0
- package/dist/commands/search/web.d.ts +1 -0
- package/dist/commands/search/web.js +65 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +24 -0
- package/dist/commands/sys/__tests__/sync-import.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
- package/dist/commands/sys/config.js +59 -10
- package/dist/commands/sys/doctor.js +56 -4
- package/dist/commands/sys/prompt-review.d.ts +1 -0
- package/dist/commands/sys/prompt-review.js +178 -0
- package/dist/commands/sys/promptstudio.d.ts +2 -0
- package/dist/commands/sys/promptstudio.js +65 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +16 -0
- package/dist/commands/sys/sync.js +311 -159
- package/dist/commands/sys/sysprompt.d.ts +1 -0
- package/dist/commands/sys/sysprompt.js +86 -0
- package/dist/commands/sys.js +9 -5
- package/dist/commands/view-new.js +2 -2
- package/dist/core/__tests__/artifact-paths.test.d.ts +1 -0
- package/dist/core/__tests__/artifact-paths.test.js +44 -0
- package/dist/core/__tests__/broker-preflight.test.d.ts +1 -0
- package/dist/core/__tests__/broker-preflight.test.js +85 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +15 -0
- package/dist/core/__tests__/canvas.test.js +37 -1
- package/dist/core/__tests__/chat-view-reconnect.test.d.ts +1 -0
- package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
- package/dist/core/__tests__/child-death-wake.test.js +11 -2
- package/dist/core/__tests__/close.test.js +39 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +1 -0
- package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
- package/dist/core/__tests__/context-intro.test.js +48 -14
- package/dist/core/__tests__/daemon-boot.test.js +182 -9
- package/dist/core/__tests__/editor-label.test.d.ts +1 -0
- package/dist/core/__tests__/editor-label.test.js +26 -0
- package/dist/core/__tests__/fault-marker.test.d.ts +1 -0
- package/dist/core/__tests__/fault-marker.test.js +112 -0
- package/dist/core/__tests__/fault-retry-rewind.test.d.ts +1 -0
- package/dist/core/__tests__/fault-retry-rewind.test.js +123 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +156 -17
- package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
- package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +9 -3
- package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
- package/dist/core/__tests__/human-surface-target.test.js +1 -1
- package/dist/core/__tests__/kickoff.test.js +18 -19
- package/dist/core/__tests__/memory-resolver.test.js +12 -1
- package/dist/core/__tests__/migration.test.js +49 -14
- package/dist/core/__tests__/model-ladders.test.d.ts +1 -0
- package/dist/core/__tests__/model-ladders.test.js +160 -0
- package/dist/core/__tests__/node-env.test.d.ts +1 -0
- package/dist/core/__tests__/node-env.test.js +26 -0
- package/dist/core/__tests__/push-final-guard.test.js +7 -1
- package/dist/core/__tests__/relaunch-root.test.js +9 -9
- package/dist/core/__tests__/revive.test.js +36 -6
- package/dist/core/__tests__/spawn-root.test.js +24 -1
- package/dist/core/__tests__/stop-guard.test.js +10 -0
- package/dist/core/__tests__/stranded-relaunch.test.d.ts +1 -0
- package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
- package/dist/core/__tests__/tmux-surface.test.js +5 -1
- package/dist/core/__tests__/unknown-path.test.js +19 -19
- package/dist/core/__tests__/yield-ensures-daemon.test.d.ts +1 -0
- package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
- package/dist/core/artifact.d.ts +2 -33
- package/dist/core/artifact.js +27 -87
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
- package/dist/core/canvas/browse/app.js +87 -17
- package/dist/core/canvas/browse/model.d.ts +10 -1
- package/dist/core/canvas/browse/model.js +37 -1
- package/dist/core/canvas/browse/render.js +35 -33
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +54 -5
- package/dist/core/canvas/db.js +35 -8
- package/dist/core/canvas/labels.d.ts +6 -8
- package/dist/core/canvas/labels.js +8 -11
- package/dist/core/canvas/nav-model.d.ts +5 -4
- package/dist/core/canvas/nav-model.js +31 -17
- package/dist/core/canvas/paths.d.ts +18 -1
- package/dist/core/canvas/paths.js +37 -2
- package/dist/core/canvas/render.d.ts +25 -0
- package/dist/core/canvas/render.js +96 -13
- package/dist/core/canvas/status-glyph.d.ts +35 -0
- package/dist/core/canvas/status-glyph.js +104 -0
- package/dist/core/canvas/types.d.ts +2 -2
- package/dist/core/config.js +114 -16
- package/dist/core/fault-classifier.d.ts +41 -0
- package/dist/core/fault-classifier.js +110 -0
- package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +1 -0
- package/dist/core/feed/__tests__/empty-node-guard.test.js +45 -0
- package/dist/core/feed/feed.js +8 -0
- package/dist/core/feed/inbox.d.ts +15 -0
- package/dist/core/feed/inbox.js +40 -14
- package/dist/core/frontmatter.d.ts +1 -11
- package/dist/core/frontmatter.js +1 -55
- package/dist/core/hearth/config.d.ts +2 -0
- package/dist/core/hearth/config.js +75 -0
- package/dist/core/hearth/guest-env.d.ts +2 -0
- package/dist/core/hearth/guest-env.js +11 -0
- package/dist/core/hearth/index.d.ts +5 -0
- package/dist/core/hearth/index.js +5 -0
- package/dist/core/hearth/provider.d.ts +21 -0
- package/dist/core/hearth/provider.js +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
- package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
- package/dist/core/hearth/providers/blaxel-home.js +126 -0
- package/dist/core/hearth/providers/blaxel.d.ts +26 -0
- package/dist/core/hearth/providers/blaxel.js +208 -0
- package/dist/core/hearth/providers/types.d.ts +72 -0
- package/dist/core/hearth/providers/types.js +1 -0
- package/dist/core/hearth/registry.d.ts +15 -0
- package/dist/core/hearth/registry.js +179 -0
- package/dist/core/hearth/types.d.ts +121 -0
- package/dist/core/hearth/types.js +1 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
- package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
- package/dist/core/host-exports/builtins.js +68 -0
- package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
- package/dist/core/{skill-sync → host-exports}/export.js +37 -14
- package/dist/core/io.js +1 -1
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +113 -0
- package/dist/core/memory-resolver.d.ts +8 -8
- package/dist/core/memory-resolver.js +15 -28
- package/dist/core/personas/index.d.ts +4 -4
- package/dist/core/personas/index.js +2 -2
- package/dist/core/personas/loader.d.ts +51 -0
- package/dist/core/personas/loader.js +54 -50
- package/dist/core/personas/resolve.d.ts +43 -27
- package/dist/core/personas/resolve.js +336 -94
- package/dist/core/runtime/auth-reload.d.ts +7 -0
- package/dist/core/runtime/auth-reload.js +69 -0
- package/dist/core/runtime/bearings.d.ts +35 -15
- package/dist/core/runtime/bearings.js +305 -44
- package/dist/core/runtime/branded-host.d.ts +4 -2
- package/dist/core/runtime/branded-host.js +66 -4
- package/dist/core/runtime/broker-protocol.d.ts +38 -3
- package/dist/core/runtime/broker.js +343 -18
- package/dist/core/runtime/close.js +37 -29
- package/dist/core/runtime/connectivity.d.ts +12 -0
- package/dist/core/runtime/connectivity.js +73 -0
- package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
- package/dist/core/runtime/fault-recovery-nudge.js +2 -0
- package/dist/core/runtime/fault-recovery.d.ts +23 -0
- package/dist/core/runtime/fault-recovery.js +74 -0
- package/dist/core/runtime/fault.d.ts +25 -0
- package/dist/core/runtime/fault.js +176 -0
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/host.d.ts +10 -0
- package/dist/core/runtime/host.js +71 -9
- package/dist/core/runtime/kickoff.js +31 -45
- package/dist/core/runtime/launch.d.ts +32 -18
- package/dist/core/runtime/launch.js +148 -43
- package/dist/core/runtime/model-swap.d.ts +18 -0
- package/dist/core/runtime/model-swap.js +95 -0
- package/dist/core/runtime/naming.d.ts +7 -0
- package/dist/core/runtime/naming.js +61 -9
- package/dist/core/runtime/nodes.js +6 -1
- package/dist/core/runtime/persona.js +16 -17
- package/dist/core/runtime/placement.d.ts +36 -31
- package/dist/core/runtime/placement.js +149 -64
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +23 -3
- package/dist/core/runtime/recycle.js +6 -4
- package/dist/core/runtime/reset.d.ts +5 -2
- package/dist/core/runtime/reset.js +2 -2
- package/dist/core/runtime/revive.d.ts +4 -0
- package/dist/core/runtime/revive.js +27 -9
- package/dist/core/runtime/roadmap.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +2 -2
- package/dist/core/runtime/spawn.js +30 -6
- package/dist/core/runtime/stop-guard.js +6 -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 +29 -6
- package/dist/core/runtime/tmux.js +134 -80
- package/dist/core/runtime/view-socket-client.d.ts +46 -0
- package/dist/core/runtime/view-socket-client.js +203 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/on-read.js +16 -4
- package/dist/core/substrate/render.d.ts +10 -13
- package/dist/core/substrate/render.js +56 -51
- package/dist/core/tui/host.js +83 -18
- package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
- package/dist/core/view/__tests__/transport-cache.test.js +62 -0
- package/dist/core/view/contract.d.ts +20 -0
- package/dist/core/view/stream-local.d.ts +3 -0
- package/dist/core/view/stream-local.js +184 -0
- package/dist/core/view/transport-cache.d.ts +8 -0
- package/dist/core/view/transport-cache.js +38 -0
- package/dist/core/view/transport-local.js +4 -0
- package/dist/core/view/transport.d.ts +7 -1
- package/dist/daemon/crtrd.d.ts +3 -19
- package/dist/daemon/crtrd.js +139 -178
- package/dist/daemon/manage.d.ts +18 -1
- package/dist/daemon/manage.js +54 -9
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
- package/dist/hearth/wake-proxy/auth.d.ts +22 -0
- package/dist/hearth/wake-proxy/auth.js +128 -0
- package/dist/hearth/wake-proxy/config.d.ts +2 -0
- package/dist/hearth/wake-proxy/config.js +151 -0
- package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
- package/dist/hearth/wake-proxy/guest-source.js +130 -0
- package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
- package/dist/hearth/wake-proxy/hearth-status.js +267 -0
- package/dist/hearth/wake-proxy/home.d.ts +67 -0
- package/dist/hearth/wake-proxy/home.js +297 -0
- package/dist/hearth/wake-proxy/main.d.ts +1 -0
- package/dist/hearth/wake-proxy/main.js +134 -0
- package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
- package/dist/hearth/wake-proxy/model-auth.js +345 -0
- package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
- package/dist/hearth/wake-proxy/proxy.js +716 -0
- package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
- package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
- package/dist/hearth/wake-proxy/redact.d.ts +1 -0
- package/dist/hearth/wake-proxy/redact.js +17 -0
- package/dist/hearth/wake-proxy/server.d.ts +11 -0
- package/dist/hearth/wake-proxy/server.js +142 -0
- package/dist/hearth/wake-proxy/state.d.ts +18 -0
- package/dist/hearth/wake-proxy/state.js +342 -0
- package/dist/hearth/wake-proxy/types.d.ts +76 -0
- package/dist/hearth/wake-proxy/types.js +1 -0
- package/dist/index.js +3 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
- package/dist/pi-extensions/canvas-context-intro.js +7 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
- package/dist/pi-extensions/canvas-nav.js +15 -8
- package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
- package/dist/pi-extensions/canvas-stophook.js +78 -11
- package/dist/types.d.ts +39 -20
- package/dist/types.js +38 -13
- package/dist/web/runtime.js +141 -42
- package/dist/web/transport-http.js +7 -5
- package/dist/web/transport-stream.d.ts +3 -0
- package/dist/web/transport-stream.js +204 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
- package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
- package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
- package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
- package/dist/web-client/assets/index-D36PNBj4.js +80 -0
- package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
- package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/index.html +3 -2
- package/package.json +32 -10
- package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
- package/dist/core/skill-sync/builtins.js +0 -112
- package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
- package/dist/core/skill-sync/claude-plugins.js +0 -71
- package/dist/core/skill-sync/engine.d.ts +0 -42
- package/dist/core/skill-sync/engine.js +0 -633
- package/dist/core/skill-sync/manifest.d.ts +0 -64
- package/dist/core/skill-sync/manifest.js +0 -181
- package/dist/core/skill-sync/profile.d.ts +0 -76
- package/dist/core/skill-sync/profile.js +0 -173
- package/dist/core/skill-sync/snapshot.d.ts +0 -57
- package/dist/core/skill-sync/snapshot.js +0 -120
- package/dist/pi-extensions/canvas-commands.d.ts +0 -37
- package/dist/pi-extensions/canvas-commands.js +0 -113
- package/dist/pi-extensions/canvas-resume.d.ts +0 -21
- package/dist/pi-extensions/canvas-resume.js +0 -83
- package/dist/pi-extensions/canvas-view.d.ts +0 -21
- package/dist/pi-extensions/canvas-view.js +0 -76
- package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
- package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
- /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
- /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
|
@@ -3,13 +3,13 @@ import { InputError } from '../../core/io.js';
|
|
|
3
3
|
import { pushFinal } from '../../core/feed/feed.js';
|
|
4
4
|
import { interactionsRoot, interactionDir } from '../../core/artifact.js';
|
|
5
5
|
import { paginate } from '../../core/pagination.js';
|
|
6
|
-
import { getNode, subscribersOf } from '../../core/canvas/index.js';
|
|
6
|
+
import { getNode, listNodes, subscribersOf } from '../../core/canvas/index.js';
|
|
7
7
|
import { transition } from '../../core/runtime/lifecycle.js';
|
|
8
8
|
import { appendInbox } from '../../core/feed/inbox.js';
|
|
9
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
10
|
-
import { join } from 'node:path';
|
|
9
|
+
import { closeSync, existsSync, linkSync, openSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { basename, join } from 'node:path';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
|
-
import { inbox, scanInbox, parseDeck, deckPath, responsePath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
|
|
12
|
+
import { inbox, scanInbox, parseDeck, deckPath, responsePath, progressPath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
|
|
13
13
|
import { killPane } from './shared.js';
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// stranded-answer healing
|
|
@@ -18,12 +18,26 @@ import { killPane } from './shared.js';
|
|
|
18
18
|
* `<id>: <option>[ — <freetext>]` shape. Used when reconstructing a
|
|
19
19
|
* ResolutionEnvelope from a bare on-disk response.json (which stores only
|
|
20
20
|
* `{responses, completedAt}`), so a healed deliver-back reads like the live one. */
|
|
21
|
-
function summarizeResponses(responses) {
|
|
21
|
+
function summarizeResponses(responses, deck) {
|
|
22
|
+
const interactions = new Map((deck?.interactions ?? []).map((it) => [it.id, it]));
|
|
22
23
|
return responses
|
|
23
24
|
.map((r) => {
|
|
24
|
-
const
|
|
25
|
+
const it = interactions.get(r.id);
|
|
26
|
+
let picked = '';
|
|
27
|
+
if (r.selectedOptionIds !== undefined) {
|
|
28
|
+
picked = r.selectedOptionIds
|
|
29
|
+
.map((id) => {
|
|
30
|
+
const label = it?.options.find((o) => o.id === id)?.label ?? id;
|
|
31
|
+
const note = r.optionComments?.[id];
|
|
32
|
+
return typeof note === 'string' && note.trim() !== '' ? `${label} ("${note.trim()}")` : label;
|
|
33
|
+
})
|
|
34
|
+
.join(', ');
|
|
35
|
+
}
|
|
36
|
+
else if (r.selectedOptionId !== undefined) {
|
|
37
|
+
picked = it?.options.find((o) => o.id === r.selectedOptionId)?.label ?? r.selectedOptionId;
|
|
38
|
+
}
|
|
25
39
|
const ft = typeof r.freetext === 'string' && r.freetext.trim() !== '' ? ` — ${r.freetext.trim()}` : '';
|
|
26
|
-
return `${r.id}: ${
|
|
40
|
+
return `${it?.title ?? r.id}: ${picked}${ft}`;
|
|
27
41
|
})
|
|
28
42
|
.join('\n');
|
|
29
43
|
}
|
|
@@ -39,47 +53,56 @@ function summarizeResponses(responses) {
|
|
|
39
53
|
* flips status=done). Reconstructs the same pushFinal body `_run` would have
|
|
40
54
|
* emitted, per mode; a canceled-on-disk response reaps the node without
|
|
41
55
|
* delivering a result (mirrors `human cancel`). Returns true iff it acted. */
|
|
42
|
-
export async function finalizeResolvedInteraction(jobId) {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
return false;
|
|
46
|
-
if (node.status !== 'active' && node.status !== 'idle')
|
|
56
|
+
export async function finalizeResolvedInteraction(jobId, claim) {
|
|
57
|
+
const firstNode = getNode(jobId);
|
|
58
|
+
if (firstNode === null)
|
|
47
59
|
return false;
|
|
48
|
-
const idir = interactionDir(jobId,
|
|
49
|
-
|
|
60
|
+
const idir = interactionDir(jobId, firstNode.cwd);
|
|
61
|
+
const ownedClaim = claim ?? acquireInteractionClaim(idir, jobId, { allowResolved: true });
|
|
62
|
+
if (ownedClaim === 'already_resolved' || ownedClaim === 'claimed')
|
|
50
63
|
return false;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
64
|
+
try {
|
|
65
|
+
const node = getNode(jobId);
|
|
66
|
+
if (node === null)
|
|
67
|
+
return false;
|
|
68
|
+
if (node.status !== 'active' && node.status !== 'idle')
|
|
69
|
+
return false;
|
|
70
|
+
if (!isResolved(idir))
|
|
71
|
+
return false;
|
|
72
|
+
const rc = readJson(join(idir, 'run.json'));
|
|
73
|
+
if (rc === null)
|
|
74
|
+
return false;
|
|
75
|
+
const resp = readJson(responsePath(idir));
|
|
76
|
+
if (resp === null)
|
|
77
|
+
return false;
|
|
78
|
+
// Canceled out-of-band (a raw canceled response.json, not via `human cancel`):
|
|
79
|
+
// there is no answer to deliver — just reap the node and tell waiting
|
|
80
|
+
// subscribers no answer is coming, the same quiet deferred note `human cancel`
|
|
81
|
+
// emits. (`finalize` is legal from active|idle; the status guard above holds.)
|
|
82
|
+
if (resp['canceled'] === true) {
|
|
83
|
+
transition(jobId, 'finalize');
|
|
84
|
+
const note = typeof resp['reason'] === 'string' && resp['reason'] !== '' ? ` — ${resp['reason']}` : '';
|
|
85
|
+
for (const sub of subscribersOf(jobId)) {
|
|
86
|
+
appendInbox(sub.node_id, {
|
|
87
|
+
from: jobId,
|
|
88
|
+
tier: 'deferred',
|
|
89
|
+
kind: 'message',
|
|
90
|
+
label: `human interaction ${jobId} canceled — no answer is coming${note}`,
|
|
91
|
+
data: { body: `The human interaction ${jobId} was canceled${note}. No response will arrive.` },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
72
95
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
const responses = resp['responses'] ?? [];
|
|
97
|
+
const completedAt = resp['completedAt'] ?? new Date().toISOString();
|
|
98
|
+
let deck;
|
|
99
|
+
try {
|
|
100
|
+
deck = parseDeck(deckPath(idir));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
deck = undefined;
|
|
104
|
+
}
|
|
105
|
+
const summary = summarizeResponses(responses, deck);
|
|
83
106
|
// ask (and any other answered deck): the full ResolutionEnvelope shape.
|
|
84
107
|
const env = {
|
|
85
108
|
summary,
|
|
@@ -89,8 +112,12 @@ export async function finalizeResolvedInteraction(jobId) {
|
|
|
89
112
|
completedAt,
|
|
90
113
|
};
|
|
91
114
|
await pushFinal(jobId, JSON.stringify(env));
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
if (claim === undefined)
|
|
119
|
+
ownedClaim.release();
|
|
92
120
|
}
|
|
93
|
-
return true;
|
|
94
121
|
}
|
|
95
122
|
/** Sweep every interaction under the cwd's interactions root and deliver-back +
|
|
96
123
|
* reap any that are answered-but-undelivered (see finalizeResolvedInteraction).
|
|
@@ -139,6 +166,76 @@ export const humanInbox = defineLeaf({
|
|
|
139
166
|
return { drained: true, delivered };
|
|
140
167
|
},
|
|
141
168
|
});
|
|
169
|
+
function allInteractionRoots() {
|
|
170
|
+
const roots = new Set();
|
|
171
|
+
roots.add(interactionsRoot(process.cwd()));
|
|
172
|
+
for (const node of listNodes())
|
|
173
|
+
roots.add(interactionsRoot(node.cwd));
|
|
174
|
+
return [...roots];
|
|
175
|
+
}
|
|
176
|
+
function nodeRefs() {
|
|
177
|
+
return listNodes().map((n) => ({ node_id: n.node_id, name: n.name, cwd: n.cwd, parent: n.parent }));
|
|
178
|
+
}
|
|
179
|
+
function resolveConversation(asking, nodes) {
|
|
180
|
+
if (asking === undefined)
|
|
181
|
+
return undefined;
|
|
182
|
+
const byId = new Map(nodes.map((n) => [n.node_id, n]));
|
|
183
|
+
let cur = asking;
|
|
184
|
+
const seen = new Set();
|
|
185
|
+
while (cur.parent !== null && !seen.has(cur.node_id)) {
|
|
186
|
+
seen.add(cur.node_id);
|
|
187
|
+
const parent = byId.get(cur.parent);
|
|
188
|
+
if (parent === undefined)
|
|
189
|
+
break;
|
|
190
|
+
cur = parent;
|
|
191
|
+
}
|
|
192
|
+
return cur;
|
|
193
|
+
}
|
|
194
|
+
function askingNodeFor(jobId, deck, dir, nodes) {
|
|
195
|
+
const stamped = deck?.source?.nodeId;
|
|
196
|
+
if (stamped !== undefined && stamped !== '') {
|
|
197
|
+
const found = nodes.find((n) => n.node_id === stamped);
|
|
198
|
+
if (found !== undefined)
|
|
199
|
+
return found;
|
|
200
|
+
}
|
|
201
|
+
const bridge = getNode(jobId);
|
|
202
|
+
if (bridge?.parent !== undefined && bridge.parent !== null) {
|
|
203
|
+
const parent = nodes.find((n) => n.node_id === bridge.parent);
|
|
204
|
+
if (parent !== undefined)
|
|
205
|
+
return parent;
|
|
206
|
+
}
|
|
207
|
+
for (const n of nodes) {
|
|
208
|
+
const root = interactionsRoot(n.cwd);
|
|
209
|
+
if (dir === root || dir.startsWith(`${root}/`))
|
|
210
|
+
return n;
|
|
211
|
+
}
|
|
212
|
+
return bridge !== null ? { node_id: bridge.node_id, name: bridge.name, cwd: bridge.cwd, parent: bridge.parent ?? null } : undefined;
|
|
213
|
+
}
|
|
214
|
+
function summarizeDeckItem(item, deck, nodes) {
|
|
215
|
+
const jobId = basename(item.dir);
|
|
216
|
+
if (getNode(jobId) === null)
|
|
217
|
+
return null;
|
|
218
|
+
const asking = askingNodeFor(jobId, deck, item.dir, nodes);
|
|
219
|
+
const conversation = resolveConversation(asking, nodes);
|
|
220
|
+
return {
|
|
221
|
+
id: item.id,
|
|
222
|
+
job_id: jobId,
|
|
223
|
+
dir: item.dir,
|
|
224
|
+
title: item.title !== undefined ? item.title : deck?.title ?? null,
|
|
225
|
+
kind: item.kind !== undefined ? item.kind : deck?.interactions[0]?.kind ?? null,
|
|
226
|
+
blocked_since: item.blockedSince,
|
|
227
|
+
...(asking !== undefined ? { asking_node_id: asking.node_id, asking_node_name: asking.name } : {}),
|
|
228
|
+
...(conversation !== undefined ? { conversation_id: conversation.node_id, conversation_title: conversation.name } : {}),
|
|
229
|
+
...(deck !== null ? { interaction_count: deck.interactions.length } : {}),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function interactionDirForJob(jobId) {
|
|
233
|
+
const node = getNode(jobId);
|
|
234
|
+
if (node === null) {
|
|
235
|
+
throw new InputError({ error: 'not_found', message: `no interaction node: ${jobId}`, field: 'job_id', next: 'Pass a job_id from `crtr human list`.' });
|
|
236
|
+
}
|
|
237
|
+
return { dir: interactionDir(jobId, node.cwd) };
|
|
238
|
+
}
|
|
142
239
|
// ---------------------------------------------------------------------------
|
|
143
240
|
// list (read-only, paginated)
|
|
144
241
|
// ---------------------------------------------------------------------------
|
|
@@ -154,7 +251,7 @@ export const humanList = defineLeaf({
|
|
|
154
251
|
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: "Opaque token from a previous response's next_cursor. Omit on first call." },
|
|
155
252
|
],
|
|
156
253
|
output: [
|
|
157
|
-
{ name: 'items', type: 'object[]', required: true, constraint: 'Each: {id, dir, title, kind, blocked_since}. Oldest first.' },
|
|
254
|
+
{ name: 'items', type: 'object[]', required: true, constraint: 'Each: {id, dir, title, kind, blocked_since}, enriched when derivable with asking_node_id/name, conversation_id/title, interaction_count. Oldest first.' },
|
|
158
255
|
{ name: 'next_cursor', type: 'string | null', required: true, constraint: 'Pass on the next call to continue. null means no more items.' },
|
|
159
256
|
{ name: 'total', type: 'integer | null', required: true, constraint: 'Total pending interactions.' },
|
|
160
257
|
],
|
|
@@ -165,15 +262,13 @@ export const humanList = defineLeaf({
|
|
|
165
262
|
const limitRaw = input['limit'];
|
|
166
263
|
const limit = Math.min(Math.max(1, limitRaw), 100);
|
|
167
264
|
const cursor = input['cursor'];
|
|
168
|
-
const
|
|
265
|
+
const nodes = nodeRefs();
|
|
266
|
+
const raw = scanInbox(allInteractionRoots());
|
|
169
267
|
const items = raw
|
|
170
|
-
.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
kind: i.kind !== undefined ? i.kind : null,
|
|
175
|
-
blocked_since: i.blockedSince,
|
|
176
|
-
}))
|
|
268
|
+
.flatMap((i) => {
|
|
269
|
+
const item = summarizeDeckItem(i, readJson(deckPath(i.dir)), nodes);
|
|
270
|
+
return item === null ? [] : [item];
|
|
271
|
+
})
|
|
177
272
|
.sort((a, b) => {
|
|
178
273
|
const ka = `${a.blocked_since}|${a.id}`;
|
|
179
274
|
const kb = `${b.blocked_since}|${b.id}`;
|
|
@@ -189,18 +284,308 @@ export const humanList = defineLeaf({
|
|
|
189
284
|
},
|
|
190
285
|
});
|
|
191
286
|
// ---------------------------------------------------------------------------
|
|
192
|
-
//
|
|
287
|
+
// deck detail (read-only)
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
export const humanDeck = defineLeaf({
|
|
290
|
+
name: 'deck',
|
|
291
|
+
description: 'read one pending humanloop deck',
|
|
292
|
+
whenToUse: 'you need the full questions/options for a pending human interaction',
|
|
293
|
+
help: {
|
|
294
|
+
name: 'human deck',
|
|
295
|
+
summary: 'read full pending deck detail for one human interaction job',
|
|
296
|
+
params: [{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'job_id from human ask/review/list.' }],
|
|
297
|
+
output: [
|
|
298
|
+
{ name: 'id', type: 'string', required: true, constraint: 'Interaction job id.' },
|
|
299
|
+
{ name: 'interactions', type: 'object[]', required: true, constraint: 'Deck interactions with options and freetext policy.' },
|
|
300
|
+
],
|
|
301
|
+
outputKind: 'object',
|
|
302
|
+
effects: ['Read-only: reads deck.json from the interaction dir resolved through the canvas node cwd.'],
|
|
303
|
+
},
|
|
304
|
+
run: async (input) => {
|
|
305
|
+
const jobId = input['job_id'];
|
|
306
|
+
const { dir } = interactionDirForJob(jobId);
|
|
307
|
+
if (isResolved(dir)) {
|
|
308
|
+
throw new InputError({ error: 'already_resolved', message: `interaction already resolved: ${jobId}`, field: 'job_id', next: 'Use `crtr human list` for pending interactions.' });
|
|
309
|
+
}
|
|
310
|
+
let deck;
|
|
311
|
+
try {
|
|
312
|
+
deck = parseDeck(deckPath(dir));
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
throw new InputError({ error: 'not_found', message: `no deck for interaction: ${jobId}`, field: 'job_id', next: 'Pass a pending job_id from `crtr human list`.' });
|
|
316
|
+
}
|
|
317
|
+
const nodes = nodeRefs();
|
|
318
|
+
const asking = askingNodeFor(jobId, deck, dir, nodes);
|
|
319
|
+
const conversation = resolveConversation(asking, nodes);
|
|
320
|
+
const first = deck.interactions[0];
|
|
321
|
+
return {
|
|
322
|
+
id: jobId,
|
|
323
|
+
title: deck.title ?? first?.title ?? null,
|
|
324
|
+
kind: first?.kind ?? null,
|
|
325
|
+
blocked_since: deck.source?.blockedSince ?? null,
|
|
326
|
+
...(asking !== undefined ? { asking_node_id: asking.node_id, asking_node_name: asking.name } : {}),
|
|
327
|
+
...(conversation !== undefined ? { conversation_id: conversation.node_id, conversation_title: conversation.name } : {}),
|
|
328
|
+
interaction_count: deck.interactions.length,
|
|
329
|
+
interactions: deck.interactions.map((it) => ({
|
|
330
|
+
id: it.id,
|
|
331
|
+
kind: it.kind ?? null,
|
|
332
|
+
prompt: it.body ?? it.title,
|
|
333
|
+
options: it.options.map((o) => ({ id: o.id, label: o.label, ...(o.description !== undefined ? { description: o.description } : {}) })),
|
|
334
|
+
allow_freetext: it.allowFreetext ?? false,
|
|
335
|
+
...(it.preAnswered?.selectedOptionId !== undefined ? { default_option_id: it.preAnswered.selectedOptionId } : {}),
|
|
336
|
+
})),
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
function isErrno(e, code) {
|
|
341
|
+
return typeof e === 'object' && e !== null && 'code' in e && e.code === code;
|
|
342
|
+
}
|
|
343
|
+
function pidAlive(pid) {
|
|
344
|
+
try {
|
|
345
|
+
process.kill(pid, 0);
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
if (isErrno(e, 'ESRCH'))
|
|
350
|
+
return false;
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function interactionLockPath(dir) {
|
|
355
|
+
return join(dir, 'resolve.lock');
|
|
356
|
+
}
|
|
357
|
+
function releaseClaim(dir, token) {
|
|
358
|
+
const lockFile = interactionLockPath(dir);
|
|
359
|
+
const cur = readJson(lockFile);
|
|
360
|
+
if (cur?.token === token) {
|
|
361
|
+
try {
|
|
362
|
+
unlinkSync(lockFile);
|
|
363
|
+
}
|
|
364
|
+
catch (e) {
|
|
365
|
+
if (!isErrno(e, 'ENOENT'))
|
|
366
|
+
throw e;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const progressFile = progressPath(dir);
|
|
370
|
+
const progress = readJson(progressFile);
|
|
371
|
+
if (progress?.claimToken !== token)
|
|
372
|
+
return;
|
|
373
|
+
try {
|
|
374
|
+
unlinkSync(progressFile);
|
|
375
|
+
}
|
|
376
|
+
catch (e) {
|
|
377
|
+
if (!isErrno(e, 'ENOENT'))
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
export function acquireInteractionClaim(dir, jobId, opts = {}) {
|
|
382
|
+
const allowResolved = opts.allowResolved === true;
|
|
383
|
+
const markProgress = opts.markProgress === true;
|
|
384
|
+
const respectProgress = opts.respectProgress === true;
|
|
385
|
+
const lockFile = interactionLockPath(dir);
|
|
386
|
+
const progressFile = progressPath(dir);
|
|
387
|
+
const token = `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
|
388
|
+
for (;;) {
|
|
389
|
+
if (!allowResolved && isResolved(dir))
|
|
390
|
+
return 'already_resolved';
|
|
391
|
+
if (!allowResolved && respectProgress && existsSync(progressFile) && !existsSync(lockFile)) {
|
|
392
|
+
const progress = readJson(progressFile);
|
|
393
|
+
if (progress?.claim === 'crtr human resolve') {
|
|
394
|
+
try {
|
|
395
|
+
unlinkSync(progressFile);
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
if (!isErrno(e, 'ENOENT'))
|
|
399
|
+
return 'claimed';
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
return 'claimed';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
let fd = null;
|
|
407
|
+
try {
|
|
408
|
+
fd = openSync(lockFile, 'wx');
|
|
409
|
+
writeFileSync(fd, JSON.stringify({ pid: process.pid, token, jobId, claimedAt: new Date().toISOString() }, null, 2));
|
|
410
|
+
closeSync(fd);
|
|
411
|
+
fd = null;
|
|
412
|
+
if (markProgress) {
|
|
413
|
+
atomicWriteJson(progressFile, { partial: true, claim: 'crtr human resolve', claimToken: token, jobId, claimedAt: new Date().toISOString() });
|
|
414
|
+
}
|
|
415
|
+
if (!allowResolved && isResolved(dir)) {
|
|
416
|
+
releaseClaim(dir, token);
|
|
417
|
+
return 'already_resolved';
|
|
418
|
+
}
|
|
419
|
+
return { release: () => releaseClaim(dir, token) };
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
if (fd !== null)
|
|
423
|
+
closeSync(fd);
|
|
424
|
+
if (isErrno(e, 'EEXIST')) {
|
|
425
|
+
const owner = readJson(lockFile);
|
|
426
|
+
if (typeof owner?.pid === 'number' && !pidAlive(owner.pid)) {
|
|
427
|
+
try {
|
|
428
|
+
unlinkSync(lockFile);
|
|
429
|
+
}
|
|
430
|
+
catch (unlinkErr) {
|
|
431
|
+
if (!isErrno(unlinkErr, 'ENOENT'))
|
|
432
|
+
return 'claimed';
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
return 'claimed';
|
|
437
|
+
}
|
|
438
|
+
throw e;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function writeResponseOnce(dir, responses, completedAt) {
|
|
443
|
+
const finalPath = responsePath(dir);
|
|
444
|
+
const tmpPath = `${finalPath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
445
|
+
writeFileSync(tmpPath, JSON.stringify({ responses, completedAt }, null, 2), { flag: 'wx' });
|
|
446
|
+
try {
|
|
447
|
+
linkSync(tmpPath, finalPath);
|
|
448
|
+
return 'written';
|
|
449
|
+
}
|
|
450
|
+
catch (e) {
|
|
451
|
+
if (isErrno(e, 'EEXIST'))
|
|
452
|
+
return 'already_resolved';
|
|
453
|
+
throw e;
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
try {
|
|
457
|
+
unlinkSync(tmpPath);
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
if (!isErrno(e, 'ENOENT'))
|
|
461
|
+
throw e;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function validateResolveResponses(deck, responses) {
|
|
466
|
+
const byId = new Map(deck.interactions.map((it) => [it.id, it]));
|
|
467
|
+
for (const response of responses) {
|
|
468
|
+
const interaction = byId.get(response.id);
|
|
469
|
+
if (interaction === undefined) {
|
|
470
|
+
throw new InputError({ error: 'invalid_field', message: `unknown interaction_id: ${response.id}`, field: 'responses', next: 'Use interaction ids from `crtr human deck <job_id> --json`.' });
|
|
471
|
+
}
|
|
472
|
+
if (interaction.multiSelect === true) {
|
|
473
|
+
if (response.selectedOptionId !== undefined) {
|
|
474
|
+
throw new InputError({ error: 'invalid_field', message: `interaction ${response.id} is multi-select; use selected_option_ids`, field: 'responses', next: 'Use selected_option_ids for multi-select interactions.' });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else if (response.selectedOptionIds !== undefined && response.selectedOptionIds.length > 1) {
|
|
478
|
+
throw new InputError({ error: 'invalid_field', message: `interaction ${response.id} is single-select`, field: 'responses', next: 'Use selected_option_id or one selected_option_ids value for single-select interactions.' });
|
|
479
|
+
}
|
|
480
|
+
const selected = response.selectedOptionIds ?? (response.selectedOptionId !== undefined ? [response.selectedOptionId] : []);
|
|
481
|
+
const optionIds = new Set(interaction.options.map((o) => o.id));
|
|
482
|
+
for (const id of selected) {
|
|
483
|
+
if (!optionIds.has(id)) {
|
|
484
|
+
throw new InputError({ error: 'invalid_field', message: `unknown option id for ${response.id}: ${id}`, field: 'responses', next: 'Use option ids from `crtr human deck <job_id> --json`.' });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function parseResolveBody(raw) {
|
|
490
|
+
let parsed;
|
|
491
|
+
try {
|
|
492
|
+
parsed = JSON.parse(raw);
|
|
493
|
+
}
|
|
494
|
+
catch (e) {
|
|
495
|
+
throw new InputError({ error: 'invalid_json', message: `stdin is not valid JSON: ${String(e)}`, field: 'stdin', next: 'Pass {"responses":[{"interaction_id":"..."}]} on stdin.' });
|
|
496
|
+
}
|
|
497
|
+
if (!Array.isArray(parsed.responses)) {
|
|
498
|
+
throw new InputError({ error: 'invalid_field', message: 'responses must be an array', field: 'responses', next: 'Pass {"responses":[{"interaction_id":"...","selected_option_ids":["..."]}]}.' });
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
responses: parsed.responses.map((r) => {
|
|
502
|
+
const id = r.interaction_id ?? r.id;
|
|
503
|
+
if (id === undefined || id === '') {
|
|
504
|
+
throw new InputError({ error: 'invalid_field', message: 'each response needs interaction_id', field: 'responses', next: 'Include interaction_id for every response.' });
|
|
505
|
+
}
|
|
506
|
+
const selectedOptionIds = r.selected_option_ids ?? r.selectedOptionIds;
|
|
507
|
+
const selectedOptionId = r.selected_option_id ?? r.selectedOptionId;
|
|
508
|
+
return {
|
|
509
|
+
id,
|
|
510
|
+
...(selectedOptionIds !== undefined ? { selectedOptionIds } : {}),
|
|
511
|
+
...(selectedOptionId !== undefined ? { selectedOptionId } : {}),
|
|
512
|
+
...(r.freetext !== undefined ? { freetext: r.freetext } : {}),
|
|
513
|
+
...(r.optionComments !== undefined ? { optionComments: r.optionComments } : {}),
|
|
514
|
+
};
|
|
515
|
+
}),
|
|
516
|
+
completedAt: parsed.completed_at ?? parsed.completedAt ?? new Date().toISOString(),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
export const humanResolve = defineLeaf({
|
|
520
|
+
name: 'resolve',
|
|
521
|
+
description: 'resolve one humanloop deck from stdin answers',
|
|
522
|
+
whenToUse: 'a browser or local tool is submitting answers for a pending human interaction; this writes through the crouter/humanloop finalize path',
|
|
523
|
+
help: {
|
|
524
|
+
name: 'human resolve',
|
|
525
|
+
summary: 'write deck answers atomically and deliver them back to the asking node',
|
|
526
|
+
params: [
|
|
527
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'job_id from human ask/review/list.' },
|
|
528
|
+
{ kind: 'stdin', name: 'body', required: true, constraint: 'JSON {responses:[{interaction_id, selected_option_ids?, freetext?}], completed_at?}.' },
|
|
529
|
+
],
|
|
530
|
+
output: [
|
|
531
|
+
{ name: 'resolved', type: 'boolean', required: true, constraint: 'True when this call wrote and delivered the answer.' },
|
|
532
|
+
{ name: 'job_id', type: 'string', required: true, constraint: 'Interaction job id.' },
|
|
533
|
+
{ name: 'delivered', type: 'boolean', required: false, constraint: 'True when delivered through crouter push/finalize.' },
|
|
534
|
+
{ name: 'reason', type: 'string', required: false, constraint: 'already_resolved or claimed when resolved is false.' },
|
|
535
|
+
],
|
|
536
|
+
outputKind: 'object',
|
|
537
|
+
effects: ['Claims the interaction with a local resolve.lock (and progress.json so other humanloop inbox scans skip it), writes response.json with an exclusive no-clobber create, then calls crouter finalize/deliver-back. Does not write response.json from browser/server code.'],
|
|
538
|
+
},
|
|
539
|
+
run: async (input) => {
|
|
540
|
+
const jobId = input['job_id'];
|
|
541
|
+
const { dir } = interactionDirForJob(jobId);
|
|
542
|
+
const { responses, completedAt } = parseResolveBody(input['body']);
|
|
543
|
+
try {
|
|
544
|
+
parseDeck(deckPath(dir));
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
throw new InputError({ error: 'not_found', message: `no deck for interaction: ${jobId}`, field: 'job_id', next: 'Pass a pending job_id from `crtr human list`.' });
|
|
548
|
+
}
|
|
549
|
+
const claim = acquireInteractionClaim(dir, jobId, { markProgress: true, respectProgress: true });
|
|
550
|
+
if (claim === 'already_resolved')
|
|
551
|
+
return { resolved: false, job_id: jobId, reason: 'already_resolved' };
|
|
552
|
+
if (claim === 'claimed')
|
|
553
|
+
return { resolved: false, job_id: jobId, reason: 'claimed' };
|
|
554
|
+
try {
|
|
555
|
+
let deck;
|
|
556
|
+
try {
|
|
557
|
+
deck = parseDeck(deckPath(dir));
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
throw new InputError({ error: 'not_found', message: `no deck for interaction: ${jobId}`, field: 'job_id', next: 'Pass a pending job_id from `crtr human list`.' });
|
|
561
|
+
}
|
|
562
|
+
validateResolveResponses(deck, responses);
|
|
563
|
+
const writeResult = writeResponseOnce(dir, responses, completedAt);
|
|
564
|
+
if (writeResult === 'already_resolved')
|
|
565
|
+
return { resolved: false, job_id: jobId, reason: 'already_resolved' };
|
|
566
|
+
const delivered = await finalizeResolvedInteraction(jobId, claim);
|
|
567
|
+
if (!delivered)
|
|
568
|
+
return { resolved: false, job_id: jobId, reason: 'already_resolved' };
|
|
569
|
+
return { resolved: true, job_id: jobId, delivered };
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
claim.release();
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
// ---------------------------------------------------------------------------
|
|
577
|
+
// cancel — retract a pending ask/review
|
|
193
578
|
// ---------------------------------------------------------------------------
|
|
194
579
|
export const humanCancel = defineLeaf({
|
|
195
580
|
name: 'cancel',
|
|
196
|
-
description: 'retract a pending ask/
|
|
581
|
+
description: 'retract a pending ask/review',
|
|
197
582
|
whenToUse: 'a question went stale before the human answered',
|
|
198
583
|
help: {
|
|
199
584
|
name: 'human cancel',
|
|
200
|
-
summary: 'retract a pending ask/
|
|
201
|
-
guide: 'Pass the job_id returned by `human ask`/`
|
|
585
|
+
summary: 'retract a pending ask/review you posed — kills its TUI pane, drops it from the human queue, and retires the node. Reach for this the moment a question goes stale (you answered it yourself, the situation changed) so a human is not left resolving a prompt whose answer no longer matters',
|
|
586
|
+
guide: 'Pass the job_id returned by `human ask`/`review`. Best-effort and idempotent: if the human already answered, or it was already canceled, it reports canceled:false with reason "already_resolved" and changes nothing. The agent that posed the deck is almost always the one canceling it, so the caller is never messaged — only OTHER subscribers (e.g. the asking node when a human dismisses the prompt) get a quiet deferred note that no answer is coming. Canceling a review kills its live on-screen pane and delivers no comments — the same quiet deferred note covers it.',
|
|
202
587
|
params: [
|
|
203
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Node id of the interaction to cancel — the job_id returned by ask/
|
|
588
|
+
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: 'Node id of the interaction to cancel — the job_id returned by ask/review.' },
|
|
204
589
|
{ kind: 'flag', name: 'reason', type: 'string', required: false, constraint: 'Optional short note delivered to subscribers explaining why it was retracted.' },
|
|
205
590
|
],
|
|
206
591
|
output: [
|
|
@@ -224,7 +609,7 @@ export const humanCancel = defineLeaf({
|
|
|
224
609
|
error: 'not_found',
|
|
225
610
|
message: `no interaction node: ${jobId}`,
|
|
226
611
|
field: 'job_id',
|
|
227
|
-
next: 'Pass the job_id from human ask/
|
|
612
|
+
next: 'Pass the job_id from human ask/review, or list pending with `crtr human list`.',
|
|
228
613
|
});
|
|
229
614
|
}
|
|
230
615
|
// Resolve the interaction dir from the node's RECORDED cwd: interaction dirs
|
|
@@ -312,21 +697,31 @@ export const humanRun = defineLeaf({
|
|
|
312
697
|
process.exit(1);
|
|
313
698
|
}
|
|
314
699
|
try {
|
|
315
|
-
if (rc.mode === 'ask' || rc.mode === '
|
|
700
|
+
if (rc.mode === 'ask' || rc.mode === 'notify') {
|
|
316
701
|
const deck = parseDeck(deckPath(dir));
|
|
317
|
-
const env = await ask(deck, { dir });
|
|
318
702
|
if (rc.mode === 'ask') {
|
|
319
|
-
|
|
703
|
+
const claim = acquireInteractionClaim(dir, rc.job_id);
|
|
704
|
+
if (claim === 'already_resolved') {
|
|
705
|
+
await finalizeResolvedInteraction(rc.job_id);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
if (claim === 'claimed')
|
|
709
|
+
return;
|
|
710
|
+
try {
|
|
711
|
+
await ask(deck, { dir });
|
|
712
|
+
// Pass the held claim: finalize re-acquires the same exclusive
|
|
713
|
+
// resolve.lock, which THIS worker still holds — without the claim it
|
|
714
|
+
// would see its own live lock, return 'claimed', and never deliver
|
|
715
|
+
// the answer (stranding the asker). The held claim makes finalize
|
|
716
|
+
// run under our lock; _run's finally still owns the release.
|
|
717
|
+
await finalizeResolvedInteraction(rc.job_id, claim);
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
claim.release();
|
|
721
|
+
}
|
|
320
722
|
}
|
|
321
|
-
else
|
|
322
|
-
|
|
323
|
-
await pushFinal(rc.job_id, JSON.stringify({
|
|
324
|
-
approved: sel === 'yes',
|
|
325
|
-
summary: env.summary,
|
|
326
|
-
responses: env.responses,
|
|
327
|
-
responsePath: env.responsePath,
|
|
328
|
-
completedAt: env.completedAt,
|
|
329
|
-
}));
|
|
723
|
+
else {
|
|
724
|
+
await ask(deck, { dir });
|
|
330
725
|
}
|
|
331
726
|
// notify: no job — nothing to write
|
|
332
727
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { type DetachResult } from '../../core/spawn.js';
|
|
2
2
|
export declare const DECK_SCHEMA_HINT: string;
|
|
3
3
|
export interface RunRecord {
|
|
4
|
-
mode: 'ask' | '
|
|
4
|
+
mode: 'ask' | 'notify' | 'review';
|
|
5
5
|
job_id?: string;
|
|
6
|
-
approve_iid?: string;
|
|
7
6
|
file?: string;
|
|
8
7
|
output?: string;
|
|
9
8
|
/** tmux pane id of the detached TUI, recorded so `human cancel` can kill it. */
|
|
@@ -32,7 +31,7 @@ export declare function resolveHumanTarget(): string | undefined;
|
|
|
32
31
|
/**
|
|
33
32
|
* Open the detached humanloop `_run` pane for an interaction dir, ROUTED to the
|
|
34
33
|
* session the user is watching (`resolveHumanTarget`). The single spawn path
|
|
35
|
-
* behind ask/
|
|
34
|
+
* behind ask/review (`spawnHumanJob`) and notify. `detached: true` keeps
|
|
36
35
|
* the user's view put — the prompt lands beside the watched node without
|
|
37
36
|
* jumping their session/window. `jobId`, when given, is injected as CRTR_JOB_ID.
|
|
38
37
|
*/
|
|
@@ -55,7 +54,7 @@ export declare function followUpReview(_jobId: string): string;
|
|
|
55
54
|
*
|
|
56
55
|
* Completion routing needs no bookkeeping here: the human node was created
|
|
57
56
|
* under the asking node as its parent (spawnNode auto-subscribes the parent),
|
|
58
|
-
* so the `pushFinal` the `_run` worker emits — for ask
|
|
57
|
+
* so the `pushFinal` the `_run` worker emits — for ask AND review —
|
|
59
58
|
* fans the answer straight into the asking node's inbox. The pane id is recorded
|
|
60
59
|
* on run.json (not returned) so `human cancel` can later kill the TUI.
|
|
61
60
|
*/
|