@north-light/crouter 0.3.186 → 0.3.188
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-memory/internal/agent-shaping.md +1 -1
- package/dist/builtin-memory/internal/plugins.md +9 -0
- package/dist/clients/attach/viewer.js +326 -326
- package/dist/commands/pkg/plugin-manage.js +39 -5
- package/dist/core/__tests__/plugin-kinds.test.d.ts +1 -0
- package/dist/core/__tests__/plugin-kinds.test.js +180 -0
- package/dist/core/command-plugins/bundle.d.ts +6 -0
- package/dist/core/command-plugins/bundle.js +29 -7
- package/dist/core/config.d.ts +12 -1
- package/dist/core/config.js +114 -62
- package/dist/core/installed-plugins.d.ts +2 -0
- package/dist/core/installed-plugins.js +42 -0
- package/dist/core/resolver.d.ts +1 -1
- package/dist/core/resolver.js +7 -32
- package/dist/types.d.ts +14 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
package/dist/core/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { BINDING_CATALOG, BINDING_IDS, isAttachPaneBinding } from './keybindings
|
|
|
5
5
|
import { emitEvent } from './events/emit.js';
|
|
6
6
|
import { atomicWriteJson, readJsonIfExists, writeJson, ensureDir } from './fs-utils.js';
|
|
7
7
|
import { scopeRoot, requireScopeRoot, findProjectScopeRoots } from './scope.js';
|
|
8
|
+
import { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
8
9
|
import { profileRoot } from './profiles/manifest.js';
|
|
9
10
|
import { withExclusiveDirectoryLock } from './exclusive-lock.js';
|
|
10
11
|
import { SCOPE_CONFIG_KEYS } from './user-settings.js';
|
|
@@ -264,42 +265,20 @@ function mergeSpawnEnv(raw, base = []) {
|
|
|
264
265
|
}
|
|
265
266
|
return { allow: [...new Set([...base, ...extra])] };
|
|
266
267
|
}
|
|
267
|
-
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (typeof r.whenToUse !== 'string' || r.whenToUse.trim() === '')
|
|
276
|
-
return null;
|
|
277
|
-
const out = { whenToUse: r.whenToUse };
|
|
278
|
-
if (typeof r.model === 'string' && r.model.trim() !== '')
|
|
279
|
-
out.model = r.model;
|
|
280
|
-
if (typeof r.orchestratorModel === 'string' && r.orchestratorModel.trim() !== '')
|
|
281
|
-
out.orchestratorModel = r.orchestratorModel;
|
|
282
|
-
const isStringArray = (v) => Array.isArray(v) && v.every((item) => typeof item === 'string');
|
|
283
|
-
if (isStringArray(r.tools))
|
|
284
|
-
out.tools = r.tools;
|
|
285
|
-
if (isStringArray(r.extensions))
|
|
286
|
-
out.extensions = r.extensions;
|
|
287
|
-
if (isStringArray(r.availableTo))
|
|
288
|
-
out.availableTo = r.availableTo;
|
|
289
|
-
return out;
|
|
290
|
-
}
|
|
291
|
-
/** Validate one raw `kinds.<name>` entry as a sparse PATCH — an entry with no
|
|
292
|
-
* `whenToUse` that overrides individual launch knobs (model/orchestratorModel/
|
|
293
|
-
* tools/extensions/availableTo) of a kind some lower-precedence scope already
|
|
294
|
-
* defines. This is what lets a scope (e.g. a profile) persist ONLY
|
|
295
|
-
* `{ "model": "ultra" }` for a kind instead of freezing a full registry
|
|
296
|
-
* snapshot to disk (the same staleness trap `sparseModelLaddersToPersist`
|
|
297
|
-
* exists to avoid). Returns null when no valid field is present. */
|
|
298
|
-
function normalizeKindPatch(raw) {
|
|
268
|
+
/** Collect the valid `KindConfig` fields of one raw `kinds.<name>` entry —
|
|
269
|
+
* every field is optional here, `whenToUse` included; mistyped fields are
|
|
270
|
+
* dropped, never thrown (an invalid field in config.json must not break
|
|
271
|
+
* config parsing for every other kind — the loud gate is install time, see
|
|
272
|
+
* `invalidPluginKindsReasons`). Returns null when no valid field is present.
|
|
273
|
+
* Whether `whenToUse` is REQUIRED is `mergeKinds`'s call: only an entry that
|
|
274
|
+
* introduces a kind no lower layer defines needs one. */
|
|
275
|
+
function normalizeKindFields(raw) {
|
|
299
276
|
if (raw === null || typeof raw !== 'object')
|
|
300
277
|
return null;
|
|
301
278
|
const r = raw;
|
|
302
279
|
const out = {};
|
|
280
|
+
if (typeof r.whenToUse === 'string' && r.whenToUse.trim() !== '')
|
|
281
|
+
out.whenToUse = r.whenToUse;
|
|
303
282
|
if (typeof r.model === 'string' && r.model.trim() !== '')
|
|
304
283
|
out.model = r.model;
|
|
305
284
|
if (typeof r.orchestratorModel === 'string' && r.orchestratorModel.trim() !== '')
|
|
@@ -314,35 +293,102 @@ function normalizeKindPatch(raw) {
|
|
|
314
293
|
return Object.keys(out).length > 0 ? out : null;
|
|
315
294
|
}
|
|
316
295
|
/** Merge a raw `kinds` block over `base` (defaulting to the builtin registry):
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
296
|
+
* invalid entries are dropped, never thrown. Mirrors `mergeModelLadders`'s
|
|
297
|
+
* layer-over-defaults shape so a config layer can add or override a single
|
|
298
|
+
* kind without restating the whole registry. One rule, no entry shapes:
|
|
299
|
+
* - A kind some lower layer already defines: the entry FIELD-MERGES over it
|
|
300
|
+
* — any subset of fields, `whenToUse` included, so overriding just the
|
|
301
|
+
* spawn-menu guidance of a builtin kind never strips its model tier
|
|
302
|
+
* (the sparse-override shape `persistDefaultKindModel` writes).
|
|
303
|
+
* - A NEW kind: the entry defines it and must carry `whenToUse` (a kind
|
|
304
|
+
* with no spawn-menu gloss is meaningless); dropped otherwise.
|
|
305
|
+
* Deliberately NOT expressible: wholly replacing a lower layer's kind — a
|
|
306
|
+
* field can be overridden but never removed by omission. */
|
|
325
307
|
function mergeKinds(raw, base = defaultKindsConfig()) {
|
|
326
308
|
const out = { ...base };
|
|
327
309
|
if (raw !== null && typeof raw === 'object') {
|
|
328
310
|
for (const [kind, value] of Object.entries(raw)) {
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
331
|
-
out[kind] = normalized;
|
|
311
|
+
const fields = normalizeKindFields(value);
|
|
312
|
+
if (fields === null)
|
|
332
313
|
continue;
|
|
333
|
-
}
|
|
334
314
|
const existing = out[kind];
|
|
335
|
-
if (existing
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
out[kind] =
|
|
315
|
+
if (existing !== undefined) {
|
|
316
|
+
out[kind] = { ...existing, ...fields };
|
|
317
|
+
}
|
|
318
|
+
else if (fields.whenToUse !== undefined) {
|
|
319
|
+
out[kind] = fields;
|
|
320
|
+
}
|
|
340
321
|
}
|
|
341
322
|
}
|
|
342
323
|
return out;
|
|
343
324
|
}
|
|
325
|
+
/** Layer the `kinds` contributions of every ENABLED plugin installed under
|
|
326
|
+
* `scopeRootPath` over `base`, plugin name-sorted for determinism (directory
|
|
327
|
+
* listing order is filesystem-dependent). Each plugin's block goes through
|
|
328
|
+
* the same `mergeKinds` a scope `config.json` does — full entries add or
|
|
329
|
+
* shadow, patches field-merge, invalid entries drop. Called by
|
|
330
|
+
* `readMergedLaunchConfig` BELOW the host scope's own raw `kinds`, so the
|
|
331
|
+
* scope's config.json always overrides its plugins. */
|
|
332
|
+
function layerPluginKinds(scope, scopeRootPath, base) {
|
|
333
|
+
if (scopeRootPath === null)
|
|
334
|
+
return base;
|
|
335
|
+
const plugins = listInstalledPluginsInRoot(scope, scopeRootPath)
|
|
336
|
+
.filter((p) => p.enabled && p.manifest.kinds !== undefined)
|
|
337
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
338
|
+
let out = base;
|
|
339
|
+
for (const plugin of plugins)
|
|
340
|
+
out = mergeKinds(plugin.manifest.kinds, out);
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
const KIND_CONFIG_KEYS = new Set(['whenToUse', 'model', 'orchestratorModel', 'tools', 'extensions', 'availableTo']);
|
|
344
|
+
/** STRICT install-time validation for a plugin-declared `kinds` block —
|
|
345
|
+
* the loud counterpart to `mergeKinds`'s silent read-time dropping. Read
|
|
346
|
+
* paths must never throw on bad config, but an INSTALL delivering a bad
|
|
347
|
+
* block must fail the install, not ship a kind that silently never
|
|
348
|
+
* registers. Returns one human-readable reason per defect; empty = valid.
|
|
349
|
+
* Used by the archive-bundle validator (`command-plugins/bundle.ts`) on
|
|
350
|
+
* `bundle.json`'s optional `kinds` member. */
|
|
351
|
+
export function invalidPluginKindsReasons(raw) {
|
|
352
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
353
|
+
return ['kinds must be a JSON object keyed by kind name'];
|
|
354
|
+
}
|
|
355
|
+
const reasons = [];
|
|
356
|
+
const isStringArray = (v) => Array.isArray(v) && v.every((item) => typeof item === 'string');
|
|
357
|
+
for (const [kind, value] of Object.entries(raw)) {
|
|
358
|
+
if (kind.trim() === '') {
|
|
359
|
+
reasons.push('kinds contains an empty kind name');
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
363
|
+
reasons.push(`kinds.${kind} must be a JSON object`);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const entry = value;
|
|
367
|
+
for (const key of Object.keys(entry)) {
|
|
368
|
+
if (!KIND_CONFIG_KEYS.has(key))
|
|
369
|
+
reasons.push(`kinds.${kind}.${key} is not a KindConfig field (expected one of: ${[...KIND_CONFIG_KEYS].join(', ')})`);
|
|
370
|
+
}
|
|
371
|
+
if (entry['whenToUse'] !== undefined && (typeof entry['whenToUse'] !== 'string' || entry['whenToUse'].trim() === '')) {
|
|
372
|
+
reasons.push(`kinds.${kind}.whenToUse must be a non-empty string`);
|
|
373
|
+
}
|
|
374
|
+
for (const field of ['model', 'orchestratorModel']) {
|
|
375
|
+
if (entry[field] !== undefined && (typeof entry[field] !== 'string' || entry[field].trim() === '')) {
|
|
376
|
+
reasons.push(`kinds.${kind}.${field} must be a non-empty string`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
for (const field of ['tools', 'extensions', 'availableTo']) {
|
|
380
|
+
if (entry[field] !== undefined && !isStringArray(entry[field])) {
|
|
381
|
+
reasons.push(`kinds.${kind}.${field} must be an array of strings`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (normalizeKindFields(value) === null) {
|
|
385
|
+
reasons.push(`kinds.${kind} has no valid KindConfig field — an entry must set at least one of: ${[...KIND_CONFIG_KEYS].join(', ')} (whenToUse is required when the kind exists in no lower layer)`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return reasons;
|
|
389
|
+
}
|
|
344
390
|
/** Validate one raw `remoteCanvas.targets.<name>` entry, or drop it (return
|
|
345
|
-
* null) rather than throwing — same rule `
|
|
391
|
+
* null) rather than throwing — same rule `normalizeKindFields` follows. A
|
|
346
392
|
* valid entry needs a non-empty `previewEndpoint` and `relayTokenRef`; the
|
|
347
393
|
* token itself is never stored here (see `RemoteCanvasTarget`). */
|
|
348
394
|
function normalizeRemoteCanvasTarget(raw) {
|
|
@@ -492,17 +538,16 @@ function readRawProfileConfig(profileId) {
|
|
|
492
538
|
function readRawProjectScopeConfigs(targetCwd, targetProfileId) {
|
|
493
539
|
const nearestFirst = findProjectScopeRoots(targetCwd, targetProfileId);
|
|
494
540
|
const farthestFirst = [...nearestFirst].reverse();
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (raw !== null)
|
|
499
|
-
out.push(raw);
|
|
500
|
-
}
|
|
501
|
-
return out;
|
|
541
|
+
// A root with no config.json still contributes: its installed plugins may
|
|
542
|
+
// declare kinds, so the ROOT rides along and `raw` stays null.
|
|
543
|
+
return farthestFirst.map((root) => ({ root, raw: readJsonIfExists(configPathFor(root)) }));
|
|
502
544
|
}
|
|
503
545
|
/** Merge launch knobs (`kinds`, `modelLadders`) across scopes in
|
|
504
546
|
* project stack > profile > user > builtin precedence — the same precedence
|
|
505
|
-
* order used for memory resolution.
|
|
547
|
+
* order used for memory resolution. For `kinds` only, each plugin-bearing
|
|
548
|
+
* scope (user, each project root) additionally layers its enabled plugins'
|
|
549
|
+
* manifest `kinds` blocks directly BELOW that scope's own raw config — see
|
|
550
|
+
* `layerPluginKinds`. A kind or ladder cell declared at a
|
|
506
551
|
* more-specific scope shadows the same key from a less-specific scope; the
|
|
507
552
|
* project STACK (`findProjectScopeRoots` — every ancestor `.crouter/`,
|
|
508
553
|
* widened by a selected profile's `projects`) layers nearest-root-strongest;
|
|
@@ -533,8 +578,13 @@ export function readMergedLaunchConfig(targetCwd = process.cwd(), targetProfileI
|
|
|
533
578
|
const defaults = defaultScopeConfig();
|
|
534
579
|
const userRaw = readRawScopeConfig('user');
|
|
535
580
|
const profileRaw = readRawProfileConfig(targetProfileId);
|
|
536
|
-
const
|
|
537
|
-
|
|
581
|
+
const projectLayersFarthestFirst = readRawProjectScopeConfigs(targetCwd, targetProfileId);
|
|
582
|
+
// Kinds layer per scope as: that scope's enabled PLUGINS first, then the
|
|
583
|
+
// scope's own raw config — so a plugin can add or shadow a kind (registry
|
|
584
|
+
// entry in its manifest + gated persona docs in its memory tree) and the
|
|
585
|
+
// scope's config.json still overrides its plugins. Other launch knobs
|
|
586
|
+
// (ladders/routes/spawnEnv) have no plugin channel.
|
|
587
|
+
let kinds = mergeKinds(userRaw?.kinds, layerPluginKinds('user', scopeRoot('user'), defaults.kinds));
|
|
538
588
|
let modelLadders = mergeModelLadders(userRaw?.modelLadders, defaults.modelLadders);
|
|
539
589
|
let modelRoutes = mergeModelRoutes(userRaw?.modelRoutes);
|
|
540
590
|
let modelRouting = mergeModelRouting(userRaw?.modelRouting, undefined);
|
|
@@ -546,8 +596,10 @@ export function readMergedLaunchConfig(targetCwd = process.cwd(), targetProfileI
|
|
|
546
596
|
modelRouting = mergeModelRouting(profileRaw.modelRouting, modelRouting);
|
|
547
597
|
spawnEnv = mergeSpawnEnv(profileRaw.spawnEnv, spawnEnv.allow);
|
|
548
598
|
}
|
|
549
|
-
for (const raw of
|
|
550
|
-
kinds = mergeKinds(raw
|
|
599
|
+
for (const { root, raw } of projectLayersFarthestFirst) {
|
|
600
|
+
kinds = mergeKinds(raw?.kinds, layerPluginKinds('project', root, kinds));
|
|
601
|
+
if (raw === null)
|
|
602
|
+
continue;
|
|
551
603
|
modelLadders = mergeModelLadders(raw.modelLadders, modelLadders);
|
|
552
604
|
modelRoutes = mergeModelRoutes(raw.modelRoutes, modelRoutes);
|
|
553
605
|
modelRouting = mergeModelRouting(raw.modelRouting, modelRouting);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// installed-plugins.ts — the LEAF-LEVEL installed-plugin reader: list the
|
|
2
|
+
// plugin packages physically present under one scope root, with their
|
|
3
|
+
// manifests and enabled-state from that root's config.json `plugins` block.
|
|
4
|
+
//
|
|
5
|
+
// This lives below resolver.ts (which re-exports it for its existing callers)
|
|
6
|
+
// so that config.ts can layer plugin-declared launch config (`kinds`) into
|
|
7
|
+
// `readMergedLaunchConfig` without an import cycle: resolver.ts imports
|
|
8
|
+
// config.ts, so config.ts can never import resolver.ts. Imports here stay
|
|
9
|
+
// leaf-safe: types, fs-utils, manifest only.
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { CONFIG_FILE } from '../types.js';
|
|
12
|
+
import { listDirs, pathExists, readJsonIfExists } from './fs-utils.js';
|
|
13
|
+
import { readPluginManifest } from './manifest.js';
|
|
14
|
+
function pluginConfigForRoot(root) {
|
|
15
|
+
const cfg = readJsonIfExists(join(root, CONFIG_FILE));
|
|
16
|
+
return cfg && cfg.plugins && typeof cfg.plugins === 'object' ? cfg.plugins : {};
|
|
17
|
+
}
|
|
18
|
+
export function listInstalledPluginsInRoot(scope, scopeRootPath) {
|
|
19
|
+
const dir = join(scopeRootPath, 'plugins');
|
|
20
|
+
if (!pathExists(dir))
|
|
21
|
+
return [];
|
|
22
|
+
const cfg = pluginConfigForRoot(scopeRootPath);
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const name of listDirs(dir)) {
|
|
25
|
+
const root = join(dir, name);
|
|
26
|
+
const manifest = readPluginManifest(root);
|
|
27
|
+
if (!manifest)
|
|
28
|
+
continue;
|
|
29
|
+
const entry = cfg[name];
|
|
30
|
+
const version = typeof entry?.version === 'string' ? entry.version : manifest.version;
|
|
31
|
+
out.push({
|
|
32
|
+
name,
|
|
33
|
+
scope,
|
|
34
|
+
root,
|
|
35
|
+
manifest,
|
|
36
|
+
enabled: typeof entry?.enabled === 'boolean' ? entry.enabled : true,
|
|
37
|
+
sourceMarketplace: typeof entry?.source_marketplace === 'string' ? entry.source_marketplace : undefined,
|
|
38
|
+
version,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
package/dist/core/resolver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InstalledMarketplace, InstalledPlugin, Scope } from '../types.js';
|
|
2
|
-
export
|
|
2
|
+
export { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
3
3
|
export declare function listInstalledPlugins(scope: Scope): InstalledPlugin[];
|
|
4
4
|
export declare function listAllPlugins(): InstalledPlugin[];
|
|
5
5
|
export declare function findPluginByName(name: string, scope?: Scope): InstalledPlugin | null;
|
package/dist/core/resolver.js
CHANGED
|
@@ -1,39 +1,14 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { CONFIG_FILE } from '../types.js';
|
|
3
2
|
import { readConfig } from './config.js';
|
|
4
|
-
import { listDirs, pathExists
|
|
5
|
-
import { readMarketplaceManifest
|
|
3
|
+
import { listDirs, pathExists } from './fs-utils.js';
|
|
4
|
+
import { readMarketplaceManifest } from './manifest.js';
|
|
6
5
|
import { InputError } from './io.js';
|
|
7
6
|
import { marketplacesDir, projectScopeRoot, scopeRoot, userScopeRoot, } from './scope.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const dir = join(scopeRootPath, 'plugins');
|
|
14
|
-
if (!pathExists(dir))
|
|
15
|
-
return [];
|
|
16
|
-
const cfg = pluginConfigForRoot(scopeRootPath);
|
|
17
|
-
const out = [];
|
|
18
|
-
for (const name of listDirs(dir)) {
|
|
19
|
-
const root = join(dir, name);
|
|
20
|
-
const manifest = readPluginManifest(root);
|
|
21
|
-
if (!manifest)
|
|
22
|
-
continue;
|
|
23
|
-
const entry = cfg[name];
|
|
24
|
-
const version = typeof entry?.version === 'string' ? entry.version : manifest.version;
|
|
25
|
-
out.push({
|
|
26
|
-
name,
|
|
27
|
-
scope,
|
|
28
|
-
root,
|
|
29
|
-
manifest,
|
|
30
|
-
enabled: typeof entry?.enabled === 'boolean' ? entry.enabled : true,
|
|
31
|
-
sourceMarketplace: typeof entry?.source_marketplace === 'string' ? entry.source_marketplace : undefined,
|
|
32
|
-
version,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
return out;
|
|
36
|
-
}
|
|
7
|
+
// Moved to installed-plugins.ts (a leaf module below config.ts) so
|
|
8
|
+
// `readMergedLaunchConfig` can layer plugin-declared kinds without an import
|
|
9
|
+
// cycle; re-exported here for this module's existing callers.
|
|
10
|
+
export { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
11
|
+
import { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
37
12
|
export function listInstalledPlugins(scope) {
|
|
38
13
|
// The builtin scope has no scopeRoot, so this returns [] — builtin content is
|
|
39
14
|
// the memory substrate, not plugins.
|
package/dist/types.d.ts
CHANGED
|
@@ -33,7 +33,20 @@ export interface PluginManifest {
|
|
|
33
33
|
description?: string;
|
|
34
34
|
source?: string;
|
|
35
35
|
owner?: OwnerRef;
|
|
36
|
-
|
|
36
|
+
/** Kind-registry contributions, keyed by full kind string (top-level e.g.
|
|
37
|
+
* `applet-builder`, or sub-kind e.g. `plan/reviewers/security`). Each entry
|
|
38
|
+
* is any subset of `KindConfig` fields: it FIELD-MERGES over a kind a lower
|
|
39
|
+
* layer already defines (`whenToUse` included — overriding just the spawn
|
|
40
|
+
* guidance never strips the kind's model tier), and defines a NEW kind when
|
|
41
|
+
* none does (then `whenToUse` is required) — the same rule a scope
|
|
42
|
+
* `config.json` `kinds` block follows.
|
|
43
|
+
* `readMergedLaunchConfig` layers an enabled plugin's entries directly
|
|
44
|
+
* above the builtin registry and below its host scope's own `config.json`,
|
|
45
|
+
* so a plugin can ship a persona kind (registry entry here + gated persona
|
|
46
|
+
* memory docs in its `memory/` tree) and the user still overrides it.
|
|
47
|
+
* Archive plugins declare this in `bundle.json`; the installer copies the
|
|
48
|
+
* validated block into this synthesized manifest. */
|
|
49
|
+
kinds?: Record<string, Partial<KindConfig>>;
|
|
37
50
|
/** Plugin-root-relative path to one declarative command manifest
|
|
38
51
|
* (`commands.json`). When present on an installed, enabled plugin, that
|
|
39
52
|
* manifest contributes top-level command branches to the `crtr` tree —
|
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.188",
|
|
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.188",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|