@crouton-kit/crouter 0.3.8 → 0.3.12
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/bin/crtrd +2 -0
- package/dist/builtin-personas/design/base.md +9 -0
- package/dist/builtin-personas/design/orchestrator.md +10 -0
- package/dist/builtin-personas/developer/base.md +9 -0
- package/dist/builtin-personas/developer/orchestrator.md +12 -0
- package/dist/builtin-personas/explore/base.md +9 -0
- package/dist/builtin-personas/explore/orchestrator.md +9 -0
- package/dist/builtin-personas/general/base.md +5 -0
- package/dist/builtin-personas/general/orchestrator.md +7 -0
- package/dist/builtin-personas/orchestration-kernel.md +71 -0
- package/dist/builtin-personas/plan/base.md +7 -0
- package/dist/builtin-personas/plan/orchestrator.md +12 -0
- package/dist/builtin-personas/review/base.md +7 -0
- package/dist/builtin-personas/review/orchestrator.md +9 -0
- package/dist/builtin-personas/runtime-base.md +39 -0
- package/dist/builtin-personas/spec/base.md +7 -0
- package/dist/builtin-personas/spec/orchestrator.md +10 -0
- package/dist/builtin-skills/skills/design/SKILL.md +51 -0
- package/dist/builtin-skills/skills/development/SKILL.md +109 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
- package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
- package/dist/cli.js +25 -27
- package/dist/commands/{job.d.ts → attention.d.ts} +1 -1
- package/dist/commands/attention.js +152 -0
- package/dist/commands/canvas.d.ts +2 -0
- package/dist/commands/canvas.js +35 -0
- package/dist/commands/{agent.d.ts → daemon.d.ts} +1 -1
- package/dist/commands/daemon.js +111 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +65 -0
- package/dist/commands/human/prompts.d.ts +5 -0
- package/dist/commands/human/prompts.js +269 -0
- package/dist/commands/human/queue.d.ts +3 -0
- package/dist/commands/human/queue.js +133 -0
- package/dist/commands/human/shared.d.ts +43 -0
- package/dist/commands/human/shared.js +107 -0
- package/dist/commands/human.js +15 -427
- package/dist/commands/node.d.ts +2 -0
- package/dist/commands/node.js +354 -0
- package/dist/commands/pkg/market-inspect.d.ts +1 -0
- package/dist/commands/pkg/market-inspect.js +157 -0
- package/dist/commands/pkg/market-manage.d.ts +1 -0
- package/dist/commands/pkg/market-manage.js +316 -0
- package/dist/commands/pkg/market.d.ts +1 -0
- package/dist/commands/pkg/market.js +16 -0
- package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
- package/dist/commands/pkg/plugin-inspect.js +142 -0
- package/dist/commands/pkg/plugin-manage.d.ts +1 -0
- package/dist/commands/pkg/plugin-manage.js +294 -0
- package/dist/commands/pkg/plugin.d.ts +1 -0
- package/dist/commands/pkg/plugin.js +16 -0
- package/dist/commands/pkg/shared.d.ts +5 -0
- package/dist/commands/pkg/shared.js +61 -0
- package/dist/commands/pkg.js +8 -1004
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.js +159 -0
- package/dist/commands/revive.d.ts +2 -0
- package/dist/commands/revive.js +64 -0
- package/dist/commands/skill/author.d.ts +3 -0
- package/dist/commands/skill/author.js +147 -0
- package/dist/commands/skill/find.d.ts +4 -0
- package/dist/commands/skill/find.js +254 -0
- package/dist/commands/skill/read.d.ts +1 -0
- package/dist/commands/skill/read.js +89 -0
- package/dist/commands/skill/shared.d.ts +19 -0
- package/dist/commands/skill/shared.js +207 -0
- package/dist/commands/skill/state.d.ts +3 -0
- package/dist/commands/skill/state.js +69 -0
- package/dist/commands/skill.js +12 -681
- package/dist/commands/sys/config.d.ts +1 -0
- package/dist/commands/sys/config.js +186 -0
- package/dist/commands/sys/doctor.d.ts +1 -0
- package/dist/commands/sys/doctor.js +369 -0
- package/dist/commands/sys/shared.d.ts +3 -0
- package/dist/commands/sys/shared.js +24 -0
- package/dist/commands/sys/update.d.ts +2 -0
- package/dist/commands/sys/update.js +114 -0
- package/dist/commands/sys.js +9 -694
- package/dist/core/__tests__/argv-parser.test.js +19 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
- package/dist/core/__tests__/canvas.test.js +154 -0
- package/dist/core/__tests__/reset.test.js +105 -0
- package/dist/core/__tests__/resolver.test.js +69 -1
- package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
- package/dist/core/__tests__/unknown-path.test.js +52 -0
- package/dist/core/bootstrap.d.ts +2 -0
- package/dist/core/bootstrap.js +66 -0
- package/dist/core/canvas/attention.d.ts +24 -0
- package/dist/core/canvas/attention.js +94 -0
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +210 -0
- package/dist/core/canvas/db.d.ts +7 -0
- package/dist/core/canvas/db.js +61 -0
- package/dist/core/canvas/index.d.ts +4 -0
- package/dist/core/canvas/index.js +6 -0
- package/dist/core/canvas/paths.d.ts +16 -0
- package/dist/core/canvas/paths.js +62 -0
- package/dist/core/canvas/render.d.ts +30 -0
- package/dist/core/canvas/render.js +186 -0
- package/dist/core/canvas/types.d.ts +87 -0
- package/dist/core/canvas/types.js +8 -0
- package/dist/core/command.d.ts +63 -2
- package/dist/core/command.js +97 -24
- package/dist/core/feed/feed.d.ts +43 -0
- package/dist/core/feed/feed.js +116 -0
- package/dist/core/feed/inbox.d.ts +50 -0
- package/dist/core/feed/inbox.js +124 -0
- package/dist/core/frontmatter.d.ts +10 -0
- package/dist/core/frontmatter.js +24 -9
- package/dist/core/help.d.ts +39 -8
- package/dist/core/help.js +69 -35
- package/dist/core/io.d.ts +15 -1
- package/dist/core/io.js +56 -6
- package/dist/core/personas/index.d.ts +12 -0
- package/dist/core/personas/index.js +10 -0
- package/dist/core/personas/loader.d.ts +44 -0
- package/dist/core/personas/loader.js +157 -0
- package/dist/core/personas/resolve.d.ts +36 -0
- package/dist/core/personas/resolve.js +110 -0
- package/dist/core/render.d.ts +11 -0
- package/dist/core/render.js +126 -0
- package/dist/core/resolver.d.ts +10 -0
- package/dist/core/resolver.js +160 -2
- package/dist/core/runtime/front-door.d.ts +10 -0
- package/dist/core/runtime/front-door.js +97 -0
- package/dist/core/runtime/kickoff.d.ts +23 -0
- package/dist/core/runtime/kickoff.js +134 -0
- package/dist/core/runtime/launch.d.ts +34 -0
- package/dist/core/runtime/launch.js +85 -0
- package/dist/core/runtime/nodes.d.ts +38 -0
- package/dist/core/runtime/nodes.js +95 -0
- package/dist/core/runtime/presence.d.ts +38 -0
- package/dist/core/runtime/presence.js +152 -0
- package/dist/core/runtime/promote.d.ts +30 -0
- package/dist/core/runtime/promote.js +105 -0
- package/dist/core/runtime/reset.d.ts +13 -0
- package/dist/core/runtime/reset.js +97 -0
- package/dist/core/runtime/revive.d.ts +26 -0
- package/dist/core/runtime/revive.js +89 -0
- package/dist/core/runtime/roadmap.d.ts +12 -0
- package/dist/core/runtime/roadmap.js +52 -0
- package/dist/core/runtime/spawn.d.ts +33 -0
- package/dist/core/runtime/spawn.js +118 -0
- package/dist/core/runtime/stop-guard.d.ts +18 -0
- package/dist/core/runtime/stop-guard.js +33 -0
- package/dist/core/runtime/tmux.d.ts +88 -0
- package/dist/core/runtime/tmux.js +198 -0
- package/dist/core/spawn.d.ts +17 -80
- package/dist/core/spawn.js +15 -219
- package/dist/daemon/crtrd-cli.d.ts +1 -0
- package/dist/daemon/crtrd-cli.js +4 -0
- package/dist/daemon/crtrd.d.ts +20 -0
- package/dist/daemon/crtrd.js +200 -0
- package/dist/daemon/manage.d.ts +17 -0
- package/dist/daemon/manage.js +57 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
- package/dist/pi-extensions/canvas-nav.d.ts +32 -0
- package/dist/pi-extensions/canvas-nav.js +536 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
- package/dist/pi-extensions/canvas-stophook.js +373 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/package.json +6 -5
- package/dist/commands/agent.js +0 -384
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -179
- package/dist/commands/job.js +0 -344
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -309
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -286
- package/dist/core/__tests__/flow-leaves.test.js +0 -248
- package/dist/core/__tests__/job.test.js +0 -310
- package/dist/core/__tests__/jobs.test.js +0 -66
- package/dist/core/jobs.d.ts +0 -101
- package/dist/core/jobs.js +0 -462
- package/dist/prompts/agent.d.ts +0 -18
- package/dist/prompts/agent.js +0 -153
- package/dist/prompts/debug.d.ts +0 -8
- package/dist/prompts/debug.js +0 -44
- /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
- /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
- /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// canvas-inbox-watcher.ts — pi extension for pi-native canvas agent nodes.
|
|
2
|
+
//
|
|
3
|
+
// Loaded into every canvas node's pi process via the node's launch.extensions
|
|
4
|
+
// list. INERT when CRTR_NODE_ID is absent (plain pi session or legacy job agent).
|
|
5
|
+
//
|
|
6
|
+
// The canvas model: each node is a long-lived resident that alternates between
|
|
7
|
+
// "working" (pi actively generating) and "dormant" (pi idle, waiting for a
|
|
8
|
+
// subscribed worker to push a report). This watcher bridges the dormant→working
|
|
9
|
+
// transition automatically: it polls the node's inbox.jsonl every 800ms and,
|
|
10
|
+
// when new entries arrive, coalesces them into a single digest and injects it as
|
|
11
|
+
// a pi user message — waking the node to react.
|
|
12
|
+
//
|
|
13
|
+
// Key differences from the legacy agent-inbox-watcher:
|
|
14
|
+
// • Target resolution is trivial. CRTR_NODE_ID IS the node; its inbox lives at
|
|
15
|
+
// nodes/<CRTR_NODE_ID>/inbox.jsonl. No session-dir scanning, no pi_session_id
|
|
16
|
+
// matching, no spawned-vs-top-level branching.
|
|
17
|
+
// • readInboxSince / readCursor / writeCursor from the canvas inbox primitive
|
|
18
|
+
// replace the hand-rolled JSONL scanner and cursor-file helpers.
|
|
19
|
+
// • coalesce() renders the digest (pointer list, not job-status prose).
|
|
20
|
+
// • No crtr root-init or spawnSync bootstrap — the canvas runtime wires up the
|
|
21
|
+
// node before launching pi; CRTR_NODE_ID is always present when we activate.
|
|
22
|
+
// • Deliver-as decision is driven by InboxEntry.tier (and kind): critical →
|
|
23
|
+
// true preempt (ctx.abort() the live turn, redeliver next tick), urgent →
|
|
24
|
+
// steer at the turn boundary, normal|deferred → followUp. A finished node
|
|
25
|
+
// (kind 'final') ALSO steers — a completion the subscriber may be blocked on
|
|
26
|
+
// must interrupt the current turn, not wait behind it as a follow-up.
|
|
27
|
+
//
|
|
28
|
+
// Double-notify prevention (copied from legacy watcher):
|
|
29
|
+
// A module-level `liveTimer` ensures that a /reload re-init clears the previous
|
|
30
|
+
// setInterval before starting a new one — exactly one watcher is live at a time.
|
|
31
|
+
//
|
|
32
|
+
// Plain TS-with-types — no imports from @earendil-works/* so this compiles inside
|
|
33
|
+
// crouter's own tsc build without a dep on the pi packages.
|
|
34
|
+
import { readInboxSince, readCursor, writeCursor, coalesce, } from '../core/feed/inbox.js';
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Module-level timer — prevents stacking on /reload (the double-notify bug).
|
|
37
|
+
//
|
|
38
|
+
// pi ignores an extension factory's returned disposer, so a /reload re-enters
|
|
39
|
+
// this module and would ADD a new setInterval on top of any running one.
|
|
40
|
+
// N reloads → N live watchers, each with its own in-memory cursor → N deliveries
|
|
41
|
+
// of the same entry. Clearing the prior timer on each re-init ensures exactly
|
|
42
|
+
// one watcher is live. Pattern copied verbatim from agent-inbox-watcher.ts.
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
let liveTimer;
|
|
45
|
+
const TICK_MS = 800; // polling cadence
|
|
46
|
+
const DEBOUNCE_MS = 1000; // flush once the burst has been quiet for this long
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Extension
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/**
|
|
51
|
+
* Register the canvas inbox watcher on `pi`.
|
|
52
|
+
*
|
|
53
|
+
* CRTR_NODE_ID is re-read each tick so late-injected env (edge case) is
|
|
54
|
+
* handled gracefully. Returns a disposer for testability; pi ignores it —
|
|
55
|
+
* the module-level liveTimer guard is the actual stacking prevention.
|
|
56
|
+
*/
|
|
57
|
+
export function registerCanvasInboxWatcher(pi) {
|
|
58
|
+
// Capture the latest event context so isIdle() is readable inside the timer
|
|
59
|
+
// callback, which has no ctx of its own.
|
|
60
|
+
let lastCtx;
|
|
61
|
+
let streaming = false;
|
|
62
|
+
const captureCtx = (_event, ctx) => {
|
|
63
|
+
if (ctx !== undefined)
|
|
64
|
+
lastCtx = ctx;
|
|
65
|
+
};
|
|
66
|
+
pi.on('session_start', captureCtx);
|
|
67
|
+
pi.on('turn_end', captureCtx);
|
|
68
|
+
pi.on('agent_start', (_e, ctx) => {
|
|
69
|
+
captureCtx(_e, ctx);
|
|
70
|
+
streaming = true;
|
|
71
|
+
});
|
|
72
|
+
pi.on('agent_end', (_e, ctx) => {
|
|
73
|
+
captureCtx(_e, ctx);
|
|
74
|
+
streaming = false;
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* True when pi is not currently streaming a response.
|
|
78
|
+
* When idle, sendUserMessage triggers a new turn immediately.
|
|
79
|
+
* When streaming, steer (interrupt) on urgency or a finished node, else follow up.
|
|
80
|
+
*/
|
|
81
|
+
const isIdle = () => {
|
|
82
|
+
try {
|
|
83
|
+
if (typeof lastCtx?.isIdle === 'function')
|
|
84
|
+
return lastCtx.isIdle() === true;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
/* fall through to the streaming flag */
|
|
88
|
+
}
|
|
89
|
+
return !streaming;
|
|
90
|
+
};
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Debounce state
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
/** Entries received since the last flush — coalesced into one message. */
|
|
95
|
+
let buffer = [];
|
|
96
|
+
/** Epoch-ms of the most recent entry arrival. Used to detect burst-quiet. */
|
|
97
|
+
let lastArrival = 0;
|
|
98
|
+
/**
|
|
99
|
+
* Durable cursor — ISO 8601 of the last entry we've consumed.
|
|
100
|
+
* Seeded from the persisted cursor file on first resolution; undefined means
|
|
101
|
+
* "read from the beginning" (no prior cursor → process all existing entries).
|
|
102
|
+
* NOT reset to `now` on first tick: that would silently drop entries that
|
|
103
|
+
* arrived between node creation and watcher startup (the startup race).
|
|
104
|
+
*/
|
|
105
|
+
let cursor;
|
|
106
|
+
let seeded = false;
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Flush: deliver the buffered entries as a single pi user message.
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
const flush = () => {
|
|
111
|
+
if (buffer.length === 0)
|
|
112
|
+
return;
|
|
113
|
+
// Deferred-tier entries must never WAKE an idle node — by contract they ride
|
|
114
|
+
// the next natural turn, never interrupt. If everything buffered is deferred
|
|
115
|
+
// and the node is idle, hold them (leave buffered, cheap re-check each tick)
|
|
116
|
+
// and return without delivering. They flush the moment the node is next
|
|
117
|
+
// streaming, or a higher-tier entry joins the batch (every() turns false).
|
|
118
|
+
if (isIdle() && buffer.every((e) => e.tier === 'deferred'))
|
|
119
|
+
return;
|
|
120
|
+
const batch = buffer;
|
|
121
|
+
buffer = [];
|
|
122
|
+
const digest = coalesce(batch);
|
|
123
|
+
// Tier (and kind) drive delivery mode. Critical is a TRUE preempt; urgent —
|
|
124
|
+
// and a finished node (kind 'final') — steers at the turn boundary;
|
|
125
|
+
// normal/deferred ride the next turn (followUp). (A purely-deferred idle
|
|
126
|
+
// batch was already held above and never reaches here.) A completion a
|
|
127
|
+
// subscriber is likely blocked on must not drain as a follow-up, so 'final'
|
|
128
|
+
// steers exactly like 'urgent'.
|
|
129
|
+
const anyCritical = batch.some((e) => e.tier === 'critical');
|
|
130
|
+
const steerMidStream = anyCritical || batch.some((e) => e.tier === 'urgent' || e.kind === 'final');
|
|
131
|
+
try {
|
|
132
|
+
if (isIdle()) {
|
|
133
|
+
// Idle → trigger a new turn immediately (sendUserMessage always triggers).
|
|
134
|
+
pi.sendUserMessage(digest);
|
|
135
|
+
}
|
|
136
|
+
else if (anyCritical) {
|
|
137
|
+
// Critical mid-stream → TRUE preempt. ctx.abort() cancels the live LLM
|
|
138
|
+
// stream right now (stopReason becomes 'aborted'; the stophook stays alive
|
|
139
|
+
// on that). We then re-buffer and let the next tick deliver via the idle
|
|
140
|
+
// path — by then the turn has torn down and sendUserMessage starts a fresh
|
|
141
|
+
// turn. Relying on the proven idle path (not steer-after-abort semantics)
|
|
142
|
+
// keeps this robust; if abort hasn't settled by the next tick we simply
|
|
143
|
+
// abort again and retry — idempotent and self-healing.
|
|
144
|
+
try {
|
|
145
|
+
lastCtx?.abort?.();
|
|
146
|
+
}
|
|
147
|
+
catch { /* abort is best-effort */ }
|
|
148
|
+
buffer = batch.concat(buffer);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Mid-stream → steer on urgency or a finished node, else enqueue for the
|
|
152
|
+
// turn after this one.
|
|
153
|
+
pi.sendUserMessage(digest, { deliverAs: steerMidStream ? 'steer' : 'followUp' });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Re-queue on delivery failure so a transient error doesn't silently drop
|
|
158
|
+
// inbox entries. They will be retried on the next flush.
|
|
159
|
+
buffer = batch.concat(buffer);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Tick: poll the node's inbox and buffer new arrivals.
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
const tick = () => {
|
|
166
|
+
try {
|
|
167
|
+
// Re-read env each tick: CRTR_NODE_ID could theoretically be set after the
|
|
168
|
+
// extension factory runs (e.g. the runtime injects it just before the first
|
|
169
|
+
// turn). In practice it is always present before turn_end fires, but the
|
|
170
|
+
// check is cheap and keeps the watcher robust.
|
|
171
|
+
const nodeId = process.env['CRTR_NODE_ID'];
|
|
172
|
+
if (nodeId === undefined || nodeId.trim() === '')
|
|
173
|
+
return;
|
|
174
|
+
// Seed the cursor once, on the first tick that resolves a nodeId.
|
|
175
|
+
// readCursor returns undefined when no cursor file exists → readInboxSince
|
|
176
|
+
// with undefined returns ALL entries (no truncation to `now`).
|
|
177
|
+
if (!seeded) {
|
|
178
|
+
cursor = readCursor(nodeId);
|
|
179
|
+
seeded = true;
|
|
180
|
+
}
|
|
181
|
+
const newEntries = readInboxSince(nodeId, cursor);
|
|
182
|
+
if (newEntries.length > 0) {
|
|
183
|
+
// Advance and persist the cursor BEFORE buffering, so a crash after this
|
|
184
|
+
// point loses at most one coalesced message rather than re-injecting
|
|
185
|
+
// already-delivered entries on restart (exactly-once over restart contract).
|
|
186
|
+
const latest = newEntries.reduce((a, b) => (a.ts > b.ts ? a : b));
|
|
187
|
+
cursor = latest.ts;
|
|
188
|
+
writeCursor(nodeId, cursor);
|
|
189
|
+
buffer.push(...newEntries);
|
|
190
|
+
lastArrival = Date.now();
|
|
191
|
+
}
|
|
192
|
+
// Flush only once the burst has settled (no new entry within DEBOUNCE_MS)
|
|
193
|
+
// so near-simultaneous pushes from multiple workers arrive as one message.
|
|
194
|
+
if (buffer.length > 0 && Date.now() - lastArrival >= DEBOUNCE_MS) {
|
|
195
|
+
flush();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
/* watcher is best-effort; a tick must never crash the host session */
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Timer management — clear any leftover timer from a prior /reload.
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
if (liveTimer !== undefined)
|
|
206
|
+
clearInterval(liveTimer);
|
|
207
|
+
const timer = setInterval(tick, TICK_MS);
|
|
208
|
+
// unref() so the watcher doesn't keep the Node process alive when everything
|
|
209
|
+
// else has finished (matches legacy watcher behaviour).
|
|
210
|
+
if (typeof timer.unref === 'function')
|
|
211
|
+
timer.unref();
|
|
212
|
+
liveTimer = timer;
|
|
213
|
+
// pi DOES fire session_shutdown — use it as the authoritative teardown so a
|
|
214
|
+
// re-init (e.g. /reload) never discovers a live sibling timer.
|
|
215
|
+
pi.on('session_shutdown', () => {
|
|
216
|
+
clearInterval(timer);
|
|
217
|
+
if (liveTimer === timer)
|
|
218
|
+
liveTimer = undefined;
|
|
219
|
+
});
|
|
220
|
+
// Disposer: returned for testability + explicit teardown in test harnesses.
|
|
221
|
+
// pi ignores the factory return value, so the module-level guard above is what
|
|
222
|
+
// actually prevents stacking in production.
|
|
223
|
+
return () => {
|
|
224
|
+
clearInterval(timer);
|
|
225
|
+
if (liveTimer === timer)
|
|
226
|
+
liveTimer = undefined;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export default registerCanvasInboxWatcher;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type PiEvents = 'session_start' | 'turn_end' | 'session_shutdown';
|
|
2
|
+
interface ExtensionWidgetOptions {
|
|
3
|
+
/** Where the widget is rendered. "aboveEditor" | "belowEditor" */
|
|
4
|
+
placement?: 'aboveEditor' | 'belowEditor';
|
|
5
|
+
}
|
|
6
|
+
interface UIContext {
|
|
7
|
+
setWidget(key: string, content: string[] | undefined, options?: ExtensionWidgetOptions): void;
|
|
8
|
+
/** Raw key tap that fires BEFORE the editor. Return {consume:true} to swallow
|
|
9
|
+
* the key (so e.g. UP doesn't trigger pi's history recall). Returns unsub. */
|
|
10
|
+
onTerminalInput?(handler: (data: string) => {
|
|
11
|
+
consume?: boolean;
|
|
12
|
+
data?: string;
|
|
13
|
+
} | undefined): () => void;
|
|
14
|
+
/** Current editor buffer text — used to only hijack keys on an empty editor. */
|
|
15
|
+
getEditorText?(): string;
|
|
16
|
+
/** Transient toast, used to report a failed focus. */
|
|
17
|
+
notify?(message: string, type?: 'info' | 'warning' | 'error'): void;
|
|
18
|
+
}
|
|
19
|
+
interface ExtensionCtx {
|
|
20
|
+
ui: UIContext;
|
|
21
|
+
}
|
|
22
|
+
interface PiLike {
|
|
23
|
+
on(event: PiEvents, handler: (event: any, ctx: ExtensionCtx) => void | Promise<void>): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register the canvas nav chrome on `pi`.
|
|
27
|
+
*
|
|
28
|
+
* Returns immediately when CRTR_NODE_ID is absent — the extension is fully
|
|
29
|
+
* inert in a non-canvas pi session.
|
|
30
|
+
*/
|
|
31
|
+
export declare function registerCanvasNav(pi: PiLike): void;
|
|
32
|
+
export default registerCanvasNav;
|