@crouton-kit/crouter 0.3.28 → 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 +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 +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__/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 +1 -1
- 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__/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 +262 -840
- 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 +13 -50
- package/dist/core/runtime/tmux.js +33 -87
- 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
package/dist/daemon/manage.js
CHANGED
|
@@ -8,6 +8,7 @@ import { dirname, join } from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { mkdirSync } from 'node:fs';
|
|
10
10
|
import { crtrHome } from '../core/canvas/paths.js';
|
|
11
|
+
import { hostExecPath } from '../core/runtime/branded-host.js';
|
|
11
12
|
import { isDaemonRunning, readPidfile } from './crtrd.js';
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Entry point resolution
|
|
@@ -30,8 +31,12 @@ export function spawnDaemon() {
|
|
|
30
31
|
}
|
|
31
32
|
// Ensure the canvas home directory exists so the daemon can write its pidfile.
|
|
32
33
|
mkdirSync(crtrHome(), { recursive: true });
|
|
34
|
+
// Launch from the crouter-branded host binary so the daemon shows "crouter"
|
|
35
|
+
// in macOS Full Disk Access, not "node" (see branded-host). NOTE: when crtrd
|
|
36
|
+
// is launchd-owned, the plist's ProgramArguments must point at the branded
|
|
37
|
+
// binary too — this path only covers a manual `crtr canvas daemon start`.
|
|
33
38
|
const entry = resolveCrtrdEntry();
|
|
34
|
-
const child = spawn(
|
|
39
|
+
const child = spawn(hostExecPath(), [entry], {
|
|
35
40
|
detached: true,
|
|
36
41
|
stdio: 'ignore',
|
|
37
42
|
});
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// side effects and no circular imports — keep it that way.
|
|
11
11
|
// ── Runtime control ──────────────────────────────────────────────────────
|
|
12
12
|
// Sanctioned launchers/mutators. reviveNode is the ONLY sanctioned launcher of
|
|
13
|
-
//
|
|
13
|
+
// the broker engine; every node is broker-hosted (no host_kind choice).
|
|
14
14
|
export { spawnChild } from './core/runtime/spawn.js';
|
|
15
15
|
export { reviveNode } from './core/runtime/revive.js';
|
|
16
16
|
export { closeNode } from './core/runtime/close.js';
|
|
@@ -109,37 +109,42 @@ test('natural stop while awaiting a live worker → idle-release with NO push (n
|
|
|
109
109
|
// ---------------------------------------------------------------------------
|
|
110
110
|
// §5.1 — the §1.7 agent_end branch map on the focuses table. Every assertion is
|
|
111
111
|
// on the canvas focuses/runtime rows after firing agent_end (TMUX_PANE is
|
|
112
|
-
// cleared in beforeEach
|
|
113
|
-
//
|
|
114
|
-
//
|
|
112
|
+
// cleared in beforeEach). NOTE (broker-host cut): the placement read `focusOf`
|
|
113
|
+
// used by the handler now GC-prunes a viewer row whose pane is not a LIVE tmux
|
|
114
|
+
// pane (liveOrPrune → paneExists), so a fabricated '%pane' id is pruned on the
|
|
115
|
+
// first read. To drive the handler's focus path deterministically in the
|
|
116
|
+
// tmux-free fast tier we register viewer rows with a NULL pane (a registered-
|
|
117
|
+
// but-not-yet-realized viewer, which liveOrPrune passes through). status='done'
|
|
118
|
+
// is reached by setting the runtime row directly (the branch reads
|
|
119
|
+
// getNode(nodeId).status).
|
|
115
120
|
// ---------------------------------------------------------------------------
|
|
116
|
-
test('§5.1.1 truly-done + focused
|
|
121
|
+
test('§5.1.1 truly-done + focused → focus row CLOSED, NO manager takeover (broker cut deleted handFocusToManager)', () => {
|
|
122
|
+
// Regression guard for the broker-host cut: the engine is now a detached
|
|
123
|
+
// broker, not a pane, so there is no engine to hand to a manager. The old
|
|
124
|
+
// "dormant idle-release manager takes over the finished node's focus row"
|
|
125
|
+
// path (handFocusToManager) is DELETED — a truly-done focused node just
|
|
126
|
+
// tearDownNode's its own viewer and shuts down, EVEN when a dormant
|
|
127
|
+
// idle-release manager exists (the exact case that used to trigger takeover).
|
|
117
128
|
createNode(node('root', { parent: null, lifecycle: 'resident' }));
|
|
118
|
-
// mgr is dormant + idle-release — the ONLY dormant manager the daemon will
|
|
119
|
-
// revive INTO the frozen pane, so the ONLY dormant case that is a real takeover.
|
|
120
129
|
createNode(node('mgr', { parent: 'root', lifecycle: 'terminal', mode: 'orchestrator', status: 'idle', intent: 'idle-release' }));
|
|
121
|
-
// M starts WITH a recorded LOCATION so the
|
|
130
|
+
// M starts WITH a recorded LOCATION so the done-path presence-null is observable.
|
|
122
131
|
createNode(node('M', { parent: 'mgr', lifecycle: 'terminal', pane: '%m', tmux_session: 'Suser', window: '@wm' }));
|
|
123
132
|
subscribe('mgr', 'M', true);
|
|
124
|
-
openFocusRow('fM',
|
|
133
|
+
openFocusRow('fM', null, 'Suser', 'M'); // null pane → focusOf returns it → drives tearDownNode
|
|
125
134
|
process.env['CRTR_NODE_ID'] = 'M';
|
|
126
135
|
setStatus('M', 'done'); // pushed final this turn
|
|
127
136
|
const pi = makeFakePi();
|
|
128
137
|
registerCanvasStophook(pi);
|
|
129
138
|
let shutdown = false;
|
|
130
139
|
pi.fire('agent_end', stopEvent('done — pushed final'), { shutdown: () => { shutdown = true; } });
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// getFocusByNode('M') still names fM — both asserts fail.
|
|
137
|
-
assert.equal(getFocusByNode('mgr')?.focus_id, 'fM', 'focus row taken over by the manager');
|
|
140
|
+
// The done branch: focusOf('M') != null → tearDownNode('M') CLOSES fM. The
|
|
141
|
+
// manager is NOT handed anything (takeover deleted). Non-vacuous: a takeover
|
|
142
|
+
// impl would leave getFocusByNode('mgr').focus_id === 'fM'.
|
|
143
|
+
assert.equal(getFocusByNode('mgr'), null, 'the manager is NOT handed the finished node\'s focus (takeover deleted)');
|
|
144
|
+
assert.equal(getFocusById('fM'), null, 'the finished node\'s viewer focus row is closed');
|
|
138
145
|
assert.equal(getFocusByNode('M'), null, 'the finished node no longer occupies any focus');
|
|
139
|
-
assert.equal(shutdown, true, 'pi shut down after
|
|
140
|
-
//
|
|
141
|
-
// own presence is nulled so two rows never reference %m. Non-vacuous: an impl
|
|
142
|
-
// that skips the done-path setPresence-null leaves getNode('M').pane === '%m'.
|
|
146
|
+
assert.equal(shutdown, true, 'pi shut down after teardown');
|
|
147
|
+
// The done node's own presence is nulled (setPresence(null) on the done path).
|
|
143
148
|
assert.equal(getNode('M')?.pane ?? null, null, 'the finished node\'s own LOCATION pane is nulled');
|
|
144
149
|
assert.equal(getNode('M')?.window ?? null, null, 'the finished node\'s window presence is nulled too');
|
|
145
150
|
});
|
|
@@ -206,7 +211,10 @@ test('§5.1.5 awaiting + FOCUSED → STAY ALIVE: pi keeps running (no release),
|
|
|
206
211
|
createNode(node('worker', { parent: 'mgr', lifecycle: 'terminal', status: 'active' }));
|
|
207
212
|
subscribe('root', 'mgr', true);
|
|
208
213
|
subscribe('mgr', 'worker', true); // mgr awaits a live worker → would idle-release if UNfocused
|
|
209
|
-
|
|
214
|
+
// null pane (broker cut): focusOf passes a null-pane viewer row through, so the
|
|
215
|
+
// handler reads mgr as FOCUSED in the tmux-free fast tier (a fabricated %pane
|
|
216
|
+
// would be GC-pruned by liveOrPrune and the node would wrongly release).
|
|
217
|
+
openFocusRow('fMgr', null, 'Suser', 'mgr');
|
|
210
218
|
process.env['CRTR_NODE_ID'] = 'mgr';
|
|
211
219
|
const pi = makeFakePi();
|
|
212
220
|
registerCanvasStophook(pi);
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
//
|
|
16
16
|
// The block carries: the path to the node's own context dir and the framing for
|
|
17
17
|
// what belongs there (a shared document store for the other nodes). EVERY node
|
|
18
|
-
// also gets a `<
|
|
19
|
-
// eligible `
|
|
18
|
+
// also gets a `<knowledge>` block rendered from the document substrate —
|
|
19
|
+
// eligible `knowledge` docs at their system-prompt visibility rung, plus the
|
|
20
20
|
// node's own node-local substrate docs — so the bearings name what to read on
|
|
21
|
-
// demand. (
|
|
22
|
-
//
|
|
21
|
+
// demand. (Preferences surface as their own `<preferences>` block of the
|
|
22
|
+
// system prompt, not in this block.) An
|
|
23
23
|
// orchestrator additionally gets the across-refresh-cycles framing (the one
|
|
24
24
|
// thing a terminal worker's bearings drop). The prose lives in
|
|
25
25
|
// core/runtime/bearings.ts (shared with the promotion guidance dump).
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
// It owns the substrate's two new pi hooks:
|
|
10
10
|
//
|
|
11
11
|
// 1. before_agent_start — the BOOT system-prompt half. Splices the rendered
|
|
12
|
-
// `<
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
12
|
+
// `<preferences>` + `<memory-guidance>` blocks into `event.systemPrompt`,
|
|
13
|
+
// right after pi's native "Available tools" list (before the
|
|
14
|
+
// `\n\nGuidelines:` anchor), so the substrate's standing directives sit in
|
|
15
|
+
// the tool-selection frame the agent reads while choosing a capability —
|
|
16
|
+
// mirroring the personal crouter-help.ts anchor logic. Falls back to
|
|
17
|
+
// appending when the anchor is absent. (Knowledge rides the `<crtr-context>`
|
|
18
|
+
// bearings message instead — see core/substrate/render.ts.)
|
|
19
19
|
//
|
|
20
20
|
// 2. tool_result (gated on `read`) — the ON-READ half. For each non-error
|
|
21
21
|
// `read`, surfaces the substrate docs that should appear alongside the file
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// with no dep on the pi packages. Mirrors canvas-context-intro.ts.
|
|
35
35
|
import { homedir } from 'node:os';
|
|
36
36
|
import { join, resolve } from 'node:path';
|
|
37
|
-
import { renderMemoryGuidance, renderOnReadDocs, renderPreferencesSection,
|
|
37
|
+
import { renderMemoryGuidance, renderOnReadDocs, renderPreferencesSection, } from '../core/substrate/index.js';
|
|
38
38
|
import { clearSessionCache } from '../core/substrate/session-cache.js';
|
|
39
39
|
import { loadInjectedDocs, saveInjectedDocs } from '../core/substrate/injected-store.js';
|
|
40
40
|
// ---------------------------------------------------------------------------
|
|
@@ -61,9 +61,9 @@ export function registerCanvasDocSubstrate(pi) {
|
|
|
61
61
|
// cycle. A resume reuses the same .jsonl transcript in a NEW pi process, so
|
|
62
62
|
// the set is REHYDRATED from disk at process start (not started empty) and is
|
|
63
63
|
// NOT cleared on session_start — a resume continues the transcript, so the
|
|
64
|
-
// dedup must carry forward. The launch
|
|
65
|
-
// (
|
|
66
|
-
//
|
|
64
|
+
// dedup must carry forward. The one launch path that begins a FRESH transcript
|
|
65
|
+
// (reviveNode with resume=false) deletes the file, so a new conversation
|
|
66
|
+
// rehydrates empty here. See core/substrate/injected-store.ts.
|
|
67
67
|
const injectedDocs = loadInjectedDocs(nodeId);
|
|
68
68
|
pi.on('session_start', () => {
|
|
69
69
|
// Only the per-session substrate PARSE cache resets each session (so the
|
|
@@ -71,14 +71,14 @@ export function registerCanvasDocSubstrate(pi) {
|
|
|
71
71
|
// transcript-scoped, not session-scoped — deliberately NOT cleared here.
|
|
72
72
|
clearSessionCache();
|
|
73
73
|
});
|
|
74
|
-
// 1. BOOT system-prompt half — splice `<
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
74
|
+
// 1. BOOT system-prompt half — splice `<preferences>` + `<memory-guidance>`.
|
|
75
|
+
// The guidance block is always present for a canvas node (the memory system
|
|
76
|
+
// always exists), so the splice is never empty even when the preferences
|
|
77
|
+
// tree is. (Knowledge no longer enters the system prompt — it rides the
|
|
78
|
+
// `<crtr-context>` bearings message via renderKnowledgeBlock.)
|
|
78
79
|
pi.on('before_agent_start', (event) => {
|
|
79
80
|
try {
|
|
80
81
|
const sections = [
|
|
81
|
-
renderSkillsSection(nodeId),
|
|
82
82
|
renderPreferencesSection(nodeId),
|
|
83
83
|
renderMemoryGuidance(),
|
|
84
84
|
].filter((s) => s !== '');
|
|
@@ -47,6 +47,7 @@ import { execFile } from 'node:child_process';
|
|
|
47
47
|
import { fullName } from '../core/canvas/index.js';
|
|
48
48
|
import { beginFrame, cNode, managerOf, liveReports, sortedChildIds, subtreeIds, climbRoot, computeSubtreeActivity, buildGraphModel, renderGraphRow, navLabel, coloredGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, focusedNodeIds, graphWidgetBudget, fetchAsksMap, shortId, DIM, RESET, BOLD, YELLOW, GRAPH_HINT, } from '../core/canvas/nav-model.js';
|
|
49
49
|
import { readConfig } from '../core/config.js';
|
|
50
|
+
import { onNavRerender } from './widget-order-bus.js';
|
|
50
51
|
// ---------------------------------------------------------------------------
|
|
51
52
|
// Module-level state — persists across /reload so guards don't stack and fold
|
|
52
53
|
// state / current view survive a hot-swap.
|
|
@@ -317,6 +318,10 @@ export function registerCanvasNav(pi) {
|
|
|
317
318
|
render();
|
|
318
319
|
}, RENDER_DEBOUNCE_MS);
|
|
319
320
|
};
|
|
321
|
+
// Let canvas-recap ask us to re-assert the manager line so its recap card
|
|
322
|
+
// stays the topmost aboveEditor chrome (pi's widget store is insertion-
|
|
323
|
+
// ordered; re-setting crtr-managers drops it below crtr-recap).
|
|
324
|
+
onNavRerender(scheduleRender);
|
|
320
325
|
// -------------------------------------------------------------------------
|
|
321
326
|
// Actions (all shell out; the extension stays tmux/revive-free)
|
|
322
327
|
// -------------------------------------------------------------------------
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface TextContentLike {
|
|
2
|
+
type: string;
|
|
3
|
+
text?: string;
|
|
4
|
+
}
|
|
5
|
+
interface MessageLike {
|
|
6
|
+
role: string;
|
|
7
|
+
content: string | TextContentLike[];
|
|
8
|
+
}
|
|
9
|
+
interface SessionEntryLike {
|
|
10
|
+
type: string;
|
|
11
|
+
message?: MessageLike;
|
|
12
|
+
}
|
|
13
|
+
interface SessionManagerLike {
|
|
14
|
+
getEntries(): SessionEntryLike[];
|
|
15
|
+
}
|
|
16
|
+
interface UIContextLike {
|
|
17
|
+
setWidget(key: string, content: string[] | undefined, options?: {
|
|
18
|
+
placement?: 'aboveEditor' | 'belowEditor';
|
|
19
|
+
}): void;
|
|
20
|
+
}
|
|
21
|
+
interface ExtensionCtxLike {
|
|
22
|
+
ui?: UIContextLike;
|
|
23
|
+
mode?: string;
|
|
24
|
+
sessionManager?: SessionManagerLike;
|
|
25
|
+
}
|
|
26
|
+
type PiEvents = 'session_start' | 'input' | 'turn_end' | 'session_shutdown';
|
|
27
|
+
interface PiLike {
|
|
28
|
+
on(event: PiEvents, handler: (event: any, ctx: ExtensionCtxLike) => void): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register the inactivity-recap chrome on `pi`.
|
|
32
|
+
*
|
|
33
|
+
* Returns immediately (inert) when CRTR_NODE_ID is absent — a plain pi session
|
|
34
|
+
* or legacy job agent loads it as a no-op.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerCanvasRecap(pi: PiLike): void;
|
|
37
|
+
export default registerCanvasRecap;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// canvas-recap.ts — pi extension for pi-native canvas agent nodes.
|
|
2
|
+
//
|
|
3
|
+
// A per-node INACTIVITY RECAP, drawn as the topmost chrome above the editor.
|
|
4
|
+
// When a node has had NO new message (user or assistant) for IDLE_MS (300s by
|
|
5
|
+
// default), it runs Haiku over the literal conversation and pins a terse
|
|
6
|
+
// three-fragment card — goal / doing-now / next — above canvas-nav's manager
|
|
7
|
+
// line. The card disappears the instant the next message arrives, and the idle
|
|
8
|
+
// clock restarts.
|
|
9
|
+
//
|
|
10
|
+
// trigger/clear are keyed off MESSAGES, not turns-of-work:
|
|
11
|
+
// • a user `input` event OR an assistant `turn_end` resets the idle clock
|
|
12
|
+
// and clears any shown recap.
|
|
13
|
+
// • a single low-rate timer (no busy-loop) checks elapsed-since-last-message
|
|
14
|
+
// and, on crossing IDLE_MS, generates + shows the recap once.
|
|
15
|
+
//
|
|
16
|
+
// Haiku input = ALL user + assistant LITERAL text, most-recent within a char
|
|
17
|
+
// budget. Tool calls, tool results, thinking blocks, and extension-injected
|
|
18
|
+
// custom messages are excluded (only `message` entries with role user/assistant,
|
|
19
|
+
// and only their `text` content blocks).
|
|
20
|
+
//
|
|
21
|
+
// Placement: key `crtr-recap`, placement `aboveEditor`, ordered ABOVE
|
|
22
|
+
// `crtr-managers` via the widget-order bus (pi's widget store is insertion-
|
|
23
|
+
// ordered and re-setting moves a key to the bottom; after we paint the recap
|
|
24
|
+
// we ask canvas-nav to re-assert its manager line, which drops it below us).
|
|
25
|
+
//
|
|
26
|
+
// Surface gating: shows in BOTH the in-pane pi TUI (mode 'tui') and the
|
|
27
|
+
// headless broker → attach/web viewers (mode 'print', setWidget broadcasts to
|
|
28
|
+
// all surfaces). It does NOT gate on mode === 'tui' (that would kill the
|
|
29
|
+
// headless+attach path). It NO-OPs only when ui/setWidget is unavailable, and
|
|
30
|
+
// harmlessly in a true one-shot `pi -p` (the unref'd timer never fires before
|
|
31
|
+
// the process exits, and naming-style one-shots load with --no-extensions).
|
|
32
|
+
//
|
|
33
|
+
// INERT when CRTR_NODE_ID is absent (a plain pi session or legacy job agent).
|
|
34
|
+
//
|
|
35
|
+
// Plain TS-with-types — no imports from @earendil-works/* so this compiles
|
|
36
|
+
// inside crouter's own tsc build without a dep on the pi packages.
|
|
37
|
+
import { generateRecap } from '../core/runtime/recap.js';
|
|
38
|
+
import { requestNavRerender } from './widget-order-bus.js';
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Tuning constants
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/** No-new-message window before a recap is shown. 300s by default; override with
|
|
43
|
+
* CRTR_RECAP_IDLE_MS (for fast runtime verification). */
|
|
44
|
+
function idleMs() {
|
|
45
|
+
const raw = process.env['CRTR_RECAP_IDLE_MS'];
|
|
46
|
+
const n = raw !== undefined ? parseInt(raw, 10) : NaN;
|
|
47
|
+
return Number.isFinite(n) && n > 0 ? n : 300_000;
|
|
48
|
+
}
|
|
49
|
+
/** How often the (single, low-rate) timer checks elapsed-since-last-message.
|
|
50
|
+
* Mirrors canvas-nav's ASK_POLL_MS — cheap and tmux/crtr-free. */
|
|
51
|
+
const POLL_MS = 7_000;
|
|
52
|
+
/** Char budget on the conversation fed to Haiku — the model only needs the gist,
|
|
53
|
+
* so we keep the MOST-RECENT text within this cap (a recap is about where we
|
|
54
|
+
* left off, not the whole history). */
|
|
55
|
+
const CONVO_CHAR_BUDGET = 12_000;
|
|
56
|
+
const WIDGET_KEY = 'crtr-recap';
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Module-level state — persists across /reload so the timer doesn't stack.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/** The one live poll timer. Cleared and replaced on every re-registration. */
|
|
61
|
+
let liveTimer;
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Conversation extraction — literal user+assistant text ONLY.
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
/** Pull the literal text out of a message's content (string or text blocks),
|
|
66
|
+
* dropping non-text blocks (thinking, toolCall, image). */
|
|
67
|
+
function messageText(msg) {
|
|
68
|
+
const c = msg.content;
|
|
69
|
+
if (typeof c === 'string')
|
|
70
|
+
return c.trim();
|
|
71
|
+
if (!Array.isArray(c))
|
|
72
|
+
return '';
|
|
73
|
+
return c
|
|
74
|
+
.filter((b) => b !== null && typeof b === 'object' && b.type === 'text' && typeof b.text === 'string')
|
|
75
|
+
.map((b) => (b.text ?? '').trim())
|
|
76
|
+
.filter((t) => t !== '')
|
|
77
|
+
.join('\n')
|
|
78
|
+
.trim();
|
|
79
|
+
}
|
|
80
|
+
/** Concatenate the conversation's user + assistant literal text into a single
|
|
81
|
+
* `User:`/`Agent:` transcript, keeping only the MOST-RECENT CONVO_CHAR_BUDGET
|
|
82
|
+
* characters. Excludes tool calls/results (role toolResult, plus toolCall/
|
|
83
|
+
* thinking content blocks dropped by messageText) and extension-injected
|
|
84
|
+
* custom messages (entry.type !== 'message'). Returns '' when there is nothing
|
|
85
|
+
* to summarize. */
|
|
86
|
+
function buildConversation(sm) {
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = sm.getEntries();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
const parts = [];
|
|
95
|
+
for (const e of entries) {
|
|
96
|
+
if (e.type !== 'message' || e.message === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
const role = e.message.role;
|
|
99
|
+
if (role !== 'user' && role !== 'assistant')
|
|
100
|
+
continue; // drop toolResult etc.
|
|
101
|
+
const text = messageText(e.message);
|
|
102
|
+
if (text === '')
|
|
103
|
+
continue;
|
|
104
|
+
parts.push(`${role === 'user' ? 'User' : 'Agent'}: ${text}`);
|
|
105
|
+
}
|
|
106
|
+
if (parts.length === 0)
|
|
107
|
+
return '';
|
|
108
|
+
const full = parts.join('\n\n');
|
|
109
|
+
return full.length > CONVO_CHAR_BUDGET ? full.slice(full.length - CONVO_CHAR_BUDGET) : full;
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Extension
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
/**
|
|
115
|
+
* Register the inactivity-recap chrome on `pi`.
|
|
116
|
+
*
|
|
117
|
+
* Returns immediately (inert) when CRTR_NODE_ID is absent — a plain pi session
|
|
118
|
+
* or legacy job agent loads it as a no-op.
|
|
119
|
+
*/
|
|
120
|
+
export function registerCanvasRecap(pi) {
|
|
121
|
+
const nodeId = process.env['CRTR_NODE_ID'];
|
|
122
|
+
if (nodeId === undefined || nodeId.trim() === '')
|
|
123
|
+
return; // not a canvas node
|
|
124
|
+
// Captured on session_start; used by the timer and the message handlers.
|
|
125
|
+
let ui;
|
|
126
|
+
let sessionManager;
|
|
127
|
+
/** Wall-clock of the most recent user/assistant message. The idle clock. */
|
|
128
|
+
let lastMessageAt = Date.now();
|
|
129
|
+
/** True while a recap is currently shown (so we generate at most once per
|
|
130
|
+
* idle window, and know whether a clear is needed on the next message). */
|
|
131
|
+
let recapShown = false;
|
|
132
|
+
/** True while a Haiku generation is in flight (so we don't fire a second). */
|
|
133
|
+
let generating = false;
|
|
134
|
+
const canPaint = () => ui !== undefined && typeof ui.setWidget === 'function';
|
|
135
|
+
const clearRecap = () => {
|
|
136
|
+
if (!canPaint())
|
|
137
|
+
return;
|
|
138
|
+
try {
|
|
139
|
+
ui.setWidget(WIDGET_KEY, undefined, { placement: 'aboveEditor' });
|
|
140
|
+
}
|
|
141
|
+
catch { /* best-effort */ }
|
|
142
|
+
};
|
|
143
|
+
/** Render the three fragments as the topmost above-editor card. */
|
|
144
|
+
const showRecap = (fragments) => {
|
|
145
|
+
if (!canPaint())
|
|
146
|
+
return;
|
|
147
|
+
const labels = ['◷ goal', '· now ', '→ next'];
|
|
148
|
+
const lines = fragments.slice(0, 3).map((f, i) => `${labels[i] ?? ' '} ${f}`);
|
|
149
|
+
if (lines.length === 0)
|
|
150
|
+
return;
|
|
151
|
+
try {
|
|
152
|
+
ui.setWidget(WIDGET_KEY, lines, { placement: 'aboveEditor' });
|
|
153
|
+
}
|
|
154
|
+
catch { /* best-effort */ }
|
|
155
|
+
// Re-assert the manager line BELOW us (pi's widget store is insertion-
|
|
156
|
+
// ordered; re-setting crtr-managers drops it under crtr-recap).
|
|
157
|
+
requestNavRerender();
|
|
158
|
+
};
|
|
159
|
+
/** A user or assistant message landed: reset the idle clock and clear any
|
|
160
|
+
* shown recap (the conversation has moved on). */
|
|
161
|
+
const onMessage = () => {
|
|
162
|
+
lastMessageAt = Date.now();
|
|
163
|
+
if (recapShown) {
|
|
164
|
+
recapShown = false;
|
|
165
|
+
clearRecap();
|
|
166
|
+
requestNavRerender();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Events
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
pi.on('session_start', (_event, ctx) => {
|
|
173
|
+
ui = ctx.ui;
|
|
174
|
+
sessionManager = ctx.sessionManager;
|
|
175
|
+
// Fresh session / hot-swap: start the idle clock now and drop any stale
|
|
176
|
+
// recap that bled through a /reload.
|
|
177
|
+
lastMessageAt = Date.now();
|
|
178
|
+
recapShown = false;
|
|
179
|
+
generating = false;
|
|
180
|
+
clearRecap();
|
|
181
|
+
});
|
|
182
|
+
pi.on('input', (_event, _ctx) => {
|
|
183
|
+
// Every user message (any source) is activity — reset + clear.
|
|
184
|
+
onMessage();
|
|
185
|
+
});
|
|
186
|
+
pi.on('turn_end', (_event, _ctx) => {
|
|
187
|
+
// A completed assistant turn is activity — reset + clear.
|
|
188
|
+
onMessage();
|
|
189
|
+
});
|
|
190
|
+
// -------------------------------------------------------------------------
|
|
191
|
+
// The single low-rate idle poll — no busy-loop, mirrors canvas-nav's timer.
|
|
192
|
+
// -------------------------------------------------------------------------
|
|
193
|
+
if (liveTimer !== undefined)
|
|
194
|
+
clearInterval(liveTimer);
|
|
195
|
+
const timer = setInterval(() => {
|
|
196
|
+
try {
|
|
197
|
+
if (!canPaint() || sessionManager === undefined)
|
|
198
|
+
return;
|
|
199
|
+
if (recapShown || generating)
|
|
200
|
+
return;
|
|
201
|
+
if (Date.now() - lastMessageAt < idleMs())
|
|
202
|
+
return;
|
|
203
|
+
const convo = buildConversation(sessionManager);
|
|
204
|
+
if (convo === '')
|
|
205
|
+
return; // nothing to summarize yet
|
|
206
|
+
// Snapshot the clock: if a message arrives while Haiku runs, discard the
|
|
207
|
+
// (now-stale) result instead of painting over the live conversation.
|
|
208
|
+
const startedAt = lastMessageAt;
|
|
209
|
+
generating = true;
|
|
210
|
+
generateRecap(convo, (fragments) => {
|
|
211
|
+
generating = false;
|
|
212
|
+
if (lastMessageAt !== startedAt)
|
|
213
|
+
return; // a message landed mid-flight
|
|
214
|
+
if (!canPaint())
|
|
215
|
+
return;
|
|
216
|
+
recapShown = true;
|
|
217
|
+
showRecap(fragments);
|
|
218
|
+
});
|
|
219
|
+
// generateRecap is silent on failure (never calls back), so clear the
|
|
220
|
+
// in-flight flag after the timeout window even if no callback fires.
|
|
221
|
+
setTimeout(() => { generating = false; }, 30_000).unref?.();
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* timer is best-effort */
|
|
225
|
+
}
|
|
226
|
+
}, POLL_MS);
|
|
227
|
+
if (typeof timer.unref === 'function')
|
|
228
|
+
timer.unref();
|
|
229
|
+
liveTimer = timer;
|
|
230
|
+
pi.on('session_shutdown', () => {
|
|
231
|
+
clearInterval(timer);
|
|
232
|
+
if (liveTimer === timer)
|
|
233
|
+
liveTimer = undefined;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
export default registerCanvasRecap;
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
// inside crouter's own tsc build without a dep on the pi packages (mirrors
|
|
28
28
|
// canvas-nav.ts / canvas-commands.ts).
|
|
29
29
|
import { execFile } from 'node:child_process';
|
|
30
|
+
import { surfaceTmuxStyleArgs } from '../core/runtime/surface-bg.js';
|
|
30
31
|
/** Single-quote a string for safe interpolation into a `sh -c` command line —
|
|
31
32
|
* tmux runs the display-popup trailing string through the shell, so a cwd with
|
|
32
33
|
* spaces or quotes must be escaped. */
|
|
@@ -66,7 +67,7 @@ export function registerCanvasResume(pi) {
|
|
|
66
67
|
const cwd = shellQuote(process.cwd());
|
|
67
68
|
const cmd = `crtr canvas browse --return-pane ${origPane} --cwd ${cwd}`;
|
|
68
69
|
try {
|
|
69
|
-
execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', cmd], () => { });
|
|
70
|
+
execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', ...surfaceTmuxStyleArgs(), cmd], () => { });
|
|
70
71
|
}
|
|
71
72
|
catch { /* best-effort */ }
|
|
72
73
|
return;
|
|
@@ -6,20 +6,22 @@ interface PiLike {
|
|
|
6
6
|
}) => void;
|
|
7
7
|
}
|
|
8
8
|
/** The nudge text for a crossed band, specialized to the node's (mode,
|
|
9
|
-
* lifecycle) persona + how far along the escalation it is.
|
|
9
|
+
* lifecycle) persona + how far along the escalation it is. `crtr node yield` is
|
|
10
|
+
* the single "keep going past this window" verb for every persona: it refreshes
|
|
11
|
+
* the node into a clean window against a roadmap, and for a base node
|
|
12
|
+
* auto-promotes transparently — so no persona is ever steered at a bare `node
|
|
13
|
+
* promote` (which framed promotion as a heavy "become an orchestrator" step and
|
|
14
|
+
* bred yield-avoidance).
|
|
10
15
|
*
|
|
11
|
-
* - orchestrator (terminal OR resident): checkpoint
|
|
12
|
-
* (
|
|
13
|
-
* - resident/base (root conversation):
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - terminal/base (worker): PROMOTE itself — become an orchestrator — when
|
|
18
|
-
* work remains, with an "ignore if nearly done, finish with push final" once
|
|
19
|
-
* it's deeper in.
|
|
16
|
+
* - orchestrator (terminal OR resident): checkpoint context/roadmap.md, then
|
|
17
|
+
* yield (it already has a roadmap to bring current first).
|
|
18
|
+
* - resident/base (root conversation): yield to continue in a fresh window if
|
|
19
|
+
* the chat is growing into a job, else wrap up — a root can simply end.
|
|
20
|
+
* - terminal/base (worker): yield to continue if work remains, or `push final`
|
|
21
|
+
* when close.
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
* drifting further risks an overflow. */
|
|
23
|
+
* The wording strengthens with depth: gentle < 150k → firm < 185k → pushy at/
|
|
24
|
+
* past 185k, where drifting further risks an overflow. */
|
|
23
25
|
export declare function steerNote(at: number, lifecycle: string, mode: string): string;
|
|
24
26
|
/**
|
|
25
27
|
* Register the canvas turn_end / agent_end handlers on `pi`.
|