@north-light/crouter 0.3.176 → 0.3.177
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 +4 -4
- package/dist/api/client.js +2 -2
- package/dist/api/dto/lifecycle.d.ts +1 -1
- package/dist/api/dto/reports.d.ts +1 -1
- package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
- package/dist/clients/attach/config.js +5 -5
- package/dist/clients/attach/input/clipboard-image.js +2 -2
- package/dist/clients/attach/input/ref-autocomplete.js +1 -1
- package/dist/clients/attach/input/titled-editor.d.ts +12 -13
- package/dist/clients/attach/input/titled-editor.js +14 -15
- package/dist/clients/attach/overlays/dialogs.d.ts +1 -1
- package/dist/clients/attach/overlays/dialogs.js +2 -2
- package/dist/clients/attach/overlays/mcp.js +5 -12
- package/dist/clients/attach/render/chat-view.d.ts +1 -1
- package/dist/clients/attach/render/chat-view.js +2 -2
- package/dist/clients/attach/render/group-recap.js +7 -0
- package/dist/clients/attach/session/context.js +3 -3
- package/dist/clients/attach/session/editor-inventory.js +6 -6
- package/dist/clients/attach/session/input-wiring.js +1 -1
- package/dist/clients/attach/session/teardown.js +7 -9
- package/dist/clients/attach/slash/dispatch.d.ts +1 -2
- package/dist/clients/attach/slash/dispatch.js +4 -8
- package/dist/clients/attach/viewer.js +394 -394
- package/dist/clients/inbox/controller.js +3 -2
- package/dist/clients/inbox/deck-adapter.js +3 -2
- package/dist/clients/surfaces/host.d.ts +1 -1
- package/dist/commands/api-client.d.ts +12 -13
- package/dist/commands/api-client.js +15 -16
- package/dist/commands/chord.js +1 -2
- package/dist/commands/human/queue.js +6 -5
- package/dist/commands/memory/find.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node-lifecycle-revive.d.ts +1 -1
- package/dist/commands/node-lifecycle-revive.js +3 -3
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/placement.d.ts +1 -1
- package/dist/commands/surface/node/placement.js +1 -1
- package/dist/commands/surface-edit.js +1 -4
- package/dist/commands/sys/panels/panel.d.ts +3 -3
- package/dist/commands/sys/panels/panel.js +3 -3
- package/dist/commands/sys/setup-core.js +1 -7
- package/dist/commands/sys/sync-deps.js +1 -3
- package/dist/commands/sys/sync-project-guidance.js +2 -11
- package/dist/commands/sys/sysprompt.js +1 -3
- package/dist/core/__tests__/fixtures/fake-engine.js +1 -1
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +1 -1
- package/dist/core/canvas/boot.js +1 -1
- package/dist/core/canvas/browse/app.js +2 -2
- package/dist/core/canvas/canvas.d.ts +3 -3
- package/dist/core/canvas/canvas.js +3 -3
- package/dist/core/canvas/daemon-owner.js +1 -2
- package/dist/core/canvas/db.js +7 -8
- package/dist/core/canvas/index.js +2 -2
- package/dist/core/canvas/pid.d.ts +12 -14
- package/dist/core/canvas/pid.js +16 -20
- package/dist/core/canvas/remote-transport.js +2 -2
- package/dist/core/canvas/status-glyph.d.ts +1 -1
- package/dist/core/canvas/status-glyph.js +1 -1
- package/dist/core/canvas/types.d.ts +1 -1
- package/dist/core/command-manifests/manifest.js +4 -6
- package/dist/core/command-manifests/schema.js +10 -12
- package/dist/core/command-plugins/transport/exec-invoke.js +5 -7
- package/dist/core/command-plugins/transport/http-invoke.js +4 -6
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +2 -2
- package/dist/core/events/read.js +1 -3
- package/dist/core/fault-classifier.js +1 -3
- package/dist/core/fs-utils.d.ts +7 -0
- package/dist/core/fs-utils.js +29 -2
- package/dist/core/host-exports/export.d.ts +4 -5
- package/dist/core/host-exports/export.js +4 -5
- package/dist/core/human/claim.js +3 -2
- package/dist/core/human/convention.d.ts +0 -1
- package/dist/core/human/convention.js +1 -9
- package/dist/core/human/scan.js +5 -4
- package/dist/core/profiles/manifest.d.ts +1 -1
- package/dist/core/profiles/manifest.js +1 -1
- package/dist/core/review/types.d.ts +1 -1
- package/dist/core/review/types.js +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +4 -4
- package/dist/core/runtime/broker-cli.js +1 -1
- package/dist/core/runtime/broker-protocol.d.ts +9 -10
- package/dist/core/runtime/broker-protocol.js +9 -10
- package/dist/core/runtime/broker-sdk.d.ts +1 -1
- package/dist/core/runtime/broker.js +6 -6
- package/dist/core/runtime/host.js +14 -14
- package/dist/core/runtime/launch.d.ts +1 -1
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/lifecycle.js +16 -21
- package/dist/core/runtime/node-read.js +2 -3
- package/dist/core/runtime/package-health.js +1 -8
- package/dist/core/runtime/persona.js +2 -2
- package/dist/core/runtime/pi-vendored.d.ts +4 -2
- package/dist/core/runtime/pi-vendored.js +5 -16
- package/dist/core/runtime/promote.js +1 -1
- package/dist/core/runtime/reopen.js +2 -2
- package/dist/core/runtime/revive-all.d.ts +3 -3
- package/dist/core/runtime/revive-all.js +6 -6
- package/dist/core/runtime/revive.js +1 -1
- package/dist/core/runtime/spawn.d.ts +1 -2
- package/dist/core/runtime/spawn.js +2 -3
- package/dist/core/runtime/structured-output.js +4 -6
- package/dist/core/runtime/tmux-driver.d.ts +2 -2
- package/dist/core/runtime/tmux-driver.js +3 -5
- package/dist/core/runtime/tool-group-summary.js +5 -4
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/scope.js +2 -9
- package/dist/core/self-update.js +3 -3
- package/dist/core/spawn.d.ts +0 -1
- package/dist/core/spawn.js +1 -4
- package/dist/core/substrate/on-read.js +1 -9
- package/dist/core/wake.js +2 -2
- package/dist/core/worktree.d.ts +3 -3
- package/dist/core/worktree.js +5 -7
- package/dist/daemon/api/bridge.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +1 -2
- package/dist/daemon/api/handlers/messages.js +2 -2
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/reports.js +1 -1
- package/dist/daemon/api/handlers/validate.d.ts +2 -1
- package/dist/daemon/api/handlers/validate.js +2 -3
- package/dist/daemon/cron-run.js +7 -7
- package/dist/daemon/human/finish.js +3 -2
- package/dist/daemon/manage.d.ts +6 -6
- package/dist/daemon/manage.js +15 -16
- package/dist/pi-extensions/canvas-context-intro.js +1 -14
- package/dist/pi-extensions/canvas-doc-substrate.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.js +1 -14
- package/dist/pi-extensions/truncate.d.ts +5 -0
- package/dist/pi-extensions/truncate.js +14 -0
- package/dist/shared/predicates.d.ts +2 -0
- package/dist/shared/predicates.js +4 -0
- package/dist/shared/shell-quote.d.ts +3 -0
- package/dist/shared/shell-quote.js +5 -0
- package/dist/shared/tool-groups.d.ts +4 -2
- package/dist/shared/tool-groups.js +3 -3
- package/dist/types.d.ts +4 -5
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -7,7 +7,8 @@ import { scanInbox } from '../../core/human/scan.js';
|
|
|
7
7
|
import { watchInboxActivity } from '../../core/human/root.js';
|
|
8
8
|
import { claimTicket, heartbeatClaim, releaseClaim } from '../../core/human/claim.js';
|
|
9
9
|
import { readTicketResult } from '../../core/human/tickets.js';
|
|
10
|
-
import { clearProgress, deckPath
|
|
10
|
+
import { clearProgress, deckPath } from '../../core/human/convention.js';
|
|
11
|
+
import { readJsonOrNull } from '../../core/fs-utils.js';
|
|
11
12
|
import { DeckAdapter } from './deck-adapter.js';
|
|
12
13
|
import { validateDeck } from '../../core/human/deck-schema.js';
|
|
13
14
|
import { ReviewAdapter } from './review-adapter.js';
|
|
@@ -258,7 +259,7 @@ export class InboxController {
|
|
|
258
259
|
this.markDirty();
|
|
259
260
|
}
|
|
260
261
|
readDeck(dir) {
|
|
261
|
-
const deck =
|
|
262
|
+
const deck = readJsonOrNull(deckPath(dir));
|
|
262
263
|
if (deck === null)
|
|
263
264
|
return undefined;
|
|
264
265
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { progressPath
|
|
1
|
+
import { progressPath } from '../../core/human/convention.js';
|
|
2
|
+
import { readJsonOrNull } from '../../core/fs-utils.js';
|
|
2
3
|
import { mountPanel } from './tui/panel.js';
|
|
3
4
|
/** Embeds the single deck renderer in a controller-owned rectangle. */
|
|
4
5
|
export class DeckAdapter {
|
|
@@ -50,7 +51,7 @@ function notificationsAcknowledged(deck, responses) {
|
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
function initialResponses(deck, dir) {
|
|
53
|
-
const saved =
|
|
54
|
+
const saved = readJsonOrNull(progressPath(dir))?.responses;
|
|
54
55
|
if (Array.isArray(saved))
|
|
55
56
|
return saved;
|
|
56
57
|
return deck.interactions.flatMap((interaction) => {
|
|
@@ -12,7 +12,7 @@ export interface SurfaceComponent extends Component {
|
|
|
12
12
|
/** Consume wheel input when applicable. */
|
|
13
13
|
handleWheel?(direction: 'up' | 'down', lines: number): boolean;
|
|
14
14
|
}
|
|
15
|
-
/** The identities used for mounted
|
|
15
|
+
/** The identities used for mounted surfaces. */
|
|
16
16
|
export type SurfaceId = 'inbox' | 'review';
|
|
17
17
|
/** How long a notice stays up. */
|
|
18
18
|
export interface SurfaceNoticeOptions {
|
|
@@ -20,14 +20,14 @@ export declare function rethrowAsCliError(err: unknown, next?: string): never;
|
|
|
20
20
|
* 404-only→null mapping as `ApiCanvasSource.orNull`, but returning the raw DTO
|
|
21
21
|
* and rethrowing non-404s as CLI errors rather than swallowing them. */
|
|
22
22
|
export declare function getNodeOrNull(nodeId: string): Promise<NodeDetailDTO | null>;
|
|
23
|
-
/** Bounded tail of crtrd.err surfaced on a cold-start `/healthz` timeout
|
|
24
|
-
*
|
|
23
|
+
/** Bounded tail of crtrd.err surfaced on a cold-start `/healthz` timeout.
|
|
24
|
+
* Large enough to catch a real startup failure/stack trace,
|
|
25
25
|
* small enough that a runaway daemon log can never leak unbounded into a CLI
|
|
26
26
|
* error message. */
|
|
27
27
|
export declare const COLD_START_DIAGNOSTIC_TAIL_BYTES = 4096;
|
|
28
28
|
/** Read a bounded tail of crtrd's stderr log to enrich a `daemon_unavailable`
|
|
29
|
-
* cold-start timeout
|
|
30
|
-
*
|
|
29
|
+
* cold-start timeout, so the actual startup failure is not discarded behind a
|
|
30
|
+
* bare message. Bounds the I/O itself — opens the file and reads only the
|
|
31
31
|
* final `COLD_START_DIAGNOSTIC_TAIL_BYTES` bytes via `fstatSync`+`readSync`,
|
|
32
32
|
* never `readFileSync`-ing the whole append-only log — then decodes/filters
|
|
33
33
|
* that bounded slice. Best-effort and synchronous per the `coldStartDiagnostic`
|
|
@@ -42,15 +42,14 @@ export declare function readColdStartDiagnostic(): string | undefined;
|
|
|
42
42
|
* autostart is disabled — the client throws `daemon_unavailable` (spec §7.1,
|
|
43
43
|
* §8). This is the ONLY CLI reference to `ensureDaemon`.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* `
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `daemon_unavailable` at 10s. Passing the SAME `DAEMON_VERIFY_WINDOW_MS`
|
|
45
|
+
* `onColdSocket` is fire-and-forget — `ensureDaemon()` spawns crtrd and
|
|
46
|
+
* returns immediately, so the client's OWN `/healthz` poll is the only
|
|
47
|
+
* deadline the initiating CLI invocation actually waits on. It must never be
|
|
48
|
+
* narrower than the window `spawnDaemon`'s `verifyDaemonStartup` allows (which
|
|
49
|
+
* `ensureDaemon`'s spawn transitively awaits, in the detached child, on the
|
|
50
|
+
* SAME cold-start path): a daemon that takes 12s is a valid, verified startup,
|
|
51
|
+
* and a 10s client poll would still give up and report `daemon_unavailable`
|
|
52
|
+
* against it. Passing the SAME `DAEMON_VERIFY_WINDOW_MS`
|
|
54
53
|
* here keeps the two in lockstep — one authoritative window, not two that can
|
|
55
54
|
* drift apart — without adding any retry/fallback poll loop. */
|
|
56
55
|
export declare function cliClient(opts?: {
|
|
@@ -65,15 +65,15 @@ export async function getNodeOrNull(nodeId) {
|
|
|
65
65
|
function autostartDisabled() {
|
|
66
66
|
return process.env['CRTR_NO_DAEMON_AUTOSTART'] === '1' || process.argv.includes('--no-autostart');
|
|
67
67
|
}
|
|
68
|
-
/** Bounded tail of crtrd.err surfaced on a cold-start `/healthz` timeout
|
|
69
|
-
*
|
|
68
|
+
/** Bounded tail of crtrd.err surfaced on a cold-start `/healthz` timeout.
|
|
69
|
+
* Large enough to catch a real startup failure/stack trace,
|
|
70
70
|
* small enough that a runaway daemon log can never leak unbounded into a CLI
|
|
71
71
|
* error message. */
|
|
72
72
|
export const COLD_START_DIAGNOSTIC_TAIL_BYTES = 4_096;
|
|
73
73
|
const COLD_START_DIAGNOSTIC_TAIL_LINES = 20;
|
|
74
74
|
/** Read a bounded tail of crtrd's stderr log to enrich a `daemon_unavailable`
|
|
75
|
-
* cold-start timeout
|
|
76
|
-
*
|
|
75
|
+
* cold-start timeout, so the actual startup failure is not discarded behind a
|
|
76
|
+
* bare message. Bounds the I/O itself — opens the file and reads only the
|
|
77
77
|
* final `COLD_START_DIAGNOSTIC_TAIL_BYTES` bytes via `fstatSync`+`readSync`,
|
|
78
78
|
* never `readFileSync`-ing the whole append-only log — then decodes/filters
|
|
79
79
|
* that bounded slice. Best-effort and synchronous per the `coldStartDiagnostic`
|
|
@@ -116,15 +116,14 @@ export function readColdStartDiagnostic() {
|
|
|
116
116
|
* autostart is disabled — the client throws `daemon_unavailable` (spec §7.1,
|
|
117
117
|
* §8). This is the ONLY CLI reference to `ensureDaemon`.
|
|
118
118
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* `
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* `daemon_unavailable` at 10s. Passing the SAME `DAEMON_VERIFY_WINDOW_MS`
|
|
119
|
+
* `onColdSocket` is fire-and-forget — `ensureDaemon()` spawns crtrd and
|
|
120
|
+
* returns immediately, so the client's OWN `/healthz` poll is the only
|
|
121
|
+
* deadline the initiating CLI invocation actually waits on. It must never be
|
|
122
|
+
* narrower than the window `spawnDaemon`'s `verifyDaemonStartup` allows (which
|
|
123
|
+
* `ensureDaemon`'s spawn transitively awaits, in the detached child, on the
|
|
124
|
+
* SAME cold-start path): a daemon that takes 12s is a valid, verified startup,
|
|
125
|
+
* and a 10s client poll would still give up and report `daemon_unavailable`
|
|
126
|
+
* against it. Passing the SAME `DAEMON_VERIFY_WINDOW_MS`
|
|
128
127
|
* here keeps the two in lockstep — one authoritative window, not two that can
|
|
129
128
|
* drift apart — without adding any retry/fallback poll loop. */
|
|
130
129
|
export function cliClient(opts) {
|
|
@@ -140,7 +139,7 @@ export function cliClient(opts) {
|
|
|
140
139
|
ensureDaemon();
|
|
141
140
|
},
|
|
142
141
|
// On a cold-start timeout, enrich the bare `daemon_unavailable` with the
|
|
143
|
-
// tail of crtrd's own stderr log
|
|
142
|
+
// tail of crtrd's own stderr log instead of discarding it.
|
|
144
143
|
coldStartDiagnostic: readColdStartDiagnostic,
|
|
145
144
|
coldStartPollWindowMs: DAEMON_VERIFY_WINDOW_MS,
|
|
146
145
|
});
|
|
@@ -153,8 +152,8 @@ export function cliClient(opts) {
|
|
|
153
152
|
// performs no lossy legacy reconstruction or invented local-presence values.
|
|
154
153
|
// The attach viewer's recurring subtree walk (chrome/canvas-panels.ts + overlays/graph.ts
|
|
155
154
|
// computeActivity/visitActivity) calls subscriptionsOf/subscribersOf once per
|
|
156
|
-
// visited node on every poll tick
|
|
157
|
-
//
|
|
155
|
+
// visited node on every poll tick, and a `GET /v1/nodes/:id` per call just to
|
|
156
|
+
// read the edge list off the detail DTO costs one round trip per visited node.
|
|
158
157
|
// This short-TTL cache backs those two methods with the lean roster
|
|
159
158
|
// (`GET /v1/canvas/roster`) instead — one shared fetch per TTL window covers
|
|
160
159
|
// every topology walk in that window, comfortably under the attach viewer's 5s
|
package/dist/commands/chord.js
CHANGED
|
@@ -19,8 +19,7 @@ const pexec = promisify(execFile);
|
|
|
19
19
|
* here rather than importing node.ts's `nodeInPane` (which reaches openDb via the
|
|
20
20
|
* canvas accessors) so this dispatcher's import graph stays db-free. Under the
|
|
21
21
|
* headless-broker model every engine runs DETACHED (window=null), so the pane tag
|
|
22
|
-
* is the ONLY node handle
|
|
23
|
-
* matched legacy in-pane engines, which no longer exist. A stale tag is ignored
|
|
22
|
+
* is the ONLY node handle. A stale tag is ignored
|
|
24
23
|
* (the tagged node must still be live), validated through the daemon API. */
|
|
25
24
|
async function nodeInPane(pane) {
|
|
26
25
|
if (pane === undefined || pane === '')
|
|
@@ -5,7 +5,8 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
5
5
|
import { ticketDir } from '../../core/human/root.js';
|
|
6
6
|
import { scanInbox } from '../../core/human/scan.js';
|
|
7
7
|
import { parseDeck } from '../../core/human/deck-schema.js';
|
|
8
|
-
import { deckPath, isResolved,
|
|
8
|
+
import { deckPath, isResolved, reviewPath } from '../../core/human/convention.js';
|
|
9
|
+
import { readJsonOrNull } from '../../core/fs-utils.js';
|
|
9
10
|
import { InputError } from '../../core/io.js';
|
|
10
11
|
import { paginate } from '../../core/pagination.js';
|
|
11
12
|
import { cliClient, rethrowAsCliError } from '../api-client.js';
|
|
@@ -14,9 +15,9 @@ function toRef(n) {
|
|
|
14
15
|
return { node_id: n.node_id, name: n.name, cwd: n.cwd, parent: n.parent, status: n.status };
|
|
15
16
|
}
|
|
16
17
|
/** The canvas roster, fetched ONCE per leaf run through the API and threaded
|
|
17
|
-
* into the sync provenance helpers below (
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* into the sync provenance helpers below (the API surface is async, so the
|
|
19
|
+
* fetch is hoisted to the leaf and the helpers read the pre-fetched
|
|
20
|
+
* array/map). */
|
|
20
21
|
async function fetchNodeRefs() {
|
|
21
22
|
return (await cliClient().listNodes()).map(toRef);
|
|
22
23
|
}
|
|
@@ -67,7 +68,7 @@ function summarizeTicket(summary, nodes, byId) {
|
|
|
67
68
|
return null;
|
|
68
69
|
const asking = askingNodeFor(jobId, byId);
|
|
69
70
|
const conversation = resolveConversation(asking, nodes);
|
|
70
|
-
const interactionCount = summary.kind === 'deck' ?
|
|
71
|
+
const interactionCount = summary.kind === 'deck' ? readJsonOrNull(deckPath(summary.dir))?.interactions.length : undefined;
|
|
71
72
|
const sourceLabel = readableSourceLabel(summary.source);
|
|
72
73
|
return {
|
|
73
74
|
id: summary.id,
|
|
@@ -155,9 +155,9 @@ export const findLeaf = defineLeaf({
|
|
|
155
155
|
// Sort comparator MUST match keyOf exactly (score desc, then scope asc,
|
|
156
156
|
// then name asc as the final deterministic identity component — the
|
|
157
157
|
// candidate dedup keys on scope+name, so this pair is always unique).
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
158
|
+
// Sorting equal-score hits from different scopes by name alone while the
|
|
159
|
+
// cursor key orders by scope first would let the array disagree with its
|
|
160
|
+
// own keys, and paginate() would silently skip a later item.
|
|
161
161
|
hits.sort((a, b) => {
|
|
162
162
|
if (b.score !== a.score)
|
|
163
163
|
return b.score - a.score;
|
|
@@ -375,7 +375,7 @@ export const nodeYield = defineLeaf({
|
|
|
375
375
|
{ kind: 'flag', name: 'promote', type: 'bool', required: false, constraint: 'Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded.' },
|
|
376
376
|
{ kind: 'flag', name: 'kind', type: 'string', required: false, constraint: 'Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a sub-kind (e.g. plan/reviewers/security) is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode.' },
|
|
377
377
|
{ kind: 'flag', name: 'model', type: 'enum', choices: ['ultra', 'strong'], required: false, constraint: 'Change the model your fresh revive runs on, by capability tier: `ultra` (frontier — reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus — regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives.' },
|
|
378
|
-
// Deliberate fail-first contract
|
|
378
|
+
// Deliberate fail-first contract: builtin orchestration guidance shows bare
|
|
379
379
|
// `crtr node yield`; that invocation fails fast and surfaces yield's full help just in
|
|
380
380
|
// time, at the point of use. Keep the note required rather than pre-documenting stdin
|
|
381
381
|
// in kernel guidance.
|
|
@@ -3,7 +3,7 @@ import type { Fault } from '../core/runtime/fault.js';
|
|
|
3
3
|
/** --now eligibility for a live, non-busy fault: a daemon-owned AUTO retry
|
|
4
4
|
* (kick it early instead of waiting out the schedule), or any fault the
|
|
5
5
|
* canvas dashboard flags "needs you" (`faultNeedsYou`, status-glyph.ts) — the
|
|
6
|
-
* disposition that otherwise has NO sanctioned recovery verb
|
|
6
|
+
* disposition that otherwise has NO sanctioned recovery verb.
|
|
7
7
|
* Both are scoped to `pi→provider`: refuses a client-owned auto retry
|
|
8
8
|
* (redialing) already in flight, and refuses any OTHER link — notably a
|
|
9
9
|
* `daemon→node` wedge fault, which --now can't clear (SIGTERM doesn't touch
|
|
@@ -40,7 +40,7 @@ import { waitForViewSocketReady } from '../core/runtime/view-socket.js';
|
|
|
40
40
|
/** --now eligibility for a live, non-busy fault: a daemon-owned AUTO retry
|
|
41
41
|
* (kick it early instead of waiting out the schedule), or any fault the
|
|
42
42
|
* canvas dashboard flags "needs you" (`faultNeedsYou`, status-glyph.ts) — the
|
|
43
|
-
* disposition that otherwise has NO sanctioned recovery verb
|
|
43
|
+
* disposition that otherwise has NO sanctioned recovery verb.
|
|
44
44
|
* Both are scoped to `pi→provider`: refuses a client-owned auto retry
|
|
45
45
|
* (redialing) already in flight, and refuses any OTHER link — notably a
|
|
46
46
|
* `daemon→node` wedge fault, which --now can't clear (SIGTERM doesn't touch
|
|
@@ -143,7 +143,7 @@ export const nodeReviveLeaf = defineLeaf({
|
|
|
143
143
|
// above) so it can't be used to nuke a healthy node. This whole path stays
|
|
144
144
|
// LOCAL: the kick is a direct SIGTERM to the broker pid (there is no revive
|
|
145
145
|
// API call), and the node detail comes from the daemon read (`getNode`). The
|
|
146
|
-
// reopen gate
|
|
146
|
+
// reopen gate is NOT invoked here — --now targets a LIVE broker, and a
|
|
147
147
|
// finalized node has none (its own live-pid check below refuses it anyway).
|
|
148
148
|
if (now) {
|
|
149
149
|
// The detail DTO projects `pi_pid_identity` (the launch-time process-identity
|
|
@@ -201,7 +201,7 @@ export const nodeReviveLeaf = defineLeaf({
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
// Ordinary revive: the daemon relaunches the broker (the ONLY sanctioned
|
|
204
|
-
// launcher). Node-existence validation (`not_found`) and the
|
|
204
|
+
// launcher). Node-existence validation (`not_found`) and the reopen gate
|
|
205
205
|
// are both enforced server-side in `handleRevive`; a thrown gate/not-found
|
|
206
206
|
// error round-trips through `ApiError.details` and is reconstructed into the
|
|
207
207
|
// identical `InputError` by the shared `apiErrorToCliError` (via
|
package/dist/commands/push.js
CHANGED
|
@@ -73,7 +73,7 @@ function makeTierLeaf(tier) {
|
|
|
73
73
|
...(tier === 'final'
|
|
74
74
|
? [
|
|
75
75
|
'Marks the node done (status + intent) server-side; its engine shuts down on next stop.',
|
|
76
|
-
'When this node owns a managed worktree with zero commits ahead of its base and no uncommitted changes, the server auto-drops it instead of blocking on `node worktree close
|
|
76
|
+
'When this node owns a managed worktree with zero commits ahead of its base and no uncommitted changes, the server auto-drops it instead of blocking on `node worktree close`; a worktree with real commits or real uncommitted changes still blocks with open_managed_worktree.',
|
|
77
77
|
]
|
|
78
78
|
: []),
|
|
79
79
|
],
|
|
@@ -39,7 +39,7 @@ export declare function openSpawnViewer(nodeId: string, cwd: string, name: strin
|
|
|
39
39
|
* A `crtr surface attach` viewer self-tags its pane with the node it views
|
|
40
40
|
* (`@crtr_node`) — the tag IS the handle (a broker engine runs DETACHED with
|
|
41
41
|
* window=null, so there is no window→node fallback). Kept SYNC + tag-only: the
|
|
42
|
-
*
|
|
42
|
+
* CLI does not read canvas state to confirm the tagged node is
|
|
43
43
|
* still live, so a stale tag (attach SIGKILLed without clearing) resolves until
|
|
44
44
|
* the pane is respawned. Callers that mutate then re-validate the id via the
|
|
45
45
|
* API (getNode 404 → clean error). */
|
|
@@ -111,7 +111,7 @@ export function openSpawnViewer(nodeId, cwd, name) {
|
|
|
111
111
|
* A `crtr surface attach` viewer self-tags its pane with the node it views
|
|
112
112
|
* (`@crtr_node`) — the tag IS the handle (a broker engine runs DETACHED with
|
|
113
113
|
* window=null, so there is no window→node fallback). Kept SYNC + tag-only: the
|
|
114
|
-
*
|
|
114
|
+
* CLI does not read canvas state to confirm the tagged node is
|
|
115
115
|
* still live, so a stale tag (attach SIGKILLed without clearing) resolves until
|
|
116
116
|
* the pane is respawned. Callers that mutate then re-validate the id via the
|
|
117
117
|
* API (getNode 404 → clean error). */
|
|
@@ -33,6 +33,7 @@ import { isAbsolute, resolve } from 'node:path';
|
|
|
33
33
|
import { defineLeaf } from '../core/command.js';
|
|
34
34
|
import { InputError } from '../core/io.js';
|
|
35
35
|
import { currentTmux, splitWindow, paneLocation, paneOfNode, } from '../core/runtime/placement-tmux.js';
|
|
36
|
+
import { shellQuote } from '../shared/shell-quote.js';
|
|
36
37
|
/** Resolve the pane the editor should open BESIDE — the pane that opened this
|
|
37
38
|
* command: explicit --pane, the caller's own $TMUX_PANE, the pane where THIS
|
|
38
39
|
* node is being watched (its `@crtr_node`-tagged pane, whatever viewer put it
|
|
@@ -57,10 +58,6 @@ function resolveTargetPane(explicit) {
|
|
|
57
58
|
}
|
|
58
59
|
return currentTmux()?.pane ?? null;
|
|
59
60
|
}
|
|
60
|
-
/** POSIX single-quote a shell word so paths survive spaces/specials. */
|
|
61
|
-
function shellQuote(s) {
|
|
62
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
63
|
-
}
|
|
64
61
|
/** The command the new pane runs. The pane INHERITS the tmux server environment
|
|
65
62
|
* (like every crtr chrome pane — the subagent viewer panes get theirs the same
|
|
66
63
|
* way), and a tmux server started from the human's interactive shell captured
|
|
@@ -7,9 +7,9 @@ export interface SetupPanel {
|
|
|
7
7
|
type ThemeFgRole = "border" | "accent" | "dim" | "success" | "warning" | "muted" | "mdLink" | "mdLinkUrl";
|
|
8
8
|
export declare const theme: {
|
|
9
9
|
fg(role: ThemeFgRole, text: string): string;
|
|
10
|
-
/** A real filled selection band. `selectedBg`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
/** A real filled selection band. An identity `selectedBg` would leave the
|
|
11
|
+
* active tab and the active row with no background at all — reverse video is
|
|
12
|
+
* the one background every terminal theme honours. */
|
|
13
13
|
bg(_role: "selectedBg", text: string): string;
|
|
14
14
|
bold(text: string): string;
|
|
15
15
|
};
|
|
@@ -23,9 +23,9 @@ export const theme = {
|
|
|
23
23
|
return ansiYellow(text);
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
-
/** A real filled selection band. `selectedBg`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
/** A real filled selection band. An identity `selectedBg` would leave the
|
|
27
|
+
* active tab and the active row with no background at all — reverse video is
|
|
28
|
+
* the one background every terminal theme honours. */
|
|
29
29
|
bg(_role, text) {
|
|
30
30
|
return `\x1b[7m${text}\x1b[27m`;
|
|
31
31
|
},
|
|
@@ -14,6 +14,7 @@ import { detectNerdFont, nerdFontInstallCommand, NERD_FONT_SELECT_HINT } from '.
|
|
|
14
14
|
import { ensureRenderer, isRendererReady } from '../../core/termrender/termrender.js';
|
|
15
15
|
import { installFromMarketplace } from '../pkg/plugin-manage.js';
|
|
16
16
|
import { findPluginByName } from '../../core/resolver.js';
|
|
17
|
+
import { expandTilde } from '../../core/fs-utils.js';
|
|
17
18
|
function isKeybindingsObject(value) {
|
|
18
19
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
19
20
|
}
|
|
@@ -214,13 +215,6 @@ export function buildSetupManifest(resolveBuiltinPackageDir = builtinPiPackageDi
|
|
|
214
215
|
};
|
|
215
216
|
});
|
|
216
217
|
}
|
|
217
|
-
function expandTilde(pathValue, homeDir) {
|
|
218
|
-
if (pathValue === '~')
|
|
219
|
-
return homeDir;
|
|
220
|
-
if (pathValue.startsWith('~/'))
|
|
221
|
-
return join(homeDir, pathValue.slice(2));
|
|
222
|
-
return pathValue;
|
|
223
|
-
}
|
|
224
218
|
function npmPackageName(source) {
|
|
225
219
|
if (!source.startsWith('npm:'))
|
|
226
220
|
return null;
|
|
@@ -32,13 +32,11 @@ import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
|
32
32
|
import { pathExists, readJsonIfExists, readText, walkFiles, writeText } from '../../core/fs-utils.js';
|
|
33
33
|
import { serializeMemoryDoc } from '../memory/shared.js';
|
|
34
34
|
import { routeFromDescription, scalarString } from './sync-shared.js';
|
|
35
|
+
import { isRecord } from '../../shared/predicates.js';
|
|
35
36
|
/** Marker value written to the generated top INDEX's `generated-by` field —
|
|
36
37
|
* the wipe guard: `deps` only ever deletes a deps/ dir whose INDEX carries
|
|
37
38
|
* this marker. */
|
|
38
39
|
export const DEPS_GENERATED_MARKER = 'crtr sys sync deps';
|
|
39
|
-
function isRecord(value) {
|
|
40
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
41
|
-
}
|
|
42
40
|
function invalidDepsConfig(message) {
|
|
43
41
|
throw usage(`--deps: invalid package.json crtr.sync.deps.exclude: ${message}`);
|
|
44
42
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// `.crouter/memory/`. Each project guide becomes the root INDEX front door;
|
|
4
4
|
// rules retain their explicit path routes, or use the workspace-open `.` route
|
|
5
5
|
// when they are pathless.
|
|
6
|
-
import { existsSync, readdirSync,
|
|
6
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
7
7
|
import { basename, dirname, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { defineLeaf } from '../../core/command.js';
|
|
9
9
|
import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
@@ -13,6 +13,7 @@ import { loadProfileManifest } from '../../core/profiles/manifest.js';
|
|
|
13
13
|
import { CRTR_DIR_NAME } from '../../types.js';
|
|
14
14
|
import { memoryFilePath, serializeMemoryDoc } from '../memory/shared.js';
|
|
15
15
|
import { normalizedName, routeFromDescription, scalarString } from './sync-shared.js';
|
|
16
|
+
import { realpathOrSelf } from '../../core/fs-utils.js';
|
|
16
17
|
// Directories a project-doc walk never descends into — build/dependency trees
|
|
17
18
|
// plus crouter's own state dir.
|
|
18
19
|
const PROJECT_DOC_JUNK_DIRS = new Set([
|
|
@@ -136,16 +137,6 @@ function gitWorktreeRoot(dir) {
|
|
|
136
137
|
const top = res.stdout.trim();
|
|
137
138
|
return top === '' ? undefined : resolve(top);
|
|
138
139
|
}
|
|
139
|
-
/** `realpathSync`, tolerant of a path that doesn't exist or can't be resolved
|
|
140
|
-
* (falls back to the path as given). */
|
|
141
|
-
function realpathOrSelf(p) {
|
|
142
|
-
try {
|
|
143
|
-
return realpathSync(p);
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
return p;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
140
|
/** First Markdown H1 heading in the doc body, heading marker stripped. */
|
|
150
141
|
function h1Title(body) {
|
|
151
142
|
for (const raw of body.split('\n')) {
|
|
@@ -7,9 +7,7 @@ import { jobDir } from '../../core/canvas/paths.js';
|
|
|
7
7
|
import { openNodeWindow } from '../../core/runtime/placement-tmux.js';
|
|
8
8
|
import { ApiError } from '../../api/index.js';
|
|
9
9
|
import { cliClient } from '../api-client.js';
|
|
10
|
-
|
|
11
|
-
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
12
|
-
}
|
|
10
|
+
import { shellQuote } from '../../shared/shell-quote.js';
|
|
13
11
|
export const sysSyspromptLeaf = defineLeaf({
|
|
14
12
|
name: 'sysprompt',
|
|
15
13
|
description: 'print a node\'s assembled system prompt',
|
|
@@ -139,7 +139,7 @@ export class SessionManager {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
// ---------------------------------------------------------------------------
|
|
142
|
-
// AgentSessionEvent stream synthesis
|
|
142
|
+
// AgentSessionEvent stream synthesis. The fake emits a
|
|
143
143
|
// REALISTIC streaming assistant turn through session.subscribe — the SAME channel
|
|
144
144
|
// the real pi engine feeds the broker's fan-out — so the frames a `crtr surface attach`
|
|
145
145
|
// client receives are byte-for-byte real AgentSessionEvents. The EMITTED event
|
|
@@ -37,7 +37,7 @@ export interface HarnessOpts {
|
|
|
37
37
|
* harness does NOT gate on tmux, does NOT eagerly create the isolated tmux
|
|
38
38
|
* session, and skips the per-pane `set-environment` seeding — nothing in a
|
|
39
39
|
* headless run opens a window. CRTR_NODE_SESSION/CRTR_ROOT_SESSION are still
|
|
40
|
-
* scrubbed/restored for env hygiene, but
|
|
40
|
+
* scrubbed/restored for env hygiene, but do not route placement: a managed
|
|
41
41
|
* child opens NO viewer (it boots a detached broker, supervised by pid), and a
|
|
42
42
|
* --root opens one viewer in the caller's current session. Headless tests must
|
|
43
43
|
* use `spawnHeadlessChild`, never `spawnChild`. See createHeadlessHarness(). */
|
|
@@ -312,7 +312,7 @@ export async function createHarness(opts = {}) {
|
|
|
312
312
|
}
|
|
313
313
|
// Pre-create the isolated session on the DEFAULT server so teardown always
|
|
314
314
|
// has a kill-sessions target.
|
|
315
|
-
// ISOLATION ASSUMPTION (see header
|
|
315
|
+
// ISOLATION ASSUMPTION (see header): isolation is by SESSION NAME on
|
|
316
316
|
// the DEFAULT tmux server only. The runtime CLI shells `tmux` with no `-L`, so
|
|
317
317
|
// a custom-socket server (`tmux -L foo`) would be invisible to it; this harness
|
|
318
318
|
// therefore assumes the default socket and only ever kill-sessions, never the
|
package/dist/core/canvas/boot.js
CHANGED
|
@@ -148,7 +148,7 @@ export function reconcileBootLiveness(current = currentBootIdentity()) {
|
|
|
148
148
|
const rows = db
|
|
149
149
|
.prepare('SELECT node_id FROM nodes WHERE pi_pid IS NOT NULL')
|
|
150
150
|
.all();
|
|
151
|
-
// Clear the launch-time identity baseline
|
|
151
|
+
// Clear the launch-time identity baseline
|
|
152
152
|
// alongside `pi_pid` — a boot resets the OS process table, so any recorded
|
|
153
153
|
// identity is as meaningless as the pid it was captured for; leaving it
|
|
154
154
|
// behind would let a coincidentally-reused pid on the NEW boot compare
|
|
@@ -358,8 +358,8 @@ export async function runBrowse(opts) {
|
|
|
358
358
|
// Coalesced search repaint. The expensive work per keystroke is recompute()
|
|
359
359
|
// (a fuzzyMatch flatten over the whole corpus) + a full renderFrame. Doing it
|
|
360
360
|
// synchronously on every key blocks the event loop, so fast typing batches into
|
|
361
|
-
// the stdin reads
|
|
362
|
-
// Instead, each key only mutates state.query (cheap) and marks the view dirty;
|
|
361
|
+
// the stdin reads, where a naive per-key handler drops batched chunks (see
|
|
362
|
+
// onKeySearch). Instead, each key only mutates state.query (cheap) and marks the view dirty;
|
|
363
363
|
// a single setImmediate drains AFTER every queued stdin 'data' callback (poll
|
|
364
364
|
// phase) has appended its chars, so N rapid keys collapse to ONE recompute+paint
|
|
365
365
|
// and no keystroke is ignored. The sort may still take a moment — that's fine.
|
|
@@ -33,7 +33,7 @@ export declare function setPresence(nodeId: string, presence: {
|
|
|
33
33
|
pane?: string | null;
|
|
34
34
|
}): void;
|
|
35
35
|
/** Record the live pi pid (daemon liveness signal) PLUS its launch-time
|
|
36
|
-
* identity fingerprint, in one atomic write
|
|
36
|
+
* identity fingerprint, in one atomic write:
|
|
37
37
|
* `capturePidIdentities([pid])` is a signal-0-adjacent `ps -p <pid>` probe
|
|
38
38
|
* taken RIGHT NOW, while `pid` is the process we just spawned/bound and
|
|
39
39
|
* therefore definitely still exists — this is the LAUNCH-time baseline
|
|
@@ -242,7 +242,7 @@ export type TerminalGuardResult<T> = {
|
|
|
242
242
|
} | {
|
|
243
243
|
kind: 'missing';
|
|
244
244
|
};
|
|
245
|
-
/**
|
|
245
|
+
/** Close the TOCTOU between checking a target has a natural cycle ahead
|
|
246
246
|
* of it and the write that depends on that (for example, a deferred inbox
|
|
247
247
|
* append). Fresh-reads `nodeId`'s
|
|
248
248
|
* (status, final_report) and either runs `body` (a write) or short-circuits
|
|
@@ -254,7 +254,7 @@ export type TerminalGuardResult<T> = {
|
|
|
254
254
|
* write left open. Pass the shared `hasNoNaturalCycle` predicate rather than
|
|
255
255
|
* a bespoke one so the two never drift.
|
|
256
256
|
*
|
|
257
|
-
*
|
|
257
|
+
* A dangling `nodeId` — the row deleted after the
|
|
258
258
|
* caller's own selection but before this guard's fresh read — MUST NEVER run
|
|
259
259
|
* `body`. Reporting `'missing'` distinctly from `'terminal'` (rather than
|
|
260
260
|
* folding it into either the terminal branch or a fabricated "safe" empty
|
|
@@ -243,7 +243,7 @@ export function setPresence(nodeId, presence) {
|
|
|
243
243
|
.run(presence.tmux_session ?? null, presence.window ?? null, presence.pane ?? null, nodeId);
|
|
244
244
|
}
|
|
245
245
|
/** Record the live pi pid (daemon liveness signal) PLUS its launch-time
|
|
246
|
-
* identity fingerprint, in one atomic write
|
|
246
|
+
* identity fingerprint, in one atomic write:
|
|
247
247
|
* `capturePidIdentities([pid])` is a signal-0-adjacent `ps -p <pid>` probe
|
|
248
248
|
* taken RIGHT NOW, while `pid` is the process we just spawned/bound and
|
|
249
249
|
* therefore definitely still exists — this is the LAUNCH-time baseline
|
|
@@ -633,7 +633,7 @@ export function settleDeadMessageWait(nodeId, controller, deliver) {
|
|
|
633
633
|
return result;
|
|
634
634
|
});
|
|
635
635
|
}
|
|
636
|
-
/**
|
|
636
|
+
/** Close the TOCTOU between checking a target has a natural cycle ahead
|
|
637
637
|
* of it and the write that depends on that (for example, a deferred inbox
|
|
638
638
|
* append). Fresh-reads `nodeId`'s
|
|
639
639
|
* (status, final_report) and either runs `body` (a write) or short-circuits
|
|
@@ -645,7 +645,7 @@ export function settleDeadMessageWait(nodeId, controller, deliver) {
|
|
|
645
645
|
* write left open. Pass the shared `hasNoNaturalCycle` predicate rather than
|
|
646
646
|
* a bespoke one so the two never drift.
|
|
647
647
|
*
|
|
648
|
-
*
|
|
648
|
+
* A dangling `nodeId` — the row deleted after the
|
|
649
649
|
* caller's own selection but before this guard's fresh read — MUST NEVER run
|
|
650
650
|
* `body`. Reporting `'missing'` distinctly from `'terminal'` (rather than
|
|
651
651
|
* folding it into either the terminal branch or a fabricated "safe" empty
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// daemon-owner.ts — the canvas-layer authoritative daemon ownership primitive
|
|
2
|
-
// (Phase 1 runtime-boundary, task D0).
|
|
1
|
+
// daemon-owner.ts — the canvas-layer authoritative daemon ownership primitive.
|
|
3
2
|
//
|
|
4
3
|
// A pidfile check followed by a later write is not singleton ownership: two
|
|
5
4
|
// `crtrd` invocations racing a cold start can both read "no pidfile", both
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -58,10 +58,9 @@ function addNodeColumnIfMissing(db, columns, name, ddl) {
|
|
|
58
58
|
db.exec(ddl);
|
|
59
59
|
columns.add(name);
|
|
60
60
|
}
|
|
61
|
-
/** v2 — additive runtime columns
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* NULL, so nothing observes a behavior change until a later phase reads them. */
|
|
61
|
+
/** v2 — the additive runtime columns: `intent, pi_pid, window, tmux_session`.
|
|
62
|
+
* `status` already lives in the baseline row, so it is NOT re-added here. All
|
|
63
|
+
* four default to NULL; v3 backfills them. */
|
|
65
64
|
function addRuntimeColumns(db) {
|
|
66
65
|
const columns = nodeColumns(db);
|
|
67
66
|
addNodeColumnIfMissing(db, columns, 'intent', `ALTER TABLE nodes ADD COLUMN intent TEXT;`);
|
|
@@ -69,14 +68,14 @@ function addRuntimeColumns(db) {
|
|
|
69
68
|
addNodeColumnIfMissing(db, columns, 'window', `ALTER TABLE nodes ADD COLUMN window TEXT;`);
|
|
70
69
|
addNodeColumnIfMissing(db, columns, 'tmux_session', `ALTER TABLE nodes ADD COLUMN tmux_session TEXT;`);
|
|
71
70
|
}
|
|
72
|
-
/** v3 — DATA backfill
|
|
71
|
+
/** v3 — DATA backfill. The runtime fields
|
|
73
72
|
* (`intent, pi_pid, window, tmux_session`) become authoritative in the row;
|
|
74
73
|
* copy each existing node's values out of its meta.json into the row columns
|
|
75
74
|
* once, so the version boundary loses no live state. `status` already mirrors
|
|
76
75
|
* the row, so it is not re-copied.
|
|
77
76
|
*
|
|
78
|
-
* LAYERING NOTE
|
|
79
|
-
*
|
|
77
|
+
* LAYERING NOTE — a sanctioned exception: a *data* migration must read
|
|
78
|
+
* meta.json, which db.ts normally would not. Reading it
|
|
80
79
|
* directly here — via paths.ts, a one-time, clearly-labeled boot-time data
|
|
81
80
|
* migration — is the deliberate choice over splitting the `user_version`
|
|
82
81
|
* counter across two modules. Idempotent and gated: it runs exactly once at the
|
|
@@ -367,7 +366,7 @@ function addProfileIdColumn(db) {
|
|
|
367
366
|
* identity fingerprint (`pid.ts`'s `composeIdentity`) recorded alongside
|
|
368
367
|
* `pi_pid` (v2) by `recordPid`, so `headlessBrokerHost.teardown()` can refuse
|
|
369
368
|
* to signal a `pi_pid` the OS has since recycled for an unrelated process
|
|
370
|
-
* (
|
|
369
|
+
* (see `NodeRuntime.pi_pid_identity`).
|
|
371
370
|
* Defaults NULL ⇒ no baseline ⇒ the guard fails open, exactly like a node
|
|
372
371
|
* booted before this column existed. No data backfill: an existing `pi_pid`
|
|
373
372
|
* with no recorded identity simply has no launch-time baseline to compare
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// The canvas: one global graph of nodes + edges.
|
|
2
|
-
//
|
|
1
|
+
// The canvas: one global graph of nodes + edges.
|
|
2
|
+
// Topology in sqlite (WAL), node flesh on disk.
|
|
3
3
|
export * from './types.js';
|
|
4
4
|
export * from './labels.js';
|
|
5
5
|
export * from './paths.js';
|