@north-light/crouter 0.3.271 → 0.3.273
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/commands/canvas-browse.js +1 -1
- package/dist/commands/cron.js +9 -8
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +36 -0
- package/dist/commands/memory/read.js +6 -2
- package/dist/commands/memory/write.js +7 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/core/command-plugins/transport/exec-invoke.js +1 -1
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +3 -3
- package/dist/core/nested-stores.d.ts +1 -1
- package/dist/core/nested-stores.js +4 -2
- package/dist/core/runtime/broker-extension-render.js +4 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -64,7 +64,7 @@ export const browseLeaf = defineLeaf({
|
|
|
64
64
|
profileId,
|
|
65
65
|
source,
|
|
66
66
|
viewedNodeIds: async () => new Set((await cliClient().listFocuses()).map((focus) => focus.node_id)),
|
|
67
|
-
closeNode: (id) => cliClient().closeNode(id, { finish: true, caller_pid: process.ppid }).then((r) => ({ closed: r.closed })),
|
|
67
|
+
closeNode: (id) => cliClient().closeNode(id, process.ppid > 0 ? { finish: true, caller_pid: process.ppid } : { finish: true }).then((r) => ({ closed: r.closed })),
|
|
68
68
|
});
|
|
69
69
|
},
|
|
70
70
|
});
|
package/dist/commands/cron.js
CHANGED
|
@@ -63,12 +63,11 @@ function parseRunTimeout(raw) {
|
|
|
63
63
|
}
|
|
64
64
|
return seconds;
|
|
65
65
|
}
|
|
66
|
-
/** The
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const p = envProfileId();
|
|
66
|
+
/** The profile scope for a cron route: an explicit selector overrides the
|
|
67
|
+
* calling agent's profile. Outside a profile there is no scope to narrow to,
|
|
68
|
+
* so the caller sees the whole canvas home. */
|
|
69
|
+
function scopeQuery(profile) {
|
|
70
|
+
const p = typeof profile === 'string' && profile.trim() !== '' ? canonicalProfileId(profile) : envProfileId();
|
|
72
71
|
return p !== undefined && p.trim() !== '' ? { profile: p } : undefined;
|
|
73
72
|
}
|
|
74
73
|
/** Resolve the chosen-else-inherited cwd, and refuse one that is not a
|
|
@@ -270,7 +269,7 @@ const addLeaf = defineLeaf({
|
|
|
270
269
|
render: (r) => {
|
|
271
270
|
const recur = String(r['recur']);
|
|
272
271
|
const cadence = recur === 'none' ? 'one-shot (row deleted after the run)' : recur;
|
|
273
|
-
return `Cron armed — ${String(r['cron_id'])}.\n\n- next fire: ${String(r['fire_at'])}\n- cadence: ${cadence}\n\nIt runs daemon-side; nothing more to do here. Inspect it with \`crtr cron show ${String(r['cron_id'])}\`; cancel
|
|
272
|
+
return `Cron armed — ${String(r['cron_id'])}.\n\n- next fire: ${String(r['fire_at'])}\n- cadence: ${cadence}\n\nIt runs daemon-side; nothing more to do here. Inspect it with \`crtr cron show ${String(r['cron_id'])}\`; cancel with \`crtr cron cancel ${String(r['cron_id'])}\` from its profile, or pass \`--profile <profile>\` from another profile.`;
|
|
274
273
|
},
|
|
275
274
|
});
|
|
276
275
|
// cron list
|
|
@@ -494,9 +493,11 @@ const cancelLeaf = defineLeaf({
|
|
|
494
493
|
summary: 'remove one cron',
|
|
495
494
|
params: [
|
|
496
495
|
{ kind: 'positional', name: 'cron-id', required: true, constraint: 'The cron to remove (from `cron list`, or $CRTR_CRON_ID inside a run).' },
|
|
496
|
+
{ kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile scope to cancel within. Defaults to the calling node\'s profile; pass the profile id or manifest name for a cron armed under another profile.' },
|
|
497
497
|
],
|
|
498
498
|
output: [],
|
|
499
499
|
outputKind: 'object',
|
|
500
|
+
dynamicState: () => profilesStateBlock(),
|
|
500
501
|
effects: ['Deletes the cron row and its run history; kills any in-flight run. A no-op if already gone.'],
|
|
501
502
|
},
|
|
502
503
|
run: async (input) => {
|
|
@@ -504,7 +505,7 @@ const cancelLeaf = defineLeaf({
|
|
|
504
505
|
const runId = process.env['CRTR_RUN_ID'];
|
|
505
506
|
await cliClient()
|
|
506
507
|
.cancelCron(id, {
|
|
507
|
-
...scopeQuery(),
|
|
508
|
+
...scopeQuery(input['profile']),
|
|
508
509
|
...(runId === undefined || runId === '' ? {} : { run_id: runId }),
|
|
509
510
|
})
|
|
510
511
|
.catch((err) => rethrowAsCliError(err, 'Check the id against `crtr cron list`.'));
|
|
@@ -283,6 +283,42 @@ test('a failing lint returns its declared rows and fails by exit code', async ()
|
|
|
283
283
|
process.exitCode = 0;
|
|
284
284
|
}
|
|
285
285
|
});
|
|
286
|
+
test('initializing a repository-root store does not lint its valid namespace as misplaced', async () => {
|
|
287
|
+
const fresh = join(base, 'fresh-root');
|
|
288
|
+
mkdirSync(fresh, { recursive: true });
|
|
289
|
+
const result = (await writeLeaf.run({
|
|
290
|
+
name: 'fresh-root',
|
|
291
|
+
kind: 'knowledge',
|
|
292
|
+
dir: fresh,
|
|
293
|
+
body: 'FRESH ROOT BODY',
|
|
294
|
+
whenAndWhyToRead: 'When opening this repository, read this because it explains the workspace.',
|
|
295
|
+
surface: ['{on: workspace-open, at: content}', '{on: read, match: "./**", at: content}'],
|
|
296
|
+
}));
|
|
297
|
+
assert.equal(result.findings, undefined, JSON.stringify(result.findings));
|
|
298
|
+
});
|
|
299
|
+
test('a targeted read suppresses malformed descendant-store warnings', async () => {
|
|
300
|
+
const noisy = join(base, 'noisy');
|
|
301
|
+
mkdirSync(storeDir(noisy), { recursive: true });
|
|
302
|
+
writeDoc(join(home, '.crouter', 'memory', 'quiet-target.md'), 'kind: knowledge', 'QUIET TARGET BODY');
|
|
303
|
+
writeDoc(join(noisy, 'unrelated', '.crouter', 'memory', 'doc.md'), 'kind: knowledge', 'UNRELATED BODY');
|
|
304
|
+
process.chdir(noisy);
|
|
305
|
+
resetScopeCache();
|
|
306
|
+
const stderr = process.stderr;
|
|
307
|
+
const originalWrite = stderr.write;
|
|
308
|
+
const writes = [];
|
|
309
|
+
stderr.write = ((chunk) => {
|
|
310
|
+
writes.push(String(chunk));
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
try {
|
|
314
|
+
const read = (await readLeaf.run({ name: 'quiet-target', kind: undefined, frontmatter: false, scope: undefined, dir: undefined }));
|
|
315
|
+
assert.match(read.content, /QUIET TARGET BODY/);
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
stderr.write = originalWrite;
|
|
319
|
+
}
|
|
320
|
+
assert.equal(writes.some((line) => line.includes('memory store not mounted')), false, writes.join(''));
|
|
321
|
+
});
|
|
286
322
|
test('deleted history points at the revision holding the retained text, never a read that cannot answer', async () => {
|
|
287
323
|
await deleteLeaf.run({ name: 'acme/disposable', dir: root });
|
|
288
324
|
const result = (await historyLeaf.run({ name: 'acme/disposable', dir: root }));
|
|
@@ -4,7 +4,7 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
4
4
|
import { CrtrError } from '../../core/errors.js';
|
|
5
5
|
import { memoryExtensionEffectiveCatalog, projectEffectiveMemoryExtensions } from '../../core/memory/extensions.js';
|
|
6
6
|
import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
|
|
7
|
-
import { createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
|
|
7
|
+
import { ambientMemoryTarget, createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
|
|
8
8
|
import { expandShellBlocks, hasShellBlocks, makeNodeShellRunner } from '../../core/runtime/shell-expansion.js';
|
|
9
9
|
import { emptyContextExposureState, exposureTarget, loadContextExposureState, registerDocumentExposure, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
|
|
10
10
|
import { dirDedupKey, docsByName, isDirName, renderDirListing } from '../../core/substrate/listings.js';
|
|
@@ -121,7 +121,10 @@ export const readLeaf = defineLeaf({
|
|
|
121
121
|
...(selector.scope === undefined ? {} : { scope: selector.scope }),
|
|
122
122
|
includeDescendants: true,
|
|
123
123
|
};
|
|
124
|
-
|
|
124
|
+
// A named read must not turn malformed stores elsewhere in the corpus
|
|
125
|
+
// into warnings about the requested document. `memory lint` owns that
|
|
126
|
+
// corpus health report; resolution still includes every candidate.
|
|
127
|
+
listingByName = linkByName = docsByName(listAllMemoryDocs(selector.scope, true, true));
|
|
125
128
|
try {
|
|
126
129
|
set = resolveMemoryCandidates(nameRaw, resolveOpts);
|
|
127
130
|
}
|
|
@@ -190,6 +193,7 @@ export const readLeaf = defineLeaf({
|
|
|
190
193
|
subject,
|
|
191
194
|
target,
|
|
192
195
|
byName: listingByName,
|
|
196
|
+
memoryTarget: ambientMemoryTarget(),
|
|
193
197
|
listings: 'blocks',
|
|
194
198
|
explicit: true,
|
|
195
199
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { envNodeId, envProfileId } from '../../shared/env.js';
|
|
2
|
-
import { join } from 'node:path';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
3
|
import { defineLeaf } from '../../core/command.js';
|
|
4
4
|
import { usage } from '../../core/errors.js';
|
|
5
5
|
import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
|
|
6
6
|
import { lintMemoryDocument, resolvableCanonicalNames } from '../../core/memory/lint.js';
|
|
7
7
|
import { descendantStoreRoots } from '../../core/nested-stores.js';
|
|
8
8
|
import { projectScopeRoots } from '../../core/scope.js';
|
|
9
|
-
import { loadMemoryTargetView } from '../../core/memory-resolver.js';
|
|
9
|
+
import { loadMemoryTargetView, openProjectMemoryStore } from '../../core/memory-resolver.js';
|
|
10
10
|
import { parsedSubstrateSurfaces } from '../../core/substrate/frontmatter-validation.js';
|
|
11
11
|
import { ensureDir, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
|
|
12
12
|
import { associateRepository } from '../../core/memory/repository-association.js';
|
|
@@ -187,7 +187,11 @@ export const writeLeaf = defineLeaf({
|
|
|
187
187
|
const surfaceEntries = parsedSubstrateSurfaces(frontmatter);
|
|
188
188
|
const carriesReference = docLinkNames(body).length > 0 || surfaceEntries.some((entry) => entry.on === 'memory-read');
|
|
189
189
|
const carriesBootEntry = surfaceEntries.some((entry) => entry.on === 'boot');
|
|
190
|
-
|
|
190
|
+
// Initializing created the root declaration after selector resolution, so
|
|
191
|
+
// validate against a freshly described store: the stale descriptor has no
|
|
192
|
+
// repository-root association yet and would reject its valid namespace.
|
|
193
|
+
const lintStore = initializing ? openProjectMemoryStore(store.ownerDir ?? dirname(dirname(memoryDir))) : store;
|
|
194
|
+
const lint = lintMemoryDocument(lintStore, path, {
|
|
191
195
|
...(carriesReference
|
|
192
196
|
? {
|
|
193
197
|
resolvable: resolvableCanonicalNames(loadMemoryTargetView({
|
|
@@ -100,7 +100,7 @@ const nodeClose = defineLeaf({
|
|
|
100
100
|
throw new InputError({ error: 'no_node', message: 'no node found in this pane to close', next: 'Pass --node <id>, or run from inside the agent\'s pane.' });
|
|
101
101
|
}
|
|
102
102
|
const res = await cliClient()
|
|
103
|
-
.closeNode(id, { caller_pid: process.ppid })
|
|
103
|
+
.closeNode(id, process.ppid > 0 ? { caller_pid: process.ppid } : {})
|
|
104
104
|
.catch((err) => rethrowAsCliError(err, 'List nodes with `crtr node inspect list`.'));
|
|
105
105
|
return { node_id: res.root, count: res.closed.length, closed_ids: res.closed, spared: res.spared };
|
|
106
106
|
},
|
|
@@ -21,7 +21,7 @@ const NEXT = 'Run `crtr pkg plugin show` or `crtr sys doctor` to inspect the plu
|
|
|
21
21
|
* Returns null if valid; otherwise returns a { field, receivedType } error.
|
|
22
22
|
* Transport-neutral: used by exec and HTTP transport adapters. */
|
|
23
23
|
export function validateDeclaredResult(output, result) {
|
|
24
|
-
return validateFields(output, result,
|
|
24
|
+
return validateFields(output, result, true);
|
|
25
25
|
}
|
|
26
26
|
/** Core leaf declarations may explicitly require a nullable field. */
|
|
27
27
|
export function validateCoreDeclaredResult(output, result) {
|
|
@@ -195,7 +195,7 @@ export declare function openProjectMemoryStore(ownerDir: string): MemoryStoreDes
|
|
|
195
195
|
/** Every store a target sees, in precedence order: node, each project store
|
|
196
196
|
* nearest-first (its enabled plugins after it), then profile, user, builtin.
|
|
197
197
|
* One real store reached twice contributes once, at its first position. */
|
|
198
|
-
export declare function memoryStoresInPrecedence(target: MemoryTarget, scope?: MemoryScope, includeDescendants?: boolean): MemoryStoreDescriptor[];
|
|
198
|
+
export declare function memoryStoresInPrecedence(target: MemoryTarget, scope?: MemoryScope, includeDescendants?: boolean, quiet?: boolean): MemoryStoreDescriptor[];
|
|
199
199
|
/** The native (non-plugin) MOUNTED store dirs in resolution precedence.
|
|
200
200
|
* Addresses a store directly when the document itself may be absent, which is
|
|
201
201
|
* how a DELETED doc's revision log is still found: history outlives the doc,
|
|
@@ -193,7 +193,7 @@ export function openProjectMemoryStore(ownerDir) {
|
|
|
193
193
|
/** Every store a target sees, in precedence order: node, each project store
|
|
194
194
|
* nearest-first (its enabled plugins after it), then profile, user, builtin.
|
|
195
195
|
* One real store reached twice contributes once, at its first position. */
|
|
196
|
-
export function memoryStoresInPrecedence(target, scope, includeDescendants = false) {
|
|
196
|
+
export function memoryStoresInPrecedence(target, scope, includeDescendants = false, quiet = false) {
|
|
197
197
|
const out = [];
|
|
198
198
|
const projectStores = (crtrRoot, projectMemory) => {
|
|
199
199
|
out.push(projectStoreDescriptor(dirname(crtrRoot), join(crtrRoot, 'memory'), projectMemory));
|
|
@@ -211,7 +211,7 @@ export function memoryStoresInPrecedence(target, scope, includeDescendants = fal
|
|
|
211
211
|
// nested one. They carry the neutral ceiling — no profile entry names
|
|
212
212
|
// them, and descendant discovery is off for every automatic delivery path.
|
|
213
213
|
if (includeDescendants) {
|
|
214
|
-
for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r))) {
|
|
214
|
+
for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r), quiet)) {
|
|
215
215
|
projectStores(root, NEUTRAL_PROJECT_MEMORY);
|
|
216
216
|
}
|
|
217
217
|
}
|
|
@@ -438,7 +438,7 @@ function makeView(stores, docs, scope) {
|
|
|
438
438
|
* with its migration remedy and contributes nothing. */
|
|
439
439
|
export function loadMemoryTargetView(target, opts = {}) {
|
|
440
440
|
const quiet = opts.quiet ?? false;
|
|
441
|
-
const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false);
|
|
441
|
+
const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false, quiet);
|
|
442
442
|
const docs = [];
|
|
443
443
|
for (const store of stores) {
|
|
444
444
|
if (store.mountStatus === 'absent')
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* excluding centrally gives the resolver and lint the identical call with no
|
|
10
10
|
* duplicated dedupe logic.
|
|
11
11
|
*/
|
|
12
|
-
export declare function descendantStoreRoots(ancestorRoots: string[]): string[];
|
|
12
|
+
export declare function descendantStoreRoots(ancestorRoots: string[], quiet?: boolean): string[];
|
|
13
13
|
/** Complete descendant-store discovery for state migrations. Unlike the
|
|
14
14
|
* interactive addressing lane, this filesystem walk has no time or depth
|
|
15
15
|
* budget, crosses embedded repositories, admits plugin-only `.crouter` roots,
|
|
@@ -39,7 +39,7 @@ const cache = new Map();
|
|
|
39
39
|
* excluding centrally gives the resolver and lint the identical call with no
|
|
40
40
|
* duplicated dedupe logic.
|
|
41
41
|
*/
|
|
42
|
-
export function descendantStoreRoots(ancestorRoots) {
|
|
42
|
+
export function descendantStoreRoots(ancestorRoots, quiet = false) {
|
|
43
43
|
const key = [...ancestorRoots].sort().join('\n');
|
|
44
44
|
const cached = cache.get(key);
|
|
45
45
|
if (cached !== undefined)
|
|
@@ -56,7 +56,9 @@ export function descendantStoreRoots(ancestorRoots) {
|
|
|
56
56
|
owners.push(owner);
|
|
57
57
|
}
|
|
58
58
|
const tripBudget = (owner) => {
|
|
59
|
-
|
|
59
|
+
if (!quiet) {
|
|
60
|
+
warn(`memory: nested-store discovery under ${owner} exceeded its ${TIME_BUDGET_MS}ms budget — nested stores may be missing from this listing; register deep stores explicitly (add their dirs to the selected profile's projects)`);
|
|
61
|
+
}
|
|
60
62
|
};
|
|
61
63
|
const found = [];
|
|
62
64
|
const foundSeen = new Set();
|
|
@@ -88,7 +88,10 @@ export function buildContextBearingsFromState(state, projectContext, situational
|
|
|
88
88
|
}
|
|
89
89
|
export function buildForkBearingsFromState(state, situational = '') {
|
|
90
90
|
const { node, fork_source: source } = state;
|
|
91
|
-
const
|
|
91
|
+
const sourceMandate = source?.status === 'active' || source?.status === 'idle'
|
|
92
|
+
? `Source node ${source.node_id} ("${fullName(source)}") is still live and still holds the mandate from the copied conversation; do not resume that work here.`
|
|
93
|
+
: 'The source node’s earlier graph position, lifecycle, children, schedules, worktree, situational context, and context-directory path do not carry over. Shared conversation, project, and operating context remain reference material.';
|
|
94
|
+
const bearings = ['<crtr-bearings>', identity(state), '', sourceMandate, '', '## Your node now', `Lifecycle: ${node.lifecycle}.`];
|
|
92
95
|
const map = graphMap(state);
|
|
93
96
|
bearings.push(...(map === '' ? ['Canvas: independent root with no parent or inherited graph edges.'] : ['', map]));
|
|
94
97
|
bearings.push(`Context directory: \`${contextDir(node.node_id)}\` (\`$CRTR_CONTEXT_DIR\`). Use this path in place of every source-node context path above.`);
|
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.273",
|
|
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.273",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|