ultimate-pi 0.16.0 → 0.17.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.
Files changed (42) hide show
  1. package/.pi/agents/harness/planning/hypothesis.md +1 -1
  2. package/.pi/agents/harness/planning/implementation-researcher.md +1 -1
  3. package/.pi/extensions/harness-debate-tools.ts +12 -3
  4. package/.pi/extensions/harness-run-context.ts +12 -0
  5. package/.pi/extensions/harness-subagent-submit.ts +2 -25
  6. package/.pi/extensions/harness-telemetry.ts +29 -4
  7. package/.pi/extensions/lib/debate-bus-core.ts +15 -9
  8. package/.pi/extensions/lib/harness-subagent-auth.ts +104 -19
  9. package/.pi/extensions/lib/harness-subagent-policy.ts +14 -0
  10. package/.pi/extensions/lib/harness-subagents-bridge.ts +85 -0
  11. package/.pi/extensions/lib/plan-debate-eligibility.ts +61 -8
  12. package/.pi/extensions/lib/plan-debate-focus.ts +21 -9
  13. package/.pi/extensions/lib/plan-debate-gate.ts +80 -17
  14. package/.pi/extensions/lib/plan-debate-lanes.ts +27 -3
  15. package/.pi/extensions/lib/plan-debate-round-status.ts +18 -7
  16. package/.pi/extensions/lib/plan-messenger.ts +4 -0
  17. package/.pi/extensions/lib/plan-review-gate.ts +51 -0
  18. package/.pi/extensions/trace-recorder.ts +1 -0
  19. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/implementation-research.yaml +28 -0
  20. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/artifacts/review-round-consolidated.yaml +25 -0
  21. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-packet.yaml +196 -0
  22. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/plan-review.md +14 -0
  23. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med-fast/research-brief.yaml +62 -0
  24. package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +40 -17
  25. package/.pi/harness/specs/plan-review-round-draft.schema.json +1 -1
  26. package/.pi/model-router.example.json +13 -4
  27. package/.pi/prompts/harness-plan.md +25 -7
  28. package/.pi/prompts/harness-setup.md +4 -4
  29. package/.pi/scripts/harness-generate-model-router.mjs +118 -36
  30. package/.pi/scripts/harness-model-router-routing.test.mjs +97 -0
  31. package/.pi/scripts/harness-sync-model-router.mjs +15 -2
  32. package/.pi/scripts/harness-verify.mjs +29 -0
  33. package/CHANGELOG.md +11 -0
  34. package/package.json +1 -1
  35. package/vendor/pi-model-router/UPSTREAM_PIN.md +3 -1
  36. package/vendor/pi-model-router/extensions/commands.ts +4 -4
  37. package/vendor/pi-model-router/extensions/index.ts +21 -0
  38. package/vendor/pi-model-router/extensions/provider.ts +130 -79
  39. package/vendor/pi-model-router/extensions/routing.ts +148 -0
  40. package/vendor/pi-model-router/extensions/state.ts +3 -0
  41. package/vendor/pi-model-router/extensions/types.ts +9 -0
  42. package/vendor/pi-model-router/extensions/ui.ts +16 -2
@@ -19,15 +19,21 @@ import type {
19
19
  RouterTier,
20
20
  RouterPinByProfile,
21
21
  RouterThinkingByProfile,
22
+ SessionLock,
22
23
  } from './types.js';
23
24
  import { profileNames, parseCanonicalModelRef, ROUTER_TIERS } from './config.js';
24
25
  import {
25
26
  phaseForTier,
26
27
  buildRoutingDecision,
27
28
  decideRouting,
29
+ decideSessionLock,
28
30
  runClassifier,
29
31
  extractTextFromContent,
30
32
  hasImageAttachment,
33
+ buildSessionLockContext,
34
+ sessionLockToRoutingDecision,
35
+ routingDecisionToSessionLock,
36
+ applyThinkingToDecision,
31
37
  } from './routing.js';
32
38
 
