@north-light/crouter 0.3.235 → 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 +14 -5
- 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 +399 -399
- package/dist/clients/inbox/review/__tests__/editor-roundtrip.test.js +6 -0
- package/dist/clients/inbox/review/roundtrip.js +1 -1
- package/dist/commands/__tests__/node-new.test.js +58 -0
- 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 +4 -1
- package/dist/commands/node/lifecycle.js +2 -2
- 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__/canvas-inbox-watcher-naming.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +258 -0
- package/dist/core/__tests__/child-death-wake.test.js +38 -22
- package/dist/core/__tests__/cron-self-cancel-delivery.test.d.ts +1 -0
- 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__/helpers/harness.js +1 -0
- 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 +76 -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 +22 -3
- 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/launch-target.js +1 -1
- 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 +9 -13
- package/dist/core/runtime/spawn.js +40 -17
- 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 -4
- 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/dist/pi-extensions/canvas-inbox-watcher.js +12 -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 → commands/__tests__/node-new.test.d.ts} +0 -0
|
@@ -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
|
}
|
|
@@ -15,19 +15,11 @@ export interface SpawnChildOpts {
|
|
|
15
15
|
* subscription back to the spawner, spawned_by=spawner. Resident and brought
|
|
16
16
|
* forefront by default — see `rootLifecycle` for the unattended variant. */
|
|
17
17
|
root?: boolean;
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `'terminal'` is the UNATTENDED root: still parentless (nobody subscribes,
|
|
25
|
-
* so nothing receives its push) but it still owes a final, so it reaps when
|
|
26
|
-
* its work is done — and it is NEVER brought forefront. This is what
|
|
27
|
-
* machinery firing with no human present must birth: the cron executor
|
|
28
|
-
* spawns one when its cron's creating node is gone. A resident root there
|
|
29
|
-
* would accrete one permanently-live node per fire (the count-based prune
|
|
30
|
-
* never deletes live nodes) and steal the human's tmux focus every time. */
|
|
18
|
+
/** Lifecycle for the new node. Roots default to resident and managed
|
|
19
|
+
* children default to terminal. A resident child remains an interactable,
|
|
20
|
+
* wakeable conversation without owing a final; a terminal child remains a
|
|
21
|
+
* bounded worker that must finalize. For roots, terminal keeps the
|
|
22
|
+
* unattended no-viewer behavior. */
|
|
31
23
|
rootLifecycle?: Lifecycle;
|
|
32
24
|
/** Direct root creation waits until the broker accepts viewers before it
|
|
33
25
|
* returns. Cron-launched births use `launch`: the daemon must never wait in
|
|
@@ -103,6 +95,10 @@ export declare function assertLaunchModelRegistered(request: ModelRequest | unde
|
|
|
103
95
|
}, registry?: ModelRegistryLike): Promise<void>;
|
|
104
96
|
export interface SpawnChildResult {
|
|
105
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;
|
|
106
102
|
/** The viewer window opened for a --root in tmux; null otherwise (a managed
|
|
107
103
|
* child opens no viewer, and a --root spawned outside tmux opens none). */
|
|
108
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';
|
|
@@ -209,16 +211,10 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
|
|
|
209
211
|
const root = opts.root === true;
|
|
210
212
|
const spawner = resolveSpawner(opts.parent, ctx.nodeId ?? null, root);
|
|
211
213
|
const mode = opts.mode ?? 'base';
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
// (delegates + holds a roadmap, but still reports up), NOT resident.
|
|
217
|
-
//
|
|
218
|
-
// `rootLifecycle: 'terminal'` is the one deliberate exception: a parentless
|
|
219
|
-
// node that STILL owes a final (see the option's docblock). It is unattended
|
|
220
|
-
// by construction, so `attendedRoot` below also suppresses viewer placement.
|
|
221
|
-
const lifecycle = root ? (opts.rootLifecycle ?? 'resident') : 'terminal';
|
|
214
|
+
// Roots are resident by default; managed children are terminal workers by
|
|
215
|
+
// default. The one lifecycle option deliberately applies to both shapes so a
|
|
216
|
+
// child can be born as a standing, wakeable conversation too.
|
|
217
|
+
const lifecycle = opts.rootLifecycle ?? (root ? 'resident' : 'terminal');
|
|
222
218
|
const attendedRoot = root && lifecycle === 'resident';
|
|
223
219
|
// Born WITHOUT a name — the canvas-goal-capture extension names it async from
|
|
224
220
|
// its first message (the kickoff task) inside its own pi process, so spawn
|
|
@@ -341,16 +337,43 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
|
|
|
341
337
|
// Snapshot the boot-fault attempt the instant before launch — see
|
|
342
338
|
// beginBootFaultAttempt — so a later diagnostic read (root+await only, below)
|
|
343
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
|
+
}
|
|
344
361
|
const bootFault = beginBootFaultAttempt(meta.node_id);
|
|
345
362
|
// Broker is the only host: launch the detached broker ENGINE. A managed child
|
|
346
363
|
// gets NO viewer — spawning it returns once the engine is launched and a human
|
|
347
364
|
// opens a viewer on demand via `crtr surface node focus` / surface attach. A --root gets
|
|
348
365
|
// a foreground viewer below.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
}
|
|
354
377
|
// Definitive failure: no broker pid ⇒ the node has no engine. Crash it (so the
|
|
355
378
|
// daemon doesn't watch a zombie 'active') and throw so `crtr node new` exits
|
|
356
379
|
// non-zero. transition('crash') is a legal from-LIVE move (still 'active' from
|
|
@@ -418,7 +441,7 @@ export async function spawnChildPrepared(opts, beforeBrokerLaunch) {
|
|
|
418
441
|
catch { /* best-effort — a headless spawn must never fail on viewer placement */ }
|
|
419
442
|
}
|
|
420
443
|
const saved = getNode(meta.node_id);
|
|
421
|
-
return { node: saved, window, session };
|
|
444
|
+
return { node: saved, frozen: false, window, session };
|
|
422
445
|
}
|
|
423
446
|
catch (err) {
|
|
424
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 {};
|
|
@@ -33,20 +33,20 @@
|
|
|
33
33
|
// renders at the highest rung matched by that event.
|
|
34
34
|
//
|
|
35
35
|
// Equal-canonical candidates (the same identity in two physical stores) do not
|
|
36
|
-
// each deliver:
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
36
|
+
// each deliver: the plan collapses them to the first one ELIGIBLE for that
|
|
37
|
+
// event, in corpus order.
|
|
38
|
+
//
|
|
39
|
+
// Every selection decision — discovery, gates, the profile cap, the rung fold,
|
|
40
|
+
// the first-wins collapse — belongs to planDelivery (plan.ts). This module is
|
|
41
|
+
// the plan→text adapter: it owns the `<memory>` envelope, the
|
|
42
|
+
// `<auto-loaded-context>` wrapper, and the exposure ledger.
|
|
43
|
+
import { sep } from 'node:path';
|
|
44
|
+
import { ambientMemoryTarget, listAllMemoryDocs } from '../memory-resolver.js';
|
|
45
|
+
import { owningRootOf } from './surface-match.js';
|
|
46
46
|
import { demoteDocumentTranscriptExposure, documentExposedAtOrAbove, emptyContextExposureState, exposureTarget, registerDocumentExposure, } from './injected-store.js';
|
|
47
|
-
import {
|
|
47
|
+
import { planDelivery } from './plan.js';
|
|
48
|
+
import { parseSubstrateDoc, previewLine } from './schema.js';
|
|
48
49
|
import { cachedEventCorpusInclusive } from './session-cache.js';
|
|
49
|
-
const JUNK_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.cache', '.yalc']);
|
|
50
50
|
function attr(s) {
|
|
51
51
|
return s
|
|
52
52
|
.replace(/&/g, '&')
|
|
@@ -54,48 +54,11 @@ function attr(s) {
|
|
|
54
54
|
.replace(/</g, '<')
|
|
55
55
|
.replace(/>/g, '>');
|
|
56
56
|
}
|
|
57
|
-
function isJunkAncestor(dir) {
|
|
58
|
-
return dir.split(sep).some((segment) => JUNK_DIRS.has(segment));
|
|
59
|
-
}
|
|
60
|
-
/** Project docs from every workspace store enclosing the read file, nearest
|
|
61
|
-
* first. This is a discovery walk only: each returned doc must still match an
|
|
62
|
-
* explicit trigger. A store that declares no namespace does not mount here
|
|
63
|
-
* either — an unmounted store has no canonical identities to deliver under.
|
|
64
|
-
* The stores carry the neutral ceiling: the read event is uncapped by design,
|
|
65
|
-
* reached only by an explicit file read. */
|
|
66
|
-
function enclosingProjectDocs(absReadFile) {
|
|
67
|
-
const out = [];
|
|
68
|
-
const seen = new Set();
|
|
69
|
-
const home = realpathOrSelf(homedir());
|
|
70
|
-
const userRoot = realpathOrSelf(userScopeRoot());
|
|
71
|
-
const fsRoot = parse(absReadFile).root;
|
|
72
|
-
let dir = dirname(absReadFile);
|
|
73
|
-
while (true) {
|
|
74
|
-
if (dir !== home && dir !== userRoot && !isJunkAncestor(dir)) {
|
|
75
|
-
const store = openProjectMemoryStore(dir);
|
|
76
|
-
if (store.mountStatus === 'ready' && !seen.has(store.storeRoot)) {
|
|
77
|
-
seen.add(store.storeRoot);
|
|
78
|
-
for (const source of loadStoreMemoryDocs(store, true)) {
|
|
79
|
-
const doc = parseSubstrateDoc(source);
|
|
80
|
-
if (doc !== null)
|
|
81
|
-
out.push({ doc, source });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (dir === home || dir === fsRoot)
|
|
86
|
-
break;
|
|
87
|
-
const parent = dirname(dir);
|
|
88
|
-
if (parent === dir)
|
|
89
|
-
break;
|
|
90
|
-
dir = parent;
|
|
91
|
-
}
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
57
|
// The event corpus INCLUDES node-scope docs: surfaces entries are explicit
|
|
95
58
|
// authored routing, so a node-local doc's read/memory-read/command entry
|
|
96
|
-
// fires like any other store's. Only boot excludes node scope (render.ts
|
|
97
|
-
//
|
|
98
|
-
//
|
|
59
|
+
// fires like any other store's. Only boot excludes node scope (render.ts owns
|
|
60
|
+
// node-local boot rendering); the active exposure state prevents a lower-rung
|
|
61
|
+
// repeat here.
|
|
99
62
|
function resolvedDocs() {
|
|
100
63
|
try {
|
|
101
64
|
return cachedEventCorpusInclusive(listAllMemoryDocs, parseSubstrateDoc);
|
|
@@ -104,16 +67,6 @@ function resolvedDocs() {
|
|
|
104
67
|
return [];
|
|
105
68
|
}
|
|
106
69
|
}
|
|
107
|
-
function readFileFrontmatter(absReadFile) {
|
|
108
|
-
if (!/\.(md|mdx|markdown)$/i.test(absReadFile))
|
|
109
|
-
return {};
|
|
110
|
-
try {
|
|
111
|
-
return parseFrontmatterGeneric(readText(absReadFile)).data ?? {};
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return {};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
70
|
// Two shapes: element-with-body means the content is here; self-closing means
|
|
118
71
|
// it is not — a preview carries its routing line as `readWhen` metadata so the
|
|
119
72
|
// one-liner can never be mistaken for the document's content. No disk path:
|
|
@@ -135,37 +88,14 @@ function renderDocEnvelope(doc, rung) {
|
|
|
135
88
|
}
|
|
136
89
|
return `<memory ${attrs} />`;
|
|
137
90
|
}
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
* already gave a delivering rung. A nearer candidate that is gated off or does
|
|
142
|
-
* not deliver never suppresses a farther one — the filter runs before the
|
|
143
|
-
* first-wins collapse, not after it.
|
|
144
|
-
*
|
|
145
|
-
* A `null` subject skips gated documents and still delivers ungated ones. */
|
|
146
|
-
function selectEventCandidates(subject, candidates) {
|
|
147
|
-
const selected = [];
|
|
148
|
-
const seen = new Set();
|
|
149
|
-
for (const candidate of candidates) {
|
|
150
|
-
const { doc, rung } = candidate;
|
|
151
|
-
if (rung === 'none' || seen.has(doc.name))
|
|
152
|
-
continue;
|
|
153
|
-
try {
|
|
154
|
-
if (subject === null ? doc.gate !== undefined : !gatePasses(doc, subject))
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
seen.add(doc.name);
|
|
161
|
-
selected.push(candidate);
|
|
162
|
-
}
|
|
163
|
-
return selected;
|
|
91
|
+
/** One winner per doc, in plan (precedence) order. */
|
|
92
|
+
function planWinners(plan) {
|
|
93
|
+
return plan.docs.filter((r) => r.winner).map((r) => ({ doc: r.doc, rung: r.finalRung }));
|
|
164
94
|
}
|
|
165
|
-
/** Render the
|
|
166
|
-
|
|
95
|
+
/** Render the plan's winners that are not already loaded at their matched rung. */
|
|
96
|
+
function renderWinnerBlocks(winners, target) {
|
|
167
97
|
const rendered = [];
|
|
168
|
-
for (const { doc, rung } of
|
|
98
|
+
for (const { doc, rung } of winners) {
|
|
169
99
|
if (documentExposedAtOrAbove(target.state, doc.path, doc.body, rung))
|
|
170
100
|
continue;
|
|
171
101
|
const block = renderDocEnvelope(doc, rung);
|
|
@@ -179,23 +109,19 @@ export function renderCandidateBlocks(subject, candidates, target) {
|
|
|
179
109
|
function transientTranscriptTarget() {
|
|
180
110
|
return exposureTarget(emptyContextExposureState(), 'transcript');
|
|
181
111
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
112
|
+
function wrapAutoLoaded(blocks) {
|
|
113
|
+
return blocks.length === 0 ? '' : `<auto-loaded-context>\n${blocks.join('\n')}\n</auto-loaded-context>`;
|
|
114
|
+
}
|
|
115
|
+
/** The corpus-carrying plan for one event. Every renderer here passes the
|
|
116
|
+
* session-cached corpus so a hook never re-walks the filesystem. */
|
|
117
|
+
function eventPlan(subject, event, payload) {
|
|
118
|
+
return planDelivery(subject, ambientMemoryTarget(), event, payload, resolvedDocs());
|
|
187
119
|
}
|
|
188
120
|
/** Surface docs whose `read` entries match a successfully read file, using
|
|
189
121
|
* the caller-supplied daemon snapshot subject. */
|
|
190
122
|
export function renderOnReadDocsForSubject(subject, readFilePath, target = transientTranscriptTarget()) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
// Enclosing stores lead: a store discovered from the read file is the nearest
|
|
194
|
-
// source of an identity the mounted corpus may also carry.
|
|
195
|
-
const candidates = [...enclosingProjectDocs(absReadFile), ...resolvedDocs()]
|
|
196
|
-
.filter(({ doc }) => realpathOrSelf(doc.path) !== absReadFile)
|
|
197
|
-
.map(({ doc }) => ({ doc, rung: readDeliveryRung(doc, subject, absReadFile, readFrontmatter) }));
|
|
198
|
-
return renderCandidates(subject, candidates, target);
|
|
123
|
+
const plan = eventPlan(subject, 'read', { absoluteFile: readFilePath });
|
|
124
|
+
return wrapAutoLoaded(renderWinnerBlocks(planWinners(plan), target));
|
|
199
125
|
}
|
|
200
126
|
/** Workspace boot docs using the snapshot's cwd/profile instead of a canvas
|
|
201
127
|
* lookup. The shared exposure target prevents a later read from repeating a
|
|
@@ -206,25 +132,24 @@ export function renderOnReadDocsForSubject(subject, readFilePath, target = trans
|
|
|
206
132
|
* registration. A later explicit `crtr memory read` can still raise a document
|
|
207
133
|
* that workspace-open disclosed at a lower rung. */
|
|
208
134
|
export function renderWorkspaceOpenDocsForSubject(subject, cwd, profileId, target = transientTranscriptTarget()) {
|
|
209
|
-
|
|
135
|
+
const workspaceTarget = { cwd, profileId, nodeId: null };
|
|
136
|
+
let winners;
|
|
210
137
|
try {
|
|
211
|
-
|
|
212
|
-
.map(parseSubstrateDoc)
|
|
213
|
-
.filter((doc) => doc !== null);
|
|
138
|
+
winners = planWinners(planDelivery(subject, workspaceTarget, 'workspace-open'));
|
|
214
139
|
}
|
|
215
140
|
catch {
|
|
216
141
|
return '';
|
|
217
142
|
}
|
|
218
|
-
// Selection
|
|
219
|
-
// equal-canonical tie;
|
|
220
|
-
//
|
|
221
|
-
|
|
143
|
+
// Selection ran on the corpus's PRECEDENCE order, so the nearest store won an
|
|
144
|
+
// equal-canonical tie; this sort is display order only — outermost workspace
|
|
145
|
+
// first, so context arrives general before specific.
|
|
146
|
+
winners.sort((a, b) => {
|
|
222
147
|
const aRoot = owningRootOf(a.doc) ?? '';
|
|
223
148
|
const bRoot = owningRootOf(b.doc) ?? '';
|
|
224
149
|
const depth = aRoot.split(sep).filter(Boolean).length - bRoot.split(sep).filter(Boolean).length;
|
|
225
150
|
return depth || aRoot.localeCompare(bRoot) || a.doc.path.localeCompare(b.doc.path);
|
|
226
151
|
});
|
|
227
|
-
return
|
|
152
|
+
return wrapAutoLoaded(renderWinnerBlocks(winners, target));
|
|
228
153
|
}
|
|
229
154
|
/** Inner `<memory>` blocks for the memory-read event: docs whose
|
|
230
155
|
* `memory-read` entries match the doc a `crtr memory read` just resolved
|
|
@@ -233,20 +158,17 @@ export function renderWorkspaceOpenDocsForSubject(subject, cwd, profileId, targe
|
|
|
233
158
|
* unwrapped blocks — the read leaf composes one `<auto-loaded-context>`
|
|
234
159
|
* envelope from these plus the directory listings. */
|
|
235
160
|
export function memoryReadDocBlocks(subject, excludeRealpath, name, target) {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}));
|
|
242
|
-
return renderCandidateBlocks(subject, candidates, target);
|
|
161
|
+
const plan = eventPlan(subject, 'memory-read', {
|
|
162
|
+
resolvedCanonicalName: name,
|
|
163
|
+
resolvedDocPath: excludeRealpath,
|
|
164
|
+
});
|
|
165
|
+
return renderWinnerBlocks(planWinners(plan), target);
|
|
243
166
|
}
|
|
244
167
|
/** Surface docs whose `command` entries match an executed bash command.
|
|
245
168
|
* The corpus is the resolved cwd/profile set; a command has no file from which
|
|
246
169
|
* to discover enclosing project stores. */
|
|
247
170
|
export function renderOnCommandDocsForSubject(subject, command, target = transientTranscriptTarget()) {
|
|
248
|
-
|
|
249
|
-
return renderCandidates(subject, candidates, target);
|
|
171
|
+
return wrapAutoLoaded(renderWinnerBlocks(planWinners(eventPlan(subject, 'command', { command })), target));
|
|
250
172
|
}
|
|
251
173
|
/** Surface docs whose `pre-command` entries match a bash command that has NOT
|
|
252
174
|
* run yet. The exact twin of the post-execution renderer above, and its empty
|
|
@@ -254,8 +176,7 @@ export function renderOnCommandDocsForSubject(subject, command, target = transie
|
|
|
254
176
|
* its matching rung or above, which is the release — the caller lets the
|
|
255
177
|
* command through. */
|
|
256
178
|
export function renderPreCommandDocsForSubject(subject, command, target = transientTranscriptTarget()) {
|
|
257
|
-
|
|
258
|
-
return renderCandidates(subject, candidates, target);
|
|
179
|
+
return wrapAutoLoaded(renderWinnerBlocks(planWinners(eventPlan(subject, 'pre-command', { command })), target));
|
|
259
180
|
}
|
|
260
181
|
function carriesPreCommandEntry(doc) {
|
|
261
182
|
return doc.surfaces.some((entry) => entry.on === 'pre-command');
|