pi-maestro-teammate 1.2.0 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-maestro-teammate",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Pi extension — teammate agent dispatch with DAG task graphs, RPC messaging, and compact TUI",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -127,6 +127,7 @@ import {
127
127
  getTeammatePermissionBroker,
128
128
  registerTeammateChildProxyCaller,
129
129
  } from "../runs/child-extensions.ts";
130
+ import { setQuietMode } from "../quiet-state.ts";
130
131
 
131
132
  export const TEAMMATE_PROMPT_SNIPPET =
132
133
  "Dispatch bounded work to discovered teammate roles for parallel, sequential, or specialist execution.";
@@ -146,7 +147,10 @@ export const TEAMMATE_PROMPT_GUIDELINES = [
146
147
  ];
147
148
 
148
149
  function displayMessageForResult(result: SingleResult): string {
149
- const lastMessage = result.messages.at(-1)?.content ?? "(no output)";
150
+ const lastMessage = result.messages.at(-1)?.content
151
+ ?? (result.structuredOutput !== undefined
152
+ ? `[structured_output] ${truncateUtf8Tail(JSON.stringify(result.structuredOutput), 512)}`
153
+ : "(no output)");
150
154
  if (result.exitCode === 0) return lastMessage;
151
155
 
152
156
  const schemaDiagnostic = result.messages
@@ -1812,6 +1816,7 @@ export default function registerTeammateExtension(
1812
1816
  .map((name) => taskIndexByName.get(name))
1813
1817
  .filter((dependency): dependency is number => dependency !== undefined),
1814
1818
  status: "pending",
1819
+ requestedModel: task.model,
1815
1820
  });
1816
1821
  });
1817
1822
  }
