@crouton-kit/crouter 0.3.57 → 0.3.59
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/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +20 -5
- package/dist/clients/attach/attach-cmd.js +5 -5
- package/dist/clients/attach/chat-view.js +5 -0
- package/dist/clients/web/__tests__/push-engine.test.d.ts +1 -0
- package/dist/clients/web/__tests__/push-engine.test.js +302 -0
- package/dist/clients/web/__tests__/push-registry.test.d.ts +1 -0
- package/dist/clients/web/__tests__/push-registry.test.js +177 -0
- package/dist/clients/web/events.d.ts +5 -0
- package/dist/clients/web/events.js +16 -0
- package/dist/clients/web/push-engine.d.ts +108 -0
- package/dist/clients/web/push-engine.js +256 -0
- package/dist/clients/web/push-registry.d.ts +89 -0
- package/dist/clients/web/push-registry.js +378 -0
- package/dist/clients/web/server.js +191 -8
- package/dist/clients/web/web-client/shared/protocol.d.ts +308 -0
- package/dist/clients/web/web-client/shared/protocol.js +8 -0
- package/dist/commands/memory/read.js +8 -1
- package/dist/core/canvas/paths.d.ts +10 -0
- package/dist/core/canvas/paths.js +14 -0
- package/dist/core/substrate/render.js +7 -2
- package/dist/web-client/assets/{index-FWBlgUV2.js → index-BOlkXTJY.js} +24 -24
- package/dist/web-client/assets/index-CllMPSPq.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/package.json +3 -1
- package/dist/web-client/assets/index-CKvdUlkn.css +0 -2
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types for the crouter web client/server split.
|
|
3
|
+
*
|
|
4
|
+
* The SPA imports the pi payload types it needs from this module — never from
|
|
5
|
+
* broker-protocol internals. The type-only re-exports below keep the web
|
|
6
|
+
* package's shared payload shapes in one place.
|
|
7
|
+
*/
|
|
8
|
+
export type { UserMessage, AssistantMessage, ToolResultMessage, TextContent, ToolCall, ImageContent, } from '@earendil-works/pi-ai';
|
|
9
|
+
export type { ThinkingLevel } from '@earendil-works/pi-agent-core';
|
|
10
|
+
export type { RpcExtensionUIRequest, RpcExtensionUIResponse } from '@earendil-works/pi-coding-agent';
|
|
11
|
+
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
12
|
+
import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
|
|
13
|
+
/**
|
|
14
|
+
* An `AgentMessage` enriched with a client-side origin tag.
|
|
15
|
+
* Only `role:'user'` messages can carry `origin:'inbox'`; all others leave
|
|
16
|
+
* `origin` undefined. `'human'` is reserved for future explicit human-origin
|
|
17
|
+
* tagging (currently unset — absence means human by default).
|
|
18
|
+
*
|
|
19
|
+
* Defined as an intersection (not `interface extends`) because `AgentMessage`
|
|
20
|
+
* is a union type and TypeScript does not allow extending unions.
|
|
21
|
+
*/
|
|
22
|
+
export type FoldedMessage = AgentMessage & {
|
|
23
|
+
/** Set to `'inbox'` when this user message is recognized as injected by the
|
|
24
|
+
* canvas-inbox-watcher extension (a coalesced inbox digest). */
|
|
25
|
+
origin?: 'inbox' | 'human';
|
|
26
|
+
};
|
|
27
|
+
/** base session vs orchestrator. */
|
|
28
|
+
export type NodeMode = 'base' | 'orchestrator';
|
|
29
|
+
/** terminal (reaps when done) vs resident (root) node. */
|
|
30
|
+
export type NodeLifecycle = 'terminal' | 'resident';
|
|
31
|
+
/** Canvas lifecycle status. */
|
|
32
|
+
export type NodeLifeStatus = 'active' | 'idle' | 'done' | 'dead' | 'canceled';
|
|
33
|
+
/** broker-hosted (enterable) vs tmux-pane (shown, non-enterable). */
|
|
34
|
+
export type HostKind = 'broker' | 'tmux';
|
|
35
|
+
/** One row from `crtr canvas snapshot --json` — node identity + runtime, no chrome. */
|
|
36
|
+
export interface NodeSummary {
|
|
37
|
+
node_id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
/** Node kind (developer/explore/…) — open set. */
|
|
40
|
+
kind: string;
|
|
41
|
+
mode: NodeMode;
|
|
42
|
+
lifecycle: NodeLifecycle;
|
|
43
|
+
status: NodeLifeStatus;
|
|
44
|
+
cwd: string;
|
|
45
|
+
/** Spine parent node id, or null for a root. */
|
|
46
|
+
parent: string | null;
|
|
47
|
+
/** ISO-8601 creation timestamp. */
|
|
48
|
+
created: string;
|
|
49
|
+
host_kind: HostKind;
|
|
50
|
+
/** True iff `host_kind === 'broker'` (the web UI can open a live session). */
|
|
51
|
+
enterable: boolean;
|
|
52
|
+
/** Count of pending human asks (blocked-on-human indicator). */
|
|
53
|
+
attention_count: number;
|
|
54
|
+
/** Canvas cycle count for this node (revive/yield generations). Optional for
|
|
55
|
+
* back-compat with snapshots produced before the field existed. */
|
|
56
|
+
cycles?: number;
|
|
57
|
+
/** ISO-8601 of the node's most recent work (session activity), distinct from
|
|
58
|
+
* `created`. Optional/back-compat; falls back to `created` when absent. */
|
|
59
|
+
last_activity?: string;
|
|
60
|
+
}
|
|
61
|
+
/** input/output token burn, with cache reads where the provider reports them. */
|
|
62
|
+
export interface TokenBurn {
|
|
63
|
+
input: number;
|
|
64
|
+
output: number;
|
|
65
|
+
cache?: number;
|
|
66
|
+
}
|
|
67
|
+
/** Live context-window usage. */
|
|
68
|
+
export interface ContextUsage {
|
|
69
|
+
tokens: number;
|
|
70
|
+
window: number;
|
|
71
|
+
percent: number;
|
|
72
|
+
}
|
|
73
|
+
/** Coarse session stats for chrome (distinct from pi's full `SessionStats`). */
|
|
74
|
+
export interface SessionStatsSummary {
|
|
75
|
+
turns: number;
|
|
76
|
+
user_messages: number;
|
|
77
|
+
assistant_messages: number;
|
|
78
|
+
cost?: number;
|
|
79
|
+
}
|
|
80
|
+
/** Viewer/controller presence for a node. */
|
|
81
|
+
export interface Presence {
|
|
82
|
+
viewers: number;
|
|
83
|
+
controller: string | null;
|
|
84
|
+
}
|
|
85
|
+
/** Git working-tree change counts for the meta strip. */
|
|
86
|
+
export interface GitStatus {
|
|
87
|
+
added: number;
|
|
88
|
+
modified: number;
|
|
89
|
+
deleted: number;
|
|
90
|
+
untracked: number;
|
|
91
|
+
}
|
|
92
|
+
/** `crtr node inspect show --json`'s node payload — `NodeSummary` plus the per-node chrome. */
|
|
93
|
+
export interface NodeDetail extends NodeSummary {
|
|
94
|
+
branch: string | null;
|
|
95
|
+
model: string | null;
|
|
96
|
+
tokens: TokenBurn | null;
|
|
97
|
+
context: ContextUsage | null;
|
|
98
|
+
tool_calls: number | null;
|
|
99
|
+
stats: SessionStatsSummary | null;
|
|
100
|
+
presence: Presence | null;
|
|
101
|
+
/** True while a broker is live; false for a dormant (last-known) view. */
|
|
102
|
+
live: boolean;
|
|
103
|
+
/** Working-tree change counts for the meta strip (null when unavailable). */
|
|
104
|
+
git_status?: GitStatus | null;
|
|
105
|
+
}
|
|
106
|
+
/** A slash command in a node's palette (from the broker `get_commands` reply). */
|
|
107
|
+
export interface Command {
|
|
108
|
+
name: string;
|
|
109
|
+
description: string;
|
|
110
|
+
source: 'builtin' | 'command' | 'template';
|
|
111
|
+
location?: 'user' | 'project' | 'path';
|
|
112
|
+
path?: string;
|
|
113
|
+
argument_hint?: string;
|
|
114
|
+
}
|
|
115
|
+
/** `crtr canvas snapshot --json` body. */
|
|
116
|
+
export interface CanvasSnapshot {
|
|
117
|
+
nodes: NodeSummary[];
|
|
118
|
+
/** ISO-8601. */
|
|
119
|
+
generated_at: string;
|
|
120
|
+
}
|
|
121
|
+
/** `crtr node new --json` success. */
|
|
122
|
+
export interface SpawnResponse {
|
|
123
|
+
node_id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
window?: string;
|
|
126
|
+
session?: string;
|
|
127
|
+
status: string;
|
|
128
|
+
follow_up: string;
|
|
129
|
+
}
|
|
130
|
+
/** `crtr node msg --json` success. */
|
|
131
|
+
export interface MessageResponse {
|
|
132
|
+
mode: string;
|
|
133
|
+
armed: boolean;
|
|
134
|
+
target: string;
|
|
135
|
+
delivered?: boolean;
|
|
136
|
+
revived?: boolean;
|
|
137
|
+
guidance: string;
|
|
138
|
+
}
|
|
139
|
+
/** `crtr node lifecycle revive --json` success. */
|
|
140
|
+
export interface ReviveResponse {
|
|
141
|
+
window: null;
|
|
142
|
+
session: string | null;
|
|
143
|
+
resumed: boolean;
|
|
144
|
+
ready: boolean;
|
|
145
|
+
}
|
|
146
|
+
/** `crtr node lifecycle close --json` success. */
|
|
147
|
+
export interface CloseResponse {
|
|
148
|
+
closed: boolean;
|
|
149
|
+
node_id: string;
|
|
150
|
+
count: number;
|
|
151
|
+
closed_ids: string[];
|
|
152
|
+
spared: string[];
|
|
153
|
+
}
|
|
154
|
+
/** `crtr node new` body. Always headless; `root` toggles a resident node. */
|
|
155
|
+
export interface SpawnRequest {
|
|
156
|
+
prompt: string;
|
|
157
|
+
kind: string;
|
|
158
|
+
mode?: NodeMode;
|
|
159
|
+
root?: boolean;
|
|
160
|
+
cwd?: string;
|
|
161
|
+
name?: string;
|
|
162
|
+
model?: string;
|
|
163
|
+
parent?: string;
|
|
164
|
+
}
|
|
165
|
+
/** `crtr node msg` body. */
|
|
166
|
+
export interface MessageRequest {
|
|
167
|
+
body: string;
|
|
168
|
+
tier?: string;
|
|
169
|
+
}
|
|
170
|
+
/** `crtr node lifecycle revive` body. */
|
|
171
|
+
export interface ReviveRequest {
|
|
172
|
+
fresh?: boolean;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The five resolution flows (design §5.2). humanloop's `InteractionKind` also
|
|
176
|
+
* has `review`; the deck loader normalizes it (and any unknown/absent kind)
|
|
177
|
+
* onto one of these five so a deck always renders through a known flow.
|
|
178
|
+
*/
|
|
179
|
+
export type DeckKind = 'notify' | 'validation' | 'decision' | 'context' | 'error';
|
|
180
|
+
/** One option of a decision/validation interaction (humanloop InteractionOption). */
|
|
181
|
+
export interface DeckOption {
|
|
182
|
+
id: string;
|
|
183
|
+
label: string;
|
|
184
|
+
/** The option's consequence/explanation (humanloop `description`). */
|
|
185
|
+
description?: string;
|
|
186
|
+
}
|
|
187
|
+
/** One interaction within a deck (humanloop Interaction, normalized for the web). */
|
|
188
|
+
export interface DeckInteraction {
|
|
189
|
+
id: string;
|
|
190
|
+
title: string;
|
|
191
|
+
subtitle?: string;
|
|
192
|
+
/** Markdown body (bodyPath already inlined by `crtr human deck`). */
|
|
193
|
+
body?: string;
|
|
194
|
+
kind: DeckKind;
|
|
195
|
+
options: DeckOption[];
|
|
196
|
+
multiSelect: boolean;
|
|
197
|
+
allowFreetext: boolean;
|
|
198
|
+
freetextLabel?: string;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A pending ask, ranked in the inbox list (design §5.2). Provenance carries
|
|
202
|
+
* BOTH the conversation-facing summary (rendered by default, never the node
|
|
203
|
+
* id alone) and the raw asking node, shown alongside it.
|
|
204
|
+
*/
|
|
205
|
+
export interface DeckSummary {
|
|
206
|
+
/** Opaque, stable inbox-entry id (base64url of the interaction dir). */
|
|
207
|
+
id: string;
|
|
208
|
+
/** The interaction job id (basename of the interaction dir). */
|
|
209
|
+
job_id: string;
|
|
210
|
+
/** The first interaction's kind — the glyph + flow for the row. */
|
|
211
|
+
kind: DeckKind;
|
|
212
|
+
title: string;
|
|
213
|
+
subtitle?: string;
|
|
214
|
+
/** ISO-8601 — when the ask started blocking (drives the wait duration + rank). */
|
|
215
|
+
blocked_since: string;
|
|
216
|
+
/** The conversation (spine root) this ask belongs to. */
|
|
217
|
+
conversation_id: string;
|
|
218
|
+
conversation_title: string;
|
|
219
|
+
/** The node that raised the ask. */
|
|
220
|
+
asking_node_id: string;
|
|
221
|
+
asking_node_name: string;
|
|
222
|
+
/** The asking node's cwd — sub-DAG scoping. */
|
|
223
|
+
cwd: string;
|
|
224
|
+
/** How many interactions the deck holds (a multi-question deck). */
|
|
225
|
+
interaction_count: number;
|
|
226
|
+
}
|
|
227
|
+
/** The full deck for a resolution flow (`crtr human deck <id> --json`). */
|
|
228
|
+
export interface DeckDetail extends DeckSummary {
|
|
229
|
+
interactions: DeckInteraction[];
|
|
230
|
+
}
|
|
231
|
+
/** One interaction's answer (humanloop InteractionResponse). */
|
|
232
|
+
export interface DeckAnswer {
|
|
233
|
+
id: string;
|
|
234
|
+
selectedOptionId?: string;
|
|
235
|
+
selectedOptionIds?: string[];
|
|
236
|
+
freetext?: string;
|
|
237
|
+
optionComments?: Record<string, string>;
|
|
238
|
+
}
|
|
239
|
+
/** `crtr human resolve <id> --json` body. */
|
|
240
|
+
export interface ResolveDeckRequest {
|
|
241
|
+
responses: DeckAnswer[];
|
|
242
|
+
}
|
|
243
|
+
/** `crtr human resolve <id> --json` success. */
|
|
244
|
+
export interface ResolveDeckResponse {
|
|
245
|
+
resolved: true;
|
|
246
|
+
job_id: string;
|
|
247
|
+
delivered: boolean;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Engine state carried in `snapshot.state` — the broker snapshot's `state`
|
|
251
|
+
* (mirrors `BrokerSnapshot.state`), reconstructed here in pi/primitive types so
|
|
252
|
+
* the SPA never imports the broker-protocol `BrokerSnapshot`. The SPA's
|
|
253
|
+
* streaming/idle indicator reads `isStreaming` (spec C.10).
|
|
254
|
+
*/
|
|
255
|
+
export interface SessionState {
|
|
256
|
+
sessionId: string;
|
|
257
|
+
sessionFile: string | null;
|
|
258
|
+
model: string | null;
|
|
259
|
+
isStreaming: boolean;
|
|
260
|
+
thinkingLevel: ThinkingLevel;
|
|
261
|
+
steeringMode: 'all' | 'one-at-a-time';
|
|
262
|
+
followUpMode: 'all' | 'one-at-a-time';
|
|
263
|
+
sessionName: string | null;
|
|
264
|
+
autoCompactionEnabled: boolean;
|
|
265
|
+
pendingMessageCount: number;
|
|
266
|
+
}
|
|
267
|
+
/** Web role of one browser tab's session connection. */
|
|
268
|
+
export type WebRole = 'observer' | 'controller';
|
|
269
|
+
/** Upstream broker connectivity, surfaced to the tab (spec §6.2). */
|
|
270
|
+
export type BrokerStatus = 'connected' | 'reconnecting' | 'down' | 'revived';
|
|
271
|
+
/** Response payload to an extension dialog (`RpcExtensionUIResponse` minus id/type). */
|
|
272
|
+
export type DialogResponseValue = {
|
|
273
|
+
value: string;
|
|
274
|
+
} | {
|
|
275
|
+
confirmed: boolean;
|
|
276
|
+
} | {
|
|
277
|
+
cancelled: true;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* A typed content block within a view tab. Discriminated union of three
|
|
281
|
+
* block kinds: inline/sourced markdown, a KPI grid, and a bar-list chart.
|
|
282
|
+
*/
|
|
283
|
+
export type ViewBlock = {
|
|
284
|
+
kind: 'markdown';
|
|
285
|
+
source: {
|
|
286
|
+
node_id: string;
|
|
287
|
+
path: string;
|
|
288
|
+
} | {
|
|
289
|
+
inline: string;
|
|
290
|
+
};
|
|
291
|
+
} | {
|
|
292
|
+
kind: 'kpis';
|
|
293
|
+
items: {
|
|
294
|
+
label: string;
|
|
295
|
+
value: string;
|
|
296
|
+
unit?: string;
|
|
297
|
+
sub?: string;
|
|
298
|
+
}[];
|
|
299
|
+
} | {
|
|
300
|
+
kind: 'barlist';
|
|
301
|
+
title: string;
|
|
302
|
+
rows: {
|
|
303
|
+
label: string;
|
|
304
|
+
value: number;
|
|
305
|
+
max?: number;
|
|
306
|
+
note?: string;
|
|
307
|
+
}[];
|
|
308
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire types for the crouter web client/server split.
|
|
3
|
+
*
|
|
4
|
+
* The SPA imports the pi payload types it needs from this module — never from
|
|
5
|
+
* broker-protocol internals. The type-only re-exports below keep the web
|
|
6
|
+
* package's shared payload shapes in one place.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -2,6 +2,7 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
2
2
|
import { CrtrError, notFound } from '../../core/errors.js';
|
|
3
3
|
import { resolveMemoryDoc } from '../../core/memory-resolver.js';
|
|
4
4
|
import { parseSubstrateDoc } from '../../core/substrate/index.js';
|
|
5
|
+
import { interpolateNodePaths } from '../../core/canvas/paths.js';
|
|
5
6
|
import { readText } from '../../core/fs-utils.js';
|
|
6
7
|
import { MEMORY_KINDS } from './shared.js';
|
|
7
8
|
export const readLeaf = defineLeaf({
|
|
@@ -51,7 +52,13 @@ export const readLeaf = defineLeaf({
|
|
|
51
52
|
: 'knowledge';
|
|
52
53
|
// --kind asserts the resolved kind; a mismatch falls through to not-found.
|
|
53
54
|
if (kindFilter === undefined || kind === kindFilter) {
|
|
54
|
-
const
|
|
55
|
+
const raw = includeFrontmatter ? readText(doc.path) : doc.body;
|
|
56
|
+
// In-node reads (CRTR_NODE_ID set) get the literal `$CRTR_CONTEXT_DIR`
|
|
57
|
+
// token swapped for this node's real absolute path, so agents don't
|
|
58
|
+
// paste it into the non-expanding Write tool. A human/contributor
|
|
59
|
+
// reading generically (env unset) keeps the token literal.
|
|
60
|
+
const nodeId = process.env['CRTR_NODE_ID'];
|
|
61
|
+
const content = nodeId ? interpolateNodePaths(raw, nodeId) : raw;
|
|
55
62
|
return {
|
|
56
63
|
name: doc.name,
|
|
57
64
|
kind,
|
|
@@ -25,6 +25,16 @@ export declare function viewSocketPath(nodeId: string): string;
|
|
|
25
25
|
export declare function nodesRoot(): string;
|
|
26
26
|
export declare function nodeDir(nodeId: string): string;
|
|
27
27
|
export declare function contextDir(nodeId: string): string;
|
|
28
|
+
/** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
|
|
29
|
+
* agent-facing doc text with the node's real values, so a doc surfaced INTO a
|
|
30
|
+
* node hands it a concrete absolute path instead of a shell-only string. The
|
|
31
|
+
* env vars only expand in bash; an agent that pastes `$CRTR_CONTEXT_DIR/x.md`
|
|
32
|
+
* into the (non-expanding) Write tool creates a literal `$CRTR_CONTEXT_DIR/`
|
|
33
|
+
* dir under the project repo. The resolved absolute path works in both bash and
|
|
34
|
+
* Write, so this substitution is strictly safer. Callers must only apply it
|
|
35
|
+
* when they have a concrete node to render for (never on generic, node-less
|
|
36
|
+
* output). Pure string work — no I/O. */
|
|
37
|
+
export declare function interpolateNodePaths(text: string, nodeId: string): string;
|
|
28
38
|
export declare function jobDir(nodeId: string): string;
|
|
29
39
|
export declare function reportsDir(nodeId: string): string;
|
|
30
40
|
/** Overflow store for inbox entries whose inline body exceeds the digest's
|
|
@@ -69,6 +69,20 @@ export function nodeDir(nodeId) {
|
|
|
69
69
|
export function contextDir(nodeId) {
|
|
70
70
|
return join(nodeDir(nodeId), 'context');
|
|
71
71
|
}
|
|
72
|
+
/** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
|
|
73
|
+
* agent-facing doc text with the node's real values, so a doc surfaced INTO a
|
|
74
|
+
* node hands it a concrete absolute path instead of a shell-only string. The
|
|
75
|
+
* env vars only expand in bash; an agent that pastes `$CRTR_CONTEXT_DIR/x.md`
|
|
76
|
+
* into the (non-expanding) Write tool creates a literal `$CRTR_CONTEXT_DIR/`
|
|
77
|
+
* dir under the project repo. The resolved absolute path works in both bash and
|
|
78
|
+
* Write, so this substitution is strictly safer. Callers must only apply it
|
|
79
|
+
* when they have a concrete node to render for (never on generic, node-less
|
|
80
|
+
* output). Pure string work — no I/O. */
|
|
81
|
+
export function interpolateNodePaths(text, nodeId) {
|
|
82
|
+
return text
|
|
83
|
+
.replaceAll('$CRTR_CONTEXT_DIR', contextDir(nodeId))
|
|
84
|
+
.replaceAll('$CRTR_NODE_ID', nodeId);
|
|
85
|
+
}
|
|
72
86
|
export function jobDir(nodeId) {
|
|
73
87
|
return join(nodeDir(nodeId), 'job');
|
|
74
88
|
}
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
// (parseSubstrateDoc returns null for a non-substrate doc; per-file node-local
|
|
49
49
|
// loads are wrapped), and tree construction is pure string work.
|
|
50
50
|
import { relative, sep } from 'node:path';
|
|
51
|
+
import { interpolateNodePaths } from '../canvas/paths.js';
|
|
51
52
|
import { listAllMemoryDocs, resolveMemoryDoc } from '../memory-resolver.js';
|
|
52
53
|
import { subKindsAvailableTo } from '../config.js';
|
|
53
54
|
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
@@ -504,7 +505,10 @@ function buildPreferencesBlock(subject) {
|
|
|
504
505
|
}
|
|
505
506
|
export function renderPreferencesSection(nodeId) {
|
|
506
507
|
const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
|
|
507
|
-
|
|
508
|
+
// Surfaced INTO this node: swap the literal `$CRTR_CONTEXT_DIR` env-var token
|
|
509
|
+
// for the node's real absolute path so agents don't paste it into the
|
|
510
|
+
// non-expanding Write tool (see interpolateNodePaths).
|
|
511
|
+
return interpolateNodePaths(buildPreferencesBlock(subject).block, nodeId);
|
|
508
512
|
}
|
|
509
513
|
export function renderPreferencesForSubject(subject) {
|
|
510
514
|
return buildPreferencesBlock(subject);
|
|
@@ -543,5 +547,6 @@ export function renderKnowledgeBlock(nodeId) {
|
|
|
543
547
|
if (tree !== '')
|
|
544
548
|
out += `\n\n${tree}`;
|
|
545
549
|
out += `\n\n${KNOWLEDGE_OUTRO}`;
|
|
546
|
-
|
|
550
|
+
// Surfaced INTO this node: swap literal `$CRTR_CONTEXT_DIR` for the real path.
|
|
551
|
+
return interpolateNodePaths(`<memory kind="knowledge">\n${out}\n</memory>`, nodeId);
|
|
547
552
|
}
|