@crouton-kit/crouter 0.3.17 → 0.3.19
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/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/design/orchestrator.md +1 -1
- package/dist/builtin-personas/developer/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/developer/orchestrator.md +1 -1
- package/dist/builtin-personas/explore/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/general/{base.md → PERSONA.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/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/plan/orchestrator.md +1 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/plan/reviewers/security/{base.md → PERSONA.md} +1 -1
- package/dist/builtin-personas/review/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/{base.md → PERSONA.md} +4 -0
- package/dist/builtin-personas/spec/orchestrator.md +1 -1
- package/dist/builtin-personas/waiting.md +8 -0
- package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +24 -14
- package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +4 -4
- 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 +4 -3
- package/dist/commands/human/prompts.js +4 -10
- package/dist/commands/human/queue.js +43 -8
- package/dist/commands/human/shared.d.ts +28 -1
- package/dist/commands/human/shared.js +48 -10
- 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 +660 -65
- 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-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +677 -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__/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/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__/human-surface-target.test.d.ts +1 -0
- package/dist/core/__tests__/human-surface-target.test.js +98 -0
- package/dist/core/__tests__/kickoff.test.js +37 -3
- package/dist/core/__tests__/live-mutation.test.js +50 -33
- package/dist/core/__tests__/memory.test.js +23 -115
- package/dist/core/__tests__/persona-subkind.test.js +18 -15
- 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 +15 -22
- 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/paths.d.ts +4 -1
- package/dist/core/canvas/paths.js +10 -4
- 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 +85 -3
- package/dist/core/canvas/types.js +2 -2
- 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/help.d.ts +6 -0
- package/dist/core/help.js +7 -0
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/index.d.ts +4 -3
- package/dist/core/personas/index.js +3 -2
- package/dist/core/personas/loader.d.ts +41 -16
- package/dist/core/personas/loader.js +133 -29
- package/dist/core/personas/resolve.d.ts +4 -4
- package/dist/core/personas/resolve.js +28 -16
- 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 +45 -2
- package/dist/core/runtime/launch.js +65 -2
- 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 +33 -0
- package/dist/core/runtime/nodes.js +59 -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 +42 -14
- package/dist/core/runtime/placement.js +228 -38
- package/dist/core/runtime/promote.d.ts +0 -6
- package/dist/core/runtime/promote.js +1 -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 +8 -6
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +19 -0
- package/dist/core/runtime/spawn.js +75 -22
- 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 +25 -1
- package/dist/core/spawn.js +72 -7
- 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 +145 -34
- 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
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, renameSync, readdirSync, rmSync, } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { openDb } from './db.js';
|
|
10
|
+
import { isPidAlive } from './pid.js';
|
|
10
11
|
import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, } from './paths.js';
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
// meta.json (durable identity — the source of truth for what PERSISTS)
|
|
@@ -19,8 +20,8 @@ import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, } from '.
|
|
|
19
20
|
/** The identity keys meta.json persists. Listed explicitly so no runtime field
|
|
20
21
|
* can ever leak onto disk even when a fully-hydrated NodeMeta is handed in. */
|
|
21
22
|
const IDENTITY_KEYS = [
|
|
22
|
-
'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'kind', 'mode',
|
|
23
|
-
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'passive_default',
|
|
23
|
+
'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'host_kind', 'kind', 'mode',
|
|
24
|
+
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'fork_from', 'passive_default',
|
|
24
25
|
'home_session', 'pi_session_id', 'pi_session_file', 'launch',
|
|
25
26
|
];
|
|
26
27
|
/** Project any node object down to its durable-identity subset. */
|
|
@@ -60,12 +61,13 @@ function writeMeta(meta) {
|
|
|
60
61
|
* state. A fresh insert takes the schema defaults for runtime. */
|
|
61
62
|
function upsertRow(meta) {
|
|
62
63
|
openDb()
|
|
63
|
-
.prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, parent, created)
|
|
64
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
64
|
+
.prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, host_kind, parent, created)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
65
66
|
ON CONFLICT(node_id) DO UPDATE SET
|
|
66
67
|
name=excluded.name, kind=excluded.kind, mode=excluded.mode,
|
|
67
|
-
lifecycle=excluded.lifecycle, cwd=excluded.cwd,
|
|
68
|
-
|
|
68
|
+
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
69
|
+
parent=excluded.parent`)
|
|
70
|
+
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.parent ?? null, meta.created);
|
|
69
71
|
}
|
|
70
72
|
/** Seed a node's row at BIRTH: identity columns + runtime columns taken from the
|
|
71
73
|
* incoming meta (defaults: status='active', the rest null). The only writer
|
|
@@ -74,16 +76,17 @@ function upsertRow(meta) {
|
|
|
74
76
|
function seedRow(meta) {
|
|
75
77
|
openDb()
|
|
76
78
|
.prepare(`INSERT INTO nodes
|
|
77
|
-
(node_id, name, kind, mode, lifecycle, cwd, parent, created,
|
|
79
|
+
(node_id, name, kind, mode, lifecycle, cwd, host_kind, parent, created,
|
|
78
80
|
status, intent, pi_pid, "window", tmux_session, pane)
|
|
79
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
80
82
|
ON CONFLICT(node_id) DO UPDATE SET
|
|
81
83
|
name=excluded.name, kind=excluded.kind, mode=excluded.mode,
|
|
82
|
-
lifecycle=excluded.lifecycle, cwd=excluded.cwd,
|
|
84
|
+
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
85
|
+
parent=excluded.parent,
|
|
83
86
|
status=excluded.status, intent=excluded.intent, pi_pid=excluded.pi_pid,
|
|
84
87
|
"window"=excluded."window", tmux_session=excluded.tmux_session,
|
|
85
88
|
pane=excluded.pane`)
|
|
86
|
-
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
89
|
+
.run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
87
90
|
}
|
|
88
91
|
function rowFrom(r) {
|
|
89
92
|
return {
|
|
@@ -94,6 +97,7 @@ function rowFrom(r) {
|
|
|
94
97
|
lifecycle: r['lifecycle'],
|
|
95
98
|
status: r['status'],
|
|
96
99
|
cwd: r['cwd'],
|
|
100
|
+
host_kind: r['host_kind'] ?? null,
|
|
97
101
|
parent: r['parent'] ?? null,
|
|
98
102
|
created: r['created'],
|
|
99
103
|
intent: r['intent'] ?? null,
|
|
@@ -352,17 +356,6 @@ export function rebuildIndex() {
|
|
|
352
356
|
recordSpawn(meta.node_id, prov);
|
|
353
357
|
}
|
|
354
358
|
}
|
|
355
|
-
/** Is `pid` a live process? `kill(pid, 0)` sends no signal — it only probes
|
|
356
|
-
* existence/permission. ESRCH ⇒ gone; EPERM ⇒ alive but not ours (still alive). */
|
|
357
|
-
function pidAlive(pid) {
|
|
358
|
-
try {
|
|
359
|
-
process.kill(pid, 0);
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
catch (e) {
|
|
363
|
-
return e.code === 'EPERM';
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
359
|
/** Retention sweep: remove TERMINAL nodes (status dead | done | canceled) whose
|
|
367
360
|
* `created` is older than `ttlDays`, bounding the otherwise-unbounded growth of
|
|
368
361
|
* node rows + dirs. The edges→nodes FK (`ON DELETE CASCADE`, migration v4) GCs
|
|
@@ -406,7 +399,7 @@ export function pruneNodes(opts) {
|
|
|
406
399
|
if (r['node_id'] === selfId)
|
|
407
400
|
return false;
|
|
408
401
|
const pid = r['pi_pid'];
|
|
409
|
-
return
|
|
402
|
+
return !isPidAlive(pid);
|
|
410
403
|
})
|
|
411
404
|
.map((r) => ({
|
|
412
405
|
node_id: r['node_id'],
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -208,6 +208,50 @@ CREATE TABLE IF NOT EXISTS focuses (
|
|
|
208
208
|
);
|
|
209
209
|
`);
|
|
210
210
|
}
|
|
211
|
+
/** v7 — the `wakeups` table: durable store for scheduled wake-ups (the
|
|
212
|
+
* scheduled-wakeups feature). One row per armed wake. `node_id` is the TARGET
|
|
213
|
+
* and the node-anchored cancel anchor — it carries a `ON DELETE CASCADE` FK to
|
|
214
|
+
* `nodes(node_id)` so a pruned/deleted node's node-anchored wakes are reaped in
|
|
215
|
+
* the same transaction (mirroring the edges v4 cascade; works because
|
|
216
|
+
* `PRAGMA foreign_keys = ON` is set at every open). NULL `node_id` = a
|
|
217
|
+
* canvas-detached wake (deferred spawn / spawn-cron) that survives.
|
|
218
|
+
*
|
|
219
|
+
* `owner_id` is the ARMER, set on EVERY row, and is deliberately a PLAIN indexed
|
|
220
|
+
* column with NO FK (design D1/D2): an FK on `owner_id` would cascade-drop a
|
|
221
|
+
* crashed-then-pruned armer's detached cron, violating Invariant E. A detached
|
|
222
|
+
* wake is reaped only by the explicit `cancelWakesFor` DELETE, never by cascade.
|
|
223
|
+
*
|
|
224
|
+
* `fire_at` is an ISO 8601 UTC string for the NEXT occurrence, so the per-tick
|
|
225
|
+
* due query is a lexicographic `<=` compare. The partial unique index
|
|
226
|
+
* `idx_wakeups_deadline` enforces ≤1 deadline per node. Additive, forward-only. */
|
|
227
|
+
function appendWakeupsTable(db) {
|
|
228
|
+
db.exec(`
|
|
229
|
+
CREATE TABLE IF NOT EXISTS wakeups (
|
|
230
|
+
wakeup_id TEXT PRIMARY KEY,
|
|
231
|
+
node_id TEXT, -- TARGET + node-anchored cancel anchor; NULL = canvas-detached
|
|
232
|
+
owner_id TEXT NOT NULL, -- ARMER; set on EVERY row. PLAIN column, NO FK (design D1/D2)
|
|
233
|
+
fire_at TEXT NOT NULL, -- ISO 8601 UTC; NEXT occurrence; lexicographic <=
|
|
234
|
+
kind TEXT NOT NULL, -- 'bare' | 'noted' | 'deadline' | 'spawn'
|
|
235
|
+
recur TEXT, -- NULL = one-shot; else the pinned recur JSON
|
|
236
|
+
payload TEXT, -- JSON per kind (shared contracts)
|
|
237
|
+
created TEXT NOT NULL,
|
|
238
|
+
FOREIGN KEY (node_id) REFERENCES nodes(node_id) ON DELETE CASCADE
|
|
239
|
+
);
|
|
240
|
+
CREATE INDEX IF NOT EXISTS idx_wakeups_fire ON wakeups(fire_at); -- per-tick due query
|
|
241
|
+
CREATE INDEX IF NOT EXISTS idx_wakeups_node ON wakeups(node_id); -- by-node cancel / list
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_wakeups_owner ON wakeups(owner_id); -- by-owner reap (ruling A)
|
|
243
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_wakeups_deadline ON wakeups(node_id) WHERE kind = 'deadline';
|
|
244
|
+
`);
|
|
245
|
+
}
|
|
246
|
+
/** v8 — additive identity column `host_kind`: which HOST launches + supervises a
|
|
247
|
+
* node — `'tmux'` (a pane) or `'broker'` (the headless in-process engine). It is
|
|
248
|
+
* durable IDENTITY mirrored from meta.json like `cwd` (NOT runtime like `pane`),
|
|
249
|
+
* so it survives an index rebuild via the `IDENTITY_KEYS`/`upsertRow` path.
|
|
250
|
+
* Defaults NULL ⇒ `'tmux'` (every existing node is tmux-hosted); no data
|
|
251
|
+
* backfill. Additive, forward-only. */
|
|
252
|
+
function addHostKindColumn(db) {
|
|
253
|
+
db.exec(`ALTER TABLE nodes ADD COLUMN host_kind TEXT;`);
|
|
254
|
+
}
|
|
211
255
|
/** The ordered migration list. Index `i` is migration version `i + 1`; the db's
|
|
212
256
|
* `user_version` tracks how many have been applied. Append only. */
|
|
213
257
|
export const MIGRATIONS = [
|
|
@@ -217,6 +261,8 @@ export const MIGRATIONS = [
|
|
|
217
261
|
/* v4 */ edgesForeignKeyCascade,
|
|
218
262
|
/* v5 */ addPaneColumn,
|
|
219
263
|
/* v6 */ addFocusesTable,
|
|
264
|
+
/* v7 */ appendWakeupsTable,
|
|
265
|
+
/* v8 */ addHostKindColumn,
|
|
220
266
|
];
|
|
221
267
|
/** Bring `db` up to the latest schema version. Reads `user_version`, runs each
|
|
222
268
|
* pending migration in order, and bumps `user_version` after each so the work
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/** Root of the global canvas home (`~/.
|
|
1
|
+
/** Root of the global canvas home (`~/.crouter/canvas` unless `CRTR_HOME` is set).
|
|
2
|
+
* Nested under the `.crouter` scope root so the whole runtime lives in one
|
|
3
|
+
* visible top-level dir; `canvas/` keeps node-graph runtime state separate from
|
|
4
|
+
* durable user content (skills/plugins/marketplaces/config) at the scope root. */
|
|
2
5
|
export declare function crtrHome(): string;
|
|
3
6
|
export declare function canvasDbPath(): string;
|
|
4
7
|
export declare function nodesRoot(): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The `~/.
|
|
1
|
+
// The `~/.crouter/canvas/` layout. One global, cwd-agnostic home for the whole canvas.
|
|
2
2
|
//
|
|
3
|
-
// ~/.
|
|
3
|
+
// ~/.crouter/canvas/
|
|
4
4
|
// canvas.db sqlite (WAL) — topology only (nodes + edges)
|
|
5
5
|
// nodes/<node_id>/
|
|
6
6
|
// meta.json source of truth for the node's row
|
|
@@ -15,10 +15,16 @@
|
|
|
15
15
|
import { homedir } from 'node:os';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
17
|
import { mkdirSync } from 'node:fs';
|
|
18
|
-
|
|
18
|
+
import { CRTR_DIR_NAME } from '../../types.js';
|
|
19
|
+
/** Root of the global canvas home (`~/.crouter/canvas` unless `CRTR_HOME` is set).
|
|
20
|
+
* Nested under the `.crouter` scope root so the whole runtime lives in one
|
|
21
|
+
* visible top-level dir; `canvas/` keeps node-graph runtime state separate from
|
|
22
|
+
* durable user content (skills/plugins/marketplaces/config) at the scope root. */
|
|
19
23
|
export function crtrHome() {
|
|
20
24
|
const override = process.env['CRTR_HOME'];
|
|
21
|
-
return override !== undefined && override !== ''
|
|
25
|
+
return override !== undefined && override !== ''
|
|
26
|
+
? override
|
|
27
|
+
: join(homedir(), CRTR_DIR_NAME, 'canvas');
|
|
22
28
|
}
|
|
23
29
|
export function canvasDbPath() {
|
|
24
30
|
return join(crtrHome(), 'canvas.db');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** True if a process with `pid` is currently alive (signal-0 probe). `kill(pid,
|
|
2
|
+
* 0)` throws ESRCH when the process is gone; EPERM means it exists but isn't
|
|
3
|
+
* ours — still alive. A null/undefined pid (legacy / never-booted) reads dead. */
|
|
4
|
+
export declare function isPidAlive(pid: number | null | undefined): boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/core/canvas/pid.ts
|
|
2
|
+
//
|
|
3
|
+
// The ONE shared signal-0 liveness probe. It lives at the canvas/ layer — the
|
|
4
|
+
// LOWEST shared layer — so canvas/, runtime/, AND daemon/ can all import it
|
|
5
|
+
// "down" without a reverse-layer violation (runtime/ sits above canvas/, daemon/
|
|
6
|
+
// above runtime/). It is a pure process-existence utility, NOT data-model access,
|
|
7
|
+
// so it is exempt from the "only canvas.ts touches the db" rule. Collapses the
|
|
8
|
+
// four near-identical copies that used to live in canvas.ts, revive.ts,
|
|
9
|
+
// placement.ts, and crtrd.ts into one (Phase 3 review reuse MINOR-1).
|
|
10
|
+
/** True if a process with `pid` is currently alive (signal-0 probe). `kill(pid,
|
|
11
|
+
* 0)` throws ESRCH when the process is gone; EPERM means it exists but isn't
|
|
12
|
+
* ours — still alive. A null/undefined pid (legacy / never-booted) reads dead. */
|
|
13
|
+
export function isPidAlive(pid) {
|
|
14
|
+
if (pid == null)
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return e.code === 'EPERM';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeStatus } from './types.js';
|
|
1
|
+
import type { NodeStatus, Lifecycle } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Render the subscription sub-DAG rooted at `rootId` as an ASCII tree.
|
|
4
4
|
* The root is the first line (no connector prefix); children are indented.
|
|
@@ -28,11 +28,20 @@ export interface DashboardRow {
|
|
|
28
28
|
cwd: string;
|
|
29
29
|
/** ISO 8601 birth timestamp — drives the recency sort + the relative-age cue. */
|
|
30
30
|
created: string;
|
|
31
|
+
/** terminal = one-shot worker (finalizes on push --final); resident = persistent
|
|
32
|
+
* agent you come back to. Drives the browser's resident-only lifecycle filter.
|
|
33
|
+
* Only populated by dashboardRowsAll (the browser snapshot). */
|
|
34
|
+
lifecycle?: Lifecycle;
|
|
31
35
|
/** The node's spawn prompt (context/initial-prompt.md), trimmed + capped. Only
|
|
32
36
|
* populated by dashboardRowsAll (the browser snapshot) — the dashboard leaf
|
|
33
37
|
* leaves it undefined to avoid a file read per node. Indexed by super-search
|
|
34
|
-
* and shown in the preview panel. */
|
|
38
|
+
* and shown in the preview panel when there is no live query. */
|
|
35
39
|
goal?: string;
|
|
40
|
+
/** EVERY user prompt across the node's pi session — the whole conversation, not
|
|
41
|
+
* just the spawn prompt — joined + capped. Populated by dashboardRowsAll, undefined
|
|
42
|
+
* for never-revived nodes (no session file). Powers whole-conversation super-search
|
|
43
|
+
* + the windowed match snippet in the preview. */
|
|
44
|
+
prompts?: string;
|
|
36
45
|
}
|
|
37
46
|
/** One row per node visible in the sub-DAG of `rootId` (including root). */
|
|
38
47
|
export declare function dashboardRows(rootId: string): DashboardRow[];
|
|
@@ -181,6 +181,73 @@ function readGoalText(nodeId) {
|
|
|
181
181
|
return undefined;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
/** EVERY user prompt across a node's pi session — the whole conversation, not just
|
|
185
|
+
* the spawn prompt. Streams the session jsonl off disk, prefiltering to user-role
|
|
186
|
+
* lines so the big assistant/toolResult lines are never JSON-parsed, extracts each
|
|
187
|
+
* user message's text, joins newline-separated, and caps total + per-message so a
|
|
188
|
+
* long session can't bloat the snapshot. Never throws; returns undefined when there
|
|
189
|
+
* is no session file yet (a node that was never revived). */
|
|
190
|
+
const CONVO_CAP = 8192;
|
|
191
|
+
const CONVO_MSG_CAP = 2048;
|
|
192
|
+
function readConversationPrompts(sessionFile) {
|
|
193
|
+
if (sessionFile === undefined || sessionFile === null || sessionFile === '')
|
|
194
|
+
return undefined;
|
|
195
|
+
try {
|
|
196
|
+
if (!existsSync(sessionFile))
|
|
197
|
+
return undefined;
|
|
198
|
+
const raw = readFileSync(sessionFile, 'utf8');
|
|
199
|
+
const parts = [];
|
|
200
|
+
let total = 0;
|
|
201
|
+
for (const line of raw.split('\n')) {
|
|
202
|
+
// Cheap prefilter: skip every line that isn't a user-role message before the
|
|
203
|
+
// (relatively costly) JSON.parse. Pi writes compact JSON (no spaces), but
|
|
204
|
+
// tolerate the spaced form too.
|
|
205
|
+
if (line === '' || (line.indexOf('"role":"user"') === -1 && line.indexOf('"role": "user"') === -1))
|
|
206
|
+
continue;
|
|
207
|
+
let rec;
|
|
208
|
+
try {
|
|
209
|
+
rec = JSON.parse(line);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (rec.type !== 'message' || rec.message?.role !== 'user')
|
|
215
|
+
continue;
|
|
216
|
+
const text = extractUserText(rec.message.content);
|
|
217
|
+
if (text === '')
|
|
218
|
+
continue;
|
|
219
|
+
const capped = text.length > CONVO_MSG_CAP ? text.slice(0, CONVO_MSG_CAP) : text;
|
|
220
|
+
parts.push(capped);
|
|
221
|
+
total += capped.length + 1;
|
|
222
|
+
if (total >= CONVO_CAP)
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
if (parts.length === 0)
|
|
226
|
+
return undefined;
|
|
227
|
+
const joined = parts.join('\n');
|
|
228
|
+
return joined.length > CONVO_CAP ? joined.slice(0, CONVO_CAP) : joined;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Concatenate the `text` blocks of one pi user message's content. Content is
|
|
235
|
+
* usually an array of `{type,text}` blocks but may be a bare string. */
|
|
236
|
+
function extractUserText(content) {
|
|
237
|
+
if (typeof content === 'string')
|
|
238
|
+
return content.trim();
|
|
239
|
+
if (!Array.isArray(content))
|
|
240
|
+
return '';
|
|
241
|
+
const out = [];
|
|
242
|
+
for (const block of content) {
|
|
243
|
+
if (block !== null && typeof block === 'object') {
|
|
244
|
+
const b = block;
|
|
245
|
+
if (b.type === 'text' && typeof b.text === 'string')
|
|
246
|
+
out.push(b.text);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return out.join(' ').trim();
|
|
250
|
+
}
|
|
184
251
|
/** One row per node visible in the sub-DAG of `rootId` (including root). */
|
|
185
252
|
export function dashboardRows(rootId) {
|
|
186
253
|
const ids = [rootId, ...view(rootId)];
|
|
@@ -219,7 +286,9 @@ export function dashboardRowsAll() {
|
|
|
219
286
|
asks: countAsks(row.node_id),
|
|
220
287
|
cwd: row.cwd,
|
|
221
288
|
created: row.created,
|
|
289
|
+
lifecycle: row.lifecycle,
|
|
222
290
|
goal: readGoalText(row.node_id),
|
|
291
|
+
prompts: readConversationPrompts(meta?.pi_session_file),
|
|
223
292
|
}];
|
|
224
293
|
});
|
|
225
294
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
1
|
+
import type { SpawnChildOpts } from '../runtime/spawn.js';
|
|
2
|
+
/** What a node is doing right now. `active` means the engine process is live on
|
|
3
|
+
* its host — a tmux pane OR a headless broker (`host_kind`) — NOT that it holds
|
|
4
|
+
* a window or that anyone is watching it (attachment is a separate axis: the
|
|
5
|
+
* `focuses` table for tmux, the broker's viewer set for headless). UI shows
|
|
6
|
+
* active+idle; `done` is hidden but revivable; `canceled` is a user-closed node
|
|
7
|
+
* (also hidden, also revivable — not a fault); only `dead` is a fault. */
|
|
4
8
|
export type NodeStatus = 'active' | 'idle' | 'done' | 'dead' | 'canceled';
|
|
5
9
|
/** Does stopping finalize the node? terminal = worker (finalizes on push --final);
|
|
6
10
|
* resident = manager/orchestrator (stays dormant, woken by inbox). */
|
|
@@ -44,6 +48,11 @@ export interface NodeIdentity {
|
|
|
44
48
|
created: string;
|
|
45
49
|
/** The dir this node is pinned to — its cwd (where pi runs, bash executes). */
|
|
46
50
|
cwd: string;
|
|
51
|
+
/** Which HOST launches + supervises this node: 'tmux' (a pane) or 'broker' (the
|
|
52
|
+
* headless in-process engine). Durable IDENTITY (mirrored to the row like
|
|
53
|
+
* `cwd`, re-derived on index rebuild), NOT live runtime like `pane`.
|
|
54
|
+
* NULL ⇒ 'tmux' (every pre-broker node). */
|
|
55
|
+
host_kind?: 'tmux' | 'broker' | null;
|
|
47
56
|
/** Role the node was born as: explore | developer | plan | review | general… */
|
|
48
57
|
kind: string;
|
|
49
58
|
mode: Mode;
|
|
@@ -65,6 +74,14 @@ export interface NodeIdentity {
|
|
|
65
74
|
* `parent` so an INDEPENDENT root (parent=null) still records its lineage.
|
|
66
75
|
* Audit only; null for a user-opened root. Defaults to `parent` for a child. */
|
|
67
76
|
spawned_by?: string | null;
|
|
77
|
+
/** Fork provenance — the `--fork-from` source this node was forked from at
|
|
78
|
+
* spawn (the node id / session path / pi session uuid the caller passed). pi
|
|
79
|
+
* COPIED that conversation into this node's first session, so the node boots
|
|
80
|
+
* carrying the SOURCE's first-person history. Persisted here so the boot
|
|
81
|
+
* intro can assert this node's OWN identity over that inherited narrative
|
|
82
|
+
* (without it a fork impersonates its source). Undefined for a fresh node;
|
|
83
|
+
* identity/audit only. */
|
|
84
|
+
fork_from?: string | null;
|
|
68
85
|
/** New subscriptions this node opens default to passive when true. */
|
|
69
86
|
passive_default?: boolean;
|
|
70
87
|
/** REVIVE-HOME — the tmux session a node is (re)opened into when it must
|
|
@@ -143,6 +160,8 @@ export interface NodeRow {
|
|
|
143
160
|
lifecycle: Lifecycle;
|
|
144
161
|
status: NodeStatus;
|
|
145
162
|
cwd: string;
|
|
163
|
+
/** Which HOST launches + supervises this node; see NodeIdentity.host_kind. */
|
|
164
|
+
host_kind: 'tmux' | 'broker' | null;
|
|
146
165
|
parent: string | null;
|
|
147
166
|
created: string;
|
|
148
167
|
/** Authoritative runtime columns (see NodeRuntime). */
|
|
@@ -188,3 +207,66 @@ export interface SubscriptionRef {
|
|
|
188
207
|
active: boolean;
|
|
189
208
|
created: string;
|
|
190
209
|
}
|
|
210
|
+
/** The four firing actions a wakeup can carry (design §4):
|
|
211
|
+
* - bare → revive the target node directly (resume:false), no inbox entry.
|
|
212
|
+
* - noted → deliver a note to the target's inbox (normal tier).
|
|
213
|
+
* - deadline → deliver a timeout note to the target's inbox (urgent tier); ≤1 per node.
|
|
214
|
+
* - spawn → spawnChild a fresh node from a stored recipe (detached, node_id NULL). */
|
|
215
|
+
export type WakeKind = 'bare' | 'noted' | 'deadline' | 'spawn';
|
|
216
|
+
/** payload for a `bare` wake — `null`, or a label carried only for list rendering. */
|
|
217
|
+
export interface BareWakePayload {
|
|
218
|
+
label?: string;
|
|
219
|
+
}
|
|
220
|
+
/** payload for a `noted` wake — a non-empty note body + a ≤120-char first-line label. */
|
|
221
|
+
export interface NotedWakePayload {
|
|
222
|
+
body: string;
|
|
223
|
+
label: string;
|
|
224
|
+
}
|
|
225
|
+
/** payload for a `deadline` wake — a non-empty timeout note + the machine-readable
|
|
226
|
+
* `timeout` mirror. The VISIBLE timeout signal rides the RENDERED label/body, not
|
|
227
|
+
* this flag (inbox digests never surface arbitrary data keys). ≤1 per node. */
|
|
228
|
+
export interface DeadlineWakePayload {
|
|
229
|
+
body: string;
|
|
230
|
+
timeout: true;
|
|
231
|
+
label: string;
|
|
232
|
+
}
|
|
233
|
+
/** The parsed per-kind payload union (Maj-1): the data-access layer JSON.stringifies
|
|
234
|
+
* this on write and JSON.parses it back on read, so callers consume `payload.body`
|
|
235
|
+
* / `payload as SpawnChildOpts` directly with no re-parse. A `spawn` payload is a
|
|
236
|
+
* SpawnChildOpts recipe with `parent` resolved to a NON-NULL armer id at arm time. */
|
|
237
|
+
export type WakePayload = null | BareWakePayload | NotedWakePayload | DeadlineWakePayload | SpawnChildOpts;
|
|
238
|
+
/** A wakeup row as stored in the `wakeups` table (canvas.db, migration v7). One row =
|
|
239
|
+
* "do <kind> at fire_at [every recur]". `node_id` is the revive TARGET + node-anchored
|
|
240
|
+
* cancel anchor (NULL = canvas-detached); `owner_id` is the ARMER (set on every row).
|
|
241
|
+
* `payload` is the PARSED per-kind union, never the raw JSON string (Maj-1). */
|
|
242
|
+
export interface Wakeup {
|
|
243
|
+
/** Stable id (caller-supplied at arm time, minted as `wk-${newNodeId()}`). */
|
|
244
|
+
wakeup_id: string;
|
|
245
|
+
/** Revive target + node-anchored cancel anchor; NULL = canvas-detached (spawn). */
|
|
246
|
+
node_id: string | null;
|
|
247
|
+
/** The node that armed this wake; set on EVERY row. == node_id for a self-alarm. */
|
|
248
|
+
owner_id: string;
|
|
249
|
+
/** Next absolute fire time, ISO 8601 UTC; lexicographic <= compare. */
|
|
250
|
+
fire_at: string;
|
|
251
|
+
kind: WakeKind;
|
|
252
|
+
/** NULL = one-shot/adaptive; else the recur JSON the daemon advances. */
|
|
253
|
+
recur: string | null;
|
|
254
|
+
/** Parsed per-kind payload (NULL for a bare one-shot with no label). */
|
|
255
|
+
payload: WakePayload;
|
|
256
|
+
/** ISO 8601, audit/order. */
|
|
257
|
+
created: string;
|
|
258
|
+
}
|
|
259
|
+
/** The arm-a-wake spec (the pinned shared contract). `wakeup_id` is CALLER-SUPPLIED —
|
|
260
|
+
* the canvas layer never mints ids (importing newNodeId from runtime/nodes.ts would
|
|
261
|
+
* force a canvas → runtime → canvas cycle, Min-1); the command surface mints
|
|
262
|
+
* `wk-${newNodeId()}` and passes it in. `owner_id` is required on every row;
|
|
263
|
+
* `node_id` is NULL for a detached wake (deferred spawn / spawn-cron). */
|
|
264
|
+
export interface ArmWakeSpec {
|
|
265
|
+
wakeup_id: string;
|
|
266
|
+
node_id?: string | null;
|
|
267
|
+
owner_id: string;
|
|
268
|
+
fire_at: string;
|
|
269
|
+
kind: WakeKind;
|
|
270
|
+
recur?: string | null;
|
|
271
|
+
payload?: WakePayload;
|
|
272
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The canvas vocabulary — the node + edge model the whole runtime hangs on.
|
|
2
2
|
//
|
|
3
|
-
// One global canvas (`~/.
|
|
4
|
-
// each node's flesh lives on disk under `~/.
|
|
3
|
+
// One global canvas (`~/.crouter/canvas/canvas.db`) holds the topology (nodes +
|
|
4
|
+
// edges); each node's flesh lives on disk under `~/.crouter/canvas/nodes/<id>/`. A node's
|
|
5
5
|
// `meta.json` is the source of truth for its own row; the db is a queryable
|
|
6
6
|
// index over those metas, plus the authoritative store for the mutable
|
|
7
7
|
// `subscribes_to` edges (which no single meta owns).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ArmWakeSpec, Wakeup } from './types.js';
|
|
2
|
+
/** A thrown integrity-backstop error from armWake. `code` is the AC-N3 code the
|
|
3
|
+
* command surface (the ONLY sanctioned caller of armWake) maps to a rendered
|
|
4
|
+
* error block — armWake carries integrity backstops only (empty body / recur-on-
|
|
5
|
+
* deadline / unknown kind); target-resolvability + recoverable-state + the
|
|
6
|
+
* per-owner cap live in the surface (Min-6), so a non-surface armer could insert
|
|
7
|
+
* a dangling wake — every armer MUST route through the surface. */
|
|
8
|
+
export declare class WakeArmError extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
constructor(code: string, message: string);
|
|
11
|
+
}
|
|
12
|
+
/** Which wakes a listWakes call returns. Distinguished by which key is present
|
|
13
|
+
* (mirrors listNodes' filter shape):
|
|
14
|
+
* - { node } — wakes anchored to this node (node_id = ?).
|
|
15
|
+
* - { owner } — wakes this node ARMED (owner_id = ?); the per-owner cap count.
|
|
16
|
+
* - { subtree } — wakes anchored to any node in this id set (node_id IN (…)).
|
|
17
|
+
* - { due } — wakes whose fire_at <= this ISO instant.
|
|
18
|
+
* - { canvas } — every wake on the canvas. */
|
|
19
|
+
export type WakeScope = {
|
|
20
|
+
node: string;
|
|
21
|
+
} | {
|
|
22
|
+
owner: string;
|
|
23
|
+
} | {
|
|
24
|
+
subtree: string[];
|
|
25
|
+
} | {
|
|
26
|
+
due: string;
|
|
27
|
+
} | {
|
|
28
|
+
canvas: true;
|
|
29
|
+
};
|
|
30
|
+
/** INSERT one wakeup row from the pinned spec, returning its (caller-supplied)
|
|
31
|
+
* `wakeup_id`. JSON.stringifies the typed `payload` union into the TEXT column
|
|
32
|
+
* and stamps `created`.
|
|
33
|
+
*
|
|
34
|
+
* Deadline upsert (design Q3, "≤1 deadline per node"): for `kind === 'deadline'`
|
|
35
|
+
* the node's existing deadline is canceled first, then the new one inserted — the
|
|
36
|
+
* partial unique index `idx_wakeups_deadline` is the schema-level backstop.
|
|
37
|
+
*
|
|
38
|
+
* Integrity backstops (throw WakeArmError; the surface maps the code to AC-N3):
|
|
39
|
+
* - empty/whitespace note `body` for `noted`/`deadline` → `empty_note`.
|
|
40
|
+
* - non-NULL `recur` on a `deadline` → `deadline_cannot_recur`.
|
|
41
|
+
* - unknown `kind` → `bad_kind`.
|
|
42
|
+
* NO target-resolvability / recoverable-state / cap backstop here (Min-6). */
|
|
43
|
+
export declare function armWake(spec: ArmWakeSpec): string;
|
|
44
|
+
/** UPDATE the next fire time — recurrence advancement (the daemon settles a
|
|
45
|
+
* recurring row to its next slot BEFORE enacting, crash-safe; design D4). */
|
|
46
|
+
export declare function advanceWake(wakeup_id: string, nextFireIso: string): void;
|
|
47
|
+
/** DELETE one wakeup by id — one-shot consumption (the daemon settles a one-shot
|
|
48
|
+
* row BEFORE enacting, crash-safe; design D4). */
|
|
49
|
+
export declare function consumeWake(wakeup_id: string): void;
|
|
50
|
+
/** DELETE one wakeup by id — the explicit user/surface cancel. Idempotent:
|
|
51
|
+
* 0 rows ⇒ no error (AC-C2). */
|
|
52
|
+
export declare function cancelWake(wakeup_id: string): void;
|
|
53
|
+
/** DELETE every wake anchored to this node AND every detached wake it ARMED —
|
|
54
|
+
* deliberate close/reap (ruling A). Hooked at transition('cancel'). */
|
|
55
|
+
export declare function cancelWakesFor(node_id: string): void;
|
|
56
|
+
/** DELETE this node's one-shot self-alarms (recur IS NULL); node-anchored crons
|
|
57
|
+
* SURVIVE — finalize is the instance finishing, not a teardown of standing
|
|
58
|
+
* schedules (design Q1). Hooked at transition('finalize'). */
|
|
59
|
+
export declare function cancelSelfAlarms(node_id: string): void;
|
|
60
|
+
/** DELETE this node's pending deadline(s) — cancel-on-wake (design §6.4).
|
|
61
|
+
* Hooked at reviveNode, so the deadline always belongs to the dormancy left. */
|
|
62
|
+
export declare function cancelDeadlinesFor(node_id: string): void;
|
|
63
|
+
/** The daemon's per-tick due query: every row whose fire_at has arrived, in
|
|
64
|
+
* fire_at order, with `payload` parsed into the typed union. Returns exactly ONE
|
|
65
|
+
* row per recurring wake regardless of missed slots — coalescing is structural
|
|
66
|
+
* (AC-E2). */
|
|
67
|
+
export declare function dueWakes(nowIso: string): Wakeup[];
|
|
68
|
+
/** List wakes for a scope (node / owner / subtree / due / canvas), `payload`
|
|
69
|
+
* parsed as in dueWakes. The subtree variant binds the id set with `?`
|
|
70
|
+
* placeholders (mirroring listNodes — the codebase's one safe dynamic-IN spot);
|
|
71
|
+
* ids are NEVER string-concatenated (Min-10). */
|
|
72
|
+
export declare function listWakes(scope: WakeScope): Wakeup[];
|
|
73
|
+
/** True if the node has ANY pending self-anchored wake (of any kind). Consumed by
|
|
74
|
+
* the stop-guard self-wake seam so a no-child poll node releases dormant after
|
|
75
|
+
* arming instead of being nagged to finish (AC-X3/AC-R1). */
|
|
76
|
+
export declare function hasPendingSelfWake(node_id: string): boolean;
|