@crouton-kit/crouter 0.3.18 → 0.3.20
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 +6 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
- package/dist/builtin-memory/crouter-development/personas.md +117 -0
- package/dist/builtin-memory/crouter-development/plugins.md +163 -0
- package/dist/builtin-memory/design.md +60 -0
- package/dist/builtin-memory/development.md +116 -0
- package/dist/builtin-memory/planning.md +66 -0
- package/dist/builtin-memory/spec.md +99 -0
- package/dist/builtin-personas/design/PERSONA.md +1 -0
- package/dist/builtin-personas/design/orchestrator.md +2 -1
- package/dist/builtin-personas/developer/PERSONA.md +1 -0
- package/dist/builtin-personas/developer/orchestrator.md +2 -1
- package/dist/builtin-personas/explore/PERSONA.md +1 -0
- package/dist/builtin-personas/explore/orchestrator.md +4 -0
- package/dist/builtin-personas/general/PERSONA.md +1 -0
- package/dist/builtin-personas/general/orchestrator.md +4 -0
- package/dist/builtin-personas/lifecycle/resident.md +1 -1
- package/dist/builtin-personas/lifecycle/terminal.md +5 -2
- package/dist/builtin-personas/orchestration-kernel.md +15 -24
- package/dist/builtin-personas/plan/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/orchestrator.md +2 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -0
- package/dist/builtin-personas/review/orchestrator.md +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/PERSONA.md +1 -0
- package/dist/builtin-personas/spec/orchestrator.md +2 -1
- package/dist/builtin-personas/waiting.md +8 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
- package/dist/builtin-views/_lib/states.mjs +161 -0
- package/dist/builtin-views/canvas/client.mjs +303 -0
- package/dist/builtin-views/canvas/view.mjs +576 -0
- package/dist/builtin-views/git-pr/client.mjs +440 -0
- package/dist/builtin-views/git-pr/view.mjs +675 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
- package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
- package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
- package/dist/builtin-views/inbox/view.mjs +889 -0
- package/dist/builtin-views/linkedin/client.mjs +610 -0
- package/dist/builtin-views/linkedin/view.mjs +1171 -0
- package/dist/clients/attach/attach-cmd.d.ts +2 -0
- package/dist/clients/attach/attach-cmd.js +354 -0
- package/dist/clients/attach/chat-view.d.ts +77 -0
- package/dist/clients/attach/chat-view.js +450 -0
- package/dist/clients/attach/clipboard-image.d.ts +16 -0
- package/dist/clients/attach/clipboard-image.js +113 -0
- package/dist/clients/attach/config-load.d.ts +31 -0
- package/dist/clients/attach/config-load.js +113 -0
- package/dist/clients/attach/extension-dialogs.d.ts +29 -0
- package/dist/clients/attach/extension-dialogs.js +101 -0
- package/dist/clients/attach/input-controller.d.ts +54 -0
- package/dist/clients/attach/input-controller.js +204 -0
- package/dist/clients/attach/slash-commands.d.ts +36 -0
- package/dist/clients/attach/slash-commands.js +200 -0
- package/dist/clients/attach/view-socket.d.ts +48 -0
- package/dist/clients/attach/view-socket.js +126 -0
- package/dist/commands/attention.js +3 -3
- package/dist/commands/canvas-prune.js +1 -1
- package/dist/commands/daemon.js +3 -2
- package/dist/commands/human/prompts.js +1 -1
- package/dist/commands/human/queue.js +54 -8
- package/dist/commands/human/shared.d.ts +2 -0
- package/dist/commands/memory/find.d.ts +1 -0
- package/dist/commands/memory/find.js +180 -0
- package/dist/commands/memory/lint.d.ts +1 -0
- package/dist/commands/memory/lint.js +140 -0
- package/dist/commands/memory/list.d.ts +1 -0
- package/dist/commands/memory/list.js +79 -0
- package/dist/commands/memory/read.js +103 -0
- package/dist/commands/memory/shared.d.ts +30 -0
- package/dist/commands/memory/shared.js +122 -0
- package/dist/commands/memory/write.d.ts +1 -0
- package/dist/commands/memory/write.js +85 -0
- package/dist/commands/memory.d.ts +2 -0
- package/dist/commands/memory.js +27 -0
- package/dist/commands/node.d.ts +3 -2
- package/dist/commands/node.js +610 -61
- package/dist/commands/pkg/market-manage.js +1 -1
- package/dist/commands/push.js +6 -6
- package/dist/commands/revive.js +1 -1
- package/dist/commands/skill/author.js +1 -1
- package/dist/commands/skill/shared.d.ts +1 -8
- package/dist/commands/skill/shared.js +2 -55
- package/dist/commands/skill.js +9 -14
- package/dist/commands/sys/doctor.js +1 -1
- package/dist/commands/sys/update.js +1 -1
- package/dist/commands/view-cycle.d.ts +2 -0
- package/dist/commands/view-cycle.js +125 -0
- package/dist/commands/view-list.d.ts +2 -0
- package/dist/commands/view-list.js +66 -0
- package/dist/commands/view-new.d.ts +2 -0
- package/dist/commands/view-new.js +70 -0
- package/dist/commands/view-pick.d.ts +2 -0
- package/dist/commands/view-pick.js +119 -0
- package/dist/commands/view-run.d.ts +2 -0
- package/dist/commands/view-run.js +191 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +29 -0
- package/dist/core/__tests__/broker-attach-limits.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
- package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
- package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
- package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dialogs.test.js +126 -0
- package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +87 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
- package/dist/core/__tests__/cascade-close.test.js +12 -2
- package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
- package/dist/core/__tests__/child-death-wake.test.js +245 -0
- package/dist/core/__tests__/context-intro.test.js +76 -62
- package/dist/core/__tests__/daemon-boot.test.js +14 -5
- package/dist/core/__tests__/daemon-liveness.test.js +34 -9
- package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
- package/dist/core/__tests__/detach-focus.test.js +206 -0
- package/dist/core/__tests__/draw-style.test.d.ts +1 -0
- package/dist/core/__tests__/draw-style.test.js +258 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
- package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
- package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
- package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
- package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
- package/dist/core/__tests__/helpers/broker-clients.d.ts +43 -0
- package/dist/core/__tests__/helpers/broker-clients.js +178 -0
- package/dist/core/__tests__/helpers/harness.d.ts +9 -0
- package/dist/core/__tests__/helpers/harness.js +111 -1
- package/dist/core/__tests__/home-session.test.js +41 -8
- package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
- package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
- package/dist/core/__tests__/kickoff.test.js +37 -3
- package/dist/core/__tests__/live-mutation-verbs.test.d.ts +1 -0
- package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
- package/dist/core/__tests__/live-mutation.test.js +6 -132
- package/dist/core/__tests__/memory.test.js +23 -115
- package/dist/core/__tests__/placement-focus.test.js +5 -0
- package/dist/core/__tests__/placement-teardown.test.js +54 -11
- package/dist/core/__tests__/relaunch.test.js +4 -3
- package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
- package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
- package/dist/core/__tests__/spawn-root.test.js +10 -0
- package/dist/core/__tests__/spike-harness.test.js +1 -0
- package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
- package/dist/core/__tests__/wake-bearings.test.js +156 -0
- package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
- package/dist/core/__tests__/wake-origin.test.js +110 -0
- package/dist/core/bootstrap.js +1 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +24 -2
- package/dist/core/canvas/browse/model.d.ts +38 -2
- package/dist/core/canvas/browse/model.js +134 -10
- package/dist/core/canvas/browse/render.d.ts +6 -12
- package/dist/core/canvas/browse/render.js +72 -104
- package/dist/core/canvas/canvas.js +16 -23
- package/dist/core/canvas/db.js +46 -0
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/canvas/pid.d.ts +4 -0
- package/dist/core/canvas/pid.js +23 -0
- package/dist/core/canvas/render.d.ts +11 -2
- package/dist/core/canvas/render.js +69 -0
- package/dist/core/canvas/types.d.ts +91 -3
- package/dist/core/canvas/wakeups.d.ts +76 -0
- package/dist/core/canvas/wakeups.js +185 -0
- package/dist/core/config.js +4 -1
- package/dist/core/frontmatter.js +37 -124
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/loader.d.ts +7 -0
- package/dist/core/personas/loader.js +36 -5
- package/dist/core/personas/resolve.js +13 -3
- package/dist/core/predicate.d.ts +63 -0
- package/dist/core/predicate.js +189 -0
- package/dist/core/resolver.js +26 -5
- package/dist/core/runtime/bearings.d.ts +53 -12
- package/dist/core/runtime/bearings.js +132 -59
- package/dist/core/runtime/broker-cli.d.ts +1 -0
- package/dist/core/runtime/broker-cli.js +46 -0
- package/dist/core/runtime/broker-protocol.d.ts +332 -0
- package/dist/core/runtime/broker-protocol.js +153 -0
- package/dist/core/runtime/broker-sdk.d.ts +48 -0
- package/dist/core/runtime/broker-sdk.js +72 -0
- package/dist/core/runtime/broker.d.ts +55 -0
- package/dist/core/runtime/broker.js +1128 -0
- package/dist/core/runtime/close.js +35 -6
- package/dist/core/runtime/host.d.ts +53 -0
- package/dist/core/runtime/host.js +186 -0
- package/dist/core/runtime/kickoff.d.ts +2 -1
- package/dist/core/runtime/kickoff.js +91 -5
- package/dist/core/runtime/launch.d.ts +54 -4
- package/dist/core/runtime/launch.js +85 -5
- package/dist/core/runtime/lifecycle.js +23 -6
- package/dist/core/runtime/memory.d.ts +2 -42
- package/dist/core/runtime/memory.js +11 -162
- package/dist/core/runtime/nodes.d.ts +37 -0
- package/dist/core/runtime/nodes.js +60 -1
- package/dist/core/runtime/persona.js +21 -11
- package/dist/core/runtime/pi-vendored.d.ts +18 -0
- package/dist/core/runtime/pi-vendored.js +49 -0
- package/dist/core/runtime/placement.d.ts +32 -14
- package/dist/core/runtime/placement.js +206 -52
- package/dist/core/runtime/promote.d.ts +0 -6
- package/dist/core/runtime/promote.js +4 -12
- package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
- package/dist/core/runtime/{demote.js → recycle.js} +27 -11
- package/dist/core/runtime/reset.js +11 -8
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +23 -0
- package/dist/core/runtime/spawn.js +77 -23
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +6 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +28 -0
- package/dist/core/runtime/tmux.js +80 -6
- package/dist/core/scope.d.ts +11 -0
- package/dist/core/scope.js +39 -0
- package/dist/core/spawn.d.ts +5 -0
- package/dist/core/spawn.js +20 -2
- package/dist/core/substrate/gate.d.ts +13 -0
- package/dist/core/substrate/gate.js +21 -0
- package/dist/core/substrate/index.d.ts +7 -0
- package/dist/core/substrate/index.js +18 -0
- package/dist/core/substrate/on-read.d.ts +14 -0
- package/dist/core/substrate/on-read.js +292 -0
- package/dist/core/substrate/render.d.ts +25 -0
- package/dist/core/substrate/render.js +256 -0
- package/dist/core/substrate/schema.d.ts +76 -0
- package/dist/core/substrate/schema.js +124 -0
- package/dist/core/substrate/session-cache.d.ts +30 -0
- package/dist/core/substrate/session-cache.js +77 -0
- package/dist/core/substrate/subject.d.ts +41 -0
- package/dist/core/substrate/subject.js +54 -0
- package/dist/core/tui/contract.d.ts +83 -0
- package/dist/core/tui/contract.js +8 -0
- package/dist/core/tui/draw.d.ts +96 -0
- package/dist/core/tui/draw.js +339 -0
- package/dist/core/tui/host.d.ts +29 -0
- package/dist/core/tui/host.js +379 -0
- package/dist/core/tui/loader.d.ts +16 -0
- package/dist/core/tui/loader.js +94 -0
- package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
- package/dist/core/wake.d.ts +86 -0
- package/dist/core/wake.js +308 -0
- package/dist/daemon/crtrd.d.ts +29 -4
- package/dist/daemon/crtrd.js +662 -46
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
- package/dist/pi-extensions/canvas-commands.d.ts +3 -0
- package/dist/pi-extensions/canvas-commands.js +10 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
- package/dist/pi-extensions/canvas-context-intro.js +55 -15
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
- package/dist/pi-extensions/canvas-nav.d.ts +3 -0
- package/dist/pi-extensions/canvas-nav.js +87 -23
- package/dist/pi-extensions/canvas-stophook.js +17 -8
- package/dist/pi-extensions/canvas-view.d.ts +21 -0
- package/dist/pi-extensions/canvas-view.js +75 -0
- package/dist/prompts/skill.js +19 -26
- package/dist/prompts/view.d.ts +7 -0
- package/dist/prompts/view.js +101 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +1 -0
- package/package.json +9 -4
- package/dist/commands/skill/find.d.ts +0 -4
- package/dist/commands/skill/find.js +0 -257
- package/dist/commands/skill/read.js +0 -91
- /package/dist/commands/{skill → memory}/read.d.ts +0 -0
- /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
// bearings.ts — the <crtr-
|
|
2
|
-
// deliver it so they can
|
|
1
|
+
// bearings.ts — the boot-intro prose (the <crtr-identity> assertion + the
|
|
2
|
+
// <crtr-context> framing), shared by the paths that deliver it so they can
|
|
3
|
+
// never drift:
|
|
3
4
|
//
|
|
4
5
|
// • the context-intro pi-extension injects buildContextBearings() as the
|
|
5
|
-
// node's first session message in every brand-new chat
|
|
6
|
+
// node's first session message in every brand-new chat. It opens with a
|
|
7
|
+
// <crtr-identity> block (buildIdentityAssertion) that names the node and,
|
|
8
|
+
// ONLY for a fork, disowns the source's copied first-person narrative as
|
|
9
|
+
// inherited context — the fix for the `--fork-from` bug where a fork copies
|
|
10
|
+
// the source's whole conversation and then impersonates it. A non-fork
|
|
11
|
+
// node's bearings are its FIRST entry, so there is nothing earlier to
|
|
12
|
+
// disown; it gets only the declarative identity line;
|
|
6
13
|
// • promote.ts folds orchestratorContextNote() into the promotion guidance
|
|
7
14
|
// dump, so a node that becomes an orchestrator MID-LIFE gets the
|
|
8
15
|
// orchestrator framing it never received at spawn — it spawned as a base
|
|
@@ -12,17 +19,17 @@
|
|
|
12
19
|
// one place other nodes on the canvas can read from, so it is for documents
|
|
13
20
|
// worth a shared reference, NOT a task tracker, and NOT a "future memory-wiped
|
|
14
21
|
// you" stash (a terminal worker has no future cycle — that framing only makes
|
|
15
|
-
// sense once a node is a resident orchestrator).
|
|
22
|
+
// sense once a node is a resident orchestrator). The `## References` block
|
|
23
|
+
// (substrate reference docs + node-local docs) rides into the context message.
|
|
16
24
|
//
|
|
17
|
-
// Orchestrator addendum (
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import { hasMemory, memoryDir, readMemory, hasUserMemory, userMemoryDir, readUserMemory, hasProjectMemory, projectMemoryDir, readProjectMemory, } from './memory.js';
|
|
25
|
+
// Orchestrator addendum (gated on orchestrator MODE): the dir ALSO survives
|
|
26
|
+
// refresh cycles, so it is where a future cycle of the orchestrator resumes.
|
|
27
|
+
// This across-cycles note is the ONE thing a terminal worker's bearings drop.
|
|
28
|
+
import { contextDir, getNode, fullName } from '../canvas/index.js';
|
|
29
|
+
import { cadenceDisplay } from '../wake.js';
|
|
30
|
+
import { renderReferencesBlock } from '../substrate/index.js';
|
|
24
31
|
/** Base framing — present for every node. No path baked in: the caller carries
|
|
25
|
-
* the dir in the
|
|
32
|
+
* the dir in the `<crtr-context dir="…">` attribute. */
|
|
26
33
|
export const BASE_CONTEXT_NOTE = 'This is your context directory — durable scratch space on disk, and the one place the other ' +
|
|
27
34
|
'nodes on the canvas can read from. Put documents here that you want to share by reference ' +
|
|
28
35
|
'instead of re-explaining them in a prompt: specs, designs, findings, notes worth pointing a ' +
|
|
@@ -36,57 +43,123 @@ export function orchestratorContextNote(nodeId) {
|
|
|
36
43
|
`also where a future cycle of you resumes the work — keep the working notes and decisions a ` +
|
|
37
44
|
`refreshed you would need there, alongside the docs you share with the nodes you spawn.`);
|
|
38
45
|
}
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
/** The IDENTITY assertion that opens every boot intro — the load-bearing fix
|
|
47
|
+
* for the `--fork-from` impersonation bug. A fork copies the SOURCE node's
|
|
48
|
+
* entire first-person conversation into its own session, so without an explicit
|
|
49
|
+
* re-assertion the forked agent reads that copied narrative as its own and
|
|
50
|
+
* impersonates the source (it kept "monitoring itself" as a phantom child).
|
|
51
|
+
* This block names the node unambiguously. ONLY when the node IS a fork does it
|
|
52
|
+
* additionally name the source AND disown the copied first-person narrative as
|
|
53
|
+
* inherited reference material — a non-fork node's bearings are its first session
|
|
54
|
+
* entry, so there is no earlier narrative to disown (emitting a disown line there
|
|
55
|
+
* is dead weight that reads as a contradiction with no referent). Always the FIRST
|
|
56
|
+
* thing the node reads. Exported for testing. */
|
|
57
|
+
export function buildIdentityAssertion(nodeId) {
|
|
58
|
+
const meta = getNode(nodeId);
|
|
59
|
+
const name = meta?.name ?? nodeId;
|
|
60
|
+
const kind = meta?.kind ?? 'general';
|
|
61
|
+
const mode = meta?.mode ?? 'base';
|
|
62
|
+
const lines = [
|
|
63
|
+
'<crtr-identity>',
|
|
64
|
+
`You are node ${nodeId} — name "${name}", kind ${kind}, mode ${mode}.`,
|
|
65
|
+
];
|
|
66
|
+
const forkFrom = meta?.fork_from;
|
|
67
|
+
if (forkFrom !== undefined && forkFrom !== null && forkFrom !== '') {
|
|
68
|
+
// Name the source: a known node id gets its human label; a raw path/uuid
|
|
69
|
+
// passes through as-is.
|
|
70
|
+
const src = getNode(forkFrom);
|
|
71
|
+
const sourceLabel = src !== null ? `${forkFrom} ("${fullName(src)}")` : forkFrom;
|
|
72
|
+
lines.push(`You are a FORK of ${sourceLabel}: at spawn pi COPIED that node's conversation into your ` +
|
|
73
|
+
`session as a starting point. You are NOT ${sourceLabel}. Everything earlier in this ` +
|
|
74
|
+
'conversation is THEIR first-person history — inherited reference material, not your own ' +
|
|
75
|
+
'past. Do not speak or act as them, do not continue their task as if it were yours, and do ' +
|
|
76
|
+
'not "monitor yourself" as though you were a child they spawned.');
|
|
77
|
+
}
|
|
78
|
+
lines.push('</crtr-identity>');
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
/** Build a WakeOrigin from a fired wakeup row, resolving the armer's display
|
|
82
|
+
* name if the node still exists. The daemon calls this at fire time for the
|
|
83
|
+
* bare-revive and spawn-birth seams. */
|
|
84
|
+
export function wakeOriginFrom(w) {
|
|
85
|
+
const owner = w.owner_id != null ? getNode(w.owner_id) : null;
|
|
86
|
+
return {
|
|
87
|
+
kind: w.kind,
|
|
88
|
+
ownerId: w.owner_id ?? null,
|
|
89
|
+
ownerName: owner !== null ? fullName(owner) : undefined,
|
|
90
|
+
armedAt: w.created,
|
|
91
|
+
recur: w.recur,
|
|
92
|
+
};
|
|
52
93
|
}
|
|
53
|
-
/** The
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
/** The armer rendered for prose, ROLE-explicit so a newborn never mistakes the
|
|
95
|
+
* id for its own: `node <id> ("<name>")` when the name resolved, `node <id>
|
|
96
|
+
* (now gone)` when the armer was reaped, `an unknown node` only if the owner id
|
|
97
|
+
* is somehow absent (owner_id is NOT NULL on every real row — defensive). */
|
|
98
|
+
function armerPhrase(origin) {
|
|
99
|
+
if (origin.ownerId === null || origin.ownerId === '')
|
|
100
|
+
return 'an unknown node';
|
|
101
|
+
if (origin.ownerName !== undefined)
|
|
102
|
+
return `node ${origin.ownerId} ("${origin.ownerName}")`;
|
|
103
|
+
return `node ${origin.ownerId} (now gone)`;
|
|
104
|
+
}
|
|
105
|
+
/** The <crtr-wake> provenance block — load-bearing agent-facing prose read by
|
|
106
|
+
* every wake-born or wake-woken node. Decision-first: it leads with the fact a
|
|
107
|
+
* TIMER (not a message/event) caused this turn, names the wake kind, surfaces
|
|
108
|
+
* the cadence for a recurrence (so the agent knows it is one run of a standing
|
|
109
|
+
* job, not a one-off), and ends with the directive. The spawn (birth) variant
|
|
110
|
+
* names the ARMER explicitly ("armed by node X") so the newborn never reads that
|
|
111
|
+
* id as its own; the bare (revive) variant drops armer attribution entirely — a
|
|
112
|
+
* bare wake can be armed for a node by ANOTHER (`--node`), and who armed it is
|
|
113
|
+
* not decision-relevant to a timed re-check. No timestamp is rendered ("to fire
|
|
114
|
+
* now" / the cadence already carry the signal; a raw ISO instant is noise an
|
|
115
|
+
* agent cannot cheaply turn into an elapsed delta). Rendered for 'spawn' and
|
|
116
|
+
* 'bare'; other kinds self-mark elsewhere. */
|
|
117
|
+
export function buildWakeBearings(origin) {
|
|
118
|
+
const recurring = origin.recur !== null && origin.recur !== undefined && origin.recur !== '';
|
|
119
|
+
const cadence = recurring ? cadenceDisplay(origin.recur) : null;
|
|
120
|
+
let body;
|
|
121
|
+
if (origin.kind === 'spawn') {
|
|
122
|
+
const armer = armerPhrase(origin);
|
|
123
|
+
body = recurring
|
|
124
|
+
? `You were BORN by a scheduled wake, not spawned on demand — a recurring spawn-cron armed by ` +
|
|
125
|
+
`${armer}, firing ${cadence}. The runtime re-births a fresh node like you on this cadence whether ` +
|
|
126
|
+
`or not earlier runs survived: you are one run of a standing job, not a one-off, and you inherit ` +
|
|
127
|
+
`nothing from prior runs but this task. Your task follows.`
|
|
128
|
+
: `You were BORN by a scheduled wake, not spawned on demand — a one-shot deferred birth armed by ` +
|
|
129
|
+
`${armer} to fire now. You are its only run, not a recurring job. Your task follows.`;
|
|
64
130
|
}
|
|
65
|
-
|
|
66
|
-
|
|
131
|
+
else {
|
|
132
|
+
// bare scheduled alarm (the only kind delivered through this block at the
|
|
133
|
+
// revive seam; noted/deadline self-mark via their inbox label instead).
|
|
134
|
+
body = recurring
|
|
135
|
+
? `You woke because a recurring scheduled alarm fired (${cadence}) — a timer, NOT a new message or ` +
|
|
136
|
+
`request. This is one tick of a standing re-check: re-read your roadmap and the disk bearings below ` +
|
|
137
|
+
`and decide what this moment calls for.`
|
|
138
|
+
: `You woke because a scheduled alarm fired — a timer, NOT a new message or request. This is a timed ` +
|
|
139
|
+
`re-check, not a new task: re-read your roadmap and the disk bearings below and decide what this ` +
|
|
140
|
+
`moment calls for.`;
|
|
67
141
|
}
|
|
68
|
-
|
|
69
|
-
const n = stanzas.length;
|
|
70
|
-
return ('<memory>\n' +
|
|
71
|
-
`Long-term memory, ${n} scope${n === 1 ? '' : 's'}. Each line ` +
|
|
72
|
-
'`- [Title](slug.md) — hook`; load a detail file by slug from the scope dir on demand. ' +
|
|
73
|
-
'Write a new fact to the scope matching its `type` (see "Your long-term memory").\n\n' +
|
|
74
|
-
stanzas.join('\n\n') +
|
|
75
|
-
'\n</memory>');
|
|
142
|
+
return `<crtr-wake>\n${body}\n</crtr-wake>`;
|
|
76
143
|
}
|
|
77
|
-
/** The full
|
|
78
|
-
*
|
|
79
|
-
*
|
|
144
|
+
/** The full boot intro: the IDENTITY assertion (always first, so it overrides
|
|
145
|
+
* any copied-in persona) followed by the `<crtr-context>` bearings block. Base
|
|
146
|
+
* framing rides for EVERY node; the across-cycles context-dir note is added
|
|
147
|
+
* ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
|
|
148
|
+
* resumes from. The `## References` block (substrate reference docs +
|
|
149
|
+
* node-local docs) replaces the old `<memory>` block. */
|
|
80
150
|
export function buildContextBearings(nodeId) {
|
|
151
|
+
const identity = buildIdentityAssertion(nodeId);
|
|
81
152
|
const dir = contextDir(nodeId);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
153
|
+
const node = getNode(nodeId);
|
|
154
|
+
const parts = [BASE_CONTEXT_NOTE];
|
|
155
|
+
// Orchestrator-only: the across-cycles framing (a terminal has no future cycle).
|
|
156
|
+
if (node?.mode === 'orchestrator')
|
|
157
|
+
parts.push(orchestratorContextNote(nodeId));
|
|
158
|
+
// The substrate references block: eligible `reference` docs at their
|
|
159
|
+
// system-prompt rung + node-local memory docs; dropped (returns '') when
|
|
160
|
+
// nothing is eligible.
|
|
161
|
+
const references = renderReferencesBlock(nodeId);
|
|
162
|
+
if (references !== '')
|
|
163
|
+
parts.push(references);
|
|
164
|
+
return `${identity}\n<crtr-context dir="${dir}">\n${parts.join('\n')}\n</crtr-context>`;
|
|
92
165
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// broker-cli.ts — the headless broker's dedicated detached entry (plan T4 /
|
|
2
|
+
// decision §1.13). Spawned directly via `spawn(execPath, [thisFile, nodeId], …)`
|
|
3
|
+
// by HeadlessBrokerHost.launch (T6) — NEVER routed through src/cli.ts, which
|
|
4
|
+
// would run the whole bootstrap chain (auto-update / scope-init / slash-template
|
|
5
|
+
// rewrite) on every broker boot. Mirrors src/daemon/crtrd-cli.ts: parse the one
|
|
6
|
+
// positional arg and hand off; keep this file a thin shim.
|
|
7
|
+
import { runBroker, disposeActiveSession } from './broker.js';
|
|
8
|
+
const nodeId = process.argv[2];
|
|
9
|
+
if (nodeId === undefined || nodeId.trim() === '') {
|
|
10
|
+
process.stderr.write('[broker] usage: broker-cli <nodeId>\n');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// M3 (scout mq5thyli): dispose the live engine before a FATAL exit. The bash tool
|
|
14
|
+
// spawns children `detached` (own pgid); only session.dispose() (→ abortBash /
|
|
15
|
+
// agent.abort → killProcessTree) reaps them. The graceful path (shutdownHandler /
|
|
16
|
+
// SIGTERM → disposeAndExit) already disposes; this routes the crash path —
|
|
17
|
+
// uncaughtException / unhandledRejection / a runBroker reject — through dispose
|
|
18
|
+
// too, so a fatal error never ORPHANS in-flight bash subprocesses. dispose is
|
|
19
|
+
// idempotent + a no-op once the graceful path has run.
|
|
20
|
+
function disposeAndExit(code) {
|
|
21
|
+
try {
|
|
22
|
+
disposeActiveSession();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* dispose must not block the fatal exit */
|
|
26
|
+
}
|
|
27
|
+
process.exit(code);
|
|
28
|
+
}
|
|
29
|
+
// Last-resort safety net (review N-4): a stray throw/rejection from the SDK event
|
|
30
|
+
// stream or an extension's async hook would otherwise crash the broker on Node's
|
|
31
|
+
// default handler. Now that the host redirects the broker's stderr to the node's
|
|
32
|
+
// job/broker.log (M-2), log it there and exit non-zero — pre-session_start this
|
|
33
|
+
// folds into the daemon's boot-failure detection (M-1: pid stays null past the
|
|
34
|
+
// boot grace → surfaceBootFailure); post-session_start it is a clean grace-revive.
|
|
35
|
+
process.on('uncaughtException', (err) => {
|
|
36
|
+
process.stderr.write(`[broker] uncaughtException: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
37
|
+
disposeAndExit(1);
|
|
38
|
+
});
|
|
39
|
+
process.on('unhandledRejection', (reason) => {
|
|
40
|
+
process.stderr.write(`[broker] unhandledRejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}\n`);
|
|
41
|
+
disposeAndExit(1);
|
|
42
|
+
});
|
|
43
|
+
runBroker(nodeId).catch((err) => {
|
|
44
|
+
process.stderr.write(`[broker] fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
45
|
+
disposeAndExit(1);
|
|
46
|
+
});
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import type { AgentSession, AgentSessionEvent, SessionStats } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import type { ImageContent } from '@earendil-works/pi-ai';
|
|
3
|
+
/** Emitted when an extension needs user input (mirror of pi rpc-types.d.ts). */
|
|
4
|
+
export type RpcExtensionUIRequest = {
|
|
5
|
+
type: 'extension_ui_request';
|
|
6
|
+
id: string;
|
|
7
|
+
method: 'select';
|
|
8
|
+
title: string;
|
|
9
|
+
options: string[];
|
|
10
|
+
timeout?: number;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'extension_ui_request';
|
|
13
|
+
id: string;
|
|
14
|
+
method: 'confirm';
|
|
15
|
+
title: string;
|
|
16
|
+
message: string;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'extension_ui_request';
|
|
20
|
+
id: string;
|
|
21
|
+
method: 'input';
|
|
22
|
+
title: string;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
timeout?: number;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'extension_ui_request';
|
|
27
|
+
id: string;
|
|
28
|
+
method: 'editor';
|
|
29
|
+
title: string;
|
|
30
|
+
prefill?: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'extension_ui_request';
|
|
33
|
+
id: string;
|
|
34
|
+
method: 'notify';
|
|
35
|
+
message: string;
|
|
36
|
+
notifyType?: 'info' | 'warning' | 'error';
|
|
37
|
+
} | {
|
|
38
|
+
type: 'extension_ui_request';
|
|
39
|
+
id: string;
|
|
40
|
+
method: 'setStatus';
|
|
41
|
+
statusKey: string;
|
|
42
|
+
statusText: string | undefined;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'extension_ui_request';
|
|
45
|
+
id: string;
|
|
46
|
+
method: 'setWidget';
|
|
47
|
+
widgetKey: string;
|
|
48
|
+
widgetLines: string[] | undefined;
|
|
49
|
+
widgetPlacement?: 'aboveEditor' | 'belowEditor';
|
|
50
|
+
} | {
|
|
51
|
+
type: 'extension_ui_request';
|
|
52
|
+
id: string;
|
|
53
|
+
method: 'setTitle';
|
|
54
|
+
title: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'extension_ui_request';
|
|
57
|
+
id: string;
|
|
58
|
+
method: 'set_editor_text';
|
|
59
|
+
text: string;
|
|
60
|
+
};
|
|
61
|
+
/** Response to an extension UI request (mirror of pi rpc-types.d.ts). */
|
|
62
|
+
export type RpcExtensionUIResponse = {
|
|
63
|
+
type: 'extension_ui_response';
|
|
64
|
+
id: string;
|
|
65
|
+
value: string;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'extension_ui_response';
|
|
68
|
+
id: string;
|
|
69
|
+
confirmed: boolean;
|
|
70
|
+
} | {
|
|
71
|
+
type: 'extension_ui_response';
|
|
72
|
+
id: string;
|
|
73
|
+
cancelled: true;
|
|
74
|
+
};
|
|
75
|
+
/** Single controller (drives the engine) + N read-only observers (§5.3). */
|
|
76
|
+
export type ClientRole = 'controller' | 'observer';
|
|
77
|
+
/** The full state a (re)attaching client needs to catch up instantly — the
|
|
78
|
+
* broker's authoritative in-memory view (design §5.2: get_messages +
|
|
79
|
+
* get_state + get_session_stats). `messages` is pi's `AgentMessage[]` (the
|
|
80
|
+
* `session.messages` getter type), kept structural to avoid a deep type
|
|
81
|
+
* import. */
|
|
82
|
+
export interface BrokerSnapshot {
|
|
83
|
+
messages: AgentSession['messages'];
|
|
84
|
+
stats: SessionStats;
|
|
85
|
+
state: {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
sessionFile: string | undefined;
|
|
88
|
+
model: string | undefined;
|
|
89
|
+
isStreaming: boolean;
|
|
90
|
+
thinkingLevel: AgentSession['thinkingLevel'];
|
|
91
|
+
steeringMode: AgentSession['steeringMode'];
|
|
92
|
+
followUpMode: AgentSession['followUpMode'];
|
|
93
|
+
sessionName: AgentSession['sessionName'];
|
|
94
|
+
autoCompactionEnabled: AgentSession['autoCompactionEnabled'];
|
|
95
|
+
pendingMessageCount: AgentSession['pendingMessageCount'];
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export interface HelloFrame {
|
|
99
|
+
type: 'hello';
|
|
100
|
+
role: ClientRole;
|
|
101
|
+
client_id: string;
|
|
102
|
+
/** Terminal geometry of a tmux-pane viewer; absent for a browser/headless. */
|
|
103
|
+
term?: {
|
|
104
|
+
cols: number;
|
|
105
|
+
rows: number;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Drive the engine — controller only. Map 1:1 to session.prompt/steer/followUp/abort.
|
|
109
|
+
* `images` carries pasted/attached images to the engine (review M1): pi accepts
|
|
110
|
+
* `prompt(text,{images})` / `steer(text,images)` / `followUp(text,images)` at
|
|
111
|
+
* 0.78.1. The wire TYPE lives here; T3/T6 wire the runtime side. The BROKER read
|
|
112
|
+
* cap (`BROKER_READ_CAPS`) is sized to hold a resizeImage-bounded PNG's base64. */
|
|
113
|
+
export interface PromptFrame {
|
|
114
|
+
type: 'prompt';
|
|
115
|
+
text: string;
|
|
116
|
+
images?: ImageContent[];
|
|
117
|
+
}
|
|
118
|
+
export interface SteerFrame {
|
|
119
|
+
type: 'steer';
|
|
120
|
+
text: string;
|
|
121
|
+
images?: ImageContent[];
|
|
122
|
+
}
|
|
123
|
+
export interface FollowUpFrame {
|
|
124
|
+
type: 'follow_up';
|
|
125
|
+
text: string;
|
|
126
|
+
images?: ImageContent[];
|
|
127
|
+
}
|
|
128
|
+
export interface AbortFrame {
|
|
129
|
+
type: 'abort';
|
|
130
|
+
}
|
|
131
|
+
/** Controller arbitration (§5.3). */
|
|
132
|
+
export interface RequestControlFrame {
|
|
133
|
+
type: 'request_control';
|
|
134
|
+
}
|
|
135
|
+
export interface ReleaseControlFrame {
|
|
136
|
+
type: 'release_control';
|
|
137
|
+
}
|
|
138
|
+
/** Detach this client — the engine runs on (distinct from `shutdown`). */
|
|
139
|
+
export interface ByeFrame {
|
|
140
|
+
type: 'bye';
|
|
141
|
+
}
|
|
142
|
+
/** Graceful teardown: dispose the engine + exit the broker (design §3.3, the
|
|
143
|
+
* `HeadlessBrokerHost.teardown` happy path). A FIRST-CLASS control frame,
|
|
144
|
+
* distinct from `bye` (which only drops one listener). §5.2's wire table omits
|
|
145
|
+
* it; the plan reconciles §3.3's "graceful teardown over the socket" by
|
|
146
|
+
* treating `shutdown` as a client→broker control frame. */
|
|
147
|
+
export interface ShutdownFrame {
|
|
148
|
+
type: 'shutdown';
|
|
149
|
+
}
|
|
150
|
+
/** `setModel(model)` — `/model` (selector resolves → chosen id). */
|
|
151
|
+
export interface SetModelFrame {
|
|
152
|
+
type: 'set_model';
|
|
153
|
+
model: string;
|
|
154
|
+
}
|
|
155
|
+
/** `cycleModel()`. */
|
|
156
|
+
export interface CycleModelFrame {
|
|
157
|
+
type: 'cycle_model';
|
|
158
|
+
}
|
|
159
|
+
/** `setThinkingLevel(level)` — `/settings` thinking. */
|
|
160
|
+
export interface SetThinkingLevelFrame {
|
|
161
|
+
type: 'set_thinking_level';
|
|
162
|
+
level: AgentSession['thinkingLevel'];
|
|
163
|
+
}
|
|
164
|
+
/** `setAutoRetryEnabled(enabled)`. */
|
|
165
|
+
export interface SetAutoRetryFrame {
|
|
166
|
+
type: 'set_auto_retry';
|
|
167
|
+
enabled: boolean;
|
|
168
|
+
}
|
|
169
|
+
/** `setAutoCompactionEnabled(enabled)` — `/settings`. */
|
|
170
|
+
export interface SetAutoCompactionFrame {
|
|
171
|
+
type: 'set_auto_compaction';
|
|
172
|
+
enabled: boolean;
|
|
173
|
+
}
|
|
174
|
+
/** `compact(instructions?)` — `/compact`. */
|
|
175
|
+
export interface CompactFrame {
|
|
176
|
+
type: 'compact';
|
|
177
|
+
instructions?: string;
|
|
178
|
+
}
|
|
179
|
+
/** `runtimeHost.newSession()` + rebind — `/new`. */
|
|
180
|
+
export interface NewSessionFrame {
|
|
181
|
+
type: 'new_session';
|
|
182
|
+
}
|
|
183
|
+
/** `runtimeHost.switchSession(path)` + rebind — `/resume` (selector). */
|
|
184
|
+
export interface SwitchSessionFrame {
|
|
185
|
+
type: 'switch_session';
|
|
186
|
+
path: string;
|
|
187
|
+
}
|
|
188
|
+
/** `runtimeHost.fork(entryId)` + rebind — `/fork` (selector). */
|
|
189
|
+
export interface ForkFrame {
|
|
190
|
+
type: 'fork';
|
|
191
|
+
entryId: string;
|
|
192
|
+
}
|
|
193
|
+
/** `setSessionName(name)` — `/name`. */
|
|
194
|
+
export interface SetSessionNameFrame {
|
|
195
|
+
type: 'set_session_name';
|
|
196
|
+
name: string;
|
|
197
|
+
}
|
|
198
|
+
/** Registered commands + templates + skills, MERGED with BUILTIN_SLASH_COMMANDS
|
|
199
|
+
* (M9: RPC omits builtins) — drives autocomplete. Broker replies via `ack`. */
|
|
200
|
+
export interface GetCommandsFrame {
|
|
201
|
+
type: 'get_commands';
|
|
202
|
+
}
|
|
203
|
+
/** `navigateTree(targetId, options?)` — `/tree`. Options mirror AgentSession.navigateTree. */
|
|
204
|
+
export interface NavigateTreeFrame {
|
|
205
|
+
type: 'navigate_tree';
|
|
206
|
+
targetId: string;
|
|
207
|
+
options?: {
|
|
208
|
+
summarize?: boolean;
|
|
209
|
+
customInstructions?: string;
|
|
210
|
+
replaceInstructions?: boolean;
|
|
211
|
+
label?: string;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/** `reload()` — `/reload`. */
|
|
215
|
+
export interface ReloadFrame {
|
|
216
|
+
type: 'reload';
|
|
217
|
+
}
|
|
218
|
+
/** `exportToHtml(path)` / `exportToJsonl(path)` — `/export`. */
|
|
219
|
+
export interface ExportFrame {
|
|
220
|
+
type: 'export';
|
|
221
|
+
path: string;
|
|
222
|
+
format: 'html' | 'jsonl';
|
|
223
|
+
}
|
|
224
|
+
/** Answer a blocking extension dialog — pi's public RPC response type. */
|
|
225
|
+
export type ExtensionUIResponseFrame = RpcExtensionUIResponse;
|
|
226
|
+
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | RequestControlFrame | ReleaseControlFrame | ByeFrame | ShutdownFrame | SetModelFrame | CycleModelFrame | SetThinkingLevelFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ExtensionUIResponseFrame;
|
|
227
|
+
export interface WelcomeFrame {
|
|
228
|
+
type: 'welcome';
|
|
229
|
+
snapshot: BrokerSnapshot;
|
|
230
|
+
role: ClientRole;
|
|
231
|
+
controller_id: string | null;
|
|
232
|
+
/** A dialog already in-flight when this client attached (Phase 4
|
|
233
|
+
* attach-mid-dialog). The field exists now; it is only ever populated in
|
|
234
|
+
* Phase 4 — Phase 3 always sends it absent/null. */
|
|
235
|
+
pending_dialog?: RpcExtensionUIRequest | null;
|
|
236
|
+
}
|
|
237
|
+
export interface ControlChangedFrame {
|
|
238
|
+
type: 'control_changed';
|
|
239
|
+
controller_id: string | null;
|
|
240
|
+
}
|
|
241
|
+
export interface ErrorFrame {
|
|
242
|
+
type: 'error';
|
|
243
|
+
code: string;
|
|
244
|
+
message: string;
|
|
245
|
+
}
|
|
246
|
+
/** Result of a controller command op (§1.3): `for` echoes the op name, `ok` the
|
|
247
|
+
* outcome, `detail` an optional human-readable note. */
|
|
248
|
+
export interface AckFrame {
|
|
249
|
+
type: 'ack';
|
|
250
|
+
for: string;
|
|
251
|
+
ok: boolean;
|
|
252
|
+
detail?: string;
|
|
253
|
+
}
|
|
254
|
+
/** A blocking dialog routed to the controller — pi's public RPC request type. */
|
|
255
|
+
export type ExtensionUIRequestFrame = RpcExtensionUIRequest;
|
|
256
|
+
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
257
|
+
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
258
|
+
* non-colliding `type` discriminants. */
|
|
259
|
+
export type BrokerToClient = WelcomeFrame | ControlChangedFrame | ErrorFrame | AckFrame | ExtensionUIRequestFrame | AgentSessionEvent;
|
|
260
|
+
/** Encode one frame as a single newline-terminated JSON line. */
|
|
261
|
+
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
262
|
+
/** Byte bounds for a {@link FrameDecoder} (C5). */
|
|
263
|
+
export interface FrameDecoderCaps {
|
|
264
|
+
/** Largest single (unterminated) frame the decoder will buffer before throwing. */
|
|
265
|
+
maxLineBytes: number;
|
|
266
|
+
/** Largest the internal buffer may peak to in one push before throwing. */
|
|
267
|
+
maxTotalBytes: number;
|
|
268
|
+
}
|
|
269
|
+
/** Thrown by {@link FrameDecoder.push} when a peer's buffered bytes exceed a cap
|
|
270
|
+
* (C5). The caller catches it, best-effort sends `error{code:'frame_overflow'}`,
|
|
271
|
+
* and DESTROYS that socket — cap-and-drop the peer, never grow-to-OOM. `kind`
|
|
272
|
+
* says which bound tripped: a single oversized frame (`line`) or total backlog
|
|
273
|
+
* (`total`). */
|
|
274
|
+
export declare class FrameOverflowError extends Error {
|
|
275
|
+
readonly kind: 'line' | 'total';
|
|
276
|
+
readonly bytes: number;
|
|
277
|
+
readonly cap: number;
|
|
278
|
+
constructor(kind: 'line' | 'total', bytes: number, cap: number);
|
|
279
|
+
}
|
|
280
|
+
/** Caps the CLIENT uses reading BROKER frames. The `welcome.snapshot` carries the
|
|
281
|
+
* full message history and can be many MiB, so these are generous. Covers
|
|
282
|
+
* realistic long sessions; snapshot CHUNKING is deferred (plan §1.1 known
|
|
283
|
+
* limitation, review m3 — the long-lived broker makes a big welcome a realistic,
|
|
284
|
+
* not pathological, trigger, but the fixed cap is accepted for Phase 4). */
|
|
285
|
+
export declare const CLIENT_READ_CAPS: FrameDecoderCaps;
|
|
286
|
+
/** Caps the BROKER uses reading CLIENT frames. Plan §1.1 specified a tight
|
|
287
|
+
* 4/16 MiB, but review M1 requires image-paste frames to fit: a resizeImage-
|
|
288
|
+
* bounded PNG's base64 is a few MiB and would clip a 4 MiB line cap. So these are
|
|
289
|
+
* RAISED above the plan's 4/16 to hold an image-bearing `prompt`/`steer`/
|
|
290
|
+
* `follow_up` frame (M1). Still bounded, so a malicious/buggy client→broker frame
|
|
291
|
+
* is cap-and-dropped, never grow-to-OOM (C5). */
|
|
292
|
+
export declare const BROKER_READ_CAPS: FrameDecoderCaps;
|
|
293
|
+
/** Incremental newline-delimited JSON reader: feed raw socket chunks, get back
|
|
294
|
+
* the complete frames decoded so far. Buffers a partial trailing line across
|
|
295
|
+
* pushes; silently drops a malformed (unparseable) line (a viewer must never
|
|
296
|
+
* crash the broker on bad JSON). The caller narrows the `unknown` against the
|
|
297
|
+
* frame unions.
|
|
298
|
+
*
|
|
299
|
+
* BOUNDED (C5): an oversized buffer is the THROW case. If a single push peaks the
|
|
300
|
+
* internal buffer above `maxTotalBytes`, or the surviving (unterminated) partial
|
|
301
|
+
* line exceeds `maxLineBytes`, {@link push} throws {@link FrameOverflowError}
|
|
302
|
+
* instead of returning frames — the caller drops that peer rather than letting a
|
|
303
|
+
* malicious/buggy stream grow the buffer to OOM. Bytes are measured with
|
|
304
|
+
* `Buffer.byteLength`, never string length; a running counter makes each push's
|
|
305
|
+
* size CHECK O(1) (no re-scan of the whole buffer to measure it).
|
|
306
|
+
*
|
|
307
|
+
* AMORTIZED O(total bytes) (perf regression fix, 2026-06-09): the partial line
|
|
308
|
+
* carried across pushes is held as an ARRAY of chunk strings (`parts`), joined
|
|
309
|
+
* only when its terminating `\n` arrives. The `\n` scan looks at each incoming
|
|
310
|
+
* chunk ONCE — never re-scanning the accumulated carry — so a multi-MiB frame
|
|
311
|
+
* arriving in ~64 KiB socket chunks costs O(frame) total instead of O(frame ×
|
|
312
|
+
* chunks). The old `buf += chunk; buf.indexOf('\n')` shape flattened + re-walked
|
|
313
|
+
* the whole carry per chunk: a 16 MiB welcome snapshot cost ~250 ms of
|
|
314
|
+
* event-loop stall in `crtr attach` (typing lag) and the same again in the
|
|
315
|
+
* broker; this shape decodes it in ~13 ms.
|
|
316
|
+
*
|
|
317
|
+
* A `StringDecoder` decodes incoming Buffer chunks: an incomplete trailing
|
|
318
|
+
* multibyte sequence is held back across pushes instead of being mangled to
|
|
319
|
+
* U+FFFD, so a char split at a `node:net` chunk boundary is never corrupted, and
|
|
320
|
+
* `partBytes` counts decoded-string bytes (a raw `chunk.length` would desync
|
|
321
|
+
* and weaken the C5 cap on multibyte/invalid input). */
|
|
322
|
+
export declare class FrameDecoder {
|
|
323
|
+
private readonly caps;
|
|
324
|
+
/** The unterminated partial line carried across pushes, as unjoined chunks
|
|
325
|
+
* (never contains a '\n'). Joined lazily when its newline arrives. */
|
|
326
|
+
private parts;
|
|
327
|
+
/** Byte length (Buffer.byteLength) of `parts` joined — kept in exact lockstep. */
|
|
328
|
+
private partBytes;
|
|
329
|
+
private readonly utf8;
|
|
330
|
+
constructor(caps: FrameDecoderCaps);
|
|
331
|
+
push(chunk: Buffer | string): unknown[];
|
|
332
|
+
}
|