@north-light/crouter 0.3.176 → 0.3.178
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__/frame-selection.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/frame-selection.test.js +84 -0
- package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
- package/dist/clients/attach/ansi-cells.d.ts +31 -0
- package/dist/clients/attach/ansi-cells.js +120 -0
- 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/help.js +2 -1
- 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/file-links.js +15 -50
- package/dist/clients/attach/session/frame.d.ts +5 -1
- package/dist/clients/attach/session/frame.js +19 -4
- package/dist/clients/attach/session/input-wiring.js +2 -2
- package/dist/clients/attach/session/mouse.d.ts +4 -0
- package/dist/clients/attach/session/mouse.js +14 -3
- package/dist/clients/attach/session/selection.d.ts +28 -0
- package/dist/clients/attach/session/selection.js +108 -0
- 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 +5 -9
- package/dist/clients/attach/viewer.js +552 -550
- 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
package/dist/core/human/claim.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { hostname } from 'node:os';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { claimPath, deckPath,
|
|
4
|
+
import { claimPath, deckPath, responsePath, reviewPath } from './convention.js';
|
|
5
|
+
import { readJsonOrNull } from '../fs-utils.js';
|
|
5
6
|
import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
|
|
6
7
|
const REMOTE_STALE_MS = 30_000;
|
|
7
8
|
function parseClaim(raw) {
|
|
@@ -12,7 +13,7 @@ function parseClaim(raw) {
|
|
|
12
13
|
return null;
|
|
13
14
|
return { token: claim.token, host: claim.host, pid: claim.pid, claimedAt: claim.claimedAt, heartbeatAt: claim.heartbeatAt, ...(typeof claim.tmuxClient === 'string' ? { tmuxClient: claim.tmuxClient } : {}) };
|
|
14
15
|
}
|
|
15
|
-
export function readTicketClaim(dir) { return parseClaim(
|
|
16
|
+
export function readTicketClaim(dir) { return parseClaim(readJsonOrNull(claimPath(dir))); }
|
|
16
17
|
function isLiveLocalPid(pid) { try {
|
|
17
18
|
process.kill(pid, 0);
|
|
18
19
|
return true;
|
|
@@ -14,7 +14,6 @@ export declare function stampCanvasNode(deck: Deck): void;
|
|
|
14
14
|
export declare function atomicWriteJson(path: string, value: unknown): void;
|
|
15
15
|
/** Publish one immutable JSON record without replacing an existing winner. */
|
|
16
16
|
export declare function publishJsonExclusive(path: string, value: unknown): boolean;
|
|
17
|
-
export declare function readJson<T>(path: string): T | null;
|
|
18
17
|
export declare function writeResponse(dir: string, responses: InteractionResponse[], completedAt: string, deck?: Deck): string;
|
|
19
18
|
export declare function writeProgress(dir: string, responses: InteractionResponse[]): void;
|
|
20
19
|
export declare function clearProgress(dir: string): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, linkSync, mkdirSync,
|
|
1
|
+
import { existsSync, linkSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { buildSummary } from './summary.js';
|
|
4
4
|
export function deckPath(dir) { return `${dir}/deck.json`; }
|
|
@@ -53,14 +53,6 @@ export function publishJsonExclusive(path, value) {
|
|
|
53
53
|
unlinkSync(tmp);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
export function readJson(path) {
|
|
57
|
-
try {
|
|
58
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
56
|
export function writeResponse(dir, responses, completedAt, deck) {
|
|
65
57
|
const summary = deck === undefined ? '' : buildSummary(deck, responses);
|
|
66
58
|
atomicWriteJson(responsePath(dir), { schema: 'humanloop.response/v2', kind: 'deck', responses, summary, completedAt });
|
package/dist/core/human/scan.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
|
-
import { claimPath, deckPath, isResolved,
|
|
3
|
+
import { claimPath, deckPath, isResolved, reviewPath } from './convention.js';
|
|
4
|
+
import { readJsonOrNull } from '../fs-utils.js';
|
|
4
5
|
import { validateDeck, validateReviewDescriptor } from './deck-schema.js';
|
|
5
6
|
import { ticketsRoot } from './root.js';
|
|
6
7
|
function claimSummary(dir) {
|
|
7
|
-
const claim =
|
|
8
|
+
const claim = readJsonOrNull(claimPath(dir));
|
|
8
9
|
if (claim === null || typeof claim.host !== 'string' || typeof claim.claimedAt !== 'string' || typeof claim.heartbeatAt !== 'string')
|
|
9
10
|
return undefined;
|
|
10
11
|
return { owner: claim.host, claimedAt: claim.claimedAt, heartbeatAt: claim.heartbeatAt };
|
|
11
12
|
}
|
|
12
13
|
function deckSummary(dir, id) {
|
|
13
|
-
const raw =
|
|
14
|
+
const raw = readJsonOrNull(deckPath(dir));
|
|
14
15
|
if (raw === null)
|
|
15
16
|
return null;
|
|
16
17
|
let deck;
|
|
@@ -35,7 +36,7 @@ function deckSummary(dir, id) {
|
|
|
35
36
|
return { dir, id, kind: 'deck', title: deck.title, subtitle: first.subtitle, interactionKind: first.kind, interactionCount: deck.interactions.length, source: deck.source ?? {}, blockedSince, claim: claimSummary(dir) };
|
|
36
37
|
}
|
|
37
38
|
function reviewSummary(dir, id) {
|
|
38
|
-
const raw =
|
|
39
|
+
const raw = readJsonOrNull(reviewPath(dir));
|
|
39
40
|
if (raw === null)
|
|
40
41
|
return null;
|
|
41
42
|
let review;
|
|
@@ -27,7 +27,7 @@ export declare function listProfiles(): ProfileEntry[];
|
|
|
27
27
|
* actually-enumerated dirs, never a blind join), then a unique manifest
|
|
28
28
|
* `name` match. Ambiguous names fail listing every matching id; no match
|
|
29
29
|
* fails naming `profile list`/`profile new` as recovery. This is the ONLY
|
|
30
|
-
* function every command leaf and
|
|
30
|
+
* function every command leaf and runtime consumer should
|
|
31
31
|
* call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
|
|
32
32
|
* safe profile id. */
|
|
33
33
|
export declare function loadProfileManifest(profileIdOrName: string): ProfileEntry;
|
|
@@ -147,7 +147,7 @@ export function listProfiles() {
|
|
|
147
147
|
* actually-enumerated dirs, never a blind join), then a unique manifest
|
|
148
148
|
* `name` match. Ambiguous names fail listing every matching id; no match
|
|
149
149
|
* fails naming `profile list`/`profile new` as recovery. This is the ONLY
|
|
150
|
-
* function every command leaf and
|
|
150
|
+
* function every command leaf and runtime consumer should
|
|
151
151
|
* call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
|
|
152
152
|
* safe profile id. */
|
|
153
153
|
export function loadProfileManifest(profileIdOrName) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal review/comment/error vocabulary and constants.
|
|
3
|
-
*
|
|
3
|
+
* Backs the daemon-owned review lifecycle and comment authority.
|
|
4
4
|
* This module declares no HTTP status, DTOs, or I/O.
|
|
5
5
|
*/
|
|
6
6
|
import type { FeedbackComment } from '../human/types.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal review/comment/error vocabulary and constants.
|
|
3
|
-
*
|
|
3
|
+
* Backs the daemon-owned review lifecycle and comment authority.
|
|
4
4
|
* This module declares no HTTP status, DTOs, or I/O.
|
|
5
5
|
*/
|
|
6
6
|
/** Limits */
|
|
@@ -620,8 +620,8 @@ export function createFrameDispatchContext(deps) {
|
|
|
620
620
|
}
|
|
621
621
|
};
|
|
622
622
|
// -------------------------------------------------------------------------
|
|
623
|
-
// Command-op helpers
|
|
624
|
-
//
|
|
623
|
+
// Command-op helpers. The write guard is hoisted here and reused by all
|
|
624
|
+
// mutating ops; the ack/error replies
|
|
625
625
|
// and a few resolvers keep the per-op cases one-liners.
|
|
626
626
|
// -------------------------------------------------------------------------
|
|
627
627
|
/** Reject a read-only client for a mutating op. The decision reads this
|
|
@@ -841,7 +841,7 @@ export function createFrameDispatchContext(deps) {
|
|
|
841
841
|
};
|
|
842
842
|
const handleEngineControlFrame = (client, frame) => {
|
|
843
843
|
switch (frame.type) {
|
|
844
|
-
// --- extended engine-command ops
|
|
844
|
+
// --- extended engine-command ops -------------------------------------
|
|
845
845
|
case 'set_model': {
|
|
846
846
|
if (notWritable(client, 'set the model'))
|
|
847
847
|
break;
|
|
@@ -1070,7 +1070,7 @@ export function createFrameDispatchContext(deps) {
|
|
|
1070
1070
|
// engine, so it is not a controller-gated op. Terminal observer
|
|
1071
1071
|
// connections use it to populate the command palette.
|
|
1072
1072
|
// The merged command list rides in ack.detail as JSON (the foundation's
|
|
1073
|
-
// AckFrame.detail field) — the viewer
|
|
1073
|
+
// AckFrame.detail field) — the viewer JSON.parses it when for ===
|
|
1074
1074
|
// 'get_commands'. Keeps every command op a uniform ack reply.
|
|
1075
1075
|
try {
|
|
1076
1076
|
ackTo(client, 'get_commands', true, JSON.stringify(buildCommandList(currentSession())));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// broker-cli.ts — the headless broker's dedicated detached entry (plan T4 /
|
|
2
2
|
// decision §1.13). Spawned directly via `spawn(execPath, [thisFile, nodeId], …)`
|
|
3
|
-
// by HeadlessBrokerHost.launch
|
|
3
|
+
// by HeadlessBrokerHost.launch — NEVER routed through src/cli.ts, which
|
|
4
4
|
// would run the whole bootstrap chain (auto-update / scope-init / slash-template
|
|
5
5
|
// rewrite) on every broker boot. Mirrors src/daemon/crtrd-cli.ts: parse the one
|
|
6
6
|
// positional arg and hand off; keep this file a thin shim.
|
|
@@ -47,7 +47,7 @@ export interface BrokerSnapshot {
|
|
|
47
47
|
title: string | undefined;
|
|
48
48
|
};
|
|
49
49
|
/** Broker-owned summaries keyed by the first tool-call id in each group.
|
|
50
|
-
* Structured objects with bullets, nodesSpawned, and
|
|
50
|
+
* Structured objects with bullets, nodesSpawned, and filesDeleted.
|
|
51
51
|
* Optional so snapshots from an older live runtime remain readable. */
|
|
52
52
|
toolGroupSummaries?: Record<string, ToolGroupSummary>;
|
|
53
53
|
/** The broker-selected normal activity label for the current turn. Optional so
|
|
@@ -68,7 +68,7 @@ export interface HelloFrame {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
/** Drive the engine — writable (`controller`) clients only. Map 1:1 to session.prompt/steer/followUp/abort.
|
|
71
|
-
* `images` carries pasted/attached images to the engine
|
|
71
|
+
* `images` carries pasted/attached images to the engine: pi accepts
|
|
72
72
|
* `prompt(text,{images})` / `steer(text,images)` / `followUp(text,images)` at
|
|
73
73
|
* 0.78.1. The wire TYPE lives here; T3/T6 wire the runtime side. The BROKER read
|
|
74
74
|
* cap (`BROKER_READ_CAPS`) is sized to hold a resizeImage-bounded PNG's base64. */
|
|
@@ -612,15 +612,14 @@ export declare class FrameOverflowError extends Error {
|
|
|
612
612
|
}
|
|
613
613
|
/** Caps the CLIENT uses reading BROKER frames. The `welcome.snapshot` carries the
|
|
614
614
|
* full message history and can be many MiB, so these are generous. Covers
|
|
615
|
-
* realistic long sessions; snapshot CHUNKING is deferred
|
|
616
|
-
*
|
|
617
|
-
*
|
|
615
|
+
* realistic long sessions; snapshot CHUNKING is deferred — the long-lived
|
|
616
|
+
* broker makes a big welcome a realistic, not pathological, trigger, but the
|
|
617
|
+
* fixed cap is accepted. */
|
|
618
618
|
export declare const CLIENT_READ_CAPS: FrameDecoderCaps;
|
|
619
|
-
/** Caps the BROKER uses reading CLIENT frames.
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
* `follow_up` frame (M1). Still bounded, so a malicious/buggy client→broker frame
|
|
619
|
+
/** Caps the BROKER uses reading CLIENT frames. Sized so an image-paste frame
|
|
620
|
+
* fits: a resizeImage-bounded PNG's base64 is a few MiB and would clip a
|
|
621
|
+
* tighter 4 MiB line cap, so these hold an image-bearing `prompt`/`steer`/
|
|
622
|
+
* `follow_up` frame. Still bounded, so a malicious/buggy client→broker frame
|
|
624
623
|
* is cap-and-dropped, never grow-to-OOM (C5). */
|
|
625
624
|
export declare const BROKER_READ_CAPS: FrameDecoderCaps;
|
|
626
625
|
/** Incremental newline-delimited JSON reader: feed raw socket chunks, get back
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// broker-protocol.ts — the client↔broker wire protocol for the headless node
|
|
2
2
|
// Surface (design §5.2). Pure types + a newline-delimited JSON codec; no I/O, no
|
|
3
|
-
// SDK construction. Consumed by the broker (src/core/runtime/broker.ts)
|
|
4
|
-
//
|
|
3
|
+
// SDK construction. Consumed by the broker (src/core/runtime/broker.ts), the
|
|
4
|
+
// `crtr surface attach` terminal client, and crtrd's attach bridge.
|
|
5
5
|
//
|
|
6
6
|
// The transport is one unix socket per node (`nodeDir(id)/view.sock`) speaking
|
|
7
7
|
// newline-delimited JSON frames. Live engine events are relayed VERBATIM — the
|
|
@@ -43,18 +43,17 @@ export class FrameOverflowError extends Error {
|
|
|
43
43
|
}
|
|
44
44
|
/** Caps the CLIENT uses reading BROKER frames. The `welcome.snapshot` carries the
|
|
45
45
|
* full message history and can be many MiB, so these are generous. Covers
|
|
46
|
-
* realistic long sessions; snapshot CHUNKING is deferred
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* realistic long sessions; snapshot CHUNKING is deferred — the long-lived
|
|
47
|
+
* broker makes a big welcome a realistic, not pathological, trigger, but the
|
|
48
|
+
* fixed cap is accepted. */
|
|
49
49
|
export const CLIENT_READ_CAPS = {
|
|
50
50
|
maxLineBytes: 256 * 1024 * 1024,
|
|
51
51
|
maxTotalBytes: 256 * 1024 * 1024,
|
|
52
52
|
};
|
|
53
|
-
/** Caps the BROKER uses reading CLIENT frames.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* `follow_up` frame (M1). Still bounded, so a malicious/buggy client→broker frame
|
|
53
|
+
/** Caps the BROKER uses reading CLIENT frames. Sized so an image-paste frame
|
|
54
|
+
* fits: a resizeImage-bounded PNG's base64 is a few MiB and would clip a
|
|
55
|
+
* tighter 4 MiB line cap, so these hold an image-bearing `prompt`/`steer`/
|
|
56
|
+
* `follow_up` frame. Still bounded, so a malicious/buggy client→broker frame
|
|
58
57
|
* is cap-and-dropped, never grow-to-OOM (C5). */
|
|
59
58
|
export const BROKER_READ_CAPS = {
|
|
60
59
|
maxLineBytes: 24 * 1024 * 1024,
|
|
@@ -15,7 +15,7 @@ export interface BrokerEngine {
|
|
|
15
15
|
createAgentSessionServices: typeof createAgentSessionServices;
|
|
16
16
|
createAgentSessionFromServices: typeof createAgentSessionFromServices;
|
|
17
17
|
/**
|
|
18
|
-
* The session-replacement runtime factory (
|
|
18
|
+
* The session-replacement runtime factory (new_session/switch_session/fork).
|
|
19
19
|
* OPTIONAL: the real SDK (0.78.1) exposes it, so production gets full session
|
|
20
20
|
* replacement; the `fake-engine` test fixture does NOT provide it, so the
|
|
21
21
|
* broker degrades those three ops to an `error{engine_error}` reply rather than
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// broker.ts — the headless node broker (design §4, §5
|
|
1
|
+
// broker.ts — the headless node broker (design §4, §5).
|
|
2
2
|
//
|
|
3
3
|
// One broker process per node — the SOLE host. It hosts ONE pi engine IN-PROCESS
|
|
4
4
|
// via the SDK (createAgentSession), is the SOLE writer of the node's session
|
|
@@ -81,9 +81,9 @@ function formatExactModelSpec(model, thinkingLevel) {
|
|
|
81
81
|
return base === null ? null : `${base}:${thinkingLevel === undefined || thinkingLevel === '' ? 'off' : thinkingLevel}`;
|
|
82
82
|
}
|
|
83
83
|
// ---------------------------------------------------------------------------
|
|
84
|
-
// Tunables (
|
|
84
|
+
// Tunables (backpressure / dialog anti-deadlock)
|
|
85
85
|
// ---------------------------------------------------------------------------
|
|
86
|
-
/** Broker-side default dialog timeout (
|
|
86
|
+
/** Broker-side default dialog timeout (anti-deadlock). When an extension
|
|
87
87
|
* dialog is forwarded to every writable client, the broker ALWAYS arms a timeout
|
|
88
88
|
* (this default, or a shorter per-dialog `opts.timeout` if the extension passed
|
|
89
89
|
* one) so unanswered dialogs — including ones whose writable viewers all detach
|
|
@@ -167,7 +167,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
167
167
|
};
|
|
168
168
|
// Defensive: any `crtr` child the engine/extensions spawn must inherit the
|
|
169
169
|
// front-door recursion guard (see
|
|
170
|
-
// src/core/runtime/.crouter/memory/INDEX.md). The host
|
|
170
|
+
// src/core/runtime/.crouter/memory/INDEX.md). The host also sets this;
|
|
171
171
|
// setting it here keeps the broker self-sufficient.
|
|
172
172
|
process.env[FRONT_DOOR_ENV] = '1';
|
|
173
173
|
// A successful in-turn crtr command stamps this process id into its stop
|
|
@@ -203,7 +203,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
203
203
|
// 2–4. Build the engine session via the SERVICES path (C3) — see
|
|
204
204
|
// buildBrokerSession below. Register it so the FATAL exit path (M3) can
|
|
205
205
|
// dispose it and reap detached bash children.
|
|
206
|
-
// `session` + `services` are MUTABLE holders (
|
|
206
|
+
// `session` + `services` are MUTABLE holders (session-rebind): when the
|
|
207
207
|
// controller drives new_session/switch_session/fork, the AgentSessionRuntime
|
|
208
208
|
// tears down the old session and builds the next one, then runs our rebind
|
|
209
209
|
// callback which reassigns these. Every closure below (buildSnapshot,
|
|
@@ -411,7 +411,7 @@ export async function runBroker(nodeId, startupAt = process.hrtime.bigint(), sta
|
|
|
411
411
|
registry.forEachHelloed(sendWelcome);
|
|
412
412
|
};
|
|
413
413
|
// -------------------------------------------------------------------------
|
|
414
|
-
// The single exit helper
|
|
414
|
+
// The single exit helper: dispose the engine, close + unlink the
|
|
415
415
|
// socket, exit 0. Idempotent — every trigger converges here.
|
|
416
416
|
// -------------------------------------------------------------------------
|
|
417
417
|
const completeExit = (status) => {
|
|
@@ -127,7 +127,7 @@ const BROKER_SIGKILL_GRACE_MS = 2_000;
|
|
|
127
127
|
/** How often `pollUntilTreeDead` re-checks the process TREE. Short enough that
|
|
128
128
|
* the common/happy path (tree already dead) pays only a couple of ticks; the
|
|
129
129
|
* full grace ceilings above are reserved for the genuinely-wedged case,
|
|
130
|
-
* where the added latency is the whole point
|
|
130
|
+
* where the added latency is the whole point. */
|
|
131
131
|
const TEARDOWN_POLL_MS = 150;
|
|
132
132
|
/** Poll `isAlive` every `TEARDOWN_POLL_MS` until it reports dead. If it is
|
|
133
133
|
* STILL alive once `timeoutMs` elapses, fire `onTimeout` exactly once (the
|
|
@@ -136,7 +136,7 @@ const TEARDOWN_POLL_MS = 150;
|
|
|
136
136
|
* up (an unkillable/D-state descendant is out of scope for this primitive).
|
|
137
137
|
*
|
|
138
138
|
* Deliberately a recursive, NEVER-`.unref()`'d `setTimeout` rather than a
|
|
139
|
-
* fixed unref'd wait
|
|
139
|
+
* fixed unref'd wait: most teardown callers
|
|
140
140
|
* are short-lived CLI subprocesses (`node lifecycle close`, `canvas prune`)
|
|
141
141
|
* that would otherwise exit before an unref'd SIGKILL timer ever fires,
|
|
142
142
|
* silently skipping the last-resort rung. Polling (instead of a blind fixed
|
|
@@ -167,7 +167,7 @@ function pollUntil(isAlive, timeoutMs, onTimeout, hardCeilingMs = timeoutMs + 5_
|
|
|
167
167
|
* `captureTeardownSnapshot` identity map over that same list) — both taken
|
|
168
168
|
* ONCE by `teardown()` synchronously at its very top, before EITHER
|
|
169
169
|
* escalation path (connect-error or post-shutdown-frame) can run, and
|
|
170
|
-
* threaded through here unchanged
|
|
170
|
+
* threaded through here unchanged. This
|
|
171
171
|
* function must NEVER re-walk the process tree itself: `launch()` spawns the
|
|
172
172
|
* broker `detached: true`, so
|
|
173
173
|
* signaling the broker's OWN group (the first thing `killProcessTreePids`
|
|
@@ -180,8 +180,8 @@ function pollUntil(isAlive, timeoutMs, onTimeout, hardCeilingMs = timeoutMs + 5_
|
|
|
180
180
|
* "lose" a still-alive detached descendant — e.g. the pi bash tool's shell
|
|
181
181
|
* child, itself `detached: true` and thus its own group leader, distinct
|
|
182
182
|
* from the broker's group, so a plain `kill(-brokerPid)` never reaches it
|
|
183
|
-
* either
|
|
184
|
-
* PID REUSE across the multi-second escalation window
|
|
183
|
+
* either. `identities` guards each signal against
|
|
184
|
+
* PID REUSE across the multi-second escalation window —
|
|
185
185
|
* see `killProcessTreePids`. `null` means the initial `captureTeardownSnapshot`
|
|
186
186
|
* probe itself failed (no baseline to compare against) — `killProcessTreePids`
|
|
187
187
|
* falls through to its normal unguarded signal path in that case, never
|
|
@@ -312,19 +312,19 @@ export const headlessBrokerHost = {
|
|
|
312
312
|
// won't revive. On connect failure (broker dead/crashed/wedged) fall back
|
|
313
313
|
// to escalateBrokerTeardown — a tree-wide SIGTERM now, a tree-wide SIGKILL
|
|
314
314
|
// as the last resort — so no orphaned descendant of a dead/wedged broker
|
|
315
|
-
// survives the node
|
|
315
|
+
// survives the node — + unlink the stale socket.
|
|
316
316
|
//
|
|
317
317
|
// Capture the broker pid ONCE, synchronously, right here — never re-query
|
|
318
318
|
// `getNode(nodeId)` inside the async callbacks/timers below. Callers such
|
|
319
319
|
// as placement.ts's `reapIfEmpty()` call `teardown()` then immediately
|
|
320
320
|
// `deleteNode()` in the same tick; by the time an async callback fires the
|
|
321
321
|
// row can already be gone, so a re-query would silently resolve to
|
|
322
|
-
// `undefined` and skip escalation entirely
|
|
322
|
+
// `undefined` and skip escalation entirely.
|
|
323
323
|
const node = getNode(nodeId);
|
|
324
324
|
const pid = node?.pi_pid;
|
|
325
|
-
// Snapshot the full descendant TREE
|
|
325
|
+
// Snapshot the full descendant TREE — the
|
|
326
326
|
// root pid plus every transitive descendant — and a stable identity
|
|
327
|
-
// fingerprint for every pid in it
|
|
327
|
+
// fingerprint for every pid in it, BOTH from ONE `ps`
|
|
328
328
|
// table read (`captureTeardownSnapshot`, final review: closes the small
|
|
329
329
|
// race between two separate probes), exactly ONCE, right here, while the
|
|
330
330
|
// broker (if alive) still holds parentage over any detached descendant.
|
|
@@ -393,10 +393,10 @@ export const headlessBrokerHost = {
|
|
|
393
393
|
// Bounded exit confirmation: a broker that connects fine but then HANGS
|
|
394
394
|
// inside session.dispose() (disposeAndExit catches a throw, not a hang)
|
|
395
395
|
// would leak the process holding the sole .jsonl writer — and any
|
|
396
|
-
// bash→test→app descendants it spawned along the way
|
|
396
|
+
// bash→test→app descendants it spawned along the way. Poll
|
|
397
397
|
// the whole process TREE (not just the broker's own pid) for up to
|
|
398
|
-
// `BROKER_SHUTDOWN_GRACE_MS`, UNCONDITIONAL on the broker's own liveness
|
|
399
|
-
//
|
|
398
|
+
// `BROKER_SHUTDOWN_GRACE_MS`, UNCONDITIONAL on the broker's own liveness:
|
|
399
|
+
// a broker that exits cleanly can still
|
|
400
400
|
// leave a live descendant behind (dispose() partially failed, or never
|
|
401
401
|
// reached a detached SDK child), which is exactly the shape this
|
|
402
402
|
// closes. `pollUntil` is ref'd — not `.unref()`'d — so a short-lived
|
|
@@ -405,8 +405,8 @@ export const headlessBrokerHost = {
|
|
|
405
405
|
// couple of poll ticks.
|
|
406
406
|
//
|
|
407
407
|
// Checks `isAnyPidAlive(tree)` against the FIXED pre-signal snapshot,
|
|
408
|
-
// NEVER a fresh `isProcessTreeAlive`/`descendantPids` re-walk
|
|
409
|
-
//
|
|
408
|
+
// NEVER a fresh `isProcessTreeAlive`/`descendantPids` re-walk: the broker
|
|
409
|
+
// can exit at any point during this
|
|
410
410
|
// grace window while leaving a detached descendant alive, and the
|
|
411
411
|
// instant it does, the kernel reparents that descendant away from `pid`
|
|
412
412
|
// — a re-walk-by-ppid would then find nothing and silently skip
|
|
@@ -56,7 +56,7 @@ export declare function nextLadderModel(currentSpec: string, direction?: 1 | -1,
|
|
|
56
56
|
* 3. Unset → pi default.
|
|
57
57
|
* Callers pass the authoritative lifecycle + hasManager (`parent !== null`) so
|
|
58
58
|
* a polymorph/flip rebuilds the recipe faithfully; `lifecycle` is returned
|
|
59
|
-
* as-given
|
|
59
|
+
* as-given, never derived from persona frontmatter. The two canvas
|
|
60
60
|
* extensions are always first; kind-declared extensions follow. */
|
|
61
61
|
export declare function buildLaunchSpec(kind: string, mode: Mode, opts: {
|
|
62
62
|
lifecycle: Lifecycle;
|
|
@@ -249,7 +249,7 @@ export function nextLadderModel(currentSpec, direction = 1, providers) {
|
|
|
249
249
|
* 3. Unset → pi default.
|
|
250
250
|
* Callers pass the authoritative lifecycle + hasManager (`parent !== null`) so
|
|
251
251
|
* a polymorph/flip rebuilds the recipe faithfully; `lifecycle` is returned
|
|
252
|
-
* as-given
|
|
252
|
+
* as-given, never derived from persona frontmatter. The two canvas
|
|
253
253
|
* extensions are always first; kind-declared extensions follow. */
|
|
254
254
|
export function buildLaunchSpec(kind, mode, opts) {
|
|
255
255
|
const merged = readMergedLaunchConfig(opts.cwd ?? process.cwd(), opts.profileId !== undefined ? opts.profileId : (process.env['CRTR_PROFILE_ID'] || null));
|
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
// lifecycle.ts — the node status×intent state machine.
|
|
2
2
|
//
|
|
3
3
|
// ONE place defines which (status, intent) moves are legal and enacts them.
|
|
4
|
-
//
|
|
5
|
-
// reset
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// in ONE atomic statement (built on Phase 2's WAL'd row setters) so the two can
|
|
10
|
-
// never disagree.
|
|
4
|
+
// The legal transition TABLE is the definition, and `transition(id, event)` is
|
|
5
|
+
// the single writer of status+intent for every caller (reset, close, revive,
|
|
6
|
+
// feed, daemon, stophook, queue, promote): it validates the from-status, then
|
|
7
|
+
// writes both fields in ONE atomic statement (on the WAL'd row setters) so the
|
|
8
|
+
// two can never disagree.
|
|
11
9
|
//
|
|
12
10
|
// This mirrors persona.ts: persona.ts is the single source of transition PROSE;
|
|
13
11
|
// lifecycle.ts is the single source of which status/intent move is LEGAL. Two
|
|
14
12
|
// parallel, legible state machines instead of scattered enactment.
|
|
15
13
|
//
|
|
16
|
-
// Crash-safety invariant
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// now the DEFINITION of the `cancel` event: callers flip via transition()
|
|
22
|
-
// and only THEN tear the host down.
|
|
14
|
+
// Crash-safety invariant: flip status to a non-supervised value + clear intent
|
|
15
|
+
// BEFORE killing the host (tmux pane or headless broker) — the daemon only ever
|
|
16
|
+
// revives active|idle nodes, so a teardown must leave the node done/canceled
|
|
17
|
+
// first to close the revive race. That invariant is the DEFINITION of the
|
|
18
|
+
// `cancel` event: callers flip via transition() and only THEN tear the host down.
|
|
23
19
|
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// is
|
|
29
|
-
//
|
|
30
|
-
// it was COLLAPSED into `cancel`; reset.ts's reapDescendants routes through
|
|
20
|
+
// An externally-reaped node — torn down because the user moved on (close
|
|
21
|
+
// cascade) OR because a root relaunch superseded its workers — ends `canceled`,
|
|
22
|
+
// NOT `done`. `done` is reserved for a node that finished its OWN work (finish)
|
|
23
|
+
// — including a relaunched root, which is parked `done` as history, not
|
|
24
|
+
// canceled. There is no separate reap event: it would be identical to `cancel`
|
|
25
|
+
// in every field and side effect, so reset.ts's reapDescendants routes through
|
|
31
26
|
// `cancel`.
|
|
32
27
|
//
|
|
33
28
|
// Layering note: lifecycle.ts is runtime, but it is the canvas write surface's
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// These are canvas/runtime reads (getNode + SessionManager + session-cycles):
|
|
5
5
|
// they belong SERVER-SIDE. crtrd's node read handlers (daemon/api/handlers/
|
|
6
6
|
// nodes.ts) call them behind `GET /v1/nodes/{id}/{snapshot,transcript}`. They
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// so the CLI leaves' import graph reaches no canvas state store (spec §1/§10).
|
|
7
|
+
// live here rather than in the CLI leaves so the leaves' import graph reaches
|
|
8
|
+
// no canvas state store (spec §1/§10).
|
|
10
9
|
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
11
10
|
import { readJsonIfExists } from '../fs-utils.js';
|
|
12
11
|
import { tmpdir } from 'node:os';
|
|
@@ -14,6 +14,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import { join, resolve } from 'node:path';
|
|
16
16
|
import { isRendererReady } from '../termrender/termrender.js';
|
|
17
|
+
import { expandTilde } from '../fs-utils.js';
|
|
17
18
|
/** A non-fatal, action-oriented diagnostic for an unprovisioned managed
|
|
18
19
|
* renderer — the one remaining out-of-tree dependency. Deliberately calls
|
|
19
20
|
* `isRendererReady()`, NOT `ensureRenderer()`: this runs on the front-door boot
|
|
@@ -25,14 +26,6 @@ export function rendererWarning() {
|
|
|
25
26
|
return 'crtr: the pinned termrender renderer is not provisioned — decks, reviews, and inline diagrams render as plaintext.\n' +
|
|
26
27
|
' Fix: run `crtr sys setup` (requires `uv`: curl -LsSf https://astral.sh/uv/install.sh | sh).';
|
|
27
28
|
}
|
|
28
|
-
/** Expand a leading `~` against `homeDir` (pi's own convention). */
|
|
29
|
-
function expandTilde(pathValue, homeDir) {
|
|
30
|
-
if (pathValue === '~')
|
|
31
|
-
return homeDir;
|
|
32
|
-
if (pathValue.startsWith('~/'))
|
|
33
|
-
return join(homeDir, pathValue.slice(2));
|
|
34
|
-
return pathValue;
|
|
35
|
-
}
|
|
36
29
|
/**
|
|
37
30
|
* The pi agent config dir (`~/.pi/agent`, or `$PI_CODING_AGENT_DIR`) — a local
|
|
38
31
|
* reimplementation of pi's `getAgentDir()`. Reimplemented (not imported) ON
|
|
@@ -30,7 +30,7 @@ import { orchestratorContextNote } from './bearings.js';
|
|
|
30
30
|
/** Load a builtin/user/project memory doc's body by its normalized name, or ''
|
|
31
31
|
* if it can't be resolved. The single source for the lifecycle/spine/
|
|
32
32
|
* orchestration-kernel fragments this injector re-delivers on a transition —
|
|
33
|
-
* the SAME docs
|
|
33
|
+
* the SAME docs under `src/builtin-memory/`, so static
|
|
34
34
|
* (system-prompt splice) and transition prose can never drift. */
|
|
35
35
|
function loadMemoryBody(name) {
|
|
36
36
|
try {
|
|
@@ -48,7 +48,7 @@ function loadMemoryBody(name) {
|
|
|
48
48
|
* resource is its own context window. Roadmap-shaping guidance is just another orchestrator-gated memory
|
|
49
49
|
* doc the node already gets via the boot-render splice (kinds/<kind>/orchestrator
|
|
50
50
|
* gated `{kind, mode: orchestrator}`). (Lifecycle is left to its own section —
|
|
51
|
-
* promotion
|
|
51
|
+
* promotion does not force resident, so this never asserts residency.) */
|
|
52
52
|
function orchestrationGuidance(nodeId, kind) {
|
|
53
53
|
const kernel = loadMemoryBody('orchestration-kernel');
|
|
54
54
|
const roadmap = readRoadmap(nodeId) ?? '(no roadmap yet)';
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* heavy pi SDK index on crtr's front-door hot path (the reason broker-sdk.ts
|
|
5
5
|
* dynamic-imports the engine). Mirrors pi: `PI_CODING_AGENT_DIR` env, else
|
|
6
6
|
* `~/.pi/agent` (APP_NAME defaults to 'pi'). Re-sync on a pi SDK bump that moves
|
|
7
|
-
* the sessions dir — same vendoring rationale as the slash-command list below.
|
|
7
|
+
* the sessions dir — same vendoring rationale as the slash-command list below.
|
|
8
|
+
* `PI_CODING_AGENT_DIR` goes through the same tilde expansion pi's
|
|
9
|
+
* `getAgentDir()` applies, or `~/custom-pi` yields a broken relative path. */
|
|
8
10
|
export declare function piSessionsRoot(): string;
|
|
9
11
|
/**
|
|
10
12
|
* pi's builtin slash commands — vendored verbatim from pi `core/slash-commands.js`
|
|
@@ -13,7 +15,7 @@ export declare function piSessionsRoot(): string;
|
|
|
13
15
|
* Builtins are NOT engine-interpreted (`session.prompt('/model')` ships `/model`
|
|
14
16
|
* to the LLM as literal text), so the broker's `get_commands` op MERGES this list
|
|
15
17
|
* with the engine's registered commands/templates/skills, and the viewer
|
|
16
|
-
* autocomplete parses these locally. Both the broker
|
|
18
|
+
* autocomplete parses these locally. Both the broker and the viewer
|
|
17
19
|
* import this single copy.
|
|
18
20
|
*
|
|
19
21
|
* Upstream interpolates `${APP_NAME}` into the `quit` description; `APP_NAME`
|
|
@@ -12,32 +12,21 @@
|
|
|
12
12
|
// below to match and adjust the count note.
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
|
+
import { expandTilde } from '../fs-utils.js';
|
|
15
16
|
/** pi's sessions root, VENDORED from pi `config.getSessionsDir()` (= `<agentDir>/
|
|
16
17
|
* sessions`). pi's package `exports` map is `.`-only, so config.js can't be
|
|
17
18
|
* deep-imported, and a ROOT import of `getAgentDir` would eager-load the entire
|
|
18
19
|
* heavy pi SDK index on crtr's front-door hot path (the reason broker-sdk.ts
|
|
19
20
|
* dynamic-imports the engine). Mirrors pi: `PI_CODING_AGENT_DIR` env, else
|
|
20
21
|
* `~/.pi/agent` (APP_NAME defaults to 'pi'). Re-sync on a pi SDK bump that moves
|
|
21
|
-
* the sessions dir — same vendoring rationale as the slash-command list below.
|
|
22
|
+
* the sessions dir — same vendoring rationale as the slash-command list below.
|
|
23
|
+
* `PI_CODING_AGENT_DIR` goes through the same tilde expansion pi's
|
|
24
|
+
* `getAgentDir()` applies, or `~/custom-pi` yields a broken relative path. */
|
|
22
25
|
export function piSessionsRoot() {
|
|
23
26
|
const env = process.env['PI_CODING_AGENT_DIR'];
|
|
24
27
|
const agentDir = env !== undefined && env !== '' ? expandTilde(env) : join(homedir(), '.pi', 'agent');
|
|
25
28
|
return join(agentDir, 'sessions');
|
|
26
29
|
}
|
|
27
|
-
/** Mirror pi's `expandTildePath` (= `normalizePath` with the default expandTilde
|
|
28
|
-
* on): a bare `~` is the home dir, and a leading `~/` (or `~\` on win32) joins
|
|
29
|
-
* the remainder under home. pi's `getAgentDir()` runs `PI_CODING_AGENT_DIR`
|
|
30
|
-
* through this, so the vendored root must too or `~/custom-pi` yields a broken
|
|
31
|
-
* relative path. Other forms (absolute, relative, `~user`) pass through as pi
|
|
32
|
-
* leaves them. */
|
|
33
|
-
function expandTilde(p) {
|
|
34
|
-
if (p === '~')
|
|
35
|
-
return homedir();
|
|
36
|
-
if (p.startsWith('~/') || (process.platform === 'win32' && p.startsWith('~\\'))) {
|
|
37
|
-
return join(homedir(), p.slice(2));
|
|
38
|
-
}
|
|
39
|
-
return p;
|
|
40
|
-
}
|
|
41
30
|
/**
|
|
42
31
|
* pi's builtin slash commands — vendored verbatim from pi `core/slash-commands.js`
|
|
43
32
|
* `BUILTIN_SLASH_COMMANDS` (review C1). **21 entries at 0.78.1** (review n1 — NOT 23).
|
|
@@ -45,7 +34,7 @@ function expandTilde(p) {
|
|
|
45
34
|
* Builtins are NOT engine-interpreted (`session.prompt('/model')` ships `/model`
|
|
46
35
|
* to the LLM as literal text), so the broker's `get_commands` op MERGES this list
|
|
47
36
|
* with the engine's registered commands/templates/skills, and the viewer
|
|
48
|
-
* autocomplete parses these locally. Both the broker
|
|
37
|
+
* autocomplete parses these locally. Both the broker and the viewer
|
|
49
38
|
* import this single copy.
|
|
50
39
|
*
|
|
51
40
|
* Upstream interpolates `${APP_NAME}` into the `quit` description; `APP_NAME`
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// orchestrator), and seeds a roadmap scaffold.
|
|
8
8
|
// The transition guidance the node needs is injected CENTRALLY by the
|
|
9
9
|
// persona injector (runtime/persona.ts) at the turn boundary — promote()
|
|
10
|
-
// itself no
|
|
10
|
+
// itself returns no guidance and emits none by hand.
|
|
11
11
|
// 2. Refresh → persona swap (permanent). On the next fresh revive the node
|
|
12
12
|
// starts with the orchestrator system prompt baked in (because the launch
|
|
13
13
|
// spec now says orchestrator). The injected guidance bridges until then.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The re-task/reopen gate
|
|
1
|
+
// The re-task/reopen gate — enforced at every doorway that can revive a
|
|
2
2
|
// node: `node message send` immediate delivery, `node wait deadline`, and
|
|
3
3
|
// `node lifecycle revive`.
|
|
4
4
|
//
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// so the node's next real `push final` re-latches through the normal
|
|
13
13
|
// `WHERE final_report IS NULL` guard.
|
|
14
14
|
//
|
|
15
|
-
// Concurrency
|
|
15
|
+
// Concurrency: every check here reads `final_report` FRESH from
|
|
16
16
|
// the DB — never a caller-cached snapshot from an earlier point in the
|
|
17
17
|
// command — and the actual clear is a compare-and-swap (`UPDATE ... WHERE
|
|
18
18
|
// final_report = <the exact value just observed>`), mirroring the CAS
|