@north-light/crouter 0.3.219 → 0.3.221
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 +9 -0
- package/dist/api/client.js +10 -0
- package/dist/api/dto/chat-inventory.d.ts +43 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/profiles.d.ts +19 -5
- package/dist/api/dto/profiles.js +2 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/build-root.d.ts +2 -6
- package/dist/build-root.js +51 -4
- package/dist/builtin-memory/internal/agent-shaping.md +3 -1
- package/dist/builtin-memory/internal/memory-loading.md +4 -0
- package/dist/builtin-memory/plan/roadmap.md +7 -1
- package/dist/builtin-memory/spec/guide.md +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +7 -2
- package/dist/clients/attach/__tests__/ref-autocomplete.test.js +1 -1
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -1
- package/dist/clients/attach/overlays/file-review.js +2 -2
- package/dist/clients/attach/session/keys.d.ts +1 -1
- package/dist/clients/attach/session/profile-files.js +1 -1
- package/dist/clients/attach/viewer.js +690 -690
- package/dist/clients/inbox/review/launch.d.ts +8 -4
- package/dist/clients/inbox/review/launch.js +55 -5
- package/dist/clients/inbox/review/review-client.d.ts +1 -0
- package/dist/clients/inbox/review/review-client.js +4 -0
- package/dist/clients/inbox/review-adapter.d.ts +1 -8
- package/dist/clients/inbox/review-adapter.js +4 -52
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/read.js +1 -0
- package/dist/commands/memory.js +1 -1
- package/dist/commands/pkg/market-manage.js +165 -75
- package/dist/commands/pkg/plugin-inspect.js +19 -2
- package/dist/commands/pkg/plugin-manage.d.ts +8 -3
- package/dist/commands/pkg/plugin-manage.js +72 -24
- package/dist/commands/profile/default.js +6 -10
- package/dist/commands/profile/list.js +5 -3
- package/dist/commands/profile/new.js +21 -8
- package/dist/commands/profile/project.js +25 -19
- package/dist/commands/profile/show.js +3 -3
- package/dist/commands/surface-inbox.js +1 -0
- package/dist/commands/sys/__tests__/migrate.test.js +16 -5
- package/dist/commands/sys/doctor.js +35 -5
- package/dist/commands/sys/migrate.js +38 -19
- package/dist/commands/sys/panels/broker-limits-panel.js +3 -3
- package/dist/commands/sys/setup-core.js +1 -1
- package/dist/commands/sys/sync-project-guidance.js +1 -1
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +1 -1
- package/dist/core/__tests__/fixtures/c5-command-boundary-ext.js +24 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -18
- package/dist/core/__tests__/fixtures/fake-engine.js +8 -1
- package/dist/core/__tests__/helpers/broker-clients.d.ts +1 -0
- package/dist/core/__tests__/helpers/broker-clients.js +1 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +36 -2
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +217 -0
- package/dist/core/__tests__/seam/dormancy-release.test.js +37 -1
- package/dist/core/__tests__/serial/broker-sdk-wiring.test.js +102 -2
- package/dist/core/bootstrap.js +6 -0
- package/dist/core/canvas/browse/app.js +5 -2
- package/dist/core/canvas/browse/model.d.ts +25 -15
- package/dist/core/canvas/browse/model.js +86 -65
- package/dist/core/canvas/render-source.d.ts +6 -0
- package/dist/core/canvas/render-source.js +7 -1
- package/dist/core/canvas/render.js +10 -2
- package/dist/core/command-hooks/artifact.d.ts +10 -0
- package/dist/core/command-hooks/artifact.js +129 -0
- package/dist/core/command-hooks/catalog.d.ts +14 -0
- package/dist/core/command-hooks/catalog.js +38 -0
- package/dist/core/command-hooks/compose.d.ts +15 -0
- package/dist/core/command-hooks/compose.js +99 -0
- package/dist/core/command-hooks/discovery.d.ts +87 -0
- package/dist/core/command-hooks/discovery.js +174 -0
- package/dist/core/command-hooks/help.d.ts +5 -0
- package/dist/core/command-hooks/help.js +18 -0
- package/dist/core/command-hooks/index.d.ts +6 -0
- package/dist/core/command-hooks/index.js +6 -0
- package/dist/core/command-hooks/report.d.ts +23 -0
- package/dist/core/command-hooks/report.js +19 -0
- package/dist/core/command-hooks/schema.d.ts +27 -0
- package/dist/core/command-hooks/schema.js +68 -0
- package/dist/core/command-hooks/transport/exec-invoke.d.ts +22 -0
- package/dist/core/command-hooks/transport/exec-invoke.js +274 -0
- package/dist/core/command-plugins/presence.d.ts +2 -0
- package/dist/core/command-plugins/presence.js +17 -0
- package/dist/core/command-plugins/transport/exec-invoke.d.ts +5 -0
- package/dist/core/command-plugins/transport/exec-invoke.js +58 -5
- package/dist/core/command.d.ts +8 -1
- package/dist/core/command.js +12 -10
- package/dist/core/help.d.ts +7 -1
- package/dist/core/io.d.ts +9 -1
- package/dist/core/io.js +44 -2
- package/dist/core/memory/inline-ref-inventory.d.ts +2 -1
- package/dist/core/memory/inline-ref-inventory.js +15 -8
- package/dist/core/memory-resolver.d.ts +13 -1
- package/dist/core/memory-resolver.js +25 -19
- package/dist/core/profiles/manifest.d.ts +13 -2
- package/dist/core/profiles/manifest.js +84 -18
- package/dist/core/profiles/select.js +9 -9
- package/dist/core/render.js +11 -0
- package/dist/core/runtime/advertised-command-invocation.d.ts +20 -0
- package/dist/core/runtime/advertised-command-invocation.js +233 -0
- package/dist/core/runtime/bearings.js +1 -1
- package/dist/core/runtime/broker/client-registry.d.ts +6 -3
- package/dist/core/runtime/broker/client-registry.js +6 -4
- package/dist/core/runtime/broker/event-projection.js +7 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +16 -10
- package/dist/core/runtime/broker/read-ops.d.ts +4 -0
- package/dist/core/runtime/broker/read-ops.js +6 -2
- package/dist/core/runtime/broker-extension-render.js +1 -1
- package/dist/core/runtime/broker-inventory.d.ts +5 -0
- package/dist/core/runtime/broker-inventory.js +191 -0
- package/dist/core/runtime/broker-protocol.d.ts +13 -2
- package/dist/core/runtime/broker.js +10 -1
- package/dist/core/runtime/command-surface.d.ts +33 -0
- package/dist/core/runtime/command-surface.js +81 -0
- package/dist/core/runtime/node-read.js +5 -0
- package/dist/core/scope.d.ts +26 -1
- package/dist/core/scope.js +52 -12
- package/dist/core/substrate/on-read.d.ts +7 -1
- package/dist/core/substrate/on-read.js +13 -4
- package/dist/core/substrate/render.js +14 -5
- package/dist/core/substrate/schema.d.ts +11 -1
- package/dist/core/substrate/schema.js +11 -2
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -4
- package/dist/daemon/api/handlers/chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/chat-inventory.js +25 -0
- package/dist/daemon/api/handlers/profiles.js +7 -1
- package/dist/daemon/api/map.d.ts +2 -1
- package/dist/daemon/api/map.js +3 -2
- package/dist/daemon/api/server.js +2 -0
- package/dist/hook-authoring.d.ts +75 -0
- package/dist/hook-authoring.js +358 -0
- package/dist/hook-process.d.ts +7 -0
- package/dist/hook-process.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/migrations/002-profile-project-memory.d.ts +2 -0
- package/dist/migrations/002-profile-project-memory.js +71 -0
- package/dist/migrations/profile-manifests.d.ts +30 -0
- package/dist/migrations/profile-manifests.js +70 -0
- package/dist/migrations/registry.js +10 -5
- package/dist/migrations/types.d.ts +28 -1
- package/dist/migrations/types.js +15 -9
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +21 -4
- package/dist/pi-extensions/canvas-structured-output.js +85 -2
- package/dist/types.d.ts +15 -6
- package/dist/types.js +5 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/clients/attach/__tests__/file-review-focus.test.js +0 -49
- /package/dist/{clients/attach/__tests__/file-review-focus.test.d.ts → core/__tests__/profile-project-memory-delivery.test.d.ts} +0 -0
|
@@ -82,6 +82,10 @@ export interface HelloFrame {
|
|
|
82
82
|
type: 'hello';
|
|
83
83
|
role: ClientRole;
|
|
84
84
|
client_id: string;
|
|
85
|
+
/** Whether this socket represents a person attending the conversation. Defaults
|
|
86
|
+
* true for backward compatibility; system observers set false without changing
|
|
87
|
+
* their read-only `role`. */
|
|
88
|
+
attends?: boolean;
|
|
85
89
|
/** Per-attach welcome transcript bound; absent or invalid keeps the full history. */
|
|
86
90
|
snapshot_tail?: number;
|
|
87
91
|
/** Terminal geometry of a tmux-pane viewer; absent for a headless client. */
|
|
@@ -404,6 +408,11 @@ export interface ModelChangedFrame {
|
|
|
404
408
|
* frames still parse. */
|
|
405
409
|
spec?: string | null;
|
|
406
410
|
}
|
|
411
|
+
/** Broadcast to attached observers after a successful reload has cleared the
|
|
412
|
+
* command and memory inventories. */
|
|
413
|
+
export interface ResourcesReloadedFrame {
|
|
414
|
+
type: 'resources_reloaded';
|
|
415
|
+
}
|
|
407
416
|
/** The `error` control frame. SOURCED from the shared `@north-light/crouter-api`
|
|
408
417
|
* contract (`src/api/dto/broker.ts`, the same zero-runtime-dep `/v1` module
|
|
409
418
|
* Northlight Core imports) so the wire shape here and the shape Core consumes
|
|
@@ -504,12 +513,14 @@ export interface WireSettings extends SettingsConfig {
|
|
|
504
513
|
* `'taste/foo'`) and what `crtr memory read <name>` expects; `scope` is the
|
|
505
514
|
* WINNING scope after first-wins-by-name precedence dedup (display only —
|
|
506
515
|
* resolution is by `name` alone). No body/source path: this is a metadata-only
|
|
507
|
-
* inventory, never the document content.
|
|
516
|
+
* inventory, never the document content. `gatewayVisible` is broker-computed
|
|
517
|
+
* chat-surface eligibility; terminal viewers retain every row. */
|
|
508
518
|
export interface RefMeta {
|
|
509
519
|
name: string;
|
|
510
520
|
kind: 'knowledge' | 'preference';
|
|
511
521
|
scope: 'node' | 'project' | 'profile' | 'user' | 'builtin';
|
|
512
522
|
shortForm: string;
|
|
523
|
+
gatewayVisible: boolean;
|
|
513
524
|
}
|
|
514
525
|
export interface ListModelsData {
|
|
515
526
|
type: 'data';
|
|
@@ -626,7 +637,7 @@ export interface ToolGroupSummaryFrame {
|
|
|
626
637
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
627
638
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
628
639
|
* non-colliding `type` discriminants. */
|
|
629
|
-
export type BrokerToClient = WelcomeFrame | WorkingActivityFrame | ModelChangedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
|
|
640
|
+
export type BrokerToClient = WelcomeFrame | WorkingActivityFrame | ModelChangedFrame | ResourcesReloadedFrame | ErrorFrame | NodeNamedFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | ToolGroupSummaryFrame | AgentSessionEvent;
|
|
630
641
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
631
642
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
632
643
|
/** Byte bounds for a {@link FrameDecoder} (C5). */
|
|
@@ -31,6 +31,7 @@ import { FRONT_DOOR_ENV } from './front-door.js';
|
|
|
31
31
|
import { CANVAS_EXTENSIONS, piInvocationToSdkConfig, } from './launch.js';
|
|
32
32
|
import { assertEngineVersion, loadBrokerEngine } from './broker-sdk.js';
|
|
33
33
|
import { resolveBundledPiPackageDir } from './pi-cli.js';
|
|
34
|
+
import { captureSessionPromptTemplateChatSurfaces } from './command-surface.js';
|
|
34
35
|
import { PoolCredentialStore } from '../pool-credential-store.js';
|
|
35
36
|
import { clearCleanAbort, markBusy, markCleanAbort } from './busy.js';
|
|
36
37
|
import { recordFaultForCurrentNode } from './fault.js';
|
|
@@ -45,7 +46,8 @@ import { ToolGroupTracker } from './broker/tool-groups.js';
|
|
|
45
46
|
import { onNodeNamed } from './broker/node-named.js';
|
|
46
47
|
import { FaultRetry } from './broker/fault-retry.js';
|
|
47
48
|
import { EventProjection } from './broker/event-projection.js';
|
|
48
|
-
import { createFrameDispatchContext, handleFrame } from './broker/frame-dispatch.js';
|
|
49
|
+
import { createFrameDispatchContext, handleFrame, } from './broker/frame-dispatch.js';
|
|
50
|
+
import { hydratePersistedAdvertisedCommandMessages, installAdvertisedCommandInvocationContract, } from './advertised-command-invocation.js';
|
|
49
51
|
import { RebindDriver } from './broker/rebind.js';
|
|
50
52
|
import { brokerExtensionState, commitBrokerModel } from './broker/daemon-ops.js';
|
|
51
53
|
import { FrameOverflowError, normalizeSnapshotTail, } from './broker-protocol.js';
|
|
@@ -715,6 +717,9 @@ export function snapshotMessages(session, boundaryReviewId) {
|
|
|
715
717
|
// `crtr-cycle` marker's `{cycle, fromLeaf}` shape — the fields we read off it
|
|
716
718
|
// are ones ONLY this module's own `appendCustomEntry` call (above) ever writes.
|
|
717
719
|
const messages = cycleAwareMessages(session.sessionManager);
|
|
720
|
+
if (messages !== undefined) {
|
|
721
|
+
hydratePersistedAdvertisedCommandMessages(messages.map(({ message }) => message));
|
|
722
|
+
}
|
|
718
723
|
return messages === undefined ? undefined : visibleMessages(messages, { boundaryReviewId });
|
|
719
724
|
}
|
|
720
725
|
// ---------------------------------------------------------------------------
|
|
@@ -893,6 +898,10 @@ export async function buildBrokerSession(engine, cfg) {
|
|
|
893
898
|
tools: cfg.tools,
|
|
894
899
|
sessionStartEvent: o.sessionStartEvent,
|
|
895
900
|
});
|
|
901
|
+
installAdvertisedCommandInvocationContract(created.session);
|
|
902
|
+
// The resource loader has completed its initial template load. Capture
|
|
903
|
+
// crouter-only template metadata before command disclosure can observe it.
|
|
904
|
+
captureSessionPromptTemplateChatSurfaces(created.session);
|
|
896
905
|
// CONSTRUCTION-ONLY MASK — see modelLessRegistry: reveal now that model
|
|
897
906
|
// selection (construction-time only) is done, so a later /login re-drive's
|
|
898
907
|
// list_models/cycle_model/provider-rotation see the real registry.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** The optional fields a shipped extension registration may add so its command
|
|
2
|
+
* is disclosed to chat surfaces. Registrations cast their options through
|
|
3
|
+
* `ChatCapableRegistration` rather than widening pi's own types. */
|
|
4
|
+
export interface ChatCommandMetadata {
|
|
5
|
+
/** Present and exactly `true` to opt this command into chat disclosure. */
|
|
6
|
+
gateway?: true;
|
|
7
|
+
/** Argument shape shown beside the name, e.g. `'[args]'`. */
|
|
8
|
+
argumentHint?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Registration options plus the preserved custom fields crouter reads back off
|
|
11
|
+
* the resolved command. `T` is `Parameters<ExtensionAPI['registerCommand']>[1]`
|
|
12
|
+
* at the call site. */
|
|
13
|
+
export type ChatCapableRegistration<T> = T & ChatCommandMetadata;
|
|
14
|
+
/** What the command disclosure needs to know about one command's chat surface. */
|
|
15
|
+
export interface ChatCommandSurface {
|
|
16
|
+
gatewayCapable: boolean;
|
|
17
|
+
argumentHint?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Read the chat surface off a resolved extension command. `command` is an
|
|
20
|
+
* arbitrary runtime object from another package, so every field is probed
|
|
21
|
+
* rather than trusted. */
|
|
22
|
+
export declare function registrationChatSurface(command: unknown): ChatCommandSurface;
|
|
23
|
+
/** Capture prompt-template chat metadata immediately after pi loads or reloads
|
|
24
|
+
* its templates. The opt-in is the template file's own frontmatter; the hint
|
|
25
|
+
* is pi's already-parsed `argument-hint`. A missing file, unreadable path, or
|
|
26
|
+
* invalid YAML is not chat-capable. */
|
|
27
|
+
export declare function capturePromptTemplateChatSurfaces(templates: unknown): void;
|
|
28
|
+
/** Capture every prompt template a session currently exposes. A broken getter
|
|
29
|
+
* leaves its templates uncaptured and therefore fail-closed. */
|
|
30
|
+
export declare function captureSessionPromptTemplateChatSurfaces(session: unknown): void;
|
|
31
|
+
/** Read the chat surface captured for this loaded template. An uncaptured
|
|
32
|
+
* template is not disclosed to chat surfaces. */
|
|
33
|
+
export declare function templateChatSurface(template: unknown): ChatCommandSurface;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// command-surface.ts — crouter's own metadata about the commands the engine
|
|
2
|
+
// registers, and the one place that decides whether a command can complete its
|
|
3
|
+
// outcome from a non-terminal chat surface.
|
|
4
|
+
//
|
|
5
|
+
// Eligibility is OPT-IN and fail-closed. An extension registration declares
|
|
6
|
+
// `gateway: true`; a prompt template declares `gateway: true` in frontmatter.
|
|
7
|
+
// Anything else — a missing flag, a malformed value, an unreadable file — is
|
|
8
|
+
// not chat-capable. Capability is NEVER inferred from a command's name, its
|
|
9
|
+
// source, or a hard-coded list, because the command set is whatever the active
|
|
10
|
+
// VM registered, not a catalog crouter ships.
|
|
11
|
+
//
|
|
12
|
+
// pi keeps unknown registration fields on the resolved command object, so
|
|
13
|
+
// `gateway`/`argumentHint` ride along beside `expansion` without touching pi's
|
|
14
|
+
// global registration types. pi's prompt-template loader is the opposite: it
|
|
15
|
+
// parses `argument-hint` into `argumentHint` and DROPS every other frontmatter
|
|
16
|
+
// key. Capture `gateway` from the template file when the loader finishes, so
|
|
17
|
+
// the eligibility and loaded template content share the same reload boundary.
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
20
|
+
const NOT_CAPABLE = { gatewayCapable: false };
|
|
21
|
+
// Pi drops unknown prompt frontmatter keys. This cache restores crouter's
|
|
22
|
+
// metadata without re-reading a template during command disclosure.
|
|
23
|
+
const templateChatSurfaces = new WeakMap();
|
|
24
|
+
function hintOf(value) {
|
|
25
|
+
return typeof value === 'string' && value.trim() !== '' ? { argumentHint: value } : {};
|
|
26
|
+
}
|
|
27
|
+
/** Read the chat surface off a resolved extension command. `command` is an
|
|
28
|
+
* arbitrary runtime object from another package, so every field is probed
|
|
29
|
+
* rather than trusted. */
|
|
30
|
+
export function registrationChatSurface(command) {
|
|
31
|
+
if (typeof command !== 'object' || command === null)
|
|
32
|
+
return NOT_CAPABLE;
|
|
33
|
+
const record = command;
|
|
34
|
+
if (record['gateway'] !== true)
|
|
35
|
+
return NOT_CAPABLE;
|
|
36
|
+
return { gatewayCapable: true, ...hintOf(record['argumentHint']) };
|
|
37
|
+
}
|
|
38
|
+
/** Capture prompt-template chat metadata immediately after pi loads or reloads
|
|
39
|
+
* its templates. The opt-in is the template file's own frontmatter; the hint
|
|
40
|
+
* is pi's already-parsed `argument-hint`. A missing file, unreadable path, or
|
|
41
|
+
* invalid YAML is not chat-capable. */
|
|
42
|
+
export function capturePromptTemplateChatSurfaces(templates) {
|
|
43
|
+
if (!Array.isArray(templates))
|
|
44
|
+
return;
|
|
45
|
+
for (const template of templates) {
|
|
46
|
+
if (typeof template !== 'object' || template === null)
|
|
47
|
+
continue;
|
|
48
|
+
const { filePath, argumentHint } = template;
|
|
49
|
+
let surface = NOT_CAPABLE;
|
|
50
|
+
if (typeof filePath === 'string' && filePath !== '') {
|
|
51
|
+
try {
|
|
52
|
+
const frontmatter = parseFrontmatterGeneric(readFileSync(filePath, 'utf8')).data;
|
|
53
|
+
if (frontmatter?.['gateway'] === true)
|
|
54
|
+
surface = { gatewayCapable: true, ...hintOf(argumentHint) };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Fail closed for malformed or unreadable template metadata.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
templateChatSurfaces.set(template, surface);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Capture every prompt template a session currently exposes. A broken getter
|
|
64
|
+
* leaves its templates uncaptured and therefore fail-closed. */
|
|
65
|
+
export function captureSessionPromptTemplateChatSurfaces(session) {
|
|
66
|
+
try {
|
|
67
|
+
if (typeof session !== 'object' || session === null)
|
|
68
|
+
return;
|
|
69
|
+
capturePromptTemplateChatSurfaces(session.promptTemplates);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Fail closed when an engine cannot expose its prompt templates.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Read the chat surface captured for this loaded template. An uncaptured
|
|
76
|
+
* template is not disclosed to chat surfaces. */
|
|
77
|
+
export function templateChatSurface(template) {
|
|
78
|
+
if (typeof template !== 'object' || template === null)
|
|
79
|
+
return NOT_CAPABLE;
|
|
80
|
+
return templateChatSurfaces.get(template) ?? NOT_CAPABLE;
|
|
81
|
+
}
|
|
@@ -13,6 +13,7 @@ import { basename, join } from 'node:path';
|
|
|
13
13
|
import { InputError } from '../io.js';
|
|
14
14
|
import { fullName, getNode } from '../canvas/index.js';
|
|
15
15
|
import { jobDir, nodeDir, sessionPtrPath } from '../canvas/paths.js';
|
|
16
|
+
import { hydratePersistedAdvertisedCommandMessages } from './advertised-command-invocation.js';
|
|
16
17
|
import { SessionManager } from './broker-sdk.js';
|
|
17
18
|
import { openModelRegistry } from './model-registry.js';
|
|
18
19
|
import { BUILTIN_SLASH_COMMANDS } from './pi-vendored.js';
|
|
@@ -153,7 +154,11 @@ async function reconstructVisibleMessages(nodeId) {
|
|
|
153
154
|
copyFileSync(sessionFile, copy);
|
|
154
155
|
const manager = SessionManager.open(copy);
|
|
155
156
|
const context = manager.buildSessionContext();
|
|
157
|
+
hydratePersistedAdvertisedCommandMessages(context.messages);
|
|
156
158
|
const reconstructed = cycleAwareMessages(manager);
|
|
159
|
+
if (reconstructed !== undefined) {
|
|
160
|
+
hydratePersistedAdvertisedCommandMessages(reconstructed.map(({ message }) => message));
|
|
161
|
+
}
|
|
157
162
|
const identified = reconstructed === undefined
|
|
158
163
|
? undefined
|
|
159
164
|
: visibleMessages(reconstructed, { boundaryReviewId: node.review_binding?.review_id });
|
package/dist/core/scope.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { InstalledPlugin, Scope } from '../types.js';
|
|
2
|
+
import type { ProfileProjectMemory } from '../api/dto/profiles.js';
|
|
3
|
+
/** The ceiling a project root carries when no profile relationship contributes
|
|
4
|
+
* it — the top rung, i.e. no cap at all. */
|
|
5
|
+
export declare const NEUTRAL_PROJECT_MEMORY: ProfileProjectMemory;
|
|
6
|
+
/** One resolved project scope root plus the selected profile relationship's
|
|
7
|
+
* memory ceiling for it. The ceiling is INDEPENDENT of how the root was
|
|
8
|
+
* discovered: a root reached by both cwd and a profile entry still takes that
|
|
9
|
+
* entry's value (first entry in manifest order wins). */
|
|
10
|
+
export interface ProjectScopeRoot {
|
|
11
|
+
/** Absolute path of the `.crouter/` dir. */
|
|
12
|
+
root: string;
|
|
13
|
+
memory: ProfileProjectMemory;
|
|
14
|
+
}
|
|
2
15
|
export declare function builtinMemoryRoot(): string;
|
|
3
16
|
export declare function builtinPiPackagesDir(): string;
|
|
4
17
|
export declare function builtinPiPackageDir(name: string): string;
|
|
@@ -15,10 +28,22 @@ export declare function findProjectScopeRoot(startDir?: string): string | null;
|
|
|
15
28
|
* selected profile (`CRTR_PROFILE_ID`); a null/unresolvable profile collapses
|
|
16
29
|
* to the single `[startDir]` pointer, reproducing today's behavior exactly.
|
|
17
30
|
* Results are cached per (resolved startDir, profileId) — `resetScopeCache()`
|
|
18
|
-
* invalidates on any profile mutation that can change project dirs or names.
|
|
31
|
+
* invalidates on any profile mutation that can change project dirs or names.
|
|
32
|
+
*
|
|
33
|
+
* Each root also carries the selected profile relationship's memory ceiling
|
|
34
|
+
* (`findProjectScopeRootsWithMemory`), a projection layered onto this order
|
|
35
|
+
* rather than part of it: discovery provenance decides precedence, the
|
|
36
|
+
* relationship decides the ceiling, and the two never influence each other. */
|
|
19
37
|
export declare function findProjectScopeRoots(startDir?: string, profileId?: string | null): string[];
|
|
38
|
+
/** `findProjectScopeRoots` with each root's profile memory ceiling attached —
|
|
39
|
+
* same roots, same order. A root contributed by a profile entry's ancestor
|
|
40
|
+
* walk takes that entry's `memory` (first entry in manifest order wins, even
|
|
41
|
+
* when cwd also reaches the root); every other root takes the neutral
|
|
42
|
+
* `content` ceiling. */
|
|
43
|
+
export declare function findProjectScopeRootsWithMemory(startDir?: string, profileId?: string | null): ProjectScopeRoot[];
|
|
20
44
|
export declare function projectScopeRoot(startDir?: string): string | null;
|
|
21
45
|
export declare function projectScopeRoots(startDir?: string, profileId?: string | null): string[];
|
|
46
|
+
export declare function projectScopeRootsWithMemory(startDir?: string, profileId?: string | null): ProjectScopeRoot[];
|
|
22
47
|
export declare function scopeRoot(scope: Scope): string | null;
|
|
23
48
|
export declare function requireScopeRoot(scope: Scope): string;
|
|
24
49
|
export declare function ensureProjectScopeRoot(startDir?: string): string;
|
package/dist/core/scope.js
CHANGED
|
@@ -6,6 +6,9 @@ import { assetRoot } from './asset-root.js';
|
|
|
6
6
|
import { usage } from './errors.js';
|
|
7
7
|
import { loadProfileManifest } from './profiles/manifest.js';
|
|
8
8
|
import { realpathOrSelf } from './fs-utils.js';
|
|
9
|
+
/** The ceiling a project root carries when no profile relationship contributes
|
|
10
|
+
* it — the top rung, i.e. no cap at all. */
|
|
11
|
+
export const NEUTRAL_PROJECT_MEMORY = 'content';
|
|
9
12
|
// Cache of resolved project-scope-root STACKS, keyed by (resolved cwd, profile
|
|
10
13
|
// id). A profile widens the pointer set beyond the bare cwd (see
|
|
11
14
|
// findProjectScopeRoots below), so the cache key must carry both — the same
|
|
@@ -13,7 +16,8 @@ import { realpathOrSelf } from './fs-utils.js';
|
|
|
13
16
|
// `resetScopeCache()` (called by every profile mutation in
|
|
14
17
|
// src/core/profiles/manifest.ts) clears the whole cache; there is no
|
|
15
18
|
// per-entry invalidation because a profile edit can change which pointer set
|
|
16
|
-
// any cached cwd belongs to.
|
|
19
|
+
// any cached cwd belongs to. Path-only callers read `roots`, which is the same
|
|
20
|
+
// array in the same order as `annotated`.
|
|
17
21
|
const scopeRootsCache = new Map();
|
|
18
22
|
function scopeRootsCacheKey(resolvedStartDir, profileId) {
|
|
19
23
|
return `${resolvedStartDir}\u0000${profileId ?? ''}`;
|
|
@@ -94,19 +98,40 @@ function collectAncestorScopeRoots(startDir, userRoot) {
|
|
|
94
98
|
* selected profile (`CRTR_PROFILE_ID`); a null/unresolvable profile collapses
|
|
95
99
|
* to the single `[startDir]` pointer, reproducing today's behavior exactly.
|
|
96
100
|
* Results are cached per (resolved startDir, profileId) — `resetScopeCache()`
|
|
97
|
-
* invalidates on any profile mutation that can change project dirs or names.
|
|
101
|
+
* invalidates on any profile mutation that can change project dirs or names.
|
|
102
|
+
*
|
|
103
|
+
* Each root also carries the selected profile relationship's memory ceiling
|
|
104
|
+
* (`findProjectScopeRootsWithMemory`), a projection layered onto this order
|
|
105
|
+
* rather than part of it: discovery provenance decides precedence, the
|
|
106
|
+
* relationship decides the ceiling, and the two never influence each other. */
|
|
98
107
|
export function findProjectScopeRoots(startDir = process.cwd(), profileId = process.env['CRTR_PROFILE_ID'] || null) {
|
|
108
|
+
return resolveProjectScopeRoots(startDir, profileId).roots;
|
|
109
|
+
}
|
|
110
|
+
/** `findProjectScopeRoots` with each root's profile memory ceiling attached —
|
|
111
|
+
* same roots, same order. A root contributed by a profile entry's ancestor
|
|
112
|
+
* walk takes that entry's `memory` (first entry in manifest order wins, even
|
|
113
|
+
* when cwd also reaches the root); every other root takes the neutral
|
|
114
|
+
* `content` ceiling. */
|
|
115
|
+
export function findProjectScopeRootsWithMemory(startDir = process.cwd(), profileId = process.env['CRTR_PROFILE_ID'] || null) {
|
|
116
|
+
return resolveProjectScopeRoots(startDir, profileId).annotated;
|
|
117
|
+
}
|
|
118
|
+
function resolveProjectScopeRoots(startDir, profileId) {
|
|
99
119
|
const resolvedStart = resolve(startDir);
|
|
100
120
|
const key = scopeRootsCacheKey(resolvedStart, profileId);
|
|
101
121
|
const cached = scopeRootsCache.get(key);
|
|
102
122
|
if (cached !== undefined)
|
|
103
123
|
return cached;
|
|
104
124
|
const userRoot = userScopeRoot();
|
|
105
|
-
|
|
125
|
+
// A null `memory` marks the cwd pointer: it decides precedence like any
|
|
126
|
+
// other pointer but contributes no ceiling.
|
|
127
|
+
const pointers = [
|
|
128
|
+
{ dir: resolvedStart, memory: null },
|
|
129
|
+
];
|
|
106
130
|
if (profileId !== null && profileId !== '') {
|
|
107
131
|
try {
|
|
108
132
|
const { manifest } = loadProfileManifest(profileId);
|
|
109
|
-
|
|
133
|
+
for (const project of manifest.projects)
|
|
134
|
+
pointers.push({ dir: project.path, memory: project.memory });
|
|
110
135
|
}
|
|
111
136
|
catch {
|
|
112
137
|
// Missing/deleted/invalid profile: this is a hot path every memory and
|
|
@@ -116,16 +141,23 @@ export function findProjectScopeRoots(startDir = process.cwd(), profileId = proc
|
|
|
116
141
|
}
|
|
117
142
|
const seen = new Set();
|
|
118
143
|
const roots = [];
|
|
144
|
+
const caps = new Map();
|
|
119
145
|
for (const pointer of pointers) {
|
|
120
|
-
for (const root of collectAncestorScopeRoots(pointer, userRoot)) {
|
|
121
|
-
if (seen.has(root))
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
for (const root of collectAncestorScopeRoots(pointer.dir, userRoot)) {
|
|
147
|
+
if (!seen.has(root)) {
|
|
148
|
+
seen.add(root);
|
|
149
|
+
roots.push(root);
|
|
150
|
+
}
|
|
151
|
+
// Independent of the order dedupe above: a root already discovered from
|
|
152
|
+
// cwd still takes the first profile entry's ceiling.
|
|
153
|
+
if (pointer.memory !== null && !caps.has(root))
|
|
154
|
+
caps.set(root, pointer.memory);
|
|
125
155
|
}
|
|
126
156
|
}
|
|
127
|
-
|
|
128
|
-
|
|
157
|
+
const annotated = roots.map((root) => ({ root, memory: caps.get(root) ?? NEUTRAL_PROJECT_MEMORY }));
|
|
158
|
+
const entry = { roots, annotated };
|
|
159
|
+
scopeRootsCache.set(key, entry);
|
|
160
|
+
return entry;
|
|
129
161
|
}
|
|
130
162
|
export function projectScopeRoot(startDir) {
|
|
131
163
|
return findProjectScopeRoot(startDir);
|
|
@@ -133,6 +165,9 @@ export function projectScopeRoot(startDir) {
|
|
|
133
165
|
export function projectScopeRoots(startDir, profileId) {
|
|
134
166
|
return findProjectScopeRoots(startDir, profileId);
|
|
135
167
|
}
|
|
168
|
+
export function projectScopeRootsWithMemory(startDir, profileId) {
|
|
169
|
+
return findProjectScopeRootsWithMemory(startDir, profileId);
|
|
170
|
+
}
|
|
136
171
|
export function scopeRoot(scope) {
|
|
137
172
|
// The builtin scope has no scope-root dir; its content lives in dedicated
|
|
138
173
|
// package directories such as builtinMemoryRoot().
|
|
@@ -160,7 +195,12 @@ export function ensureProjectScopeRoot(startDir = process.cwd()) {
|
|
|
160
195
|
const resolvedStart = resolve(startDir);
|
|
161
196
|
const root = join(resolvedStart, CRTR_DIR_NAME);
|
|
162
197
|
const profileId = process.env['CRTR_PROFILE_ID'] || null;
|
|
163
|
-
|
|
198
|
+
// Neutral ceiling: the empty cached stack proves no profile entry's ancestor
|
|
199
|
+
// walk reached this root, since it did not exist to be walked into.
|
|
200
|
+
scopeRootsCache.set(scopeRootsCacheKey(resolvedStart, profileId), {
|
|
201
|
+
roots: [root],
|
|
202
|
+
annotated: [{ root, memory: NEUTRAL_PROJECT_MEMORY }],
|
|
203
|
+
});
|
|
164
204
|
return root;
|
|
165
205
|
}
|
|
166
206
|
export function pluginsDir(scope) {
|
|
@@ -15,7 +15,13 @@ export declare function renderOnReadDocsForSubject(subject: NodeConfigSubject, r
|
|
|
15
15
|
/** Workspace boot docs using the snapshot's cwd/profile instead of a canvas
|
|
16
16
|
* lookup. `seen` is the transcript-scoped dedup set shared with the on-read
|
|
17
17
|
* hook, so a front door delivered here never re-delivers on a later read
|
|
18
|
-
* beneath its store.
|
|
18
|
+
* beneath its store.
|
|
19
|
+
*
|
|
20
|
+
* Each candidate's authored rung is clamped to its project's profile
|
|
21
|
+
* relationship cap BEFORE the `none` filter, the ordering, the render, and the
|
|
22
|
+
* seen-map recording — so a capped doc never renders above its cap, a `none`
|
|
23
|
+
* project contributes nothing, and a later explicit `crtr memory read` can
|
|
24
|
+
* still upgrade what workspace-open disclosed at a lower rung. */
|
|
19
25
|
export declare function renderWorkspaceOpenDocsForSubject(subject: NodeConfigSubject, cwd: string, profileId: string | null, seen?: InjectedDocs): string;
|
|
20
26
|
/** Inner `<memory>` blocks for the memory-read event: docs whose
|
|
21
27
|
* `memory-read` entries match the doc a `crtr memory read` just resolved
|
|
@@ -33,11 +33,11 @@ import { CRTR_DIR_NAME } from '../../types.js';
|
|
|
33
33
|
import { pathExists, readText, walkFiles } from '../fs-utils.js';
|
|
34
34
|
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
35
35
|
import { listAllMemoryDocs, listProjectMemoryDocs } from '../memory-resolver.js';
|
|
36
|
-
import { userScopeRoot } from '../scope.js';
|
|
36
|
+
import { NEUTRAL_PROJECT_MEMORY, userScopeRoot } from '../scope.js';
|
|
37
37
|
import { gatePasses } from './gate.js';
|
|
38
38
|
import { commandDeliveryRung, memoryReadDeliveryRung, owningRootOf, readDeliveryRung, workspaceOpenRung } from './surface-match.js';
|
|
39
39
|
import { deliveredAtOrAbove, recordDelivery } from './injected-store.js';
|
|
40
|
-
import { normalizeDocName, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, resolveDocName } from './schema.js';
|
|
40
|
+
import { minRung, normalizeDocName, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, resolveDocName } from './schema.js';
|
|
41
41
|
import { cachedSubstrateDocsInclusive } from './session-cache.js';
|
|
42
42
|
import { realpathOrSelf } from '../fs-utils.js';
|
|
43
43
|
const JUNK_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.cache', '.yalc']);
|
|
@@ -91,6 +91,9 @@ function loadProjectDoc(file, memoryDir) {
|
|
|
91
91
|
scope: 'project',
|
|
92
92
|
path: file,
|
|
93
93
|
body,
|
|
94
|
+
// The read event is uncapped by design: this walk is reached only by an
|
|
95
|
+
// explicit file read, never by automatic boot/workspace-open delivery.
|
|
96
|
+
projectMemory: NEUTRAL_PROJECT_MEMORY,
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
99
|
catch {
|
|
@@ -244,7 +247,13 @@ export function renderOnReadDocsForSubject(subject, readFilePath, seen = new Map
|
|
|
244
247
|
/** Workspace boot docs using the snapshot's cwd/profile instead of a canvas
|
|
245
248
|
* lookup. `seen` is the transcript-scoped dedup set shared with the on-read
|
|
246
249
|
* hook, so a front door delivered here never re-delivers on a later read
|
|
247
|
-
* beneath its store.
|
|
250
|
+
* beneath its store.
|
|
251
|
+
*
|
|
252
|
+
* Each candidate's authored rung is clamped to its project's profile
|
|
253
|
+
* relationship cap BEFORE the `none` filter, the ordering, the render, and the
|
|
254
|
+
* seen-map recording — so a capped doc never renders above its cap, a `none`
|
|
255
|
+
* project contributes nothing, and a later explicit `crtr memory read` can
|
|
256
|
+
* still upgrade what workspace-open disclosed at a lower rung. */
|
|
248
257
|
export function renderWorkspaceOpenDocsForSubject(subject, cwd, profileId, seen = new Map()) {
|
|
249
258
|
let docs;
|
|
250
259
|
try {
|
|
@@ -256,7 +265,7 @@ export function renderWorkspaceOpenDocsForSubject(subject, cwd, profileId, seen
|
|
|
256
265
|
return '';
|
|
257
266
|
}
|
|
258
267
|
const candidates = dedupeByPhysicalPath(docs)
|
|
259
|
-
.map((doc) => ({ doc, rung: workspaceOpenRung(doc) }))
|
|
268
|
+
.map((doc) => ({ doc, rung: minRung(workspaceOpenRung(doc), doc.projectMemory) }))
|
|
260
269
|
.filter(({ rung }) => rung !== 'none')
|
|
261
270
|
.sort((a, b) => {
|
|
262
271
|
const aRoot = owningRootOf(a.doc) ?? '';
|
|
@@ -57,14 +57,14 @@ import { subKindsAvailableTo } from '../config.js';
|
|
|
57
57
|
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
58
58
|
import { pathExists, readText, realpathOrSelf, walkFiles } from '../fs-utils.js';
|
|
59
59
|
import { memoryDir } from '../runtime/memory.js';
|
|
60
|
-
import { projectScopeRoots } from '../scope.js';
|
|
60
|
+
import { NEUTRAL_PROJECT_MEMORY, projectScopeRoots } from '../scope.js';
|
|
61
61
|
import { gatePasses } from './gate.js';
|
|
62
62
|
// De-barreled to LEAF modules on purpose: importing from './index.js' would
|
|
63
63
|
// pull subject.js (canvas-db) transitively, re-tainting every CLI consumer of
|
|
64
64
|
// the pure render fns. The canvas-db wrappers (renderPreferencesSection /
|
|
65
65
|
// renderKnowledgeBlock) live in render-node.js instead.
|
|
66
66
|
import { recordDelivery } from './injected-store.js';
|
|
67
|
-
import { bootRung, normalizeDocName, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, resolveDocName, rungRank, } from './schema.js';
|
|
67
|
+
import { bootRung, minRung, normalizeDocName, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, resolveDocName, rungRank, } from './schema.js';
|
|
68
68
|
import { cachedSubstrateDocs } from './session-cache.js';
|
|
69
69
|
/** First-wins dedup by (already-normalized) name, preserving the input's
|
|
70
70
|
* precedence order. The array's ORDER is the precedence signal — callers must
|
|
@@ -89,7 +89,13 @@ function dedupeFirstWins(docs) {
|
|
|
89
89
|
* selection ONLY; display ordering is a separate, later step (see
|
|
90
90
|
* renderGrouped) so a builtin doc can never mask a project/user override by
|
|
91
91
|
* virtue of sorting first. Uses the per-session cache so the full corpus is
|
|
92
|
-
* scanned + parsed at most once per session across the boot-render calls.
|
|
92
|
+
* scanned + parsed at most once per session across the boot-render calls.
|
|
93
|
+
*
|
|
94
|
+
* The profile relationship's cap applies HERE, before dedup: a `none` project
|
|
95
|
+
* leaves the corpus entirely (it contributes nothing, not even a `[+N more]`
|
|
96
|
+
* count, and so cannot shadow a wider-scope automatic winner), and every
|
|
97
|
+
* surviving doc's authored boot rung is clamped to its cap before dedup, the
|
|
98
|
+
* session cache, and delivery seeding see it. */
|
|
93
99
|
function selectWinners(subject, kind) {
|
|
94
100
|
let docs;
|
|
95
101
|
try {
|
|
@@ -100,8 +106,9 @@ function selectWinners(subject, kind) {
|
|
|
100
106
|
}
|
|
101
107
|
const eligible = docs
|
|
102
108
|
.filter((d) => d.kind === kind)
|
|
109
|
+
.filter((d) => d.projectMemory !== 'none')
|
|
103
110
|
.filter((d) => gatePasses(d, subject))
|
|
104
|
-
.map((d) => ({ ...d, bootRung: bootRung(d) }));
|
|
111
|
+
.map((d) => ({ ...d, bootRung: minRung(bootRung(d), d.projectMemory) }));
|
|
105
112
|
return dedupeFirstWins(eligible);
|
|
106
113
|
}
|
|
107
114
|
// ---------------------------------------------------------------------------
|
|
@@ -238,7 +245,9 @@ function nodeLocalDocs(nodeId, subject) {
|
|
|
238
245
|
// node-local is NOT a resolver scope; `scope` is a placeholder never read
|
|
239
246
|
// by gate eval (keyed off the NODE subject, not the doc) nor by the
|
|
240
247
|
// renderers (keyed off name / body / rung).
|
|
241
|
-
|
|
248
|
+
// Node-local is not a project store: no profile relationship names it,
|
|
249
|
+
// so it carries the neutral ceiling.
|
|
250
|
+
out.push({ ...schema, name, scope: 'user', path: file, body, projectMemory: NEUTRAL_PROJECT_MEMORY });
|
|
242
251
|
}
|
|
243
252
|
catch {
|
|
244
253
|
// A single malformed file is skipped, never fatal to the render.
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { type ProfileProjectMemory } from '../../api/dto/profiles.js';
|
|
1
2
|
import type { MemoryDoc, MemoryScope } from '../memory-resolver.js';
|
|
2
3
|
export declare const KINDS: readonly ["knowledge", "preference"];
|
|
3
4
|
export type DocKind = (typeof KINDS)[number];
|
|
4
5
|
/** Is `v` one of the two valid document kinds? */
|
|
5
6
|
export declare function isDocKind(v: unknown): v is DocKind;
|
|
6
7
|
export declare const RUNGS: readonly ["none", "name", "preview", "content"];
|
|
7
|
-
export type Rung =
|
|
8
|
+
export type Rung = ProfileProjectMemory;
|
|
8
9
|
/** Ordinal of a rung on the ladder (none=0 … content=3). */
|
|
9
10
|
export declare function rungRank(r: Rung): number;
|
|
10
11
|
/** Does rung `r` disclose at least as much as `min`? */
|
|
11
12
|
export declare function rungAtLeast(r: Rung, min: Rung): boolean;
|
|
13
|
+
/** The lower of two rungs — the clamp automatic delivery applies to an
|
|
14
|
+
* authored rung against its project relationship's cap. */
|
|
15
|
+
export declare function minRung(a: Rung, b: Rung): Rung;
|
|
12
16
|
/** Strip an optional `NN-` ordering prefix from ONE path segment (file or
|
|
13
17
|
* directory display name). `00-runtime-base` -> `runtime-base`; `spine` (no
|
|
14
18
|
* prefix) is unchanged. */
|
|
@@ -111,6 +115,12 @@ export interface SubstrateDoc extends SubstrateSchema {
|
|
|
111
115
|
path: string;
|
|
112
116
|
/** Document body, frontmatter stripped. */
|
|
113
117
|
body: string;
|
|
118
|
+
/** The selected profile relationship's cap on the project store this doc
|
|
119
|
+
* resolved from — the maximum rung automatic boot and workspace-open
|
|
120
|
+
* delivery may disclose it at. `content` is the neutral ceiling every
|
|
121
|
+
* non-project doc and every uncapped project root carries. Targeted reads,
|
|
122
|
+
* command routing, and directory listings ignore it. */
|
|
123
|
+
projectMemory: ProfileProjectMemory;
|
|
114
124
|
}
|
|
115
125
|
/** Parse a raw frontmatter record (from `parseFrontmatterGeneric`, via the
|
|
116
126
|
* resolver) into a typed schema with defaults applied. Returns `null` when the
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// The runtime parser is tolerant by contract (it maps over many docs and must
|
|
8
8
|
// never throw): invalid `surfaces` entries are dropped rather than crashing a
|
|
9
9
|
// render, and `crtr memory lint` owns strict authoring-time enforcement.
|
|
10
|
+
import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Kinds — the two semantic kinds (design §3): `knowledge` (consult — procedural
|
|
12
13
|
// playbooks + factual references merged) vs `preference` (behave — standing
|
|
@@ -22,7 +23,10 @@ export function isDocKind(v) {
|
|
|
22
23
|
// each rung is a superset of disclosure over the one before:
|
|
23
24
|
// none < name < preview < content
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
+
// The ladder is ONE union, owned by the public API DTO (`ProfileProjectMemory`)
|
|
27
|
+
// so a profile relationship's cap and a doc's authored rung are the same values
|
|
28
|
+
// and clamp against each other without a conversion.
|
|
29
|
+
export const RUNGS = PROFILE_PROJECT_MEMORY_VALUES;
|
|
26
30
|
/** Ordinal of a rung on the ladder (none=0 … content=3). */
|
|
27
31
|
export function rungRank(r) {
|
|
28
32
|
return RUNGS.indexOf(r);
|
|
@@ -31,6 +35,11 @@ export function rungRank(r) {
|
|
|
31
35
|
export function rungAtLeast(r, min) {
|
|
32
36
|
return rungRank(r) >= rungRank(min);
|
|
33
37
|
}
|
|
38
|
+
/** The lower of two rungs — the clamp automatic delivery applies to an
|
|
39
|
+
* authored rung against its project relationship's cap. */
|
|
40
|
+
export function minRung(a, b) {
|
|
41
|
+
return rungRank(a) <= rungRank(b) ? a : b;
|
|
42
|
+
}
|
|
34
43
|
// ---------------------------------------------------------------------------
|
|
35
44
|
// Display-name normalization — the optional `NN-` ordering pin. A doc's
|
|
36
45
|
// physical path may carry a two-digit numeric prefix on a file or directory
|
|
@@ -133,7 +142,7 @@ export function parseSubstrateDoc(doc) {
|
|
|
133
142
|
const schema = parseSubstrateFrontmatter(doc.frontmatter);
|
|
134
143
|
if (schema === null)
|
|
135
144
|
return null;
|
|
136
|
-
return { ...schema, name: doc.name, scope: doc.scope, path: doc.path, body: doc.body };
|
|
145
|
+
return { ...schema, name: doc.name, scope: doc.scope, path: doc.path, body: doc.body, projectMemory: doc.projectMemory };
|
|
137
146
|
}
|
|
138
147
|
/** The kind a doc resolves to for `--kind` filtering/matching: the substrate
|
|
139
148
|
* kind when the frontmatter parses as a valid substrate doc, else the raw
|
|
@@ -42,7 +42,7 @@ after(() => {
|
|
|
42
42
|
resetScopeCache();
|
|
43
43
|
});
|
|
44
44
|
test('create rejects a paused profile before a node exists', async () => {
|
|
45
|
-
const profile = createProfile('paused profile', [cwd]);
|
|
45
|
+
const profile = createProfile('paused profile', [{ path: cwd, memory: 'content' }]);
|
|
46
46
|
pauseProfile(profile.profileId);
|
|
47
47
|
const ctx = {
|
|
48
48
|
method: 'POST',
|
|
@@ -62,7 +62,7 @@ test('create rejects a paused profile before a node exists', async () => {
|
|
|
62
62
|
assert.equal(listNodes().length, beforeRows);
|
|
63
63
|
});
|
|
64
64
|
test('revive refuses a paused profile before launching a broker', () => {
|
|
65
|
-
const profile = createProfile('paused revive', [cwd]);
|
|
65
|
+
const profile = createProfile('paused revive', [{ path: cwd, memory: 'content' }]);
|
|
66
66
|
pauseProfile(profile.profileId);
|
|
67
67
|
const nodeId = 'paused-revive-node';
|
|
68
68
|
createNode({
|
|
@@ -90,7 +90,7 @@ test('revive refuses a paused profile before launching a broker', () => {
|
|
|
90
90
|
// because they all reach the canvas through spawnNode and none of them call
|
|
91
91
|
// handleCreate.
|
|
92
92
|
test('the birth gate refuses a paused profile for a daemon-side spawn', () => {
|
|
93
|
-
const profile = createProfile('paused spawn', [cwd]);
|
|
93
|
+
const profile = createProfile('paused spawn', [{ path: cwd, memory: 'content' }]);
|
|
94
94
|
pauseProfile(profile.profileId);
|
|
95
95
|
const beforeRows = listNodes().length;
|
|
96
96
|
assert.throws(() => spawnNode({ kind: 'general', cwd, parent: null, profile_id: profile.profileId }), (error) => {
|
|
@@ -102,7 +102,7 @@ test('the birth gate refuses a paused profile for a daemon-side spawn', () => {
|
|
|
102
102
|
assert.equal(listNodes().length, beforeRows);
|
|
103
103
|
});
|
|
104
104
|
test('create kind resolves from the profile default when omitted', () => {
|
|
105
|
-
const profile = createProfile('default kind', [cwd]);
|
|
105
|
+
const profile = createProfile('default kind', [{ path: cwd, memory: 'content' }]);
|
|
106
106
|
setProfileDefaultKind(profile.profileId, 'developer');
|
|
107
107
|
const entry = loadProfileManifest(profile.profileId);
|
|
108
108
|
assert.equal(resolveCreateKind(undefined, entry), 'developer');
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Chat-inventory handler. `GET /v1/nodes/{id}/chat-inventory` answers what a
|
|
2
|
+
// non-terminal chat surface may offer for one node: the slash commands its live
|
|
3
|
+
// engine registered that opted into chat disclosure, and the memory documents an
|
|
4
|
+
// inline `/name` token resolves to.
|
|
5
|
+
//
|
|
6
|
+
// Read-only in both directions — it takes one observer round trip against a
|
|
7
|
+
// broker that is already running and writes no canvas state. A node whose broker
|
|
8
|
+
// is not live answers 200 with `broker_live: false` and empty arrays rather than
|
|
9
|
+
// 404 or a revival: "this node has nothing to offer right now" is a normal
|
|
10
|
+
// answer, not an error.
|
|
11
|
+
import { getNode } from '../../../core/canvas/canvas.js';
|
|
12
|
+
import { isSafeNodeId } from '../../../core/canvas/paths.js';
|
|
13
|
+
import { notFound, usage } from '../../../core/errors.js';
|
|
14
|
+
import { readChatInventory } from '../../../core/runtime/broker-inventory.js';
|
|
15
|
+
async function handleGet(ctx) {
|
|
16
|
+
const id = ctx.params['id'];
|
|
17
|
+
if (!isSafeNodeId(id))
|
|
18
|
+
throw usage(`invalid node id: ${id}`, { received: id });
|
|
19
|
+
if (getNode(id) === null)
|
|
20
|
+
throw notFound(`unknown node: ${id}`, { received: id });
|
|
21
|
+
return { status: 200, body: await readChatInventory(id) };
|
|
22
|
+
}
|
|
23
|
+
export const chatInventoryRoutes = [
|
|
24
|
+
{ method: 'GET', pattern: '/v1/nodes/:id/chat-inventory', handler: handleGet },
|
|
25
|
+
];
|