@diousk/pi-subagents-fast 0.23.0 → 0.24.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.24.0] - 2026-10-02
11
+
12
+ ### Added
13
+ - **Routing runtime status in `/agents`.** Open Model routing → Runtime status to inspect effective configuration, credential presence, eligible profiles and the latest retained routing decisions, including fallback reasons and actual model/thinking. The refreshable, read-only view hides API keys and makes no classifier request.
14
+
10
15
  ## [0.23.0] - 2026-10-02
11
16
 
12
17
  ### Changed
package/README.md CHANGED
@@ -636,6 +636,8 @@ When background agents complete, they notify the main agent. The **join mode** c
636
636
 
637
637
  Open `/agents → Model routing` to choose a mode and configure a guideline path, models with descriptions and required thinking levels, or a masked TypeSafe API key. The menu shows the active mode and source and saves project settings. For machine-wide defaults, edit `~/.pi/agent/subagents.json`; project overrides go in `.pi/subagents.json`. `PI_CODING_AGENT_DIR` changes the global directory along with Pi's other configuration.
638
638
 
639
+ Use `/agents → Model routing → Runtime status` to inspect the effective mode and its project/global source, routing priority and fallback, guideline path, configured model/thinking profiles, current candidate eligibility, classifier availability and credential presence. No API key is displayed and opening the page does not classify a task. Credential presence does not confirm that the service will accept it. The page also shows the latest ten retained agent decisions, including selected versus actual model/thinking, shadow suggestions, confidence, fallback/error reasons and classifier usage. This is an in-memory view, not a durable log: evicted records and previous sessions are not included. Press `r` to refresh, Up/Down to scroll and Esc to return.
640
+
639
641
  Set one top-level field to control routing; omitting it means `auto`:
640
642
 
