@crouton-kit/crouter 0.3.27 → 0.3.29
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/build-root.js +4 -2
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +8 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +3 -3
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +445 -41
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +12 -0
- package/dist/clients/attach/canvas-panels.js +78 -0
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +43 -0
- package/dist/clients/attach/graph-overlay.js +306 -0
- package/dist/clients/attach/input-controller.d.ts +81 -13
- package/dist/clients/attach/input-controller.js +307 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +44 -1
- package/dist/clients/attach/slash-commands.js +250 -27
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +37 -2
- package/dist/clients/attach/view-socket.js +131 -7
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +1 -1
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/human/prompts.js +3 -3
- package/dist/commands/human/queue.d.ts +17 -0
- package/dist/commands/human/queue.js +111 -4
- package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
- package/dist/commands/memory/lint.d.ts +5 -4
- package/dist/commands/memory/lint.js +23 -7
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +20 -7
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/feedback.d.ts +1 -0
- package/dist/commands/sys/feedback.js +163 -0
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +4 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
- package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
- package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
- package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.d.ts +162 -0
- package/dist/core/canvas/nav-model.js +487 -0
- package/dist/core/canvas/paths.d.ts +7 -0
- package/dist/core/canvas/paths.js +9 -0
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker-sdk.d.ts +0 -12
- package/dist/core/runtime/broker-sdk.js +77 -6
- package/dist/core/runtime/broker.d.ts +5 -1
- package/dist/core/runtime/broker.js +520 -59
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +14 -7
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -334
- package/dist/core/runtime/placement.js +262 -828
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +45 -135
- package/dist/core/runtime/spawn.d.ts +15 -11
- package/dist/core/runtime/spawn.js +168 -136
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -43
- package/dist/core/runtime/tmux.js +38 -80
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/spawn.d.ts +14 -0
- package/dist/core/spawn.js +29 -9
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +7 -7
- package/dist/core/substrate/injected-store.d.ts +10 -0
- package/dist/core/substrate/injected-store.js +55 -0
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +16 -11
- package/dist/core/substrate/schema.js +32 -31
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
- package/dist/pi-extensions/canvas-nav.js +35 -385
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
- package/dist/pi-extensions/canvas-stophook.js +119 -103
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/** Deliver-back + reap an interaction whose answer is on disk but was never
|
|
2
|
+
* delivered. The detached `_run` worker's `pushFinal` is normally the SOLE
|
|
3
|
+
* deliver-back + reap step; when it never ran (e.g. a broker asker pre-fix, or
|
|
4
|
+
* a human draining via `crtr human inbox`, which writes response.json directly
|
|
5
|
+
* and never calls pushFinal), the answer strands and the bridge node leaks.
|
|
6
|
+
*
|
|
7
|
+
* Idempotent and self-gating: a no-op unless the bridge node is still LIVE
|
|
8
|
+
* (active|idle) AND its interaction is resolved on disk — so it never
|
|
9
|
+
* double-delivers an interaction the `_run` worker already finalized (pushFinal
|
|
10
|
+
* flips status=done). Reconstructs the same pushFinal body `_run` would have
|
|
11
|
+
* emitted, per mode; a canceled-on-disk response reaps the node without
|
|
12
|
+
* delivering a result (mirrors `human cancel`). Returns true iff it acted. */
|
|
13
|
+
export declare function finalizeResolvedInteraction(jobId: string): Promise<boolean>;
|
|
14
|
+
/** Sweep every interaction under the cwd's interactions root and deliver-back +
|
|
15
|
+
* reap any that are answered-but-undelivered (see finalizeResolvedInteraction).
|
|
16
|
+
* Interaction dir names ARE the bridge node ids. Returns how many it healed. */
|
|
17
|
+
export declare function healStrandedInteractions(cwd: string): Promise<number>;
|
|
1
18
|
export declare const humanInbox: import("../../core/command.js").LeafDef;
|
|
2
19
|
export declare const humanList: import("../../core/command.js").LeafDef;
|
|
3
20
|
export declare const humanCancel: import("../../core/command.js").LeafDef;
|
|
@@ -6,12 +6,109 @@ import { paginate } from '../../core/pagination.js';
|
|
|
6
6
|
import { getNode, 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 } from 'node:fs';
|
|
9
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { inbox, scanInbox, parseDeck, deckPath, responsePath, isResolved, atomicWriteJson, ask, launchReview, readJson, display, } from '@crouton-kit/humanloop';
|
|
13
13
|
import { killPane } from './shared.js';
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
|
+
// stranded-answer healing
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/** A deterministic one-line-per-interaction summary, mirroring humanloop's
|
|
18
|
+
* `<id>: <option>[ — <freetext>]` shape. Used when reconstructing a
|
|
19
|
+
* ResolutionEnvelope from a bare on-disk response.json (which stores only
|
|
20
|
+
* `{responses, completedAt}`), so a healed deliver-back reads like the live one. */
|
|
21
|
+
function summarizeResponses(responses) {
|
|
22
|
+
return responses
|
|
23
|
+
.map((r) => {
|
|
24
|
+
const opt = r.selectedOptionId ?? '';
|
|
25
|
+
const ft = typeof r.freetext === 'string' && r.freetext.trim() !== '' ? ` — ${r.freetext.trim()}` : '';
|
|
26
|
+
return `${r.id}: ${opt}${ft}`;
|
|
27
|
+
})
|
|
28
|
+
.join('\n');
|
|
29
|
+
}
|
|
30
|
+
/** Deliver-back + reap an interaction whose answer is on disk but was never
|
|
31
|
+
* delivered. The detached `_run` worker's `pushFinal` is normally the SOLE
|
|
32
|
+
* deliver-back + reap step; when it never ran (e.g. a broker asker pre-fix, or
|
|
33
|
+
* a human draining via `crtr human inbox`, which writes response.json directly
|
|
34
|
+
* and never calls pushFinal), the answer strands and the bridge node leaks.
|
|
35
|
+
*
|
|
36
|
+
* Idempotent and self-gating: a no-op unless the bridge node is still LIVE
|
|
37
|
+
* (active|idle) AND its interaction is resolved on disk — so it never
|
|
38
|
+
* double-delivers an interaction the `_run` worker already finalized (pushFinal
|
|
39
|
+
* flips status=done). Reconstructs the same pushFinal body `_run` would have
|
|
40
|
+
* emitted, per mode; a canceled-on-disk response reaps the node without
|
|
41
|
+
* delivering a result (mirrors `human cancel`). Returns true iff it acted. */
|
|
42
|
+
export async function finalizeResolvedInteraction(jobId) {
|
|
43
|
+
const node = getNode(jobId);
|
|
44
|
+
if (node === null)
|
|
45
|
+
return false;
|
|
46
|
+
if (node.status !== 'active' && node.status !== 'idle')
|
|
47
|
+
return false;
|
|
48
|
+
const idir = interactionDir(jobId, node.cwd);
|
|
49
|
+
if (!isResolved(idir))
|
|
50
|
+
return false;
|
|
51
|
+
const rc = readJson(join(idir, 'run.json'));
|
|
52
|
+
if (rc === null)
|
|
53
|
+
return false;
|
|
54
|
+
const resp = readJson(responsePath(idir));
|
|
55
|
+
if (resp === null)
|
|
56
|
+
return false;
|
|
57
|
+
// Canceled out-of-band (a raw canceled response.json, not via `human cancel`):
|
|
58
|
+
// there is no answer to deliver — just reap the node and tell waiting
|
|
59
|
+
// subscribers no answer is coming, the same quiet deferred note `human cancel`
|
|
60
|
+
// emits. (`finalize` is legal from active|idle; the status guard above holds.)
|
|
61
|
+
if (resp['canceled'] === true) {
|
|
62
|
+
transition(jobId, 'finalize');
|
|
63
|
+
const note = typeof resp['reason'] === 'string' && resp['reason'] !== '' ? ` — ${resp['reason']}` : '';
|
|
64
|
+
for (const sub of subscribersOf(jobId)) {
|
|
65
|
+
appendInbox(sub.node_id, {
|
|
66
|
+
from: jobId,
|
|
67
|
+
tier: 'deferred',
|
|
68
|
+
kind: 'message',
|
|
69
|
+
label: `human interaction ${jobId} canceled — no answer is coming${note}`,
|
|
70
|
+
data: { body: `The human interaction ${jobId} was canceled${note}. No response will arrive.` },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
const responses = resp['responses'] ?? [];
|
|
76
|
+
const completedAt = resp['completedAt'] ?? new Date().toISOString();
|
|
77
|
+
const summary = summarizeResponses(responses);
|
|
78
|
+
if (rc.mode === 'approve') {
|
|
79
|
+
const sel = responses.find((r) => r.id === rc.approve_iid)?.selectedOptionId;
|
|
80
|
+
await pushFinal(jobId, JSON.stringify({ approved: sel === 'yes', summary, responses, responsePath: responsePath(idir), completedAt }));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// ask (and any other answered deck): the full ResolutionEnvelope shape.
|
|
84
|
+
const env = {
|
|
85
|
+
summary,
|
|
86
|
+
responsePath: responsePath(idir),
|
|
87
|
+
schema: 'humanloop.response/v2',
|
|
88
|
+
responses,
|
|
89
|
+
completedAt,
|
|
90
|
+
};
|
|
91
|
+
await pushFinal(jobId, JSON.stringify(env));
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/** Sweep every interaction under the cwd's interactions root and deliver-back +
|
|
96
|
+
* reap any that are answered-but-undelivered (see finalizeResolvedInteraction).
|
|
97
|
+
* Interaction dir names ARE the bridge node ids. Returns how many it healed. */
|
|
98
|
+
export async function healStrandedInteractions(cwd) {
|
|
99
|
+
const root = interactionsRoot(cwd);
|
|
100
|
+
if (!existsSync(root))
|
|
101
|
+
return 0;
|
|
102
|
+
let healed = 0;
|
|
103
|
+
for (const ent of readdirSync(root, { withFileTypes: true })) {
|
|
104
|
+
if (!ent.isDirectory())
|
|
105
|
+
continue;
|
|
106
|
+
if (await finalizeResolvedInteraction(ent.name))
|
|
107
|
+
healed++;
|
|
108
|
+
}
|
|
109
|
+
return healed;
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
15
112
|
// inbox (human-invoked, blocking)
|
|
16
113
|
// ---------------------------------------------------------------------------
|
|
17
114
|
export const humanInbox = defineLeaf({
|
|
@@ -23,13 +120,23 @@ export const humanInbox = defineLeaf({
|
|
|
23
120
|
summary: 'interactively drain pending interactions at your own terminal',
|
|
24
121
|
params: [],
|
|
25
122
|
inputNote: 'No input. Run this at a human terminal — it blocks until the backlog is drained or you quit.',
|
|
26
|
-
output: [
|
|
123
|
+
output: [
|
|
124
|
+
{ name: 'drained', type: 'boolean', required: true, constraint: 'True once the loop returns.' },
|
|
125
|
+
{ name: 'delivered', type: 'integer', required: true, constraint: 'How many answered-but-undelivered interactions were delivered back to their askers and reaped.' },
|
|
126
|
+
],
|
|
27
127
|
outputKind: 'object',
|
|
28
|
-
effects: [
|
|
128
|
+
effects: [
|
|
129
|
+
'Resolves pending interactions in the per-project interactions root via the TUI.',
|
|
130
|
+
'Delivers any answered-but-undelivered interaction back to its asking node and reaps the bridge node (the deliver-back the detached _run worker would have done).',
|
|
131
|
+
],
|
|
29
132
|
},
|
|
30
133
|
run: async () => {
|
|
31
134
|
await inbox([interactionsRoot(process.cwd())]);
|
|
32
|
-
|
|
135
|
+
// humanloop's inbox() writes response.json but never calls pushFinal — so a
|
|
136
|
+
// deck drained here would strand (no answer-back, leaked bridge node). Heal
|
|
137
|
+
// every resolved-but-live interaction: deliver its answer to the asker + reap.
|
|
138
|
+
const delivered = await healStrandedInteractions(process.cwd());
|
|
139
|
+
return { drained: true, delivered };
|
|
33
140
|
},
|
|
34
141
|
});
|
|
35
142
|
// ---------------------------------------------------------------------------
|
|
@@ -9,21 +9,44 @@ import { test } from 'node:test';
|
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
10
|
import { lintSubstrateSchema } from '../lint.js';
|
|
11
11
|
const ROUTING = 'When you are X, this reference should be read because Y';
|
|
12
|
+
const RUNGS = { 'system-prompt-visibility': 'name', 'file-read-visibility': 'none' };
|
|
12
13
|
test('lint rejects a doc still carrying the retired `when` key', () => {
|
|
13
|
-
const err = lintSubstrateSchema({ kind: '
|
|
14
|
+
const err = lintSubstrateSchema({ kind: 'knowledge', when: 'when X' });
|
|
14
15
|
assert.ok(err !== null, 'old-shape `when` must produce a finding');
|
|
15
16
|
assert.match(err, /when-and-why-to-read/, 'the message points at the new field');
|
|
16
17
|
});
|
|
17
18
|
test('lint rejects a doc still carrying the retired `why` key', () => {
|
|
18
|
-
const err = lintSubstrateSchema({ kind: '
|
|
19
|
+
const err = lintSubstrateSchema({ kind: 'knowledge', why: 'because Y' });
|
|
19
20
|
assert.ok(err !== null, 'old-shape `why` must produce a finding');
|
|
20
21
|
assert.match(err, /when-and-why-to-read/, 'the message points at the new field');
|
|
21
22
|
});
|
|
22
23
|
test('lint rejects a substrate doc missing the merged routing field', () => {
|
|
23
|
-
const err = lintSubstrateSchema({ kind: '
|
|
24
|
+
const err = lintSubstrateSchema({ kind: 'knowledge' });
|
|
24
25
|
assert.ok(err !== null, 'a substrate doc must carry when-and-why-to-read');
|
|
25
26
|
assert.match(err, /when-and-why-to-read/);
|
|
26
27
|
});
|
|
27
28
|
test('lint accepts the merged new-shape frontmatter', () => {
|
|
28
|
-
assert.equal(lintSubstrateSchema({ kind: '
|
|
29
|
+
assert.equal(lintSubstrateSchema({ kind: 'knowledge', 'when-and-why-to-read': ROUTING, ...RUNGS }), null);
|
|
30
|
+
});
|
|
31
|
+
// Visibility is a required, case-by-case authoring call — there is no kind
|
|
32
|
+
// default. lint must reject a doc that omits either rung (the runtime parser
|
|
33
|
+
// silently floors a missing rung to `none`, which is exactly the tolerance this
|
|
34
|
+
// gate exists to catch at authoring time).
|
|
35
|
+
test('lint rejects a substrate doc missing system-prompt-visibility', () => {
|
|
36
|
+
const err = lintSubstrateSchema({
|
|
37
|
+
kind: 'knowledge',
|
|
38
|
+
'when-and-why-to-read': ROUTING,
|
|
39
|
+
'file-read-visibility': 'none',
|
|
40
|
+
});
|
|
41
|
+
assert.ok(err !== null, 'a missing rung must produce a finding');
|
|
42
|
+
assert.match(err, /missing system-prompt-visibility/);
|
|
43
|
+
});
|
|
44
|
+
test('lint rejects a substrate doc missing file-read-visibility', () => {
|
|
45
|
+
const err = lintSubstrateSchema({
|
|
46
|
+
kind: 'knowledge',
|
|
47
|
+
'when-and-why-to-read': ROUTING,
|
|
48
|
+
'system-prompt-visibility': 'name',
|
|
49
|
+
});
|
|
50
|
+
assert.ok(err !== null, 'a missing rung must produce a finding');
|
|
51
|
+
assert.match(err, /missing file-read-visibility/);
|
|
29
52
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/** Schema checks for a doc living in a substrate memory dir: a memory store
|
|
2
2
|
* holds ONLY substrate docs, so a missing/invalid `kind` is an authoring
|
|
3
|
-
* error here (elsewhere it just means "not a substrate doc").
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* error here (elsewhere it just means "not a substrate doc"). Both rungs are
|
|
4
|
+
* REQUIRED — there is no kind default to lean on, so a missing rung is an
|
|
5
|
+
* authoring error. Rung and gate values are checked RAW — the runtime parser
|
|
6
|
+
* silently falls back to the neutral floor / inert gates, which is exactly the
|
|
7
|
+
* silent tolerance this lint exists to catch at authoring time. */
|
|
7
8
|
export declare function lintSubstrateSchema(fm: Record<string, unknown> | null): string | null;
|
|
8
9
|
export declare const lintLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -17,15 +17,16 @@ const VALID_RUNGS = [...RUNGS, 'always'];
|
|
|
17
17
|
const RUNG_FIELDS = ['system-prompt-visibility', 'file-read-visibility'];
|
|
18
18
|
/** Schema checks for a doc living in a substrate memory dir: a memory store
|
|
19
19
|
* holds ONLY substrate docs, so a missing/invalid `kind` is an authoring
|
|
20
|
-
* error here (elsewhere it just means "not a substrate doc").
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* error here (elsewhere it just means "not a substrate doc"). Both rungs are
|
|
21
|
+
* REQUIRED — there is no kind default to lean on, so a missing rung is an
|
|
22
|
+
* authoring error. Rung and gate values are checked RAW — the runtime parser
|
|
23
|
+
* silently falls back to the neutral floor / inert gates, which is exactly the
|
|
24
|
+
* silent tolerance this lint exists to catch at authoring time. */
|
|
24
25
|
export function lintSubstrateSchema(fm) {
|
|
25
26
|
if (fm === null)
|
|
26
|
-
return 'missing frontmatter: a memory store doc requires `kind:
|
|
27
|
+
return 'missing frontmatter: a memory store doc requires `kind: knowledge|preference`';
|
|
27
28
|
if (!isDocKind(fm.kind)) {
|
|
28
|
-
return `invalid kind: ${JSON.stringify(fm.kind)} (expected
|
|
29
|
+
return `invalid kind: ${JSON.stringify(fm.kind)} (expected knowledge|preference)`;
|
|
29
30
|
}
|
|
30
31
|
// The retired `when`/`why` pair was merged into one read-routing field. The
|
|
31
32
|
// hard cut is enforced HERE: an old-shape doc must fail, never be silently
|
|
@@ -38,7 +39,10 @@ export function lintSubstrateSchema(fm) {
|
|
|
38
39
|
}
|
|
39
40
|
for (const field of RUNG_FIELDS) {
|
|
40
41
|
const v = fm[field];
|
|
41
|
-
if (v
|
|
42
|
+
if (v === undefined) {
|
|
43
|
+
return `missing ${field}: choose a rung explicitly (${RUNGS.join('|')}) — there is no kind default`;
|
|
44
|
+
}
|
|
45
|
+
if (typeof v !== 'string' || !VALID_RUNGS.includes(v)) {
|
|
42
46
|
return `invalid ${field}: ${JSON.stringify(v)} (expected ${RUNGS.join('|')})`;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
@@ -52,6 +56,18 @@ export function lintSubstrateSchema(fm) {
|
|
|
52
56
|
!(Array.isArray(appliesTo) && appliesTo.every((g) => typeof g === 'string'))) {
|
|
53
57
|
return `invalid applies-to: ${JSON.stringify(appliesTo)} (expected a glob or glob list)`;
|
|
54
58
|
}
|
|
59
|
+
// read-when (Stream A on-read frontmatter trigger): same well-formed-object
|
|
60
|
+
// contract as gate — a non-object is inert (never fires), so catch it here.
|
|
61
|
+
const readWhen = fm['read-when'];
|
|
62
|
+
if (readWhen !== undefined && (readWhen === null || typeof readWhen !== 'object' || Array.isArray(readWhen))) {
|
|
63
|
+
return `invalid read-when: ${JSON.stringify(readWhen)} (expected a field→matcher object)`;
|
|
64
|
+
}
|
|
65
|
+
// A dead on-read trigger: an explicit applies-to/read-when with nothing to
|
|
66
|
+
// surface (file-read-visibility none) can never fire — flag it loudly rather
|
|
67
|
+
// than store a silent no-op.
|
|
68
|
+
if (fm['file-read-visibility'] === 'none' && (appliesTo !== undefined || readWhen !== undefined)) {
|
|
69
|
+
return 'dead on-read trigger: applies-to/read-when is set but file-read-visibility is `none` — raise the rung or drop the trigger';
|
|
70
|
+
}
|
|
55
71
|
return null;
|
|
56
72
|
}
|
|
57
73
|
/** Strict-parse one file; push a finding on a YAML error, then run the
|
|
@@ -18,7 +18,7 @@ export const readLeaf = defineLeaf({
|
|
|
18
18
|
],
|
|
19
19
|
output: [
|
|
20
20
|
{ name: 'name', type: 'string', required: true, constraint: 'Resolved document name.' },
|
|
21
|
-
{ name: 'kind', type: 'string', required: true, constraint: 'Resolved kind:
|
|
21
|
+
{ name: 'kind', type: 'string', required: true, constraint: 'Resolved kind: knowledge or preference.' },
|
|
22
22
|
{ name: 'scope', type: 'string', required: true, constraint: 'Scope the document was resolved from: project, user, or builtin.' },
|
|
23
23
|
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the document on disk.' },
|
|
24
24
|
{ name: 'content', type: 'string', required: true, constraint: 'Document body. Frontmatter stripped unless --frontmatter is set.' },
|
|
@@ -48,7 +48,7 @@ export const readLeaf = defineLeaf({
|
|
|
48
48
|
? sub.kind
|
|
49
49
|
: typeof doc.frontmatter?.['kind'] === 'string'
|
|
50
50
|
? doc.frontmatter['kind']
|
|
51
|
-
: '
|
|
51
|
+
: 'knowledge';
|
|
52
52
|
// --kind asserts the resolved kind; a mismatch falls through to not-found.
|
|
53
53
|
if (kindFilter === undefined || kind === kindFilter) {
|
|
54
54
|
const content = includeFrontmatter ? readText(doc.path) : doc.body;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Scope } from '../../types.js';
|
|
2
|
-
export declare const MEMORY_KINDS: readonly ["
|
|
2
|
+
export declare const MEMORY_KINDS: readonly ["knowledge", "preference"];
|
|
3
3
|
export declare const VISIBILITY_RUNGS: readonly ["none", "name", "preview", "content"];
|
|
4
4
|
export declare const MEMORY_SCOPES: readonly ["user", "project"];
|
|
5
5
|
/** Scope sort weight matching resolution precedence (project > user > builtin).
|
|
@@ -17,6 +17,12 @@ export declare function resolveWriteTarget(scopeArg: string | undefined): {
|
|
|
17
17
|
* memory dir, guarding against traversal/absolute escapes. */
|
|
18
18
|
export declare function memoryFilePath(memoryDir: string, name: string): string;
|
|
19
19
|
export declare function coerceGate(raw: string): Record<string, unknown>;
|
|
20
|
+
/** Coerce a `--read-when` string into a predicate tree. Like `--gate`, the
|
|
21
|
+
* read-when field MUST be a YAML/JSON object (the field→matcher map the schema
|
|
22
|
+
* expects, evaluated against a read file's own frontmatter). A scalar/array
|
|
23
|
+
* read-when is inert (never matches), so passing one is always a mistake and is
|
|
24
|
+
* caught at authoring time rather than stored. */
|
|
25
|
+
export declare function coerceReadWhen(raw: string): Record<string, unknown>;
|
|
20
26
|
/** Coerce a `--applies-to` string to the schema's glob form: a comma-separated
|
|
21
27
|
* list becomes an array, a single glob stays a string. */
|
|
22
28
|
export declare function coerceAppliesTo(raw: string): unknown;
|
|
@@ -7,9 +7,10 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
|
|
8
8
|
import { usage } from '../../core/errors.js';
|
|
9
9
|
import { scopeMemoryDir, projectScopeRoot, ensureProjectScopeRoot, } from '../../core/scope.js';
|
|
10
|
-
// The
|
|
11
|
-
//
|
|
12
|
-
|
|
10
|
+
// The two memory kinds — knowledge (consult: procedural playbooks + factual
|
|
11
|
+
// references merged) vs preference (behave: standing directives). Used as the
|
|
12
|
+
// `--kind` enum choices everywhere.
|
|
13
|
+
export const MEMORY_KINDS = ['knowledge', 'preference'];
|
|
13
14
|
// Visibility rungs — how much of a document surfaces (none → name → preview →
|
|
14
15
|
// content). Shared by --system-prompt-visibility and --file-read-visibility.
|
|
15
16
|
export const VISIBILITY_RUNGS = ['none', 'name', 'preview', 'content'];
|
|
@@ -76,6 +77,19 @@ export function coerceGate(raw) {
|
|
|
76
77
|
}
|
|
77
78
|
return result;
|
|
78
79
|
}
|
|
80
|
+
/** Coerce a `--read-when` string into a predicate tree. Like `--gate`, the
|
|
81
|
+
* read-when field MUST be a YAML/JSON object (the field→matcher map the schema
|
|
82
|
+
* expects, evaluated against a read file's own frontmatter). A scalar/array
|
|
83
|
+
* read-when is inert (never matches), so passing one is always a mistake and is
|
|
84
|
+
* caught at authoring time rather than stored. */
|
|
85
|
+
export function coerceReadWhen(raw) {
|
|
86
|
+
const result = parseYamlObject(raw);
|
|
87
|
+
if (typeof result === 'string') {
|
|
88
|
+
throw usage(`--read-when must be a YAML/JSON object (field→matcher map over a read file's frontmatter): ${result}. ` +
|
|
89
|
+
`Example: --read-when '{tags: {contains: security}}'`);
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
79
93
|
/** Coerce a `--applies-to` string to the schema's glob form: a comma-separated
|
|
80
94
|
* list becomes an array, a single glob stays a string. */
|
|
81
95
|
export function coerceAppliesTo(raw) {
|
|
@@ -97,6 +111,7 @@ const FRONTMATTER_ORDER = [
|
|
|
97
111
|
'file-read-visibility',
|
|
98
112
|
'gate',
|
|
99
113
|
'applies-to',
|
|
114
|
+
'read-when',
|
|
100
115
|
];
|
|
101
116
|
/** Serialize a substrate frontmatter record + body into a complete `.md`
|
|
102
117
|
* document. Frontmatter is emitted as a `---` fenced YAML block (the `yaml`
|
|
@@ -2,18 +2,18 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
2
2
|
import { usage } from '../../core/errors.js';
|
|
3
3
|
import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
4
4
|
import { readText, writeText, pathExists } from '../../core/fs-utils.js';
|
|
5
|
-
import { MEMORY_KINDS, MEMORY_SCOPES, VISIBILITY_RUNGS, resolveWriteTarget, memoryFilePath, coerceGate, coerceAppliesTo, serializeMemoryDoc, } from './shared.js';
|
|
5
|
+
import { MEMORY_KINDS, MEMORY_SCOPES, VISIBILITY_RUNGS, resolveWriteTarget, memoryFilePath, coerceGate, coerceAppliesTo, coerceReadWhen, serializeMemoryDoc, } from './shared.js';
|
|
6
6
|
export const writeLeaf = defineLeaf({
|
|
7
7
|
name: 'write',
|
|
8
8
|
description: 'create or update a memory document',
|
|
9
|
-
whenToUse: 'you are recording a new
|
|
9
|
+
whenToUse: 'you are recording a new knowledge document or preference — or revising one that already exists. Writes memory/<name>.md at the resolved scope from the frontmatter flags plus a body piped on stdin. Identity is path-derived: if <name> already exists at the scope it is updated in place, otherwise it is created.',
|
|
10
10
|
help: {
|
|
11
11
|
name: 'memory write',
|
|
12
12
|
summary: 'create or update memory/<name>.md at the resolved scope from frontmatter flags + a stdin body',
|
|
13
13
|
guide: 'The body is the easy part; the craft is ROUTING — every frontmatter flag decides who sees this doc, when, and at what context cost. Each rung up is paid by every future agent at every boot or read, forever, so default each rung DOWN.\n\n' +
|
|
14
|
-
'Pick the kind.
|
|
15
|
-
'Set the rungs (
|
|
16
|
-
'Choose the hook \u2014 boot vs file-read. There are exactly two moments a doc can surface. Behavior and procedure
|
|
14
|
+
'Pick the kind. knowledge = anything you CONSULT \u2014 how to DO something (a repeatable procedure/playbook) OR what is TRUE / how something WORKS (a fact about the user, a system\u2019s behavior, code docs); preference = how to BEHAVE (a directive, a standing correction). The test that splits them: does it DIRECT behavior ("always lint after authoring" \u2192 preference) or INFORM what you know ("Silas likes chicken", "the daemon never reloads dist/" \u2192 knowledge)? A correction yields a preference; a learned fact or a repeatable procedure yields knowledge.\n\n' +
|
|
15
|
+
'Set the rungs \u2014 BOTH --system-prompt-visibility (boot) and --file-read-visibility (on-read) are REQUIRED on create. There is no default: the right rung is a case-by-case call, never a function of kind. The ladder, lowest to highest: `none` for niche docs almost nothing should pull into context; `name` for the common case \u2014 references and skills that are relatively uncommon but an agent (or the user, by name) may reach for; `preview` for docs important enough that their routing line is worth its token cost every session (preferences usually land here); `content` (full body injected) for a doc that would be `preview` except its body is already so short you may as well inline it whole. `content` is rare \u2014 when a `content` doc grows past a bullet or two, downgrade it to `preview`. Each axis is independent: usually one carries a real rung and the other is `none` (see the hook paragraph next).\n\n' +
|
|
16
|
+
'Choose the hook \u2014 boot vs file-read. There are exactly two moments a doc can surface. Behavior (preferences) and how-to procedure are relevant whatever file is open \u2192 surface at boot. Knowledge about code belongs NEXT TO the code: put the file in that directory\u2019s .crouter/memory/ and it fires positionally when files there are read, costing nothing at boot. The exception that matters: a knowledge doc about a PERSON or PROCESS has no code directory to anchor to, so on-read triggering is meaningless \u2014 set --system-prompt-visibility preview so its routing line surfaces at boot instead.\n\n' +
|
|
17
17
|
'Write the routing line (--when-and-why-to-read) FIRST, before storing anything: "When <circumstance the agent is in>, this <kind> should be read because <what the read buys>." The test: can a stranger mid-task decide from that one line alone whether to spend the read? If you cannot name the concrete situation that triggers it, you do not yet understand the memory \u2014 ask the user ONE sharp question instead of improvising. ("Remember I like chicken" routes cleanly \u2192 food/meal decisions; "be careful with the API" does not \u2192 which API, careful how, against what failure?) And NEVER paraphrase the advice in the routing line \u2014 a preview that gives away the gist makes every future agent skip the real read. BAD: "\u2026because it carries the placement policy \u2014 -h first, memory only for dev-mode material." GOOD: "\u2026because it carries the standing placement policy."\n\n' +
|
|
18
18
|
'Find before write. `crtr memory find <topic>` first; grow ONE doc per recurring circumstance rather than minting near-duplicates \u2014 extend `food-preferences`, do not create `likes-chicken`. Group related docs with path names (area/topic). Do not store what is already recorded (code structure, git history, CLAUDE.md) or what only matters to this conversation.\n\n' +
|
|
19
19
|
'Body: write for a STRANGER \u2014 a future session that shares none of this conversation. State current truth, not the history of getting there (no "as discussed"). Keep the reasoning behind a rule and cut everything else; dense beats complete, since every line costs a mid-task reader.\n\n' +
|
|
@@ -23,10 +23,11 @@ export const writeLeaf = defineLeaf({
|
|
|
23
23
|
{ kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: true, constraint: 'Document kind.' },
|
|
24
24
|
{ kind: 'flag', name: 'when-and-why-to-read', type: 'string', required: false, constraint: 'The read-routing line, authored as ONE sentence: "When <circumstance>, this <kind> should be read <because <payoff>>." It states WHEN to read this doc and WHY the read is worth it — read-routing, NEVER a justification of why the content should be obeyed. Rendered verbatim as the preview. Required when creating.' },
|
|
25
25
|
{ kind: 'flag', name: 'short-form', type: 'string', required: false, constraint: 'Frontmatter short-form — a very abbreviated version of the content, the hook shown in `crtr memory list`.' },
|
|
26
|
-
{ kind: 'flag', name: 'system-prompt-visibility', type: 'enum', choices: [...VISIBILITY_RUNGS], required: false, constraint: 'Rung controlling how much of this document auto-loads into the system prompt / CLI help.' },
|
|
27
|
-
{ kind: 'flag', name: 'file-read-visibility', type: 'enum', choices: [...VISIBILITY_RUNGS], required: false, constraint: 'Rung controlling how much of this document surfaces when it is read off disk.' },
|
|
26
|
+
{ kind: 'flag', name: 'system-prompt-visibility', type: 'enum', choices: [...VISIBILITY_RUNGS], required: false, constraint: 'Rung controlling how much of this document auto-loads into the system prompt / CLI help. Required when creating — there is no kind default; pick a rung explicitly.' },
|
|
27
|
+
{ kind: 'flag', name: 'file-read-visibility', type: 'enum', choices: [...VISIBILITY_RUNGS], required: false, constraint: 'Rung controlling how much of this document surfaces when it is read off disk. Required when creating — there is no kind default; pick a rung explicitly.' },
|
|
28
28
|
{ kind: 'flag', name: 'gate', type: 'string', required: false, constraint: 'Frontmatter gate — expression/condition that determines when this document applies.' },
|
|
29
29
|
{ kind: 'flag', name: 'applies-to', type: 'string', required: false, constraint: 'Frontmatter applies-to — glob/path scope the document applies to.' },
|
|
30
|
+
{ kind: 'flag', name: 'read-when', type: 'string', required: false, constraint: 'Frontmatter read-when — a condition (field→matcher object) over a READ FILE’s own frontmatter that surfaces this doc on-read (a native rule). Same predicate vocabulary as --gate. Example: \'{tags: {contains: security}}\'.' },
|
|
30
31
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user.' },
|
|
31
32
|
{ kind: 'stdin', name: 'body', required: true, constraint: 'Document body (markdown, no frontmatter). Piped on stdin, or passed as the bare positional after <name>.' },
|
|
32
33
|
],
|
|
@@ -56,6 +57,15 @@ export const writeLeaf = defineLeaf({
|
|
|
56
57
|
if (created && input['whenAndWhyToRead'] === undefined) {
|
|
57
58
|
throw usage(`creating ${name} requires --when-and-why-to-read: one read-routing sentence "When <circumstance>, this ${kind} should be read <because <payoff>>." (rendered verbatim as the preview).`);
|
|
58
59
|
}
|
|
60
|
+
// CREATE requires BOTH visibility rungs as an explicit, case-by-case call —
|
|
61
|
+
// there is no kind default to lean on. UPDATE inherits whatever the existing
|
|
62
|
+
// doc already carries.
|
|
63
|
+
if (created && input['systemPromptVisibility'] === undefined) {
|
|
64
|
+
throw usage(`creating ${name} requires --system-prompt-visibility: pick a rung explicitly (none|name|preview|content) — there is no kind default. See \`crtr memory write -h\` for what each rung means.`);
|
|
65
|
+
}
|
|
66
|
+
if (created && input['fileReadVisibility'] === undefined) {
|
|
67
|
+
throw usage(`creating ${name} requires --file-read-visibility: pick a rung explicitly (none|name|preview|content) — there is no kind default. See \`crtr memory write -h\` for what each rung means.`);
|
|
68
|
+
}
|
|
59
69
|
// In-place update: start from the existing frontmatter (preserving fields
|
|
60
70
|
// not passed this time), then overlay the provided ones. Create: start clean.
|
|
61
71
|
const frontmatter = created
|
|
@@ -77,6 +87,9 @@ export const writeLeaf = defineLeaf({
|
|
|
77
87
|
if (input['appliesTo'] !== undefined) {
|
|
78
88
|
frontmatter['applies-to'] = coerceAppliesTo(input['appliesTo']);
|
|
79
89
|
}
|
|
90
|
+
if (input['readWhen'] !== undefined) {
|
|
91
|
+
frontmatter['read-when'] = coerceReadWhen(input['readWhen']);
|
|
92
|
+
}
|
|
80
93
|
writeText(path, serializeMemoryDoc(frontmatter, body));
|
|
81
94
|
return {
|
|
82
95
|
name,
|
package/dist/commands/memory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// `crtr memory` subtree — the document substrate (
|
|
1
|
+
// `crtr memory` subtree — the document substrate (knowledge and
|
|
2
2
|
// preferences) accessed via the CLI. Four flat leaves: list, read, find, write.
|
|
3
3
|
// SKELETON ONLY (task B1): the `-h` contracts here are final; the leaf handlers
|
|
4
4
|
// are stubs that B2 fills in. Mirrors `skill.ts` minus the loaded-skills
|
|
@@ -13,13 +13,13 @@ export function registerMemory() {
|
|
|
13
13
|
return defineBranch({
|
|
14
14
|
name: 'memory',
|
|
15
15
|
rootEntry: {
|
|
16
|
-
concept: 'a memory document you read on demand —
|
|
16
|
+
concept: 'a memory document you read on demand — knowledge or a preference',
|
|
17
17
|
desc: 'list, read, search, and write memory documents',
|
|
18
|
-
useWhen: 'a task matches
|
|
18
|
+
useWhen: 'a task matches stored knowledge or a preference — read it before improvising. `crtr memory read <name>` loads one by name; `crtr memory list` browses the inventory; `crtr memory find` searches by topic when you do not yet know the name. Names are path-derived crtr identifiers, not file paths — never cat or find the markdown off disk.',
|
|
19
19
|
},
|
|
20
20
|
help: {
|
|
21
21
|
name: 'memory',
|
|
22
|
-
summary: 'list, read, search, and write memory documents —
|
|
22
|
+
summary: 'list, read, search, and write memory documents — knowledge and preferences',
|
|
23
23
|
model: '`list` for a human inventory of what is stored — one line per document, the only surface that shows short-form. `read` (leaf) loads one document body by name, resolved project > user > builtin with leaf-name fallback; --frontmatter keeps the YAML header. `find` when you do not yet know which document applies — it ranks by relevance over name/when/why/short-form, --body to also weigh bodies, --grep for an exact regex over bodies. `write` creates or updates memory/<name>.md at a scope from frontmatter flags + a body piped on stdin. `lint` strict-parses the whole bounded corpus and fails on any invalid frontmatter — run it after authoring. A directory may carry an `INDEX.md` with the same frontmatter schema as any doc; the dir then renders as one entry at the INDEX\'s rung, and that rung is a ceiling for its whole subtree (`none` hides the dir) — when a doc mysteriously is not surfacing, check its ancestors\' INDEX rungs and its gate. Append `-h` at any leaf for its full schema, and `crtr memory write -h` for the authoring guide.',
|
|
24
24
|
},
|
|
25
25
|
children: [listLeaf, readLeaf, findLeaf, writeLeaf, lintLeaf],
|
package/dist/commands/node.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare function childFollowUp(spawnerId: string | undefined): string;
|
|
|
7
7
|
/** The live node "in front of you" in a tmux pane, HOST-AGNOSTIC. Defaults to
|
|
8
8
|
* $TMUX_PANE / the caller's current pane when `pane` is omitted — shared by
|
|
9
9
|
* `node recycle` / `node demote` / `node lifecycle` / `node close` / `node
|
|
10
|
-
* cycle`, and the `canvas chord`
|
|
10
|
+
* cycle`, and the `canvas chord` leaf.
|
|
11
11
|
*
|
|
12
12
|
* Two resolutions, tried in order:
|
|
13
13
|
* 1. VIEWER pane (broker host): a `crtr attach` viewer self-tags its pane with
|