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 +1 -1
- package/src/extension/index.ts +124 -19
- package/src/quiet-state.ts +35 -0
- package/src/runs/execution.ts +118 -17
- package/src/shared/types.ts +12 -0
- package/src/tui/render.ts +114 -0
- package/types/extension/index.d.ts +13 -0
- package/types/quiet-state.d.ts +6 -0
- package/types/runs/execution.d.ts +2 -3
- package/types/shared/types.d.ts +12 -0
package/package.json
CHANGED
package/src/extension/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/runs/execution.ts
CHANGED
|
@@ -244,15 +244,15 @@ export function isPiResultReadyTurn(event: Record<string, unknown>): boolean {
|
|
|
244
244
|
));
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
):
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1714
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
}
|
package/src/shared/types.ts
CHANGED
|
@@ -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
|
-
*
|
|
138
|
-
*
|
|
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;
|
package/types/shared/types.d.ts
CHANGED
|
@@ -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";
|