641
643
  ```json
@@ -1117,6 +1119,7 @@ src/
1117
1119
  agent-mention.ts # `@` roster (running, resumable, and startable agents) + popup rows
1118
1120
  schedule-menu.ts # /agents → Scheduled jobs submenu
1119
1121
  model-routing-menu.ts # Guideline/model descriptions and masked API-key editor
1122
+ routing-status.ts # Read-only effective routing configuration and retained decisions
1120
1123
  select-item.ts # Collision-safe ctx.ui.select wrapper (numbered rows)
1121
1124
  workflow-card.ts # Inline workflow card (tool result and session entry)
1122
1125
  workflow-dialog.ts # /agents → Workflows two-pane inspector
package/dist/index.js CHANGED
@@ -2752,7 +2752,7 @@ Terse command-style prompts produce shallow, generic work.
2752
2752
  await showAgentsMenu(ctx);
2753
2753
  }
2754
2754
  else if (choice === "Model routing") {
2755
- const patch = await showRoutingMenu(ctx);
2755
+ const patch = await showRoutingMenu(ctx, () => manager.listAgents());
2756
2756
  if (patch) {
2757
2757
  const toast = saveAndEmitChanged({ ...snapshotSettings(), ...patch }, "Model routing settings updated", (event, payload) => pi.events.emit(event, payload), ctx.cwd);
2758
2758
  ctx.ui.notify(toast.message, toast.level);
@@ -44,6 +44,7 @@ export interface RoutingDecision {
44
44
  export declare function loadRoutingPolicy(cwd: string, loadedAgents?: Map<string, AgentConfig>): RoutingPolicy;
45
45
  /** Added in every description mode and refreshed before each main-agent turn. */
46
46
  export declare function routingGuidance(policy: RoutingPolicy): string;
47
+ export declare function eligibleModels(ctx: ExtensionContext): Map<string, Model<Api>>;
47
48
  /** Bounded classifier pool, independent of agent concurrency and nesting. */
48
49
  export declare class ModelRouter {
49
50
  private active;
@@ -67,7 +67,7 @@ export function routingGuidance(policy) {
67
67
  return "Routing mode: shadow. Jev records a suggestion for each fresh delegated task but never changes the model. Choose using the default guidance below, or keep the existing model when no guidance applies.\n" + guidance;
68
68
  return guidance && policy.source !== "jev" ? guidance + "\nJev is inactive under auto mode while this routing source applies." : guidance;
69
69
  }
70
- function eligibleModels(ctx) {
70
+ export function eligibleModels(ctx) {
71
71
  const scope = ctx.scopedModels;
72
72
  const allowed = scope?.length ? new Set(scope.map(entry => `${entry.model.provider}/${entry.model.id}`)) : undefined;
73
73
  const enabled = isScopeModelsEnabled() ? resolveEnabledModels(readEnabledModels(ctx.cwd), ctx.modelRegistry, ctx.cwd) : undefined;
@@ -1,8 +1,9 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { type SubagentsSettings } from "../settings.js";
3
+ import type { AgentRecord } from "../types.js";
3
4
  type RoutingSettings = Pick<SubagentsSettings, "routingMode" | "customGuideline" | "jev">;
4
5
  /** Input handles paste/editing, but its unmasked renderer is never called. */
5
6
  export declare function maskedApiKey(ctx: ExtensionCommandContext): Promise<string | undefined>;
6
7
  /** Simple mode, guideline, models and credentials; saves project overrides. */
7
- export declare function showRoutingMenu(ctx: ExtensionCommandContext): Promise<RoutingSettings | undefined>;
8
+ export declare function showRoutingMenu(ctx: ExtensionCommandContext, records?: () => AgentRecord[]): Promise<RoutingSettings | undefined>;
8
9
  export {};
@@ -2,6 +2,7 @@ import { Input, Text } from "@earendil-works/pi-tui";
2
2
  import { loadRoutingPolicy } from "../model-routing.js";
3
3
  import { parseJevConfig, ROUTING_THINKING_LEVELS } from "../routing-config.js";
4
4
  import { loadSettings, projectRoutingSettings } from "../settings.js";
5
+ import { showRoutingStatus } from "./routing-status.js";
5
6
  /** Input handles paste/editing, but its unmasked renderer is never called. */
6
7
  export async function maskedApiKey(ctx) {
7
8
  return ctx.ui.custom((_tui, _theme, _kb, done) => {
@@ -16,7 +17,7 @@ export async function maskedApiKey(ctx) {
16
17
  });
17
18
  }
18
19
  /** Simple mode, guideline, models and credentials; saves project overrides. */
19
- export async function showRoutingMenu(ctx) {
20
+ export async function showRoutingMenu(ctx, records = () => []) {
20
21
  const settings = loadSettings(ctx.cwd);
21
22
  const local = projectRoutingSettings(ctx.cwd);
22
23
  const policy = loadRoutingPolicy(ctx.cwd);
@@ -28,10 +29,14 @@ export async function showRoutingMenu(ctx) {
28
29
  : policy.mode === "jev" ? `Jev first; fallback: ${labels[policy.fallbackSource ?? "baseline"]}.${policy.jev ? "" : " Configure Jev models and credentials to use Jev."}`
29
30
  : policy.diagnostic ?? inactive;
30
31
  const choice = await ctx.ui.select(`Model routing: ${policy.mode} — ${note || labels[policy.source]}`, [
31
- "Routing mode", "Custom guideline path", "Jev models and descriptions", "Typesafe API key", "Use Pi/environment credentials", "Disable custom guideline", "Disable Jev", "Back",
32
+ "Runtime status", "Routing mode", "Custom guideline path", "Jev models and descriptions", "Typesafe API key", "Use Pi/environment credentials", "Disable custom guideline", "Disable Jev", "Back",
32
33
  ]);
33
34
  if (!choice || choice === "Back")
34
35
  return;
36
+ if (choice === "Runtime status") {
37
+ await showRoutingStatus(ctx, records);
38
+ return;
39
+ }
35
40
  if (choice === "Routing mode") {
36
41
  const modes = [
37
42
  { mode: "auto", label: "auto — default priority: agents, guideline, Jev, existing model" },
@@ -0,0 +1,5 @@
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type { AgentRecord } from "../types.js";
3
+ /** Read current configuration and retained decisions without making a paid classifier request. */
4
+ export declare function routingStatusText(ctx: ExtensionCommandContext, records: readonly AgentRecord[]): Promise<string>;
5
+ export declare function showRoutingStatus(ctx: ExtensionCommandContext, records: () => AgentRecord[]): Promise<void>;
@@ -0,0 +1,113 @@
1
+ import { matchesKey, Text } from "@earendil-works/pi-tui";
2
+ import { eligibleModels, loadRoutingPolicy } from "../model-routing.js";
3
+ import { isScopeModelsEnabled } from "../model-scope.js";
4
+ import { loadRoutingSettings, projectRoutingSettings } from "../settings.js";
5
+ /** Read current configuration and retained decisions without making a paid classifier request. */
6
+ export async function routingStatusText(ctx, records) {
7
+ const { settings, guidelineFile } = loadRoutingSettings(ctx.cwd);
8
+ const local = projectRoutingSettings(ctx.cwd);
9
+ const policy = loadRoutingPolicy(ctx.cwd);
10
+ const config = settings.jev;
11
+ const apiKey = config ? config.TYPESAFE_API_KEY : undefined;
12
+ const available = eligibleModels(ctx);
13
+ const labels = { agents: "Custom agents", guideline: "Custom guideline", jev: "Jev", baseline: "Existing model" };
14
+ const lines = [
15
+ `Directory: ${ctx.cwd}`,
16
+ `Mode: ${policy.mode} (${local.routingMode !== undefined ? "project" : settings.routingMode !== undefined ? "global" : "default"})`,
17
+ `Routing source: ${labels[policy.source]}`,
18
+ `Fallback source: ${labels[policy.fallbackSource ?? (policy.source === "jev" ? "baseline" : policy.source)]}`,
19
+ `Parent model: ${ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "Pi default"}`,
20
+ `Custom agents: ${policy.agents?.map(agent => agent.name).join(", ") || "none active in this policy"}`,
21
+ `Guideline: ${guidelineFile ?? "not configured"}${policy.source !== "guideline" && policy.fallbackSource !== "guideline" ? " (inactive)" : ""}`,
22
+ `Jev config: ${config ? "valid" : config === false ? "disabled or invalid; check subagents.json" : "not configured"} (${local.jev !== undefined ? "project" : settings.jev !== undefined ? "global" : "default"})`,
23
+ `Scope: Pi scoped models ${ctx.scopedModels?.length ? "active" : "unrestricted"}; enabledModels filter ${isScopeModelsEnabled() ? "on" : "off"}`,
24
+ ];
25
+ if (policy.diagnostic)
26
+ lines.push(`Diagnostic: ${policy.diagnostic}`);
27
+ const classifier = ctx.modelRegistry.findOfType("classifier", "typesafe", "jev-latest");
28
+ lines.push(`Jev classifier: ${classifier ? "available" : "unavailable in Pi registry"}`);
29
+ if (apiKey) {
30
+ lines.push("Credentials: configured key present (acceptance checked on next route)");
31
+ }
32
+ else {
33
+ const controller = new AbortController();
34
+ let timer;
35
+ try {
36
+ const availableCredentials = await Promise.race([
37
+ ctx.modelRegistry.getAvailableOfType("classifier", "typesafe", { signal: controller.signal }),
38
+ new Promise(resolve => { timer = setTimeout(() => { controller.abort(); resolve(undefined); }, 2000); }),
39
+ ]);
40
+ lines.push(`Credentials: ${availableCredentials === undefined ? "check timed out" : availableCredentials.some(model => model.id === "jev-latest") ? "Pi/environment credentials available (acceptance checked on next route)" : "missing Pi/environment credentials"}`);
41
+ }
42
+ catch {
43
+ lines.push("Credentials: availability check failed");
44
+ }
45
+ finally {
46
+ clearTimeout(timer);
47
+ }
48
+ }
49
+ lines.push("", "Configured profiles:");
50
+ if (config) {
51
+ for (const profile of config.models) {
52
+ lines.push(`${profile.model} | thinking: ${profile.thinkingLevel} | ${available.has(profile.model) ? "eligible" : "unavailable or excluded by scope"}`, ` ${profile.description}`);
53
+ }
54
+ }
55
+ else
56
+ lines.push("None. Every profile requires model, description and thinkingLevel.");
57
+ lines.push("", "Recent retained agents (latest 10; includes nested/workflow agents):");
58
+ const recent = [...records].filter(record => record.routing).sort((a, b) => b.startedAt - a.startedAt).slice(0, 10);
59
+ if (!recent.length)
60
+ lines.push("No routing decisions yet. Start a fresh subagent; resumed agents do not reroute.");
61
+ for (const record of recent) {
62
+ const route = record.routing;
63
+ lines.push(`${new Date(record.startedAt).toISOString()} | ${record.id} | ${record.status}`, ` Mode: ${route.mode} | Source: ${route.source} | Result: ${route.code}`, ` ${route.reason}`);
64
+ if (route.model)
65
+ lines.push(` Selected: ${route.model} | requested thinking: ${route.thinkingLevel ?? "inherited"}`);
66
+ if (route.suggestedModel)
67
+ lines.push(` Shadow suggestion: ${route.suggestedModel} | thinking: ${route.suggestedThinkingLevel ?? "inherited"}`);
68
+ if (record.invocation?.modelId)
69
+ lines.push(` Actual: ${record.invocation.modelId} | effective thinking: ${record.invocation.thinking ?? "unknown"}`);
70
+ if (route.confidence !== undefined)
71
+ lines.push(` Confidence: ${(route.confidence * 100).toFixed(1)}%`);
72
+ if (record.routingUsage)
73
+ lines.push(` Classifier tokens: ${record.routingUsage.totalTokens} | ${route.unpriced ? "price unavailable" : `reported cost: $${record.routingUsage.cost.total}`}`);
74
+ }
75
+ lines.push("", "Snapshot only. Refresh to reload settings and retained decisions. Opening this page does not classify a task.");
76
+ const text = lines.join("\n");
77
+ return apiKey ? text.replaceAll(apiKey, "[redacted]") : text;
78
+ }
79
+ export async function showRoutingStatus(ctx, records) {
80
+ let refresh = true;
81
+ while (refresh) {
82
+ const content = await routingStatusText(ctx, records());
83
+ refresh = await ctx.ui.custom((tui, _theme, _kb, done) => {
84
+ let offset = 0;
85
+ let maxOffset = 0;
86
+ return {
87
+ render(width) {
88
+ const rows = new Text(content, 0, 0).render(width);
89
+ const height = Math.max(1, tui.terminal.rows - 6);
90
+ maxOffset = Math.max(0, rows.length - height);
91
+ offset = Math.min(offset, maxOffset);
92
+ return [...new Text("Routing runtime status | Up/Down: scroll | r: refresh | Esc: back", 0, 0).render(width), ...rows.slice(offset, offset + height)];
93
+ },
94
+ invalidate() { },
95
+ handleInput(data) {
96
+ if (matchesKey(data, "escape") || matchesKey(data, "q")) {
97
+ done(false);
98
+ return;
99
+ }
100
+ if (matchesKey(data, "r")) {
101
+ done(true);
102
+ return;
103
+ }
104
+ if (matchesKey(data, "up"))
105
+ offset = Math.max(0, offset - 1);
106
+ if (matchesKey(data, "down"))
107
+ offset = Math.min(maxOffset, offset + 1);
108
+ tui.requestRender();
109
+ },
110
+ };
111
+ });
112
+ }
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diousk/pi-subagents-fast",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "A pi extension that brings Claude Code-like sub-agents and workflow orchestration to pi — parallel execution, live widget, fleet view, custom agent types, mid-run steering, dynamic workflows, Claude Code compatibility, look and feel.",
5
5
  "author": "tintinweb",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -2994,7 +2994,7 @@ Terse command-style prompts produce shallow, generic work.
2994
2994
  await showSettings(ctx);
2995
2995
  await showAgentsMenu(ctx);
2996
2996
  } else if (choice === "Model routing") {
2997
- const patch = await showRoutingMenu(ctx);
2997
+ const patch = await showRoutingMenu(ctx, () => manager.listAgents());
2998
2998
  if (patch) {
2999
2999
  const toast = saveAndEmitChanged({ ...snapshotSettings(), ...patch }, "Model routing settings updated", (event, payload) => pi.events.emit(event, payload), ctx.cwd);
3000
3000
  ctx.ui.notify(toast.message, toast.level);
@@ -102,7 +102,7 @@ export function routingGuidance(policy: RoutingPolicy): string {
102
102
  return guidance && policy.source !== "jev" ? guidance + "\nJev is inactive under auto mode while this routing source applies." : guidance;
103
103
  }
104
104
 
105
- function eligibleModels(ctx: ExtensionContext): Map<string, Model<Api>> {
105
+ export function eligibleModels(ctx: ExtensionContext): Map<string, Model<Api>> {
106
106
  const scope = ctx.scopedModels;
107
107
  const allowed = scope?.length ? new Set(scope.map(entry => `${entry.model.provider}/${entry.model.id}`)) : undefined;
108
108
  const enabled = isScopeModelsEnabled() ? resolveEnabledModels(readEnabledModels(ctx.cwd), ctx.modelRegistry, ctx.cwd) : undefined;
@@ -3,6 +3,8 @@ import { Input, Text } from "@earendil-works/pi-tui";
3
3
  import { loadRoutingPolicy } from "../model-routing.js";
4
4
  import { parseJevConfig, ROUTING_THINKING_LEVELS, type RoutingMode } from "../routing-config.js";
5
5
  import { loadSettings, projectRoutingSettings, type SubagentsSettings } from "../settings.js";
6
+ import type { AgentRecord } from "../types.js";
7
+ import { showRoutingStatus } from "./routing-status.js";
6
8
 
7
9
  type RoutingSettings = Pick<SubagentsSettings, "routingMode" | "customGuideline" | "jev">;
8
10
 
@@ -21,7 +23,7 @@ export async function maskedApiKey(ctx: ExtensionCommandContext): Promise<string
21
23
  }
22
24
 
23
25
  /** Simple mode, guideline, models and credentials; saves project overrides. */
24
- export async function showRoutingMenu(ctx: ExtensionCommandContext): Promise<RoutingSettings | undefined> {
26
+ export async function showRoutingMenu(ctx: ExtensionCommandContext, records: () => AgentRecord[] = () => []): Promise<RoutingSettings | undefined> {
25
27
  const settings = loadSettings(ctx.cwd);
26
28
  const local = projectRoutingSettings(ctx.cwd);
27
29
  const policy = loadRoutingPolicy(ctx.cwd);
@@ -33,9 +35,13 @@ export async function showRoutingMenu(ctx: ExtensionCommandContext): Promise<Rou
33
35
  : policy.mode === "jev" ? `Jev first; fallback: ${labels[policy.fallbackSource ?? "baseline"]}.${policy.jev ? "" : " Configure Jev models and credentials to use Jev."}`
34
36
  : policy.diagnostic ?? inactive;
35
37
  const choice = await ctx.ui.select(`Model routing: ${policy.mode} — ${note || labels[policy.source]}`, [
36
- "Routing mode", "Custom guideline path", "Jev models and descriptions", "Typesafe API key", "Use Pi/environment credentials", "Disable custom guideline", "Disable Jev", "Back",
38
+ "Runtime status", "Routing mode", "Custom guideline path", "Jev models and descriptions", "Typesafe API key", "Use Pi/environment credentials", "Disable custom guideline", "Disable Jev", "Back",
37
39
  ]);
38
40
  if (!choice || choice === "Back") return;
41
+ if (choice === "Runtime status") {
42
+ await showRoutingStatus(ctx, records);
43
+ return;
44
+ }
39
45
  if (choice === "Routing mode") {
40
46
  const modes: { mode: RoutingMode; label: string }[] = [
41
47
  { mode: "auto", label: "auto — default priority: agents, guideline, Jev, existing model" },
@@ -0,0 +1,97 @@
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import { matchesKey, Text } from "@earendil-works/pi-tui";
3
+ import { eligibleModels, loadRoutingPolicy } from "../model-routing.js";
4
+ import { isScopeModelsEnabled } from "../model-scope.js";
5
+ import { loadRoutingSettings, projectRoutingSettings } from "../settings.js";
6
+ import type { AgentRecord } from "../types.js";
7
+
8
+ /** Read current configuration and retained decisions without making a paid classifier request. */
9
+ export async function routingStatusText(ctx: ExtensionCommandContext, records: readonly AgentRecord[]): Promise<string> {
10
+ const { settings, guidelineFile } = loadRoutingSettings(ctx.cwd);
11
+ const local = projectRoutingSettings(ctx.cwd);
12
+ const policy = loadRoutingPolicy(ctx.cwd);
13
+ const config = settings.jev;
14
+ const apiKey = config ? config.TYPESAFE_API_KEY : undefined;
15
+ const available = eligibleModels(ctx);
16
+ const labels = { agents: "Custom agents", guideline: "Custom guideline", jev: "Jev", baseline: "Existing model" };
17
+ const lines = [
18
+ `Directory: ${ctx.cwd}`,
19
+ `Mode: ${policy.mode} (${local.routingMode !== undefined ? "project" : settings.routingMode !== undefined ? "global" : "default"})`,
20
+ `Routing source: ${labels[policy.source]}`,
21
+ `Fallback source: ${labels[policy.fallbackSource ?? (policy.source === "jev" ? "baseline" : policy.source)]}`,
22
+ `Parent model: ${ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "Pi default"}`,
23
+ `Custom agents: ${policy.agents?.map(agent => agent.name).join(", ") || "none active in this policy"}`,
24
+ `Guideline: ${guidelineFile ?? "not configured"}${policy.source !== "guideline" && policy.fallbackSource !== "guideline" ? " (inactive)" : ""}`,
25
+ `Jev config: ${config ? "valid" : config === false ? "disabled or invalid; check subagents.json" : "not configured"} (${local.jev !== undefined ? "project" : settings.jev !== undefined ? "global" : "default"})`,
26
+ `Scope: Pi scoped models ${ctx.scopedModels?.length ? "active" : "unrestricted"}; enabledModels filter ${isScopeModelsEnabled() ? "on" : "off"}`,
27
+ ];
28
+ if (policy.diagnostic) lines.push(`Diagnostic: ${policy.diagnostic}`);
29
+ const classifier = ctx.modelRegistry.findOfType("classifier", "typesafe", "jev-latest");
30
+ lines.push(`Jev classifier: ${classifier ? "available" : "unavailable in Pi registry"}`);
31
+ if (apiKey) {
32
+ lines.push("Credentials: configured key present (acceptance checked on next route)");
33
+ } else {
34
+ const controller = new AbortController();
35
+ let timer: ReturnType<typeof setTimeout> | undefined;
36
+ try {
37
+ const availableCredentials = await Promise.race([
38
+ ctx.modelRegistry.getAvailableOfType("classifier", "typesafe", { signal: controller.signal }),
39
+ new Promise<undefined>(resolve => { timer = setTimeout(() => { controller.abort(); resolve(undefined); }, 2000); }),
40
+ ]);
41
+ lines.push(`Credentials: ${availableCredentials === undefined ? "check timed out" : availableCredentials.some(model => model.id === "jev-latest") ? "Pi/environment credentials available (acceptance checked on next route)" : "missing Pi/environment credentials"}`);
42
+ } catch {
43
+ lines.push("Credentials: availability check failed");
44
+ } finally { clearTimeout(timer); }
45
+ }
46
+ lines.push("", "Configured profiles:");
47
+ if (config) {
48
+ for (const profile of config.models) {
49
+ lines.push(`${profile.model} | thinking: ${profile.thinkingLevel} | ${available.has(profile.model) ? "eligible" : "unavailable or excluded by scope"}`, ` ${profile.description}`);
50
+ }
51
+ } else lines.push("None. Every profile requires model, description and thinkingLevel.");
52
+ lines.push("", "Recent retained agents (latest 10; includes nested/workflow agents):");
53
+ const recent = [...records].filter(record => record.routing).sort((a, b) => b.startedAt - a.startedAt).slice(0, 10);
54
+ if (!recent.length) lines.push("No routing decisions yet. Start a fresh subagent; resumed agents do not reroute.");
55
+ for (const record of recent) {
56
+ const route = record.routing!;
57
+ lines.push(`${new Date(record.startedAt).toISOString()} | ${record.id} | ${record.status}`,
58
+ ` Mode: ${route.mode} | Source: ${route.source} | Result: ${route.code}`,
59
+ ` ${route.reason}`);
60
+ if (route.model) lines.push(` Selected: ${route.model} | requested thinking: ${route.thinkingLevel ?? "inherited"}`);
61
+ if (route.suggestedModel) lines.push(` Shadow suggestion: ${route.suggestedModel} | thinking: ${route.suggestedThinkingLevel ?? "inherited"}`);
62
+ if (record.invocation?.modelId) lines.push(` Actual: ${record.invocation.modelId} | effective thinking: ${record.invocation.thinking ?? "unknown"}`);
63
+ if (route.confidence !== undefined) lines.push(` Confidence: ${(route.confidence * 100).toFixed(1)}%`);
64
+ if (record.routingUsage) lines.push(` Classifier tokens: ${record.routingUsage.totalTokens} | ${route.unpriced ? "price unavailable" : `reported cost: $${record.routingUsage.cost.total}`}`);
65
+ }
66
+ lines.push("", "Snapshot only. Refresh to reload settings and retained decisions. Opening this page does not classify a task.");
67
+ const text = lines.join("\n");
68
+ return apiKey ? text.replaceAll(apiKey, "[redacted]") : text;
69
+ }
70
+
71
+ export async function showRoutingStatus(ctx: ExtensionCommandContext, records: () => AgentRecord[]): Promise<void> {
72
+ let refresh = true;
73
+ while (refresh) {
74
+ const content = await routingStatusText(ctx, records());
75
+ refresh = await ctx.ui.custom<boolean>((tui, _theme, _kb, done) => {
76
+ let offset = 0;
77
+ let maxOffset = 0;
78
+ return {
79
+ render(width: number) {
80
+ const rows = new Text(content, 0, 0).render(width);
81
+ const height = Math.max(1, tui.terminal.rows - 6);
82
+ maxOffset = Math.max(0, rows.length - height);
83
+ offset = Math.min(offset, maxOffset);
84
+ return [...new Text("Routing runtime status | Up/Down: scroll | r: refresh | Esc: back", 0, 0).render(width), ...rows.slice(offset, offset + height)];
85
+ },
86
+ invalidate() {},
87
+ handleInput(data: string) {
88
+ if (matchesKey(data, "escape") || matchesKey(data, "q")) { done(false); return; }
89
+ if (matchesKey(data, "r")) { done(true); return; }
90
+ if (matchesKey(data, "up")) offset = Math.max(0, offset - 1);
91
+ if (matchesKey(data, "down")) offset = Math.min(maxOffset, offset + 1);
92
+ tui.requestRender();
93
+ },
94
+ };
95
+ });
96
+ }
97
+ }