@north-light/crouter 0.3.261 → 0.3.263

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.
Files changed (48) hide show
  1. package/dist/api/dto/health.d.ts +2 -0
  2. package/dist/builtin-memory/internal/memory-loading.md +3 -3
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +4 -3
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +5 -3
  5. package/dist/clients/attach/input/controller.d.ts +4 -0
  6. package/dist/clients/attach/input/controller.js +8 -0
  7. package/dist/clients/attach/session/bindings.d.ts +3 -0
  8. package/dist/clients/attach/session/bindings.js +7 -0
  9. package/dist/clients/attach/session/keys.d.ts +2 -0
  10. package/dist/clients/attach/session/keys.js +3 -3
  11. package/dist/clients/attach/viewer.js +616 -616
  12. package/dist/commands/memory/read.js +15 -38
  13. package/dist/commands/memory/shared.d.ts +1 -1
  14. package/dist/commands/memory/shared.js +1 -1
  15. package/dist/commands/sys/context/admin/docs-panel.d.ts +3 -3
  16. package/dist/commands/sys/context/admin/model.d.ts +7 -2
  17. package/dist/commands/sys/context/admin/model.js +7 -1
  18. package/dist/commands/sys/context/admin/read-view.d.ts +5 -5
  19. package/dist/commands/sys/context/admin/read-view.js +9 -4
  20. package/dist/commands/sys/context/resolve.d.ts +32 -3
  21. package/dist/commands/sys/context/resolve.js +74 -25
  22. package/dist/commands/sys/panels/models-panel.d.ts +28 -5
  23. package/dist/commands/sys/panels/models-panel.js +558 -75
  24. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +1 -1
  25. package/dist/core/__tests__/model-routes-config.test.d.ts +1 -0
  26. package/dist/core/__tests__/model-routes-config.test.js +142 -0
  27. package/dist/core/__tests__/on-read-nested-store.test.js +8 -0
  28. package/dist/core/__tests__/profile-project-memory-delivery.test.js +264 -1
  29. package/dist/core/__tests__/seam/broker-provider-retry.test.js +2 -2
  30. package/dist/core/canvas/render-source.d.ts +1 -1
  31. package/dist/core/canvas/render-source.js +3 -2
  32. package/dist/core/config.d.ts +19 -2
  33. package/dist/core/config.js +162 -47
  34. package/dist/core/keybindings/catalog.js +9 -7
  35. package/dist/core/model-routes.js +10 -3
  36. package/dist/core/substrate/listings.d.ts +1 -1
  37. package/dist/core/substrate/listings.js +6 -2
  38. package/dist/core/substrate/on-read.d.ts +61 -5
  39. package/dist/core/substrate/on-read.js +129 -22
  40. package/dist/core/substrate/render.d.ts +3 -2
  41. package/dist/core/substrate/render.js +133 -38
  42. package/dist/daemon/api/__tests__/seam/api-server.test.js +34 -0
  43. package/dist/daemon/api/handlers/canvas.js +18 -2
  44. package/dist/daemon/api/handlers/health.js +17 -12
  45. package/dist/pi-extensions/__tests__/pre-command-gate.test.js +2 -2
  46. package/dist/types.d.ts +4 -2
  47. package/package.json +1 -1
  48. package/runtime.lock.json +2 -2
@@ -3,13 +3,12 @@ import { CrtrClient } from '../../api/index.js';
3
3
  import { defineLeaf } from '../../core/command.js';
4
4
  import { CrtrError } from '../../core/errors.js';
5
5
  import { memoryExtensionEffectiveCatalog, projectEffectiveMemoryExtensions } from '../../core/memory/extensions.js';
6
- import { realpathOrSelf } from '../../core/fs-utils.js';
7
6
  import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
