@north-light/crouter 0.3.175 → 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/nodes.d.ts +7 -1
- package/dist/api/dto/profiles.d.ts +3 -0
- 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/crtr-output.d.ts +3 -3
- package/dist/clients/attach/render/crtr-output.js +25 -13
- 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 +536 -566
- 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/create.js +7 -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/profile/list.js +3 -1
- package/dist/commands/profile/new.js +4 -1
- package/dist/commands/profile/project.js +23 -9
- package/dist/commands/profile/show.js +3 -1
- package/dist/commands/profile.js +2 -2
- 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/logs.js +4 -2
- 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__/branded-host-concurrency.test.js +1 -1
- 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/__tests__/on-read-crouter-home-fence.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.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/extensions.d.ts +1 -1
- package/dist/core/canvas/extensions.js +7 -1
- 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 +14 -8
- package/dist/core/command.js +3 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +6 -29
- package/dist/core/events/read.js +1 -3
- package/dist/core/exclusive-lock.d.ts +14 -0
- package/dist/core/exclusive-lock.js +124 -0
- 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 +4 -2
- package/dist/core/human/convention.d.ts +0 -11
- package/dist/core/human/convention.js +1 -131
- package/dist/core/human/scan.js +5 -4
- package/dist/core/io.d.ts +23 -0
- package/dist/core/io.js +60 -0
- package/dist/core/preview-registry.d.ts +10 -5
- package/dist/core/preview-registry.js +136 -2537
- package/dist/core/profiles/manifest.d.ts +13 -2
- package/dist/core/profiles/manifest.js +58 -49
- package/dist/core/review/store.d.ts +4 -1
- package/dist/core/review/store.js +10 -0
- package/dist/core/review/types.d.ts +1 -1
- package/dist/core/review/types.js +1 -1
- package/dist/core/runtime/branded-host.js +6 -84
- package/dist/core/runtime/broker/frame-dispatch.js +4 -4
- package/dist/core/runtime/broker-cli.js +1 -1
- package/dist/core/runtime/broker-extension-render.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/canvas-extensions.d.ts +1 -0
- package/dist/core/runtime/canvas-extensions.js +2 -0
- 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-bindings.js +1 -1
- 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.d.ts +9 -8
- package/dist/core/runtime/warm-pool.js +58 -62
- 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/index.d.ts +2 -1
- package/dist/core/substrate/index.js +2 -1
- package/dist/core/substrate/on-read-node.d.ts +7 -0
- package/dist/core/substrate/on-read-node.js +37 -0
- package/dist/core/substrate/on-read.d.ts +7 -5
- package/dist/core/substrate/on-read.js +16 -32
- package/dist/core/termrender/termrender.js +16 -96
- package/dist/core/wake.js +2 -2
- package/dist/core/worktree.d.ts +3 -3
- package/dist/core/worktree.js +16 -107
- 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 +33 -10
- package/dist/daemon/api/handlers/reports.js +1 -1
- package/dist/daemon/api/handlers/review-comments.js +5 -44
- package/dist/daemon/api/handlers/reviews.js +5 -53
- package/dist/daemon/api/handlers/validate.d.ts +23 -0
- package/dist/daemon/api/handlers/validate.js +58 -0
- package/dist/daemon/api/map.js +1 -0
- 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/daemon/review/finish.js +1 -10
- package/dist/pi-extensions/canvas-context-intro.js +1 -14
- package/dist/pi-extensions/canvas-doc-substrate.js +2 -2
- package/dist/pi-extensions/canvas-preview-result.d.ts +22 -0
- package/dist/pi-extensions/canvas-preview-result.js +60 -0
- 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 +8 -5
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/core/substrate/on-read-pure.d.ts +0 -7
- package/dist/core/substrate/on-read-pure.js +0 -272
package/dist/daemon/manage.d.ts
CHANGED
|
@@ -78,12 +78,12 @@ export declare function waitForDaemonExit(pid: number, timeoutMs?: number, deps?
|
|
|
78
78
|
/** Stop the RECORDED daemon: SIGTERM, and if it does not exit within the wait
|
|
79
79
|
* window, escalate to SIGKILL exactly as `sweepStrayDaemons` does for a stray.
|
|
80
80
|
*
|
|
81
|
-
* The escalation is the point.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* unstoppable from the CLI: every retry re-
|
|
86
|
-
* ignoring, and `start`
|
|
81
|
+
* The escalation is the point. Without it — SIGTERM, wait, throw on timeout,
|
|
82
|
+
* never reaching the stray sweep — the one daemon `stop` is actually aimed at
|
|
83
|
+
* would be the only crtrd process exempt from the SIGKILL backstop, and a
|
|
84
|
+
* daemon whose shutdown hangs (or outruns the window) becomes permanently
|
|
85
|
+
* unstoppable from the CLI: every retry re-sends a SIGTERM it is already
|
|
86
|
+
* ignoring, and `start` keeps refusing as "already running".
|
|
87
87
|
*
|
|
88
88
|
* A SIGKILL'd daemon runs no exit handler, so it leaves its pidfile behind
|
|
89
89
|
* pointing at a dead pid. That is a stale record, not an owner: `readPidfile`
|
package/dist/daemon/manage.js
CHANGED
|
@@ -193,15 +193,14 @@ export function sweepStrayDaemons(keepPid) {
|
|
|
193
193
|
// Daemon lifecycle transitions are bounded so callers never claim a state
|
|
194
194
|
// change before the pidfile and process liveness agree.
|
|
195
195
|
//
|
|
196
|
-
//
|
|
197
|
-
// generation, possible pending db migration) can take several seconds to
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
// code) still fails immediately regardless of this window.
|
|
196
|
+
// A cold boot right after `install-runtime` (empty module cache, fresh
|
|
197
|
+
// generation, possible pending db migration) can take several seconds to reach
|
|
198
|
+
// writePidfile(), so the window is wide enough to tolerate that slow-but-valid
|
|
199
|
+
// cold start rather than report a false "did not become ready"; a process exit
|
|
200
|
+
// (signal/non-zero code) still fails immediately regardless of this window.
|
|
202
201
|
// Exported so `commands/api-client.ts` can align the CLI-side `CrtrClient`'s
|
|
203
202
|
// cold-start `/healthz` poll window with this authoritative valid-startup bound
|
|
204
|
-
//
|
|
203
|
+
// instead of the two windows drifting independently.
|
|
205
204
|
export const DAEMON_VERIFY_WINDOW_MS = 20_000;
|
|
206
205
|
const DAEMON_VERIFY_POLL_MS = 10;
|
|
207
206
|
function sleepMs(ms) {
|
|
@@ -272,12 +271,12 @@ const DAEMON_KILL_WINDOW_MS = 2_000;
|
|
|
272
271
|
/** Stop the RECORDED daemon: SIGTERM, and if it does not exit within the wait
|
|
273
272
|
* window, escalate to SIGKILL exactly as `sweepStrayDaemons` does for a stray.
|
|
274
273
|
*
|
|
275
|
-
* The escalation is the point.
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* unstoppable from the CLI: every retry re-
|
|
280
|
-
* ignoring, and `start`
|
|
274
|
+
* The escalation is the point. Without it — SIGTERM, wait, throw on timeout,
|
|
275
|
+
* never reaching the stray sweep — the one daemon `stop` is actually aimed at
|
|
276
|
+
* would be the only crtrd process exempt from the SIGKILL backstop, and a
|
|
277
|
+
* daemon whose shutdown hangs (or outruns the window) becomes permanently
|
|
278
|
+
* unstoppable from the CLI: every retry re-sends a SIGTERM it is already
|
|
279
|
+
* ignoring, and `start` keeps refusing as "already running".
|
|
281
280
|
*
|
|
282
281
|
* A SIGKILL'd daemon runs no exit handler, so it leaves its pidfile behind
|
|
283
282
|
* pointing at a dead pid. That is a stale record, not an owner: `readPidfile`
|
|
@@ -332,8 +331,8 @@ export async function spawnDaemon() {
|
|
|
332
331
|
// output the daemon doesn't control, plus the narrow set of authorized raw
|
|
333
332
|
// `[crtrd] …` bootstrap/fatal lines that by construction predate or escape
|
|
334
333
|
// canonical event emission (the pre-bind losing-claim line and a cleanup-
|
|
335
|
-
// fatal write in crtrd.ts)
|
|
336
|
-
// started daemon, the common case (only a launchd-owned daemon
|
|
334
|
+
// fatal write in crtrd.ts). Without this fd all of it is discarded for any
|
|
335
|
+
// manually-started daemon, the common case (only a launchd-owned daemon has a
|
|
337
336
|
// logging plist). Every other daemon diagnostic — including per-node
|
|
338
337
|
// supervise/relaunch failures — is a canonical `emitEvent` write, not raw
|
|
339
338
|
// stderr; this file is not where those live. Mirrors host.ts's broker.log
|
|
@@ -344,7 +343,7 @@ export async function spawnDaemon() {
|
|
|
344
343
|
bindDaemonEventSource();
|
|
345
344
|
// `--canvas-home` declares this daemon's canvas ownership in argv so
|
|
346
345
|
// findDaemonPids()/sweepStrayDaemons (above) can match its own daemon
|
|
347
|
-
// exactly instead of scanning the whole machine
|
|
346
|
+
// exactly instead of scanning the whole machine. crtrd-cli.ts's argv
|
|
348
347
|
// parsing only looks for `--tcp`, so this extra pair of tokens is inert
|
|
349
348
|
// there — it exists purely for `ps` visibility.
|
|
350
349
|
const child = spawn(hostExecPath(), [...launchArgs, '--canvas-home', crtrHome()], {
|
|
@@ -8,7 +8,7 @@ import { reviewResultPath } from '../../core/canvas/paths.js';
|
|
|
8
8
|
import { snapshotCommentsLocked } from '../../core/review/comments.js';
|
|
9
9
|
import { readReviewSource } from '../../core/review/document.js';
|
|
10
10
|
import { signalReviewActivity } from '../../core/review/signal.js';
|
|
11
|
-
import { approveReviewLocked, cancelReviewRow,
|
|
11
|
+
import { approveReviewLocked, cancelReviewRow, markDelivered, markDeliveryError, markProjected, markProjectionError, requireVisibleReview, } from '../../core/review/store.js';
|
|
12
12
|
import { ReviewOperationError } from '../../core/review/types.js';
|
|
13
13
|
import { transition } from '../../core/runtime/lifecycle.js';
|
|
14
14
|
import { headlessBrokerHost } from '../../core/runtime/host.js';
|
|
@@ -22,15 +22,6 @@ function now() {
|
|
|
22
22
|
function errorText(error) {
|
|
23
23
|
return error instanceof Error ? error.message : String(error);
|
|
24
24
|
}
|
|
25
|
-
function unknownReview(reviewId) {
|
|
26
|
-
throw new ReviewOperationError('unknown_review', 'review was not found', { received: reviewId });
|
|
27
|
-
}
|
|
28
|
-
function requireVisibleReview(reviewId) {
|
|
29
|
-
const review = getReview(reviewId);
|
|
30
|
-
if (review === null || review.state === 'binding')
|
|
31
|
-
return unknownReview(reviewId);
|
|
32
|
-
return review;
|
|
33
|
-
}
|
|
34
25
|
function terminalConflict(review, requested) {
|
|
35
26
|
throw new ReviewOperationError('review_terminal', 'review already has a terminal outcome', {
|
|
36
27
|
state: review.state,
|
|
@@ -30,6 +30,7 @@ import { CONTEXT_INTRO_CUSTOM_TYPE } from '../shared/generated-context.js';
|
|
|
30
30
|
import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
|
|
31
31
|
import { buildContextBearingsFromState, buildForkBearingsFromState, buildProjectContextBlockForBroker } from '../core/runtime/broker-extension-render.js';
|
|
32
32
|
import { situationalContextBlock } from '../core/runtime/situational-context.js';
|
|
33
|
+
import { truncateToWidth } from './truncate.js';
|
|
33
34
|
/** The `customType` stamped on the injected session message. Used both to write
|
|
34
35
|
* the entry and to detect it on resume (the idempotency guard). Defined beside
|
|
35
36
|
* the block builder in `core/runtime/bearings.ts` because the warm-pool claim's
|
|
@@ -91,20 +92,6 @@ function wrapLine(line, width) {
|
|
|
91
92
|
out.push(chars.slice(i, i + width).join(''));
|
|
92
93
|
return out;
|
|
93
94
|
}
|
|
94
|
-
/** Truncate plain text to at most `width` columns, appending an ellipsis when it
|
|
95
|
-
* would overflow. Content here is ANSI-free plain text (label + prose), so a
|
|
96
|
-
* code-point count stands in for visible width. The renderer MUST keep every
|
|
97
|
-
* emitted line within the terminal width or pi's TUI aborts the whole render. */
|
|
98
|
-
function truncateToWidth(text, width) {
|
|
99
|
-
if (width <= 0)
|
|
100
|
-
return '';
|
|
101
|
-
const chars = Array.from(text);
|
|
102
|
-
if (chars.length <= width)
|
|
103
|
-
return text;
|
|
104
|
-
if (width === 1)
|
|
105
|
-
return '…';
|
|
106
|
-
return chars.slice(0, width - 1).join('') + '…';
|
|
107
|
-
}
|
|
108
95
|
/**
|
|
109
96
|
* Renderer for `crtr-context` messages. Collapsed (default) shows a one-line
|
|
110
97
|
* stub; expanded (Ctrl+O) shows the label + full body. Returns a plain object
|
|
@@ -34,7 +34,7 @@ import { homedir } from 'node:os';
|
|
|
34
34
|
import { join, resolve } from 'node:path';
|
|
35
35
|
import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
|
|
36
36
|
import { renderPreferencesFromState } from '../core/runtime/broker-extension-render.js';
|
|
37
|
-
import { renderOnReadDocsForSubject } from '../core/substrate/on-read
|
|
37
|
+
import { renderOnReadDocsForSubject } from '../core/substrate/on-read.js';
|
|
38
38
|
import { clearSessionCache } from '../core/substrate/session-cache.js';
|
|
39
39
|
import { loadInjectedDocs, saveInjectedDocs } from '../core/substrate/injected-store.js';
|
|
40
40
|
import { autoLoadedContextInner, mergeAutoLoadedContext } from './envelope-merge.js';
|
|
@@ -76,7 +76,7 @@ export function registerCanvasDocSubstrate(pi) {
|
|
|
76
76
|
// (memory-usage guidance + preferences). The guidance frame is always
|
|
77
77
|
// present for a canvas node (the memory system always exists), so the block
|
|
78
78
|
// is never empty even when no preference is eligible. (The knowledge catalog
|
|
79
|
-
//
|
|
79
|
+
// does not enter the system prompt — it rides the bearings message via
|
|
80
80
|
// renderKnowledgeBlock.)
|
|
81
81
|
pi.on('before_agent_start', async (event) => {
|
|
82
82
|
try {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface ToolCallEvent {
|
|
2
|
+
toolName: string;
|
|
3
|
+
toolCallId: string;
|
|
4
|
+
input: {
|
|
5
|
+
command?: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
interface ToolResultEvent {
|
|
9
|
+
toolName: string;
|
|
10
|
+
toolCallId: string;
|
|
11
|
+
details?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
interface PiLike {
|
|
14
|
+
on(event: 'tool_call', handler: (event: ToolCallEvent) => void): void;
|
|
15
|
+
on(event: 'tool_result', handler: (event: ToolResultEvent) => {
|
|
16
|
+
details: Record<string, unknown>;
|
|
17
|
+
} | void): void;
|
|
18
|
+
}
|
|
19
|
+
/** Loaded into every canvas broker. Plain Pi sessions have no node id and
|
|
20
|
+
* deliberately receive no filesystem transport. */
|
|
21
|
+
export declare function registerCanvasPreviewResult(pi: PiLike): void;
|
|
22
|
+
export default registerCanvasPreviewResult;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// canvas-preview-result.ts — private structured result transport for crtr bash calls.
|
|
2
|
+
//
|
|
3
|
+
// A crtr leaf returns a record, but the CLI normally renders that record to
|
|
4
|
+
// stdout for the agent. stdout is model context, so this hook assigns every
|
|
5
|
+
// bash call a private file, lets crtr atomically mirror its record there, then
|
|
6
|
+
// attaches the record to Pi's ToolResult.details. The attach viewer can render
|
|
7
|
+
// a structured preview without parsing the agent-facing text.
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
function isPreviewRecord(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object' || typeof value['path'] !== 'string')
|
|
13
|
+
return false;
|
|
14
|
+
const record = value;
|
|
15
|
+
return (record.result !== undefined && record.result !== null && typeof record.result === 'object' && !Array.isArray(record.result))
|
|
16
|
+
|| (Array.isArray(record.jsonl) && record.jsonl.every((item) => item !== null && typeof item === 'object' && !Array.isArray(item)))
|
|
17
|
+
|| (record.error !== undefined && record.error !== null && typeof record.error === 'object' && !Array.isArray(record.error));
|
|
18
|
+
}
|
|
19
|
+
/** Loaded into every canvas broker. Plain Pi sessions have no node id and
|
|
20
|
+
* deliberately receive no filesystem transport. */
|
|
21
|
+
export function registerCanvasPreviewResult(pi) {
|
|
22
|
+
if (process.env['CRTR_NODE_ID'] === undefined || process.env['CRTR_NODE_ID'] === '')
|
|
23
|
+
return;
|
|
24
|
+
const directory = join(tmpdir(), 'crouter-preview-results');
|
|
25
|
+
const paths = new Map();
|
|
26
|
+
pi.on('tool_call', (event) => {
|
|
27
|
+
if (event.toolName !== 'bash' || typeof event.input.command !== 'string')
|
|
28
|
+
return;
|
|
29
|
+
mkdirSync(directory, { recursive: true });
|
|
30
|
+
const path = join(directory, `${encodeURIComponent(event.toolCallId)}.json`);
|
|
31
|
+
paths.set(event.toolCallId, path);
|
|
32
|
+
// Export before the complete script rather than prefixing its first simple
|
|
33
|
+
// command: agent bash calls routinely start with `cd … &&`, a compound
|
|
34
|
+
// statement, or setup commands before crtr itself.
|
|
35
|
+
event.input.command = `export CRTR_PREVIEW_RESULT_PATH=${JSON.stringify(path)}\n${event.input.command}`;
|
|
36
|
+
});
|
|
37
|
+
pi.on('tool_result', (event) => {
|
|
38
|
+
const path = paths.get(event.toolCallId);
|
|
39
|
+
if (path === undefined)
|
|
40
|
+
return;
|
|
41
|
+
paths.delete(event.toolCallId);
|
|
42
|
+
if (!existsSync(path))
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
const record = JSON.parse(readFileSync(path, 'utf8'));
|
|
46
|
+
if (!isPreviewRecord(record))
|
|
47
|
+
return;
|
|
48
|
+
return { details: { ...(event.details ?? {}), crtrPreview: record } };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// A malformed side-channel record is not agent-visible output and must
|
|
52
|
+
// not change the bash call's actual result.
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
rmSync(path, { force: true });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export default registerCanvasPreviewResult;
|
|
@@ -6,23 +6,10 @@
|
|
|
6
6
|
// inherited context.
|
|
7
7
|
import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
|
|
8
8
|
import { isReviewBoundary, REVIEW_BOUNDARY_CUSTOM_TYPE } from '../core/runtime/session-visibility.js';
|
|
9
|
+
import { truncateToWidth } from './truncate.js';
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Collapsed-by-default rendering
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
|
-
/** Truncate plain text to at most `width` columns, appending an ellipsis when it
|
|
13
|
-
* would overflow. Content here is ANSI-free plain text, so code-point count
|
|
14
|
-
* stands in for visible width. The renderer MUST keep every emitted line within
|
|
15
|
-
* the terminal width or pi's TUI aborts the whole render. */
|
|
16
|
-
function truncateToWidth(text, width) {
|
|
17
|
-
if (width <= 0)
|
|
18
|
-
return '';
|
|
19
|
-
const chars = Array.from(text);
|
|
20
|
-
if (chars.length <= width)
|
|
21
|
-
return text;
|
|
22
|
-
if (width === 1)
|
|
23
|
-
return '…';
|
|
24
|
-
return chars.slice(0, width - 1).join('') + '…';
|
|
25
|
-
}
|
|
26
13
|
/** Render the marker as a single compact divider; its full content remains in
|
|
27
14
|
* the message list for the model and generated-context presenters. */
|
|
28
15
|
export function renderReviewBoundary(_message, _options, theme) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Hard-truncate to `width` columns with an ellipsis when the text would
|
|
2
|
+
* overflow. Extension-rendered content is ANSI-free plain text, so a
|
|
3
|
+
* code-point count stands in for visible width. Every renderer MUST keep its
|
|
4
|
+
* emitted lines within the terminal width or pi's TUI aborts the whole render. */
|
|
5
|
+
export declare function truncateToWidth(text: string, width: number): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Hard-truncate to `width` columns with an ellipsis when the text would
|
|
2
|
+
* overflow. Extension-rendered content is ANSI-free plain text, so a
|
|
3
|
+
* code-point count stands in for visible width. Every renderer MUST keep its
|
|
4
|
+
* emitted lines within the terminal width or pi's TUI aborts the whole render. */
|
|
5
|
+
export function truncateToWidth(text, width) {
|
|
6
|
+
if (width <= 0)
|
|
7
|
+
return '';
|
|
8
|
+
const chars = Array.from(text);
|
|
9
|
+
if (chars.length <= width)
|
|
10
|
+
return text;
|
|
11
|
+
if (width === 1)
|
|
12
|
+
return '…';
|
|
13
|
+
return chars.slice(0, width - 1).join('') + '…';
|
|
14
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
/** Broker-generated activity recap for one completed tool-call group.
|
|
1
|
+
/** Broker-generated activity recap for one completed tool-call group. Changed
|
|
2
|
+
* files are counted deterministically by the viewer's GroupActivityRecorder;
|
|
3
|
+
* deletions have no tool of their own, so only the model can evidence them. */
|
|
2
4
|
export interface ToolGroupSummary {
|
|
3
5
|
bullets: string[];
|
|
4
6
|
nodesSpawned: number;
|
|
5
|
-
|
|
7
|
+
filesDeleted: number;
|
|
6
8
|
}
|
|
7
9
|
export interface ToolGroupMessageLike {
|
|
8
10
|
role?: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Keep this independent of renderer state: both live event tracking and snapshot
|
|
3
3
|
// reconstruction must agree on which prose closes a run of tools.
|
|
4
4
|
import { generatedContextPresentation, generatedContextText } from './generated-context.js';
|
|
5
|
-
const TOOL_GROUP_SUMMARY_KEYS = new Set(['bullets', 'nodesSpawned', '
|
|
5
|
+
const TOOL_GROUP_SUMMARY_KEYS = new Set(['bullets', 'nodesSpawned', 'filesDeleted']);
|
|
6
6
|
const MAX_TOOL_GROUP_BULLETS = 5;
|
|
7
7
|
const MAX_TOOL_GROUP_BULLET_LENGTH = 1_000;
|
|
8
8
|
/** Validate the one durable summary shape and cap its display-sized fields.
|
|
@@ -21,14 +21,14 @@ export function normalizeToolGroupSummary(value) {
|
|
|
21
21
|
return null;
|
|
22
22
|
if (typeof record.nodesSpawned !== 'number' || !Number.isSafeInteger(record.nodesSpawned) || record.nodesSpawned < 0)
|
|
23
23
|
return null;
|
|
24
|
-
if (typeof record.
|
|
24
|
+
if (typeof record.filesDeleted !== 'number' || !Number.isSafeInteger(record.filesDeleted) || record.filesDeleted < 0)
|
|
25
25
|
return null;
|
|
26
26
|
return {
|
|
27
27
|
bullets: record.bullets
|
|
28
28
|
.slice(0, MAX_TOOL_GROUP_BULLETS)
|
|
29
29
|
.map((bullet) => bullet.trim().slice(0, MAX_TOOL_GROUP_BULLET_LENGTH).trimEnd()),
|
|
30
30
|
nodesSpawned: record.nodesSpawned,
|
|
31
|
-
|
|
31
|
+
filesDeleted: record.filesDeleted,
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
/** Validate sidecar/snapshot entries independently so one malformed or legacy
|
package/dist/types.d.ts
CHANGED
|
@@ -128,13 +128,12 @@ export interface ModelRoutingConfig {
|
|
|
128
128
|
/** Launch metadata for one kind (a top-level kind like `developer`, or a full
|
|
129
129
|
* sub-kind string like `plan/reviewers/security`) — the settings-file
|
|
130
130
|
* replacement for per-kind persona frontmatter. `model`/`tools`/`extensions`
|
|
131
|
-
* are launch knobs consumed by `buildLaunchSpec
|
|
131
|
+
* are launch knobs consumed by `buildLaunchSpec`; `whenToUse` is the
|
|
132
132
|
* one-line gloss shown in `node new -h` / `node promote -h`. `availableTo` is
|
|
133
133
|
* VISIBILITY-ONLY (never launch validation): the list of top-level kind names
|
|
134
134
|
* whose spawn menus surface this sub-kind, `'*'` meaning every kind; omitted
|
|
135
135
|
* defaults to the sub-kind's own top-level ancestor (e.g.
|
|
136
|
-
* `plan/reviewers/security` defaults to `['plan']`)
|
|
137
|
-
* original sub-persona `availableTo` semantics. A direct launch by full kind
|
|
136
|
+
* `plan/reviewers/security` defaults to `['plan']`). A direct launch by full kind
|
|
138
137
|
* string is always valid regardless of `availableTo`. */
|
|
139
138
|
export interface KindConfig {
|
|
140
139
|
model?: string;
|
|
@@ -313,9 +312,9 @@ export interface InstalledMarketplace {
|
|
|
313
312
|
* (spec §2.1). A profile is an agent identity defined at the user root: it
|
|
314
313
|
* names N project directories and owns its own `memory/` store. `projects`
|
|
315
314
|
* are absolute, real-path-resolved directories in manifest order — the
|
|
316
|
-
* pointer set `findProjectScopeRoots` walks
|
|
315
|
+
* pointer set `findProjectScopeRoots` walks, after the node's own
|
|
317
316
|
* cwd. `name` is the mutable display name (and the gateable `profile` subject
|
|
318
|
-
* field
|
|
317
|
+
* field); `<slug>-<id>` in the directory name is the stable id and
|
|
319
318
|
* never changes on rename. See `src/core/profiles/manifest.ts` for every
|
|
320
319
|
* read/write of this file — it is the sole owner of path resolution and
|
|
321
320
|
* mutation. */
|
|
@@ -323,6 +322,10 @@ export interface ProfileManifest {
|
|
|
323
322
|
schema_version: number;
|
|
324
323
|
name: string;
|
|
325
324
|
projects: string[];
|
|
325
|
+
/** The directory nodes under this profile are pinned to (see
|
|
326
|
+
* `profileHome`). Normalized on read to the first project when the manifest
|
|
327
|
+
* carries none, so a profile written before homes existed still has one. */
|
|
328
|
+
home: string | null;
|
|
326
329
|
created_at: string;
|
|
327
330
|
last_used_at: string | null;
|
|
328
331
|
}
|
package/package.json
CHANGED
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.177",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/crouter",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.177",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { NodeConfigSubject } from './subject-fields.js';
|
|
2
|
-
/** Surface docs matched by a successful read tool call using the caller-supplied
|
|
3
|
-
* daemon snapshot subject. */
|
|
4
|
-
export declare function renderOnReadDocsForSubject(subject: NodeConfigSubject, readFilePath: string, seen?: Set<string>): string;
|
|
5
|
-
/** Workspace boot docs using the snapshot's cwd/profile instead of a canvas
|
|
6
|
-
* lookup. */
|
|
7
|
-
export declare function renderWorkspaceOpenDocsForSubject(subject: NodeConfigSubject, cwd: string, profileId: string | null): string;
|