@north-light/crouter 0.3.220 → 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/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__/inline-memory-refs.test.js +36 -2
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +217 -0
- 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/event-projection.js +7 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +15 -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 +9 -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/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
package/dist/api/client.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { AttachEnsureRequest, AttachEnsureResultDTO } from './dto/attach.js
|
|
|
12
12
|
import type { DeleteProfileRequest, DeleteProfileResultDTO, EnsureProfileRequest, ProfileDTO } from './dto/profiles.js';
|
|
13
13
|
import type { FilePeekDTO } from './dto/files.js';
|
|
14
14
|
import type { MemoryDocRefDTO } from './dto/memory.js';
|
|
15
|
+
import type { ChatInventoryDTO } from './dto/chat-inventory.js';
|
|
15
16
|
import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredentialRequest, ModelAuthListDTO } from './dto/modelauth.js';
|
|
16
17
|
import type { CreateHumanBridgeRequest, HumanBridgeResultDTO, HumanCancelRequest, HumanCancelResultDTO, HumanResolveRequest, HumanResolveResultDTO } from './dto/human.js';
|
|
17
18
|
import type { CancelReviewRequest, CreateReviewRequest, ListReviewsQuery, ReviewCancelResultDTO, ReviewDocumentBaseDTO, ReviewDTO, ReviewListDTO, ReviewSubmitResultDTO } from './dto/reviews.js';
|
|
@@ -151,6 +152,11 @@ export declare class CrtrClient {
|
|
|
151
152
|
/** The node's conversation exactly as it ran — raw `.jsonl` bytes plus the
|
|
152
153
|
* assembled system prompt. For exports; `getSnapshot` is for renderers. */
|
|
153
154
|
getSession(id: string): Promise<NodeSessionDTO>;
|
|
155
|
+
/** What a non-terminal chat surface may offer for this node: the chat-capable
|
|
156
|
+
* slash commands its live engine registered, and the memory documents an
|
|
157
|
+
* inline `/name` token resolves to. Never revives — a node whose broker is
|
|
158
|
+
* not live answers `broker_live: false` with empty arrays. */
|
|
159
|
+
getChatInventory(id: string): Promise<ChatInventoryDTO>;
|
|
154
160
|
getArtifacts(id: string, q?: ArtifactsQuery): Promise<ArtifactListDTO>;
|
|
155
161
|
getContext(id: string): Promise<ContextListDTO>;
|
|
156
162
|
/** Read an absolute host path as UTF-8 (capped, `truncated` when clipped) for
|
|
@@ -160,6 +166,9 @@ export declare class CrtrClient {
|
|
|
160
166
|
* node would read — the node's own precedence chain, not this process's.
|
|
161
167
|
* Pair with `peekFile` to render the document. */
|
|
162
168
|
resolveMemoryDoc(name: string, nodeId: string): Promise<MemoryDocRefDTO>;
|
|
169
|
+
/** Create-or-return by name. Supplied `projects` are shape-checked even when
|
|
170
|
+
* the profile already exists; their directories are only required to exist
|
|
171
|
+
* when this call creates the profile. */
|
|
163
172
|
ensureProfile(name: string, req?: EnsureProfileRequest): Promise<ProfileDTO>;
|
|
164
173
|
listProfiles(): Promise<ProfileDTO[]>;
|
|
165
174
|
getProfile(name: string): Promise<ProfileDTO>;
|
package/dist/api/client.js
CHANGED
|
@@ -267,6 +267,13 @@ export class CrtrClient {
|
|
|
267
267
|
getSession(id) {
|
|
268
268
|
return this.request('GET', routes.nodeSession(this.nodePath(id)));
|
|
269
269
|
}
|
|
270
|
+
/** What a non-terminal chat surface may offer for this node: the chat-capable
|
|
271
|
+
* slash commands its live engine registered, and the memory documents an
|
|
272
|
+
* inline `/name` token resolves to. Never revives — a node whose broker is
|
|
273
|
+
* not live answers `broker_live: false` with empty arrays. */
|
|
274
|
+
getChatInventory(id) {
|
|
275
|
+
return this.request('GET', routes.nodeChatInventory(this.nodePath(id)));
|
|
276
|
+
}
|
|
270
277
|
getArtifacts(id, q) {
|
|
271
278
|
return this.request('GET', withQuery(routes.nodeArtifacts(this.nodePath(id)), q));
|
|
272
279
|
}
|
|
@@ -287,6 +294,9 @@ export class CrtrClient {
|
|
|
287
294
|
return this.request('GET', withQuery(routes.memoryResolve(), { name, node: nodeId }));
|
|
288
295
|
}
|
|
289
296
|
// ---- Profiles ----------------------------------------------------------
|
|
297
|
+
/** Create-or-return by name. Supplied `projects` are shape-checked even when
|
|
298
|
+
* the profile already exists; their directories are only required to exist
|
|
299
|
+
* when this call creates the profile. */
|
|
290
300
|
ensureProfile(name, req) {
|
|
291
301
|
return this.request('PUT', routes.profile(name), req ?? {});
|
|
292
302
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** One command a chat surface may advertise. Builtin and non-opted-in rows are
|
|
2
|
+
* dropped before this DTO exists, so `source` never carries `'builtin'`. */
|
|
3
|
+
export interface ChatInventoryCommandDTO {
|
|
4
|
+
/** No leading slash, exactly as the engine dispatches it. */
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
source: 'command' | 'template';
|
|
8
|
+
/** Argument shape to display beside the name, when the command supplies one. */
|
|
9
|
+
argument_hint?: string;
|
|
10
|
+
/** Deterministic expansion metadata for a memory-slash command — the same
|
|
11
|
+
* material the terminal preview uses, so a chat preview cannot drift from
|
|
12
|
+
* what submission sends. */
|
|
13
|
+
expansion?: {
|
|
14
|
+
kind: 'memory-slash';
|
|
15
|
+
commandName: string;
|
|
16
|
+
body: string;
|
|
17
|
+
};
|
|
18
|
+
/** Raw prompt-template content (frontmatter stripped), for `source: 'template'`. */
|
|
19
|
+
template?: string;
|
|
20
|
+
}
|
|
21
|
+
/** One resolvable inline memory reference. Metadata only — never the document
|
|
22
|
+
* body or its source path. `shortForm` stays camelCase to mirror the broker's
|
|
23
|
+
* own `RefMeta`, which is where these rows come from. */
|
|
24
|
+
export interface ChatInventoryMemoryRefDTO {
|
|
25
|
+
/** Canonical `/`-joined name, e.g. `taste/writing`. */
|
|
26
|
+
name: string;
|
|
27
|
+
kind: 'knowledge' | 'preference';
|
|
28
|
+
scope: 'node' | 'project' | 'profile' | 'user' | 'builtin';
|
|
29
|
+
shortForm: string;
|
|
30
|
+
}
|
|
31
|
+
/** `GET /v1/nodes/{id}/chat-inventory` result.
|
|
32
|
+
*
|
|
33
|
+
* `broker_live` reports whether the node's engine was reachable at all. There
|
|
34
|
+
* is no per-part error flag: a part that failed and a part that is genuinely
|
|
35
|
+
* empty both arrive as an empty array, and a client's behavior is identical
|
|
36
|
+
* for both. A dormant node answers 200 with `broker_live: false` — this read
|
|
37
|
+
* never revives an engine. */
|
|
38
|
+
export interface ChatInventoryDTO {
|
|
39
|
+
node_id: string;
|
|
40
|
+
broker_live: boolean;
|
|
41
|
+
commands: ChatInventoryCommandDTO[];
|
|
42
|
+
memory_refs: ChatInventoryMemoryRefDTO[];
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Chat-inventory DTO. Backs `GET /v1/nodes/{id}/chat-inventory` — the one read
|
|
2
|
+
// a non-terminal chat surface makes to learn what the node's live engine will
|
|
3
|
+
// accept: the slash commands that can complete their outcome from a chat
|
|
4
|
+
// conversation, and the memory documents an inline `/name` token resolves to.
|
|
5
|
+
//
|
|
6
|
+
// Eligibility is decided in crouter and disclosed here. A client never filters
|
|
7
|
+
// by name, infers capability, or invents a row: what is absent from these
|
|
8
|
+
// arrays is not offered.
|
|
9
|
+
//
|
|
10
|
+
// PURITY (spec §3.1): Node built-ins + `src/api/*` only.
|
|
11
|
+
export {};
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
/** The disclosure ladder, lowest to highest. Index order is the ordering. */
|
|
2
|
+
export declare const PROFILE_PROJECT_MEMORY_VALUES: readonly ["none", "name", "preview", "content"];
|
|
3
|
+
/** How much of a project's memory stores the profile relationship lets an
|
|
4
|
+
* automatic delivery disclose. */
|
|
5
|
+
export type ProfileProjectMemory = (typeof PROFILE_PROJECT_MEMORY_VALUES)[number];
|
|
6
|
+
/** One project directory in a profile's purview. */
|
|
7
|
+
export interface ProfileProject {
|
|
8
|
+
/** Absolute, real-path-resolved directory. */
|
|
9
|
+
path: string;
|
|
10
|
+
/** Maximum rung automatic boot and workspace-open delivery may reach from
|
|
11
|
+
* this project's memory stores, regardless of the node's working directory.
|
|
12
|
+
* An authored lower rung stays lower; targeted reads are never capped. */
|
|
13
|
+
memory: ProfileProjectMemory;
|
|
14
|
+
}
|
|
1
15
|
/** `PUT /v1/profiles/{name}` body — idempotent ensure. Every field applies
|
|
2
16
|
* only at create; an existing same-named profile is returned untouched. */
|
|
3
17
|
export interface EnsureProfileRequest {
|
|
4
|
-
/**
|
|
5
|
-
projects?:
|
|
18
|
+
/** Project directories in the profile's purview, each with its memory cap. */
|
|
19
|
+
projects?: ProfileProject[];
|
|
6
20
|
/** Persona kind for node creates under the profile that omit kind. */
|
|
7
21
|
default_kind?: string;
|
|
8
22
|
/** Profile facts (identity, role); each entry reaches every broker
|
|
@@ -37,9 +51,9 @@ export interface ProfileDTO {
|
|
|
37
51
|
/** Stable profile-directory id (`<slug>-<id>`). */
|
|
38
52
|
id: string;
|
|
39
53
|
name: string;
|
|
40
|
-
projects:
|
|
41
|
-
/** Where nodes under this profile run — one of `projects` (the first
|
|
42
|
-
* re-pointed), or null when the profile owns none. */
|
|
54
|
+
projects: ProfileProject[];
|
|
55
|
+
/** Where nodes under this profile run — one of `projects[].path` (the first
|
|
56
|
+
* unless re-pointed), or null when the profile owns none. */
|
|
43
57
|
home: string | null;
|
|
44
58
|
/** ISO timestamp when the profile was paused, or null while active. */
|
|
45
59
|
paused_at: string | null;
|
package/dist/api/dto/profiles.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
// Profile DTOs (spec §6.6). Profile deletion crosses profile, canvas, cron,
|
|
2
2
|
// inbox, and review state, so every consumer routes it through crtrd.
|
|
3
|
-
|
|
3
|
+
/** The disclosure ladder, lowest to highest. Index order is the ordering. */
|
|
4
|
+
export const PROFILE_PROJECT_MEMORY_VALUES = ['none', 'name', 'preview', 'content'];
|
package/dist/api/index.d.ts
CHANGED
package/dist/api/index.js
CHANGED
package/dist/api/routes.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare const routes: {
|
|
|
11
11
|
readonly nodeSnapshot: (id: string) => string;
|
|
12
12
|
readonly nodeSubject: (id: string) => string;
|
|
13
13
|
readonly nodeSession: (id: string) => string;
|
|
14
|
+
readonly nodeChatInventory: (id: string) => string;
|
|
14
15
|
readonly nodeTranscript: (id: string) => string;
|
|
15
16
|
readonly nodeContext: (id: string) => string;
|
|
16
17
|
readonly nodeArtifacts: (id: string) => string;
|
package/dist/api/routes.js
CHANGED
|
@@ -27,6 +27,7 @@ export const routes = {
|
|
|
27
27
|
nodeSnapshot: (id) => `${V}/nodes/${id}/snapshot`,
|
|
28
28
|
nodeSubject: (id) => `${V}/nodes/${id}/subject`,
|
|
29
29
|
nodeSession: (id) => `${V}/nodes/${id}/session`,
|
|
30
|
+
nodeChatInventory: (id) => `${V}/nodes/${id}/chat-inventory`,
|
|
30
31
|
nodeTranscript: (id) => `${V}/nodes/${id}/transcript`,
|
|
31
32
|
nodeContext: (id) => `${V}/nodes/${id}/context`,
|
|
32
33
|
nodeArtifacts: (id) => `${V}/nodes/${id}/artifacts`,
|
package/dist/build-root.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { RootDef } from './core/command.js';
|
|
2
|
+
import { type CoreHookCatalog } from './core/command-hooks/catalog.js';
|
|
2
3
|
/** Every shipped subtree name. Cheap (no module loading) — the front-door
|
|
3
4
|
* recursion guard and the dispatcher's first-token routing need only names. */
|
|
4
5
|
export declare const SUBTREE_NAMES: readonly string[];
|
|
5
|
-
|
|
6
|
-
* plugin `helpAddenda` keys are validated against at the strict gates
|
|
7
|
-
* (install, bundle parse, doctor/inspect reports). Loads every core subtree,
|
|
8
|
-
* so call it only from those gates, never on a dispatch path. Passthrough
|
|
9
|
-
* branches are excluded: crtr never renders their help, so an addendum
|
|
10
|
-
* targeting one could never appear. */
|
|
6
|
+
export declare function coreHookCatalog(): Promise<CoreHookCatalog>;
|
|
11
7
|
export declare function coreCommandPaths(): Promise<ReadonlySet<string>>;
|
|
12
8
|
/** Build a root that contains only the subtree `first` dispatches into.
|
|
13
9
|
* Returns the FULL root when `first` is not a recognized subtree — bare `crtr`,
|
package/dist/build-root.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { defineRoot } from './core/command.js';
|
|
2
|
+
import { createCoreHookCatalog } from './core/command-hooks/catalog.js';
|
|
3
|
+
import { composeCoreHooks } from './core/command-hooks/compose.js';
|
|
4
|
+
import { mark } from './core/timing.js';
|
|
2
5
|
const TAGLINE = 'crtr: agentic runtime.';
|
|
3
6
|
/** Flags handled before dispatch, so they belong to no leaf schema and are
|
|
4
7
|
* stated once at root (help.ts renders them as the Globals footer). */
|
|
@@ -43,8 +46,51 @@ export const SUBTREE_NAMES = Object.freeze(Object.keys(SUBTREE_LOADERS));
|
|
|
43
46
|
* so call it only from those gates, never on a dispatch path. Passthrough
|
|
44
47
|
* branches are excluded: crtr never renders their help, so an addendum
|
|
45
48
|
* targeting one could never appear. */
|
|
49
|
+
/** Every core subtree, built once per process. The hook catalog and the full
|
|
50
|
+
* root both need the complete set; a recognized core first token still loads
|
|
51
|
+
* only its own subtree until something actually demands the whole tree. */
|
|
52
|
+
let coreSubtrees;
|
|
53
|
+
function loadCoreSubtrees() {
|
|
54
|
+
coreSubtrees ??= Promise.all(SUBTREE_NAMES.map((n) => SUBTREE_LOADERS[n]()));
|
|
55
|
+
return coreSubtrees;
|
|
56
|
+
}
|
|
57
|
+
/** Complete static catalog of hook-eligible core leaves. Lifecycle and recovery
|
|
58
|
+
* inspection share the same memoized subtree load as root composition. */
|
|
59
|
+
let hookCatalog;
|
|
60
|
+
export function coreHookCatalog() {
|
|
61
|
+
mark('cli.hooks.catalog');
|
|
62
|
+
hookCatalog ??= loadCoreSubtrees().then(createCoreHookCatalog);
|
|
63
|
+
return hookCatalog;
|
|
64
|
+
}
|
|
65
|
+
/** A loader for the effective hook registry, compiled over the COMPLETE core
|
|
66
|
+
* catalog so a hook targeting any other subtree is never mistaken for a stale
|
|
67
|
+
* target. Loading is lazy — only eligible leaf help or dispatch reaches it,
|
|
68
|
+
* and static help discovery never runs a plugin executable. The memoization
|
|
69
|
+
* is per composed root, never process-global: discovery reads
|
|
70
|
+
* stored plugin bytes and the caller's scope stack, so a plugin install,
|
|
71
|
+
* update, or disable (and a cwd change in a long-lived host) must govern the
|
|
72
|
+
* next root instead of being pinned by the first one. */
|
|
73
|
+
function hookRegistryLoader() {
|
|
74
|
+
let registry;
|
|
75
|
+
return () => {
|
|
76
|
+
// Dynamic import keeps hook discovery (manifest/artifact validation, plugin
|
|
77
|
+
// resolution) off the module graph of an invocation that neither renders
|
|
78
|
+
// eligible leaf help nor dispatches one, matching this file's lazy rationale.
|
|
79
|
+
registry ??= (async () => {
|
|
80
|
+
const { compileHookRegistry, effectiveHookPlugins } = await import('./core/command-hooks/discovery.js');
|
|
81
|
+
const plugins = effectiveHookPlugins();
|
|
82
|
+
mark('cli.hooks.effective_plugins', { count: plugins.length });
|
|
83
|
+
if (plugins.length === 0) {
|
|
84
|
+
mark('cli.hooks.empty_registry');
|
|
85
|
+
return compileHookRegistry(createCoreHookCatalog([]), []);
|
|
86
|
+
}
|
|
87
|
+
return compileHookRegistry(await coreHookCatalog(), plugins);
|
|
88
|
+
})();
|
|
89
|
+
return registry;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
46
92
|
export async function coreCommandPaths() {
|
|
47
|
-
const core = await
|
|
93
|
+
const core = await loadCoreSubtrees();
|
|
48
94
|
const paths = new Set();
|
|
49
95
|
const visit = (node, prefix) => {
|
|
50
96
|
if (node.kind === 'branch' && node.passthrough !== undefined)
|
|
@@ -67,7 +113,7 @@ export async function coreCommandPaths() {
|
|
|
67
113
|
export async function resolveRoot(first) {
|
|
68
114
|
const loader = first !== undefined ? SUBTREE_LOADERS[first] : undefined;
|
|
69
115
|
if (loader !== undefined) {
|
|
70
|
-
return defineRoot({ tagline: TAGLINE, globals: GLOBALS, subtrees: [await loader()] });
|
|
116
|
+
return defineRoot({ tagline: TAGLINE, globals: GLOBALS, subtrees: composeCoreHooks([await loader()], hookRegistryLoader()) });
|
|
71
117
|
}
|
|
72
118
|
return buildRoot();
|
|
73
119
|
}
|
|
@@ -88,7 +134,7 @@ export async function resolveRoot(first) {
|
|
|
88
134
|
* HTTP-transport plugin manifest replacement takes effect on the next invocation
|
|
89
135
|
* automatically. Composition performs no network I/O. */
|
|
90
136
|
export async function buildRoot() {
|
|
91
|
-
const core = await
|
|
137
|
+
const core = await loadCoreSubtrees();
|
|
92
138
|
// Dynamic import keeps external command discovery/compose (plugin +
|
|
93
139
|
// HTTP-transport plugin, and their resolver/manifest/HTTP deps) OFF the hot leaf
|
|
94
140
|
// path's module graph — they load only here, on the fallthrough, matching
|
|
@@ -102,5 +148,6 @@ export async function buildRoot() {
|
|
|
102
148
|
// sys doctor). Reads only stored manifests — zero network.
|
|
103
149
|
const snapshot = buildExternalCommandSnapshot(new Set(SUBTREE_NAMES));
|
|
104
150
|
const external = composeExternalSubtrees(snapshot);
|
|
105
|
-
|
|
151
|
+
// Only core leaves are hookable, so external contributions compose unwrapped.
|
|
152
|
+
return defineRoot({ tagline: TAGLINE, globals: GLOBALS, subtrees: [...composeCoreHooks(core, hookRegistryLoader()), ...external] });
|
|
106
153
|
}
|
|
@@ -63,6 +63,8 @@ Every kind has both a `base` and an `orchestrator` persona; mode picks which one
|
|
|
63
63
|
|
|
64
64
|
A profile is a stable **agent identity**: a fixed id, a display name, its own memory store, and a **purview** of project directories it resolves memory and config from. Select it at spawn with `crtr node new --profile <id-or-name>`; omit and a child inherits the caller's profile. Pin a directory's default profile with `crtr profile default` so the startup chooser stops asking. Manage the identity and purview with `crtr profile new/show/project/rename/delete` (see `crtr profile -h`).
|
|
65
65
|
|
|
66
|
+
Each project in the purview carries its own `memory` value, capping how much of that project's stores reach a node's automatic boot and workspace-open context from any directory that node works in. Give `content` to the repos whose front doors the profile's nodes should operate inside, `preview` or `name` to a project the profile must know exists but rarely enters, and `none` to purview held for config, plugins, and deliberate reads alone. It is a delivery dial, not an access one: `crtr memory read` and file- and command-routed docs still see those stores whole.
|
|
67
|
+
|
|
66
68
|
Reach for a **new** profile when a distinct body of work has its own set of directories and its own conventions worth a dedicated store — not for every repo. The **profile memory scope** is exactly where cross-repo conventions and your stance toward that body of work belong (see the memory tiers below); a profile spanning several related dirs lets one doc reach every node working anywhere in that bundle.
|
|
67
69
|
|
|
68
70
|
## Memory tiers — where a doc lives decides who sees it
|
|
@@ -71,6 +73,6 @@ A memory doc's **scope** is a reach dial: the wider the scope, the more agents p
|
|
|
71
73
|
|
|
72
74
|
- **node** (`nodes/<id>/context/memory/`) — only *this* running node sees it; rides its boot context and dies with the node. Scratch memory for one goal's cross-refresh state.
|
|
73
75
|
- **profile** (the profile's own store) — every node running under that profile, across all the dirs in its purview. Cross-repo conventions and the user's stance toward that bundle of work.
|
|
74
|
-
- **project** (`<project>/.crouter/memory/`) — any agent operating in that one repo. Facts and procedures tied to that codebase. Resolves to the nearest ancestor `.crouter/` walking up from cwd; `--dir` pins an exact repo.
|
|
76
|
+
- **project** (`<project>/.crouter/memory/`) — any agent operating in that one repo. Facts and procedures tied to that codebase. Resolves to the nearest ancestor `.crouter/` walking up from cwd, plus every project in the selected profile's purview; `--dir` pins an exact repo.
|
|
75
77
|
- **user** (`~/.crouter/memory/`) — person-wide facts and preferences that follow the user everywhere, regardless of repo or profile.
|
|
76
78
|
- **builtin** (`src/builtin-memory/` in the crouter repo) — ships inside crtr, so *every crtr user on every host* carries it. This tier is the runtime's own self-documentation (this doc lives here); a change here is a change to the product. Author here only for guidance every crouter user needs, never for anything person- or repo-specific.
|
|
@@ -47,6 +47,10 @@ Every delivery dedups per transcript keyed on (doc, rung), higher rungs piercing
|
|
|
47
47
|
|
|
48
48
|
At boot/first-message assembly the runtime mounts: builtin docs, the user store (`~/.crouter/memory/`), the selected profile's store, and every project store — ancestor `.crouter/memory/` dirs walking up from cwd plus each project in the profile's purview. Physical duplicates are deduplicated; name collisions resolve nearest-first (project over profile over user over builtin), which is what lets a project doc shadow a builtin one.
|
|
49
49
|
|
|
50
|
+
Each project the selected profile has a relationship with carries a `memory` value — `none`, `name`, `preview`, or `content` — and that value is the maximum rung anything in that project's stores delivers at boot and workspace-open, whatever directory the node is working in. It only lowers: an entry authored below the maximum delivers at its authored rung. `none` contributes nothing to either automatic event, so a `none` project cannot shadow a same-named doc from a wider scope — that wider doc becomes the winner. A project store the selected profile has no relationship with delivers exactly what it authored.
|
|
51
|
+
|
|
52
|
+
The maximum reaches those two events and nothing else. Read, memory-read, and command entries fire at their authored rungs; directory listings, `crtr memory read`, `crtr memory find`, config resolution, and plugin discovery all see the full corpus. An explicit `crtr memory read` is itself a content delivery, so reading a capped doc returns its whole body and records content — the upgrade path for a doc the automatic events disclosed only by name or preview.
|
|
53
|
+
|
|
50
54
|
A workspace's front door is an ordinary doc carrying the entry pair `{on: workspace-open, at: content}` + `{on: read, match: "./**", at: content}` — the operating guide loads when that workspace mounts or its files are read, not in every boot catalog. `crtr memory lint` requires exactly one workspace-open content doc per profile-managed project store. Multiple mounted roots render broad-to-specific.
|
|
51
55
|
|
|
52
56
|
A `.crouter/memory/` store nested BELOW a mounted root (a package or subsystem dir) is delivered by the read path, not by boot: reading any file beneath its owning dir surfaces its read-routed docs, deduplicated against everything already in the transcript. Addressability is separate — the `crtr memory` leaves (list/read/find/lint/delete/origin) discover nested stores through a bounded walk (git-aware, depth- and time-capped), while the boot catalog stays ancestor+profile only, which is why a nested doc's boot entries are inert (lint warns; drop them).
|
|
@@ -4,7 +4,7 @@ when-and-why-to-read: When shaping a planning roadmap, deciding plan structure,
|
|
|
4
4
|
short-form: Use when shaping a planning roadmap, deciding plan structure, or preparing a consequential plan for implementation.
|
|
5
5
|
gate: {kind: plan}
|
|
6
6
|
rationale: >-
|
|
7
|
-
The original playbook required five parallel plan reviewers before every consequential implementation and made “passes all five lenses” the ready bar. Combined with the plan persona's re-review loop, this turned lenses into agents and resolution into reviewer polling rather than plan-owner judgment.
|
|
7
|
+
The original playbook required five parallel plan reviewers before every consequential implementation and made “passes all five lenses” the ready bar. Combined with the plan persona's re-review loop, this turned lenses into agents and resolution into reviewer polling rather than plan-owner judgment. Planners also turned plausible improvements outside the specification into implementation tasks without asking, silently expanding scope.
|
|
8
8
|
surfaces:
|
|
9
9
|
- on: boot
|
|
10
10
|
at: preview
|
|
@@ -12,6 +12,12 @@ surfaces:
|
|
|
12
12
|
|
|
13
13
|
# Planning Playbook
|
|
14
14
|
|
|
15
|
+
## Hold the specified scope
|
|
16
|
+
|
|
17
|
+
Plan the simplest complete implementation of the specification and what it necessarily requires. Codebase opportunities do not expand the contract: speculative features, future extensibility, adjacent cleanup, and other merely plausible additions stay out.
|
|
18
|
+
|
|
19
|
+
When something seems likely desirable but is not explicitly or implicitly required by the specification, ask the user whether to include it through `crtr human` before finishing the plan (`crtr human send -h`), wait for their answer, and make the resulting boundary explicit. Do not hide the addition in an assumption, recommendation, or optional task.
|
|
20
|
+
|
|
15
21
|
## Plan Shapes and the Decomposition Decision
|
|
16
22
|
|
|
17
23
|
Every planning effort produces either a flat plan or a decomposed plan (index + part-plans). Choose decomposition for worthwhile parallel planning, not raw size: a flat plan can span many yields, while part-plans add delegation and synthesis cost that independent slices must repay.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
kind: knowledge
|
|
3
3
|
when-and-why-to-read: When eliciting or writing a specification, this knowledge should be read because downstream design and planning need settled intent without making the user answer avoidable questions or forcing every request through the same ceremony.
|
|
4
4
|
short-form: Elicit only consequential uncertainty, then write a right-sized behavioral contract a downstream reader can use without guessing.
|
|
5
|
-
rationale: Specification quality and elicitation guidance lived inside an always-loaded spec persona while the lightweight /spec command had almost none, leaving agents to choose between a vague one-shot and a fixed discovery workflow.
|
|
5
|
+
rationale: Specification quality and elicitation guidance lived inside an always-loaded spec persona while the lightweight /spec command had almost none, leaving agents to choose between a vague one-shot and a fixed discovery workflow. Spec writers also promoted plausible nice-to-haves into requirements without asking, silently expanding the requested work.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Writing a specification
|
|
@@ -17,6 +17,12 @@ Reflect a concrete interpretation of the request so the user can confirm or corr
|
|
|
17
17
|
|
|
18
18
|
Spend attention where judgment is load-bearing, not where detail is merely available. Keep settled points moving and fold each answer into the specification as current truth. Stop eliciting when another answer would not materially change the behavioral contract. Explicit approval is warranted when the user is co-authoring the document or the remaining decision is consequential; ordinary reversible work does not need a ritual approval loop.
|
|
19
19
|
|
|
20
|
+
## Hold the requested scope
|
|
21
|
+
|
|
22
|
+
Specify the smallest complete outcome the request and its necessary implications support. Discovered possibilities do not become requirements: speculative features, future extensibility, adjacent cleanup, and other merely plausible additions stay out.
|
|
23
|
+
|
|
24
|
+
When something seems likely desirable but is not explicitly or implicitly required by the request, ask the user whether to include it through `crtr human` before finishing the specification (`crtr human send -h`), wait for their answer, and make the resulting boundary explicit. Do not hide the addition in an assumption, recommendation, or optional requirement.
|
|
25
|
+
|
|
20
26
|
## The finished specification
|
|
21
27
|
|
|
22
28
|
A downstream reader should be able to understand the required outcome and produce a design or plan without inventing intent. Include the dimensions that matter for this request rather than forcing a section template:
|
|
@@ -164,7 +164,7 @@ function selectedProfileProjects(): string[] {
|
|
|
164
164
|
const profileId = process.env["CRTR_PROFILE_ID"] ?? "";
|
|
165
165
|
if (profileId === "") return [];
|
|
166
166
|
try {
|
|
167
|
-
return loadProfileManifest(profileId).manifest.projects;
|
|
167
|
+
return loadProfileManifest(profileId).manifest.projects.map((project) => project.path);
|
|
168
168
|
} catch {
|
|
169
169
|
// A deleted or invalid selected profile must not hide cwd-local commands.
|
|
170
170
|
return [];
|
|
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
|
|
|
4
4
|
import { readFileSync, existsSync, realpathSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
+
import type { ChatCommandMetadata } from "../../../../core/runtime/command-surface.js";
|
|
7
8
|
|
|
8
9
|
const exec = promisify(execFile);
|
|
9
10
|
|
|
@@ -149,6 +150,11 @@ export default async function (pi: ExtensionAPI) {
|
|
|
149
150
|
const invocation = `Run \`crtr ${path.join(" ")} -h\` (pass args to run the real command)`;
|
|
150
151
|
pi.registerCommand(name, {
|
|
151
152
|
description: node.description ? `${node.description} — ${invocation}` : invocation,
|
|
153
|
+
// Chat-capable: the handler's outcome is the command's output placed into
|
|
154
|
+
// the conversation as a displayed message. The transient status line is
|
|
155
|
+
// decoration a surface without one simply does not show.
|
|
156
|
+
gateway: true as const,
|
|
157
|
+
argumentHint: "[args]",
|
|
152
158
|
handler: async (args, ctx) => {
|
|
153
159
|
const extra = (args ?? "").trim();
|
|
154
160
|
// No args -> show help. With args -> run the real command.
|
|
@@ -165,7 +171,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
165
171
|
{ triggerTurn: TRIGGER_TURN },
|
|
166
172
|
);
|
|
167
173
|
},
|
|
168
|
-
});
|
|
174
|
+
} as Parameters<ExtensionAPI["registerCommand"]>[1] & ChatCommandMetadata);
|
|
169
175
|
};
|
|
170
176
|
|
|
171
177
|
const filters = loadFilters();
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type DeterministicCommandExpansion,
|
|
7
7
|
} from "../../../core/runtime/command-expansion.js";
|
|
8
8
|
import { expandShellBlocks, hasShellBlocks, DEFAULT_SHELL_TIMEOUT_MS } from "../../../core/runtime/shell-expansion.js";
|
|
9
|
+
import type { ChatCommandMetadata } from "../../../core/runtime/command-surface.js";
|
|
9
10
|
import { piShellRunner } from "./pi-shell-runner.js";
|
|
10
11
|
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
@@ -126,7 +127,11 @@ export default async function (pi: ExtensionAPI) {
|
|
|
126
127
|
// drift from submission. A failed/dynamic command has no expansion metadata.
|
|
127
128
|
pi.registerCommand(doc.commandName, {
|
|
128
129
|
description: doc.description,
|
|
129
|
-
|
|
130
|
+
// Chat-capable: the handler's whole outcome is a conversation turn
|
|
131
|
+
// carrying the expanded document, which any surface hosting the
|
|
132
|
+
// conversation receives. Only when the expansion loaded — without it the
|
|
133
|
+
// sole outcome is a terminal-only error notify, so it stays undisclosed.
|
|
134
|
+
...(expansion === undefined ? {} : { expansion, gateway: true as const }),
|
|
130
135
|
handler: async (args, ctx) => {
|
|
131
136
|
if (expansion === undefined) {
|
|
132
137
|
ctx.ui.notify(
|
|
@@ -157,6 +162,6 @@ export default async function (pi: ExtensionAPI) {
|
|
|
157
162
|
{ triggerTurn: true },
|
|
158
163
|
);
|
|
159
164
|
},
|
|
160
|
-
} as Parameters<ExtensionAPI["registerCommand"]>[1] & { expansion?: DeterministicCommandExpansion });
|
|
165
|
+
} as Parameters<ExtensionAPI["registerCommand"]>[1] & { expansion?: DeterministicCommandExpansion } & ChatCommandMetadata);
|
|
161
166
|
}
|
|
162
167
|
}
|
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import { findRefCompletionContext, RefAwareAutocompleteProvider } from '../input/ref-autocomplete.js';
|
|
4
4
|
function ref(name, shortForm = `${name} docs`) {
|
|
5
|
-
return { name, kind: 'knowledge', scope: 'user', shortForm };
|
|
5
|
+
return { name, kind: 'knowledge', scope: 'user', shortForm, gatewayVisible: true };
|
|
6
6
|
}
|
|
7
7
|
/** The wrapped provider; ref handling must never reach it in a ref context. */
|
|
8
8
|
const inertDelegate = {
|
|
@@ -47,7 +47,7 @@ test('Enter-confirming a non-leading ref completion accepts the token and return
|
|
|
47
47
|
editor.onSubmit = (text) => {
|
|
48
48
|
submitted = text;
|
|
49
49
|
};
|
|
50
|
-
const refs = [{ name: 'dev', kind: 'knowledge', scope: 'user', shortForm: 'dev docs' }];
|
|
50
|
+
const refs = [{ name: 'dev', kind: 'knowledge', scope: 'user', shortForm: 'dev docs', gatewayVisible: true }];
|
|
51
51
|
const delegate = {
|
|
52
52
|
async getSuggestions() {
|
|
53
53
|
return null;
|
|
@@ -2,7 +2,7 @@ import { homedir } from 'node:os';
|
|
|
2
2
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
3
|
import { Input, SelectList, getKeybindings, } from '@earendil-works/pi-tui';
|
|
4
4
|
import { getSelectListTheme } from '@earendil-works/pi-coding-agent';
|
|
5
|
-
import {
|
|
5
|
+
import { openReviewWindow } from '../../inbox/review/launch.js';
|
|
6
6
|
import { createReviewClient, ReviewTerminalError, terminalRefusalNotice } from '../../inbox/review/review-client.js';
|
|
7
7
|
import { checkReviewableFile, extractFilePaths } from '#core/human/visible-paths';
|
|
8
8
|
const MAX_VISIBLE_ROWS = 12;
|
|
@@ -123,6 +123,6 @@ export async function launchFileReview(host, file, opts) {
|
|
|
123
123
|
opts.onNotice(err instanceof ReviewTerminalError ? terminalRefusalNotice(err) : err instanceof Error ? err.message : String(err));
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
openReviewWindow(host, review);
|
|
127
127
|
}
|
|
128
128
|
export { extractFilePaths };
|
|
@@ -31,7 +31,7 @@ export interface KeyHooks {
|
|
|
31
31
|
toggleInboxStrip: () => void;
|
|
32
32
|
cycleModelLadder: (direction: 'forward' | 'backward') => void;
|
|
33
33
|
inspectLoadedCommand: () => void;
|
|
34
|
-
/** The file-review chord —
|
|
34
|
+
/** The file-review chord — pick a transcript file and open its review window. */
|
|
35
35
|
openFileReview: () => void;
|
|
36
36
|
/** Search the current node's local profile project files. */
|
|
37
37
|
openProfileFiles: () => void;
|
|
@@ -50,7 +50,7 @@ function profileSearchRoots(profileId) {
|
|
|
50
50
|
return { projects: [], memory: null };
|
|
51
51
|
try {
|
|
52
52
|
const { profileId: resolvedId, manifest } = loadProfileManifest(profileId);
|
|
53
|
-
return { projects: manifest.projects, memory: profileMemoryDir(resolvedId) };
|
|
53
|
+
return { projects: manifest.projects.map((project) => project.path), memory: profileMemoryDir(resolvedId) };
|
|
54
54
|
}
|
|
55
55
|
catch {
|
|
56
56
|
return { projects: [], memory: null };
|