8
7
  import { createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
9
8
  import { expandShellBlocks, hasShellBlocks, makeNodeShellRunner } from '../../core/runtime/shell-expansion.js';
10
- import { emptyContextExposureState, exposedAtOrAbove, exposureTarget, loadContextExposureState, registerDocumentExposure, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
11
- import { dirDedupKey, docListingDirs, docsByName, isDirName, renderDirListing } from '../../core/substrate/listings.js';
12
- import { memoryReadDocBlocks } from '../../core/substrate/on-read.js';
9
+ import { emptyContextExposureState, exposureTarget, loadContextExposureState, registerDocumentExposure, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
10
+ import { dirDedupKey, docsByName, isDirName, renderDirListing } from '../../core/substrate/listings.js';
11
+ import { contentReadAttachments } from '../../core/substrate/on-read.js';
13
12
  import { renderResult } from '../../core/render.js';
14
13
  import { effectiveDocKind } from '../../core/substrate/schema.js';
15
14
  import { canonicalSegments } from '../../core/memory/identity.js';
@@ -49,13 +48,6 @@ export function candidateRow(doc, winner) {
49
48
  path: doc.path,
50
49
  };
51
50
  }
52
- function attr(s) {
53
- return s
54
- .replace(/&/g, '&')
55
- .replace(/"/g, '"')
56
- .replace(/</g, '&lt;')
57
- .replace(/>/g, '&gt;');
58
- }
59
51
  export const readLeaf = defineLeaf({
60
52
  name: 'read',
61
53
  description: 'load a memory document body by name',
@@ -190,36 +182,21 @@ export const readLeaf = defineLeaf({
190
182
  : emptyContextExposureState();
191
183
  const target = exposureTarget(contextExposure, 'transcript');
192
184
  const subject = await fetchSubject(nodeId);
193
- const excludeReal = realpathOrSelf(doc.path);
194
- // The explicit read IS a content delivery: record it first so unsolicited
195
- // channels (including this doc's own line in its directory listing) stay
196
- // quiet about a doc whose full body is already in the transcript.
185
+ // An explicit read is itself content delivery. Register it before shared
186
+ // attachments, so the directory tree and companion routes dedupe against
187
+ // the body just returned exactly as every automatic content delivery does.
197
188
  registerDocumentExposure(target, doc.path, doc.body, 'content');
198
- const blocks = [];
199
- // A doc that fronts a directory carries its members with its body: that
200
- // listing is part of the deliberate read (every line renders) and returns
201
- // as `listing`, while the ancestors above it stay unsolicited neighborhood
202
- // context. The node's own document is never among its children, so nothing
203
- // repeats.
204
- const dirs = docListingDirs(listingByName, doc.name);
205
- const ownDir = dirs[0] === doc.name;
206
- let listing;
207
- if (ownDir) {
208
- listing = renderDirListing(listingByName, doc.name, target, false);
209
- registerExposure(target, dirDedupKey(doc.name), 'preview');
210
- }
211
- for (const dir of ownDir ? dirs.slice(1) : dirs) {
212
- if (exposedAtOrAbove(contextExposure, dirDedupKey(dir), 'preview'))
213
- continue;
214
- const lines = renderDirListing(listingByName, dir, target, true);
215
- registerExposure(target, dirDedupKey(dir), 'preview');
216
- if (lines.length > 0)
217
- blocks.push(`<memory-listing dir="${attr(dir)}">\n${lines.join('\n')}\n</memory-listing>`);
218
- }
219
- blocks.push(...memoryReadDocBlocks(subject, excludeReal, doc.name, target));
189
+ const attachments = contentReadAttachments(doc, {
190
+ subject,
191
+ target,
192
+ byName: listingByName,
193
+ listings: 'blocks',
194
+ explicit: true,
195
+ });
220
196
  if (nodeId !== undefined)
221
197
  saveContextExposureState(nodeId, contextExposure);
222
- const finalContent = blocks.length === 0 ? content : `<auto-loaded-context>\n${blocks.join('\n')}\n</auto-loaded-context>\n\n${content}`;
198
+ const finalContent = attachments.blocks.length === 0 ? content : `<auto-loaded-context>\n${attachments.blocks.join('\n')}\n</auto-loaded-context>\n\n${content}`;
199
+ const listing = attachments.listing;
223
200
  const hasListing = listing !== undefined && listing.length > 0;
224
201
  const followUp = hasListing
225
202
  ? links.length > 0
@@ -195,7 +195,7 @@ export declare function overlayParam(name: string, overrides?: Partial<FlagParam
195
195
  * `--doc-rationale`, because `--rationale` there means why THIS REVISION is
196
196
  * happening. Same field, same prose, two flag names that cannot be confused. */
197
197
  export declare const DOC_RATIONALE_CONSTRAINT = "Frontmatter rationale \u2014 the observed agent failure that made this doc necessary. Maintainer-facing only: never ships in any delivered surface (boot render, on-read injection, `memory read` content), visible only via `memory read --frontmatter`. Omitting the flag preserves an existing rationale unchanged.";
198
- export declare const GUIDE_SURFACES = "Every doc appears in its directory\u2019s listing by default (suppress with --unlisted); everything beyond the listing is explicit `surfaces` routing \u2014 entries declaring WHEN the doc delivers and how much. Events: `boot` delivers in the boot catalog every agent sees; `workspace-open` delivers in first-message context when cwd/profile mounts the doc\u2019s project store (project stores only); `read` fires when the agent reads a matching file (globs vs the file\u2019s absolute path and basename, `./`-anchored globs vs its path relative to the store\u2019s owning repo dir, `match-frontmatter` predicates over the read file\u2019s own YAML frontmatter); `memory-read` fires when another memory doc is read (globs vs that doc\u2019s canonical name, `./` anchored to this doc\u2019s routing anchor \u2014 its own canonical name when it is its directory\u2019s document, otherwise the canonical directory it sits in); `command` fires when a matching shell command runs (globs vs each shell segment of the command line \u2014 split on `&&`/`||`/`;`/`|`/newlines outside quotes, leading `VAR=value` assignments dropped \u2014 where `*` spans whole whitespace-separated tokens and never part of one, `?` is one character inside a token, and quoted argument text is opaque, so `*git commit*` fires for `cd x && git commit -m \"\u2026\"` but not for `git commit-tree` or an `echo` that merely mentions it); `pre-command` fires BEFORE a matching bash command runs (same matching): when the memory has not been read yet, the command does not execute \u2014 the memory comes back as the tool result instead, and the agent re-issues the command, which then runs. Use it to guarantee a memory is seen before a sensitive action is taken. Requires `match`. An entry may also carry `gate`, a node-config predicate using the same vocabulary as the document-level `gate`: event constraints and entry gate both must match, then all participating entries fold to the highest `at` (`content` > `preview` > `name`), with no cross-entry deny precedence. The document-level `gate` remains the hard eligibility check: when it fails, no automatic surface can deliver the document. `at` sets how much delivers: `name` the bare tag, `preview` the routing line, `content` the whole body. Explicit `memory read` and listings remain deliberate access and ignore gates/rungs; only companion docs routed by their `memory-read` entries use entry gates. Each entry is paid by every future agent it fires on, so default down: no surfaces at all is correct for reference depth reached through listings and [[links]], and when a doc fits in a single sentence, deliver `content` directly \u2014 a `preview` routing line would run longer than the doc itself. Sentence-length `content` docs are correct; never pad a memory to be more verbose than the rule or fact it carries.";
198
+ export declare const GUIDE_SURFACES = "Every doc appears in its directory\u2019s listing by default (suppress with --unlisted); everything beyond the listing is explicit `surfaces` routing \u2014 entries declaring WHEN the doc delivers and how much. Events: `boot` delivers in the boot catalog every agent sees; `workspace-open` delivers in first-message context when cwd/profile mounts the doc\u2019s project store (project stores only); `read` fires when the agent reads a matching file (globs vs the file\u2019s absolute path and basename, `./`-anchored globs vs its path relative to the store\u2019s owning repo dir, `match-frontmatter` predicates over the read file\u2019s own YAML frontmatter); `memory-read` fires when another memory doc\u2019s full body is delivered, including an explicit read (globs vs that doc\u2019s canonical name, `./` anchored to this doc\u2019s routing anchor \u2014 its own canonical name when it is its directory\u2019s document, otherwise the canonical directory it sits in); `command` fires when a matching shell command runs (globs vs each shell segment of the command line \u2014 split on `&&`/`||`/`;`/`|`/newlines outside quotes, leading `VAR=value` assignments dropped \u2014 where `*` spans whole whitespace-separated tokens and never part of one, `?` is one character inside a token, and quoted argument text is opaque, so `*git commit*` fires for `cd x && git commit -m \"\u2026\"` but not for `git commit-tree` or an `echo` that merely mentions it); `pre-command` fires BEFORE a matching bash command runs (same matching): when the memory has not been read yet, the command does not execute \u2014 the memory comes back as the tool result instead, and the agent re-issues the command, which then runs. Use it to guarantee a memory is seen before a sensitive action is taken. Requires `match`. An entry may also carry `gate`, a node-config predicate using the same vocabulary as the document-level `gate`: event constraints and entry gate both must match, then all participating entries fold to the highest `at` (`content` > `preview` > `name`), with no cross-entry deny precedence. The document-level `gate` remains the hard eligibility check: when it fails, no automatic surface can deliver the document. `at` sets how much delivers: `name` the bare tag, `preview` the routing line, `content` the whole body. Explicit `memory read` and listings remain deliberate access and ignore gates/rungs; only companion docs routed by their `memory-read` entries use entry gates. Each entry is paid by every future agent it fires on, so default down: no surfaces at all is correct for reference depth reached through listings and [[links]], and when a doc fits in a single sentence, deliver `content` directly \u2014 a `preview` routing line would run longer than the doc itself. Sentence-length `content` docs are correct; never pad a memory to be more verbose than the rule or fact it carries.";
199
199
  export declare const GUIDE_ROUTING_LINE = "The routing line (--when-and-why-to-read) is the only text an agent reads before deciding to load the doc. The test for its when-clause: it names an observable circumstance in the reader\u2019s current work, not the doc\u2019s topic reworded as an activity. If the WHEN can be inferred from the title alone, it is not a real trigger. Bad on a todo list: \"When planning or prioritizing work across this profile.\" Good: \"When the user mentions something from their todos, or asks what is still outstanding across this profile.\" The test for its because-clause: if it can be derived by paraphrasing the doc\u2019s advice, it is a restatement, not a payoff \u2014 a real payoff names a consequence in the reader\u2019s world that the document itself never asserts. Bad: \"because only genuine first principles belong in taste memory.\" Bad: \"because keeping the test loop fast and free of speculative tests protects the development pace\" \u2014 the doc\u2019s rule as an outcome, derivable straight from its advice. Good: \"because identifying throughlines in user taste lets future decisions be made faster and with less re-litigation.\" Someone mid-task who has not read the doc must be able to decide from this line alone whether the read is worth it. If you cannot name the concrete situation that triggers it, you do not yet understand the memory \u2014 ask the user one sharp question instead of improvising.";
200
200
  export declare const GUIDE_PREDICATE_VOCABULARY = "Document gates, surface-entry gates, and match-frontmatter share the same predicate language: a field map is AND-ed across fields; dotted fields resolve nested values; field matchers may be scalar, array, or object. Scalar matchers do exact equality, with arrays matching any element. Array matchers do membership or intersection. Object matchers accept `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, and `lte`. Combinators are `all`, `any`, and `not`; sibling field matchers next to them are AND-ed in. An empty condition is inert. An unknown op never matches.";
201
201
  export declare const GUIDE_DOC_LINKS = "Link related docs with `[[canonical/name]]`. A doc link in any body is a first-class cross-reference to another memory document by its exact canonical name \u2014 the same identifier `crtr memory read` takes, namespace included for a project document. A bare directory name is a valid link too: following it returns that directory\u2019s own document when it has one, plus the directory listing. Links are pointers, never transclusion: the linked body is loaded only when a reader follows it, so a link costs nothing until needed. `crtr memory lint` fails on a link that resolves to no document or directory, and `crtr memory read` lists a doc\u2019s resolvable links alongside its body. There is no alias, label, or old-name form \u2014 a renamed target needs its links rewritten.";
@@ -795,7 +795,7 @@ export function overlayParam(name, overrides = {}, extraConstraint) {
795
795
  * `--doc-rationale`, because `--rationale` there means why THIS REVISION is
796
796
  * happening. Same field, same prose, two flag names that cannot be confused. */
797
797
  export const DOC_RATIONALE_CONSTRAINT = 'Frontmatter rationale \u2014 the observed agent failure that made this doc necessary. Maintainer-facing only: never ships in any delivered surface (boot render, on-read injection, `memory read` content), visible only via `memory read --frontmatter`. Omitting the flag preserves an existing rationale unchanged.';
798
- export const GUIDE_SURFACES = 'Every doc appears in its directory’s listing by default (suppress with --unlisted); everything beyond the listing is explicit `surfaces` routing — entries declaring WHEN the doc delivers and how much. Events: `boot` delivers in the boot catalog every agent sees; `workspace-open` delivers in first-message context when cwd/profile mounts the doc’s project store (project stores only); `read` fires when the agent reads a matching file (globs vs the file’s absolute path and basename, `./`-anchored globs vs its path relative to the store’s owning repo dir, `match-frontmatter` predicates over the read file’s own YAML frontmatter); `memory-read` fires when another memory doc is read (globs vs that doc’s canonical name, `./` anchored to this doc’s routing anchor — its own canonical name when it is its directory’s document, otherwise the canonical directory it sits in); `command` fires when a matching shell command runs (globs vs each shell segment of the command line — split on `&&`/`||`/`;`/`|`/newlines outside quotes, leading `VAR=value` assignments dropped — where `*` spans whole whitespace-separated tokens and never part of one, `?` is one character inside a token, and quoted argument text is opaque, so `*git commit*` fires for `cd x && git commit -m "…"` but not for `git commit-tree` or an `echo` that merely mentions it); `pre-command` fires BEFORE a matching bash command runs (same matching): when the memory has not been read yet, the command does not execute — the memory comes back as the tool result instead, and the agent re-issues the command, which then runs. Use it to guarantee a memory is seen before a sensitive action is taken. Requires `match`. An entry may also carry `gate`, a node-config predicate using the same vocabulary as the document-level `gate`: event constraints and entry gate both must match, then all participating entries fold to the highest `at` (`content` > `preview` > `name`), with no cross-entry deny precedence. The document-level `gate` remains the hard eligibility check: when it fails, no automatic surface can deliver the document. `at` sets how much delivers: `name` the bare tag, `preview` the routing line, `content` the whole body. Explicit `memory read` and listings remain deliberate access and ignore gates/rungs; only companion docs routed by their `memory-read` entries use entry gates. Each entry is paid by every future agent it fires on, so default down: no surfaces at all is correct for reference depth reached through listings and [[links]], and when a doc fits in a single sentence, deliver `content` directly — a `preview` routing line would run longer than the doc itself. Sentence-length `content` docs are correct; never pad a memory to be more verbose than the rule or fact it carries.';
798
+ export const GUIDE_SURFACES = 'Every doc appears in its directory’s listing by default (suppress with --unlisted); everything beyond the listing is explicit `surfaces` routing — entries declaring WHEN the doc delivers and how much. Events: `boot` delivers in the boot catalog every agent sees; `workspace-open` delivers in first-message context when cwd/profile mounts the doc’s project store (project stores only); `read` fires when the agent reads a matching file (globs vs the file’s absolute path and basename, `./`-anchored globs vs its path relative to the store’s owning repo dir, `match-frontmatter` predicates over the read file’s own YAML frontmatter); `memory-read` fires when another memory doc’s full body is delivered, including an explicit read (globs vs that doc’s canonical name, `./` anchored to this doc’s routing anchor — its own canonical name when it is its directory’s document, otherwise the canonical directory it sits in); `command` fires when a matching shell command runs (globs vs each shell segment of the command line — split on `&&`/`||`/`;`/`|`/newlines outside quotes, leading `VAR=value` assignments dropped — where `*` spans whole whitespace-separated tokens and never part of one, `?` is one character inside a token, and quoted argument text is opaque, so `*git commit*` fires for `cd x && git commit -m "…"` but not for `git commit-tree` or an `echo` that merely mentions it); `pre-command` fires BEFORE a matching bash command runs (same matching): when the memory has not been read yet, the command does not execute — the memory comes back as the tool result instead, and the agent re-issues the command, which then runs. Use it to guarantee a memory is seen before a sensitive action is taken. Requires `match`. An entry may also carry `gate`, a node-config predicate using the same vocabulary as the document-level `gate`: event constraints and entry gate both must match, then all participating entries fold to the highest `at` (`content` > `preview` > `name`), with no cross-entry deny precedence. The document-level `gate` remains the hard eligibility check: when it fails, no automatic surface can deliver the document. `at` sets how much delivers: `name` the bare tag, `preview` the routing line, `content` the whole body. Explicit `memory read` and listings remain deliberate access and ignore gates/rungs; only companion docs routed by their `memory-read` entries use entry gates. Each entry is paid by every future agent it fires on, so default down: no surfaces at all is correct for reference depth reached through listings and [[links]], and when a doc fits in a single sentence, deliver `content` directly — a `preview` routing line would run longer than the doc itself. Sentence-length `content` docs are correct; never pad a memory to be more verbose than the rule or fact it carries.';
799
799
  export const GUIDE_ROUTING_LINE = 'The routing line (--when-and-why-to-read) is the only text an agent reads before deciding to load the doc. The test for its when-clause: it names an observable circumstance in the reader\u2019s current work, not the doc\u2019s topic reworded as an activity. If the WHEN can be inferred from the title alone, it is not a real trigger. Bad on a todo list: "When planning or prioritizing work across this profile." Good: "When the user mentions something from their todos, or asks what is still outstanding across this profile." The test for its because-clause: if it can be derived by paraphrasing the doc\u2019s advice, it is a restatement, not a payoff \u2014 a real payoff names a consequence in the reader\u2019s world that the document itself never asserts. Bad: "because only genuine first principles belong in taste memory." Bad: "because keeping the test loop fast and free of speculative tests protects the development pace" \u2014 the doc\u2019s rule as an outcome, derivable straight from its advice. Good: "because identifying throughlines in user taste lets future decisions be made faster and with less re-litigation." Someone mid-task who has not read the doc must be able to decide from this line alone whether the read is worth it. If you cannot name the concrete situation that triggers it, you do not yet understand the memory \u2014 ask the user one sharp question instead of improvising.';
800
800
  export const GUIDE_PREDICATE_VOCABULARY = 'Document gates, surface-entry gates, and match-frontmatter share the same predicate language: a field map is AND-ed across fields; dotted fields resolve nested values; field matchers may be scalar, array, or object. Scalar matchers do exact equality, with arrays matching any element. Array matchers do membership or intersection. Object matchers accept `eq`, `ne`, `in`, `nin`, `exists`, `contains`, `containsAll`, `containsAny`, `matches`, `imatches`, `gt`, `gte`, `lt`, and `lte`. Combinators are `all`, `any`, and `not`; sibling field matchers next to them are AND-ed in. An empty condition is inert. An unknown op never matches.';
801
801
  export const GUIDE_DOC_LINKS = 'Link related docs with `[[canonical/name]]`. A doc link in any body is a first-class cross-reference to another memory document by its exact canonical name \u2014 the same identifier `crtr memory read` takes, namespace included for a project document. A bare directory name is a valid link too: following it returns that directory\u2019s own document when it has one, plus the directory listing. Links are pointers, never transclusion: the linked body is loaded only when a reader follows it, so a link costs nothing until needed. `crtr memory lint` fails on a link that resolves to no document or directory, and `crtr memory read` lists a doc\u2019s resolvable links alongside its body. There is no alias, label, or old-name form \u2014 a renamed target needs its links rewritten.';
@@ -1,5 +1,5 @@
1
1
  import { type SurfaceEvent } from '../../../../core/substrate/schema.js';
2
- import type { DeliveryRecord } from '../../../../core/substrate/plan.js';
2
+ import type { EffectiveDeliveryRecord } from '../resolve.js';
3
3
  import { PlanSet } from './model.js';
4
4
  import { type Filters } from './list-view.js';
5
5
  /** One physical document across every event. `records` is sparse: an event
@@ -11,8 +11,8 @@ export interface DocRow {
11
11
  /** The record for the focused event, or the first one any event carries —
12
12
  * every panel needs a document to describe even when the focused event's
13
13
  * corpus excludes it. */
14
- anchor: DeliveryRecord;
15
- records: Map<SurfaceEvent, DeliveryRecord>;
14
+ anchor: EffectiveDeliveryRecord;
15
+ records: Map<SurfaceEvent, EffectiveDeliveryRecord>;
16
16
  /** Present only in an event other than the focused one. */
17
17
  offEvent: boolean;
18
18
  }
@@ -1,4 +1,5 @@
1
1
  import { type DeliveryPlan, type DeliveryRecord } from '../../../../core/substrate/plan.js';
2
+ import { type EffectiveDeliveryRecord } from '../resolve.js';
2
3
  import { type Rung, type SurfaceEntry, type SurfaceEvent } from '../../../../core/substrate/schema.js';
3
4
  import { type NodeConfigSubject } from '../../../../core/substrate/subject-fields.js';
4
5
  import { type MemoryTarget } from '../../../../core/memory-resolver.js';
@@ -45,11 +46,15 @@ export declare class PlanSet {
45
46
  invalidate(): void;
46
47
  dispose(): void;
47
48
  /** The plan for one event, computing it now. */
48
- plan(event: SurfaceEvent): DeliveryPlan;
49
+ plan(event: SurfaceEvent): DeliveryPlan & {
50
+ docs: EffectiveDeliveryRecord[];
51
+ };
49
52
  /** The plan for one event only if it is already computed — what the rail's
50
53
  * EVENTS counts read, so an unwarmed event draws `…` instead of stalling the
51
54
  * frame on five extra corpus loads. */
52
- peek(event: SurfaceEvent): DeliveryPlan | undefined;
55
+ peek(event: SurfaceEvent): (DeliveryPlan & {
56
+ docs: EffectiveDeliveryRecord[];
57
+ }) | undefined;
53
58
  /** Compute one pending event per macrotask, calling back after each so the
54
59
  * rail's counts fill in row by row. */
55
60
  warm(onProgress: () => void): void;
@@ -4,6 +4,7 @@
4
4
  import { resolve as resolvePath } from 'node:path';
5
5
  import { realpathOrSelf } from '../../../../core/fs-utils.js';
6
6
  import { planDelivery, } from '../../../../core/substrate/plan.js';
7
+ import { assembleEventAccounting, effectivePlan, } from '../resolve.js';
7
8
  import { RUNGS, SURFACE_EVENTS, previewLine, rungRank, } from '../../../../core/substrate/schema.js';
8
9
  import { scopeForCwd, profileNameFor } from '../../../../core/substrate/subject-fields.js';
9
10
  import { resolveMemoryDocForTarget } from '../../../../core/memory-resolver.js';
@@ -71,7 +72,12 @@ export class PlanSet {
71
72
  const held = this.plans.get(event);
72
73
  if (held !== undefined)
73
74
  return held;
74
- const computed = planDelivery(subjectOf(this.snapshot), targetOf(this.snapshot), event, this.payload(event));
75
+ const subject = subjectOf(this.snapshot);
76
+ const target = targetOf(this.snapshot);
77
+ const payload = this.payload(event);
78
+ const direct = planDelivery(subject, target, event, payload);
79
+ const rendered = assembleEventAccounting({ subject, target, nodeMissing: false }, event, payload);
80
+ const computed = effectivePlan(direct, rendered.accounting.deliveries, payload);
75
81
  this.plans.set(event, computed);
76
82
  return computed;
77
83
  }
@@ -1,5 +1,5 @@
1
1
  import { type Rung, type SurfaceEvent } from '../../../../core/substrate/schema.js';
2
- import type { DeliveryRecord } from '../../../../core/substrate/plan.js';
2
+ import type { EffectiveDeliveryRecord } from '../resolve.js';
3
3
  import type { NodeConfigSubject } from '../../../../core/substrate/subject-fields.js';
4
4
  /** The profile project entry whose `memory` rung ceilings this document's
5
5
  * store, or null when no project governs it. */
@@ -11,7 +11,7 @@ export interface CapOwner {
11
11
  * the sibling events' records, and the two facts the shell resolves. */
12
12
  export interface DossierContext {
13
13
  event: SurfaceEvent;
14
- records: Map<SurfaceEvent, DeliveryRecord>;
14
+ records: Map<SurfaceEvent, EffectiveDeliveryRecord>;
15
15
  capOwner: CapOwner | null;
16
16
  readOnly: boolean;
17
17
  /** The configuration every record here was planned against — what a failed
@@ -20,12 +20,12 @@ export interface DossierContext {
20
20
  }
21
21
  /** The resting card, and — when `bodyOpen` — the document's own text beneath
22
22
  * it in place of the affordance line that names it. */
23
- export declare function renderDossierRead(record: DeliveryRecord, context: DossierContext, width: number, bodyOpen: boolean): string[];
23
+ export declare function renderDossierRead(record: EffectiveDeliveryRecord, context: DossierContext, width: number, bodyOpen: boolean): string[];
24
24
  /** Identity and routing — what the edit face keeps overhead of its field list,
25
25
  * because they are the context a field is being edited against. */
26
- export declare function renderDossierHeader(record: DeliveryRecord, context: DossierContext, width: number): string[];
26
+ export declare function renderDossierHeader(record: EffectiveDeliveryRecord, context: DossierContext, width: number): string[];
27
27
  /** Does it load here, what does it cost, and — only when there is one — what
28
28
  * interfered. The three questions the old verdict, cost table and event grid
29
29
  * each answered a piece of. */
30
- export declare function renderDossierDelivery(record: DeliveryRecord, context: DossierContext, width: number): string[];
30
+ export declare function renderDossierDelivery(record: EffectiveDeliveryRecord, context: DossierContext, width: number): string[];
31
31
  export declare function bodyLineCount(body: string): number;
@@ -25,11 +25,13 @@ const INDENT = ' ';
25
25
  export function renderDossierRead(record, context, width, bodyOpen) {
26
26
  const inner = Math.max(1, width - INDENT.length);
27
27
  const out = renderDossierHeader(record, context, width);
28
- if (record.doc.shortForm.trim() !== '') {
29
- out.push('');
30
- out.push(...indent(wrapped(record.doc.shortForm, inner)));
31
- }
32
28
  out.push(...renderDossierDelivery(record, context, width));
29
+ // The gist sits against the body it summarizes: last of the card, directly
30
+ // above the affordance that opens the text it stands in for.
31
+ const gist = record.doc.shortForm.trim();
32
+ if (gist !== '') {
33
+ out.push('', theme.fg('dim', `${INDENT}SHORT FORM`), ...indent(wrapped(gist, inner)));
34
+ }
33
35
  const lines = bodyLineCount(record.doc.body);
34
36
  if (bodyOpen) {
35
37
  out.push('');
@@ -139,6 +141,9 @@ export function renderDossierDelivery(record, context, width) {
139
141
  ? `capped to ${record.cappedRung} by a profile ceiling on this store`
140
142
  : `capped to ${record.cappedRung} by the ${context.capOwner.memory} ceiling on ${context.capOwner.path}`);
141
143
  }
144
+ if (record.deliveryCause !== undefined) {
145
+ out.push(...dim(record.deliveryCause, width));
146
+ }
142
147
  if (record.shadowedBy !== null) {
143
148
  out.push(...indent(wrapped(`shadowed by the ${record.shadowedBy.scope} copy at ${record.shadowedBy.path}`, inner)).map((line) => theme.fg('muted', line)));
144
149
  }
@@ -1,6 +1,7 @@
1
- import { type MemoryTarget } from '../../../core/memory-resolver.js';
2
- import { type DeliveryPayload, type DeliveryRecord, type GateOutcome } from '../../../core/substrate/plan.js';
1
+ import { type MemoryDoc, type MemoryTarget } from '../../../core/memory-resolver.js';
2
+ import { type DeliveryPayload, type DeliveryPlan, type DeliveryRecord, type GateOutcome } from '../../../core/substrate/plan.js';
3
3
  import { type GateClause } from '../../../core/substrate/gate-explain.js';
4
+ import { deliveryAccounting } from '../../../core/substrate/on-read.js';
4
5
  import { type Rung, type SurfaceEntry, type SurfaceEvent } from '../../../core/substrate/schema.js';
5
6
  import { type NodeConfigSubject } from '../../../core/substrate/subject-fields.js';
6
7
  export declare const resolveLeaf: import("../../../core/command.js").LeafDef;
@@ -26,6 +27,10 @@ export interface PayloadFlags {
26
27
  export declare function payloadFor(event: SurfaceEvent, flags: PayloadFlags, target: MemoryTarget): DeliveryPayload;
27
28
  export declare function subjectSummary(subject: NodeConfigSubject): Record<string, unknown>;
28
29
  export declare function optionalString(value: unknown): string | null;
30
+ export interface EffectiveDeliveryRecord extends DeliveryRecord {
31
+ /** Present when the renderer raised this doc beyond its direct event plan. */
32
+ deliveryCause?: string;
33
+ }
29
34
  export interface DocRow {
30
35
  name: string;
31
36
  kind: string;
@@ -40,13 +45,24 @@ export interface DocRow {
40
45
  doc_gate: string;
41
46
  entry_gate: string | null;
42
47
  matched_entry: string | null;
48
+ delivery_cause?: string;
43
49
  excluded?: string;
44
50
  }
51
+ /** Overlay the production renderer's actual document deliveries onto direct
52
+ * planner records. The plan still supplies gates, caps, and exclusion detail;
53
+ * only an attachment that genuinely rendered may raise its final rung. */
54
+ export declare function effectivePlan(plan: DeliveryPlan, deliveries: ReadonlyMap<string, {
55
+ rung: Rung;
56
+ cause?: string;
57
+ source?: MemoryDoc;
58
+ }>, payload?: DeliveryPayload): DeliveryPlan & {
59
+ docs: EffectiveDeliveryRecord[];
60
+ };
45
61
  /** A document delivers when it won its canonical name AND resolved to a rung
46
62
  * above the no-delivery floor — boot keeps rung-`none` winners in the plan so
47
63
  * the catalog can still count them. */
48
64
  export declare function delivers(record: DeliveryRecord): boolean;
49
- export declare function docRow(record: DeliveryRecord, includeAll: boolean, subject: NodeConfigSubject | null): DocRow;
65
+ export declare function docRow(record: EffectiveDeliveryRecord, includeAll: boolean, subject: NodeConfigSubject | null): DocRow;
50
66
  /** A gate verdict as one cell: `pass`, or `fail:` plus the clauses that failed
51
67
  * — the planner's generic sentence only when the explainer cannot decompose
52
68
  * the predicate. */
@@ -55,5 +71,18 @@ export declare function entryText(entry: SurfaceEntry | null): string | null;
55
71
  /** Why a considered document delivers nothing, in the order the planner applies
56
72
  * its checks — the first failing one is the answer an engineer acts on. */
57
73
  export declare function exclusionReason(record: DeliveryRecord, subject: NodeConfigSubject | null): string;
74
+ /** Every renderer resolves its corpus from the AMBIENT process (cwd plus the
75
+ * two env seams), not from a target argument, so a snapshot dialed away from
76
+ * this process is only honored by standing in its place for the call. The
77
+ * session parse cache is cleared on both sides so neither the emulated corpus
78
+ * nor the real one is served from the other's scan. */
79
+ export interface AccountedEventRender {
80
+ assembled: string;
81
+ accounting: ReturnType<typeof deliveryAccounting>;
82
+ }
83
+ /** Run the production renderer once and retain the structured document
84
+ * deliveries it recorded. Context diagnostics and the admin viewer consume
85
+ * this result rather than reconstructing attachment/companion fixpoints. */
86
+ export declare function assembleEventAccounting(snapshot: ContextSnapshot, event: SurfaceEvent, payload: DeliveryPayload): AccountedEventRender;
58
87
  export declare function describeSubject(subject: Record<string, unknown>): string;
59
88
  export declare function cell(v: unknown): string;
@@ -19,9 +19,9 @@ import { planDelivery, } from '../../../core/substrate/plan.js';
19
19
  import { explainRecordGate, formatClause } from '../../../core/substrate/gate-explain.js';
20
20
  import { emptyContextExposureState, exposureTarget } from '../../../core/substrate/injected-store.js';
21
21
  import { renderPreferencesForSubject, renderKnowledgeForSubject } from '../../../core/substrate/render.js';
22
- import { memoryReadDocBlocks, renderOnCommandDocsForSubject, renderOnReadDocsForSubject, renderPreCommandDocsForSubject, renderWorkspaceOpenDocsForSubject, } from '../../../core/substrate/on-read.js';
22
+ import { deliveryAccounting, memoryReadDocBlocks, renderOnCommandDocsForSubject, renderOnReadDocsForSubject, renderPreCommandDocsForSubject, renderWorkspaceOpenDocsForSubject, } from '../../../core/substrate/on-read.js';
23
23
  import { clearSessionCache } from '../../../core/substrate/session-cache.js';
24
- import { rungAtLeast, SURFACE_EVENTS, } from '../../../core/substrate/schema.js';
24
+ import { parseSubstrateDoc, rungAtLeast, SURFACE_EVENTS, } from '../../../core/substrate/schema.js';
25
25
  import { scopeForCwd, profileNameFor } from '../../../core/substrate/subject-fields.js';
26
26
  import { resetScopeCache } from '../../../core/scope.js';
27
27
  import { getDefaultProfileId } from '../../../core/profiles/default-binding.js';
@@ -52,7 +52,7 @@ export const resolveLeaf = defineLeaf({
52
52
  { name: 'event', type: 'string', required: false, constraint: 'The resolved event. Absent with --all-events, where every entry in `plans` carries its own.' },
53
53
  { name: 'subject', type: 'object', required: true, constraint: 'The configuration snapshot used: kind, mode, lifecycle, hasManager, profile, scope.' },
54
54
  { name: 'target', type: 'object', required: true, constraint: 'Store resolution inputs: cwd, profileId, nodeId.' },
55
- { name: 'docs', type: 'array', required: false, constraint: 'One entry per document, at `docs`/`all` granularity: name, kind, scope, store_path, authored_rung, project_memory_cap, capped_rung, final_rung, winner, shadowed_by, doc_gate, entry_gate, matched_entry, and excluded when --all includes a non-delivering document.' },
55
+ { name: 'docs', type: 'array', required: false, constraint: 'One entry per document, at `docs`/`all` granularity: name, kind, scope, store_path, authored_rung, project_memory_cap, capped_rung, final_rung, winner, shadowed_by, doc_gate, entry_gate, matched_entry, delivery_cause when the renderer raised it through a content attachment, and excluded when --all includes a non-delivering document.' },
56
56
  { name: 'counts', type: 'object', required: false, constraint: 'At `summary` granularity: {considered, delivered: {content, preview, name}, delivered_total, excluded: {<reason>: count}}.' },
57
57
  { name: 'all_ignored', type: 'boolean', required: false, constraint: 'Present only when --all is supplied with --granularity assembled; true means assembled text is unchanged because --all applies only to docs granularity.' },
58
58
  { name: 'assembled', type: 'string', required: false, constraint: 'At `assembled` granularity: the exact text the node would receive for this event.' },
@@ -188,15 +188,59 @@ async function fetchSubjectOrNull(nodeId) {
188
188
  }
189
189
  }
190
190
  function planSection(snapshot, event, payload, granularity, includeAll) {
191
- if (granularity === 'assembled') {
192
- return { event, assembled: assembleEvent(snapshot, event, payload) };
193
- }
194
- const plan = planDelivery(snapshot.subject, snapshot.target, event, payload);
191
+ const rendered = assembleEventAccounting(snapshot, event, payload);
192
+ if (granularity === 'assembled')
193
+ return { event, assembled: rendered.assembled };
194
+ const plan = effectivePlan(planDelivery(snapshot.subject, snapshot.target, event, payload), rendered.accounting.deliveries, payload);
195
195
  if (granularity === 'summary')
196
196
  return { event, counts: countsOf(plan) };
197
197
  const docs = plan.docs.filter((r) => includeAll || delivers(r)).map((r) => docRow(r, includeAll, plan.subject));
198
198
  return { event, docs };
199
199
  }
200
+ /** Overlay the production renderer's actual document deliveries onto direct
201
+ * planner records. The plan still supplies gates, caps, and exclusion detail;
202
+ * only an attachment that genuinely rendered may raise its final rung. */
203
+ export function effectivePlan(plan, deliveries, payload = {}) {
204
+ const seen = new Set();
205
+ const docs = plan.docs.map((record) => {
206
+ seen.add(realpathOrSelf(record.doc.path));
207
+ const delivered = deliveries.get(realpathOrSelf(record.doc.path));
208
+ if (delivered === undefined || !rungAtLeast(delivered.rung, record.finalRung))
209
+ return record;
210
+ const raised = delivered.rung !== record.finalRung;
211
+ return {
212
+ ...record,
213
+ // A companion has its own memory-read winner set. If it is shadowed or
214
+ // silent in the outer event plan but genuinely rendered through that
215
+ // attachment, the rendered delivery is the truth this view reports.
216
+ ...(raised ? { winner: true, shadowedBy: null } : {}),
217
+ finalRung: delivered.rung,
218
+ ...(raised && delivered.cause !== undefined ? { deliveryCause: delivered.cause } : {}),
219
+ };
220
+ });
221
+ for (const delivered of deliveries.values()) {
222
+ if (delivered.source === undefined || seen.has(realpathOrSelf(delivered.source.path)))
223
+ continue;
224
+ const doc = parseSubstrateDoc(delivered.source);
225
+ if (doc === null)
226
+ continue;
227
+ // Workspace-open deliberately plans only project stores, while its real
228
+ // listing/companion attachments answer from the full target view. Plan this
229
+ // one rendered source solely to retain normal dossier metadata, then make
230
+ // its renderer-recorded rung and cause authoritative.
231
+ const record = planDelivery(plan.subject, plan.target, plan.event, payload, [{ doc, source: delivered.source }]).docs[0];
232
+ if (record === undefined)
233
+ continue;
234
+ docs.push({
235
+ ...record,
236
+ winner: true,
237
+ shadowedBy: null,
238
+ finalRung: delivered.rung,
239
+ ...(delivered.cause === undefined ? {} : { deliveryCause: delivered.cause }),
240
+ });
241
+ }
242
+ return { ...plan, docs };
243
+ }
200
244
  /** A document delivers when it won its canonical name AND resolved to a rung
201
245
  * above the no-delivery floor — boot keeps rung-`none` winners in the plan so
202
246
  * the catalog can still count them. */
@@ -218,6 +262,7 @@ export function docRow(record, includeAll, subject) {
218
262
  doc_gate: gateText(record.docGate, explainRecordGate(record, 'doc', subject)),
219
263
  entry_gate: record.entryGate === null ? null : gateText(record.entryGate, explainRecordGate(record, 'entry', subject)),
220
264
  matched_entry: entryText(record.matchedEntry),
265
+ ...(record.deliveryCause === undefined ? {} : { delivery_cause: record.deliveryCause }),
221
266
  };
222
267
  if (includeAll && !delivers(record))
223
268
  row.excluded = exclusionReason(record, subject);
@@ -294,26 +339,23 @@ function exclusionBucket(record) {
294
339
  }
295
340
  return record.cappedRung === 'none' ? 'capped' : 'rung_none';
296
341
  }
297
- // Assembled granularity — the real renderers, so the highest-fidelity view is
298
- // byte-identical to what the node receives.
299
- /** Every renderer resolves its corpus from the AMBIENT process (cwd plus the
300
- * two env seams), not from a target argument, so a snapshot dialed away from
301
- * this process is only honored by standing in its place for the call. The
302
- * session parse cache is cleared on both sides so neither the emulated corpus
303
- * nor the real one is served from the other's scan. */
304
- function assembleEvent(snapshot, event, payload) {
342
+ /** Run the production renderer once and retain the structured document
343
+ * deliveries it recorded. Context diagnostics and the admin viewer consume
344
+ * this result rather than reconstructing attachment/companion fixpoints. */
345
+ export function assembleEventAccounting(snapshot, event, payload) {
305
346
  const restore = {
306
347
  cwd: process.cwd(),
307
348
  profileId: envProfileId(),
308
349
  nodeId: envNodeId(),
309
350
  };
351
+ const accounting = deliveryAccounting();
310
352
  clearSessionCache();
311
353
  resetScopeCache();
312
354
  try {
313
355
  process.chdir(snapshot.target.cwd);
314
356
  setEnv('CRTR_PROFILE_ID', snapshot.target.profileId ?? undefined);
315
357
  setEnv('CRTR_NODE_ID', snapshot.target.nodeId ?? undefined);
316
- return renderEvent(snapshot, event, payload);
358
+ return { assembled: renderEvent(snapshot, event, payload, accounting), accounting };
317
359
  }
318
360
  finally {
319
361
  process.chdir(restore.cwd);
@@ -329,29 +371,34 @@ function setEnv(name, value) {
329
371
  else
330
372
  process.env[name] = value;
331
373
  }
332
- function renderEvent(snapshot, event, payload) {
374
+ function renderEvent(snapshot, event, payload, accounting) {
333
375
  const { subject, target } = snapshot;
334
376
  switch (event) {
335
377
  case 'boot': {
336
- const { block } = renderPreferencesForSubject(subject);
337
- const knowledge = target.nodeId === null ? '' : renderKnowledgeForSubject(subject, target.nodeId);
378
+ const { block } = renderPreferencesForSubject(subject, undefined, accounting);
379
+ const knowledge = target.nodeId === null ? '' : renderKnowledgeForSubject(subject, target.nodeId, undefined, accounting);
338
380
  return knowledge === '' ? block : `${block}\n\n${knowledge}`;
339
381
  }
340
382
  case 'workspace-open':
341
- return renderWorkspaceOpenDocsForSubject(subject, target.cwd, target.profileId);
383
+ return renderWorkspaceOpenDocsForSubject(subject, target.cwd, target.profileId, undefined, accounting);
342
384
  case 'read':
343
- return payload.absoluteFile === undefined ? '' : renderOnReadDocsForSubject(subject, payload.absoluteFile);
385
+ return payload.absoluteFile === undefined ? '' : renderOnReadDocsForSubject(subject, payload.absoluteFile, undefined, accounting);
344
386
  case 'memory-read': {
345
387
  const name = payload.resolvedCanonicalName;
346
388
  if (name === undefined)
347
389
  return '';
348
- const blocks = memoryReadDocBlocks(subject, payload.resolvedDocPath ?? '', name, exposureTarget(emptyContextExposureState(), 'transcript'));
390
+ const blocks = memoryReadDocBlocks(payload.resolvedDocPath ?? '', name, {
391
+ subject,
392
+ target: exposureTarget(emptyContextExposureState(), 'transcript'),
393
+ listings: 'blocks',
394
+ accounting,
395
+ });
349
396
  return blocks.length === 0 ? '' : `<auto-loaded-context>\n${blocks.join('\n')}\n</auto-loaded-context>`;
350
397
  }
351
398
  case 'command':
352
- return payload.command === undefined ? '' : renderOnCommandDocsForSubject(subject, payload.command);
399
+ return payload.command === undefined ? '' : renderOnCommandDocsForSubject(subject, payload.command, undefined, accounting);
353
400
  case 'pre-command':
354
- return payload.command === undefined ? '' : renderPreCommandDocsForSubject(subject, payload.command);
401
+ return payload.command === undefined ? '' : renderPreCommandDocsForSubject(subject, payload.command, undefined, accounting);
355
402
  }
356
403
  }
357
404
  /** The read document never delivers to itself, so the plan needs its physical
@@ -403,7 +450,8 @@ function renderSection(section) {
403
450
  if (docs.length === 0)
404
451
  return 'No document delivers for this event.';
405
452
  const showExcluded = docs.some((d) => d.excluded !== undefined);
406
- const cols = ['document', 'scope', 'kind', 'authored', 'cap', 'final', 'matched', 'gate', ...(showExcluded ? ['excluded'] : [])];
453
+ const showCause = docs.some((d) => d.delivery_cause !== undefined);
454
+ const cols = ['document', 'scope', 'kind', 'authored', 'cap', 'final', 'matched', 'gate', ...(showCause ? ['cause'] : []), ...(showExcluded ? ['excluded'] : [])];
407
455
  const rows = docs.map((d) => [
408
456
  d.name,
409
457
  d.scope,
@@ -413,6 +461,7 @@ function renderSection(section) {
413
461
  d.final_rung,
414
462
  d.matched_entry ?? '—',
415
463
  d.doc_gate,
464
+ ...(showCause ? [d.delivery_cause ?? ''] : []),
416
465
  ...(showExcluded ? [d.excluded ?? ''] : []),
417
466
  ]);
418
467
  return [
@@ -10,6 +10,8 @@ export declare class ModelsPanel implements SetupPanel {
10
10
  private readonly label;
11
11
  private mode;
12
12
  private ladders;
13
+ private modelRoutes;
14
+ private legacyModelRoutes;
13
15
  private kinds;
14
16
  /** The ladder providers this panel offers — only columns whose runtime
15
17
  * provider is signed in (all of them when nobody is, so config stays
@@ -27,8 +29,11 @@ export declare class ModelsPanel implements SetupPanel {
27
29
  private modelSelector;
28
30
  private thinkingSelector;
29
31
  private pending;
30
- private pendingDim;
31
- private dimPickIndex;
32
+ private choice;
33
+ private choiceIndex;
34
+ private routeForm;
35
+ private readonly routeInput;
36
+ private pendingRouteCell;
32
37
  onChange?: () => void;
33
38
  constructor(tui: TUI, runtime: ModelRuntime, registry: ModelRegistry, matches: (id: BindingId, data: string) => boolean, label: (id: BindingId) => string);
34
39
  /** Same signed-in rule as the broker's Alt+M cycle: pool occupancy for a
@@ -38,6 +43,8 @@ export declare class ModelsPanel implements SetupPanel {
38
43
  private cycleTarget;
39
44
  private shadowingPath;
40
45
  private savedButShadowed;
46
+ private routeIds;
47
+ private routeFamilies;
41
48
  private buildRows;
42
49
  private reload;
43
50
  /** Re-read config and auth state — called when the Models tab is entered so
@@ -50,12 +57,26 @@ export declare class ModelsPanel implements SetupPanel {
50
57
  private clampColumn;
51
58
  handleInput(data: string): boolean;
52
59
  private activateCell;
53
- /** Snapshot of the mode's current provider+strength. For orchestrator this
60
+ private portableSelection;
61
+ private familyLabel;
62
+ private familyOptions;
63
+ /** Snapshot of the mode's current family+strength. For orchestrator this
54
64
  * falls back to the base `model` when no explicit override exists, so the
55
65
  * inherited pair is what an edit preserves. */
56
66
  private kindSelection;
57
67
  private openDimensionPicker;
58
- private commitDimensionPick;
68
+ private writeKindSelection;
69
+ private commitChoice;
70
+ private openRouteForm;
71
+ private closeRouteForm;
72
+ private activateRouteField;
73
+ private setRouteProvider;
74
+ private openRouteCell;
75
+ private onRouteModelPicked;
76
+ private saveRoute;
77
+ private declaringPath;
78
+ private deleteRoute;
79
+ private showModelSelector;
59
80
  private openModelPicker;
60
81
  private onModelPicked;
61
82
  private onThinkingPicked;
@@ -66,7 +87,9 @@ export declare class ModelsPanel implements SetupPanel {
66
87
  private targetKindOverride;
67
88
  footerHint(): string;
68
89
  /** Body lines only — the caller frames and windows them. */
69
- private renderDimensionPicker;
90
+ private renderChoicePicker;
91
+ private renderRouteForm;
92
+ private renderRouteDelete;
70
93
  /** Fits the panel body into `budget` rows. The wizard draws header/tabs above
71
94
  * and a footer below, so a body taller than its budget pushes the tab strip
72
95
  * off the top of the terminal — the panel must window itself instead. Chrome