@north-light/crouter 0.3.236 → 0.3.237
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/api/client.d.ts +2 -1
- package/dist/api/client.js +3 -0
- package/dist/api/dto/broker-ops.d.ts +1 -1
- package/dist/api/dto/common.d.ts +2 -0
- package/dist/api/dto/lifecycle.d.ts +9 -2
- package/dist/api/dto/messages.d.ts +5 -0
- package/dist/api/dto/nodes.d.ts +10 -2
- package/dist/api/dto/worktree.d.ts +10 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/00-runtime-base/00-authoring.md +1 -1
- package/dist/builtin-memory/internal/agent-shaping.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +1 -1
- package/dist/clients/attach/render/tool-calls.js +18 -3
- package/dist/clients/attach/viewer.js +426 -426
- package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +6 -0
- package/dist/clients/inbox/review/roundtrip.js +1 -1
- package/dist/commands/memory/lint.js +22 -13
- package/dist/commands/memory/shared.js +1 -1
- package/dist/commands/node/create.d.ts +1 -1
- package/dist/commands/node/create.js +1 -1
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node-worktree.js +39 -8
- package/dist/commands/revive.js +11 -2
- package/dist/commands/sys/context/admin/actions.d.ts +48 -0
- package/dist/commands/sys/context/admin/actions.js +246 -0
- package/dist/commands/sys/context/admin/axes-panel.d.ts +21 -0
- package/dist/commands/sys/context/admin/axes-panel.js +117 -0
- package/dist/commands/sys/context/admin/detail-panel.d.ts +53 -0
- package/dist/commands/sys/context/admin/detail-panel.js +178 -0
- package/dist/commands/sys/context/admin/docs-panel.d.ts +45 -0
- package/dist/commands/sys/context/admin/docs-panel.js +186 -0
- package/dist/commands/sys/context/admin/model.d.ts +75 -0
- package/dist/commands/sys/context/admin/model.js +209 -0
- package/dist/commands/sys/context/admin/shell.d.ts +82 -0
- package/dist/commands/sys/context/admin/shell.js +669 -0
- package/dist/commands/sys/context/admin.d.ts +1 -0
- package/dist/commands/sys/context/admin.js +124 -0
- package/dist/commands/sys/context/doc.d.ts +1 -0
- package/dist/commands/sys/context/doc.js +196 -0
- package/dist/commands/sys/context/prompt-review.d.ts +1 -0
- package/dist/commands/sys/{prompt-review.js → context/prompt-review.js} +10 -10
- package/dist/commands/sys/context/resolve.d.ts +55 -0
- package/dist/commands/sys/context/resolve.js +428 -0
- package/dist/commands/sys/context/sysprompt.d.ts +1 -0
- package/dist/commands/sys/{sysprompt.js → context/sysprompt.js} +11 -11
- package/dist/commands/sys/context.d.ts +2 -0
- package/dist/commands/sys/context.js +20 -0
- package/dist/commands/sys.js +3 -4
- package/dist/core/__tests__/child-death-wake.test.js +38 -22
- package/dist/core/__tests__/cron-self-cancel-delivery.test.js +72 -0
- package/dist/core/__tests__/daemon-boot.test.js +40 -28
- package/dist/core/__tests__/human-deliver.test.js +1 -1
- package/dist/core/__tests__/human-node-not-supervised.test.js +10 -15
- package/dist/core/__tests__/integration/revive.test.js +12 -12
- package/dist/core/__tests__/integration/spawn-root.test.js +6 -0
- package/dist/core/__tests__/integration/worktree-reap.test.js +32 -0
- package/dist/core/__tests__/lifecycle-terminal-reasons.test.d.ts +1 -0
- package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +61 -0
- package/dist/core/__tests__/migration.test.js +38 -1
- package/dist/core/__tests__/push-final-guard.test.js +1 -1
- package/dist/core/__tests__/revive-capacity.test.d.ts +1 -0
- package/dist/core/__tests__/revive-capacity.test.js +127 -0
- package/dist/core/__tests__/revive-parked-fresh.test.js +7 -7
- package/dist/core/__tests__/seam/broker-cap-freeze.test.d.ts +1 -0
- package/dist/core/__tests__/seam/broker-cap-freeze.test.js +81 -0
- package/dist/core/__tests__/seam/dormancy-release.test.js +7 -7
- package/dist/core/__tests__/seam/yield-refresh-transaction.test.js +1 -1
- package/dist/core/canvas/browse/render.js +11 -3
- package/dist/core/canvas/canvas.d.ts +15 -3
- package/dist/core/canvas/canvas.js +52 -16
- package/dist/core/canvas/migrations.js +21 -0
- package/dist/core/canvas/nav-render.js +3 -1
- package/dist/core/canvas/remote-canvas-source.js +2 -0
- package/dist/core/canvas/render-source.d.ts +3 -1
- package/dist/core/canvas/render-source.js +7 -3
- package/dist/core/canvas/status-glyph.d.ts +10 -1
- package/dist/core/canvas/status-glyph.js +19 -3
- package/dist/core/canvas/types.d.ts +20 -1
- package/dist/core/feed/feed.js +1 -1
- package/dist/core/human/feedback-companion.js +5 -2
- package/dist/core/memory/lint.d.ts +19 -0
- package/dist/core/memory/lint.js +97 -0
- package/dist/core/memory-resolver.d.ts +4 -0
- package/dist/core/memory-resolver.js +7 -7
- package/dist/core/preview-registry.js +4 -4
- package/dist/core/review/realize.js +4 -2
- package/dist/core/runtime/close.js +1 -1
- package/dist/core/runtime/fleet.d.ts +29 -1
- package/dist/core/runtime/fleet.js +23 -0
- package/dist/core/runtime/host.js +32 -11
- package/dist/core/runtime/launch-prompt.d.ts +1 -1
- package/dist/core/runtime/launch-prompt.js +2 -2
- package/dist/core/runtime/lifecycle.d.ts +4 -2
- package/dist/core/runtime/lifecycle.js +12 -1
- package/dist/core/runtime/memory.js +1 -1
- package/dist/core/runtime/reopen.js +10 -1
- package/dist/core/runtime/reset.js +1 -1
- package/dist/core/runtime/revive-all.d.ts +3 -0
- package/dist/core/runtime/revive-all.js +10 -4
- package/dist/core/runtime/revive.d.ts +12 -0
- package/dist/core/runtime/revive.js +62 -3
- package/dist/core/runtime/spawn.d.ts +4 -0
- package/dist/core/runtime/spawn.js +36 -7
- package/dist/core/substrate/frontmatter-validation.js +1 -1
- package/dist/core/substrate/index.d.ts +6 -3
- package/dist/core/substrate/index.js +4 -3
- package/dist/core/substrate/on-read.d.ts +0 -8
- package/dist/core/substrate/on-read.js +45 -124
- package/dist/core/substrate/plan.d.ts +94 -0
- package/dist/core/substrate/plan.js +266 -0
- package/dist/core/substrate/render.d.ts +5 -6
- package/dist/core/substrate/render.js +76 -129
- package/dist/core/substrate/schema.d.ts +1 -6
- package/dist/core/substrate/schema.js +1 -15
- package/dist/core/substrate/session-cache.d.ts +2 -10
- package/dist/core/substrate/session-cache.js +9 -47
- package/dist/core/substrate/subject-fields.d.ts +1 -4
- package/dist/core/substrate/subject-fields.js +2 -5
- package/dist/core/substrate/surface-match.d.ts +37 -17
- package/dist/core/substrate/surface-match.js +44 -53
- package/dist/core/tui/page-host.d.ts +6 -0
- package/dist/core/tui/page-host.js +12 -3
- package/dist/core/worktree.d.ts +8 -0
- package/dist/core/worktree.js +65 -0
- package/dist/daemon/__tests__/reconciler-signature.test.js +3 -2
- package/dist/daemon/__tests__/thaw-order.test.d.ts +1 -0
- package/dist/daemon/__tests__/thaw-order.test.js +55 -0
- package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +6 -0
- package/dist/daemon/api/__tests__/node-create-description.test.js +6 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +1 -1
- package/dist/daemon/api/bridge.js +5 -1
- package/dist/daemon/api/handlers/attach.js +5 -1
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/messages.js +10 -2
- package/dist/daemon/api/handlers/nodes.js +8 -1
- package/dist/daemon/api/handlers/worktree.js +10 -1
- package/dist/daemon/api/map.js +7 -0
- package/dist/daemon/companion-retire.js +1 -1
- package/dist/daemon/cron/passes.d.ts +0 -5
- package/dist/daemon/cron/passes.js +0 -20
- package/dist/daemon/cron/sinks.js +1 -1
- package/dist/daemon/cron-run.d.ts +5 -6
- package/dist/daemon/cron-run.js +7 -16
- package/dist/daemon/crtrd.d.ts +0 -10
- package/dist/daemon/crtrd.js +23 -61
- package/dist/daemon/fleet.d.ts +10 -7
- package/dist/daemon/fleet.js +71 -12
- package/dist/daemon/messaging/node-message.js +3 -1
- package/dist/daemon/profile-delete.js +1 -1
- package/dist/daemon/reconcilers/broker-supervision.d.ts +26 -9
- package/dist/daemon/reconcilers/broker-supervision.js +39 -52
- package/dist/daemon/reconcilers/controller-death.d.ts +7 -0
- package/dist/daemon/reconcilers/controller-death.js +26 -0
- package/dist/daemon/reconcilers/cron-lane.d.ts +0 -3
- package/dist/daemon/reconcilers/cron-lane.js +0 -3
- package/dist/daemon/reconcilers/live-obligation.js +3 -1
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +53 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +170 -0
- package/dist/daemon/reconcilers/node-lifecycle/respawn-policy.d.ts +5 -0
- package/dist/daemon/reconcilers/node-lifecycle/respawn-policy.js +16 -0
- package/dist/daemon/reconcilers/node-lifecycle/terminating.d.ts +4 -0
- package/dist/daemon/reconcilers/node-lifecycle/terminating.js +13 -0
- package/dist/daemon/reconcilers/node-lifecycle/tick.d.ts +26 -0
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +140 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/commands/sys/prompt-review.d.ts +0 -1
- package/dist/commands/sys/sysprompt.d.ts +0 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +0 -119
- package/dist/daemon/reconcilers/dormant-inbox.d.ts +0 -18
- package/dist/daemon/reconcilers/dormant-inbox.js +0 -157
- /package/dist/core/__tests__/{cron-broker-capacity.test.d.ts → cron-self-cancel-delivery.test.d.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// launch-prompt.ts — the minimal static system-prompt scaffold, isolated from
|
|
2
|
-
// launch.ts so CLI consumers (`sys prompt-review`) can import the exact string
|
|
2
|
+
// launch.ts so CLI consumers (`sys context prompt-review`) can import the exact string
|
|
3
3
|
// boot uses WITHOUT pulling launch.ts's canvas-db reach (editorLabel et al.).
|
|
4
4
|
// Pure: zero imports.
|
|
5
5
|
/** The minimal static system prompt every node launches with — a thin launcher
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
* before. ALL runtime/spine/lifecycle/kind prompting now lives as gated builtin
|
|
9
9
|
* memory docs (src/builtin-memory/), spliced in fresh every turn — this scaffold
|
|
10
10
|
* must never duplicate that prose. Imported by launch.ts (its own systemPrompt)
|
|
11
|
-
* and `sys prompt-review` so the two can never drift. */
|
|
11
|
+
* and `sys context prompt-review` so the two can never drift. */
|
|
12
12
|
export const LAUNCH_SYSTEM_PROMPT = 'You are a node in the crtr canvas — an agentic runtime.\n\nGuidelines:\n' +
|
|
13
13
|
'- Use the crtr canvas and memory substrate according to injected context.';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeMeta, NodeStatus, ExitIntent } from '../canvas/types.js';
|
|
1
|
+
import type { NodeMeta, NodeStatus, ExitIntent, TerminalReason } from '../canvas/types.js';
|
|
2
2
|
/** The lifecycle events — the only vocabulary for moving a node's status/intent.
|
|
3
3
|
* Each maps (in the table below) to a target status and/or intent plus the set
|
|
4
4
|
* of from-statuses it is legal from. */
|
|
@@ -9,7 +9,9 @@ export type LifecycleEvent = 'finish' | 'park' | 'cancel' | 'crash' | 'yield' |
|
|
|
9
9
|
*
|
|
10
10
|
* Throws on an unknown node or an illegal move. The conditional update makes
|
|
11
11
|
* legality authoritative at SQLite write time rather than at a stale read. */
|
|
12
|
-
export declare function transition(nodeId: string, event: LifecycleEvent
|
|
12
|
+
export declare function transition(nodeId: string, event: LifecycleEvent, opts?: {
|
|
13
|
+
reason?: TerminalReason;
|
|
14
|
+
}): NodeMeta;
|
|
13
15
|
/** Whether this row is a PARKED node — terminalized by the unattended clock
|
|
14
16
|
* rather than by finishing its own work. The one read of the park marker:
|
|
15
17
|
* `done` qualified by `intent='parked'`, both written in the same atomic
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
// inside `core/canvas/`).
|
|
34
34
|
import { getNode, cancelCronsOnWake, transitionStatusIntent } from '../canvas/index.js';
|
|
35
35
|
const ANY = '*';
|
|
36
|
+
const TERMINAL_REASONS = {
|
|
37
|
+
finish: 'finished',
|
|
38
|
+
park: 'parked',
|
|
39
|
+
cancel: 'stranded',
|
|
40
|
+
crash: 'crashed',
|
|
41
|
+
'refresh-launch': null,
|
|
42
|
+
revive: null,
|
|
43
|
+
};
|
|
36
44
|
/** The supervised statuses — a live node the daemon watches. */
|
|
37
45
|
const LIVE = ['active', 'idle'];
|
|
38
46
|
/** The legal transition table — derived directly from the (status, intent) pairs
|
|
@@ -69,15 +77,18 @@ const TRANSITIONS = {
|
|
|
69
77
|
*
|
|
70
78
|
* Throws on an unknown node or an illegal move. The conditional update makes
|
|
71
79
|
* legality authoritative at SQLite write time rather than at a stale read. */
|
|
72
|
-
export function transition(nodeId, event) {
|
|
80
|
+
export function transition(nodeId, event, opts) {
|
|
73
81
|
const spec = TRANSITIONS[event];
|
|
74
82
|
const writeStatus = Object.prototype.hasOwnProperty.call(spec, 'status');
|
|
75
83
|
const writeIntent = Object.prototype.hasOwnProperty.call(spec, 'intent');
|
|
84
|
+
const reason = opts?.reason ?? TERMINAL_REASONS[event];
|
|
76
85
|
const fields = {};
|
|
77
86
|
if (writeStatus)
|
|
78
87
|
fields.status = spec.status;
|
|
79
88
|
if (writeIntent)
|
|
80
89
|
fields.intent = spec.intent ?? null;
|
|
90
|
+
if (reason !== undefined)
|
|
91
|
+
fields.terminal_reason = reason;
|
|
81
92
|
// A committed canonical result is protected from a concurrent close.
|
|
82
93
|
const changes = transitionStatusIntent(nodeId, fields, {
|
|
83
94
|
fromStatuses: spec.from === ANY ? undefined : spec.from,
|
|
@@ -8,7 +8,7 @@ import { join } from 'node:path';
|
|
|
8
8
|
// Import the pure path helper from paths.ts directly, NOT the canvas/index.js
|
|
9
9
|
// barrel: the barrel re-exports `openDb`/canvas.ts accessors, so pulling it here
|
|
10
10
|
// would drag canvas.db into the runtime graph of every pure-client CLI leaf that
|
|
11
|
-
// resolves memory docs (e.g. `memory origin`, `sys prompt-review`). Stage B-9.
|
|
11
|
+
// resolves memory docs (e.g. `memory origin`, `sys context prompt-review`). Stage B-9.
|
|
12
12
|
import { contextDir } from '../canvas/paths.js';
|
|
13
13
|
/** The node-local memory directory in a node's context dir — holds substrate
|
|
14
14
|
* docs (.md files with kind/when/why frontmatter). */
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
// mutating the live pointer — the explicit operation must not retract a
|
|
39
39
|
// canonical final unless it can actually enact the requested re-task.
|
|
40
40
|
import { join } from 'node:path';
|
|
41
|
-
import { clearFinalReportIfMatches, getFinalReport, reportsDir, updateNode } from '../canvas/index.js';
|
|
41
|
+
import { clearFinalReportIfMatches, getFinalReport, getNode, reportsDir, updateNode } from '../canvas/index.js';
|
|
42
42
|
import { InputError } from '../io.js';
|
|
43
|
+
import { transition } from './lifecycle.js';
|
|
43
44
|
function currentFinalReport(nodeId) {
|
|
44
45
|
return getFinalReport(nodeId);
|
|
45
46
|
}
|
|
@@ -106,6 +107,14 @@ export function commitReopen(nodeId, expectedFinalReport) {
|
|
|
106
107
|
export function commitReopenResident(nodeId) {
|
|
107
108
|
const finalReport = currentFinalReport(nodeId);
|
|
108
109
|
updateNode(nodeId, { lifecycle: 'resident' });
|
|
110
|
+
// A reopened node that is still parked in a terminal status has to come back
|
|
111
|
+
// to `active`, or its row keeps claiming it is finished while a broker runs
|
|
112
|
+
// its new mandate. Gated on terminal so a message landing on a live node
|
|
113
|
+
// never rewrites the intent it is working under.
|
|
114
|
+
const meta = getNode(nodeId);
|
|
115
|
+
if (meta !== null && (meta.status === 'done' || meta.status === 'canceled' || meta.status === 'dead')) {
|
|
116
|
+
transition(nodeId, 'revive');
|
|
117
|
+
}
|
|
109
118
|
if (finalReport !== null)
|
|
110
119
|
commitReopen(nodeId, finalReport);
|
|
111
120
|
}
|
|
@@ -50,7 +50,7 @@ export function reapDescendants(rootId) {
|
|
|
50
50
|
// sole .jsonl writer; tearDownNode then proactively closes the on-screen
|
|
51
51
|
// viewer pane + registry row (attach auto-reconnects, so the viewer must be
|
|
52
52
|
// closed here or it lingers ~30s in a misleading "reconnecting…" state).
|
|
53
|
-
transition(id, 'cancel');
|
|
53
|
+
transition(id, 'cancel', { reason: 'closed' });
|
|
54
54
|
headlessBrokerHost.teardown(id);
|
|
55
55
|
tearDownNode(id);
|
|
56
56
|
reaped.push(id);
|
|
@@ -52,6 +52,9 @@ export interface ReviveAllResult {
|
|
|
52
52
|
node_id: string;
|
|
53
53
|
error: string;
|
|
54
54
|
}[];
|
|
55
|
+
/** Node ids the cap turned away. They are not failures: each row is frozen
|
|
56
|
+
* and the daemon's tick relaunches it as slots free. */
|
|
57
|
+
frozen: string[];
|
|
55
58
|
}
|
|
56
59
|
/** RESUME every disconnected node. reviveNode is the ONLY sanctioned launcher
|
|
57
60
|
* and self-guards the double-spawn (a node whose broker pid is already live is a
|
|
@@ -92,13 +92,19 @@ export function listDisconnected() {
|
|
|
92
92
|
* with resume:true. One failing node never aborts the sweep — its error is
|
|
93
93
|
* collected and the rest proceed. */
|
|
94
94
|
export function reviveAll() {
|
|
95
|
-
const result = { revived: [], failed: [] };
|
|
95
|
+
const result = { revived: [], failed: [], frozen: [] };
|
|
96
96
|
for (const meta of listDisconnected()) {
|
|
97
97
|
try {
|
|
98
98
|
// Reconnecting a disconnected broker is recovery, not a wake — a node
|
|
99
|
-
// waiting on an armed deadline keeps it across the sweep.
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// waiting on an armed deadline keeps it across the sweep. A sweep over a
|
|
100
|
+
// whole canvas is the likeliest way to meet the cap, and every node in it
|
|
101
|
+
// is one the daemon wants back: freeze the overflow and let the tick
|
|
102
|
+
// bring them up as slots free.
|
|
103
|
+
const outcome = reviveNode(meta.node_id, { resume: true, recovery: true, capacity: 'freeze' }).outcome;
|
|
104
|
+
if (outcome === 'frozen')
|
|
105
|
+
result.frozen.push(meta.node_id);
|
|
106
|
+
else
|
|
107
|
+
result.revived.push(meta.node_id);
|
|
102
108
|
}
|
|
103
109
|
catch (err) {
|
|
104
110
|
result.failed.push({ node_id: meta.node_id, error: err.message });
|
|
@@ -51,7 +51,18 @@ export interface ReviveResult {
|
|
|
51
51
|
* Present only when a fresh launch actually happened (absent on the
|
|
52
52
|
* double-revive no-op, where nothing was cleared or relaunched). */
|
|
53
53
|
faultExplanation?: () => string | null;
|
|
54
|
+
/** What this call actually did. `already-live` means a broker was already
|
|
55
|
+
* running for the node and nothing was launched; `frozen` means no slot was
|
|
56
|
+
* free and the row is now waiting for one — the tick thaws it. */
|
|
57
|
+
outcome: ReviveOutcome;
|
|
54
58
|
}
|
|
59
|
+
export type ReviveOutcome = 'launched' | 'already-live' | 'frozen';
|
|
60
|
+
/** What a caller wants when the broker cap is full. `freeze` suits a caller
|
|
61
|
+
* whose reason to run survives waiting (durable mail, a cron sink, a recovery
|
|
62
|
+
* respawn): the row is marked frozen and the tick relaunches it when a slot
|
|
63
|
+
* frees. `refuse` suits a caller that must have a live broker NOW (an attach,
|
|
64
|
+
* a bridge, a companion bind) and can only report the refusal. */
|
|
65
|
+
export type ReviveCapacityPolicy = 'freeze' | 'refuse';
|
|
55
66
|
/** Relaunch `nodeId`'s broker engine from its persisted recipe and update canvas
|
|
56
67
|
* meta. Opens no viewer (engine-only).
|
|
57
68
|
*
|
|
@@ -66,6 +77,7 @@ export interface ReviveResult {
|
|
|
66
77
|
*/
|
|
67
78
|
export declare function reviveNode(nodeId: string, opts: {
|
|
68
79
|
resume: boolean;
|
|
80
|
+
capacity: ReviveCapacityPolicy;
|
|
69
81
|
wakeReason?: ReviveWakeReason;
|
|
70
82
|
recovery?: boolean;
|
|
71
83
|
}): ReviveResult;
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
// reviveNode remains the ONLY sanctioned launcher of the node engine.
|
|
25
25
|
import { existsSync } from 'node:fs';
|
|
26
26
|
import { isAbsolute } from 'node:path';
|
|
27
|
-
import { findNodeBySessionFile, getNode, updateNode, clearPid, recordPid, fullName, subscribersOf, } from '../canvas/index.js';
|
|
27
|
+
import { findNodeBySessionFile, getNode, updateNode, clearPid, recordPid, setFrozen, clearFrozen, fullName, subscribersOf, } from '../canvas/index.js';
|
|
28
|
+
import { emitEvent } from '../events/emit.js';
|
|
28
29
|
import { cancelCronsOnWake } from '../canvas/crons.js';
|
|
29
30
|
import { isParked, transition } from './lifecycle.js';
|
|
30
31
|
import { fanDoctrineWake } from './close.js';
|
|
@@ -34,7 +35,7 @@ import { buildReviveKickoff, drainBearings, readGoal, readYieldMessage, RUNTIME_
|
|
|
34
35
|
import { readRoadmap } from './roadmap.js';
|
|
35
36
|
import { hasUnseenWakeMail } from '../feed/inbox.js';
|
|
36
37
|
import { headlessBrokerHost } from './host.js';
|
|
37
|
-
import { boundFleet } from './fleet.js';
|
|
38
|
+
import { boundFleet, brokerCapReached, brokerThresholdsForDaemon } from './fleet.js';
|
|
38
39
|
import { clearFault, beginBootFaultAttempt } from './fault.js';
|
|
39
40
|
import { clearContextExposureState, initializeForkContextExposure, } from '../substrate/injected-store.js';
|
|
40
41
|
import { rootOfSpine } from './nodes.js';
|
|
@@ -126,13 +127,67 @@ export function reviveNode(nodeId, opts) {
|
|
|
126
127
|
// check before the first's registration lands. A node with a live fleet
|
|
127
128
|
// entry was already revived by another path — re-launching would put a
|
|
128
129
|
// SECOND broker on the same session file.
|
|
129
|
-
|
|
130
|
+
const fleet = boundFleet();
|
|
131
|
+
if (fleet.has(nodeId)) {
|
|
132
|
+
// The node is already running, but the ROW may still say terminal: a reopen
|
|
133
|
+
// or a retask lands on a live broker, and only this transition puts the row
|
|
134
|
+
// back where its liveness already is. Both specs accept any prior status,
|
|
135
|
+
// so this is a no-op for a row that is already active.
|
|
136
|
+
transition(nodeId, meta.intent === 'refresh' ? 'refresh-launch' : 'revive');
|
|
130
137
|
return {
|
|
131
138
|
window: null,
|
|
132
139
|
session: meta.tmux_session ?? null,
|
|
133
140
|
resumed: false,
|
|
141
|
+
outcome: 'already-live',
|
|
134
142
|
};
|
|
135
143
|
}
|
|
144
|
+
// The cap, consulted BEFORE any mutation below: a node that cannot get a
|
|
145
|
+
// broker must not have its cycle counter bumped, its session identity
|
|
146
|
+
// cleared, or its bearings drained on the way to being turned away.
|
|
147
|
+
if (!fleet.reserveSlot(nodeId)) {
|
|
148
|
+
if (opts.capacity === 'refuse') {
|
|
149
|
+
throw brokerCapReached(brokerThresholdsForDaemon().automaticReviveCap, fleet.size());
|
|
150
|
+
}
|
|
151
|
+
// Freezing is durable and once-per-episode: the mark stays until a launch
|
|
152
|
+
// clears it, so a node frozen across many saturated ticks announces itself
|
|
153
|
+
// exactly once and keeps its original wait timestamp (the thaw order reads
|
|
154
|
+
// it).
|
|
155
|
+
if (meta.frozen_at == null) {
|
|
156
|
+
setFrozen(nodeId, new Date().toISOString());
|
|
157
|
+
// The row holds no engine and is not getting one this pass, so a pid left
|
|
158
|
+
// over from the instance that just died would read as stale-active on
|
|
159
|
+
// every status surface. Clearing it is what makes `isFrozenRow` true and
|
|
160
|
+
// what tells the tick this row has never launched OR has already been
|
|
161
|
+
// written off, so the freeze lane owes it a launch rather than a verdict.
|
|
162
|
+
clearPid(nodeId);
|
|
163
|
+
emitEvent({
|
|
164
|
+
level: 'info',
|
|
165
|
+
event: 'broker.frozen',
|
|
166
|
+
node_id: nodeId,
|
|
167
|
+
fields: { live_count: fleet.size(), hard_cap: brokerThresholdsForDaemon().automaticReviveCap },
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
window: null,
|
|
172
|
+
session: meta.tmux_session ?? null,
|
|
173
|
+
resumed: false,
|
|
174
|
+
outcome: 'frozen',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
return launchRevive(nodeId, meta, opts);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
// The slot is either consumed by the fleet registration inside `launch`
|
|
182
|
+
// (making this a no-op) or given back here — the one release path covering
|
|
183
|
+
// every preflight refusal and launch failure between the claim and the
|
|
184
|
+
// registration.
|
|
185
|
+
fleet.releaseReservation(nodeId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** The launch itself, from the cwd preflight through `recordPid`. Split out so
|
|
189
|
+
* the capacity claim above has exactly one release path around it. */
|
|
190
|
+
function launchRevive(nodeId, meta, opts) {
|
|
136
191
|
// Preflight, before any state mutation below: a recorded cwd that no longer
|
|
137
192
|
// exists (e.g. a closed managed worktree whose tombstone dir was later
|
|
138
193
|
// reaped) can never host a launch. Fail loud with a typed, user-visible
|
|
@@ -345,11 +400,15 @@ export function reviveNode(nodeId, opts) {
|
|
|
345
400
|
// and status surfaces (canvas views, the daemon's identity rechecks at
|
|
346
401
|
// signal boundaries) still read it.
|
|
347
402
|
recordPid(nodeId, launched.pid);
|
|
403
|
+
// A launch is the definition of not-frozen, and every launcher passes
|
|
404
|
+
// through here — so this is the one place the wait mark is retired.
|
|
405
|
+
clearFrozen(nodeId);
|
|
348
406
|
return {
|
|
349
407
|
window: null,
|
|
350
408
|
session: meta.tmux_session ?? null,
|
|
351
409
|
resumed: resuming,
|
|
352
410
|
launch: launched,
|
|
353
411
|
faultExplanation: bootFault.explanation,
|
|
412
|
+
outcome: 'launched',
|
|
354
413
|
};
|
|
355
414
|
}
|
|
@@ -95,6 +95,10 @@ export declare function assertLaunchModelRegistered(request: ModelRequest | unde
|
|
|
95
95
|
}, registry?: ModelRegistryLike): Promise<void>;
|
|
96
96
|
export interface SpawnChildResult {
|
|
97
97
|
node: NodeMeta;
|
|
98
|
+
/** The birth found the broker cap full: the row exists and is marked frozen,
|
|
99
|
+
* but no engine was launched. The node-lifecycle tick starts it when a slot
|
|
100
|
+
* frees. */
|
|
101
|
+
frozen: boolean;
|
|
98
102
|
/** The viewer window opened for a --root in tmux; null otherwise (a managed
|
|
99
103
|
* child opens no viewer, and a --root spawned outside tmux opens none). */
|
|
100
104
|
window: string | null;
|
|
@@ -21,7 +21,9 @@ import { writeGoal } from './kickoff.js';
|
|
|
21
21
|
import { appendSituationalContext, formatSituationalProse } from './situational-context.js';
|
|
22
22
|
import { hasRoadmap, seedRoadmap } from './roadmap.js';
|
|
23
23
|
import { buildWakeBearings } from './bearings.js';
|
|
24
|
-
import { canonicalSessionFile, contextDir, findNodeBySessionFile, getNode, fullName, recordPid } from '../canvas/index.js';
|
|
24
|
+
import { canonicalSessionFile, contextDir, findNodeBySessionFile, getNode, fullName, recordPid, setFrozen } from '../canvas/index.js';
|
|
25
|
+
import { boundFleet, brokerThresholdsForDaemon } from './fleet.js';
|
|
26
|
+
import { emitEvent } from '../events/emit.js';
|
|
25
27
|
import { jobDir } from '../canvas/paths.js';
|
|
26
28
|
import { openViewerWindow, focusOf, windowOfPane, } from './placement.js';
|
|
27
29
|
import { waitForBrokerViewSocket } from './placement-tmux.js';
|
|
@@ -335,16 +337,43 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
|
|
|
335
337
|
// Snapshot the boot-fault attempt the instant before launch — see
|
|
336
338
|
// beginBootFaultAttempt — so a later diagnostic read (root+await only, below)
|
|
337
339
|
// can only ever surface a fault THIS launch recorded, never a stale one.
|
|
340
|
+
// Claim the slot BEFORE the launch: `launchBrokerOrCrash` turns any launch
|
|
341
|
+
// failure — including the ledger's refusal — into a crashed row, and a birth
|
|
342
|
+
// that merely has to wait for capacity must not be crashed. Consulting the
|
|
343
|
+
// ledger here keeps the freeze decision ahead of that conversion. The
|
|
344
|
+
// reservation is consumed by the fleet's own `register` on a successful
|
|
345
|
+
// launch, so the release below is a no-op then and a give-back on any path
|
|
346
|
+
// that never registered a handle.
|
|
347
|
+
const fleet = boundFleet();
|
|
348
|
+
if (!fleet.reserveSlot(meta.node_id)) {
|
|
349
|
+
// Freezing is durable and once-per-episode; a birth is by definition the
|
|
350
|
+
// first, so the mark and its announcement always land here.
|
|
351
|
+
setFrozen(meta.node_id, new Date().toISOString());
|
|
352
|
+
emitEvent({
|
|
353
|
+
level: 'info',
|
|
354
|
+
event: 'broker.frozen',
|
|
355
|
+
node_id: meta.node_id,
|
|
356
|
+
fields: { live_count: fleet.size(), hard_cap: brokerThresholdsForDaemon().automaticReviveCap },
|
|
357
|
+
});
|
|
358
|
+
// No engine, so no readiness to wait for and no viewer to place.
|
|
359
|
+
return { node: getNode(meta.node_id), frozen: true, window: null, session: null };
|
|
360
|
+
}
|
|
338
361
|
const bootFault = beginBootFaultAttempt(meta.node_id);
|
|
339
362
|
// Broker is the only host: launch the detached broker ENGINE. A managed child
|
|
340
363
|
// gets NO viewer — spawning it returns once the engine is launched and a human
|
|
341
364
|
// opens a viewer on demand via `crtr surface node focus` / surface attach. A --root gets
|
|
342
365
|
// a foreground viewer below.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
366
|
+
let placed;
|
|
367
|
+
try {
|
|
368
|
+
placed = launchBrokerOrCrash(meta.node_id, inv, {
|
|
369
|
+
cwd: meta.cwd,
|
|
370
|
+
name: fullName(meta),
|
|
371
|
+
resuming: false,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
finally {
|
|
375
|
+
fleet.releaseReservation(meta.node_id);
|
|
376
|
+
}
|
|
348
377
|
// Definitive failure: no broker pid ⇒ the node has no engine. Crash it (so the
|
|
349
378
|
// daemon doesn't watch a zombie 'active') and throw so `crtr node new` exits
|
|
350
379
|
// non-zero. transition('crash') is a legal from-LIVE move (still 'active' from
|
|
@@ -412,7 +441,7 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
|
|
|
412
441
|
catch { /* best-effort — a headless spawn must never fail on viewer placement */ }
|
|
413
442
|
}
|
|
414
443
|
const saved = getNode(meta.node_id);
|
|
415
|
-
return { node: saved, window, session };
|
|
444
|
+
return { node: saved, frozen: false, window, session };
|
|
416
445
|
}
|
|
417
446
|
catch (err) {
|
|
418
447
|
// Cleanup scope: a managed worktree is only removed on failure while NO
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDocKind, parseSubstrateFrontmatter, SURFACE_EVENTS, SURFACE_RUNGS } from './schema.js';
|
|
2
2
|
const RETIRED_FIELDS = ['system-prompt-visibility', 'file-read-visibility', 'applies-to', 'read-when'];
|
|
3
3
|
const SURFACE_ENTRY_KEYS = ['on', 'match', 'match-frontmatter', 'gate', 'at'];
|
|
4
|
-
export const SUPPRESSIBLE_RULES = ['length', 'broad-memory-read'];
|
|
4
|
+
export const SUPPRESSIBLE_RULES = ['length', 'broad-memory-read', 'orphan'];
|
|
5
5
|
/** Strict validation of one raw routing entry. Runtime parsing deliberately
|
|
6
6
|
* tolerates malformed docs; this authoring/preflight contract does not. */
|
|
7
7
|
export function lintSubstrateSurfaces(value) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, SURFACE_EVENTS, SURFACE_RUNGS,
|
|
1
|
+
export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, SURFACE_EVENTS, SURFACE_RUNGS, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, normalizeNameSegment, normalizeDocName, resolveDocName, } from './schema.js';
|
|
2
2
|
export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc, SurfaceEntry, SurfaceEvent, SurfaceRung } from './schema.js';
|
|
3
3
|
export { lintSubstrateFrontmatter, lintSubstrateSchema, lintSubstrateSurfaces, parsedSubstrateSurfaces } from './frontmatter-validation.js';
|
|
4
|
-
export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, owningRootOf, underOwningRoot } from './surface-match.js';
|
|
4
|
+
export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, matchingSurfaceEntry, surfaceEntryParticipates, owningRootOf, underOwningRoot } from './surface-match.js';
|
|
5
|
+
export type { SurfaceMatchPayload } from './surface-match.js';
|
|
6
|
+
export { planDelivery, bootPartitionOf } from './plan.js';
|
|
7
|
+
export type { DeliveryPlan, DeliveryRecord, DeliveryPayload, EntryDecision, GateOutcome } from './plan.js';
|
|
5
8
|
export { dirDedupKey, docsByName, isDirName, ancestorDirsOf, renderDirListing } from './listings.js';
|
|
6
9
|
export { clearContextExposureState, cloneContextExposureState, cloneTranscriptExposureState, contentExposureIdentity, contextExposureStatesEqual, documentExposedAtOrAbove, emptyContextExposureState, exposedAtOrAbove, exposureTarget, freezePreferenceSnapshot, hasTranscriptExposure, initializeForkContextExposure, loadContextExposureState, mergeContextExposureState, registerDocumentExposure, registerExposure, replaceContextExposureState, saveContextExposureState, sharedContextExposureState, } from './injected-store.js';
|
|
7
10
|
export type { ContextExposureState, ExposureSource, ExposureTarget } from './injected-store.js';
|
|
@@ -11,5 +14,5 @@ export { spineDepth, assembleNodeSubject } from './subject.js';
|
|
|
11
14
|
export { gatePasses } from './gate.js';
|
|
12
15
|
export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
|
|
13
16
|
export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
|
|
14
|
-
export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject,
|
|
17
|
+
export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject, memoryReadDocBlocks } from './on-read.js';
|
|
15
18
|
export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read-node.js';
|
|
@@ -9,7 +9,7 @@ KINDS, isDocKind,
|
|
|
9
9
|
// ladder
|
|
10
10
|
RUNGS, rungRank, rungAtLeast,
|
|
11
11
|
// surfaces (event routing)
|
|
12
|
-
SURFACE_EVENTS, SURFACE_RUNGS,
|
|
12
|
+
SURFACE_EVENTS, SURFACE_RUNGS,
|
|
13
13
|
// parse + render-shared helpers
|
|
14
14
|
parseSubstrateFrontmatter, parseSubstrateDoc, previewLine,
|
|
15
15
|
// display-name normalization
|
|
@@ -17,7 +17,8 @@ normalizeNameSegment, normalizeDocName,
|
|
|
17
17
|
// substrate identity (explicit-name-then-path-fallback)
|
|
18
18
|
resolveDocName, } from './schema.js';
|
|
19
19
|
export { lintSubstrateFrontmatter, lintSubstrateSchema, lintSubstrateSurfaces, parsedSubstrateSurfaces } from './frontmatter-validation.js';
|
|
20
|
-
export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, owningRootOf, underOwningRoot } from './surface-match.js';
|
|
20
|
+
export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, matchingSurfaceEntry, surfaceEntryParticipates, owningRootOf, underOwningRoot } from './surface-match.js';
|
|
21
|
+
export { planDelivery, bootPartitionOf } from './plan.js';
|
|
21
22
|
export { dirDedupKey, docsByName, isDirName, ancestorDirsOf, renderDirListing } from './listings.js';
|
|
22
23
|
export { clearContextExposureState, cloneContextExposureState, cloneTranscriptExposureState, contentExposureIdentity, contextExposureStatesEqual, documentExposedAtOrAbove, emptyContextExposureState, exposedAtOrAbove, exposureTarget, freezePreferenceSnapshot, hasTranscriptExposure, initializeForkContextExposure, loadContextExposureState, mergeContextExposureState, registerDocumentExposure, registerExposure, replaceContextExposureState, saveContextExposureState, sharedContextExposureState, } from './injected-store.js';
|
|
23
24
|
export { scopeForCwd, profileNameFor } from './subject-fields.js';
|
|
@@ -25,5 +26,5 @@ export { spineDepth, assembleNodeSubject } from './subject.js';
|
|
|
25
26
|
export { gatePasses } from './gate.js';
|
|
26
27
|
export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
|
|
27
28
|
export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
|
|
28
|
-
export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject,
|
|
29
|
+
export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject, memoryReadDocBlocks } from './on-read.js';
|
|
29
30
|
export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read-node.js';
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { type ContextExposureState, type ExposureTarget } from './injected-store.js';
|
|
2
|
-
import { type Rung, type SubstrateDoc } from './schema.js';
|
|
3
2
|
import type { NodeConfigSubject } from './subject-fields.js';
|
|
4
|
-
interface EventCandidate {
|
|
5
|
-
doc: SubstrateDoc;
|
|
6
|
-
rung: Rung;
|
|
7
|
-
}
|
|
8
|
-
/** Render the selected candidates not already loaded at their matched rung. */
|
|
9
|
-
export declare function renderCandidateBlocks(subject: NodeConfigSubject | null, candidates: EventCandidate[], target: ExposureTarget): string[];
|
|
10
3
|
/** Surface docs whose `read` entries match a successfully read file, using
|
|
11
4
|
* the caller-supplied daemon snapshot subject. */
|
|
12
5
|
export declare function renderOnReadDocsForSubject(subject: NodeConfigSubject, readFilePath: string, target?: ExposureTarget): string;
|
|
@@ -46,4 +39,3 @@ export declare function corpusHasPreCommandSurfaces(): boolean;
|
|
|
46
39
|
* held once and then silently absent for the rest of the session. Lives here
|
|
47
40
|
* rather than in the extension because the corpus accessor is private. */
|
|
48
41
|
export declare function demotePreCommandExposures(state: ContextExposureState): void;
|
|
49
|
-
export {};
|