@@ -1840,6 +1845,7 @@ export default function registerTeammateExtension(
1840
1845
  inbox: [],
1841
1846
  outputLog: [],
1842
1847
  lastActivityAt: Date.now(),
1848
+ requestedModel: isMultiTask ? undefined : singleTask.model,
1843
1849
  replyTo: params.reply_to,
1844
1850
  // Root-tool dispatches start the tree.
1845
1851
  depth: 0,
@@ -1891,6 +1897,7 @@ export default function registerTeammateExtension(
1891
1897
  inbox: [],
1892
1898
  outputLog: [],
1893
1899
  lastActivityAt: Date.now(),
1900
+ requestedModel: task.model,
1894
1901
  spawnedBy: correlationId,
1895
1902
  // Graph tasks belong to their dispatch, so they share its depth;
1896
1903
  // a teammate call made *by* one of them is what advances it.
@@ -1953,15 +1960,18 @@ export default function registerTeammateExtension(
1953
1960
  }),
1954
1961
  onRetry: (retry) => applyAgentRetryState(state, retry),
1955
1962
  onTurnComplete: (result: SingleResult) => {
1956
- const lastMessage = displayMessageForResult(result);
1957
- const settle = isMultiTask ? settleGraphTaskAgent : settleAgent;
1958
- settle(
1959
- state,
1960
- result.correlationId,
1961
- result.exitCode,
1962
- lastMessage,
1963
- result.wakeable !== false,
1964
- );
1963
+ const target = state.activeRuns.get(result.correlationId) ?? activeAgent;
1964
+ target.resolvedModel = target.resolvedModel ?? result.model;
1965
+ if (result.attemptedModels) target.attemptedModels = [...result.attemptedModels];
1966
+ const lastMessage = displayMessageForResult(result);
1967
+ const settle = isMultiTask ? settleGraphTaskAgent : settleAgent;
1968
+ settle(
1969
+ state,
1970
+ result.correlationId,
1971
+ result.exitCode,
1972
+ lastMessage,
1973
+ result.wakeable !== false,
1974
+ );
1965
1975
  },
1966
1976
  onProgress: (() => {
1967
1977
  const UPDATE_INTERVAL = 300; // ms — throttle TUI updates
@@ -2001,6 +2011,9 @@ export default function registerTeammateExtension(
2001
2011
  durationMs: data.durationMs,
2002
2012
  lastActivityAt: data.lastActivityAt,
2003
2013
  resultReadyAt: data.resultReadyAt,
2014
+ requestedModel: data.requestedModel ?? existing?.requestedModel,
2015
+ resolvedModel: data.resolvedModel ?? existing?.resolvedModel,
2016
+ attemptedModels: data.attemptedModels ?? existing?.attemptedModels,
2004
2017
  ...(data.lastMessage
2005
2018
  ? { lastMessage: truncateUtf8Tail(data.lastMessage, AGENT_BUFFER_LIMITS.lastResultBytes) }
2006
2019
  : {}),
@@ -2018,6 +2031,11 @@ export default function registerTeammateExtension(
2018
2031
  clearAgentResultReadyState(state, entry.correlationId);
2019
2032
  }
2020
2033
  const childAgent = state.activeRuns.get(entry.correlationId);
2034
+ if (childAgent) {
2035
+ childAgent.requestedModel = entry.requestedModel ?? childAgent.requestedModel;
2036
+ childAgent.resolvedModel = entry.resolvedModel ?? childAgent.resolvedModel;
2037
+ childAgent.attemptedModels = entry.attemptedModels ?? childAgent.attemptedModels;
2038
+ }
2021
2039
  if (childAgent && childAgent !== activeAgent) {
2022
2040
  childAgent.lastActivityAt = Date.now();
2023
2041
  childAgent.status = entry.status === "completed" ? "sleeping" : entry.status;
@@ -2251,7 +2269,8 @@ export default function registerTeammateExtension(
2251
2269
  settleAgent(state, correlationId, exitCode, summaries, params.context !== "fork");
2252
2270
  emitComplete(pi, id, activeGraphMode, correlationId, exitCode, totalDur);
2253
2271
 
2254
- pi.sendMessage(
2272
+ safeSendMessage(
2273
+ pi,
2255
2274
  {
2256
2275
  customType: "teammate-complete",
2257
2276
  content: summaries,
@@ -2386,7 +2405,8 @@ export default function registerTeammateExtension(
2386
2405
  if (!result.lifecyclePending) {
2387
2406
  settleAgent(state, correlationId, result.exitCode, lastMsg, result.wakeable !== false);
2388
2407
  }
2389
- pi.sendMessage(
2408
+ safeSendMessage(
2409
+ pi,
2390
2410
  {
2391
2411
  customType: "teammate-complete",
2392
2412
  content: lastMsg,
@@ -2426,7 +2446,8 @@ export default function registerTeammateExtension(
2426
2446
  settleAgent(state, correlationId, result.exitCode, lastMsg, result.wakeable !== false);
2427
2447
  }
2428
2448
 
2429
- pi.sendMessage(
2449
+ safeSendMessage(
2450
+ pi,
2430
2451
  {
2431
2452
  customType: "teammate-complete",
2432
2453
  content: lastMsg,
@@ -3381,7 +3402,9 @@ export default function registerTeammateExtension(
3381
3402
  if (!isChild) {
3382
3403
  pi.events.on(COCKPIT_UI_OWNERSHIP_EVENT, (payload) => {
3383
3404
  if (!payload || typeof payload !== "object") return;
3384
- cockpitOwnsAgents = (payload as { agents?: unknown }).agents === true;
3405
+ const ownership = payload as { agents?: unknown; quiet?: unknown; quietSymbols?: unknown };
3406
+ cockpitOwnsAgents = ownership.agents === true;
3407
+ setQuietMode(ownership.quiet === true, ownership.quietSymbols);
3385
3408
  updateAgentWidget();
3386
3409
  });
3387
3410
  pi.events.on(TEAMMATE_STARTED_EVENT, () => {
@@ -3473,6 +3496,9 @@ interface ListedAgent {
3473
3496
  resultReadyAt?: number;
3474
3497
  /** Relayed permission/question requests this agent is blocked on. */
3475
3498
  pendingInteractions?: number;
3499
+ requestedModel?: string;
3500
+ resolvedModel?: string;
3501
+ attemptedModels?: string[];
3476
3502
  }
3477
3503
 
3478
3504
  export function buildRoleList(cwd: string): { entries: AgentSummary[]; text: string } {
@@ -3557,6 +3583,9 @@ export function buildAgentList(
3557
3583
  depth,
3558
3584
  treePrefix,
3559
3585
  status: entry.status,
3586
+ requestedModel: entry.requestedModel,
3587
+ resolvedModel: entry.resolvedModel,
3588
+ attemptedModels: entry.attemptedModels,
3560
3589
  ...(entry.resultReadyAt !== undefined ? { resultReadyAt: entry.resultReadyAt } : {}),
3561
3590
  ...(entry.pendingInteractions?.size ? { pendingInteractions: entry.pendingInteractions.size } : {}),
3562
3591
  });
@@ -3604,6 +3633,9 @@ export function buildAgentList(
3604
3633
  dependencies: progress.dependencies,
3605
3634
  toolCount: progress.toolCount,
3606
3635
  tokens: progress.tokens,
3636
+ requestedModel: progress.requestedModel,
3637
+ resolvedModel: progress.resolvedModel,
3638
+ attemptedModels: progress.attemptedModels,
3607
3639
  ...(progress.resultReadyAt !== undefined ? { resultReadyAt: progress.resultReadyAt } : {}),
3608
3640
  });
3609
3641
  childIndex++;
@@ -3652,6 +3684,14 @@ export function buildAgentList(
3652
3684
  stalled ? `STALLED idle ${idleSeconds}s` : idleSeconds >= 5 ? `idle ${idleSeconds}s` : "",
3653
3685
  entry.toolCount ? `${entry.toolCount} tools` : "",
3654
3686
  entry.tokens ? `${entry.tokens} tok` : "",
3687
+ entry.resolvedModel
3688
+ ? `model=${entry.resolvedModel}`
3689
+ : entry.requestedModel
3690
+ ? `requested=${entry.requestedModel}`
3691
+ : "",
3692
+ entry.attemptedModels && entry.attemptedModels.length > 1
3693
+ ? `attempted=${entry.attemptedModels.join(",")}`
3694
+ : "",
3655
3695
  entry.inboxSize ? `inbox=${entry.inboxSize}` : "",
3656
3696
  ].filter(Boolean).join(" · ");
3657
3697
  return `${entry.treePrefix}${iconFor(entry.status)} ${identity} · ${metadata}`;
@@ -3771,6 +3811,15 @@ export function buildWatchOutput(target: WatchTarget, lineCount: number): string
3771
3811
  "---",
3772
3812
  ...log,
3773
3813
  ];
3814
+ if (agent.resolvedModel || agent.requestedModel) {
3815
+ output.push(
3816
+ `Model: ${agent.resolvedModel ?? "not reported"}`
3817
+ + (agent.requestedModel ? ` (requested ${agent.requestedModel})` : ""),
3818
+ );
3819
+ }
3820
+ if (agent.attemptedModels && agent.attemptedModels.length > 1) {
3821
+ output.push(`Attempted models: ${agent.attemptedModels.join(", ")}`);
3822
+ }
3774
3823
  if (agent.status === "retrying" && agent.retry) {
3775
3824
  const retryIn = Math.max(0, Math.ceil((agent.retry.nextRetryAt - Date.now()) / 1000));
3776
3825
  output.push(`Retry ${agent.retry.attempt}/${agent.retry.maxRetries} in ${retryIn}s: ${agent.retry.lastError}`);
@@ -3807,6 +3856,15 @@ export function buildWatchOutput(target: WatchTarget, lineCount: number): string
3807
3856
  "---",
3808
3857
  ...log,
3809
3858
  ];
3859
+ if (progress.resolvedModel || progress.requestedModel) {
3860
+ output.push(
3861
+ `Model: ${progress.resolvedModel ?? "not reported"}`
3862
+ + (progress.requestedModel ? ` (requested ${progress.requestedModel})` : ""),
3863
+ );
3864
+ }
3865
+ if (progress.attemptedModels && progress.attemptedModels.length > 1) {
3866
+ output.push(`Attempted models: ${progress.attemptedModels.join(", ")}`);
3867
+ }
3810
3868
  const lastMessage = progress.lastMessage?.trim();
3811
3869
  if (progress.resultReadyAt !== undefined && progress.status === "running") {
3812
3870
  output.push("Pi completed a no-tool assistant turn; final agent_end confirmation is pending.");
@@ -4038,6 +4096,28 @@ function emitComplete(
4038
4096
  });
4039
4097
  }
4040
4098
 
4099
+ /**
4100
+ * Deferred background and IPC callbacks routinely outlive session replacement:
4101
+ * after ctx.newSession()/fork()/switchSession()/reload() the host invalidates
4102
+ * the captured ExtensionAPI and every action method throws synchronously via
4103
+ * assertActive. The notification target no longer exists, and agent state has
4104
+ * already settled via settleAgent/killAgent plus eventBus emit (which is not
4105
+ * guarded), so drop the send instead of letting the throw escape into an
4106
+ * unhandled rejection that kills the pi process.
4107
+ */
4108
+ export function safeSendMessage(
4109
+ pi: ExtensionAPI,
4110
+ message: Parameters<ExtensionAPI["sendMessage"]>[0],
4111
+ options?: Parameters<ExtensionAPI["sendMessage"]>[1],
4112
+ ): void {
4113
+ try {
4114
+ pi.sendMessage(message, options);
4115
+ } catch (error) {
4116
+ if (error instanceof Error && error.message.includes("extension ctx is stale")) return;
4117
+ console.error("[pi-maestro-teammate] deferred sendMessage failed:", error);
4118
+ }
4119
+ }
4120
+
4041
4121
  export function notifyBackgroundFailure(
4042
4122
  pi: ExtensionAPI,
4043
4123
  id: string,
@@ -4049,7 +4129,8 @@ export function notifyBackgroundFailure(
4049
4129
  `Background teammate failed (agent=${agent}, correlationId=${correlationId}, phase=background-promise): `
4050
4130
  + `${error instanceof Error ? error.message : String(error)}`;
4051
4131
  emitComplete(pi, id, agent, correlationId, 1, 0);
4052
- pi.sendMessage(
4132
+ safeSendMessage(
4133
+ pi,
4053
4134
  {
4054
4135
  customType: "teammate-complete",
4055
4136
  content: message,
@@ -4314,6 +4395,10 @@ function settleAgentLifecycle(
4314
4395
  ): void {
4315
4396
  clearAgentResultReadyState(state, correlationId);
4316
4397
  if (exitCode !== 0) {
4398
+ const agent = state.activeRuns.get(correlationId);
4399
+ if (agent && lastResult !== undefined) {
4400
+ agent.lastResult = truncateUtf8Tail(lastResult, AGENT_BUFFER_LIMITS.lastResultBytes);
4401
+ }
4317
4402
  killAgent(state, correlationId, undefined, "failed", abortProcess);
4318
4403
  return;
4319
4404
  }
@@ -4366,6 +4451,9 @@ export function recordSettledAgent(
4366
4451
  status,
4367
4452
  settledAt: Date.now(),
4368
4453
  ...(agent.lastResult ? { lastResult: agent.lastResult } : {}),
4454
+ ...(agent.requestedModel ? { requestedModel: agent.requestedModel } : {}),
4455
+ ...(agent.resolvedModel ? { resolvedModel: agent.resolvedModel } : {}),
4456
+ ...(agent.attemptedModels ? { attemptedModels: [...agent.attemptedModels] } : {}),
4369
4457
  });
4370
4458
  while (memo.size > SETTLED_AGENT_MEMO_LIMIT) {
4371
4459
  const oldest = memo.keys().next();
@@ -4649,7 +4737,7 @@ export async function handleChildInteractionRequest(
4649
4737
  : questionSummary(payload.questions);
4650
4738
  const parentAuthorization = interaction === "permission" && payload.authorization === "parent";
4651
4739
  if (!parentAuthorization) {
4652
- pi.sendMessage({
4740
+ safeSendMessage(pi, {
4653
4741
  customType: "teammate-interaction-request",
4654
4742
  content: `? @${agentLabel} ${interaction}\n${requestSummary}`,
4655
4743
  display: true,
@@ -5381,6 +5469,7 @@ export async function handleProxyRequest(
5381
5469
  .map((name) => taskIndexByName.get(name))
5382
5470
  .filter((dependency): dependency is number => dependency !== undefined),
5383
5471
  status: "pending",
5472
+ requestedModel: task.model,
5384
5473
  });
5385
5474
  });
5386
5475
  const progressSnapshot = (): AgentProgressSnapshot[] =>
@@ -5397,6 +5486,7 @@ export async function handleProxyRequest(
5397
5486
  inbox: [],
5398
5487
  outputLog: [],
5399
5488
  lastActivityAt: Date.now(),
5489
+ requestedModel: normalizedTasks ? undefined : singleTask.model,
5400
5490
  spawnedBy: parentCid,
5401
5491
  depth: dispatchDepth,
5402
5492
  status: "running",
@@ -5462,6 +5552,7 @@ export async function handleProxyRequest(
5462
5552
  inbox: [],
5463
5553
  outputLog: [],
5464
5554
  lastActivityAt: Date.now(),
5555
+ requestedModel: task.model,
5465
5556
  spawnedBy: cid,
5466
5557
  depth: dispatchDepth,
5467
5558
  status: "pending",
@@ -5477,7 +5568,8 @@ export async function handleProxyRequest(
5477
5568
 
5478
5569
  const spawnerAgent = parentCid ? state.activeRuns.get(parentCid) : undefined;
5479
5570
  const spawnerLabel = spawnerAgent?.name ?? spawnerAgent?.agent ?? "proxy";
5480
- pi.sendMessage(
5571
+ safeSendMessage(
5572
+ pi,
5481
5573
  {
5482
5574
  customType: "teammate-started",
5483
5575
  content: `● @${spawnerLabel} spawned @${singleTask.name ?? activeAgent.agent}`,
@@ -5509,6 +5601,9 @@ export async function handleProxyRequest(
5509
5601
  durationMs: data.durationMs,
5510
5602
  lastActivityAt: data.lastActivityAt,
5511
5603
  resultReadyAt: data.resultReadyAt,
5604
+ requestedModel: data.requestedModel ?? existing?.requestedModel,
5605
+ resolvedModel: data.resolvedModel ?? existing?.resolvedModel,
5606
+ attemptedModels: data.attemptedModels ?? existing?.attemptedModels,
5512
5607
  ...(data.lastMessage
5513
5608
  ? { lastMessage: truncateUtf8Tail(data.lastMessage, AGENT_BUFFER_LIMITS.lastResultBytes) }
5514
5609
  : {}),
@@ -5528,6 +5623,11 @@ export async function handleProxyRequest(
5528
5623
  activeAgent.lastActivityAt = Date.now();
5529
5624
 
5530
5625
  const childAgent = state.activeRuns.get(correlationId);
5626
+ if (childAgent) {
5627
+ childAgent.requestedModel = entry.requestedModel ?? childAgent.requestedModel;
5628
+ childAgent.resolvedModel = entry.resolvedModel ?? childAgent.resolvedModel;
5629
+ childAgent.attemptedModels = entry.attemptedModels ?? childAgent.attemptedModels;
5630
+ }
5531
5631
  if (childAgent && childAgent !== activeAgent) {
5532
5632
  childAgent.lastActivityAt = Date.now();
5533
5633
  childAgent.status = data.status === "completed" ? "sleeping" : data.status;
@@ -5632,6 +5732,9 @@ export async function handleProxyRequest(
5632
5732
  );
5633
5733
  },
5634
5734
  onTurnComplete: (result) => {
5735
+ const target = state.activeRuns.get(result.correlationId) ?? activeAgent;
5736
+ target.resolvedModel = target.resolvedModel ?? result.model;
5737
+ if (result.attemptedModels) target.attemptedModels = [...result.attemptedModels];
5635
5738
  const lastMessage = displayMessageForResult(result);
5636
5739
  const settle = normalizedTasks ? settleGraphTaskAgent : settleAgent;
5637
5740
  settle(
@@ -5773,7 +5876,8 @@ export async function handleProxyRequest(
5773
5876
  reportChildStatus(completed.exitCode === 0 ? "completed" : "failed");
5774
5877
  emitNestedComplete(completed.exitCode);
5775
5878
  }
5776
- pi.sendMessage(
5879
+ safeSendMessage(
5880
+ pi,
5777
5881
  {
5778
5882
  customType: "teammate-complete",
5779
5883
  content: completed.summary,
@@ -5983,7 +6087,8 @@ export async function handleProxyRequest(
5983
6087
  // Notify main session TUI
5984
6088
  const senderAgent = spawnedBy ? state.activeRuns.get(spawnedBy) : undefined;
5985
6089
  const senderLabel = senderAgent?.name ?? senderAgent?.agent ?? "agent";
5986
- pi.sendMessage(
6090
+ safeSendMessage(
6091
+ pi,
5987
6092
  {
5988
6093
  customType: "teammate-message",
5989
6094
  content: `● @${senderLabel} → @${to} (${mode}): ${message.slice(0, 120)}`,
@@ -0,0 +1,35 @@
1
+ // Quiet-mode mirror for pi-maestro-teammate tool rendering.
2
+ //
3
+ // Cockpit owns the config and broadcasts it on cockpit:ui-ownership. Teammate
4
+ // mirrors both the enable flag and lifecycle glyph set so its compact tool rows
5
+ // stay visually aligned with Cockpit and Flow without a cross-package import.
6
+
7
+ export type QuietSymbolMode = "check" | "dot";
8
+ export type QuietStatus = "running" | "success" | "failure";
9
+
10
+ let quietMode = false;
11
+ let quietSymbols: QuietSymbolMode = "check";
12
+
13
+ export function setQuietMode(value: boolean, symbols?: unknown): void {
14
+ quietMode = value;
15
+ if (symbols === "check" || symbols === "dot") quietSymbols = symbols;
16
+ }
17
+
18
+ export function isQuietMode(): boolean {
19
+ return quietMode;
20
+ }
21
+
22
+ export function getQuietSymbols(): QuietSymbolMode {
23
+ return quietSymbols;
24
+ }
25
+
26
+ export function quietStatusMark(status: QuietStatus): string {
27
+ if (quietSymbols === "dot") {
28
+ if (status === "running") return "○";
29
+ if (status === "success") return "●";
30
+ return "!";
31
+ }
32
+ if (status === "running") return "…";
33
+ if (status === "success") return "✓";
34
+ return "✕";
35
+ }
@@ -244,15 +244,15 @@ export function isPiResultReadyTurn(event: Record<string, unknown>): boolean {
244
244
  ));
245
245
  }
246
246
 
247
- /**
248
- * Captures a schema-valid structured_output call from Pi's assistant event.
249
- * This is a fallback for the small window before the child output file becomes
250
- * observable to the parent runner.
251
- */
252
- export function extractValidatedStructuredOutput(
247
+ interface StructuredOutputCandidate {
248
+ value: unknown;
249
+ toolCallId?: string;
250
+ }
251
+
252
+ function extractStructuredOutputCandidate(
253
253
  event: Record<string, unknown>,
254
254
  schema: Record<string, unknown>,
255
- ): unknown | undefined {
255
+ ): StructuredOutputCandidate | undefined {
256
256
  const message = event.message as Record<string, unknown> | undefined;
257
257
  const content = message?.content;
258
258
  if (!Array.isArray(content)) return undefined;
@@ -266,11 +266,28 @@ export function extractValidatedStructuredOutput(
266
266
  if (typeof raw === "string") {
267
267
  try { value = JSON.parse(raw); } catch { return undefined; }
268
268
  }
269
- return validateStructuredOutputValue(value, schema) ? value : undefined;
269
+ if (!validateStructuredOutputValue(value, schema)) return undefined;
270
+ const toolCallId = typeof block.toolCallId === "string"
271
+ ? block.toolCallId
272
+ : typeof block.id === "string"
273
+ ? block.id
274
+ : undefined;
275
+ return { value, ...(toolCallId ? { toolCallId } : {}) };
270
276
  }
271
277
  return undefined;
272
278
  }
273
279
 
280
+ /**
281
+ * Extracts a schema-valid structured_output payload from a Pi assistant event.
282
+ * Execution code treats this as pending until the tool execution succeeds.
283
+ */
284
+ export function extractValidatedStructuredOutput(
285
+ event: Record<string, unknown>,
286
+ schema: Record<string, unknown>,
287
+ ): unknown | undefined {
288
+ return extractStructuredOutputCandidate(event, schema)?.value;
289
+ }
290
+
274
291
  export function describeStructuredOutputValidationFailure(
275
292
  event: Record<string, unknown>,
276
293
  schema: Record<string, unknown>,
@@ -1694,14 +1711,29 @@ export async function runSingleTeammate(
1694
1711
  }
1695
1712
 
1696
1713
  const breaker = options.modelCircuitBreaker ?? sharedModelCircuitBreaker;
1697
- const modelCandidates: Array<string | undefined> = candidates.length > 0 ? candidates : [undefined];
1714
+ // When no explicit model or fallbacks are configured, try the pi default
1715
+ // first (undefined), then each authenticated model as an implicit fallback.
1716
+ // This prevents a terminal failure when the default provider has no quota.
1717
+ const implicitFallbacks = candidates.length === 0
1718
+ ? (options.modelCapabilities ?? []).map((capability) => capability.id)
1719
+ : [];
1720
+ const modelCandidates: Array<string | undefined> = candidates.length > 0
1721
+ ? candidates
1722
+ : [undefined, ...implicitFallbacks];
1698
1723
  const attemptedModels: string[] = [];
1699
1724
  let lastResult: SingleResult | undefined;
1700
1725
 
1726
+ const publishTurnComplete = (result: SingleResult): void => {
1727
+ try {
1728
+ options.onTurnComplete?.(result);
1729
+ } catch {
1730
+ // Completion observers must not change the model fallback outcome.
1731
+ }
1732
+ };
1733
+
1701
1734
  for (const modelToUse of modelCandidates) {
1702
1735
  const acquisition = modelToUse ? breaker.acquireCandidate(modelToUse) : undefined;
1703
1736
  if (acquisition && !acquisition.allowed) continue;
1704
- if (modelToUse && !attemptedModels.includes(modelToUse)) attemptedModels.push(modelToUse);
1705
1737
 
1706
1738
  let settled = false;
1707
1739
  try {
@@ -1710,9 +1742,40 @@ export async function runSingleTeammate(
1710
1742
  let candidateResult: SingleResult | undefined;
1711
1743
 
1712
1744
  while (!options.signal?.aborted) {
1713
- candidateResult = await runSingleAttempt(
1714
- params, agentConfig, cwd, correlationId, replyTo, startTime, modelToUse, options,
1715
- );
1745
+ if (modelToUse && !attemptedModels.includes(modelToUse)) attemptedModels.push(modelToUse);
1746
+
1747
+ // A failed candidate is not terminal while retry or fallback remains.
1748
+ // Buffer lifecycle completion until the outer policy selects the
1749
+ // authoritative attempt; successful wakeable children then stay open
1750
+ // for later turn-complete callbacks.
1751
+ let completionState: "buffering" | "forwarding" | "discarded" = "buffering";
1752
+ const pendingCompletions: SingleResult[] = [];
1753
+ const attemptOptions: RunTeammateOptions = options.onTurnComplete
1754
+ ? {
1755
+ ...options,
1756
+ onTurnComplete(result) {
1757
+ if (completionState === "forwarding") publishTurnComplete(result);
1758
+ else if (completionState === "buffering") pendingCompletions.push(result);
1759
+ },
1760
+ }
1761
+ : options;
1762
+ const commitCompletion = (): void => {
1763
+ completionState = "forwarding";
1764
+ for (const result of pendingCompletions.splice(0)) publishTurnComplete(result);
1765
+ };
1766
+ const discardCompletion = (): void => {
1767
+ completionState = "discarded";
1768
+ pendingCompletions.length = 0;
1769
+ };
1770
+
1771
+ try {
1772
+ candidateResult = await runSingleAttempt(
1773
+ params, agentConfig, cwd, correlationId, replyTo, startTime, modelToUse, attemptOptions,
1774
+ );
1775
+ } catch (error) {
1776
+ discardCompletion();
1777
+ throw error;
1778
+ }
1716
1779
  lastResult = candidateResult;
1717
1780
  const error = resultFailureMessage(candidateResult.messages);
1718
1781
  const retryable = candidateResult.exitCode !== 0 && isRetryableProviderError(error);
@@ -1721,13 +1784,22 @@ export async function runSingleTeammate(
1721
1784
  if (candidateResult.exitCode === 0) {
1722
1785
  if (acquisition?.allowed) { breaker.recordSuccess(acquisition); settled = true; }
1723
1786
  candidateResult.attemptedModels = attemptedModels.length > 1 ? attemptedModels : undefined;
1787
+ commitCompletion();
1724
1788
  return candidateResult;
1725
1789
  }
1726
1790
  if (!retryable || retryCount >= maxRetries) {
1727
1791
  if (fallbackEligible && acquisition?.allowed) { breaker.recordRetryableFailure(acquisition); settled = true; }
1792
+ if (!fallbackEligible) {
1793
+ if (acquisition?.allowed) { breaker.releaseCandidate(acquisition); settled = true; }
1794
+ candidateResult.attemptedModels = attemptedModels.length > 1 ? attemptedModels : undefined;
1795
+ commitCompletion();
1796
+ return candidateResult;
1797
+ }
1798
+ discardCompletion();
1728
1799
  break;
1729
1800
  }
1730
1801
 
1802
+ discardCompletion();
1731
1803
  retryCount += 1;
1732
1804
  const delayMs = retryDelayMs(retryCount);
1733
1805
  options.onRetry?.({
@@ -1749,6 +1821,7 @@ export async function runSingleTeammate(
1749
1821
  if (!isFallbackModelError(candidateResult.messages)) {
1750
1822
  if (acquisition?.allowed) { breaker.releaseCandidate(acquisition); settled = true; }
1751
1823
  candidateResult.attemptedModels = attemptedModels.length > 1 ? attemptedModels : undefined;
1824
+ publishTurnComplete(candidateResult);
1752
1825
  return candidateResult;
1753
1826
  }
1754
1827
  } finally {
@@ -1763,6 +1836,7 @@ export async function runSingleTeammate(
1763
1836
 
1764
1837
  if (lastResult) {
1765
1838
  lastResult.attemptedModels = attemptedModels.length > 1 ? attemptedModels : undefined;
1839
+ publishTurnComplete(lastResult);
1766
1840
  return lastResult;
1767
1841
  }
1768
1842
  return rejectWith(
@@ -1818,6 +1892,7 @@ interface AttemptState {
1818
1892
  lastContent: string;
1819
1893
  streamingText: string;
1820
1894
  stderrBuffer: string;
1895
+ pendingStructuredOutput?: StructuredOutputCandidate;
1821
1896
  capturedStructuredOutput: unknown;
1822
1897
  structuredOutputValidationFailure?: string;
1823
1898
  reportedRuntimeErrors: Set<string>;
@@ -1960,6 +2035,7 @@ async function runSingleAttempt(
1960
2035
  lastContent: "",
1961
2036
  streamingText: "",
1962
2037
  stderrBuffer: "",
2038
+ pendingStructuredOutput: undefined,
1963
2039
  capturedStructuredOutput: undefined,
1964
2040
  structuredOutputValidationFailure: undefined,
1965
2041
  reportedRuntimeErrors: new Set(),
@@ -1976,6 +2052,7 @@ async function runSingleAttempt(
1976
2052
 
1977
2053
  // AC8: Rich progress tracking
1978
2054
  const progress = createProgress(params.agent, startTime);
2055
+ progress.requestedModel = modelOverride ?? params.model ?? agentConfig.model;
1979
2056
 
1980
2057
  const updateProgressUsage = (): void => {
1981
2058
  const inputTokens = state.completedInputTokens + usage.inputTokens + pendingMessageUsage.inputTokens;
@@ -2167,6 +2244,7 @@ async function runSingleAttempt(
2167
2244
  state.lastContent = "";
2168
2245
  state.streamingText = "";
2169
2246
  state.stderrBuffer = "";
2247
+ state.pendingStructuredOutput = undefined;
2170
2248
  state.capturedStructuredOutput = undefined;
2171
2249
  state.structuredOutputValidationFailure = undefined;
2172
2250
  state.reportedRuntimeErrors.clear();
@@ -2289,6 +2367,9 @@ async function runSingleAttempt(
2289
2367
  progress.resultReadyAt = undefined;
2290
2368
  progress.recentTools = [];
2291
2369
  state.turnToolCount = 0;
2370
+ state.pendingStructuredOutput = undefined;
2371
+ state.capturedStructuredOutput = undefined;
2372
+ state.structuredOutputValidationFailure = undefined;
2292
2373
  options.onProgress?.(progress);
2293
2374
  }
2294
2375
 
@@ -2331,6 +2412,7 @@ async function runSingleAttempt(
2331
2412
  const messageModel = typeof msg?.model === "string" ? msg.model : event.model;
2332
2413
  if (messageModel) {
2333
2414
  state.resolvedModel = messageModel;
2415
+ progress.resolvedModel = messageModel;
2334
2416
  }
2335
2417
  recordRuntimeEventError(event, event.type);
2336
2418
  options.onProgress?.(progress);
@@ -2405,10 +2487,25 @@ async function runSingleAttempt(
2405
2487
  if (
2406
2488
  event.type === "tool_execution_end"
2407
2489
  && completedTool === "structured_output"
2408
- && event.isError !== true
2409
2490
  ) {
2491
+ const pending = state.pendingStructuredOutput;
2492
+ const completedToolCallId = typeof event.toolCallId === "string"
2493
+ ? event.toolCallId
2494
+ : typeof event.id === "string"
2495
+ ? event.id
2496
+ : undefined;
2497
+ const idsMatch = !pending?.toolCallId
2498
+ || !completedToolCallId
2499
+ || pending.toolCallId === completedToolCallId;
2500
+ if (event.isError !== true && pending && idsMatch) {
2501
+ state.capturedStructuredOutput = pending.value;
2502
+ } else if (event.isError === true && idsMatch) {
2503
+ state.pendingStructuredOutput = undefined;
2504
+ }
2410
2505
  const structuredOutput = readStructuredOutput(false);
2411
- if (structuredOutput !== undefined) completeTurn(structuredOutput, true);
2506
+ if (event.isError !== true && structuredOutput !== undefined) {
2507
+ completeTurn(structuredOutput, true);
2508
+ }
2412
2509
  }
2413
2510
  }
2414
2511
 
@@ -2435,7 +2532,10 @@ async function runSingleAttempt(
2435
2532
  }
2436
2533
  }
2437
2534
  const messageModel = typeof msg?.model === "string" ? msg.model : event.model;
2438
- if (messageModel) state.resolvedModel = messageModel;
2535
+ if (messageModel) {
2536
+ state.resolvedModel = messageModel;
2537
+ progress.resolvedModel = messageModel;
2538
+ }
2439
2539
  recordRuntimeEventError(event, "turn_end");
2440
2540
  if (isPiResultReadyTurn(event)) {
2441
2541
  progress.resultReadyAt = Date.now();
@@ -2510,7 +2610,8 @@ async function runSingleAttempt(
2510
2610
  // those buffered lines from reawakening the published agent loop.
2511
2611
  if (state.terminal) return;
2512
2612
  if (state.capturedStructuredOutput === undefined && params.outputSchema) {
2513
- state.capturedStructuredOutput = extractValidatedStructuredOutput(event, params.outputSchema);
2613
+ const candidate = extractStructuredOutputCandidate(event, params.outputSchema);
2614
+ if (candidate) state.pendingStructuredOutput = candidate;
2514
2615
  state.structuredOutputValidationFailure = describeStructuredOutputValidationFailure(event, params.outputSchema)
2515
2616
  ?? state.structuredOutputValidationFailure;
2516
2617
  }
@@ -51,6 +51,9 @@ export interface AgentProgress {
51
51
  /** Pi emitted a final no-tool assistant turn; agent_end has not necessarily arrived yet. */
52
52
  resultReadyAt?: number;
53
53
  lastMessage?: string;
54
+ requestedModel?: string;
55
+ resolvedModel?: string;
56
+ attemptedModels?: string[];
54
57
  }
55
58
 
56
59
  export interface AgentProgressSnapshot {
@@ -73,6 +76,9 @@ export interface AgentProgressSnapshot {
73
76
  resultReadyAt?: number;
74
77
  lastMessage?: string;
75
78
  error?: string;
79
+ requestedModel?: string;
80
+ resolvedModel?: string;
81
+ attemptedModels?: string[];
76
82
  }
77
83
 
78
84
  export interface ChildAgentCallSnapshot {
@@ -160,6 +166,9 @@ export interface ActiveAgent {
160
166
  outputLog: string[];
161
167
  pendingResolve?: (result: SingleResult) => void;
162
168
  lastActivityAt: number;
169
+ requestedModel?: string;
170
+ resolvedModel?: string;
171
+ attemptedModels?: string[];
163
172
  /**
164
173
  * When this agent failed. Set alongside `status: "failed"`, mirroring
165
174
  * `sleptAt` for retired agents, and read by the retention sweep that
@@ -237,6 +246,9 @@ export interface SettledAgentRecord {
237
246
  status: "completed" | "failed" | "terminated";
238
247
  settledAt: number;
239
248
  lastResult?: string;
249
+ requestedModel?: string;
250
+ resolvedModel?: string;
251
+ attemptedModels?: string[];
240
252
  }
241
253
 
242
254
  export const TEAMMATE_COMPLETE_EVENT = "teammate:complete";
package/src/tui/render.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  } from "../shared/agent-status.ts";
20
20
  import type { AgentProgressSnapshot, ChildAgentCallSnapshot, Details, SingleResult } from "../shared/types.ts";
21
21
  import { extractDependencies } from "../runs/execution.ts";
22
+ import { isQuietMode, quietStatusMark } from "../quiet-state.ts";
22
23
  import {
23
24
  buildProgressTree,
24
25
  focusTaskIndex,
@@ -139,6 +140,7 @@ export function renderTeammateCall(
139
140
  theme: Theme,
140
141
  context?: { expanded?: boolean },
141
142
  ): Component {
143
+ if (isQuietMode()) return renderQuietTeammateCall(args, theme);
142
144
  const tasks = (args.tasks as Array<Omit<TaskArg, "agent"> & { agent?: string }> | undefined)
143
145
  ?.map((task) => ({ ...task, agent: task.agent ?? (args.agent as string | undefined) ?? "general" }));
144
146
  const isBg = args.background === true;
@@ -244,6 +246,7 @@ export function renderTeammateResult(
244
246
  options: { expanded: boolean },
245
247
  theme: Theme,
246
248
  ): Component {
249
+ if (isQuietMode()) return renderQuietTeammateResult(result, theme);
247
250
  const details = result.details;
248
251
 
249
252
  // No results yet — streaming progress or background ack
@@ -576,3 +579,114 @@ function renderMultiResult(
576
579
  return [...box.render(w)];
577
580
  });
578
581
  }
582
+
583
+ // ---------------------------------------------------------------------------
584
+ // Quiet mode — single-line summaries for the teammate tool stream
585
+ // ---------------------------------------------------------------------------
586
+ //
587
+ // When cockpit quiet mode is on the whole tool surface collapses to one line
588
+ // per call/result. The teammate tool is the noisy one (full progress tree,
589
+ // child subtree, streaming tail, key hints); here it becomes:
590
+ // call: " ⋯ teammate @name (agent)" / " ⋯ teammate N agents chain"
591
+ // streaming: " ■ teammate 2/3 running · @focus using read"
592
+ // done: " ✓ teammate 3/3 done · 9.4k tokens" (✗ + first error on fail)
593
+ // The tree/dependency detail dropped here is still fully shown by cockpit's
594
+ // Agents widget, which quiet mode never compresses, so no information is lost.
595
+
596
+ function qLine(theme: Theme, markedGlyph: string, name: string, rest: string): string {
597
+ const parts = [
598
+ theme.fg("toolTitle", theme.bold ? theme.bold(name) : name),
599
+ rest ? theme.fg("muted", rest) : "",
600
+ ].filter(Boolean);
601
+ return ` ${markedGlyph} ${parts.join(" ")}`;
602
+ }
603
+
604
+ function quietFirstError(r: SingleResult): string {
605
+ const text = r.messages[r.messages.length - 1]?.content ?? "";
606
+ const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
607
+ if (!line) return "failed";
608
+ return line.length > 60 ? `${line.slice(0, 59)}…` : line;
609
+ }
610
+
611
+ function quietTopoLabel(tasks: TaskArg[]): string {
612
+ const taskNames = new Set(tasks.filter((t) => t.name).map((t) => t.name!));
613
+ if (!tasks.some((t) => extractDependencies(t.prompt, taskNames).length > 0)) return "";
614
+ return isLinearChain(tasks, taskNames) ? "chain" : "graph";
615
+ }
616
+
617
+ function renderQuietTeammateCall(args: Record<string, unknown>, theme: Theme): Component {
618
+ const glyph = theme.fg("warning", quietStatusMark("running"));
619
+ const tasks = (args.tasks as Array<Omit<TaskArg, "agent"> & { agent?: string }> | undefined)
620
+ ?.map((task) => ({ ...task, agent: task.agent ?? (args.agent as string | undefined) ?? "general" }));
621
+ if (tasks?.length) {
622
+ const topo = quietTopoLabel(tasks);
623
+ const rest = `${tasks.length} agents${topo ? ` ${topo}` : ""}`;
624
+ return dynamicComponent((w) => [truncateToWidth(qLine(theme, glyph, "teammate", rest), Math.max(1, w), "…")]);
625
+ }
626
+ const agent = (args.agent as string | undefined) ?? "general";
627
+ const name = args.name as string | undefined;
628
+ const rest = name ? `@${name} (${agent})` : agent;
629
+ return dynamicComponent((w) => [truncateToWidth(qLine(theme, glyph, "teammate", rest), Math.max(1, w), "…")]);
630
+ }
631
+
632
+ function renderQuietTeammateResult(result: AgentToolResult<Details>, theme: Theme): Component {
633
+ const details = result.details;
634
+ const isError = (result as { isError?: boolean }).isError === true;
635
+
636
+ // Terminal results: one line, ✓/✗, done/failed counts + tokens or first error.
637
+ if (details && details.results.length > 0) {
638
+ const results = details.results;
639
+ const ok = results.filter((r) => r.exitCode === 0).length;
640
+ const total = results.length;
641
+ const failed = total - ok;
642
+ const totalTokens = results.reduce((sum, r) => sum + r.usage.inputTokens + r.usage.outputTokens, 0);
643
+ if (failed > 0 || isError) {
644
+ const firstFailed = results.find((r) => r.exitCode !== 0);
645
+ const err = firstFailed ? quietFirstError(firstFailed) : "failed";
646
+ const rest = total === 1
647
+ ? `${firstFailed?.agent ?? "agent"} failed · ${err}`
648
+ : `${failed}/${total} failed · ${err}`;
649
+ const glyph = theme.fg("error", quietStatusMark("failure"));
650
+ return dynamicComponent((w) => [truncateToWidth(qLine(theme, glyph, "teammate", rest), Math.max(1, w), "…")]);
651
+ }
652
+ const tokens = totalTokens > 0 ? ` · ${formatTokens(totalTokens)} tokens` : "";
653
+ const rest = total === 1 ? `${results[0].agent} done${tokens}` : `${ok}/${total} done${tokens}`;
654
+ const glyph = theme.fg("success", quietStatusMark("success"));
655
+ return dynamicComponent((w) => [truncateToWidth(qLine(theme, glyph, "teammate", rest), Math.max(1, w), "…")]);
656
+ }
657
+
658
+ // Streaming: one aggregate line — no tree, no child subtree, no stream tail.
659
+ const entries = details?.progress ?? [];
660
+ const childCalls = details?.childCalls ?? [];
661
+ const running = entries.filter((e) => e.status === "running" || e.status === "retrying").length
662
+ + childCalls.filter((c) => c.status === "running" || c.status === "retrying").length;
663
+ const failed = entries.filter((e) => e.status === "failed").length
664
+ + childCalls.filter((c) => c.status === "failed").length;
665
+ const completed = entries.filter((e) => e.status === "completed").length;
666
+ const glyph = failed > 0
667
+ ? theme.fg("error", quietStatusMark("failure"))
668
+ : running > 0
669
+ ? theme.fg("warning", quietStatusMark("running"))
670
+ : theme.fg("success", quietStatusMark("success"));
671
+ const stateText = entries.length === 0
672
+ ? `${running || childCalls.length} child agent${(running || childCalls.length) === 1 ? "" : "s"}`
673
+ : failed > 0
674
+ ? `${failed}/${entries.length} failed`
675
+ : running > 0
676
+ ? `${running}/${entries.length} running`
677
+ : `${completed}/${entries.length} done`;
678
+
679
+ return dynamicComponent((w) => {
680
+ const focus = entries.length > 0
681
+ ? entries.find((e) => e.taskIndex === focusTaskIndex(entries)) ?? entries[0]
682
+ : undefined;
683
+ const parts = [stateText];
684
+ if (focus && w >= 20) {
685
+ parts.push(`@${focus.name ?? focus.agent}`);
686
+ const activeTool = focus.recentTools?.find((t) => t.status === "running");
687
+ if (activeTool) parts.push(`using ${activeTool.name}`);
688
+ else if (focus.lastMessage) parts.push("streaming");
689
+ }
690
+ return [truncateToWidth(qLine(theme, glyph, "teammate", parts.join(" · ")), Math.max(1, w), "…")];
691
+ });
692
+ }
@@ -196,6 +196,9 @@ interface ListedAgent {
196
196
  resultReadyAt?: number;
197
197
  /** Relayed permission/question requests this agent is blocked on. */
198
198
  pendingInteractions?: number;
199
+ requestedModel?: string;
200
+ resolvedModel?: string;
201
+ attemptedModels?: string[];
199
202
  }
200
203
  export declare function buildRoleList(cwd: string): {
201
204
  entries: AgentSummary[];
@@ -230,6 +233,16 @@ export declare function waitForTeammate(state: TeammateState, params: {
230
233
  timeoutMs?: number;
231
234
  waitMs?: number;
232
235
  }, signal?: AbortSignal): Promise<TeammateWaitResult>;
236
+ /**
237
+ * Deferred background and IPC callbacks routinely outlive session replacement:
238
+ * after ctx.newSession()/fork()/switchSession()/reload() the host invalidates
239
+ * the captured ExtensionAPI and every action method throws synchronously via
240
+ * assertActive. The notification target no longer exists, and agent state has
241
+ * already settled via settleAgent/killAgent plus eventBus emit (which is not
242
+ * guarded), so drop the send instead of letting the throw escape into an
243
+ * unhandled rejection that kills the pi process.
244
+ */
245
+ export declare function safeSendMessage(pi: ExtensionAPI, message: Parameters<ExtensionAPI["sendMessage"]>[0], options?: Parameters<ExtensionAPI["sendMessage"]>[1]): void;
233
246
  export declare function notifyBackgroundFailure(pi: ExtensionAPI, id: string, agent: string, correlationId: string, error: unknown): void;
234
247
  export declare function applyAgentRetryState(state: TeammateState, retry: {
235
248
  correlationId: string;
@@ -0,0 +1,6 @@
1
+ export type QuietSymbolMode = "check" | "dot";
2
+ export type QuietStatus = "running" | "success" | "failure";
3
+ export declare function setQuietMode(value: boolean, symbols?: unknown): void;
4
+ export declare function isQuietMode(): boolean;
5
+ export declare function getQuietSymbols(): QuietSymbolMode;
6
+ export declare function quietStatusMark(status: QuietStatus): string;
@@ -134,9 +134,8 @@ export declare function correlationSessionDirectoryName(correlationId: string):
134
134
  */
135
135
  export declare function isPiResultReadyTurn(event: Record<string, unknown>): boolean;
136
136
  /**
137
- * Captures a schema-valid structured_output call from Pi's assistant event.
138
- * This is a fallback for the small window before the child output file becomes
139
- * observable to the parent runner.
137
+ * Extracts a schema-valid structured_output payload from a Pi assistant event.
138
+ * Execution code treats this as pending until the tool execution succeeds.
140
139
  */
141
140
  export declare function extractValidatedStructuredOutput(event: Record<string, unknown>, schema: Record<string, unknown>): unknown | undefined;
142
141
  export declare function describeStructuredOutputValidationFailure(event: Record<string, unknown>, schema: Record<string, unknown>): string | undefined;
@@ -53,6 +53,9 @@ export interface AgentProgress {
53
53
  /** Pi emitted a final no-tool assistant turn; agent_end has not necessarily arrived yet. */
54
54
  resultReadyAt?: number;
55
55
  lastMessage?: string;
56
+ requestedModel?: string;
57
+ resolvedModel?: string;
58
+ attemptedModels?: string[];
56
59
  }
57
60
  export interface AgentProgressSnapshot {
58
61
  agent: string;
@@ -77,6 +80,9 @@ export interface AgentProgressSnapshot {
77
80
  resultReadyAt?: number;
78
81
  lastMessage?: string;
79
82
  error?: string;
83
+ requestedModel?: string;
84
+ resolvedModel?: string;
85
+ attemptedModels?: string[];
80
86
  }
81
87
  export interface ChildAgentCallSnapshot {
82
88
  agent: string;
@@ -162,6 +168,9 @@ export interface ActiveAgent {
162
168
  outputLog: string[];
163
169
  pendingResolve?: (result: SingleResult) => void;
164
170
  lastActivityAt: number;
171
+ requestedModel?: string;
172
+ resolvedModel?: string;
173
+ attemptedModels?: string[];
165
174
  /**
166
175
  * When this agent failed. Set alongside `status: "failed"`, mirroring
167
176
  * `sleptAt` for retired agents, and read by the retention sweep that
@@ -237,6 +246,9 @@ export interface SettledAgentRecord {
237
246
  status: "completed" | "failed" | "terminated";
238
247
  settledAt: number;
239
248
  lastResult?: string;
249
+ requestedModel?: string;
250
+ resolvedModel?: string;
251
+ attemptedModels?: string[];
240
252
  }
241
253
  export declare const TEAMMATE_COMPLETE_EVENT = "teammate:complete";
242
254
  export declare const TEAMMATE_STARTED_EVENT = "teammate:started";