@north-light/crouter 0.3.221 → 0.3.222
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 +21 -1
- package/dist/api/client.js +34 -0
- package/dist/api/dto/chat-inventory.d.ts +13 -0
- package/dist/api/dto/human-requests.d.ts +88 -0
- package/dist/api/dto/human-requests.js +4 -0
- package/dist/api/dto/human.d.ts +3 -0
- package/dist/api/dto/reviews.d.ts +2 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +7 -0
- package/dist/api/routes.js +10 -0
- package/dist/builtin-memory/00-runtime-base/00-authoring.md +31 -0
- package/dist/builtin-memory/00-runtime-base/01-escalation.md +14 -0
- package/dist/builtin-memory/{insights/listen.md → 00-runtime-base/02-insight-capture.md} +1 -0
- package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +27 -0
- package/dist/builtin-memory/{02-lifecycle/01-resident.md → 02-turn-lifecycle/02-resident.md} +5 -0
- package/dist/builtin-memory/04-base-worker.md +4 -8
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/design/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/design/design-contract.md +19 -0
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/general/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/00-base.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +2 -1
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +28 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -0
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -0
- package/dist/builtin-memory/05-kinds/review/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +1 -0
- package/dist/builtin-memory/05-kinds/review/security-findings.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/00-base.md +4 -3
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -0
- package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -0
- package/dist/builtin-memory/design/guide.md +35 -0
- package/dist/builtin-memory/design/roadmap.md +21 -0
- package/dist/builtin-memory/insights/capture.md +1 -1
- package/dist/builtin-memory/internal/plugins.md +10 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/plan/roadmap.md +6 -28
- package/dist/builtin-memory/spec/guide.md +19 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +28 -15
- package/dist/clients/attach/render/markdown-source.js +106 -1
- package/dist/clients/attach/session/file-links.d.ts +13 -4
- package/dist/clients/attach/session/file-links.js +54 -58
- package/dist/clients/attach/viewer.js +525 -523
- package/dist/clients/inbox/controller.js +1 -1
- package/dist/clients/inbox/resolve.d.ts +1 -0
- package/dist/clients/inbox/review/review-client.js +3 -1
- package/dist/commands/__tests__/human.test.js +2 -2
- package/dist/commands/human/request.d.ts +2 -0
- package/dist/commands/human/request.js +281 -0
- package/dist/commands/human.js +5 -2
- package/dist/commands/sys/config.js +2 -2
- package/dist/commands/sys/doctor.js +54 -2
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +7 -4
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.d.ts +1 -0
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +71 -0
- package/dist/core/__tests__/human-action-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/human-action-delivery.test.js +140 -0
- package/dist/core/__tests__/human-actions.test.d.ts +1 -0
- package/dist/core/__tests__/human-actions.test.js +116 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +1 -1
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +1 -1
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.d.ts +1 -0
- package/dist/core/__tests__/prospective-inventory-capability-parity.test.js +91 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.d.ts +1 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +127 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.d.ts +1 -0
- package/dist/core/__tests__/seam/prospective-inventory-stdout.test.js +31 -0
- package/dist/core/canvas/db.js +23 -0
- package/dist/core/canvas/human-deliveries.d.ts +53 -0
- package/dist/core/canvas/human-deliveries.js +75 -0
- package/dist/core/config.d.ts +13 -1
- package/dist/core/config.js +51 -1
- package/dist/core/feed/inbox.d.ts +6 -0
- package/dist/core/feed/inbox.js +9 -1
- package/dist/core/human/action-binding.d.ts +21 -0
- package/dist/core/human/action-binding.js +40 -0
- package/dist/core/human/completion.d.ts +38 -0
- package/dist/core/human/completion.js +27 -0
- package/dist/core/human/convention.d.ts +2 -0
- package/dist/core/human/convention.js +2 -0
- package/dist/core/human/tickets.d.ts +25 -6
- package/dist/core/human/tickets.js +19 -13
- package/dist/core/human/types.d.ts +5 -0
- package/dist/core/human-actions.d.ts +25 -0
- package/dist/core/human-actions.js +101 -0
- package/dist/core/memory-resolver.js +1 -1
- package/dist/core/profiles/select.d.ts +2 -0
- package/dist/core/profiles/select.js +21 -4
- package/dist/core/runtime/broker/frame-dispatch.js +2 -5
- package/dist/core/runtime/broker-inventory.d.ts +1 -2
- package/dist/core/runtime/broker-inventory.js +2 -77
- package/dist/core/runtime/broker-persona-guidance.js +1 -1
- package/dist/core/runtime/broker.js +4 -4
- package/dist/core/runtime/chat-inventory-rows.d.ts +8 -0
- package/dist/core/runtime/chat-inventory-rows.js +105 -0
- package/dist/core/runtime/command-surface.d.ts +8 -3
- package/dist/core/runtime/command-surface.js +42 -6
- package/dist/core/runtime/launch-target.d.ts +25 -0
- package/dist/core/runtime/launch-target.js +54 -0
- package/dist/core/runtime/persona.js +3 -3
- package/dist/core/runtime/prospective-inventory-cli.d.ts +1 -0
- package/dist/core/runtime/prospective-inventory-cli.js +61 -0
- package/dist/core/runtime/prospective-inventory.d.ts +10 -0
- package/dist/core/runtime/prospective-inventory.js +88 -0
- package/dist/core/runtime/spawn.d.ts +3 -1
- package/dist/core/runtime/spawn.js +5 -3
- package/dist/core/substrate/on-read.js +17 -28
- package/dist/core/substrate/render-node.d.ts +3 -2
- package/dist/core/substrate/render-node.js +3 -2
- package/dist/core/substrate/render.js +51 -19
- package/dist/core/substrate/schema.d.ts +5 -1
- package/dist/core/substrate/schema.js +4 -4
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -0
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +52 -3
- package/dist/daemon/api/handlers/human-requests.d.ts +2 -0
- package/dist/daemon/api/handlers/human-requests.js +409 -0
- package/dist/daemon/api/handlers/human.js +3 -0
- package/dist/daemon/api/handlers/inbox.js +3 -0
- package/dist/daemon/api/handlers/nodes.d.ts +1 -3
- package/dist/daemon/api/handlers/nodes.js +11 -46
- package/dist/daemon/api/handlers/prospective-chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/prospective-chat-inventory.js +59 -0
- package/dist/daemon/api/handlers/reviews.js +10 -2
- package/dist/daemon/api/server.js +4 -0
- package/dist/daemon/crtrd.js +6 -0
- package/dist/daemon/human/deliver-action.d.ts +16 -0
- package/dist/daemon/human/deliver-action.js +168 -0
- package/dist/daemon/human/finish.d.ts +8 -5
- package/dist/daemon/human/finish.js +45 -6
- package/dist/daemon/human/sweep.js +4 -1
- package/dist/daemon/reconcilers/human-delivery-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/human-delivery-lane.js +41 -0
- package/dist/daemon/review/finish.d.ts +8 -3
- package/dist/daemon/review/finish.js +19 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/00-runtime-base.md +0 -55
- package/dist/builtin-memory/design.md +0 -55
- /package/dist/builtin-memory/{02-lifecycle/00-terminal.md → 02-turn-lifecycle/01-terminal.md} +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { chatInventoryCommandRows, chatInventoryMemoryRows } from './chat-inventory-rows.js';
|
|
5
|
+
import { FRONT_DOOR_ENV } from './front-door-env.js';
|
|
6
|
+
const PREFLIGHT_TIMEOUT_MS = 15_000;
|
|
7
|
+
function entryPath() {
|
|
8
|
+
return join(dirname(fileURLToPath(import.meta.url)), 'prospective-inventory-cli.js');
|
|
9
|
+
}
|
|
10
|
+
function parseDocument(response) {
|
|
11
|
+
const parsed = JSON.parse(response.trim());
|
|
12
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
13
|
+
throw new Error('preflight output must be an object');
|
|
14
|
+
const record = parsed;
|
|
15
|
+
if (!Array.isArray(record['commands']) || !Array.isArray(record['memory_refs'])) {
|
|
16
|
+
throw new Error('preflight output must contain command and memory-ref arrays');
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
commands: chatInventoryCommandRows(record['commands']),
|
|
20
|
+
memory_refs: chatInventoryMemoryRows(record['memory_refs']),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function runProspectiveInventory(input) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const env = {
|
|
26
|
+
...process.env,
|
|
27
|
+
CRTR_PROFILE_ID: input.profileId ?? '',
|
|
28
|
+
[FRONT_DOOR_ENV]: '1',
|
|
29
|
+
};
|
|
30
|
+
delete env['CRTR_NODE_ID'];
|
|
31
|
+
const child = spawn(process.execPath, [entryPath()], {
|
|
32
|
+
cwd: input.cwd,
|
|
33
|
+
env,
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
|
|
35
|
+
});
|
|
36
|
+
let stdout = '';
|
|
37
|
+
let stderr = '';
|
|
38
|
+
let response = '';
|
|
39
|
+
let settled = false;
|
|
40
|
+
const timer = setTimeout(() => {
|
|
41
|
+
if (settled)
|
|
42
|
+
return;
|
|
43
|
+
settled = true;
|
|
44
|
+
child.kill('SIGKILL');
|
|
45
|
+
reject(new Error('prospective inventory preflight timed out'));
|
|
46
|
+
}, PREFLIGHT_TIMEOUT_MS);
|
|
47
|
+
timer.unref?.();
|
|
48
|
+
child.stdout.setEncoding('utf8');
|
|
49
|
+
child.stderr.setEncoding('utf8');
|
|
50
|
+
const responseStream = child.stdio[3];
|
|
51
|
+
if (responseStream === null || responseStream === undefined || !('setEncoding' in responseStream)) {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
child.kill('SIGKILL');
|
|
54
|
+
reject(new Error('prospective inventory preflight response channel unavailable'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
responseStream.setEncoding('utf8');
|
|
58
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
59
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
60
|
+
responseStream.on('data', (chunk) => { response += chunk; });
|
|
61
|
+
child.on('error', (error) => {
|
|
62
|
+
if (settled)
|
|
63
|
+
return;
|
|
64
|
+
settled = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
reject(error);
|
|
67
|
+
});
|
|
68
|
+
child.on('close', (code, signal) => {
|
|
69
|
+
if (settled)
|
|
70
|
+
return;
|
|
71
|
+
settled = true;
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
const diagnostics = [stderr.trim(), stdout.trim()].filter(Boolean).join('\n');
|
|
74
|
+
if (code !== 0) {
|
|
75
|
+
reject(new Error(`prospective inventory preflight exited ${code ?? signal ?? 'unknown'}${diagnostics === '' ? '' : `: ${diagnostics}`}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
resolve(parseDocument(response));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
reject(new Error(`${message}${diagnostics === '' ? '' : `: ${diagnostics}`}`));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
child.stdin.end(JSON.stringify({ extensions: input.extensions }));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -88,7 +88,9 @@ export declare function resolveForkSource(value: string): string;
|
|
|
88
88
|
* shell, not from inside a node — there is no profile to inherit, so this runs
|
|
89
89
|
* the same startup selector the front door uses (MRU covering `cwd`, else the
|
|
90
90
|
* root-profile prompt/default). */
|
|
91
|
-
export declare function resolveProfileId(explicit: string | null | undefined, spawner: string | null, cwd: string
|
|
91
|
+
export declare function resolveProfileId(explicit: string | null | undefined, spawner: string | null, cwd: string, options?: {
|
|
92
|
+
readOnlySelection?: boolean;
|
|
93
|
+
}): Promise<string | null>;
|
|
92
94
|
/** Reject a permanently invalid launch recipe against the registry that will
|
|
93
95
|
* serve it. Registered-but-unauthenticated/cooling requests intentionally pass
|
|
94
96
|
* this authoring check so broker policy for transient availability is unchanged. */
|
|
@@ -13,7 +13,7 @@ import { accessSync, constants, cpSync, readdirSync, existsSync, rmSync } from '
|
|
|
13
13
|
import { isAbsolute, resolve, join } from 'node:path';
|
|
14
14
|
import { spawnNode, currentNodeContext, rootOfSpine, newNodeId, preflightNodeId } from './nodes.js';
|
|
15
15
|
import { loadProfileManifest } from '../profiles/manifest.js';
|
|
16
|
-
import { selectProfileForCwd } from '../profiles/select.js';
|
|
16
|
+
import { selectProfileForCwd, selectProfileForCwdReadOnly } from '../profiles/select.js';
|
|
17
17
|
import { buildLaunchSpecAsync, buildPiArgv } from './launch.js';
|
|
18
18
|
import { createManagedWorktree, rollbackManagedWorktree } from '../worktree.js';
|
|
19
19
|
import { usage, brokerLaunchFailed } from '../errors.js';
|
|
@@ -153,7 +153,7 @@ export function resolveForkSource(value) {
|
|
|
153
153
|
* shell, not from inside a node — there is no profile to inherit, so this runs
|
|
154
154
|
* the same startup selector the front door uses (MRU covering `cwd`, else the
|
|
155
155
|
* root-profile prompt/default). */
|
|
156
|
-
export async function resolveProfileId(explicit, spawner, cwd) {
|
|
156
|
+
export async function resolveProfileId(explicit, spawner, cwd, options = {}) {
|
|
157
157
|
if (explicit === null)
|
|
158
158
|
return null;
|
|
159
159
|
if (explicit !== undefined && explicit !== '') {
|
|
@@ -161,7 +161,9 @@ export async function resolveProfileId(explicit, spawner, cwd) {
|
|
|
161
161
|
}
|
|
162
162
|
if (spawner !== null)
|
|
163
163
|
return getNode(spawner)?.profile_id ?? null;
|
|
164
|
-
return
|
|
164
|
+
return options.readOnlySelection === true
|
|
165
|
+
? selectProfileForCwdReadOnly(cwd)
|
|
166
|
+
: selectProfileForCwd(cwd);
|
|
165
167
|
}
|
|
166
168
|
function requestFromLaunch(launch) {
|
|
167
169
|
const intent = launch.modelIntent === undefined
|
|
@@ -48,27 +48,6 @@ function attr(s) {
|
|
|
48
48
|
.replace(/</g, '<')
|
|
49
49
|
.replace(/>/g, '>');
|
|
50
50
|
}
|
|
51
|
-
function gitRootOf(p) {
|
|
52
|
-
let d = p;
|
|
53
|
-
const root = parse(d).root;
|
|
54
|
-
while (true) {
|
|
55
|
-
if (pathExists(`${d}${sep}.git`))
|
|
56
|
-
return d;
|
|
57
|
-
if (d === root)
|
|
58
|
-
return null;
|
|
59
|
-
const parent = dirname(d);
|
|
60
|
-
if (parent === d)
|
|
61
|
-
return null;
|
|
62
|
-
d = parent;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function disp(p) {
|
|
66
|
-
const root = gitRootOf(p);
|
|
67
|
-
if (!root)
|
|
68
|
-
return p;
|
|
69
|
-
const rel = relative(root, p);
|
|
70
|
-
return rel === '' ? '.' : rel;
|
|
71
|
-
}
|
|
72
51
|
function isJunkAncestor(dir) {
|
|
73
52
|
return dir.split(sep).some((segment) => JUNK_DIRS.has(segment));
|
|
74
53
|
}
|
|
@@ -184,16 +163,26 @@ function docContentKey(doc) {
|
|
|
184
163
|
return null;
|
|
185
164
|
return `content-sha256:${createHash('sha256').update(body).digest('hex')}`;
|
|
186
165
|
}
|
|
166
|
+
// Two shapes: element-with-body means the content is here; self-closing means
|
|
167
|
+
// it is not — a preview carries its routing line as `readWhen` metadata so the
|
|
168
|
+
// one-liner can never be mistaken for the document's content. No disk path:
|
|
169
|
+
// agents address memory by name through the command surface, and the explicit
|
|
170
|
+
// read leaf already returns `path`.
|
|
187
171
|
function renderDocEnvelope(doc, rung) {
|
|
188
172
|
if (rung === 'none')
|
|
189
173
|
return null;
|
|
190
|
-
let
|
|
191
|
-
if (rung === 'content')
|
|
192
|
-
body = doc.body.trim();
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
174
|
+
let attrs = `kind="${attr(doc.kind)}" name="${attr(doc.name)}"`;
|
|
175
|
+
if (rung === 'content') {
|
|
176
|
+
const body = doc.body.trim();
|
|
177
|
+
if (body !== '')
|
|
178
|
+
return `<memory ${attrs}>\n${body}\n</memory>`;
|
|
179
|
+
}
|
|
180
|
+
else if (rung === 'preview') {
|
|
181
|
+
const pl = previewLine(doc);
|
|
182
|
+
if (pl !== '')
|
|
183
|
+
attrs += ` readWhen="${attr(pl)}"`;
|
|
184
|
+
}
|
|
185
|
+
return `<memory ${attrs} />`;
|
|
197
186
|
}
|
|
198
187
|
/** Render candidates at their matched rungs, gate-checked and deduped against
|
|
199
188
|
* the transcript set (higher rungs pierce lower; a delivery records at its
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InjectedDocs } from './injected-store.js';
|
|
2
|
-
/** The system-prompt
|
|
3
|
-
*
|
|
2
|
+
/** The system-prompt substrate splice for `nodeId` — shipped-layer protocol
|
|
3
|
+
* prose followed by the `<memory kind="preference">` block: assemble
|
|
4
|
+
* the node's subject from the canvas-db, render the pure splice, then swap the
|
|
4
5
|
* literal `$CRTR_CONTEXT_DIR` env-var token for the node's real absolute path
|
|
5
6
|
* so agents don't paste it into the non-expanding Write tool (see
|
|
6
7
|
* interpolateNodePaths). A null subject (unknown id) still renders the
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
import { interpolateNodePaths } from '../canvas/paths.js';
|
|
9
9
|
import { assembleNodeSubject } from './subject.js';
|
|
10
10
|
import { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
|
|
11
|
-
/** The system-prompt
|
|
12
|
-
*
|
|
11
|
+
/** The system-prompt substrate splice for `nodeId` — shipped-layer protocol
|
|
12
|
+
* prose followed by the `<memory kind="preference">` block: assemble
|
|
13
|
+
* the node's subject from the canvas-db, render the pure splice, then swap the
|
|
13
14
|
* literal `$CRTR_CONTEXT_DIR` env-var token for the node's real absolute path
|
|
14
15
|
* so agents don't paste it into the non-expanding Write tool (see
|
|
15
16
|
* interpolateNodePaths). A null subject (unknown id) still renders the
|
|
@@ -183,9 +183,16 @@ function compareTreePosition(a, b) {
|
|
|
183
183
|
}
|
|
184
184
|
return as.length - bs.length;
|
|
185
185
|
}
|
|
186
|
+
/** A doc the running agent can revise through `crtr memory edit`: any native
|
|
187
|
+
* scope-store doc (user/profile/project/node). Shipped docs — builtin and
|
|
188
|
+
* plugin-mounted — are not editable from a node; the preference render treats
|
|
189
|
+
* them as operating protocol, not memory (see buildPreferencesBlock). */
|
|
190
|
+
function isAgentEditable(doc) {
|
|
191
|
+
return doc.scope !== 'builtin' && doc.plugin === undefined;
|
|
192
|
+
}
|
|
186
193
|
/** STEP 2 — split deduped `winners` by rung and render each group in its
|
|
187
194
|
* display order: `content` docs first, as clean prose (general→specific,
|
|
188
|
-
* then tree position, then filename — no tree chrome
|
|
195
|
+
* then tree position, then filename — no tree chrome); every
|
|
189
196
|
* other winner (preview/name/hidden-bootless) folds into ONE catalog tree
|
|
190
197
|
* exactly as before. `nodeLocalSet` marks which winners came from the
|
|
191
198
|
* node-local store (ranked most-specific in content ordering); omit it for a
|
|
@@ -489,37 +496,62 @@ function buildSubPersonaMenu(kind) {
|
|
|
489
496
|
lines.join('\n'));
|
|
490
497
|
}
|
|
491
498
|
// ---------------------------------------------------------------------------
|
|
492
|
-
// 1. Preferences — `<memory kind="preference">` (system prompt).
|
|
499
|
+
// 1. Preferences — protocol prose + `<memory kind="preference">` (system prompt).
|
|
493
500
|
// ---------------------------------------------------------------------------
|
|
494
|
-
/** The system-prompt
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
501
|
+
/** The system-prompt substrate splice, TWO parts in one string.
|
|
502
|
+
*
|
|
503
|
+
* 1. The operating protocol: shipped (builtin/plugin) content-rung bodies as
|
|
504
|
+
* bare prose, plus the sub-persona menu. No memory envelope — the protocol
|
|
505
|
+
* is the rules of the node's world, and wrapping it as recalled "memory:
|
|
506
|
+
* preference" content gave it softer epistemic status than the
|
|
507
|
+
* instructions it is; the intro/outro sentences ("how the user wants you
|
|
508
|
+
* to behave", "update the preference") were also false of it.
|
|
509
|
+
* 2. The `<memory kind="preference">` block: the always-present memory-usage
|
|
510
|
+
* guidance, then (when any are eligible) each agent-editable doc wrapped
|
|
511
|
+
* `<preference name="…">…</preference>` — the name is the edit handle the
|
|
512
|
+
* outro's update directive needs — then the preview/name catalog tree.
|
|
513
|
+
* Every frame sentence is true of everything inside it.
|
|
514
|
+
*
|
|
515
|
+
* Ordering across the two parts is unchanged from the single-block render
|
|
516
|
+
* (shipped→editable was already the scope sort), so the shared cache prefix
|
|
517
|
+
* across nodes is preserved. ALWAYS returns a non-empty splice — the guidance
|
|
518
|
+
* frame is unconditional, so it is never empty even when no preference is
|
|
519
|
+
* eligible. */
|
|
500
520
|
function buildPreferencesBlock(subject, seen) {
|
|
501
|
-
let body = MEMORY_USAGE_GUIDANCE;
|
|
502
521
|
let contentDocs = [];
|
|
503
522
|
let tree = '';
|
|
523
|
+
let protocol = '';
|
|
524
|
+
let memBody = MEMORY_USAGE_GUIDANCE;
|
|
504
525
|
if (subject !== null) {
|
|
505
526
|
const winners = selectWinners(subject, 'preference');
|
|
506
527
|
seedBootDelivery(seen, winners);
|
|
507
528
|
const grouped = renderGrouped(winners, 'preferences');
|
|
508
529
|
contentDocs = grouped.contentDocs;
|
|
509
530
|
tree = grouped.tree;
|
|
531
|
+
const shipped = contentDocs
|
|
532
|
+
.filter((d) => !isAgentEditable(d))
|
|
533
|
+
.map((d) => d.body.trim())
|
|
534
|
+
.filter((b) => b !== '');
|
|
535
|
+
const editable = contentDocs
|
|
536
|
+
.filter((d) => isAgentEditable(d))
|
|
537
|
+
.map((d) => {
|
|
538
|
+
const b = d.body.trim();
|
|
539
|
+
return b === '' ? '' : `<preference name="${d.name}">\n${b}\n</preference>`;
|
|
540
|
+
})
|
|
541
|
+
.filter((b) => b !== '');
|
|
510
542
|
const subPersonaMenu = buildSubPersonaMenu(subject.kind);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
body += `\n\n${PREFERENCES_OUTRO}`;
|
|
543
|
+
protocol = [...shipped, ...(subPersonaMenu === '' ? [] : [subPersonaMenu])].join('\n\n');
|
|
544
|
+
if (editable.length > 0 || tree !== '') {
|
|
545
|
+
memBody += `\n\n${PREFERENCES_INTRO}`;
|
|
546
|
+
if (editable.length > 0)
|
|
547
|
+
memBody += `\n\n${editable.join('\n\n')}`;
|
|
548
|
+
if (tree !== '')
|
|
549
|
+
memBody += `\n\n${tree}`;
|
|
550
|
+
memBody += `\n\n${PREFERENCES_OUTRO}`;
|
|
520
551
|
}
|
|
521
552
|
}
|
|
522
|
-
|
|
553
|
+
const memoryBlock = `<memory kind="preference">\n${memBody}\n</memory>`;
|
|
554
|
+
return { block: protocol === '' ? memoryBlock : `${protocol}\n\n${memoryBlock}`, contentDocs, tree };
|
|
523
555
|
}
|
|
524
556
|
export function renderPreferencesForSubject(subject, seen) {
|
|
525
557
|
return buildPreferencesBlock(subject, seen);
|
|
@@ -14,7 +14,7 @@ export declare function rungAtLeast(r: Rung, min: Rung): boolean;
|
|
|
14
14
|
* authored rung against its project relationship's cap. */
|
|
15
15
|
export declare function minRung(a: Rung, b: Rung): Rung;
|
|
16
16
|
/** Strip an optional `NN-` ordering prefix from ONE path segment (file or
|
|
17
|
-
* directory display name). `00-runtime-base` -> `runtime-base`; `spine` (no
|
|
17
|
+
* directory display name). `00-runtime-base/00-authoring` -> `runtime-base/authoring`; `spine` (no
|
|
18
18
|
* prefix) is unchanged. */
|
|
19
19
|
export declare function normalizeNameSegment(segment: string): string;
|
|
20
20
|
/** Normalize a full slash-separated path-derived doc name by stripping the
|
|
@@ -111,6 +111,10 @@ export interface SubstrateDoc extends SubstrateSchema {
|
|
|
111
111
|
name: string;
|
|
112
112
|
/** The scope this doc resolved from. */
|
|
113
113
|
scope: MemoryScope;
|
|
114
|
+
/** Owning plugin name when the doc is mounted from an installed plugin;
|
|
115
|
+
* undefined for a native scope doc. Shipped (builtin/plugin) docs and
|
|
116
|
+
* agent-editable scope docs render differently at boot (see renderGrouped). */
|
|
117
|
+
plugin?: string;
|
|
114
118
|
/** Absolute path to the source .md. */
|
|
115
119
|
path: string;
|
|
116
120
|
/** Document body, frontmatter stripped. */
|
|
@@ -43,18 +43,18 @@ export function minRung(a, b) {
|
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
// Display-name normalization — the optional `NN-` ordering pin. A doc's
|
|
45
45
|
// physical path may carry a two-digit numeric prefix on a file or directory
|
|
46
|
-
// segment (`00-runtime-base
|
|
46
|
+
// segment (`00-runtime-base/`, `01-spine/`) purely to pin structural order
|
|
47
47
|
// against natural sort (design: builtin prompt corpus, used sparingly). The
|
|
48
48
|
// prefix is NEVER part of the doc's identity: every identity-facing surface —
|
|
49
49
|
// MemoryDoc.name, direct lookup, leaf fallback, `crtr memory read`/`list`,
|
|
50
50
|
// prompt render, on-read display — derives from the NORMALIZED segments, so
|
|
51
|
-
// `00-runtime-base` displays/dedups/resolves as `runtime-base`. When a doc
|
|
51
|
+
// `00-runtime-base/00-authoring` displays/dedups/resolves as `runtime-base/authoring`. When a doc
|
|
52
52
|
// sets an explicit frontmatter `name`, that normalized name wins instead of
|
|
53
53
|
// the path-derived fallback. Only the physical path keeps the prefix.
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
55
|
const NUMERIC_PREFIX_RE = /^\d{2}-/;
|
|
56
56
|
/** Strip an optional `NN-` ordering prefix from ONE path segment (file or
|
|
57
|
-
* directory display name). `00-runtime-base` -> `runtime-base`; `spine` (no
|
|
57
|
+
* directory display name). `00-runtime-base/00-authoring` -> `runtime-base/authoring`; `spine` (no
|
|
58
58
|
* prefix) is unchanged. */
|
|
59
59
|
export function normalizeNameSegment(segment) {
|
|
60
60
|
return segment.replace(NUMERIC_PREFIX_RE, '');
|
|
@@ -142,7 +142,7 @@ export function parseSubstrateDoc(doc) {
|
|
|
142
142
|
const schema = parseSubstrateFrontmatter(doc.frontmatter);
|
|
143
143
|
if (schema === null)
|
|
144
144
|
return null;
|
|
145
|
-
return { ...schema, name: doc.name, scope: doc.scope, path: doc.path, body: doc.body, projectMemory: doc.projectMemory };
|
|
145
|
+
return { ...schema, name: doc.name, scope: doc.scope, plugin: doc.plugin, path: doc.path, body: doc.body, projectMemory: doc.projectMemory };
|
|
146
146
|
}
|
|
147
147
|
/** The kind a doc resolves to for `--kind` filtering/matching: the substrate
|
|
148
148
|
* kind when the frontmatter parses as a valid substrate doc, else the raw
|
|
@@ -326,6 +326,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
|
|
|
326
326
|
surface: "specialized";
|
|
327
327
|
owner: string;
|
|
328
328
|
};
|
|
329
|
+
humanActions: {
|
|
330
|
+
surface: "specialized";
|
|
331
|
+
owner: string;
|
|
332
|
+
};
|
|
329
333
|
};
|
|
330
334
|
/** The exhaustive top-level key list used by config persistence and help. */
|
|
331
335
|
export declare const SCOPE_CONFIG_KEYS: (keyof ScopeConfig)[];
|
|
@@ -389,6 +389,7 @@ export const CONFIG_SETTINGS_COVERAGE = {
|
|
|
389
389
|
remoteCanvas: { surface: 'specialized', owner: 'canvas config commands' },
|
|
390
390
|
spawnEnv: { surface: 'specialized', owner: 'spawn environment allow-list' },
|
|
391
391
|
bin: { surface: 'specialized', owner: 'bare-binary contributions on every node PATH' },
|
|
392
|
+
humanActions: { surface: 'specialized', owner: 'scope-configured human completion actions' },
|
|
392
393
|
};
|
|
393
394
|
/** The exhaustive top-level key list used by config persistence and help. */
|
|
394
395
|
export const SCOPE_CONFIG_KEYS = Object.keys(CONFIG_SETTINGS_COVERAGE);
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { after, afterEach, beforeEach, test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { createNode, getNode, listNodes } from '../../../core/canvas/canvas.js';
|
|
7
7
|
import { closeDb } from '../../../core/canvas/db.js';
|
|
8
8
|
import { CrtrError } from '../../../core/errors.js';
|
|
9
|
-
import { createProfile, loadProfileManifest, pauseProfile, setProfileDefaultKind } from '../../../core/profiles/manifest.js';
|
|
9
|
+
import { createProfile, loadProfileManifest, pauseProfile, profileManifestPath, ROOT_PROFILE_ID, setProfileDefaultKind } from '../../../core/profiles/manifest.js';
|
|
10
10
|
import { resetScopeCache } from '../../../core/scope.js';
|
|
11
11
|
import { reviveNode } from '../../../core/runtime/revive.js';
|
|
12
|
+
import { resolveLaunchTarget } from '../../../core/runtime/launch-target.js';
|
|
12
13
|
import { spawnNode } from '../../../core/runtime/nodes.js';
|
|
13
|
-
import { handleCreate, resolveCreateKind } from '../handlers/nodes.js';
|
|
14
|
+
import { handleConfig, handleCreate, resolveCreateKind } from '../handlers/nodes.js';
|
|
14
15
|
const previousCrtrHome = process.env['CRTR_HOME'];
|
|
15
16
|
const previousHome = process.env.HOME;
|
|
16
17
|
let home = '';
|
|
@@ -101,6 +102,54 @@ test('the birth gate refuses a paused profile for a daemon-side spawn', () => {
|
|
|
101
102
|
});
|
|
102
103
|
assert.equal(listNodes().length, beforeRows);
|
|
103
104
|
});
|
|
105
|
+
test('prospective root selection stays read-only before the root profile is scaffolded', async () => {
|
|
106
|
+
const manifestPath = profileManifestPath(ROOT_PROFILE_ID);
|
|
107
|
+
assert.equal(existsSync(manifestPath), false);
|
|
108
|
+
const target = await resolveLaunchTarget({ root: true, cwd }, { readOnlyProfileSelection: true });
|
|
109
|
+
assert.equal(target.profileId, ROOT_PROFILE_ID);
|
|
110
|
+
assert.equal(target.profile, null);
|
|
111
|
+
assert.equal(target.kind, 'general');
|
|
112
|
+
assert.equal(target.cwd, cwd);
|
|
113
|
+
assert.equal(existsSync(manifestPath), false);
|
|
114
|
+
});
|
|
115
|
+
test('target-scoped prospective validation does not rescope node writes', async () => {
|
|
116
|
+
mkdirSync(join(cwd, '.crouter'), { recursive: true });
|
|
117
|
+
writeFileSync(join(cwd, '.crouter', 'config.json'), JSON.stringify({
|
|
118
|
+
kinds: { 'target-only': { whenToUse: 'Only in this project.' } },
|
|
119
|
+
}));
|
|
120
|
+
const target = await resolveLaunchTarget({ root: true, cwd, kind: 'target-only' }, { readOnlyProfileSelection: true });
|
|
121
|
+
assert.equal(target.kind, 'target-only');
|
|
122
|
+
const create = {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
path: '/v1/nodes',
|
|
125
|
+
params: {},
|
|
126
|
+
query: new URLSearchParams(),
|
|
127
|
+
body: { root: true, profile: null, cwd, kind: 'target-only' },
|
|
128
|
+
};
|
|
129
|
+
await assert.rejects(() => handleCreate(create), /unknown kind: target-only/);
|
|
130
|
+
const id = 'ambient-kind-write';
|
|
131
|
+
createNode({
|
|
132
|
+
node_id: id,
|
|
133
|
+
name: id,
|
|
134
|
+
created: new Date().toISOString(),
|
|
135
|
+
cwd,
|
|
136
|
+
kind: 'general',
|
|
137
|
+
mode: 'base',
|
|
138
|
+
lifecycle: 'terminal',
|
|
139
|
+
status: 'active',
|
|
140
|
+
parent: null,
|
|
141
|
+
profile_id: null,
|
|
142
|
+
});
|
|
143
|
+
const patch = {
|
|
144
|
+
method: 'PATCH',
|
|
145
|
+
path: `/v1/nodes/${id}/config`,
|
|
146
|
+
params: { id },
|
|
147
|
+
query: new URLSearchParams(),
|
|
148
|
+
body: { kind: 'target-only' },
|
|
149
|
+
};
|
|
150
|
+
await assert.rejects(() => handleConfig(patch), /unknown kind: target-only/);
|
|
151
|
+
assert.equal(getNode(id)?.kind, 'general');
|
|
152
|
+
});
|
|
104
153
|
test('create kind resolves from the profile default when omitted', () => {
|
|
105
154
|
const profile = createProfile('default kind', [{ path: cwd, memory: 'content' }]);
|
|
106
155
|
setProfileDefaultKind(profile.profileId, 'developer');
|