@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
|
@@ -28,14 +28,13 @@
|
|
|
28
28
|
// crouter's own tsc build without a dep on the pi packages.
|
|
29
29
|
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
|
|
30
30
|
import { join } from 'node:path';
|
|
31
|
-
import { getNode, jobDir, updateNode, recordPid,
|
|
31
|
+
import { getNode, jobDir, updateNode, recordPid, setPresence } from '../core/canvas/index.js';
|
|
32
32
|
import { transition } from '../core/runtime/lifecycle.js';
|
|
33
33
|
import { markBusy, clearBusy } from '../core/runtime/busy.js';
|
|
34
34
|
import { evaluateStop } from '../core/runtime/stop-guard.js';
|
|
35
35
|
import { personaDrift, commitPersonaAck } from '../core/runtime/persona.js';
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import { focusOf, handFocusToManager, tearDownNode, closeFocusToShell } from '../core/runtime/placement.js';
|
|
36
|
+
import { handleNewSession, relaunchRoot, markCleanExitDone } from '../core/runtime/reset.js';
|
|
37
|
+
import { focusOf, tearDownNode } from '../core/runtime/placement.js';
|
|
39
38
|
/** Condense a tool call into one short line for the activity cue. Picks the most
|
|
40
39
|
* identifying arg (command / path / pattern / description), collapses whitespace,
|
|
41
40
|
* and hard-caps the length so a giant bash command can't bloat telemetry.json. */
|
|
@@ -110,18 +109,22 @@ function lastAssistantMessage(messages) {
|
|
|
110
109
|
// 185k+ pushy. Keyed on mode, NOT lifecycle: a terminal/orchestrator
|
|
111
110
|
// yields against its roadmap exactly like a resident one (the
|
|
112
111
|
// daemon's refresh-revive keys on intent='refresh', not lifecycle).
|
|
113
|
-
// resident/base (a root conversation):
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
112
|
+
// resident/base (a root conversation): NO roadmap on disk yet, so it is not
|
|
113
|
+
// told to checkpoint one. `crtr node yield` is still its continue
|
|
114
|
+
// verb — it auto-promotes + seeds a roadmap transparently — offered
|
|
115
|
+
// against wrap-up, since a root chat can simply end. Gentle → firm →
|
|
117
116
|
// pushy.
|
|
118
|
-
// terminal/base (a worker):
|
|
119
|
-
//
|
|
117
|
+
// terminal/base (a worker): steered to `crtr node yield` to continue in a
|
|
118
|
+
// fresh window when work remains (yield auto-promotes under the
|
|
119
|
+
// hood), or `push final` when close. Gentle → firm → pushy.
|
|
120
120
|
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
121
|
+
// Every persona's "keep going" verb is `crtr node yield` — NEVER a bare `node
|
|
122
|
+
// promote`, which framed promotion as a heavy "become an orchestrator" step and
|
|
123
|
+
// bred yield-avoidance. A base node's yield auto-promotes + seeds the roadmap
|
|
124
|
+
// under the hood, so the agent never decides to promote — it just yields. Only a
|
|
125
|
+
// terminal node is ever told `push final` (a resident finishes by yielding or
|
|
126
|
+
// being closed); only an orchestrator already HAS a roadmap to checkpoint before
|
|
127
|
+
// it yields.
|
|
125
128
|
// ---------------------------------------------------------------------------
|
|
126
129
|
const STEER_STEP = 10_000;
|
|
127
130
|
// Shared escalation schedule (both lifecycles). Tightens as it climbs:
|
|
@@ -144,20 +147,22 @@ function steerBand(tokens) {
|
|
|
144
147
|
return chosen;
|
|
145
148
|
}
|
|
146
149
|
/** The nudge text for a crossed band, specialized to the node's (mode,
|
|
147
|
-
* lifecycle) persona + how far along the escalation it is.
|
|
150
|
+
* lifecycle) persona + how far along the escalation it is. `crtr node yield` is
|
|
151
|
+
* the single "keep going past this window" verb for every persona: it refreshes
|
|
152
|
+
* the node into a clean window against a roadmap, and for a base node
|
|
153
|
+
* auto-promotes transparently — so no persona is ever steered at a bare `node
|
|
154
|
+
* promote` (which framed promotion as a heavy "become an orchestrator" step and
|
|
155
|
+
* bred yield-avoidance).
|
|
148
156
|
*
|
|
149
|
-
* - orchestrator (terminal OR resident): checkpoint
|
|
150
|
-
* (
|
|
151
|
-
* - resident/base (root conversation):
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* - terminal/base (worker): PROMOTE itself — become an orchestrator — when
|
|
156
|
-
* work remains, with an "ignore if nearly done, finish with push final" once
|
|
157
|
-
* it's deeper in.
|
|
157
|
+
* - orchestrator (terminal OR resident): checkpoint context/roadmap.md, then
|
|
158
|
+
* yield (it already has a roadmap to bring current first).
|
|
159
|
+
* - resident/base (root conversation): yield to continue in a fresh window if
|
|
160
|
+
* the chat is growing into a job, else wrap up — a root can simply end.
|
|
161
|
+
* - terminal/base (worker): yield to continue if work remains, or `push final`
|
|
162
|
+
* when close.
|
|
158
163
|
*
|
|
159
|
-
*
|
|
160
|
-
* drifting further risks an overflow. */
|
|
164
|
+
* The wording strengthens with depth: gentle < 150k → firm < 185k → pushy at/
|
|
165
|
+
* past 185k, where drifting further risks an overflow. */
|
|
161
166
|
export function steerNote(at, lifecycle, mode) {
|
|
162
167
|
const k = Math.round(at / 1000);
|
|
163
168
|
const pushy = at >= 185_000;
|
|
@@ -173,25 +178,31 @@ export function steerNote(at, lifecycle, mode) {
|
|
|
173
178
|
return `Context ~${k}k — this is getting long. Stop taking on new work now: checkpoint context/roadmap.md, hand off anything outstanding, and \`crtr node yield\` immediately to refresh before this context overflows.`;
|
|
174
179
|
}
|
|
175
180
|
if (lifecycle === 'resident') {
|
|
176
|
-
// resident/base — a root conversation
|
|
177
|
-
//
|
|
178
|
-
|
|
181
|
+
// resident/base — a root conversation with no roadmap yet. `crtr node yield`
|
|
182
|
+
// is still the natural "carry this forward" verb: it refreshes into a clean
|
|
183
|
+
// window against a roadmap and auto-promotes transparently (no scary
|
|
184
|
+
// "become an orchestrator" step). Offer wrap-up as the alternative — a root
|
|
185
|
+
// chat can simply end.
|
|
186
|
+
const grow = `If this is turning into a multi-phase job, \`crtr node yield\` to continue in a fresh window against a roadmap — no need to "become" anything, yield just carries you forward.`;
|
|
179
187
|
if (at < 150_000) {
|
|
180
188
|
return `Context ~${k}k and growing. ${grow} Otherwise no rush — wrap up when you reach a good stopping point.`;
|
|
181
189
|
}
|
|
182
190
|
if (!pushy) {
|
|
183
|
-
return `Context ~${k}k. ${grow} If you're near done, just finish here; if there's more open-ended work,
|
|
191
|
+
return `Context ~${k}k. ${grow} If you're near done, just finish here; if there's more open-ended work, \`crtr node yield\` rather than letting this context grow.`;
|
|
184
192
|
}
|
|
185
|
-
return `Context ~${k}k — this is getting long. Wrap up now before this context overflows: finish what's in hand, or \`crtr node
|
|
193
|
+
return `Context ~${k}k — this is getting long. Wrap up now before this context overflows: finish what's in hand, or \`crtr node yield\` immediately to continue fresh against a roadmap if substantial work remains.`;
|
|
194
|
+
}
|
|
195
|
+
// terminal/base — a worker. The natural "keep going past this window" verb is
|
|
196
|
+
// `crtr node yield`: it refreshes into a clean window against a roadmap and
|
|
197
|
+
// auto-promotes transparently, so there is no separate "become an orchestrator"
|
|
198
|
+
// decision to agonize over. Steer it to yield-or-finish, never a bare promote.
|
|
199
|
+
if (at < 150_000) {
|
|
200
|
+
return `Context ~${k}k and growing. If you're near done, finish with \`crtr push final\`. If real work remains, \`crtr node yield\` to continue in a fresh window against a roadmap — no rush yet.`;
|
|
186
201
|
}
|
|
187
|
-
// terminal — a worker.
|
|
188
|
-
const suggest = `If much more work remains than this context can finish, consider \`crtr node promote\` to become an orchestrator (seeds a roadmap, lets you delegate and \`crtr node yield\` to refresh).`;
|
|
189
|
-
if (at < 170_000)
|
|
190
|
-
return `Context ~${k}k. ${suggest}`;
|
|
191
202
|
if (!pushy) {
|
|
192
|
-
return `Context ~${k}k.
|
|
203
|
+
return `Context ~${k}k. If you're close, finish with \`crtr push final\`; otherwise \`crtr node yield\` now to refresh into a clean window and keep going against a roadmap.`;
|
|
193
204
|
}
|
|
194
|
-
return `Context ~${k}k — this is getting long. Wrap up now: \`crtr push final\` if you're close, otherwise \`crtr node
|
|
205
|
+
return `Context ~${k}k — this is getting long. Wrap up now before this context overflows: \`crtr push final\` if you're close, otherwise \`crtr node yield\` immediately to continue in a fresh window against a roadmap.`;
|
|
195
206
|
}
|
|
196
207
|
// ---------------------------------------------------------------------------
|
|
197
208
|
// Extension
|
|
@@ -240,13 +251,12 @@ export function registerCanvasStophook(pi) {
|
|
|
240
251
|
// Why reason, not a closure flag: pi RE-ACTIVATES extensions on every session
|
|
241
252
|
// swap, so any id we stash on boot is reset to its initial value before the
|
|
242
253
|
// next session_start fires and can never observe the change — a `/new` then
|
|
243
|
-
// looks identical to a boot.
|
|
244
|
-
//
|
|
245
|
-
// id + context dir never changed.) The event reason is delivered fresh on
|
|
246
|
-
// every fire and is immune to the re-activation.
|
|
254
|
+
// looks identical to a boot. The event reason is delivered fresh on every fire
|
|
255
|
+
// and is immune to the re-activation.
|
|
247
256
|
//
|
|
248
|
-
//
|
|
249
|
-
// root
|
|
257
|
+
// `/new` splits by who ran it: a ROOT relaunches into a genuinely new node
|
|
258
|
+
// (relaunchRoot — park the old root `done`, boot a fresh node, re-point this
|
|
259
|
+
// pane), a non-root CHILD just refreshes its session id (handleNewSession).
|
|
250
260
|
// ---------------------------------------------------------------------------
|
|
251
261
|
pi.on('session_start', (event, ctx) => {
|
|
252
262
|
try {
|
|
@@ -258,16 +268,19 @@ export function registerCanvasStophook(pi) {
|
|
|
258
268
|
// whereas a bare id is resolved cwd-relative and forks across projects.
|
|
259
269
|
const filed = ctx?.sessionManager?.getSessionFile?.();
|
|
260
270
|
const sessionFile = typeof filed === 'string' && filed !== '' ? filed : null;
|
|
261
|
-
// `/new` — a brand-new conversation in the same process.
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
// after handleNewSession.
|
|
271
|
+
// `/new` — a brand-new conversation in the same process. The broker already
|
|
272
|
+
// drove the engine-side new_session (the viewer's /new → broker
|
|
273
|
+
// new_session frame); the runtime side then splits on root-ness: a ROOT
|
|
274
|
+
// relaunches into a freshly-minted node (park the old root `done`, boot a
|
|
275
|
+
// new broker, re-point the viewer pane), a non-root CHILD just refreshes
|
|
276
|
+
// its session id on the SAME node.
|
|
268
277
|
if (event?.reason === 'new') {
|
|
269
278
|
try {
|
|
270
|
-
|
|
279
|
+
const m = getNode(nodeId);
|
|
280
|
+
if (m?.parent == null)
|
|
281
|
+
relaunchRoot(nodeId);
|
|
282
|
+
else
|
|
283
|
+
handleNewSession(nodeId, id, sessionFile);
|
|
271
284
|
}
|
|
272
285
|
catch { /* best-effort */ }
|
|
273
286
|
// Clear in-memory context-steering so the fresh conversation starts clean.
|
|
@@ -277,13 +290,12 @@ export function registerCanvasStophook(pi) {
|
|
|
277
290
|
return;
|
|
278
291
|
}
|
|
279
292
|
// Boot / startup / resume / reload / fork → (re)bind this process to its
|
|
280
|
-
// session id, record our OS pid (the daemon's liveness signal
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// set
|
|
285
|
-
//
|
|
286
|
-
// it — otherwise a failed respawn would look identical to a successful one.
|
|
293
|
+
// session id, record our OS pid (the daemon's broker-liveness signal), and
|
|
294
|
+
// CONFIRM any pending refresh-yield. Reaching session_start proves a fresh
|
|
295
|
+
// engine actually booted, so it is now safe to clear intent='refresh' — a
|
|
296
|
+
// refresh shuts the broker down and the daemon relaunches it FRESH, which
|
|
297
|
+
// leaves intent='refresh' set until this boot clears it (the only proof the
|
|
298
|
+
// relaunch worked; otherwise a failed relaunch would look like a success).
|
|
287
299
|
const existing = getNode(nodeId);
|
|
288
300
|
// Identity (session id/file) → meta; runtime (pid, intent) → atomic row setters.
|
|
289
301
|
updateNode(nodeId, {
|
|
@@ -447,32 +459,18 @@ export function registerCanvasStophook(pi) {
|
|
|
447
459
|
// transitions node.status → 'done' synchronously. Shut down cleanly.
|
|
448
460
|
const node = getNode(nodeId);
|
|
449
461
|
if (node?.status === 'done') {
|
|
450
|
-
// TRULY-DONE (pushed `final` this turn).
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// this focus AND flip remain-on-exit OFF on %m's window so the pane
|
|
459
|
-
// closes when this pi exits (return-to-shell) instead of freezing into
|
|
460
|
-
// an orphan. We CANNOT closePane(%m) from inside %m (self-saw), but the
|
|
461
|
-
// pi is still alive mid-shutdown, so remain-on-exit-off is safe and
|
|
462
|
-
// makes tmux reap the pane on exit. An unfocused done node just shuts
|
|
463
|
-
// down (no pane anywhere, Invariant P). M is done → it owns no pane
|
|
464
|
-
// (Invariant P), so null its own presence in BOTH sub-branches before
|
|
465
|
-
// shutdown.
|
|
466
|
-
const f = focusOf(nodeId);
|
|
467
|
-
if (f !== null) {
|
|
468
|
-
const managerId = node.parent ?? subscribersOf(nodeId)[0]?.node_id ?? null;
|
|
469
|
-
if (!handFocusToManager(f.focus_id, managerId)) {
|
|
470
|
-
// Q1 return-to-shell, self-saw-safe: close the focus row + disarm the
|
|
471
|
-
// pane's freeze so it reaps on exit (we can't closePane our own pane).
|
|
472
|
-
closeFocusToShell(f.focus_id, nodeId);
|
|
462
|
+
// TRULY-DONE (pushed `final` this turn). The engine is a detached
|
|
463
|
+
// broker, not a pane, so there is no engine to hand off — just close
|
|
464
|
+
// this node's VIEWER pane (if any) and shut the broker down. The daemon
|
|
465
|
+
// won't revive a done node. tearDownNode closes the viewer focus row +
|
|
466
|
+
// pane; ctx.shutdown() exits the broker process.
|
|
467
|
+
if (focusOf(nodeId) !== null) {
|
|
468
|
+
try {
|
|
469
|
+
tearDownNode(nodeId);
|
|
473
470
|
}
|
|
471
|
+
catch { /* best-effort */ }
|
|
474
472
|
}
|
|
475
|
-
setPresence(nodeId, { pane: null, tmux_session: null, window: null });
|
|
473
|
+
setPresence(nodeId, { pane: null, tmux_session: null, window: null });
|
|
476
474
|
try {
|
|
477
475
|
ctx?.shutdown?.();
|
|
478
476
|
}
|
|
@@ -480,27 +478,15 @@ export function registerCanvasStophook(pi) {
|
|
|
480
478
|
return;
|
|
481
479
|
}
|
|
482
480
|
// (b') Refresh-yield: the node ran `crtr node yield` this turn, setting
|
|
483
|
-
// intent='refresh'.
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
// exited into a persistent shell pane). Falls back to a clean
|
|
489
|
-
// shutdown (daemon revives in a new window) only when we're not in
|
|
490
|
-
// a tmux pane.
|
|
481
|
+
// intent='refresh'. Just shut the broker down — the daemon sees the
|
|
482
|
+
// dead pid + intent='refresh' and reviveNode's relaunches the broker
|
|
483
|
+
// FRESH (no resume) so the node re-reads its roadmap. (The daemon
|
|
484
|
+
// also force-recycles a stuck refresh whose engine never exits, so a
|
|
485
|
+
// stale in-process stophook can't strand the yield — see crtrd.)
|
|
491
486
|
if (node?.intent === 'refresh') {
|
|
492
487
|
// A yield is SILENT to subscribers: the node keeps its identity and
|
|
493
|
-
// subscription edges across the revive and reports only through its
|
|
494
|
-
//
|
|
495
|
-
// — just re-exec a fresh pi in place against the roadmap.
|
|
496
|
-
const pane = process.env['TMUX_PANE'];
|
|
497
|
-
if (pane !== undefined && pane.trim() !== '') {
|
|
498
|
-
try {
|
|
499
|
-
reviveInPlace(nodeId, pane);
|
|
500
|
-
return; // respawn-pane -k tears down this pi and starts the fresh one
|
|
501
|
-
}
|
|
502
|
-
catch { /* fall through to plain shutdown */ }
|
|
503
|
-
}
|
|
488
|
+
// subscription edges across the revive and reports only through its own
|
|
489
|
+
// explicit `crtr push` calls, so there is no checkpoint push here.
|
|
504
490
|
try {
|
|
505
491
|
ctx?.shutdown?.();
|
|
506
492
|
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// inside crouter's own tsc build without a dep on the pi packages (mirrors
|
|
22
22
|
// canvas-resume.ts / canvas-nav.ts / canvas-commands.ts).
|
|
23
23
|
import { execFile } from 'node:child_process';
|
|
24
|
+
import { surfaceTmuxStyleArgs } from '../core/runtime/surface-bg.js';
|
|
24
25
|
/** Single-quote a string for safe interpolation into a `sh -c` command line —
|
|
25
26
|
* tmux runs the display-popup trailing string through the shell, so a view
|
|
26
27
|
* name with spaces or quotes must be escaped. */
|
|
@@ -59,7 +60,7 @@ export function registerCanvasView(pi) {
|
|
|
59
60
|
// bare /view → the picker; /view <name> → that view directly.
|
|
60
61
|
const cmd = name === '' ? 'crtr view pick' : `crtr view run ${shellQuote(name)}`;
|
|
61
62
|
try {
|
|
62
|
-
execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', cmd], () => { });
|
|
63
|
+
execFile('tmux', ['display-popup', '-E', '-w', '90%', '-h', '85%', ...surfaceTmuxStyleArgs(), cmd], () => { });
|
|
63
64
|
}
|
|
64
65
|
catch { /* best-effort */ }
|
|
65
66
|
return;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Called by canvas-nav (once per session_start) to expose its scheduleRender. */
|
|
2
|
+
export declare function onNavRerender(cb: () => void): void;
|
|
3
|
+
/** Called by canvas-recap after it sets/clears `crtr-recap` to nudge canvas-nav
|
|
4
|
+
* to re-assert `crtr-managers` below it. No-op if nav has not registered (e.g.
|
|
5
|
+
* nav inert, or recap-only test harness). */
|
|
6
|
+
export declare function requestNavRerender(): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// widget-order-bus.ts — a tiny in-process bus that lets canvas-recap keep its
|
|
2
|
+
// recap widget pinned ABOVE canvas-nav's manager line.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists: pi's interactive widget store (and the broker's relay) is
|
|
5
|
+
// insertion-ordered, and re-setting an existing widget key moves it to the END
|
|
6
|
+
// (bottom) — see interactive-mode.js `setExtensionWidget` (delete+set). So the
|
|
7
|
+
// LAST extension to call setWidget for an aboveEditor key renders closest to the
|
|
8
|
+
// editor. canvas-recap only paints during idle, when canvas-nav has not re-set
|
|
9
|
+
// `crtr-managers` for a long time, so a naive recap setWidget lands BELOW the
|
|
10
|
+
// manager line. The fix: after canvas-recap changes its widget, it asks
|
|
11
|
+
// canvas-nav to re-assert `crtr-managers`, which re-inserts that key after
|
|
12
|
+
// `crtr-recap` and so drops it below the recap — making the recap the topmost
|
|
13
|
+
// chrome.
|
|
14
|
+
//
|
|
15
|
+
// Both extensions load in the SAME pi process, so this module is a shared
|
|
16
|
+
// singleton via Node's module cache. No @earendil-works/* imports — compiles in
|
|
17
|
+
// crouter's own tsc build.
|
|
18
|
+
/** canvas-nav registers its re-render here on session_start; canvas-recap calls
|
|
19
|
+
* requestNavRerender() after it shows/clears the recap so the manager line
|
|
20
|
+
* re-inserts (and thus orders) below the recap widget. */
|
|
21
|
+
let navRerender;
|
|
22
|
+
/** Called by canvas-nav (once per session_start) to expose its scheduleRender. */
|
|
23
|
+
export function onNavRerender(cb) {
|
|
24
|
+
navRerender = cb;
|
|
25
|
+
}
|
|
26
|
+
/** Called by canvas-recap after it sets/clears `crtr-recap` to nudge canvas-nav
|
|
27
|
+
* to re-assert `crtr-managers` below it. No-op if nav has not registered (e.g.
|
|
28
|
+
* nav inert, or recap-only test harness). */
|
|
29
|
+
export function requestNavRerender() {
|
|
30
|
+
try {
|
|
31
|
+
navRerender?.();
|
|
32
|
+
}
|
|
33
|
+
catch { /* best-effort */ }
|
|
34
|
+
}
|
package/dist/prompts/view.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export interface ViewScaffoldVars {
|
|
|
3
3
|
title: string;
|
|
4
4
|
description: string;
|
|
5
5
|
}
|
|
6
|
-
/** Build
|
|
7
|
-
export declare function viewScaffold(v: ViewScaffoldVars): string
|
|
6
|
+
/** Build every file of a freshly scaffolded dual-target view, keyed by filename. */
|
|
7
|
+
export declare function viewScaffold(v: ViewScaffoldVars): Record<string, string>;
|
package/dist/prompts/view.js
CHANGED
|
@@ -1,101 +1,173 @@
|
|
|
1
|
-
// Scaffold
|
|
2
|
-
// `
|
|
3
|
-
// `crtr view run <name>`
|
|
1
|
+
// Scaffold stubs for `crtr view new`. Returns the four files of a dual-target
|
|
2
|
+
// view directory — a portable `core.mjs` plus the TUI / web / text presenters —
|
|
3
|
+
// that runs as-is via `crtr view run <name>` (TUI), `crtr view serve <name>`
|
|
4
|
+
// (web), and `crtr view run <name> | cat` (static snapshot).
|
|
4
5
|
//
|
|
5
|
-
// Authoring guidance lives INSIDE
|
|
6
|
+
// Authoring guidance lives INSIDE each stub (the comment block at the top of the
|
|
6
7
|
// generated file), so a view author reads the contract right where they edit.
|
|
7
8
|
/** Escape a value for embedding inside a single-quoted JS string literal. */
|
|
8
9
|
function jsStr(s) {
|
|
9
10
|
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
10
11
|
}
|
|
11
|
-
/**
|
|
12
|
-
|
|
12
|
+
/** The portable core: manifest · init · sources · intents. Runs in BOTH Node
|
|
13
|
+
* (the TUI host) and the browser (the web bundle) — imports NOTHING. */
|
|
14
|
+
function coreStub(v) {
|
|
13
15
|
const id = jsStr(v.id);
|
|
14
16
|
const title = jsStr(v.title);
|
|
15
17
|
const description = jsStr(v.description);
|
|
16
|
-
return `// ${v.title} —
|
|
18
|
+
return `// ${v.title} — portable core (manifest · init · sources · intents).
|
|
17
19
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
20
|
+
// Runs in BOTH Node (the TUI host) and the browser (the web bundle), so it
|
|
21
|
+
// imports NOTHING — no \`node:*\`, no crtr. ALL state + behavior live here;
|
|
22
|
+
// presenters (tui.mjs / web.jsx) are pure reads that dispatch named intents.
|
|
21
23
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// init(host) build initial state — keep it CHEAP and sync-ish;
|
|
26
|
-
// do NOT do slow fetches here (do them in refresh).
|
|
27
|
-
// refresh(state,host) async data load; the host runs it on launch, every
|
|
28
|
-
// manifest.refreshMs (if set), and whenever a key handler
|
|
29
|
-
// returns { type: 'refresh' }. Mutate state in place.
|
|
30
|
-
// render(state,draw,content) paint into the \`content\` Rect via draw.* —
|
|
31
|
-
// a pure read of state; NEVER write ANSI yourself.
|
|
32
|
-
// onKey(k,state,host) handle one keystroke; mutate state; return a ViewAction.
|
|
33
|
-
// dump(state) plain text for the piped / non-TTY path (exit 0).
|
|
24
|
+
// Run it: crtr view run ${v.id} (TUI, inside tmux)
|
|
25
|
+
// crtr view run ${v.id} | cat (static snapshot, exits 0)
|
|
26
|
+
// crtr view serve ${v.id} (React + Tailwind web page)
|
|
34
27
|
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
28
|
+
// THE CONTRACT:
|
|
29
|
+
// manifest { id, title, description, refreshMs? } — id MUST equal
|
|
30
|
+
// this view's directory name.
|
|
31
|
+
// init(opts) cheap, SYNCHRONOUS initial state — NO fetch, NO screen.
|
|
32
|
+
// CLI flags arrive in \`opts\` (crtr view run --port/--target).
|
|
33
|
+
// sources { request, parse } READ descriptors. The host's transport
|
|
34
|
+
// runs request() (exec/file/http) and hands parse() the bytes
|
|
35
|
+
// — your parse is pure, runs in Node AND the browser.
|
|
36
|
+
// intents semantic actions both targets dispatch. State updates are
|
|
37
|
+
// IMMUTABLE via ctx.set; async effects (ctx.resolve a source)
|
|
38
|
+
// live in the same handler. ctx.signal raises chrome signals.
|
|
46
39
|
|
|
47
|
-
/**
|
|
48
|
-
const
|
|
40
|
+
/** A READ source: a transport-agnostic request + a pure parse. */
|
|
41
|
+
export const clockSource = {
|
|
42
|
+
id: 'clock',
|
|
43
|
+
request: () => ({ kind: 'exec', bin: 'date', args: [] }),
|
|
44
|
+
parse: (raw) =>
|
|
45
|
+
raw.ok && raw.exitCode === 0
|
|
46
|
+
? { ok: true, data: raw.stdout.trim() }
|
|
47
|
+
: { ok: false, error: { kind: 'clock-failed', display: {
|
|
48
|
+
headline: 'Could not read the clock',
|
|
49
|
+
explanation: raw.stderr || 'the date command failed',
|
|
50
|
+
nextStep: 'Check the \`date\` binary is on PATH.',
|
|
51
|
+
level: 'error', blocking: true } } },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** @type {import('@crouton-kit/crouter/dist/core/view/contract.js').ViewCore} */
|
|
55
|
+
const core = {
|
|
49
56
|
manifest: {
|
|
50
57
|
id: '${id}',
|
|
51
58
|
title: '${title}',
|
|
52
59
|
description: '${description}',
|
|
53
60
|
// refreshMs: 30000, // uncomment to auto-poll; omit ⇒ on-demand (g) only
|
|
54
|
-
keymap: [
|
|
55
|
-
{ keys: 'g', label: 'refresh' },
|
|
56
|
-
{ keys: 'q', label: 'quit' },
|
|
57
|
-
],
|
|
58
61
|
},
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return { count: 0, port: host.options.port ?? null };
|
|
63
|
-
},
|
|
63
|
+
// Cheap, synchronous setup. Stash CLI flags off \`opts\` here.
|
|
64
|
+
init: (opts) => ({ count: 0, clock: null, port: opts.port ?? null }),
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
// Load your data here (shell out, fetch, read files). Mutate \`state\`.
|
|
67
|
-
state.count += 1;
|
|
68
|
-
host.setStatus(\`refreshed \${state.count}×\`);
|
|
69
|
-
},
|
|
66
|
+
sources: { clockSource },
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
|
|
68
|
+
intents: {
|
|
69
|
+
// Async intent: ctx.set updates state immutably; ctx.resolve runs a source
|
|
70
|
+
// through the host transport. The host runs 'refresh' on launch, every
|
|
71
|
+
// manifest.refreshMs (if set), and on the \`g\` key (see tui.mjs).
|
|
72
|
+
async refresh(ctx) {
|
|
73
|
+
ctx.signal.setStatus('Reading clock…');
|
|
74
|
+
const r = await ctx.resolve(clockSource);
|
|
75
|
+
if (!r.ok) { ctx.signal.setBanner(r.error.display.headline, r.error.display.level); ctx.signal.setStatus(null); return; }
|
|
76
|
+
ctx.set((s) => ({ ...s, count: s.count + 1, clock: r.data }));
|
|
77
|
+
ctx.signal.clearBanner();
|
|
78
|
+
ctx.signal.setStatus(\`refreshed \${ctx.state.count}×\`);
|
|
79
|
+
},
|
|
80
|
+
quit: (ctx) => ctx.signal.quit(),
|
|
81
81
|
},
|
|
82
|
+
};
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
export default core;
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
/** The TUI presenter: render(state, draw, content) + keymap. Node only. */
|
|
88
|
+
function tuiStub(v) {
|
|
89
|
+
const title = jsStr(v.title);
|
|
90
|
+
return `// ${v.title} — TUI presenter: render(state, draw, content) + keymap.
|
|
91
|
+
//
|
|
92
|
+
// A pure read of state — paint via draw.*; NEVER write ANSI. Keys map to the
|
|
93
|
+
// core's intents (no logic here; the keymap names an intent, the host dispatches).
|
|
94
|
+
//
|
|
95
|
+
// draw API (all absolute cells inside \`content\`):
|
|
96
|
+
// draw.text(row,col,text,style?) · draw.spans(row,col,spans,maxWidth?)
|
|
97
|
+
// draw.hline(row,fromCol?,toCol?) · draw.box(rect,title?)
|
|
98
|
+
// draw.columns(rect,[1,2]) -> Rect[] · draw.list(rect,items,cursor,scroll)
|
|
99
|
+
// style: { fg, bg, bold, dim, reverse } (fg/bg honored only when color is on)
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
\`refreshed \${state.count} time(s)\`,
|
|
94
|
-
\`port option: \${state.port ?? '(none)'}\`,
|
|
95
|
-
].join('\\n');
|
|
96
|
-
},
|
|
97
|
-
};
|
|
101
|
+
export const keymap = [
|
|
102
|
+
{ keys: ['g'], intent: 'refresh', hint: { keys: 'g', label: 'refresh' } },
|
|
103
|
+
{ keys: ['q'], intent: 'quit', hint: { keys: 'q', label: 'quit' } },
|
|
104
|
+
];
|
|
98
105
|
|
|
99
|
-
export
|
|
106
|
+
export function render(state, draw, content) {
|
|
107
|
+
draw.text(content.row, content.col, '${title}', { bold: true });
|
|
108
|
+
draw.text(content.row + 2, content.col, \`clock: \${state.clock ?? '(loading…)'}\`);
|
|
109
|
+
draw.text(content.row + 3, content.col, \`refreshed \${state.count} time(s)\`, { dim: true });
|
|
110
|
+
draw.text(content.row + 4, content.col, \`port option: \${state.port ?? '(none)'}\`, { dim: true });
|
|
111
|
+
draw.text(content.row + 6, content.col, 'Edit core.mjs + tui.mjs to build your view — g refreshes, q quits.', { dim: true });
|
|
112
|
+
}
|
|
100
113
|
`;
|
|
101
114
|
}
|
|
115
|
+
/** The web presenter: a React + Tailwind component. Browser only (Vite owns the
|
|
116
|
+
* JSX + Tailwind transpile; this file is NEVER Node-imported). */
|
|
117
|
+
function webStub(v) {
|
|
118
|
+
const title = jsStr(v.title);
|
|
119
|
+
return `// ${v.title} — web presenter: a React + Tailwind component.
|
|
120
|
+
//
|
|
121
|
+
// A pure function of \`state\`; DOM events call \`dispatch(intent)\`. Consumed ONLY
|
|
122
|
+
// by \`crtr view serve\` (Vite owns JSX + Tailwind) — never Node-imported. The
|
|
123
|
+
// SAME state + intents the TUI reads; zero shared render code (an intentional fork).
|
|
124
|
+
|
|
125
|
+
export default function View({ state, dispatch }) {
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
className="font-mono text-sm p-4 outline-none"
|
|
129
|
+
tabIndex={0}
|
|
130
|
+
onKeyDown={(e) => { if (e.key === 'g') dispatch('refresh'); }}
|
|
131
|
+
>
|
|
132
|
+
<h1 className="text-lg font-bold">${title}</h1>
|
|
133
|
+
<p className="mt-2">clock: {state.clock ?? '(loading…)'}</p>
|
|
134
|
+
<p className="text-gray-500">refreshed {state.count} time(s)</p>
|
|
135
|
+
<button
|
|
136
|
+
className="mt-3 rounded bg-blue-600 px-2 py-1 text-white hover:bg-blue-500"
|
|
137
|
+
onClick={() => dispatch('refresh')}
|
|
138
|
+
>
|
|
139
|
+
Refresh
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
/** The text presenter: the piped / non-TTY snapshot. Node only. Optional — omit
|
|
147
|
+
* it and the host synthesizes a generic one-line dump. */
|
|
148
|
+
function textStub(v) {
|
|
149
|
+
const title = jsStr(v.title);
|
|
150
|
+
return `// ${v.title} — text presenter: the piped / non-TTY snapshot (exits 0).
|
|
151
|
+
//
|
|
152
|
+
// A pure read of current state. Omit this file and the host synthesizes a
|
|
153
|
+
// one-line dump (\`<title> — <n> items\`).
|
|
154
|
+
|
|
155
|
+
export function dump(state) {
|
|
156
|
+
return [
|
|
157
|
+
'${title}',
|
|
158
|
+
'',
|
|
159
|
+
\`clock: \${state.clock ?? '(none)'}\`,
|
|
160
|
+
\`refreshed \${state.count} time(s)\`,
|
|
161
|
+
].join('\\n');
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
/** Build every file of a freshly scaffolded dual-target view, keyed by filename. */
|
|
166
|
+
export function viewScaffold(v) {
|
|
167
|
+
return {
|
|
168
|
+
'core.mjs': coreStub(v),
|
|
169
|
+
'tui.mjs': tuiStub(v),
|
|
170
|
+
'web.jsx': webStub(v),
|
|
171
|
+
'text.mjs': textStub(v),
|
|
172
|
+
};
|
|
173
|
+
}
|