@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
@@ -15,6 +15,8 @@ export interface StatusDTO {
15
15
  tick_interval_ms: number;
16
16
  /** Node counts keyed by status. */
17
17
  node_counts: Partial<Record<NodeStatusDTO, number>>;
18
+ /** Nodes genuinely mid-turn: their busy marker exists and their recorded pid is alive. Unlike `node_counts.active`, this excludes live-but-waiting, parked, or blocked engines. */
19
+ busy_node_count: number;
18
20
  /** Bound listener addresses. `tcp` is null when the TCP listener is off. */
19
21
  listeners: {
20
22
  socket_path: string;
@@ -18,7 +18,7 @@ A doc with no `surfaces` does exactly one thing: appears in its directory's list
18
18
  - **boot** — the frozen preference system snapshot and first-message knowledge catalog assembled when a context begins. No match; the entry's presence is the match.
19
19
  - **workspace-open** — first-message context when cwd/profile mounts the doc's project store. Project stores only.
20
20
  - **read** — a `read` tool call returned a matching file: path 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.
21
- - **memory-read** — a `crtr memory read` resolved a matching doc: name globs vs its canonical name, `./` anchored to this doc's canonical routing anchor (a collapsed directory document anchors at its own directory name).
21
+ - **memory-read** — another doc's full body entered context, including `crtr memory read`: name globs vs its canonical name, `./` anchored to this doc's canonical routing anchor (a collapsed directory document anchors at its own directory name).
22
22
  - **command** — a matching shell command ran: globs vs the whole command string, `*` crossing `/`. Delivery is post-execution — right for "you are now in this territory"; when the point is "don't run this at all," use `pre-command` instead.
23
23
  - **pre-command** — a matching `bash` command is about to run. 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. Same globs as `command`, matched at command position; `match` is required. A guardrail against the faithful-but-uninformed action, not an enforcement boundary.
24
24
 
@@ -40,9 +40,9 @@ A document-level `gate` is the hard eligibility predicate over the node's own co
40
40
 
41
41
  ## Listings and dedup
42
42
 
43
- Reading a doc discloses where it sits: `crtr memory read <doc>` also renders, once per loaded context, the listing of the doc's directory and each ancestor — one routing line per member doc, one bare name per subdirectory. A directory document owns its canonical directory node, so `crtr memory read <dir>` (or a bare-dir `[[ref]]`) returns that document's body followed by the directory's immediate listing without repeating the document; a directory without a document returns its listing. `unlisted: true` suppresses a doc from every listing. Store roots are never auto-listed — `crtr memory list` is the deliberate root browse.
43
+ A full-body delivery is a read: `crtr memory read <doc>` and every content-rung surface delivery expose, once per loaded context, the listing of the doc's directory and each ancestor — one routing line per member doc, one bare name per subdirectory — then fire matching `memory-read` routes. A directory document owns its canonical directory node, so its content delivery exposes its immediate members; `crtr memory read <dir>` returns that document's body followed by the directory's immediate listing without repeating the document, while a directory without a document returns its listing. Content-delivered companions are read in turn until the finite corpus reaches the ledger-deduplicated fixpoint. `unlisted: true` suppresses a doc from every listing. Store roots are never auto-listed — `crtr memory list` is the deliberate root browse.
44
44
 
45
- Every delivery registers its document or listing identity and rung in the loaded context's exposure ledger. System and transcript ranks max-fold, so a higher rung pierces a lower one while a content delivery silences later lower-rung deliveries. A strict resume preserves that ledger and the byte-stable preference snapshot; yield or another new-context boundary replaces them with exactly the new system and first-message snapshots.
45
+ Every delivery registers its document or listing identity and rung in the loaded context's exposure ledger. System and transcript ranks max-fold, so a higher rung pierces a lower one while a content delivery silences later lower-rung deliveries and duplicate read attachments. A strict resume preserves that ledger and the byte-stable preference snapshot; yield or another new-context boundary replaces them with exactly the new system and first-message snapshots.
46
46
 
47
47
  ## Store mounting and precedence
48
48
 
@@ -1,7 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
3
- import { streamSimple as streamAnthropicMessages } from '@earendil-works/pi-ai/api/anthropic-messages';
4
- import { streamSimple as streamOpenAICodexResponses } from '@earendil-works/pi-ai/api/openai-codex-responses';
3
+ import { anthropicMessagesApi, openAICodexResponsesApi } from '@earendil-works/pi-ai/compat';
5
4
  import { ANTHROPIC_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID, INVALID_REFRESH_TOKEN_BACKOFF_MS, formatStatusLine, getProviderLabel, isManagedProvider, isInvalidRefreshTokenError, addSubscription, enrichManagedLoginIdentity, markSubscriptionAuthenticationFailed, markSubscriptionRateLimited, markSubscriptionSuccess, parseRetryAfterHeader, promoteSubscription, readRotationConfig, readSubscriptionPool, refreshSubscriptionCredential, removeManagedAccount, resolveProviderCandidates, upsertSubscriptionWithUniqueAccount, } from '../../../core/subscription-state.js';
6
5
  import { readMergedLaunchConfig } from '../../../core/config.js';
7
6
  import { expandModelCandidates, modelRequestFromConfig, probeRouteAvailability } from '../../../core/model-routes.js';
@@ -24,6 +23,8 @@ const TRANSIENT_ROTATE_COOLDOWN_MS = 60 * 1000;
24
23
  // remains busy, the provider call still proceeds. Subscription bookkeeping must never turn a
25
24
  // healthy model response into a fatal generation fault.
26
25
  const SUBSCRIPTION_LOCK_RETRY_BACKOFFS_MS = [25, 75, 150];
26
+ const { streamSimple: streamAnthropicMessages } = anthropicMessagesApi();
27
+ const { streamSimple: streamOpenAICodexResponses } = openAICodexResponsesApi();
27
28
  function createProviderRotationState(options) {
28
29
  return { delegatedProviderStreams: new Map(), transportOnlyExhaustedPasses: 0, ...(options === undefined ? {} : { options: { ...options } }) };
29
30
  }
@@ -758,7 +759,7 @@ function parseProvider(value) {
758
759
  }
759
760
  function installDeclaredRouteProviders(state, pi, ctx) {
760
761
  const config = readMergedLaunchConfig(ctx.cwd, configuredValue(state, 'profileId', 'CRTR_PROFILE_ID') || null);
761
- const providerIds = new Set(Object.values(config.modelRoutes ?? {}).map((route) => route.providerId));
762
+ const providerIds = new Set(Object.values({ ...(config.modelRoutes ?? {}), ...(config.legacyModelRoutes ?? {}) }).map((route) => route.providerId));
762
763
  for (const providerId of providerIds) {
763
764
  if (isManagedProvider(providerId) || state.delegatedProviderStreams.has(providerId))
764
765
  continue;
@@ -12,8 +12,7 @@ import {
12
12
  type OAuthAuth,
13
13
  type SimpleStreamOptions,
14
14
  } from '@earendil-works/pi-ai';
15
- import { streamSimple as streamAnthropicMessages } from '@earendil-works/pi-ai/api/anthropic-messages';
16
- import { streamSimple as streamOpenAICodexResponses } from '@earendil-works/pi-ai/api/openai-codex-responses';
15
+ import { anthropicMessagesApi, openAICodexResponsesApi } from '@earendil-works/pi-ai/compat';
17
16
  import {
18
17
  ANTHROPIC_PROVIDER_ID,
19
18
  OPENAI_CODEX_PROVIDER_ID,
@@ -67,6 +66,9 @@ const SUBSCRIPTION_LOCK_RETRY_BACKOFFS_MS = [25, 75, 150];
67
66
  // installed would recurse back into the route driver.
68
67
  type ProviderStream = (model: Model<any>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
69
68
 
69
+ const { streamSimple: streamAnthropicMessages } = anthropicMessagesApi();
70
+ const { streamSimple: streamOpenAICodexResponses } = openAICodexResponsesApi();
71
+
70
72
  export interface ProviderRotationOptions {
71
73
  modelIntent?: string;
72
74
  nodeCwd?: string;
@@ -936,7 +938,7 @@ function parseProvider(value: string | undefined): ManagedProviderId | undefined
936
938
 
937
939
  function installDeclaredRouteProviders(state: ProviderRotationState, pi: ExtensionAPI, ctx: ExtensionContext): void {
938
940
  const config = readMergedLaunchConfig(ctx.cwd, configuredValue(state, 'profileId', 'CRTR_PROFILE_ID') || null);
939
- const providerIds = new Set(Object.values(config.modelRoutes ?? {}).map((route) => route.providerId));
941
+ const providerIds = new Set(Object.values({ ...(config.modelRoutes ?? {}), ...(config.legacyModelRoutes ?? {}) }).map((route) => route.providerId));
940
942
  for (const providerId of providerIds) {
941
943
  if (isManagedProvider(providerId) || state.delegatedProviderStreams.has(providerId)) continue;
942
944
  const provider = ctx.modelRegistry.getProvider?.(providerId);
@@ -53,6 +53,10 @@ export declare class InputController {
53
53
  constructor(tui: TUI, editor: CustomEditor, keybindings: KeybindingsManager, hooks: InputControllerHooks, capabilities: ViewerCapabilities);
54
54
  cycleLadder(direction: 'forward' | 'backward'): void;
55
55
  sendInterrupting(text: string): boolean;
56
+ /** Cancel the running turn without sending a message — the ctrl+c "stop the
57
+ * agent" chord. Mirrors Esc's abort semantics. Returns false when nothing is
58
+ * streaming to cancel, so the caller can hint at detach instead. */
59
+ abortTurn(): boolean;
56
60
  attachDialog(request: RpcExtensionUIRequest): void;
57
61
  dismissDialog(id: string): void;
58
62
  setState(state: BrokerSnapshot['state']): void;
@@ -48,6 +48,14 @@ export class InputController {
48
48
  sendInterrupting(text) {
49
49
  return this.emitDrive({ type: 'prompt', text, interrupt: true });
50
50
  }
51
+ /** Cancel the running turn without sending a message — the ctrl+c "stop the
52
+ * agent" chord. Mirrors Esc's abort semantics. Returns false when nothing is
53
+ * streaming to cancel, so the caller can hint at detach instead. */
54
+ abortTurn() {
55
+ if (this.state?.isStreaming !== true)
56
+ return false;
57
+ return this.emitCommand({ type: 'abort' });
58
+ }
51
59
  attachDialog(request) {
52
60
  this.overlays.attachDialog(request);
53
61
  }
@@ -11,6 +11,9 @@ export declare function dispatchAttachAction(id: AttachBindingId, actions: Attac
11
11
  export declare function dispatchAttachBinding(bindings: AttachBindings, data: string, actions: AttachActions): boolean;
12
12
  export declare function dispatchAttachViewportBinding(bindings: AttachBindings, data: string, actions: AttachViewportActions): boolean;
13
13
  export declare function detachHint(bindings: AttachBindings): string | undefined;
14
+ /** The dedicated detach binding's spelling (ctrl+d), for pointing the user at
15
+ * detach now that a second ctrl+c cancels the running turn instead. */
16
+ export declare function detachBindingHint(bindings: AttachBindings): string | undefined;
14
17
  /** Re-resolve the user's binding settings once and republish the same snapshot
15
18
  * to every consumer: attach dispatch, the graph overlay, and tmux. Binding
16
19
  * diagnostics must never fail an attach, so an install error is swallowed. */
@@ -48,6 +48,13 @@ export function detachHint(bindings) {
48
48
  }
49
49
  return undefined;
50
50
  }
51
+ /** The dedicated detach binding's spelling (ctrl+d), for pointing the user at
52
+ * detach now that a second ctrl+c cancels the running turn instead. */
53
+ export function detachBindingHint(bindings) {
54
+ return bindings.gestures('crtr.attach.detach').length > 0
55
+ ? formatBinding(bindings, 'crtr.attach.detach')
56
+ : undefined;
57
+ }
51
58
  /** Re-resolve the user's binding settings once and republish the same snapshot
52
59
  * to every consumer: attach dispatch, the graph overlay, and tmux. Binding
53
60
  * diagnostics must never fail an attach, so an install error is swallowed. */
@@ -23,6 +23,8 @@ export interface KeyHooks {
23
23
  rosterHandleKey: (data: string) => boolean;
24
24
  setNotice: (message: string) => void;
25
25
  detach: () => void;
26
+ /** Cancel the running turn (the second ctrl+c). Detach lives on its own binding. */
27
+ cancelTurn: () => void;
26
28
  toggleGraph: () => void;
27
29
  toggleHelp: () => void;
28
30
  /** Toggle the mounted inbox surface. */
@@ -10,7 +10,7 @@
10
10
  import { extractAttachControlInput } from '../../../core/keybindings/index.js';
11
11
  import { dispatchAttachAction, dispatchAttachBinding, dispatchAttachViewportBinding, } from './bindings.js';
12
12
  import { FOCUS_IN, FOCUS_OUT } from './focus-events.js';
13
- /** Press the clear-or-detach binding twice inside this window to detach. */
13
+ /** Press the clear binding twice inside this window to cancel the running turn. */
14
14
  const CLEAR_OR_DETACH_MS = 1000;
15
15
  /** Install the listener; returns the remover, which teardown MUST call. */
16
16
  export function installGlobalKeys(ctx, hooks) {
@@ -22,12 +22,12 @@ export function installGlobalKeys(ctx, hooks) {
22
22
  const now = Date.now();
23
23
  if (now - lastClearOrDetachAt < CLEAR_OR_DETACH_MS) {
24
24
  lastClearOrDetachAt = 0;
25
- hooks.detach();
25
+ hooks.cancelTurn();
26
26
  return;
27
27
  }
28
28
  lastClearOrDetachAt = now;
29
29
  editor.setText('');
30
- hooks.setNotice(`Editor cleared — press ${hooks.detachHint()} again to detach`);
30
+ hooks.setNotice(`Editor cleared — press ${hooks.detachHint()} again to cancel the agent`);
31
31
  tui.requestRender();
32
32
  },
33
33
  'crtr.attach.graph.toggle': hooks.toggleGraph,