33
39
  export const createErrorMessage = (
@@ -138,6 +144,8 @@ export const registerRouterProvider = (
138
144
  readonly thinkingByProfile: RouterThinkingByProfile;
139
145
  readonly pinnedTierByProfile: RouterPinByProfile;
140
146
  accumulatedCost: number;
147
+ get sessionLock(): SessionLock | undefined;
148
+ set sessionLock(v: SessionLock | undefined);
141
149
  },
142
150
  actions: {
143
151
  persistState: () => void;
@@ -217,26 +225,111 @@ export const registerRouterProvider = (
217
225
  state.routerEnabled = true;
218
226
 
219
227
  const pinnedTier = state.pinnedTierByProfile[model.id];
228
+ const thinkingOverrides = state.thinkingByProfile[model.id];
220
229
  const isBudgetExceeded =
221
230
  state.currentConfig.maxSessionBudget !== undefined &&
222
231
  state.accumulatedCost >= state.currentConfig.maxSessionBudget;
223
232
 
224
- let decision: RoutingDecision = decideRouting(
233
+ const checkModelSupportsImage = (modelRef: string) => {
234
+ try {
235
+ const { provider, modelId } = parseCanonicalModelRef(modelRef);
236
+ const m = state.currentModelRegistry?.find(provider, modelId);
237
+ return m?.input?.includes('image') ?? false;
238
+ } catch {
239
+ return false;
240
+ }
241
+ };
242
+
243
+ const pickVisionCapableLock = (
244
+ lockDecision: RoutingDecision,
245
+ ): RoutingDecision => {
246
+ if (!hasImageAttachment(context)) return lockDecision;
247
+ const candidates = [
248
+ lockDecision.targetLabel,
249
+ ...(profile[lockDecision.tier].fallbacks ?? []),
250
+ ];
251
+ if (candidates.some(checkModelSupportsImage)) return lockDecision;
252
+ for (const tier of ROUTER_TIERS) {
253
+ const refs = [profile[tier].model, ...(profile[tier].fallbacks ?? [])];
254
+ const visionRef = refs.find(checkModelSupportsImage);
255
+ if (visionRef) {
256
+ const { provider, modelId } = parseCanonicalModelRef(visionRef);
257
+ return {
258
+ ...lockDecision,
259
+ tier,
260
+ targetLabel: visionRef,
261
+ targetProvider: provider,
262
+ targetModelId: modelId,
263
+ reasoning: `${lockDecision.reasoning} | Vision-capable model for image input.`,
264
+ };
265
+ }
266
+ }
267
+ return lockDecision;
268
+ };
269
+
270
+ let lock = state.sessionLock;
271
+ if (!lock || lock.profile !== model.id) {
272
+ const lockContext = buildSessionLockContext(context);
273
+ let lockDecision = decideSessionLock(
274
+ lockContext,
275
+ model.id,
276
+ profile,
277
+ pinnedTier,
278
+ thinkingOverrides,
279
+ state.currentConfig.phaseBias,
280
+ state.currentConfig.rules,
281
+ );
282
+
283
+ if (
284
+ state.currentConfig.classifierModel &&
285
+ !pinnedTier &&
286
+ !lockDecision.isRuleMatched
287
+ ) {
288
+ const classifierResult = await runClassifier(
289
+ state.currentConfig.classifierModel,
290
+ state.currentModelRegistry,
291
+ lockContext,
292
+ undefined,
293
+ );
294
+ if (classifierResult) {
295
+ lockDecision = buildRoutingDecision(
296
+ model.id,
297
+ profile,
298
+ classifierResult.tier,
299
+ phaseForTier(classifierResult.tier),
300
+ `Session lock classifier: ${classifierResult.reasoning}`,
301
+ thinkingOverrides,
302
+ true,
303
+ );
304
+ }
305
+ }
306
+
307
+ lockDecision = pickVisionCapableLock(lockDecision);
308
+ lock = routingDecisionToSessionLock(lockDecision);
309
+ state.sessionLock = lock;
310
+ }
311
+
312
+ const lockedBase = sessionLockToRoutingDecision(
313
+ lock,
314
+ profile,
315
+ thinkingOverrides,
316
+ );
317
+
318
+ let thinkingDecision: RoutingDecision = decideRouting(
225
319
  context,
226
320
  model.id,
227
321
  profile,
228
322
  state.lastDecision,
229
323
  pinnedTier,
230
- state.thinkingByProfile[model.id],
324
+ thinkingOverrides,
231
325
  state.currentConfig.phaseBias,
232
326
  state.currentConfig.rules,
233
327
  isBudgetExceeded,
234
328
  );
235
329
 
236
- // Optional Context Trigger Upgrade
237
330
  if (
238
331
  state.currentConfig.largeContextThreshold &&
239
- decision.tier !== 'high' &&
332
+ thinkingDecision.tier !== 'high' &&
240
333
  state.lastExtensionContext
241
334
  ) {
242
335
  try {
@@ -245,28 +338,24 @@ export const registerRouterProvider = (
245
338
  usage?.tokens &&
246
339
  usage.tokens > state.currentConfig.largeContextThreshold
247
340
  ) {
248
- decision = buildRoutingDecision(
249
- model.id,
250
- profile,
251
- 'high',
252
- 'planning',
253
- `Context usage (${usage.tokens}) exceeds threshold (${state.currentConfig.largeContextThreshold}). Forced high tier.`,
254
- state.thinkingByProfile[model.id],
255
- false,
256
- );
257
- decision.isContextTriggered = true;
341
+ thinkingDecision = {
342
+ ...thinkingDecision,
343
+ tier: 'high',
344
+ phase: 'planning',
345
+ reasoning: `Context usage (${usage.tokens}) exceeds threshold (${state.currentConfig.largeContextThreshold}). Forced high thinking.`,
346
+ isContextTriggered: true,
347
+ };
258
348
  }
259
- } catch (e) {
349
+ } catch (_e) {
260
350
  // ignore
261
351
  }
262
352
  }
263
353
 
264
- // Classifier Override
265
354
  if (
266
355
  state.currentConfig.classifierModel &&
267
356
  !pinnedTier &&
268
- !decision.isContextTriggered &&
269
- !decision.isRuleMatched
357
+ !thinkingDecision.isContextTriggered &&
358
+ !thinkingDecision.isRuleMatched
270
359
  ) {
271
360
  const classifierResult = await runClassifier(
272
361
  state.currentConfig.classifierModel,
@@ -275,24 +364,34 @@ export const registerRouterProvider = (
275
364
  state.lastDecision?.phase,
276
365
  );
277
366
  if (classifierResult) {
278
- decision = buildRoutingDecision(
367
+ thinkingDecision = buildRoutingDecision(
279
368
  model.id,
280
369
  profile,
281
370
  classifierResult.tier,
282
371
  phaseForTier(classifierResult.tier),
283
- `Classifier: ${classifierResult.reasoning}`,
284
- state.thinkingByProfile[model.id],
372
+ `Thinking classifier: ${classifierResult.reasoning}`,
373
+ thinkingOverrides,
285
374
  true,
286
375
  );
287
- if (isBudgetExceeded && decision.tier === 'high') {
288
- decision.tier = 'medium';
289
- decision.phase = 'implementation';
290
- decision.reasoning = `Budget exceeded. Downgraded classifier decision to medium. (Original: ${decision.reasoning})`;
291
- decision.isBudgetForced = true;
376
+ if (isBudgetExceeded && thinkingDecision.tier === 'high') {
377
+ thinkingDecision = {
378
+ ...thinkingDecision,
379
+ tier: 'medium',
380
+ phase: 'implementation',
381
+ reasoning: `Budget exceeded. Downgraded thinking to medium. (Original: ${thinkingDecision.reasoning})`,
382
+ isBudgetForced: true,
383
+ };
292
384
  }
293
385
  }
294
386
  }
295
387
 
388
+ let decision = applyThinkingToDecision(
389
+ lockedBase,
390
+ thinkingDecision,
391
+ profile,
392
+ thinkingOverrides,
393
+ );
394
+
296
395
  const lastMessage = context.messages[context.messages.length - 1];
297
396
  const previousDecision = state.lastDecision;
298
397
  const isGoogleThinkingToolContinuation =
@@ -302,7 +401,7 @@ export const registerRouterProvider = (
302
401
  previousDecision.thinking !== 'off' &&
303
402
  decision.targetProvider === 'google' &&
304
403
  decision.thinking !== 'off' &&
305
- previousDecision.targetLabel !== decision.targetLabel;
404
+ previousDecision.targetLabel === decision.targetLabel;
306
405
 
307
406
  if (isGoogleThinkingToolContinuation) {
308
407
  decision = {
@@ -319,56 +418,6 @@ export const registerRouterProvider = (
319
418
  };
320
419
  }
321
420
 
322
- const imageAttached = hasImageAttachment(context);
323
- if (imageAttached) {
324
- const checkModelSupportsImage = (modelRef: string) => {
325
- try {
326
- const { provider, modelId } = parseCanonicalModelRef(modelRef);
327
- const m = state.currentModelRegistry?.find(provider, modelId);
328
- return m?.input?.includes('image') ?? false;
329
- } catch {
330
- return false;
331
- }
332
- };
333
-
334
- const tierModels = [
335
- decision.targetLabel,
336
- ...(profile[decision.tier].fallbacks ?? []),
337
- ];
338
- if (!tierModels.some(checkModelSupportsImage)) {
339
- const tiersToTry: RouterTier[] =
340
- decision.tier === 'low'
341
- ? ['medium', 'high']
342
- : decision.tier === 'medium'
343
- ? ['high']
344
- : [];
345
-
346
- let foundTier: RouterTier | undefined;
347
- for (const t of tiersToTry) {
348
- const tModels = [
349
- profile[t].model,
350
- ...(profile[t].fallbacks ?? []),
351
- ];
352
- if (tModels.some(checkModelSupportsImage)) {
353
- foundTier = t;
354
- break;
355
- }
356
- }
357
-
358
- if (foundTier) {
359
- decision = buildRoutingDecision(
360
- model.id,
361
- profile,
362
- foundTier,
363
- phaseForTier(foundTier),
364
- `Forced ${foundTier} tier because the originally routed ${decision.tier} tier does not support image attachments.`,
365
- state.thinkingByProfile[model.id],
366
- false,
367
- );
368
- }
369
- }
370
- }
371
-
372
421
  state.lastDecision = decision;
373
422
  actions.recordDebugDecision(decision);
374
423
 
@@ -376,10 +425,12 @@ export const registerRouterProvider = (
376
425
  actions.updateStatus(state.lastExtensionContext);
377
426
  }
378
427
 
428
+ const lockTier = lock.tier;
379
429
  let modelsToTry = [
380
- decision.targetLabel,
381
- ...(profile[decision.tier].fallbacks ?? []),
430
+ lock.modelRef,
431
+ ...(profile[lockTier].fallbacks ?? []),
382
432
  ];
433
+ const imageAttached = hasImageAttachment(context);
383
434
  if (imageAttached) {
384
435
  modelsToTry = modelsToTry.filter((modelRef) => {
385
436
  try {
@@ -7,6 +7,7 @@ import type {
7
7
  RoutingDecision,
8
8
  RoutingRule,
9
9
  RouterThinkingByTier,
10
+ SessionLock,
10
11
  } from './types.js';
11
12
  import { parseCanonicalModelRef, isRouterTier } from './config.js';
12
13
 
@@ -38,6 +39,153 @@ export const getLastUserText = (context: Context): string => {
38
39
  return '';
39
40
  };
40
41
 
42
+ export const getFirstUserText = (context: Context): string => {
43
+ for (const message of context.messages) {
44
+ if (message.role === 'user') {
45
+ return extractTextFromContent(message.content).trim();
46
+ }
47
+ }
48
+ return '';
49
+ };
50
+
51
+ /** Context for one-shot session model lock: system prompt + first user message only. */
52
+ export const buildSessionLockContext = (context: Context): Context => {
53
+ const firstUser = context.messages.find((message) => message.role === 'user');
54
+ const messages = firstUser ? [firstUser] : context.messages.slice(0, 1);
55
+ return { ...context, messages };
56
+ };
57
+
58
+ export const sessionLockToRoutingDecision = (
59
+ lock: SessionLock,
60
+ profile: RouterProfile,
61
+ thinkingOverrides?: RouterThinkingByTier,
62
+ ): RoutingDecision => {
63
+ const routed = profile[lock.tier];
64
+ const { provider, modelId } = parseCanonicalModelRef(lock.modelRef);
65
+ const baseThinking =
66
+ routed.thinking ??
67
+ (lock.tier === 'high' ? 'high' : lock.tier === 'low' ? 'low' : 'medium');
68
+ const effectiveThinking = thinkingOverrides?.[lock.tier] ?? baseThinking;
69
+ return {
70
+ profile: lock.profile,
71
+ tier: lock.tier,
72
+ phase: phaseForTier(lock.tier),
73
+ targetProvider: provider,
74
+ targetModelId: modelId,
75
+ targetLabel: lock.modelRef,
76
+ reasoning: lock.reasoning,
77
+ thinking: effectiveThinking,
78
+ timestamp: Date.now(),
79
+ };
80
+ };
81
+
82
+ export const routingDecisionToSessionLock = (
83
+ decision: RoutingDecision,
84
+ ): SessionLock => ({
85
+ profile: decision.profile,
86
+ tier: decision.tier,
87
+ modelRef: decision.targetLabel,
88
+ reasoning: decision.reasoning,
89
+ });
90
+
91
+ /** Text used to score initial session complexity (system prompt + first user message). */
92
+ export const getSessionLockAnalysisText = (context: Context): string => {
93
+ const lockContext = buildSessionLockContext(context);
94
+ const system = (context.systemPrompt ?? '').trim();
95
+ const firstUser = getFirstUserText(lockContext);
96
+ if (system && firstUser) return `${system}\n\n${firstUser}`;
97
+ return system || firstUser || '';
98
+ };
99
+
100
+ /**
101
+ * One-shot tier pick for session model lock (no prior-turn stickiness).
102
+ */
103
+ export const decideSessionLock = (
104
+ context: Context,
105
+ profileName: string,
106
+ profile: RouterProfile,
107
+ pinnedTier?: RouterTier,
108
+ thinkingOverrides?: RouterThinkingByTier,
109
+ phaseBias = 0.5,
110
+ rules?: RoutingRule[],
111
+ ): RoutingDecision => {
112
+ const analysisText = getSessionLockAnalysisText(context);
113
+ const synthetic: Context = {
114
+ systemPrompt: context.systemPrompt,
115
+ messages: analysisText
116
+ ? [{ role: 'user', content: analysisText, timestamp: Date.now() }]
117
+ : [],
118
+ };
119
+ return decideRouting(
120
+ synthetic,
121
+ profileName,
122
+ profile,
123
+ undefined,
124
+ pinnedTier,
125
+ thinkingOverrides,
126
+ phaseBias,
127
+ rules,
128
+ false,
129
+ );
130
+ };
131
+
132
+ /** Per-turn thinking tier merged onto a session-locked model decision. */
133
+ export const applyThinkingToDecision = (
134
+ locked: RoutingDecision,
135
+ thinkingDecision: RoutingDecision,
136
+ profile: RouterProfile,
137
+ thinkingOverrides?: RouterThinkingByTier,
138
+ ): RoutingDecision => {
139
+ const tier = thinkingDecision.tier;
140
+ const routed = profile[tier];
141
+ const baseThinking =
142
+ routed.thinking ??
143
+ (tier === 'high' ? 'high' : tier === 'low' ? 'low' : 'medium');
144
+ const effectiveThinking = thinkingOverrides?.[tier] ?? baseThinking;
145
+
146
+ return {
147
+ profile: locked.profile,
148
+ tier,
149
+ phase: thinkingDecision.phase,
150
+ targetProvider: locked.targetProvider,
151
+ targetModelId: locked.targetModelId,
152
+ targetLabel: locked.targetLabel,
153
+ reasoning: `${locked.reasoning} | Thinking: ${thinkingDecision.reasoning}`,
154
+ thinking: effectiveThinking,
155
+ timestamp: Date.now(),
156
+ isClassifier: thinkingDecision.isClassifier,
157
+ isRuleMatched: thinkingDecision.isRuleMatched,
158
+ isBudgetForced: thinkingDecision.isBudgetForced,
159
+ isContextTriggered: thinkingDecision.isContextTriggered,
160
+ };
161
+ };
162
+
163
+ /** Harness subagents: tier from system prompt + optional first user line. */
164
+ export const resolveTierFromPrompt = (
165
+ systemPrompt: string,
166
+ userText: string,
167
+ profileName: string,
168
+ profile: RouterProfile,
169
+ rules?: RoutingRule[],
170
+ phaseBias = 0.5,
171
+ ): RouterTier => {
172
+ const context: Context = {
173
+ systemPrompt,
174
+ messages: userText
175
+ ? [{ role: 'user', content: userText, timestamp: Date.now() }]
176
+ : [],
177
+ };
178
+ return decideSessionLock(
179
+ context,
180
+ profileName,
181
+ profile,
182
+ undefined,
183
+ undefined,
184
+ phaseBias,
185
+ rules,
186
+ ).tier;
187
+ };
188
+
41
189
  export const getRecentConversationText = (
42
190
  context: Context,
43
191
  limit = 6,
@@ -3,6 +3,7 @@ import type {
3
3
  RouterThinkingByProfile,
4
4
  RoutingDecision,
5
5
  RouterPersistedState,
6
+ SessionLock,
6
7
  } from './types.js';
7
8
 
8
9
  export const isRouterPersistedState = (
@@ -30,6 +31,7 @@ export const buildPersistedState = (
30
31
  lastDecision: RoutingDecision | undefined,
31
32
  lastNonRouterModel: string | undefined,
32
33
  accumulatedCost: number,
34
+ sessionLock: SessionLock | undefined,
33
35
  ): RouterPersistedState => {
34
36
  return {
35
37
  enabled: routerEnabled,
@@ -37,6 +39,7 @@ export const buildPersistedState = (
37
39
  pinTier: pinnedTierByProfile[selectedProfile],
38
40
  pinByProfile: { ...pinnedTierByProfile },
39
41
  thinkingByProfile: { ...thinkingByProfile },
42
+ sessionLock,
40
43
  debugEnabled,
41
44
  widgetEnabled,
42
45
  debugHistory,
@@ -53,12 +53,21 @@ export interface RoutingDecision {
53
53
  isRuleMatched?: boolean;
54
54
  }
55
55
 
56
+ /** Fixed model for a router profile for the lifetime of a session (or until profile switch). */
57
+ export interface SessionLock {
58
+ profile: string;
59
+ tier: RouterTier;
60
+ modelRef: string;
61
+ reasoning: string;
62
+ }
63
+
56
64
  export interface RouterPersistedState {
57
65
  enabled: boolean;
58
66
  selectedProfile: string;
59
67
  pinTier?: RouterTier;
60
68
  pinByProfile?: RouterPinByProfile;
61
69
  thinkingByProfile?: RouterThinkingByProfile;
70
+ sessionLock?: SessionLock;
62
71
  debugEnabled?: boolean;
63
72
  widgetEnabled?: boolean;
64
73
  debugHistory?: RoutingDecision[];
@@ -4,6 +4,7 @@ import type {
4
4
  RouterConfig,
5
5
  RouterPinByProfile,
6
6
  RouterThinkingByProfile,
7
+ SessionLock,
7
8
  } from './types.js';
8
9
 
9
10
  const getEffectiveThinking = (
@@ -63,6 +64,7 @@ export const updateStatus = (
63
64
  accumulatedCost: number,
64
65
  widgetEnabled: boolean,
65
66
  currentConfig: RouterConfig,
67
+ sessionLock: SessionLock | undefined,
66
68
  ) => {
67
69
  const activeRouterProfile = routerEnabled ? selectedProfile : undefined;
68
70
  const statusProfile = selectedProfile;
@@ -81,7 +83,13 @@ export const updateStatus = (
81
83
  activeRouterProfile,
82
84
  lastDecision,
83
85
  );
84
- statusText = `router:${activeRouterProfile}${pinLabel} -> ${lastDecision.tier} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${effectiveThinking})`;
86
+ const locked =
87
+ sessionLock?.profile === activeRouterProfile
88
+ ? sessionLock.modelRef
89
+ : lastDecision.targetLabel;
90
+ statusText = `router:${activeRouterProfile}${pinLabel} locked:${locked} thinking:${lastDecision.tier} (${effectiveThinking})`;
91
+ } else if (sessionLock?.profile === activeRouterProfile) {
92
+ statusText = `router:${activeRouterProfile}${pinLabel} locked:${sessionLock.modelRef} -> waiting`;
85
93
  } else {
86
94
  statusText = `router:${activeRouterProfile}${pinLabel} -> waiting`;
87
95
  }
@@ -104,6 +112,11 @@ export const updateStatus = (
104
112
  ? ` / $${currentConfig.maxSessionBudget.toFixed(2)}`
105
113
  : ''),
106
114
  ];
115
+ if (sessionLock && sessionLock.profile === statusProfile) {
116
+ widgetLines.push(
117
+ `Locked model: ${sessionLock.modelRef} (lock tier ${sessionLock.tier})`,
118
+ );
119
+ }
107
120
  if (lastDecision && lastDecision.profile === statusProfile) {
108
121
  const effectiveThinking = getEffectiveThinking(
109
122
  thinkingByProfile,
@@ -114,8 +127,9 @@ export const updateStatus = (
114
127
  const flagsStr = flags.length > 0 ? ` [${flags.join(',')}]` : '';
115
128
 
116
129
  widgetLines.push(
117
- `Route: ${lastDecision.tier}${flagsStr} -> ${lastDecision.targetProvider}/${lastDecision.targetModelId} (${effectiveThinking})`,
130
+ `Thinking: ${lastDecision.tier}${flagsStr} (${effectiveThinking})`,
118
131
  `Phase: ${lastDecision.phase}`,
132
+ `Delegate: ${lastDecision.targetProvider}/${lastDecision.targetModelId}`,
119
133
  );
120
134
  } else if (!routerEnabled && lastNonRouterModel) {
121
135
  widgetLines.push(`Fallback: ${lastNonRouterModel}`);