@crouton-kit/crouter 0.3.28 → 0.3.30
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.d.ts +15 -7
- package/dist/build-root.js +48 -34
- 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 +5 -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 +22 -17
- 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__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- 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 +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- 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 +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -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 +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- 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 +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- 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 +4 -25
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- 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 +8 -4
- 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/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -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__/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 +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -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__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/listing-completeness.test.js +2 -2
- 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.js +2 -2
- 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.js +8 -7
- 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.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- 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 -335
- package/dist/core/runtime/placement.js +291 -858
- 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 +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- 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 -52
- package/dist/core/runtime/tmux.js +35 -99
- 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/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- 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 +10 -3
- package/dist/core/substrate/schema.js +6 -3
- 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 +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- 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 +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- 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 -297
- 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__/slash-quit-copy.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
|
@@ -10,5 +10,15 @@ export interface CloseNodeResult {
|
|
|
10
10
|
}
|
|
11
11
|
/** Close `rootId` and its exclusive subtree. Best-effort throughout: a tmux/db
|
|
12
12
|
* failure on one node never aborts the cascade. Throws only on an unknown root
|
|
13
|
-
* so the command can surface a clean not-found error.
|
|
14
|
-
|
|
13
|
+
* so the command can surface a clean not-found error.
|
|
14
|
+
*
|
|
15
|
+
* `rootEvent` decides the ROOT's terminal status (descendants are always
|
|
16
|
+
* `cancel`led — they did not finish their own work, they were torn down with the
|
|
17
|
+
* parent):
|
|
18
|
+
* 'cancel' (default) — the node was abandoned/torn down → canceled (⊜).
|
|
19
|
+
* 'finalize' — the user is marking it COMPLETE → done (✓). Used by
|
|
20
|
+
* the canvas browser's `x` (close-out) action. A root already in a terminal
|
|
21
|
+
* status keeps it (finalize is only legal from a live status). */
|
|
22
|
+
export declare function closeNode(rootId: string, opts?: {
|
|
23
|
+
rootEvent?: 'cancel' | 'finalize';
|
|
24
|
+
}): CloseNodeResult;
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
// only ever revives an active|idle node, so flipping to canceled first
|
|
13
13
|
// closes the race where the supervisor sees a window-gone live node and
|
|
14
14
|
// either revives it or marks it dead (overwriting our canceled).
|
|
15
|
-
// 2.
|
|
16
|
-
//
|
|
17
|
-
// the
|
|
18
|
-
//
|
|
15
|
+
// 2. Tear down the broker ENGINE (the `shutdown` frame → SIGTERM fallback so
|
|
16
|
+
// the broker process exits and releases the sole .jsonl writer; this also
|
|
17
|
+
// ends the inbox watcher) AND proactively close the node's on-screen viewer
|
|
18
|
+
// pane + registry row. The viewer teardown is explicit because attach
|
|
19
|
+
// auto-reconnects: left to the socket drop alone, the viewer would sit in a
|
|
20
|
+
// misleading "reconnecting…" state for ~30s on a deliberate close.
|
|
19
21
|
// 3. Append the cancellation notice to its inbox AFTER the watcher is gone.
|
|
20
22
|
// The watcher advances its cursor when it READS an entry, so appending
|
|
21
23
|
// while it is still live would let it consume + skip the notice (cursor
|
|
@@ -30,7 +32,8 @@
|
|
|
30
32
|
// to by the agent being closed", generalized to any depth via a fixpoint.
|
|
31
33
|
import { getNode, subscriptionsOf, subscribersOf, } from '../canvas/index.js';
|
|
32
34
|
import { transition } from './lifecycle.js';
|
|
33
|
-
import {
|
|
35
|
+
import { headlessBrokerHost } from './host.js';
|
|
36
|
+
import { tearDownNode } from './placement.js';
|
|
34
37
|
import { appendInbox } from '../feed/inbox.js';
|
|
35
38
|
import { appendPassive } from '../feed/passive.js';
|
|
36
39
|
/** The set of nodes to close: the root plus every descendant reachable down the
|
|
@@ -102,10 +105,19 @@ function cancellationLabel(isRoot, deadChildren) {
|
|
|
102
105
|
}
|
|
103
106
|
/** Close `rootId` and its exclusive subtree. Best-effort throughout: a tmux/db
|
|
104
107
|
* failure on one node never aborts the cascade. Throws only on an unknown root
|
|
105
|
-
* so the command can surface a clean not-found error.
|
|
106
|
-
|
|
108
|
+
* so the command can surface a clean not-found error.
|
|
109
|
+
*
|
|
110
|
+
* `rootEvent` decides the ROOT's terminal status (descendants are always
|
|
111
|
+
* `cancel`led — they did not finish their own work, they were torn down with the
|
|
112
|
+
* parent):
|
|
113
|
+
* 'cancel' (default) — the node was abandoned/torn down → canceled (⊜).
|
|
114
|
+
* 'finalize' — the user is marking it COMPLETE → done (✓). Used by
|
|
115
|
+
* the canvas browser's `x` (close-out) action. A root already in a terminal
|
|
116
|
+
* status keeps it (finalize is only legal from a live status). */
|
|
117
|
+
export function closeNode(rootId, opts = {}) {
|
|
107
118
|
if (getNode(rootId) === null)
|
|
108
119
|
throw new Error(`closeNode: unknown node ${rootId}`);
|
|
120
|
+
const rootEvent = opts.rootEvent ?? 'cancel';
|
|
109
121
|
const closing = closingSet(rootId);
|
|
110
122
|
const order = killOrder(rootId, closing);
|
|
111
123
|
// Descendants reachable from the subtree but kept alive (shared managers).
|
|
@@ -127,15 +139,24 @@ export function closeNode(rootId) {
|
|
|
127
139
|
const deadChildren = subscriptionsOf(id)
|
|
128
140
|
.map((s) => s.node_id)
|
|
129
141
|
.filter((c) => closing.has(c));
|
|
130
|
-
// 1)
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
// 1) Terminal status set BEFORE the window dies (daemon race). The root may
|
|
143
|
+
// finalize to `done` (a deliberate "mark complete" close-out); every
|
|
144
|
+
// descendant, and the root by default, `cancel`s. finalize is legal only
|
|
145
|
+
// from a live status — an already-terminal root keeps its status.
|
|
146
|
+
if (id === rootId && rootEvent === 'finalize') {
|
|
147
|
+
if (m.status === 'active' || m.status === 'idle')
|
|
148
|
+
transition(id, 'finalize');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
transition(id, 'cancel');
|
|
152
|
+
}
|
|
153
|
+
// 2) Tear the node's ENGINE down: send the `shutdown` frame so the broker
|
|
154
|
+
// PROCESS exits and releases the sole .jsonl writer. Then proactively
|
|
155
|
+
// close the viewer pane + registry row — attach auto-reconnects, so on a
|
|
156
|
+
// deliberate close the viewer must be torn down here or it lingers ~30s
|
|
157
|
+
// showing a misleading "reconnecting…" instead of going away at once.
|
|
158
|
+
headlessBrokerHost.teardown(id);
|
|
159
|
+
tearDownNode(id);
|
|
139
160
|
// 3) Leave the resume notice AFTER the watcher is gone, so it survives.
|
|
140
161
|
appendInbox(id, {
|
|
141
162
|
from: null,
|
|
@@ -23,17 +23,15 @@ function isDir(p) {
|
|
|
23
23
|
}
|
|
24
24
|
/** The `--`flags the front door OWNS (everything else after a bare `crtr` is a
|
|
25
25
|
* positional dir/prompt). A leading token in this set still boots a root —
|
|
26
|
-
* without it, `crtr --
|
|
27
|
-
*
|
|
28
|
-
const FRONT_DOOR_FLAGS = new Set(['--name', '--kind'
|
|
26
|
+
* without it, `crtr --name X` would fall through to the dispatcher and error as
|
|
27
|
+
* an unknown subcommand. */
|
|
28
|
+
const FRONT_DOOR_FLAGS = new Set(['--name', '--kind']);
|
|
29
29
|
/** Parse `[dir] [prompt]` positionals + the front-door flags out of the leftover
|
|
30
|
-
* tokens after the bare `crtr`.
|
|
31
|
-
* explicit `--headless`/`--no-headless`, `undefined` to defer to the config. */
|
|
30
|
+
* tokens after the bare `crtr`. */
|
|
32
31
|
function parseRootArgs(tokens) {
|
|
33
32
|
let cwd = process.cwd();
|
|
34
33
|
let name;
|
|
35
34
|
let kind;
|
|
36
|
-
let headless;
|
|
37
35
|
const positionals = [];
|
|
38
36
|
for (let i = 0; i < tokens.length; i++) {
|
|
39
37
|
const t = tokens[i];
|
|
@@ -43,12 +41,6 @@ function parseRootArgs(tokens) {
|
|
|
43
41
|
else if (t === '--kind') {
|
|
44
42
|
kind = tokens[++i];
|
|
45
43
|
}
|
|
46
|
-
else if (t === '--headless') {
|
|
47
|
-
headless = true;
|
|
48
|
-
}
|
|
49
|
-
else if (t === '--no-headless') {
|
|
50
|
-
headless = false;
|
|
51
|
-
}
|
|
52
44
|
else if (t.startsWith('--')) {
|
|
53
45
|
// ignore unknown flags for the front door
|
|
54
46
|
}
|
|
@@ -61,7 +53,7 @@ function parseRootArgs(tokens) {
|
|
|
61
53
|
cwd = resolvePath(positionals.shift());
|
|
62
54
|
}
|
|
63
55
|
const prompt = positionals.length > 0 ? positionals.join(' ') : undefined;
|
|
64
|
-
return { cwd, prompt, name, kind
|
|
56
|
+
return { cwd, prompt, name, kind };
|
|
65
57
|
}
|
|
66
58
|
/** Env marker set on every pi the front door boots. Its presence means we are
|
|
67
59
|
* already inside a front-door-booted root, so a nested front-door launch must
|
|
@@ -92,7 +84,7 @@ export function maybeBootRoot(root, argv) {
|
|
|
92
84
|
// • bare `crtr` (no tokens)
|
|
93
85
|
// • `crtr <dir> [prompt]` (first positional is an existing dir)
|
|
94
86
|
// • `crtr "multi word prompt"` (first token contains whitespace)
|
|
95
|
-
// • `crtr --
|
|
87
|
+
// • `crtr --name X` (a leading front-door flag)
|
|
96
88
|
// Anything else — a bare word like `job`, or an UNKNOWN leading flag — is
|
|
97
89
|
// treated as a mistyped/removed subcommand and handed to the dispatcher, which
|
|
98
90
|
// errors with "unknown subcommand: <token>". Booting pi for such tokens is what
|
|
@@ -1,53 +1,33 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type NodeRow } from '../canvas/index.js';
|
|
2
2
|
import type { PiInvocation } from './launch.js';
|
|
3
|
-
/** View-side launch hints the
|
|
4
|
-
*
|
|
5
|
-
* `command = piCommand(inv.argv)` and `env = inv.env` from the PiInvocation
|
|
6
|
-
* itself. The broker (Phase 3) uses only `cwd` + `resuming`. */
|
|
3
|
+
/** View-side launch hints the broker host needs beyond the PiInvocation. The
|
|
4
|
+
* broker uses `cwd` + `resuming`; `name` is carried for the node's display. */
|
|
7
5
|
export interface LaunchPlacement {
|
|
8
6
|
cwd: string;
|
|
9
7
|
name: string;
|
|
10
8
|
resuming: boolean;
|
|
11
9
|
}
|
|
12
|
-
/** A handle to a node's running engine container.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* and reviveNode's unchanged ReviveResult is built from window+session. The
|
|
16
|
-
* daemon never reads window/session/pane (it supervises via pid / isAlive); the
|
|
17
|
-
* broker (Phase 3) returns window=null, pane=null. */
|
|
10
|
+
/** A handle to a node's running engine container. The broker host supervises via
|
|
11
|
+
* the broker pid recorded as pi_pid; no tmux placement coords exist (the engine
|
|
12
|
+
* is never in a pane). */
|
|
18
13
|
export interface HostHandle {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
* recordPid), and reviveNode clearPid()s right after launch — so no Phase-1
|
|
23
|
-
* caller reads this. Present for the §3.2 contract + the daemon. */
|
|
14
|
+
/** Supervised pid (signal-0 target). May be null right at launch before the
|
|
15
|
+
* detached child reports its pid; the broker re-records pi_pid during its
|
|
16
|
+
* extension bind, and reviveNode clearPid()s right after launch. */
|
|
24
17
|
pid: number | null;
|
|
25
|
-
window: string | null;
|
|
26
|
-
session: string;
|
|
27
|
-
pane: string | null;
|
|
28
18
|
}
|
|
29
19
|
export interface Host {
|
|
30
20
|
/** Bring a node's ENGINE into existence from its launch recipe; return a
|
|
31
|
-
* supervisable handle.
|
|
32
|
-
* engine container). */
|
|
21
|
+
* supervisable handle. */
|
|
33
22
|
launch(nodeId: string, inv: PiInvocation, opts: LaunchPlacement): HostHandle;
|
|
34
|
-
/** Is this node's engine
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* (passes a row) — stay one-token changes. */
|
|
23
|
+
/** Is this node's engine alive? PURE / non-mutating. `string | NodeRow` so
|
|
24
|
+
* both call sites — the revive guard (passes the id) and the daemon (passes a
|
|
25
|
+
* row) — share one selector. For the broker this IS isPidAlive(pi_pid). */
|
|
38
26
|
isAlive(node: string | NodeRow): boolean;
|
|
39
27
|
/** Tear the engine down (close/cancel teardown). */
|
|
40
28
|
teardown(nodeId: string): void;
|
|
41
29
|
/** Deliver an OS signal to the engine container — present so the daemon never
|
|
42
|
-
* reaches around the abstraction.
|
|
30
|
+
* reaches around the abstraction. */
|
|
43
31
|
signal(nodeId: string, sig: NodeJS.Signals): void;
|
|
44
32
|
}
|
|
45
|
-
/** The sole Phase-1 host: today's behavior, wrapped. Stateless → a frozen
|
|
46
|
-
* singleton, no class needed. Every method delegates verbatim. */
|
|
47
|
-
export declare const tmuxPaneHost: Host;
|
|
48
33
|
export declare const headlessBrokerHost: Host;
|
|
49
|
-
/** Select the Host for a node by its persisted `host_kind` (NULL/'tmux' → the
|
|
50
|
-
* tmux host; 'broker' → the headless broker host). The param is `NodeMeta |
|
|
51
|
-
* NodeRow` so both call sites — reviveNode `hostFor(meta)` and the daemon
|
|
52
|
-
* `hostFor(row)` — share one selector. */
|
|
53
|
-
export declare function hostFor(node: NodeMeta | NodeRow): Host;
|
|
@@ -1,58 +1,26 @@
|
|
|
1
1
|
// src/core/runtime/host.ts
|
|
2
2
|
//
|
|
3
3
|
// The Host abstraction (design §3): owns an engine PROCESS's lifecycle, not its
|
|
4
|
-
// display.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// display. After the broker-is-the-host cut there is ONE impl —
|
|
5
|
+
// HeadlessBrokerHost — a detached broker process with no tmux pane. Every node
|
|
6
|
+
// (managed child, named node, --root, front-door root) runs on it; a tmux pane
|
|
7
|
+
// is only an attach VIEWER. The `Host` interface is kept as a one-impl seam (the
|
|
8
|
+
// test/future-host boundary).
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// and tmux-chrome.ts are the only sanctioned tmux.ts importers. The broker host
|
|
13
|
-
// needs no tmux verb at all.
|
|
10
|
+
// This file imports neither './tmux.js' nor './placement.js' — the broker host
|
|
11
|
+
// needs no tmux verb at all (it supervises via the broker pid, tears down over
|
|
12
|
+
// the node's unix socket).
|
|
14
13
|
import { spawn } from 'node:child_process';
|
|
15
14
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, openSync, closeSync } from 'node:fs';
|
|
16
15
|
import { dirname, join } from 'node:path';
|
|
17
16
|
import { fileURLToPath } from 'node:url';
|
|
18
17
|
import { connect } from 'node:net';
|
|
19
|
-
import { reviveIntoPlacement, isNodePaneAlive, tearDownNode, piCommand, } from './placement.js';
|
|
20
18
|
import { getNode } from '../canvas/index.js';
|
|
21
19
|
import { nodeDir, jobDir } from '../canvas/paths.js';
|
|
22
20
|
import { encodeFrame } from './broker-protocol.js';
|
|
23
21
|
import { FRONT_DOOR_ENV } from './front-door.js';
|
|
22
|
+
import { hostExecPath } from './branded-host.js';
|
|
24
23
|
import { isPidAlive } from '../canvas/pid.js';
|
|
25
|
-
/** The sole Phase-1 host: today's behavior, wrapped. Stateless → a frozen
|
|
26
|
-
* singleton, no class needed. Every method delegates verbatim. */
|
|
27
|
-
export const tmuxPaneHost = {
|
|
28
|
-
launch(nodeId, inv, opts) {
|
|
29
|
-
const placed = reviveIntoPlacement(nodeId, {
|
|
30
|
-
command: piCommand(inv.argv),
|
|
31
|
-
env: inv.env,
|
|
32
|
-
cwd: opts.cwd,
|
|
33
|
-
name: opts.name,
|
|
34
|
-
resuming: opts.resuming,
|
|
35
|
-
});
|
|
36
|
-
return { kind: 'tmux', pid: null, window: placed.window, session: placed.session, pane: placed.pane };
|
|
37
|
-
},
|
|
38
|
-
isAlive(node) {
|
|
39
|
-
return isNodePaneAlive(node);
|
|
40
|
-
},
|
|
41
|
-
teardown(nodeId) {
|
|
42
|
-
tearDownNode(nodeId);
|
|
43
|
-
},
|
|
44
|
-
signal(nodeId, sig) {
|
|
45
|
-
const pid = getNode(nodeId)?.pi_pid;
|
|
46
|
-
if (pid != null) {
|
|
47
|
-
try {
|
|
48
|
-
process.kill(pid, sig);
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
/* already gone */
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
24
|
// ---------------------------------------------------------------------------
|
|
57
25
|
// HeadlessBrokerHost (design §3.3): a detached broker PROCESS, no tmux pane. It
|
|
58
26
|
// boots the dedicated broker-cli entry directly (NEVER src/cli.ts), supervises
|
|
@@ -84,7 +52,10 @@ export const headlessBrokerHost = {
|
|
|
84
52
|
const logDir = jobDir(nodeId);
|
|
85
53
|
mkdirSync(logDir, { recursive: true });
|
|
86
54
|
const logFd = openSync(join(logDir, 'broker.log'), 'a');
|
|
87
|
-
|
|
55
|
+
// Launch from the crouter-branded host binary (a copy of node) so the
|
|
56
|
+
// broker shows "crouter" in macOS Full Disk Access, not "node". On
|
|
57
|
+
// non-darwin / dev / tests this is just process.execPath (see branded-host).
|
|
58
|
+
const child = spawn(hostExecPath(), [resolveBrokerEntry(), nodeId], {
|
|
88
59
|
cwd: opts.cwd,
|
|
89
60
|
detached: true,
|
|
90
61
|
stdio: ['ignore', logFd, logFd],
|
|
@@ -94,7 +65,7 @@ export const headlessBrokerHost = {
|
|
|
94
65
|
// launching process (CLI or daemon) never leaks it.
|
|
95
66
|
closeSync(logFd);
|
|
96
67
|
child.unref();
|
|
97
|
-
return {
|
|
68
|
+
return { pid: child.pid ?? null };
|
|
98
69
|
},
|
|
99
70
|
isAlive(node) {
|
|
100
71
|
return isPidAlive((typeof node === 'string' ? getNode(node) : node)?.pi_pid);
|
|
@@ -177,10 +148,3 @@ export const headlessBrokerHost = {
|
|
|
177
148
|
}
|
|
178
149
|
},
|
|
179
150
|
};
|
|
180
|
-
/** Select the Host for a node by its persisted `host_kind` (NULL/'tmux' → the
|
|
181
|
-
* tmux host; 'broker' → the headless broker host). The param is `NodeMeta |
|
|
182
|
-
* NodeRow` so both call sites — reviveNode `hostFor(meta)` and the daemon
|
|
183
|
-
* `hostFor(row)` — share one selector. */
|
|
184
|
-
export function hostFor(node) {
|
|
185
|
-
return node.host_kind === 'broker' ? headlessBrokerHost : tmuxPaneHost;
|
|
186
|
-
}
|
|
@@ -2,6 +2,7 @@ import type { NodeMeta, LaunchSpec, Mode, Lifecycle } from '../canvas/index.js';
|
|
|
2
2
|
export declare const CANVAS_STOPHOOK_PATH: string;
|
|
3
3
|
export declare const CANVAS_INBOX_WATCHER_PATH: string;
|
|
4
4
|
export declare const CANVAS_NAV_PATH: string;
|
|
5
|
+
export declare const CANVAS_RECAP_PATH: string;
|
|
5
6
|
export declare const CANVAS_GOAL_CAPTURE_PATH: string;
|
|
6
7
|
export declare const CANVAS_PASSIVE_CONTEXT_PATH: string;
|
|
7
8
|
export declare const CANVAS_CONTEXT_INTRO_PATH: string;
|
|
@@ -11,12 +12,14 @@ export declare const CANVAS_RESUME_PATH: string;
|
|
|
11
12
|
export declare const CANVAS_VIEW_PATH: string;
|
|
12
13
|
/** The canvas extensions every node loads, in order: stophook (routing +
|
|
13
14
|
* telemetry + session-id capture), inbox-watcher (wake), nav (in-editor
|
|
14
|
-
* graph chrome),
|
|
15
|
+
* graph chrome), recap (the per-node inactivity recap card — Haiku over the
|
|
16
|
+
* conversation, shown above the manager line after 300s of no message),
|
|
17
|
+
* goal-capture (persist the first user message as the goal),
|
|
15
18
|
* passive-context (drain passive backlog as pre-text on the next message),
|
|
16
|
-
* context-intro (inject the <crtr-context> bearings block
|
|
17
|
-
* message, once per brand-new chat),
|
|
18
|
-
* substrate's two hooks: <
|
|
19
|
-
* on-read context injection), commands (the /promote slash-command), resume (the /resume-node
|
|
19
|
+
* context-intro (inject the <crtr-context> bearings block — carrying the
|
|
20
|
+
* <knowledge> catalog — as its own session message, once per brand-new chat),
|
|
21
|
+
* doc-substrate (the unified document substrate's two hooks: <preferences> +
|
|
22
|
+
* <memory-guidance> at boot, on-read context injection), commands (the /promote slash-command), resume (the /resume-node
|
|
20
23
|
* whole-canvas picker → `crtr node focus`), view (the /view popup → `crtr
|
|
21
24
|
* view pick` / `crtr view run <name>`).
|
|
22
25
|
* All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
|
|
@@ -27,9 +30,9 @@ export declare const CANVAS_EXTENSIONS: string[];
|
|
|
27
30
|
* resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
|
|
28
31
|
export declare const MODEL_TIERS: Record<string, string>;
|
|
29
32
|
/** Resolve a model token to the spec pi gets via `--model`. A named tier
|
|
30
|
-
* (ultra/strong/medium/light) maps to its concrete spec; a bare alias
|
|
31
|
-
* (sonnet/opus/haiku)
|
|
32
|
-
*
|
|
33
|
+
* (ultra/strong/medium/light) maps to its concrete spec; a bare family alias
|
|
34
|
+
* (sonnet/opus/haiku) maps to that family's current versioned id; anything
|
|
35
|
+
* with a `/` or an unknown name passes through. */
|
|
33
36
|
export declare function normalizeModel(model: string): string;
|
|
34
37
|
/** Compose a node's full pi launch recipe from its persona. The system prompt
|
|
35
38
|
* is composed from FOUR inputs: kind×mode (the persona body) plus lifecycle
|
|
@@ -105,5 +108,5 @@ export interface BrokerSdkConfig {
|
|
|
105
108
|
* never drift. Safe because `buildPiArgv` is the SOLE producer of these flags
|
|
106
109
|
* (we own both ends). SIDE EFFECT: merges `inv.env` into `process.env` so the
|
|
107
110
|
* in-process engine + the bound canvas extensions see the same env a forked pi
|
|
108
|
-
* would (CRTR_NODE_ID,
|
|
111
|
+
* would (CRTR_NODE_ID, CRTR_SUBTREE, …). */
|
|
109
112
|
export declare function piInvocationToSdkConfig(inv: PiInvocation): BrokerSdkConfig;
|
|
@@ -29,6 +29,7 @@ function resolveExtension(name) {
|
|
|
29
29
|
export const CANVAS_STOPHOOK_PATH = resolveExtension('canvas-stophook');
|
|
30
30
|
export const CANVAS_INBOX_WATCHER_PATH = resolveExtension('canvas-inbox-watcher');
|
|
31
31
|
export const CANVAS_NAV_PATH = resolveExtension('canvas-nav');
|
|
32
|
+
export const CANVAS_RECAP_PATH = resolveExtension('canvas-recap');
|
|
32
33
|
export const CANVAS_GOAL_CAPTURE_PATH = resolveExtension('canvas-goal-capture');
|
|
33
34
|
export const CANVAS_PASSIVE_CONTEXT_PATH = resolveExtension('canvas-passive-context');
|
|
34
35
|
export const CANVAS_CONTEXT_INTRO_PATH = resolveExtension('canvas-context-intro');
|
|
@@ -38,12 +39,14 @@ export const CANVAS_RESUME_PATH = resolveExtension('canvas-resume');
|
|
|
38
39
|
export const CANVAS_VIEW_PATH = resolveExtension('canvas-view');
|
|
39
40
|
/** The canvas extensions every node loads, in order: stophook (routing +
|
|
40
41
|
* telemetry + session-id capture), inbox-watcher (wake), nav (in-editor
|
|
41
|
-
* graph chrome),
|
|
42
|
+
* graph chrome), recap (the per-node inactivity recap card — Haiku over the
|
|
43
|
+
* conversation, shown above the manager line after 300s of no message),
|
|
44
|
+
* goal-capture (persist the first user message as the goal),
|
|
42
45
|
* passive-context (drain passive backlog as pre-text on the next message),
|
|
43
|
-
* context-intro (inject the <crtr-context> bearings block
|
|
44
|
-
* message, once per brand-new chat),
|
|
45
|
-
* substrate's two hooks: <
|
|
46
|
-
* on-read context injection), commands (the /promote slash-command), resume (the /resume-node
|
|
46
|
+
* context-intro (inject the <crtr-context> bearings block — carrying the
|
|
47
|
+
* <knowledge> catalog — as its own session message, once per brand-new chat),
|
|
48
|
+
* doc-substrate (the unified document substrate's two hooks: <preferences> +
|
|
49
|
+
* <memory-guidance> at boot, on-read context injection), commands (the /promote slash-command), resume (the /resume-node
|
|
47
50
|
* whole-canvas picker → `crtr node focus`), view (the /view popup → `crtr
|
|
48
51
|
* view pick` / `crtr view run <name>`).
|
|
49
52
|
* All self-gate on CRTR_NODE_ID. goal-capture precedes passive-context so it
|
|
@@ -52,6 +55,7 @@ export const CANVAS_EXTENSIONS = [
|
|
|
52
55
|
CANVAS_STOPHOOK_PATH,
|
|
53
56
|
CANVAS_INBOX_WATCHER_PATH,
|
|
54
57
|
CANVAS_NAV_PATH,
|
|
58
|
+
CANVAS_RECAP_PATH,
|
|
55
59
|
CANVAS_GOAL_CAPTURE_PATH,
|
|
56
60
|
CANVAS_PASSIVE_CONTEXT_PATH,
|
|
57
61
|
CANVAS_CONTEXT_INTRO_PATH,
|
|
@@ -65,20 +69,28 @@ export const CANVAS_EXTENSIONS = [
|
|
|
65
69
|
* resolve to the latest opus/sonnet/haiku; `ultra` to the frontier model. */
|
|
66
70
|
export const MODEL_TIERS = {
|
|
67
71
|
ultra: 'anthropic/claude-fable-5',
|
|
68
|
-
strong: 'anthropic/opus',
|
|
69
|
-
medium: 'anthropic/sonnet',
|
|
70
|
-
light: 'anthropic/haiku',
|
|
72
|
+
strong: 'anthropic/claude-opus-4-8',
|
|
73
|
+
medium: 'anthropic/claude-sonnet-4-6',
|
|
74
|
+
light: 'anthropic/claude-haiku-4-5',
|
|
75
|
+
};
|
|
76
|
+
/** Bare family aliases → the same concrete versioned ids as the tiers. These
|
|
77
|
+
* MUST be real registry ids (see `pi --list-models`): an unversioned spec like
|
|
78
|
+
* `anthropic/sonnet` is NOT in the registry and silently falls back to the SDK
|
|
79
|
+
* default (opus), so it can never be the resolution target. */
|
|
80
|
+
const BARE_ALIASES = {
|
|
81
|
+
opus: 'anthropic/claude-opus-4-8',
|
|
82
|
+
sonnet: 'anthropic/claude-sonnet-4-6',
|
|
83
|
+
haiku: 'anthropic/claude-haiku-4-5',
|
|
71
84
|
};
|
|
72
85
|
/** Resolve a model token to the spec pi gets via `--model`. A named tier
|
|
73
|
-
* (ultra/strong/medium/light) maps to its concrete spec; a bare alias
|
|
74
|
-
* (sonnet/opus/haiku)
|
|
75
|
-
*
|
|
86
|
+
* (ultra/strong/medium/light) maps to its concrete spec; a bare family alias
|
|
87
|
+
* (sonnet/opus/haiku) maps to that family's current versioned id; anything
|
|
88
|
+
* with a `/` or an unknown name passes through. */
|
|
76
89
|
export function normalizeModel(model) {
|
|
77
90
|
if (model in MODEL_TIERS)
|
|
78
91
|
return MODEL_TIERS[model];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return `anthropic/${model}`;
|
|
92
|
+
if (model in BARE_ALIASES)
|
|
93
|
+
return BARE_ALIASES[model];
|
|
82
94
|
return model;
|
|
83
95
|
}
|
|
84
96
|
// ---------------------------------------------------------------------------
|
|
@@ -188,7 +200,7 @@ export function buildPiArgv(meta, opts = {}) {
|
|
|
188
200
|
* never drift. Safe because `buildPiArgv` is the SOLE producer of these flags
|
|
189
201
|
* (we own both ends). SIDE EFFECT: merges `inv.env` into `process.env` so the
|
|
190
202
|
* in-process engine + the bound canvas extensions see the same env a forked pi
|
|
191
|
-
* would (CRTR_NODE_ID,
|
|
203
|
+
* would (CRTR_NODE_ID, CRTR_SUBTREE, …). */
|
|
192
204
|
export function piInvocationToSdkConfig(inv) {
|
|
193
205
|
for (const [k, v] of Object.entries(inv.env))
|
|
194
206
|
process.env[k] = v;
|
|
@@ -2,7 +2,7 @@ import type { NodeMeta } from '../canvas/types.js';
|
|
|
2
2
|
/** The lifecycle events — the only vocabulary for moving a node's status/intent.
|
|
3
3
|
* Each maps (in the table below) to a target status and/or intent plus the set
|
|
4
4
|
* of from-statuses it is legal from. */
|
|
5
|
-
export type LifecycleEvent = 'finalize' | 'cancel' | 'crash' | 'yield' | 'release' | 'revive'
|
|
5
|
+
export type LifecycleEvent = 'finalize' | 'cancel' | 'crash' | 'yield' | 'release' | 'revive';
|
|
6
6
|
/** Enact a lifecycle event on a node: validate the from-status against the
|
|
7
7
|
* table, then write status+intent in ONE atomic statement (so they can never
|
|
8
8
|
* disagree). Returns the hydrated node view after the write.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// lifecycle.ts is the single source of which status/intent move is LEGAL. Two
|
|
14
14
|
// parallel, legible state machines instead of scattered enactment.
|
|
15
15
|
//
|
|
16
|
-
// Crash-safety invariant (was a comment repeated in
|
|
16
|
+
// Crash-safety invariant (was a comment repeated in close/reapDescendants):
|
|
17
17
|
// "flip status to a non-supervised value + clear intent BEFORE killing the
|
|
18
18
|
// host" (tmux pane or headless broker) — the daemon only ever revives
|
|
19
19
|
// active|idle nodes, so a teardown must
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
// and only THEN tear the host down.
|
|
23
23
|
//
|
|
24
24
|
// Unification (A5, human-confirmed 2026-06-06): an externally-reaped node — torn
|
|
25
|
-
// down because the user moved on (close cascade) OR because a root
|
|
26
|
-
// superseded
|
|
27
|
-
// finished its OWN work (finalize)
|
|
28
|
-
// `
|
|
29
|
-
//
|
|
30
|
-
//
|
|
25
|
+
// down because the user moved on (close cascade) OR because a root relaunch
|
|
26
|
+
// superseded its workers — ends `canceled`, NOT `done`. `done` is reserved for a
|
|
27
|
+
// node that finished its OWN work (finalize) — including a relaunched root, which
|
|
28
|
+
// is parked `done` as history, not canceled. The old `reap` event (→ done) was
|
|
29
|
+
// identical to `cancel` in every field and side effect once unified on status, so
|
|
30
|
+
// it was COLLAPSED into `cancel`; reset.ts's reapDescendants routes through
|
|
31
|
+
// `cancel`.
|
|
31
32
|
//
|
|
32
33
|
// Layering note: lifecycle.ts is runtime, but it is the canvas write surface's
|
|
33
34
|
// `transition` verb (the only writer of status+intent), so it owns its atomic
|
|
@@ -42,11 +43,12 @@ const LIVE = ['active', 'idle'];
|
|
|
42
43
|
* the runtime actually wrote at its audited call sites, so behavior is preserved
|
|
43
44
|
* by construction. Each entry's comment names its writer(s). */
|
|
44
45
|
const TRANSITIONS = {
|
|
45
|
-
// feed.push(final) · queue.cancelJob · markCleanExitDone (clean quit)
|
|
46
|
+
// feed.push(final) · queue.cancelJob · markCleanExitDone (clean quit) ·
|
|
47
|
+
// relaunchRoot park-old (a relaunched root is parked `done` as history).
|
|
46
48
|
finalize: { status: 'done', intent: 'done', from: LIVE },
|
|
47
|
-
// closeNode cascade · reapDescendants
|
|
48
|
-
//
|
|
49
|
-
//
|
|
49
|
+
// closeNode cascade · reapDescendants. Forced teardown of a node that did NOT
|
|
50
|
+
// finish its own work → canceled, intent cleared. (A5: done is reserved for
|
|
51
|
+
// finalize; every external reap unifies on canceled.)
|
|
50
52
|
cancel: { status: 'canceled', intent: null, from: ANY },
|
|
51
53
|
// daemon superviseTick: engine container gone (tmux pane or broker process)
|
|
52
54
|
// with no yield/release intent. Intent KEPT (the dead log line still reports it).
|
|
@@ -57,11 +59,6 @@ const TRANSITIONS = {
|
|
|
57
59
|
release: { status: 'idle', intent: 'idle-release', from: LIVE },
|
|
58
60
|
// reviveNode · resetRoot · stophook boot-confirm (clear a pending refresh net).
|
|
59
61
|
revive: { status: 'active', intent: null, from: ANY },
|
|
60
|
-
// reviveInPlace: re-exec a fresh pi in the SAME pane. Status (re)affirmed
|
|
61
|
-
// active; intent KEPT so a pending refresh survives as proof-of-boot until the
|
|
62
|
-
// fresh pi's session_start clears it (a premature clear is how a failed
|
|
63
|
-
// respawn became a silent death — see revive.ts).
|
|
64
|
-
boot: { status: 'active', from: LIVE },
|
|
65
62
|
};
|
|
66
63
|
/** Enact a lifecycle event on a node: validate the from-status against the
|
|
67
64
|
* table, then write status+intent in ONE atomic statement (so they can never
|
|
@@ -96,9 +93,8 @@ export function transition(nodeId, event) {
|
|
|
96
93
|
// Event-gated; exactly ONE delegated wakeups helper per event, never inline
|
|
97
94
|
// wakeups SQL here (design §6.7 / D2 / D6, Q1/Q2):
|
|
98
95
|
// cancel → cancelWakesFor: reap node-anchored AND owner-armed detached
|
|
99
|
-
// wakes this node armed (ruling A) — covers
|
|
100
|
-
//
|
|
101
|
-
// park-old).
|
|
96
|
+
// wakes this node armed (ruling A) — covers both cancel sites
|
|
97
|
+
// uniformly (close cascade · reapDescendants).
|
|
102
98
|
// finalize → cancelSelfAlarms: clear this node's one-shot self-alarms only;
|
|
103
99
|
// node-anchored declarative crons SURVIVE a finishing instance (Q1).
|
|
104
100
|
// crash → NOTHING. A fault is not a deliberate end-of-waiting; self-alarms
|
|
@@ -7,58 +7,6 @@ export declare function newNodeId(): string;
|
|
|
7
7
|
* without an import cycle — `CRTR_SUBTREE` is emitted as `rootOfSpine(nodeId)`
|
|
8
8
|
* at every launch site. */
|
|
9
9
|
export declare function rootOfSpine(nodeId: string): string;
|
|
10
|
-
/** The single, shared tmux session that ALL canvas node windows live in.
|
|
11
|
-
* Overridable with CRTR_NODE_SESSION (default `crtr`). Every root and every
|
|
12
|
-
* child opens a window here rather than cluttering the user's own working
|
|
13
|
-
* session — switch to it to browse the whole live graph, ignore it otherwise.
|
|
14
|
-
* Pure policy (env only, no tmux call), so it lives in the node layer, not the
|
|
15
|
-
* driver; the tmux driver imports it from here for installMenuBinding's use. */
|
|
16
|
-
export declare function nodeSession(): string;
|
|
17
|
-
/** Resolve the tmux session a freshly-born node's window/pane opens into — and
|
|
18
|
-
* thus its durable REVIVE-HOME (`home_session`). Pure so the birth decision is
|
|
19
|
-
* unit-testable without a live tmux:
|
|
20
|
-
* - managed background child (`adoptCaller=false`) → the shared backstage:
|
|
21
|
-
* the inherited `CRTR_ROOT_SESSION`, else `nodeSession()` (`crtr`).
|
|
22
|
-
* - independent `--root` / inline front door (`adoptCaller=true`) → the
|
|
23
|
-
* caller's CURRENT session when inside tmux (`here`), else the backstage.
|
|
24
|
-
* This is exactly the session each birth site already places the node into;
|
|
25
|
-
* centralizing it keeps `home_session` and the actual placement in lockstep. */
|
|
26
|
-
export declare function resolveBirthSession(opts: {
|
|
27
|
-
/** True for an independent root or the inline front door (both adopt the
|
|
28
|
-
* caller's session); false for a managed background child. */
|
|
29
|
-
adoptCaller: boolean;
|
|
30
|
-
/** The caller's current tmux location, or null when not inside tmux. */
|
|
31
|
-
here: {
|
|
32
|
-
session: string;
|
|
33
|
-
} | null;
|
|
34
|
-
/** The inherited CRTR_ROOT_SESSION (the backstage the subtree flows into). */
|
|
35
|
-
rootSession?: string | null;
|
|
36
|
-
}): string;
|
|
37
|
-
/** A node's durable REVIVE-HOME, with the legacy back-compat default. Nodes born
|
|
38
|
-
* before `home_session` existed have no such field in meta — they fall back to
|
|
39
|
-
* their last live LOCATION (`tmux_session`), then to the shared backstage
|
|
40
|
-
* (`nodeSession()`). The defaulted read for the placement layer; a present
|
|
41
|
-
* `home_session` is always returned verbatim. */
|
|
42
|
-
export declare function homeSessionOf(nodeId: string): string;
|
|
43
|
-
/** The session a node's CHILDREN spawn into — the value its pi process exports
|
|
44
|
-
* as CRTR_ROOT_SESSION (the subtree's backstage). NOT the same as where the
|
|
45
|
-
* node's OWN pane lives.
|
|
46
|
-
*
|
|
47
|
-
* For a managed CHILD this is its `home_session`: a child's home_session is
|
|
48
|
-
* ALWAYS the backstage it was born into (focus may move its live pane into a
|
|
49
|
-
* user session, but home_session stays the taint-immune backstage), so its
|
|
50
|
-
* descendants correctly inherit that backstage.
|
|
51
|
-
*
|
|
52
|
-
* For a ROOT (`parent === null`) `home_session` may be a USER session it
|
|
53
|
-
* ADOPTED — the inline front door keeps the root's own pane where the user is
|
|
54
|
-
* working (`home_session = cli`). Sourcing children's CRTR_ROOT_SESSION from
|
|
55
|
-
* that would exile the root's entire subtree into the user's session. A root's
|
|
56
|
-
* children must instead flow to the shared backstage `nodeSession()`, NEVER the
|
|
57
|
-
* user's session — mirroring spawnRoot's birth-time split (root pane → user
|
|
58
|
-
* session; children → nodeSession() via CRTR_ROOT_SESSION). The revive paths
|
|
59
|
-
* MUST honor the same split, or a refreshed front-door root re-points its whole
|
|
60
|
-
* subtree into the user's session on every yield (the bug this fixes). */
|
|
61
|
-
export declare function childBackstageOf(nodeId: string): string;
|
|
62
10
|
export interface NodeContext {
|
|
63
11
|
nodeId: string | null;
|
|
64
12
|
kind: string | null;
|
|
@@ -91,9 +39,6 @@ export interface SpawnNodeOpts {
|
|
|
91
39
|
* the boot intro can re-assert this node's OWN identity over the source's
|
|
92
40
|
* copied-in conversation. Omit for a fresh node. */
|
|
93
41
|
forkFrom?: string | null;
|
|
94
|
-
/** Which HOST launches + supervises this node: a tmux pane (default) or the
|
|
95
|
-
* headless broker. Persisted to `meta.host_kind` (NULL/'tmux' ⇒ tmux). */
|
|
96
|
-
hostKind?: 'tmux' | 'broker';
|
|
97
42
|
/** New subscriptions this node opens default to passive when true. */
|
|
98
43
|
passiveDefault?: boolean;
|
|
99
44
|
/** Resolved pi launch recipe (from resolve(kind,mode)). */
|