@north-light/crouter 0.3.207 → 0.3.209
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 +3 -1
- package/dist/api/client.js +4 -0
- package/dist/api/dto/broker-ops.d.ts +2 -12
- package/dist/api/dto/nodes.d.ts +16 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -2
- package/dist/builtin-memory/01-spine/00-has-manager.md +3 -2
- package/dist/builtin-memory/01-spine/01-no-manager.md +3 -2
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -2
- package/dist/builtin-memory/02-lifecycle/01-resident.md +3 -2
- package/dist/builtin-memory/04-base-worker.md +3 -2
- package/dist/builtin-memory/04-orchestration-kernel.md +3 -2
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/developer/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/explore/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +3 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +3 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -2
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -2
- package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -2
- package/dist/builtin-memory/advisor/council.md +0 -2
- package/dist/builtin-memory/design.md +3 -2
- package/dist/builtin-memory/development.md +3 -2
- package/dist/builtin-memory/insights/capture.md +1 -3
- package/dist/builtin-memory/insights/init.md +1 -3
- package/dist/builtin-memory/insights/listen.md +3 -2
- package/dist/builtin-memory/internal/INDEX.md +5 -4
- package/dist/builtin-memory/internal/agent-shaping.md +5 -4
- package/dist/builtin-memory/internal/examples/INDEX.md +3 -2
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -2
- package/dist/builtin-memory/internal/marketplaces.md +3 -2
- package/dist/builtin-memory/internal/memory-loading.md +31 -20
- package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -2
- package/dist/builtin-memory/internal/plugins.md +7 -6
- package/dist/builtin-memory/internal/storage-tiers.md +3 -2
- package/dist/builtin-memory/plan/roadmap.md +3 -2
- package/dist/builtin-memory/spec/guide.md +0 -2
- package/dist/builtin-memory/spec/requirements.md +0 -2
- package/dist/builtin-memory/spec/roadmap.md +3 -2
- package/dist/builtin-memory/testing.md +1 -3
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +3 -2
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +1 -4
- package/dist/clients/attach/__tests__/group-activity.test.js +8 -1
- package/dist/clients/attach/render/card-presentation.d.ts +31 -0
- package/dist/clients/attach/render/card-presentation.js +163 -0
- package/dist/clients/attach/render/chat-view.d.ts +0 -2
- package/dist/clients/attach/render/chat-view.js +12 -23
- package/dist/clients/attach/render/context-message.d.ts +5 -9
- package/dist/clients/attach/render/context-message.js +20 -43
- package/dist/clients/attach/render/group-activity.d.ts +28 -4
- package/dist/clients/attach/render/group-activity.js +65 -11
- package/dist/clients/attach/render/group-recap.d.ts +3 -0
- package/dist/clients/attach/render/group-recap.js +17 -0
- package/dist/clients/attach/viewer.js +516 -516
- package/dist/commands/memory/delete.js +3 -18
- package/dist/commands/memory/edit.js +23 -30
- package/dist/commands/memory/history.js +1 -1
- package/dist/commands/memory/lint.d.ts +7 -8
- package/dist/commands/memory/lint.js +178 -132
- package/dist/commands/memory/list.d.ts +0 -1
- package/dist/commands/memory/list.js +2 -12
- package/dist/commands/memory/move.d.ts +1 -0
- package/dist/commands/memory/move.js +195 -0
- package/dist/commands/memory/read.js +135 -141
- package/dist/commands/memory/shared.d.ts +18 -17
- package/dist/commands/memory/shared.js +93 -39
- package/dist/commands/memory/write.js +23 -33
- package/dist/commands/memory.js +5 -4
- package/dist/commands/pkg/browse/catalog.js +2 -4
- package/dist/commands/pkg/browse/doc-view.js +17 -11
- package/dist/commands/pkg/browse/model.d.ts +7 -9
- package/dist/commands/sys/migrate.d.ts +1 -0
- package/dist/commands/sys/migrate.js +106 -0
- package/dist/commands/sys/sync-deps.js +5 -10
- package/dist/commands/sys/sync-project-guidance.js +36 -16
- package/dist/commands/sys/sync-skills.js +8 -4
- package/dist/commands/sys.js +3 -2
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +1 -1
- package/dist/core/__tests__/helpers/broker-clients.d.ts +3 -1
- package/dist/core/__tests__/helpers/broker-clients.js +7 -2
- package/dist/core/__tests__/inline-memory-refs.test.js +8 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +5 -4
- package/dist/core/__tests__/nested-store-discovery.test.js +1 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +3 -3
- package/dist/core/__tests__/on-read-dedup-resume.test.js +12 -12
- package/dist/core/__tests__/on-read-nested-store.test.js +23 -20
- package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +9 -0
- package/dist/core/__tests__/serial/broker-snapshot-history.test.js +52 -1
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +1 -1
- package/dist/core/canvas/db.d.ts +3 -1
- package/dist/core/canvas/db.js +12 -2
- package/dist/core/memory/history.d.ts +4 -1
- package/dist/core/memory/history.js +1 -0
- package/dist/core/memory/inline-ref-inventory.d.ts +5 -4
- package/dist/core/memory/inline-ref-inventory.js +23 -19
- package/dist/core/memory-resolver.d.ts +4 -4
- package/dist/core/memory-resolver.js +16 -43
- package/dist/core/runtime/bearings.d.ts +4 -3
- package/dist/core/runtime/bearings.js +4 -4
- package/dist/core/runtime/broker/fault-retry.js +2 -7
- package/dist/core/runtime/broker/frame-dispatch.js +1 -1
- package/dist/core/runtime/broker/tool-groups.js +7 -6
- package/dist/core/runtime/broker-extension-render.d.ts +3 -2
- package/dist/core/runtime/broker-extension-render.js +3 -3
- package/dist/core/runtime/broker.js +4 -4
- package/dist/core/runtime/kickoff.js +4 -10
- package/dist/core/runtime/memory.js +2 -3
- package/dist/core/runtime/node-read.js +7 -1
- package/dist/core/runtime/stop-guard.js +3 -3
- package/dist/core/substrate/index.d.ts +7 -4
- package/dist/core/substrate/index.js +6 -4
- package/dist/core/substrate/injected-store.d.ts +24 -12
- package/dist/core/substrate/injected-store.js +80 -33
- package/dist/core/substrate/listings.d.ts +21 -0
- package/dist/core/substrate/listings.js +88 -0
- package/dist/core/substrate/on-read-node.d.ts +5 -5
- package/dist/core/substrate/on-read-node.js +4 -5
- package/dist/core/substrate/on-read.d.ts +25 -4
- package/dist/core/substrate/on-read.js +81 -102
- package/dist/core/substrate/render-node.d.ts +5 -2
- package/dist/core/substrate/render-node.js +5 -3
- package/dist/core/substrate/render.d.ts +9 -8
- package/dist/core/substrate/render.js +104 -96
- package/dist/core/substrate/schema.d.ts +34 -18
- package/dist/core/substrate/schema.js +75 -32
- package/dist/core/substrate/surface-match.d.ts +32 -0
- package/dist/core/substrate/surface-match.js +179 -0
- package/dist/daemon/api/handlers/nodes.js +9 -0
- package/dist/daemon/review/comment-notify.js +1 -8
- package/dist/daemon/review/deliver.js +1 -1
- package/dist/daemon/review/finish.js +1 -1
- package/dist/migrations/001-surfaces-frontmatter.d.ts +2 -0
- package/dist/migrations/001-surfaces-frontmatter.js +276 -0
- package/dist/migrations/convergent.d.ts +31 -0
- package/dist/migrations/convergent.js +71 -0
- package/dist/migrations/registry.d.ts +2 -0
- package/dist/migrations/registry.js +19 -0
- package/dist/migrations/types.d.ts +40 -0
- package/dist/migrations/types.js +11 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +1 -1
- package/dist/pi-extensions/canvas-context-intro.d.ts +2 -1
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +2 -1
- package/dist/pi-extensions/canvas-doc-substrate.js +57 -34
- package/dist/pi-extensions/canvas-review-boundary.js +1 -1
- package/dist/shared/__tests__/generated-context-grammar.test.js +19 -22
- package/dist/shared/generated-context.d.ts +0 -18
- package/dist/shared/generated-context.js +16 -120
- package/dist/shared/tool-groups.d.ts +1 -0
- package/dist/shared/tool-groups.js +7 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/core/substrate/ceiling.d.ts +0 -17
- package/dist/core/substrate/ceiling.js +0 -67
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// The ordered state-migration registry — ONE list for both lanes, so a cut
|
|
2
|
+
// that spans canvas-db and document stores ships as adjacent entries.
|
|
3
|
+
//
|
|
4
|
+
// Authoring contract (each migration is one file, `NNN-<desc>.ts`, appended
|
|
5
|
+
// here in order):
|
|
6
|
+
// • Idempotent. A convergent migration re-runs on every `crtr sys migrate`,
|
|
7
|
+
// so applying it to an already-migrated store must plan zero changes. A
|
|
8
|
+
// journaled migration runs once per database via user_version, but its
|
|
9
|
+
// body must still tolerate a crash-resume re-run of any filesystem work.
|
|
10
|
+
// • Journaled filesystem steps are crash-resumable: fs writes happen outside
|
|
11
|
+
// the db transaction, so a step interrupted mid-fs-work must complete
|
|
12
|
+
// (never corrupt) when the chain re-runs it.
|
|
13
|
+
// • Migration files import ONLY fs/frontmatter utilities (the fs-only
|
|
14
|
+
// memory resolver included) and these types — never canvas-db or canvas
|
|
15
|
+
// accessors. `DatabaseSync` arrives as a parameter; import it type-only.
|
|
16
|
+
// This keeps the registry legal in every CLI leaf's import graph (the
|
|
17
|
+
// build's V-1 gate).
|
|
18
|
+
import { surfacesFrontmatterMigration } from './001-surfaces-frontmatter.js';
|
|
19
|
+
export const STATE_MIGRATIONS = [surfacesFrontmatterMigration];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
export interface JournaledMigration {
|
|
3
|
+
lane: 'journaled';
|
|
4
|
+
description: string;
|
|
5
|
+
/** Runs inside the canvas-db migration transaction. Filesystem work under
|
|
6
|
+
* `canvasHome` is OUTSIDE that transaction — it must be crash-resumable
|
|
7
|
+
* (a re-run after a partial application completes rather than corrupts). */
|
|
8
|
+
apply(db: DatabaseSync, canvasHome: string): void;
|
|
9
|
+
}
|
|
10
|
+
export interface ConvergentMigration {
|
|
11
|
+
lane: 'convergent';
|
|
12
|
+
description: string;
|
|
13
|
+
/** Pure planning: return the full new text of each doc to rewrite. The
|
|
14
|
+
* runner filters no-op changes and performs the writes. */
|
|
15
|
+
apply(store: StoreSnapshot): DocChange[];
|
|
16
|
+
}
|
|
17
|
+
export type StateMigration = JournaledMigration | ConvergentMigration;
|
|
18
|
+
/** One parsed markdown doc in a store snapshot. */
|
|
19
|
+
export interface StoreDoc {
|
|
20
|
+
/** Absolute file path. */
|
|
21
|
+
path: string;
|
|
22
|
+
/** Path relative to the store root, `/`-separated. */
|
|
23
|
+
relPath: string;
|
|
24
|
+
/** Full file text as read from disk. */
|
|
25
|
+
source: string;
|
|
26
|
+
/** Parsed frontmatter record (null when the file has no frontmatter block). */
|
|
27
|
+
frontmatter: Record<string, unknown> | null;
|
|
28
|
+
body: string;
|
|
29
|
+
}
|
|
30
|
+
/** Every parseable `.md` doc under one memory-store root. */
|
|
31
|
+
export interface StoreSnapshot {
|
|
32
|
+
root: string;
|
|
33
|
+
docs: StoreDoc[];
|
|
34
|
+
}
|
|
35
|
+
/** One planned rewrite: the full new file text for `path`. */
|
|
36
|
+
export interface DocChange {
|
|
37
|
+
path: string;
|
|
38
|
+
/** Complete replacement text for the file. */
|
|
39
|
+
after: string;
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// State-migration shapes. Two lanes, split by which store owns the state:
|
|
2
|
+
//
|
|
3
|
+
// journaled — canvas-db state. Runs inside the db's ordered migration chain
|
|
4
|
+
// (core/canvas/db.ts appends these to MIGRATIONS), so each step
|
|
5
|
+
// gets the chain's BEGIN IMMEDIATE/COMMIT + user_version gating
|
|
6
|
+
// and runs exactly once per database.
|
|
7
|
+
// convergent — on-disk memory documents. No journal exists over a user's
|
|
8
|
+
// stores (files appear, sync in, get hand-edited), so these
|
|
9
|
+
// CONVERGE instead: every run re-scans and rewrites whatever
|
|
10
|
+
// still matches the old shape. `crtr sys migrate` is the runner.
|
|
11
|
+
export {};
|
|
@@ -409,7 +409,7 @@ test('stalled leaf (nothing live to await, no final) is still reprompted', async
|
|
|
409
409
|
let shutdown = false;
|
|
410
410
|
await settle(pi, stopEvent('I think I am basically done here'), { shutdown: () => { shutdown = true; } });
|
|
411
411
|
assert.equal(pi.injected.length, 1, 'the stall reprompt fired');
|
|
412
|
-
assert.equal(pi.injected[0].content, `<runtime kind="stop-guard"
|
|
412
|
+
assert.equal(pi.injected[0].content, `<runtime kind="stop-guard">${STALL_REPROMPT}</runtime>`, 'reprompt carries the stall nudge to push final / ask');
|
|
413
413
|
assert.equal(pi.injected[0].deliverAs, 'followUp', 'reprompt delivered as a followUp');
|
|
414
414
|
assert.equal(shutdown, false, 'a stalled leaf is NOT shut down — it is re-prompted to finish');
|
|
415
415
|
assert.notEqual(getNode('leaf')?.intent, 'idle-release', 'a stalled leaf does not idle-release');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CONTEXT_INTRO_CUSTOM_TYPE } from '../shared/generated-context.js';
|
|
2
|
+
import { type InjectedDocs } from '../core/substrate/injected-store.js';
|
|
2
3
|
/** The `customType` stamped on the injected session message. Used both to write
|
|
3
4
|
* the entry and to detect it on resume (the idempotency guard). Defined beside
|
|
4
5
|
* the block builder in `core/runtime/bearings.ts` because the warm-pool claim's
|
|
@@ -68,7 +69,7 @@ interface PiLike {
|
|
|
68
69
|
}
|
|
69
70
|
/** Build the full bearings baseline for a conversation without inherited
|
|
70
71
|
* crouter context. Exported for testing. */
|
|
71
|
-
export declare function buildContextIntro(nodeId: string, seen?:
|
|
72
|
+
export declare function buildContextIntro(nodeId: string, seen?: InjectedDocs): Promise<string>;
|
|
72
73
|
/** Build the compact node-scoped update for a fork whose copied branch already
|
|
73
74
|
* carries a full crouter baseline. Exported for testing. */
|
|
74
75
|
export declare function buildForkContextIntro(nodeId: string): Promise<string>;
|
|
@@ -17,7 +17,8 @@ interface ToolResultEventLike {
|
|
|
17
17
|
toolName: string;
|
|
18
18
|
isError?: boolean;
|
|
19
19
|
/** The tool's resolved input. For `read` it carries the file path under
|
|
20
|
-
* `path` (or the legacy `file_path`)
|
|
20
|
+
* `path` (or the legacy `file_path`); for `bash`, the command string under
|
|
21
|
+
* `command`. Typed loosely; narrowed at use. */
|
|
21
22
|
input?: unknown;
|
|
22
23
|
content: ContentBlockLike[];
|
|
23
24
|
}
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
// a capability. Falls back to appending when the anchor is absent. (The `<memory kind="knowledge">`
|
|
16
16
|
// catalog rides the bearings message instead — see core/substrate/render.ts.)
|
|
17
17
|
//
|
|
18
|
-
// 2. tool_result (gated on `read`) — the
|
|
19
|
-
// `read`, evaluates
|
|
20
|
-
// across the resolved corpus and any project stores
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
18
|
+
// 2. tool_result (gated on `read` and `bash`) — the EVENT half. For each
|
|
19
|
+
// non-error `read`, evaluates the read file against every doc's `read`
|
|
20
|
+
// surfaces entries across the resolved corpus and any project stores
|
|
21
|
+
// enclosing the read file; for each non-error `bash`, evaluates the
|
|
22
|
+
// executed command string against every doc's `command` entries. Matches
|
|
23
|
+
// render at their matched rung in `<auto-loaded-context>` before the
|
|
24
|
+
// tool output. A transcript-scoped (realpath → rung) map deduplicates
|
|
25
|
+
// repeated deliveries and resumed broker processes.
|
|
24
26
|
//
|
|
25
27
|
// Both handler bodies are wrapped in try/catch and degrade to INERT on error —
|
|
26
28
|
// a substrate bug must never brick node boot or break a `read`, only quietly
|
|
@@ -34,9 +36,9 @@ import { homedir } from 'node:os';
|
|
|
34
36
|
import { join, resolve } from 'node:path';
|
|
35
37
|
import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
|
|
36
38
|
import { renderPreferencesFromState } from '../core/runtime/broker-extension-render.js';
|
|
37
|
-
import { renderOnReadDocsForSubject } from '../core/substrate/on-read.js';
|
|
39
|
+
import { renderOnCommandDocsForSubject, renderOnReadDocsForSubject } from '../core/substrate/on-read.js';
|
|
38
40
|
import { clearSessionCache } from '../core/substrate/session-cache.js';
|
|
39
|
-
import { loadInjectedDocs, saveInjectedDocs, sharedInjectedDocs } from '../core/substrate/injected-store.js';
|
|
41
|
+
import { loadInjectedDocs, mergeInjectedDocs, saveInjectedDocs, sharedInjectedDocs } from '../core/substrate/injected-store.js';
|
|
40
42
|
import { autoLoadedContextInner, mergeAutoLoadedContext } from './envelope-merge.js';
|
|
41
43
|
// ---------------------------------------------------------------------------
|
|
42
44
|
// Extension
|
|
@@ -86,7 +88,13 @@ export function registerCanvasDocSubstrate(pi) {
|
|
|
86
88
|
pi.on('before_agent_start', async (event) => {
|
|
87
89
|
try {
|
|
88
90
|
const state = await brokerExtensionState(nodeId);
|
|
89
|
-
|
|
91
|
+
// Merge the on-disk set first so this save never clobbers an
|
|
92
|
+
// out-of-process delivery (the daemon's warm-spare bearings push), then
|
|
93
|
+
// seed each rendered preference at its boot rung. Boot renders every
|
|
94
|
+
// agent run and never skips; re-seeding at the same rung is idempotent.
|
|
95
|
+
mergeInjectedDocs(injectedDocs, loadInjectedDocs(nodeId));
|
|
96
|
+
const block = renderPreferencesFromState(state, injectedDocs);
|
|
97
|
+
saveInjectedDocs(nodeId, injectedDocs);
|
|
90
98
|
if (block === '')
|
|
91
99
|
return; // nothing eligible — leave the prompt untouched
|
|
92
100
|
const idx = event.systemPrompt.indexOf(TOOLS_ANCHOR);
|
|
@@ -101,43 +109,58 @@ export function registerCanvasDocSubstrate(pi) {
|
|
|
101
109
|
return; // inert on error — never brick node boot
|
|
102
110
|
}
|
|
103
111
|
});
|
|
104
|
-
// 2.
|
|
112
|
+
// 2. EVENT half — augment a non-error `read` with docs matched by the read
|
|
113
|
+
// file, and a non-error `bash` with docs matched by the command string.
|
|
105
114
|
pi.on('tool_result', async (event, ctx) => {
|
|
106
115
|
try {
|
|
107
|
-
if (event.
|
|
116
|
+
if (event.isError === true)
|
|
108
117
|
return;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
let injected;
|
|
119
|
+
if (event.toolName === 'read') {
|
|
120
|
+
const input = event.input;
|
|
121
|
+
const rawPath = input?.path ?? input?.file_path;
|
|
122
|
+
if (rawPath === undefined || rawPath === '')
|
|
123
|
+
return;
|
|
124
|
+
// Resolve to an absolute path (renderOnReadDocsForSubject realpaths it).
|
|
125
|
+
// pi's read tool expands a leading `~`, but event.input keeps the raw
|
|
126
|
+
// form, so expand it ourselves before resolving against the session cwd.
|
|
127
|
+
const cwd = typeof ctx?.cwd === 'string' && ctx.cwd !== '' ? ctx.cwd : process.cwd();
|
|
128
|
+
const expanded = rawPath.startsWith('~') ? join(homedir(), rawPath.slice(1)) : rawPath;
|
|
129
|
+
const absFile = resolve(cwd, expanded);
|
|
130
|
+
const state = await brokerExtensionState(nodeId);
|
|
131
|
+
// Merge the on-disk set: an out-of-process delivery (the daemon's
|
|
132
|
+
// warm-spare claim pushes bearings — including workspace-open docs —
|
|
133
|
+
// into the live session and persists what it delivered) must dedup here
|
|
134
|
+
// too. Cheap: one tiny JSON read per tool result.
|
|
135
|
+
mergeInjectedDocs(injectedDocs, loadInjectedDocs(nodeId));
|
|
136
|
+
injected = renderOnReadDocsForSubject(state.subject, absFile, injectedDocs);
|
|
137
|
+
}
|
|
138
|
+
else if (event.toolName === 'bash') {
|
|
139
|
+
const input = event.input;
|
|
140
|
+
const command = input?.command;
|
|
141
|
+
if (typeof command !== 'string' || command.trim() === '')
|
|
142
|
+
return;
|
|
143
|
+
const state = await brokerExtensionState(nodeId);
|
|
144
|
+
mergeInjectedDocs(injectedDocs, loadInjectedDocs(nodeId));
|
|
145
|
+
injected = renderOnCommandDocsForSubject(state.subject, command, injectedDocs);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
112
148
|
return;
|
|
113
|
-
|
|
114
|
-
// pi's read tool expands a leading `~`, but event.input keeps the raw
|
|
115
|
-
// form, so expand it ourselves before resolving against the session cwd.
|
|
116
|
-
const cwd = typeof ctx?.cwd === 'string' && ctx.cwd !== '' ? ctx.cwd : process.cwd();
|
|
117
|
-
const expanded = rawPath.startsWith('~') ? join(homedir(), rawPath.slice(1)) : rawPath;
|
|
118
|
-
const absFile = resolve(cwd, expanded);
|
|
119
|
-
const state = await brokerExtensionState(nodeId);
|
|
120
|
-
// Merge the on-disk set: an out-of-process delivery (the daemon's
|
|
121
|
-
// warm-spare claim pushes bearings — including workspace-open docs —
|
|
122
|
-
// into the live session and persists what it delivered) must dedup here
|
|
123
|
-
// too. Cheap: one tiny JSON read per `read`.
|
|
124
|
-
for (const real of loadInjectedDocs(nodeId))
|
|
125
|
-
injectedDocs.add(real);
|
|
126
|
-
const injected = renderOnReadDocsForSubject(state.subject, absFile, injectedDocs);
|
|
149
|
+
}
|
|
127
150
|
if (injected === '')
|
|
128
|
-
return; // nothing surfaced — pass the
|
|
129
|
-
// A doc surfaced →
|
|
130
|
-
//
|
|
151
|
+
return; // nothing surfaced — pass the result through unchanged
|
|
152
|
+
// A doc surfaced → the render grew injectedDocs; persist the set so the
|
|
153
|
+
// dedup survives a later dormancy → revive(resume).
|
|
131
154
|
saveInjectedDocs(nodeId, injectedDocs);
|
|
132
|
-
// Fold into any existing auto-loaded-context block from sibling
|
|
133
|
-
// otherwise prepend one quiet block ahead of the
|
|
155
|
+
// Fold into any existing auto-loaded-context block from sibling hooks;
|
|
156
|
+
// otherwise prepend one quiet block ahead of the tool output.
|
|
134
157
|
const inner = autoLoadedContextInner(injected);
|
|
135
158
|
if (inner === null)
|
|
136
159
|
return;
|
|
137
160
|
return { content: mergeAutoLoadedContext([inner], event.content) };
|
|
138
161
|
}
|
|
139
162
|
catch {
|
|
140
|
-
return; // inert on error — a bug must degrade a
|
|
163
|
+
return; // inert on error — a bug must degrade a tool result, never break it
|
|
141
164
|
}
|
|
142
165
|
});
|
|
143
166
|
}
|
|
@@ -82,7 +82,7 @@ export function registerCanvasReviewBoundary(pi) {
|
|
|
82
82
|
].join('\n');
|
|
83
83
|
pi.sendMessage({
|
|
84
84
|
customType: REVIEW_BOUNDARY_CUSTOM_TYPE,
|
|
85
|
-
content: formatCard('review-boundary', {
|
|
85
|
+
content: formatCard('review-boundary', {}, content),
|
|
86
86
|
display: true,
|
|
87
87
|
details: { reviewId, originNodeId, targetFile, kind: binding.kind ?? 'review' },
|
|
88
88
|
});
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
|
-
import {
|
|
4
|
-
test('
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
3
|
+
import { formatCard, formatDataCard, formatInboxCard, parseCard } from '../generated-context.js';
|
|
4
|
+
test('runtime cards preserve kind, facts, and their body encoding', () => {
|
|
5
|
+
const facts = { source: 'grammar', enabled: true, count: 2 };
|
|
6
|
+
const bare = parseCard({ role: 'user', content: formatCard('revive', facts, '<model-visible/>') });
|
|
7
|
+
assert.ok(bare);
|
|
8
|
+
assert.equal(bare.kind, 'revive');
|
|
9
|
+
assert.equal(bare.body, '<model-visible/>');
|
|
10
|
+
assert.deepEqual(bare.facts, { source: 'grammar', enabled: 'true', count: '2' });
|
|
11
|
+
const namespaced = parseCard({ role: 'user', content: formatDataCard('northlight:surface', facts, '<data body>') });
|
|
12
|
+
assert.ok(namespaced);
|
|
13
|
+
assert.equal(namespaced.kind, 'northlight:surface');
|
|
14
|
+
assert.equal(namespaced.body, '<data body>');
|
|
15
|
+
assert.deepEqual(namespaced.facts, { source: 'grammar', enabled: 'true', count: '2' });
|
|
12
16
|
});
|
|
13
17
|
test('inbox entries retain their typed disposition and escaped sender name', () => {
|
|
14
18
|
const name = 'designer [cards] & "notes"\nnext line';
|
|
@@ -58,21 +62,14 @@ test('an envelope wins over customType, with a custom-role fallback for old sess
|
|
|
58
62
|
assert.ok(preEnvelope);
|
|
59
63
|
assert.equal(preEnvelope.kind, 'bearings');
|
|
60
64
|
});
|
|
61
|
-
test('
|
|
62
|
-
const
|
|
65
|
+
test('parsed cards expose only the shared semantic grammar', () => {
|
|
66
|
+
const card = parseCard({
|
|
63
67
|
role: 'user',
|
|
64
|
-
content: formatCard('review-comment', { review_id: 'review-1', comment_id: 'comment-1'
|
|
68
|
+
content: formatCard('review-comment', { review_id: 'review-1', comment_id: 'comment-1' }, 'Review comment created.'),
|
|
65
69
|
});
|
|
66
|
-
assert.ok(
|
|
67
|
-
assert.
|
|
68
|
-
assert.
|
|
69
|
-
assert.deepEqual(comment.facts, { review_id: 'review-1', comment_id: 'comment-1', verb: 'create' });
|
|
70
|
-
const queued = parseCard({
|
|
71
|
-
role: 'user',
|
|
72
|
-
content: formatCard('review-queued', { review_id: 'review-1' }, 'Review submission is queued.'),
|
|
73
|
-
});
|
|
74
|
-
assert.ok(queued);
|
|
75
|
-
assert.equal(queued.summary, 'review submitted; completion pending');
|
|
70
|
+
assert.ok(card);
|
|
71
|
+
assert.deepEqual(card.facts, { review_id: 'review-1', comment_id: 'comment-1' });
|
|
72
|
+
assert.deepEqual(Object.keys(card).sort(), ['body', 'facts', 'kind', 'senders']);
|
|
76
73
|
});
|
|
77
74
|
test('only the whole outer runtime element classifies a user message', () => {
|
|
78
75
|
const body = 'A nested <runtime kind="inbox">is model-visible body content</runtime> remains content.';
|
|
@@ -58,24 +58,7 @@ export interface GeneratedCard {
|
|
|
58
58
|
facts: Readonly<Record<string, string>>;
|
|
59
59
|
body: string;
|
|
60
60
|
senders: CardSender[];
|
|
61
|
-
label: string;
|
|
62
|
-
summary: string;
|
|
63
|
-
expandable: boolean;
|
|
64
61
|
}
|
|
65
|
-
type CardSummary = (facts: Readonly<Record<string, string>>) => string;
|
|
66
|
-
export interface CardKindDefinition {
|
|
67
|
-
label: string;
|
|
68
|
-
summary: CardSummary;
|
|
69
|
-
expandable: boolean;
|
|
70
|
-
customTypes?: Readonly<Record<string, Partial<Pick<CardKindDefinition, 'label' | 'summary' | 'expandable'>>>>;
|
|
71
|
-
}
|
|
72
|
-
/** The closed crouter vocabulary, plus custom-role aliases for the same cards. */
|
|
73
|
-
export declare const KIND_TABLE: Readonly<Record<string, CardKindDefinition>>;
|
|
74
|
-
/** Contribute presentation for namespaced kinds this process will render.
|
|
75
|
-
* Idempotent and last-write-wins, so a re-imported module cannot fail a boot.
|
|
76
|
-
* A process that never registers is not degraded: an unregistered kind still
|
|
77
|
-
* parses and only falls back to the generic label and summary. */
|
|
78
|
-
export declare function registerCardKinds(kinds: Readonly<Record<string, CardKindDefinition>>): void;
|
|
79
62
|
/** Plain text from a generated message's string or text-block content. */
|
|
80
63
|
export declare function generatedContextText(message: GeneratedContextMessageLike): string;
|
|
81
64
|
/** Wrap a user-role runtime message in its whole-message card envelope. */
|
|
@@ -91,4 +74,3 @@ export declare function parseCard(message: GeneratedContextMessageLike): Generat
|
|
|
91
74
|
/** Format a complete inbox card from report bodies resolved by the caller. */
|
|
92
75
|
export declare function formatInboxCard(sections: readonly InboxCardSection[]): string;
|
|
93
76
|
export declare function isLegacyRuntimeText(text: string): boolean;
|
|
94
|
-
export {};
|
|
@@ -35,98 +35,13 @@ export function formatModelFallbackRecovery(previousModel, nextModel, reason) {
|
|
|
35
35
|
? `The previous model (${previousModel}) had no usable provider credential, so you were automatically switched to ${nextModel}. Continue the task from where the failed turn left off.`
|
|
36
36
|
: `The previous model (${previousModel}) was unavailable (provider 404 not_found), so you were automatically switched to ${nextModel}. Continue the task from where the failed turn left off.`;
|
|
37
37
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const updates = countFact(facts, 'updates');
|
|
46
|
-
const senders = countFact(facts, 'senders');
|
|
47
|
-
if (updates === null)
|
|
48
|
-
return 'inbox update';
|
|
49
|
-
if (senders === null || senders <= 1)
|
|
50
|
-
return `${updates === 1 ? 'message' : `${updates} messages`} received`;
|
|
51
|
-
return `${updates} messages received from ${senders} nodes`;
|
|
52
|
-
}
|
|
53
|
-
function recoverySummaryFromFacts(facts) {
|
|
54
|
-
switch (facts.reason) {
|
|
55
|
-
case 'model-fallback': return 'model route changed';
|
|
56
|
-
case 'connection': return 'network connection restored';
|
|
57
|
-
case 'provider': return 'provider retry';
|
|
58
|
-
case 'auth': return 'provider credentials updated';
|
|
59
|
-
default: return 'runtime recovery';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function reviewCommentSummary(facts) {
|
|
63
|
-
switch (facts.verb) {
|
|
64
|
-
case 'create': return 'review comment created';
|
|
65
|
-
case 'edit': return 'review comment edited';
|
|
66
|
-
case 'resolve': return 'review comment resolved';
|
|
67
|
-
case 'reopen': return 'review comment reopened';
|
|
68
|
-
case 'delete': return 'review comment deleted';
|
|
69
|
-
default: return 'review comment updated';
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/** The closed crouter vocabulary, plus custom-role aliases for the same cards. */
|
|
73
|
-
export const KIND_TABLE = {
|
|
74
|
-
inbox: { label: 'crtr inbox', summary: inboxSummary, expandable: true },
|
|
75
|
-
revive: { label: 'crtr revive', summary: () => 'fresh context kickoff', expandable: true },
|
|
76
|
-
'restart-continuation': { label: 'crouter continuation', summary: () => 'continuing from where it left off', expandable: true },
|
|
77
|
-
'stop-guard': {
|
|
78
|
-
label: 'crtr stop guard',
|
|
79
|
-
summary: (facts) => facts.reason === 'structured-output' ? 'structured output required' : 'completion required',
|
|
80
|
-
expandable: true,
|
|
81
|
-
},
|
|
82
|
-
recovery: { label: 'crouter continuation', summary: recoverySummaryFromFacts, expandable: true },
|
|
83
|
-
'persona-transition': { label: 'crtr persona', summary: () => 'runtime role update', expandable: true },
|
|
84
|
-
'review-approval': { label: 'crtr review', summary: () => 'review approved by the user', expandable: true },
|
|
85
|
-
'review-comment': { label: 'crtr review', summary: reviewCommentSummary, expandable: true },
|
|
86
|
-
'review-queued': { label: 'crtr review', summary: () => 'review submitted; completion pending', expandable: true },
|
|
87
|
-
'context-nudge': {
|
|
88
|
-
label: 'crtr',
|
|
89
|
-
summary: (facts) => facts.size === undefined ? 'context-window guidance' : `Context ${facts.size}`,
|
|
90
|
-
expandable: true,
|
|
91
|
-
customTypes: { [CONTEXT_NUDGE_CUSTOM_TYPE]: {} },
|
|
92
|
-
},
|
|
93
|
-
bearings: {
|
|
94
|
-
label: 'crtr context',
|
|
95
|
-
summary: () => 'orienting bearings',
|
|
96
|
-
expandable: true,
|
|
97
|
-
customTypes: { [CONTEXT_INTRO_CUSTOM_TYPE]: {} },
|
|
98
|
-
},
|
|
99
|
-
'review-boundary': {
|
|
100
|
-
label: 'review boundary',
|
|
101
|
-
summary: () => 'earlier conversation is not shown',
|
|
102
|
-
expandable: true,
|
|
103
|
-
customTypes: { [REVIEW_BOUNDARY_CUSTOM_TYPE]: {} },
|
|
104
|
-
},
|
|
105
|
-
situational: {
|
|
106
|
-
label: 'situational context',
|
|
107
|
-
summary: () => 'ambient context update',
|
|
108
|
-
expandable: true,
|
|
109
|
-
customTypes: { [SITUATIONAL_CONTEXT_CUSTOM_TYPE]: {} },
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
const GENERIC_CARD = {
|
|
113
|
-
label: 'crtr runtime',
|
|
114
|
-
summary: () => 'runtime context',
|
|
115
|
-
expandable: true,
|
|
116
|
-
};
|
|
117
|
-
/** Kinds contributed by a product or plugin, in whatever process renders them. */
|
|
118
|
-
const KIND_REGISTRY = {};
|
|
119
|
-
/** Contribute presentation for namespaced kinds this process will render.
|
|
120
|
-
* Idempotent and last-write-wins, so a re-imported module cannot fail a boot.
|
|
121
|
-
* A process that never registers is not degraded: an unregistered kind still
|
|
122
|
-
* parses and only falls back to the generic label and summary. */
|
|
123
|
-
export function registerCardKinds(kinds) {
|
|
124
|
-
for (const [kind, definition] of Object.entries(kinds)) {
|
|
125
|
-
// Bare kinds are crouter core's closed vocabulary. A product claiming one
|
|
126
|
-
// is a programming error at boot, not a runtime condition to tolerate.
|
|
127
|
-
if (!kind.includes(':'))
|
|
128
|
-
throw new Error(`Runtime card kind must be namespaced (contain ':'): ${kind}`);
|
|
129
|
-
KIND_REGISTRY[kind] = definition;
|
|
38
|
+
function legacyCustomTypeKind(customType) {
|
|
39
|
+
switch (customType) {
|
|
40
|
+
case CONTEXT_NUDGE_CUSTOM_TYPE: return 'context-nudge';
|
|
41
|
+
case CONTEXT_INTRO_CUSTOM_TYPE: return 'bearings';
|
|
42
|
+
case REVIEW_BOUNDARY_CUSTOM_TYPE: return 'review-boundary';
|
|
43
|
+
case SITUATIONAL_CONTEXT_CUSTOM_TYPE: return 'situational';
|
|
44
|
+
default: return undefined;
|
|
130
45
|
}
|
|
131
46
|
}
|
|
132
47
|
const XML_ATTRIBUTE_NAME = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/;
|
|
@@ -248,16 +163,7 @@ function parseInboxBody(body) {
|
|
|
248
163
|
return senders;
|
|
249
164
|
}
|
|
250
165
|
function cardFor(kind, facts, body, senders) {
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
kind,
|
|
254
|
-
facts,
|
|
255
|
-
body,
|
|
256
|
-
senders,
|
|
257
|
-
label: definition.label,
|
|
258
|
-
summary: definition.summary(facts),
|
|
259
|
-
expandable: definition.expandable,
|
|
260
|
-
};
|
|
166
|
+
return { kind, facts, body, senders };
|
|
261
167
|
}
|
|
262
168
|
/** Parse a whole-message runtime envelope, regardless of delivery role. */
|
|
263
169
|
function parseEnvelope(text) {
|
|
@@ -286,22 +192,13 @@ export function parseCard(message) {
|
|
|
286
192
|
return null;
|
|
287
193
|
// Pre-envelope custom-message fallback for old sessions. New custom messages
|
|
288
194
|
// classify through their envelope; customType is delivery metadata only.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
facts,
|
|
297
|
-
body: text,
|
|
298
|
-
senders: [],
|
|
299
|
-
label: override.label ?? definition.label,
|
|
300
|
-
summary: (override.summary ?? definition.summary)(facts),
|
|
301
|
-
expandable: override.expandable ?? definition.expandable,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
return null;
|
|
195
|
+
const kind = legacyCustomTypeKind(message.customType);
|
|
196
|
+
return kind === undefined ? null : {
|
|
197
|
+
kind,
|
|
198
|
+
facts: Object.freeze({}),
|
|
199
|
+
body: text,
|
|
200
|
+
senders: [],
|
|
201
|
+
};
|
|
305
202
|
}
|
|
306
203
|
/** Format a complete inbox card from report bodies resolved by the caller. */
|
|
307
204
|
export function formatInboxCard(sections) {
|
|
@@ -316,8 +213,7 @@ export function formatInboxCard(sections) {
|
|
|
316
213
|
}).join('\n');
|
|
317
214
|
return `<from${formatAttributes({ id: section.id, name: section.name, updates, finished: finished || undefined })}>${entries}</from>`;
|
|
318
215
|
}).join('\n');
|
|
319
|
-
|
|
320
|
-
return formatCard('inbox', { senders: sections.length, updates }, body);
|
|
216
|
+
return formatCard('inbox', {}, body);
|
|
321
217
|
}
|
|
322
218
|
// LEGACY — landed 2026-08-14. Pre-envelope runtime text, kept ONLY so old
|
|
323
219
|
// scrollback is not attributed to the person. Delete this section and its
|
|
@@ -11,6 +11,7 @@ export interface ToolGroupMessageLike {
|
|
|
11
11
|
content?: unknown;
|
|
12
12
|
customType?: string;
|
|
13
13
|
}
|
|
14
|
+
export declare function isGroupBoundaryKind(kind: string): boolean;
|
|
14
15
|
/** Validate the one durable summary shape and cap its display-sized fields.
|
|
15
16
|
* Invalid entries are absent rather than adapted, so their calls stay visible. */
|
|
16
17
|
export declare function normalizeToolGroupSummary(value: unknown): ToolGroupSummary | null;
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
// Keep this independent of renderer state: both live event tracking and snapshot
|
|
3
3
|
// reconstruction must agree on which prose closes a run of tools.
|
|
4
4
|
import { generatedContextText, isLegacyRuntimeText, parseCard } from './generated-context.js';
|
|
5
|
+
/** Card kinds that close a run of tools instead of joining one. The broker
|
|
6
|
+
* segments and the viewer folds on the same seams, so this one answer is shared;
|
|
7
|
+
* everything else about a card is the display's own business. */
|
|
8
|
+
const GROUP_BOUNDARY_KINDS = new Set(['restart-continuation', 'review-boundary']);
|
|
9
|
+
export function isGroupBoundaryKind(kind) {
|
|
10
|
+
return GROUP_BOUNDARY_KINDS.has(kind);
|
|
11
|
+
}
|
|
5
12
|
const TOOL_GROUP_SUMMARY_KEYS = new Set(['bullets', 'filesDeleted']);
|
|
6
13
|
const MAX_TOOL_GROUP_BULLETS = 5;
|
|
7
14
|
const MAX_TOOL_GROUP_BULLET_LENGTH = 1_000;
|
package/package.json
CHANGED
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.209",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/crouter",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.209",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type Rung, type SubstrateDoc } from './schema.js';
|
|
2
|
-
/** The reserved basename (no extension) that marks a directory's index doc. */
|
|
3
|
-
export declare const INDEX_NAME = "INDEX";
|
|
4
|
-
/** Does this path-derived name denote a directory INDEX (`taste/INDEX`, or a
|
|
5
|
-
* top-level `INDEX`)? */
|
|
6
|
-
export declare function isIndexName(name: string): boolean;
|
|
7
|
-
/** The directory an INDEX doc represents: `taste/INDEX` → `taste`, `INDEX` → ''. */
|
|
8
|
-
export declare function indexDirOf(name: string): string;
|
|
9
|
-
/** Returns the display path corresponding to a canonical logical identifier. */
|
|
10
|
-
export declare function displayName(name: string): string;
|
|
11
|
-
/** Build the dir → governing-INDEX map. Precedence is first-occurrence-wins, so
|
|
12
|
-
* callers MUST pass docs precedence-ordered (project > user > builtin) — the
|
|
13
|
-
* highest-precedence INDEX governs a dir, mirroring the memory resolver. */
|
|
14
|
-
export declare function buildCeilingIndex(docs: SubstrateDoc[]): Map<string, SubstrateDoc>;
|
|
15
|
-
/** A doc's effective system-prompt rung after applying every ancestor INDEX
|
|
16
|
-
* ceiling. A `none` ancestor hides the whole subtree from the boot catalog. */
|
|
17
|
-
export declare function effectiveSystemPromptRung(doc: SubstrateDoc, ceil: Map<string, SubstrateDoc>): Rung;
|