@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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { Draw, Rect } from '../tui/draw.js';
|
|
2
|
+
export interface ViewManifest {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
description: string;
|
|
7
|
+
refreshMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Severity of a {@link HostSignals.setBanner} banner — drives glyph + hue and
|
|
10
|
+
* the derived state chip (error→blocked, action→attention, info→neutral). */
|
|
11
|
+
export type BannerLevel = 'info' | 'action' | 'error';
|
|
12
|
+
export interface Banner {
|
|
13
|
+
msg: string;
|
|
14
|
+
level: BannerLevel;
|
|
15
|
+
}
|
|
16
|
+
/** Optional host context passed to the text presenter's dump on the piped path. */
|
|
17
|
+
export interface DumpContext {
|
|
18
|
+
banner: Banner | null;
|
|
19
|
+
}
|
|
20
|
+
/** Semantic chrome signals the core raises; each target renders them in its own
|
|
21
|
+
* idiom (TUI footer/banner/title-chip vs web status line/alert bar/pill). */
|
|
22
|
+
export interface HostSignals {
|
|
23
|
+
setStatus(msg: string | null): void;
|
|
24
|
+
setBanner(msg: string, level: BannerLevel): void;
|
|
25
|
+
clearBanner(): void;
|
|
26
|
+
setSubtitle(s: string | null): void;
|
|
27
|
+
/** Interaction-mode chip override (compose/react); null returns to derived. */
|
|
28
|
+
setMode(mode: string | null): void;
|
|
29
|
+
/** TUI: leave the pane; web: no-op/close tab. */
|
|
30
|
+
quit(): void;
|
|
31
|
+
}
|
|
32
|
+
/** The host-tracked chrome record both targets render. */
|
|
33
|
+
export interface ChromeState {
|
|
34
|
+
status: string | null;
|
|
35
|
+
banner: Banner | null;
|
|
36
|
+
subtitle: string | null;
|
|
37
|
+
mode: string | null;
|
|
38
|
+
busy: boolean;
|
|
39
|
+
loaded: boolean;
|
|
40
|
+
lastRefresh: number;
|
|
41
|
+
}
|
|
42
|
+
/** A transport-agnostic request descriptor the host fulfills. The core never
|
|
43
|
+
* executes anything itself — it describes WHAT to run/read/fetch and the
|
|
44
|
+
* host's Transport decides HOW (local exec today, a cloud endpoint later). */
|
|
45
|
+
export type SourceRequest = {
|
|
46
|
+
kind: 'exec';
|
|
47
|
+
bin: string;
|
|
48
|
+
args: string[];
|
|
49
|
+
cwd?: string;
|
|
50
|
+
stdin?: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'file';
|
|
53
|
+
path: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'http';
|
|
56
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
57
|
+
url: string;
|
|
58
|
+
headers?: Record<string, string>;
|
|
59
|
+
body?: string;
|
|
60
|
+
};
|
|
61
|
+
export interface RawResponse {
|
|
62
|
+
ok: boolean;
|
|
63
|
+
exitCode?: number;
|
|
64
|
+
status?: number;
|
|
65
|
+
stdout: string;
|
|
66
|
+
stderr: string;
|
|
67
|
+
}
|
|
68
|
+
/** Typed error with a render-ready display. Presenters render `display`
|
|
69
|
+
* VERBATIM and never branch on `kind` (the view-internal taxonomy). */
|
|
70
|
+
export interface SourceError {
|
|
71
|
+
kind: string;
|
|
72
|
+
display: {
|
|
73
|
+
headline: string;
|
|
74
|
+
explanation: string;
|
|
75
|
+
nextStep: string;
|
|
76
|
+
level: BannerLevel;
|
|
77
|
+
blocking: boolean;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export type Result<T> = {
|
|
81
|
+
ok: true;
|
|
82
|
+
data: T;
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
error: SourceError;
|
|
86
|
+
};
|
|
87
|
+
/** A READ: declarative descriptor + pure parse + optional cadence. Resolved by
|
|
88
|
+
* the host through its Transport via `ctx.resolve(source, args)`. */
|
|
89
|
+
export interface Source<T, A = void> {
|
|
90
|
+
id: string;
|
|
91
|
+
request(args: A): SourceRequest;
|
|
92
|
+
parse(raw: RawResponse): Result<T>;
|
|
93
|
+
refreshMs?: number;
|
|
94
|
+
}
|
|
95
|
+
/** A WRITE: same {request, parse} pair, no cadence — invoked by an intent via
|
|
96
|
+
* `ctx.execute(command, args)`. */
|
|
97
|
+
export interface Command<T, A = void> {
|
|
98
|
+
id: string;
|
|
99
|
+
request(args: A): SourceRequest;
|
|
100
|
+
parse(raw: RawResponse): Result<T>;
|
|
101
|
+
}
|
|
102
|
+
/** A semantic action. State updates are immutable via `ctx.set`; async effects
|
|
103
|
+
* (transport calls) live in the same handler — a thunk, not a pure reducer.
|
|
104
|
+
* Sync intents call `ctx.set` once; async intents `await ctx.resolve/execute`
|
|
105
|
+
* between `set`s. The host serializes async intents in its single-flight lane. */
|
|
106
|
+
export type Intent<S, P = void> = (ctx: IntentCtx<S>, payload: P) => void | Promise<void>;
|
|
107
|
+
export interface IntentCtx<S> {
|
|
108
|
+
/** Snapshot of state at read time. */
|
|
109
|
+
readonly state: S;
|
|
110
|
+
/** Immutable update → triggers a re-render. Value or (prev)=>next fn. */
|
|
111
|
+
set(next: S | ((prev: S) => S)): void;
|
|
112
|
+
/** Run a READ source through the host's transport → typed Result. */
|
|
113
|
+
resolve<T, A>(source: Source<T, A>, args?: A): Promise<Result<T>>;
|
|
114
|
+
/** Run a WRITE command through the host's transport → typed Result. */
|
|
115
|
+
execute<T, A>(command: Command<T, A>, args?: A): Promise<Result<T>>;
|
|
116
|
+
/** Semantic chrome signals (status/banner/subtitle/mode/quit). */
|
|
117
|
+
signal: HostSignals;
|
|
118
|
+
/** Chain another intent by name. */
|
|
119
|
+
dispatch(intent: string, payload?: unknown): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
export interface ViewCore<S = unknown> {
|
|
122
|
+
manifest: ViewManifest;
|
|
123
|
+
/** Cheap, synchronous initial state. No fetch, no screen. The host mounts,
|
|
124
|
+
* paints a loading frame, then dispatches the first 'refresh'. */
|
|
125
|
+
init(opts: Readonly<Record<string, string>>): S;
|
|
126
|
+
/** Declarative READ dependencies the host resolves through the transport. */
|
|
127
|
+
sources?: Record<string, Source<any, any>>;
|
|
128
|
+
/** WRITE descriptors invoked from intents. */
|
|
129
|
+
commands?: Record<string, Command<any, any>>;
|
|
130
|
+
/** Semantic actions. Both presenters emit these. */
|
|
131
|
+
intents: Record<string, Intent<S, any>>;
|
|
132
|
+
}
|
|
133
|
+
export interface KeyHint {
|
|
134
|
+
keys: string;
|
|
135
|
+
label: string;
|
|
136
|
+
}
|
|
137
|
+
export type KeyBinding<S> = {
|
|
138
|
+
keys: string[];
|
|
139
|
+
intent: string;
|
|
140
|
+
payload?: (state: S) => unknown;
|
|
141
|
+
when?: (state: S) => boolean;
|
|
142
|
+
hint?: KeyHint;
|
|
143
|
+
} | {
|
|
144
|
+
/** Text-capture binding: while `when(state)` is true the host runs a
|
|
145
|
+
* built-in line-edit buffer over raw printable/backspace keys and
|
|
146
|
+
* dispatches `capture` with the next draft value on each edit. */
|
|
147
|
+
capture: string;
|
|
148
|
+
when: (state: S) => boolean;
|
|
149
|
+
hint?: KeyHint;
|
|
150
|
+
};
|
|
151
|
+
export interface TuiPresenter<S = unknown> {
|
|
152
|
+
/** Pure read of state, paints via draw.*; never ANSI. */
|
|
153
|
+
render(state: S, draw: Draw, content: Rect): void;
|
|
154
|
+
/** Pure input→intent mapping. */
|
|
155
|
+
keymap: KeyBinding<S>[];
|
|
156
|
+
}
|
|
157
|
+
/** Props handed to web.jsx's default-export React component. The component is a
|
|
158
|
+
* pure function of state; DOM events call `dispatch`. (Typed loosely here so
|
|
159
|
+
* the core contract carries no React type dependency.) */
|
|
160
|
+
export interface ViewProps<S = unknown> {
|
|
161
|
+
state: S;
|
|
162
|
+
dispatch: (intent: string, payload?: unknown) => void;
|
|
163
|
+
chrome: ChromeState;
|
|
164
|
+
}
|
|
165
|
+
export type WebPresenter<S = unknown> = (props: ViewProps<S>) => unknown;
|
|
166
|
+
export interface TextPresenter<S = unknown> {
|
|
167
|
+
/** Static text for the non-TTY / piped path. Snapshot of current state. */
|
|
168
|
+
dump(state: S, ctx?: DumpContext): string;
|
|
169
|
+
}
|
|
170
|
+
export declare function ok<T>(data: T): Result<T>;
|
|
171
|
+
export declare function fail<T = never>(error: SourceError): Result<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// contract.ts — the dual-target view contract (portable core + thin presenters).
|
|
2
|
+
//
|
|
3
|
+
// One view definition renders to two targets — the tmux TUI (`crtr view run`)
|
|
4
|
+
// and a React+Tailwind web page (`crtr view serve`) — from ONE portable core.
|
|
5
|
+
// On disk a view is a directory of up to four files, each loaded only by the
|
|
6
|
+
// runtime that can execute it:
|
|
7
|
+
//
|
|
8
|
+
// <view>/
|
|
9
|
+
// core.mjs REQUIRED manifest · init · sources · commands · intents.
|
|
10
|
+
// Runs in BOTH Node and the browser. Imports NOTHING —
|
|
11
|
+
// no `node:*`, no crtr. Pure JS + the injected ctx.
|
|
12
|
+
// tui.mjs optional render(state, draw, content) + keymap (Node only)
|
|
13
|
+
// web.jsx optional default React component (Tailwind) (browser only)
|
|
14
|
+
// text.mjs optional dump(state, ctx) for the piped path (Node only)
|
|
15
|
+
//
|
|
16
|
+
// The invariant: ALL state + ALL behavior live in the core; presenters are pure
|
|
17
|
+
// reads of state that emit named intents. It is enforced structurally — only
|
|
18
|
+
// `core.mjs` ever sees a mutating `IntentCtx`; presenters receive `state` plus a
|
|
19
|
+
// `dispatch`/`keymap` that names intents, so a presenter has no path to data or
|
|
20
|
+
// logic.
|
|
21
|
+
// ── Result helpers (re-exported for view authors' convenience in tests) ─────
|
|
22
|
+
export function ok(data) { return { ok: true, data }; }
|
|
23
|
+
export function fail(error) { return { ok: false, error }; }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ViewCore, TuiPresenter, TextPresenter } from './contract.js';
|
|
2
|
+
export interface ResolvedView {
|
|
3
|
+
id: string;
|
|
4
|
+
dir: string;
|
|
5
|
+
scope: 'project' | 'user' | 'builtin';
|
|
6
|
+
core: string;
|
|
7
|
+
tui: string | null;
|
|
8
|
+
web: string | null;
|
|
9
|
+
text: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Scope search project→user→builtin; first hit wins. null if no scope holds a
|
|
12
|
+
* `<name>/core.mjs`. */
|
|
13
|
+
export declare function resolveView(name: string): ResolvedView | null;
|
|
14
|
+
/** Find a directory named `name` across scopes even when it holds no core.mjs —
|
|
15
|
+
* lets a caller distinguish "no such view" from "a dir that isn't a valid view"
|
|
16
|
+
* (e.g. a pre-contract `view.mjs`-only dir) and name the contract in the error. */
|
|
17
|
+
export declare function findViewDir(name: string): string | null;
|
|
18
|
+
/** Enumerate every resolvable core-shaped view across scopes (first scope wins
|
|
19
|
+
* per id). Targets per view are derivable from the tui/web/text fields. */
|
|
20
|
+
export declare function listViews(): ResolvedView[];
|
|
21
|
+
/** Import + validate the portable core. Throws a guided error if malformed. */
|
|
22
|
+
export declare function loadCore(r: ResolvedView): Promise<ViewCore>;
|
|
23
|
+
/** Import + validate the TUI presenter. Call requireTui first. */
|
|
24
|
+
export declare function loadTui(r: ResolvedView): Promise<TuiPresenter>;
|
|
25
|
+
/** Import the text presenter if present; null otherwise (the host synthesizes a
|
|
26
|
+
* generic one-line dump when a view ships no text.mjs). */
|
|
27
|
+
export declare function loadText(r: ResolvedView): Promise<TextPresenter | null>;
|
|
28
|
+
/** Per-host presenter validation: `view run` requires a TUI presenter. */
|
|
29
|
+
export declare function requireTui(r: ResolvedView): void;
|
|
30
|
+
/** Per-host presenter validation: `view serve` requires a web presenter. */
|
|
31
|
+
export declare function requireWeb(r: ResolvedView): void;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// loader.ts — resolve + load dual-target (core.mjs) views across scopes.
|
|
2
|
+
//
|
|
3
|
+
// A view = a directory containing `core.mjs` (the portable core). Up to three
|
|
4
|
+
// presenter files sit beside it: `tui.mjs` (Node), `web.jsx`/`web.tsx` (browser,
|
|
5
|
+
// via Vite — NEVER Node-imported), `text.mjs` (Node, piped path). Targets are
|
|
6
|
+
// derived from which presenter files exist — no manifest field.
|
|
7
|
+
//
|
|
8
|
+
// Resolution order: project → user → builtin, first hit wins (scope.ts
|
|
9
|
+
// viewsDir). Node-side loads are plain `import(pathToFileURL())` — no
|
|
10
|
+
// transpile, no bundle. A directory without a `core.mjs` is not a view.
|
|
11
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { pathToFileURL } from 'node:url';
|
|
14
|
+
import { viewsDir } from '../scope.js';
|
|
15
|
+
import { InputError } from '../io.js';
|
|
16
|
+
const SCOPE_ORDER = ['project', 'user', 'builtin'];
|
|
17
|
+
function probe(dir, scope, id) {
|
|
18
|
+
const core = join(dir, 'core.mjs');
|
|
19
|
+
if (!existsSync(core))
|
|
20
|
+
return null;
|
|
21
|
+
const tui = join(dir, 'tui.mjs');
|
|
22
|
+
const webJsx = join(dir, 'web.jsx');
|
|
23
|
+
const webTsx = join(dir, 'web.tsx');
|
|
24
|
+
const text = join(dir, 'text.mjs');
|
|
25
|
+
return {
|
|
26
|
+
id, dir, scope, core,
|
|
27
|
+
tui: existsSync(tui) ? tui : null,
|
|
28
|
+
web: existsSync(webJsx) ? webJsx : existsSync(webTsx) ? webTsx : null,
|
|
29
|
+
text: existsSync(text) ? text : null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Scope search project→user→builtin; first hit wins. null if no scope holds a
|
|
33
|
+
* `<name>/core.mjs`. */
|
|
34
|
+
export function resolveView(name) {
|
|
35
|
+
for (const scope of SCOPE_ORDER) {
|
|
36
|
+
const root = viewsDir(scope);
|
|
37
|
+
if (!root)
|
|
38
|
+
continue;
|
|
39
|
+
const r = probe(join(root, name), scope, name);
|
|
40
|
+
if (r)
|
|
41
|
+
return r;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/** Find a directory named `name` across scopes even when it holds no core.mjs —
|
|
46
|
+
* lets a caller distinguish "no such view" from "a dir that isn't a valid view"
|
|
47
|
+
* (e.g. a pre-contract `view.mjs`-only dir) and name the contract in the error. */
|
|
48
|
+
export function findViewDir(name) {
|
|
49
|
+
for (const scope of SCOPE_ORDER) {
|
|
50
|
+
const root = viewsDir(scope);
|
|
51
|
+
if (!root)
|
|
52
|
+
continue;
|
|
53
|
+
const dir = join(root, name);
|
|
54
|
+
try {
|
|
55
|
+
if (statSync(dir).isDirectory())
|
|
56
|
+
return dir;
|
|
57
|
+
}
|
|
58
|
+
catch { /* not here */ }
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/** Enumerate every resolvable core-shaped view across scopes (first scope wins
|
|
63
|
+
* per id). Targets per view are derivable from the tui/web/text fields. */
|
|
64
|
+
export function listViews() {
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const scope of SCOPE_ORDER) {
|
|
68
|
+
const root = viewsDir(scope);
|
|
69
|
+
if (!root || !existsSync(root))
|
|
70
|
+
continue;
|
|
71
|
+
let names;
|
|
72
|
+
try {
|
|
73
|
+
names = readdirSync(root);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
for (const name of names.sort()) {
|
|
79
|
+
if (seen.has(name))
|
|
80
|
+
continue; // first scope wins
|
|
81
|
+
const dir = join(root, name);
|
|
82
|
+
let isDir = false;
|
|
83
|
+
try {
|
|
84
|
+
isDir = statSync(dir).isDirectory();
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
isDir = false;
|
|
88
|
+
}
|
|
89
|
+
if (!isDir)
|
|
90
|
+
continue;
|
|
91
|
+
const r = probe(dir, scope, name);
|
|
92
|
+
if (!r)
|
|
93
|
+
continue;
|
|
94
|
+
seen.add(name);
|
|
95
|
+
out.push(r);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/** Import + validate the portable core. Throws a guided error if malformed. */
|
|
101
|
+
export async function loadCore(r) {
|
|
102
|
+
let mod;
|
|
103
|
+
try {
|
|
104
|
+
mod = (await import(pathToFileURL(r.core).href));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
throw guided(r, 'core.mjs', `failed to import: ${errText(e)}`);
|
|
108
|
+
}
|
|
109
|
+
const c = mod['default'];
|
|
110
|
+
if (!c || typeof c !== 'object')
|
|
111
|
+
throw guided(r, 'core.mjs', 'no default export that is a ViewCore object');
|
|
112
|
+
const m = c.manifest;
|
|
113
|
+
if (!m || typeof m.id !== 'string' || typeof m.title !== 'string') {
|
|
114
|
+
throw guided(r, 'core.mjs', 'manifest.id and manifest.title must be strings');
|
|
115
|
+
}
|
|
116
|
+
if (typeof c.init !== 'function')
|
|
117
|
+
throw guided(r, 'core.mjs', 'init() is required');
|
|
118
|
+
if (!c.intents || typeof c.intents !== 'object')
|
|
119
|
+
throw guided(r, 'core.mjs', 'intents must be an object of intent functions');
|
|
120
|
+
return c;
|
|
121
|
+
}
|
|
122
|
+
/** Import + validate the TUI presenter. Call requireTui first. */
|
|
123
|
+
export async function loadTui(r) {
|
|
124
|
+
if (!r.tui)
|
|
125
|
+
throw notTarget(r, 'tui');
|
|
126
|
+
let mod;
|
|
127
|
+
try {
|
|
128
|
+
mod = (await import(pathToFileURL(r.tui).href));
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
throw guided(r, 'tui.mjs', `failed to import: ${errText(e)}`);
|
|
132
|
+
}
|
|
133
|
+
// Accept named exports { render, keymap } or a default-exported object.
|
|
134
|
+
const candidate = (typeof mod['render'] === 'function' ? mod : mod['default']);
|
|
135
|
+
if (!candidate || typeof candidate.render !== 'function')
|
|
136
|
+
throw guided(r, 'tui.mjs', 'render(state, draw, content) is required');
|
|
137
|
+
if (!Array.isArray(candidate.keymap))
|
|
138
|
+
throw guided(r, 'tui.mjs', 'keymap must be an array of key bindings');
|
|
139
|
+
return candidate;
|
|
140
|
+
}
|
|
141
|
+
/** Import the text presenter if present; null otherwise (the host synthesizes a
|
|
142
|
+
* generic one-line dump when a view ships no text.mjs). */
|
|
143
|
+
export async function loadText(r) {
|
|
144
|
+
if (!r.text)
|
|
145
|
+
return null;
|
|
146
|
+
let mod;
|
|
147
|
+
try {
|
|
148
|
+
mod = (await import(pathToFileURL(r.text).href));
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
throw guided(r, 'text.mjs', `failed to import: ${errText(e)}`);
|
|
152
|
+
}
|
|
153
|
+
const candidate = (typeof mod['dump'] === 'function' ? mod : mod['default']);
|
|
154
|
+
if (!candidate || typeof candidate.dump !== 'function')
|
|
155
|
+
throw guided(r, 'text.mjs', 'dump(state, ctx) is required');
|
|
156
|
+
return candidate;
|
|
157
|
+
}
|
|
158
|
+
/** Per-host presenter validation: `view run` requires a TUI presenter. */
|
|
159
|
+
export function requireTui(r) {
|
|
160
|
+
if (!r.tui) {
|
|
161
|
+
throw new InputError({
|
|
162
|
+
error: 'view_web_only',
|
|
163
|
+
message: `'${r.id}' is web-only — it ships no tui.mjs presenter`,
|
|
164
|
+
next: `Run \`crtr view serve ${r.id}\` to open it in the browser.`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Per-host presenter validation: `view serve` requires a web presenter. */
|
|
169
|
+
export function requireWeb(r) {
|
|
170
|
+
if (!r.web) {
|
|
171
|
+
throw new InputError({
|
|
172
|
+
error: 'view_tui_only',
|
|
173
|
+
message: `'${r.id}' is tui-only — it ships no web.jsx presenter`,
|
|
174
|
+
next: `Run \`crtr view run ${r.id}\` to open it in a tmux pane.`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function notTarget(r, target) {
|
|
179
|
+
return new Error(`view "${r.id}" has no ${target} presenter`);
|
|
180
|
+
}
|
|
181
|
+
function guided(r, file, problem) {
|
|
182
|
+
return new Error(`invalid view "${r.id}" (${r.scope}) at ${join(r.dir, file)}: ${problem}.\n` +
|
|
183
|
+
'A dual-target view dir holds core.mjs (default export: { manifest: { id, title }, init(), intents }) ' +
|
|
184
|
+
'plus optional tui.mjs (render + keymap), web.jsx, text.mjs (dump).');
|
|
185
|
+
}
|
|
186
|
+
function errText(e) {
|
|
187
|
+
return e instanceof Error ? e.message : String(e);
|
|
188
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Transport } from './transport.js';
|
|
2
|
+
export interface LocalTransportOptions {
|
|
3
|
+
/** Working directory for `exec` requests that don't carry their own cwd.
|
|
4
|
+
* Defaults to process.cwd(). */
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function createLocalTransport(opts?: LocalTransportOptions): Transport;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// transport-local.ts — the BUILT local transport: fulfills SourceRequests on
|
|
2
|
+
// this machine. exec→execFile (cwd = the view's cwd unless the request says
|
|
3
|
+
// otherwise), file→readFile, http→fetch.
|
|
4
|
+
//
|
|
5
|
+
// Never throws. Transport-level failure (binary missing, file unreadable,
|
|
6
|
+
// network down) ⇒ { ok:false, stderr }. A spawned process exiting non-zero is
|
|
7
|
+
// transport-level SUCCESS (ok:true + exitCode) — classifying that is the
|
|
8
|
+
// source's parse() job.
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
import { readFile } from 'node:fs/promises';
|
|
11
|
+
const MAX_BUFFER = 10 * 1024 * 1024;
|
|
12
|
+
export function createLocalTransport(opts = {}) {
|
|
13
|
+
const baseCwd = opts.cwd ?? process.cwd();
|
|
14
|
+
return {
|
|
15
|
+
async send(req) {
|
|
16
|
+
switch (req.kind) {
|
|
17
|
+
case 'exec': return sendExec(req, baseCwd);
|
|
18
|
+
case 'file': return sendFile(req);
|
|
19
|
+
case 'http': return sendHttp(req);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function sendExec(req, baseCwd) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const child = execFile(req.bin, req.args, { cwd: req.cwd ?? baseCwd, maxBuffer: MAX_BUFFER, encoding: 'utf8' }, (err, stdout, stderr) => {
|
|
27
|
+
if (!err) {
|
|
28
|
+
resolve({ ok: true, exitCode: 0, stdout, stderr });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const code = err.code;
|
|
32
|
+
if (typeof code === 'number') {
|
|
33
|
+
// Ran but exited non-zero: transport-level success; parse() classifies.
|
|
34
|
+
resolve({ ok: true, exitCode: code, stdout: stdout ?? '', stderr: stderr ?? '' });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (err.signal) {
|
|
38
|
+
// Killed by signal: it ran; surface as a non-zero exit.
|
|
39
|
+
resolve({ ok: true, exitCode: 1, stdout: stdout ?? '', stderr: stderr ?? '' });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Spawn-level failure (ENOENT/EACCES/maxBuffer): transport failure.
|
|
43
|
+
const msg = code === 'ENOENT' ? `${req.bin}: command not found` : err.message;
|
|
44
|
+
resolve({ ok: false, stdout: stdout ?? '', stderr: msg });
|
|
45
|
+
});
|
|
46
|
+
if (req.stdin != null && child.stdin) {
|
|
47
|
+
child.stdin.write(req.stdin);
|
|
48
|
+
child.stdin.end();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function sendFile(req) {
|
|
53
|
+
try {
|
|
54
|
+
const text = await readFile(req.path, 'utf8');
|
|
55
|
+
return { ok: true, stdout: text, stderr: '' };
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
return { ok: false, stdout: '', stderr: e instanceof Error ? e.message : String(e) };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function sendHttp(req) {
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });
|
|
64
|
+
const body = await res.text();
|
|
65
|
+
return { ok: true, status: res.status, stdout: body, stderr: res.ok ? '' : res.statusText };
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
return { ok: false, stdout: '', stderr: e instanceof Error ? e.message : String(e) };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// transport.ts — the transport seam between a view's declarative SourceRequests
|
|
2
|
+
// and whatever fulfills them.
|
|
3
|
+
//
|
|
4
|
+
// The core is transport-blind: it only ever calls ctx.resolve(source, args) /
|
|
5
|
+
// ctx.execute(command, args); the host threads Transport.send → source.parse.
|
|
6
|
+
// Implementations:
|
|
7
|
+
// • transport-local.ts (BUILT) — Node-side: exec→execFile, file→readFile,
|
|
8
|
+
// http→fetch. Used directly by the TUI host and by the `view serve` bridge
|
|
9
|
+
// server on behalf of the browser bundle.
|
|
10
|
+
// • src/web/transport-http.ts (BUILT) — browser-side: POSTs the SourceRequest
|
|
11
|
+
// to the local bridge (`/__crtr/source`).
|
|
12
|
+
// • cloud (SEAM ONLY, not built) — same interface; send() POSTs the request
|
|
13
|
+
// to an endpoint that runs it server-side, or maps it to a real API call.
|
|
14
|
+
// Wiring a different Transport is the entire cloud migration.
|
|
15
|
+
export {};
|
package/dist/daemon/crtrd.d.ts
CHANGED
|
@@ -1,36 +1,47 @@
|
|
|
1
1
|
import { type NodeRow } from '../core/canvas/index.js';
|
|
2
2
|
import { isPidAlive } from '../core/canvas/pid.js';
|
|
3
3
|
export declare const YIELD_STALL_GRACE_MS: number;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
export declare function neverLaunchedVerdict(piSessionId: string | null, cycles: number, ageMs: number): 'leave' | 'relaunch';
|
|
15
|
-
/** Yield-stall decision (audit 2026-06-09, Bug 1 — node mq5v9hfa-74493d45 and
|
|
16
|
-
* three sibling orchestrators): a `node yield` records intent='refresh' and
|
|
17
|
-
* agent_end dispatches the in-place respawn, but at very large contexts the pi
|
|
18
|
-
* process can HANG instead of exiting — leaving pi alive, the turn over, and
|
|
19
|
-
* the intent pending forever. The daemon only revives DEAD pis, so without
|
|
20
|
-
* this verdict the node is permanently stuck (no revive, no wake). Pure core,
|
|
21
|
-
* mirroring livenessVerdict; the kill side effects live in handleYieldStall.
|
|
4
|
+
/** Refresh-stall decision (§H refresh-authority; audit 2026-06-09, Bug 1 — node
|
|
5
|
+
* mq5v9hfa-74493d45 and siblings; stuck-refresh — parent mq7s5o93): a `node
|
|
6
|
+
* yield` / refresh records intent='refresh' and relies on the in-process
|
|
7
|
+
* stophook to call shutdown at stop, but the engine can be left ALIVE — either
|
|
8
|
+
* it HANGS instead of exiting (very large contexts) or a STALE stophook (pi
|
|
9
|
+
* extensions never reload) ignores intent='refresh' entirely and never calls
|
|
10
|
+
* shutdown. Either way: engine alive, turn over, intent pending forever. The
|
|
11
|
+
* daemon only revives DEAD engines, so without this verdict the node is
|
|
12
|
+
* permanently stuck (no revive, no wake). Pure core, mirroring livenessVerdict;
|
|
13
|
+
* the kill side effects live in handleYieldStall.
|
|
22
14
|
*
|
|
23
|
-
* 'kill' ONLY when ALL of:
|
|
24
|
-
* marker absent — a node may legitimately keep working for many minutes
|
|
25
|
-
* running `node yield` mid-turn, and a working
|
|
26
|
-
* the state has persisted past YIELD_STALL_GRACE_MS. */
|
|
15
|
+
* 'kill' ONLY when ALL of: engine alive, intent='refresh', the turn is over
|
|
16
|
+
* (busy marker absent — a node may legitimately keep working for many minutes
|
|
17
|
+
* after running `node yield` mid-turn, and a working engine must never be
|
|
18
|
+
* killed), and the state has persisted past YIELD_STALL_GRACE_MS. */
|
|
27
19
|
export declare function yieldStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, stalledFor: number | null): 'leave' | 'pending' | 'kill';
|
|
20
|
+
export declare const ERROR_STALL_QUIET_MS: number;
|
|
21
|
+
/** The §I wedge signature, from a session-jsonl tail: the LAST assistant
|
|
22
|
+
* message stopped with 'error' (retry budget exhausted — pi appends the error
|
|
23
|
+
* turn and parks). Returns its errorMessage for the daemon log (the "why it
|
|
24
|
+
* wedged" trace), or null when the last assistant turn ended any other way —
|
|
25
|
+
* 'stop'/'length' is a healthy park, 'aborted' is a human Esc, and trailing
|
|
26
|
+
* non-assistant lines (toolResults, an injected user message) are walked past.
|
|
27
|
+
* The first line of a mid-file tail cut fails JSON.parse and is skipped. */
|
|
28
|
+
export declare function trailingEngineError(tail: string | null): string | null;
|
|
29
|
+
/** Error-stall decision (§I; gh issue #4 — node mqaeqzdu-0746b3b7, a 9h zombie
|
|
30
|
+
* after an Anthropic outage). Pure, mirroring yieldStallVerdict; the kill side
|
|
31
|
+
* effects live in handleErrorStall.
|
|
32
|
+
*
|
|
33
|
+
* 'kill' ONLY when ALL of: engine alive, NO pending intent (refresh is §H's
|
|
34
|
+
* domain; idle-release chose dormancy), the turn is over (busy absent — a node
|
|
35
|
+
* wedged mid-bash must be recovered by killing the subprocess, never the node),
|
|
36
|
+
* the session file has been quiet past ERROR_STALL_QUIET_MS, and its last
|
|
37
|
+
* assistant message stopped with 'error'. */
|
|
38
|
+
export declare function errorStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, quietForMs: number | null, trailingError: boolean): 'leave' | 'kill';
|
|
28
39
|
export type LivenessVerdict = 'leave' | 'pending' | 'revive';
|
|
29
|
-
/** Decide what to do with a node whose
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
40
|
+
/** Decide what to do with a node whose engine pid is DEAD, from how long it's
|
|
41
|
+
* been dead. Pure — the time/revive side effects live in handleNodeLiveness;
|
|
42
|
+
* this is the unit-testable core. (piPidAlive carries dead/alive/unknown, but
|
|
43
|
+
* only `false` ever reaches a revive decision here — alive and null are routed
|
|
44
|
+
* upstream by handleNodeLiveness.)
|
|
34
45
|
* deadFor: ms since first observed dead, or null on the first observation. */
|
|
35
46
|
export declare function livenessVerdict(piPidAlive: boolean | null, deadFor: number | null): LivenessVerdict;
|
|
36
47
|
export declare const DEAD_REAP_GRACE_MS: number;
|