@nmzpy/pi-ember-stack 0.1.6 → 0.2.2
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/README.md +83 -83
- package/package.json +62 -48
- package/plugins/devin-auth/extensions/index.ts +68 -7
- package/plugins/devin-auth/src/cloud-direct/auth.ts +246 -246
- package/plugins/devin-auth/src/cloud-direct/catalog.ts +246 -246
- package/plugins/devin-auth/src/cloud-direct/chat.ts +1096 -1091
- package/plugins/devin-auth/src/cloud-direct/index.ts +41 -41
- package/plugins/devin-auth/src/cloud-direct/metadata.ts +78 -78
- package/plugins/devin-auth/src/cloud-direct/wire.ts +202 -202
- package/plugins/devin-auth/src/models.ts +42 -196
- package/plugins/devin-auth/src/oauth/register-user.ts +174 -174
- package/plugins/devin-auth/src/oauth/types.ts +71 -71
- package/plugins/devin-auth/src/stream.ts +1 -1
- package/plugins/index.ts +29 -6
- package/plugins/pi-compact-tools/index.ts +35 -192
- package/plugins/pi-compact-tools/renderer.ts +589 -0
- package/plugins/pi-custom-agents/index.ts +727 -373
- package/plugins/pi-custom-agents/questionnaire-tool.ts +14 -5
- package/plugins/pi-custom-agents/subagent/agents/coder.md +1 -0
- package/plugins/pi-custom-agents/subagent/agents/scout.md +16 -37
- package/plugins/pi-custom-agents/subagent/extensions/agents.ts +18 -1
- package/plugins/pi-custom-agents/subagent/extensions/index.ts +118 -226
- package/plugins/pi-custom-agents/subagent/extensions/model.ts +96 -96
- package/plugins/pi-custom-agents/subagent/extensions/render.ts +205 -1
- package/plugins/pi-custom-agents/subagent/extensions/runner.ts +260 -170
- package/plugins/pi-custom-agents/subagent/extensions/test/render.test.ts +145 -0
- package/plugins/pi-ember-fff/index.ts +975 -0
- package/plugins/pi-ember-fff/query.ts +247 -0
- package/plugins/pi-ember-fff/test/query.test.ts +222 -0
- package/plugins/pi-ember-fff/test/renderer.test.ts +727 -0
- package/plugins/pi-ember-tps/index.ts +144 -0
- package/plugins/pi-ember-ui/ember.json +99 -0
- package/plugins/pi-ember-ui/index.ts +805 -0
- package/plugins/pi-ember-ui/mode-colors.ts +196 -0
- package/tsconfig.json +2 -1
- package/plugins/pi-custom-agents/subagent/agents/architect.md +0 -56
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared model resolution for pi-subagent.
|
|
3
|
-
*
|
|
4
|
-
* Provides a single canonical resolveModel() used by both the tool handler
|
|
5
|
-
* (index.ts) and the event-driven service path (service.ts), ensuring
|
|
6
|
-
* consistent error reporting across all sub-agent invocation paths.
|
|
7
|
-
*
|
|
8
|
-
* Queries the parent ModelRegistry first (catches custom-configured models
|
|
9
|
-
* with overridden base URLs, headers, compatibility settings). Falls back
|
|
10
|
-
* to the built-in registry for unconfigured models.
|
|
11
|
-
* For unqualified names (no provider prefix), known naming conventions
|
|
12
|
-
* are tried before assuming Anthropic.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Model } from "@earendil-works/pi-ai";
|
|
16
|
-
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
17
|
-
|
|
18
|
-
type BuiltInModelResolver = (provider: string, id: string) => Model<any> | undefined;
|
|
19
|
-
|
|
20
|
-
interface ModelModule {
|
|
21
|
-
getModel: BuiltInModelResolver;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Pi 0.80 exposes getModel from /compat; 0.79 exported it from the main entry.
|
|
25
|
-
const { getModel } = (await import("@earendil-works/pi-ai/compat").catch(
|
|
26
|
-
() => import("@earendil-works/pi-ai"),
|
|
27
|
-
)) as ModelModule;
|
|
28
|
-
|
|
29
|
-
export interface ResolvedModel {
|
|
30
|
-
model: Model<any> | null;
|
|
31
|
-
attempted: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Known provider prefixes for unqualified model names. */
|
|
35
|
-
const KNOWN_PROVIDERS: [string, RegExp][] = [
|
|
36
|
-
["openai", /^gpt-/i],
|
|
37
|
-
["anthropic", /^claude-/i],
|
|
38
|
-
["google", /^gemini-/i],
|
|
39
|
-
["cohere", /^command-/i],
|
|
40
|
-
["deepseek", /^(deepseek-|ds-)/i],
|
|
41
|
-
["mistral", /^mistral-/i],
|
|
42
|
-
["groq", /^(groq-|llama-)/i],
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
function tryGetModel(
|
|
46
|
-
provider: string,
|
|
47
|
-
id: string,
|
|
48
|
-
modelRegistry?: ModelRegistry,
|
|
49
|
-
): Model<any> | null {
|
|
50
|
-
// Query parent ModelRegistry first — it includes custom-configured models
|
|
51
|
-
// (overridden base URLs, headers, compatibility settings, per-model overrides).
|
|
52
|
-
// Fall back to built-in registry for unconfigured models.
|
|
53
|
-
if (modelRegistry) {
|
|
54
|
-
const found = modelRegistry.find(provider as any, id as any) ?? null;
|
|
55
|
-
if (found) return found;
|
|
56
|
-
}
|
|
57
|
-
const builtIn = getModel(provider as any, id as any) ?? null;
|
|
58
|
-
if (builtIn) return builtIn;
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function resolveModel(
|
|
63
|
-
modelName: string | undefined,
|
|
64
|
-
parentModel: Model<any> | undefined,
|
|
65
|
-
modelRegistry?: ModelRegistry,
|
|
66
|
-
): ResolvedModel {
|
|
67
|
-
const attempted: string[] = [];
|
|
68
|
-
if (modelName) {
|
|
69
|
-
const idx = modelName.indexOf("/");
|
|
70
|
-
if (idx > 0) {
|
|
71
|
-
// Provider-qualified: "openai/gpt-4o" or "openrouter/anthropic/claude-3.5"
|
|
72
|
-
const provider = modelName.slice(0, idx);
|
|
73
|
-
const id = modelName.slice(idx + 1);
|
|
74
|
-
attempted.push(modelName);
|
|
75
|
-
const found = tryGetModel(provider, id, modelRegistry);
|
|
76
|
-
if (found) return { model: found, attempted };
|
|
77
|
-
} else {
|
|
78
|
-
// Unqualified: try known providers by naming convention
|
|
79
|
-
for (const [provider, pattern] of KNOWN_PROVIDERS) {
|
|
80
|
-
if (pattern.test(modelName)) {
|
|
81
|
-
attempted.push(`${provider}/${modelName}`);
|
|
82
|
-
const found = tryGetModel(provider, modelName, modelRegistry);
|
|
83
|
-
if (found) return { model: found, attempted };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Fall back to Anthropic shorthand (backward compat)
|
|
87
|
-
attempted.push(`anthropic/${modelName}`);
|
|
88
|
-
const found = tryGetModel("anthropic", modelName, modelRegistry);
|
|
89
|
-
if (found) return { model: found, attempted };
|
|
90
|
-
}
|
|
91
|
-
} else if (parentModel) {
|
|
92
|
-
attempted.push(`${parentModel.provider}/${parentModel.id}`);
|
|
93
|
-
return { model: parentModel, attempted };
|
|
94
|
-
}
|
|
95
|
-
return { model: null, attempted };
|
|
96
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared model resolution for pi-subagent.
|
|
3
|
+
*
|
|
4
|
+
* Provides a single canonical resolveModel() used by both the tool handler
|
|
5
|
+
* (index.ts) and the event-driven service path (service.ts), ensuring
|
|
6
|
+
* consistent error reporting across all sub-agent invocation paths.
|
|
7
|
+
*
|
|
8
|
+
* Queries the parent ModelRegistry first (catches custom-configured models
|
|
9
|
+
* with overridden base URLs, headers, compatibility settings). Falls back
|
|
10
|
+
* to the built-in registry for unconfigured models.
|
|
11
|
+
* For unqualified names (no provider prefix), known naming conventions
|
|
12
|
+
* are tried before assuming Anthropic.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
16
|
+
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
|
|
18
|
+
type BuiltInModelResolver = (provider: string, id: string) => Model<any> | undefined;
|
|
19
|
+
|
|
20
|
+
interface ModelModule {
|
|
21
|
+
getModel: BuiltInModelResolver;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Pi 0.80 exposes getModel from /compat; 0.79 exported it from the main entry.
|
|
25
|
+
const { getModel } = (await import("@earendil-works/pi-ai/compat").catch(
|
|
26
|
+
() => import("@earendil-works/pi-ai"),
|
|
27
|
+
)) as ModelModule;
|
|
28
|
+
|
|
29
|
+
export interface ResolvedModel {
|
|
30
|
+
model: Model<any> | null;
|
|
31
|
+
attempted: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Known provider prefixes for unqualified model names. */
|
|
35
|
+
const KNOWN_PROVIDERS: [string, RegExp][] = [
|
|
36
|
+
["openai", /^gpt-/i],
|
|
37
|
+
["anthropic", /^claude-/i],
|
|
38
|
+
["google", /^gemini-/i],
|
|
39
|
+
["cohere", /^command-/i],
|
|
40
|
+
["deepseek", /^(deepseek-|ds-)/i],
|
|
41
|
+
["mistral", /^mistral-/i],
|
|
42
|
+
["groq", /^(groq-|llama-)/i],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function tryGetModel(
|
|
46
|
+
provider: string,
|
|
47
|
+
id: string,
|
|
48
|
+
modelRegistry?: ModelRegistry,
|
|
49
|
+
): Model<any> | null {
|
|
50
|
+
// Query parent ModelRegistry first — it includes custom-configured models
|
|
51
|
+
// (overridden base URLs, headers, compatibility settings, per-model overrides).
|
|
52
|
+
// Fall back to built-in registry for unconfigured models.
|
|
53
|
+
if (modelRegistry) {
|
|
54
|
+
const found = modelRegistry.find(provider as any, id as any) ?? null;
|
|
55
|
+
if (found) return found;
|
|
56
|
+
}
|
|
57
|
+
const builtIn = getModel(provider as any, id as any) ?? null;
|
|
58
|
+
if (builtIn) return builtIn;
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function resolveModel(
|
|
63
|
+
modelName: string | undefined,
|
|
64
|
+
parentModel: Model<any> | undefined,
|
|
65
|
+
modelRegistry?: ModelRegistry,
|
|
66
|
+
): ResolvedModel {
|
|
67
|
+
const attempted: string[] = [];
|
|
68
|
+
if (modelName) {
|
|
69
|
+
const idx = modelName.indexOf("/");
|
|
70
|
+
if (idx > 0) {
|
|
71
|
+
// Provider-qualified: "openai/gpt-4o" or "openrouter/anthropic/claude-3.5"
|
|
72
|
+
const provider = modelName.slice(0, idx);
|
|
73
|
+
const id = modelName.slice(idx + 1);
|
|
74
|
+
attempted.push(modelName);
|
|
75
|
+
const found = tryGetModel(provider, id, modelRegistry);
|
|
76
|
+
if (found) return { model: found, attempted };
|
|
77
|
+
} else {
|
|
78
|
+
// Unqualified: try known providers by naming convention
|
|
79
|
+
for (const [provider, pattern] of KNOWN_PROVIDERS) {
|
|
80
|
+
if (pattern.test(modelName)) {
|
|
81
|
+
attempted.push(`${provider}/${modelName}`);
|
|
82
|
+
const found = tryGetModel(provider, modelName, modelRegistry);
|
|
83
|
+
if (found) return { model: found, attempted };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Fall back to Anthropic shorthand (backward compat)
|
|
87
|
+
attempted.push(`anthropic/${modelName}`);
|
|
88
|
+
const found = tryGetModel("anthropic", modelName, modelRegistry);
|
|
89
|
+
if (found) return { model: found, attempted };
|
|
90
|
+
}
|
|
91
|
+
} else if (parentModel) {
|
|
92
|
+
attempted.push(`${parentModel.provider}/${parentModel.id}`);
|
|
93
|
+
return { model: parentModel, attempted };
|
|
94
|
+
}
|
|
95
|
+
return { model: null, attempted };
|
|
96
|
+
}
|
|
@@ -8,10 +8,40 @@
|
|
|
8
8
|
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import {
|
|
11
|
+
import { renderGradientLabel } from "../../../pi-ember-ui/index.ts";
|
|
12
|
+
import { getActiveModeColor } from "../../../pi-ember-ui/mode-colors.ts";
|
|
13
|
+
import { Box, Container, type Component, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
12
14
|
import type { Message } from "@earendil-works/pi-ai";
|
|
13
15
|
import { type SubAgentResult, isFailedResult, getResultOutput } from "./runner.ts";
|
|
14
16
|
|
|
17
|
+
type Foreground = (color: string, text: string) => string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Width-aware cap for the running subagent shell. It renders at the width
|
|
21
|
+
* supplied by the TUI instead of baking a terminal width into the component.
|
|
22
|
+
*/
|
|
23
|
+
export class SubagentCapLine implements Component {
|
|
24
|
+
private foreground: Foreground;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly isVisible: () => boolean,
|
|
28
|
+
foreground: Foreground,
|
|
29
|
+
) {
|
|
30
|
+
this.foreground = foreground;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setForeground(foreground: Foreground): void {
|
|
34
|
+
this.foreground = foreground;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render(width: number): string[] {
|
|
38
|
+
if (!this.isVisible()) return [];
|
|
39
|
+
return [this.foreground("border", "\u2500".repeat(Math.max(0, width)))];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
invalidate(): void {}
|
|
43
|
+
}
|
|
44
|
+
|
|
15
45
|
// ---------------------------------------------------------------------------
|
|
16
46
|
// Safe type guards
|
|
17
47
|
// ---------------------------------------------------------------------------
|
|
@@ -276,3 +306,177 @@ export function aggregateUsage(results: SubAgentResult[]) {
|
|
|
276
306
|
}
|
|
277
307
|
return total;
|
|
278
308
|
}
|
|
309
|
+
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Compact grouped layout (Exploring-style)
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Per-agent status derived from a SubAgentResult.
|
|
316
|
+
* `exitCode === -1` means still running (no result yet).
|
|
317
|
+
*/
|
|
318
|
+
type AgentStatus = "running" | "completed" | "failed";
|
|
319
|
+
|
|
320
|
+
function agentStatus(result: SubAgentResult | undefined): AgentStatus {
|
|
321
|
+
if (!result || result.exitCode === -1) return "running";
|
|
322
|
+
return isFailedResult(result) ? "failed" : "completed";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function agentIcon(status: AgentStatus, theme: any): string {
|
|
326
|
+
if (status === "failed") return theme.fg("error", "✗ ");
|
|
327
|
+
return theme.fg("success", "✓ ");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function hashPhase(name: string): number {
|
|
331
|
+
let n = 7;
|
|
332
|
+
for (const ch of name) n = (n * 31 + ch.charCodeAt(0)) >>> 0;
|
|
333
|
+
return (n % 1000) / 1000;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function renderAgentLabel(status: AgentStatus, agentName: string, theme: any, result?: SubAgentResult): string {
|
|
337
|
+
if (status === "running") return renderGradientLabel(agentName, getActiveModeColor(), hashPhase(agentName));
|
|
338
|
+
let suffix = "";
|
|
339
|
+
if (status === "failed" && result) {
|
|
340
|
+
const output = getResultOutput(result).trim();
|
|
341
|
+
if (output) {
|
|
342
|
+
const clipped = output.length > 60 ? `${output.slice(0, 60)}...` : output;
|
|
343
|
+
suffix = ` ${theme.fg("muted", clipped)}`;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return agentIcon(status, theme) + theme.fg("accent", agentName) + suffix;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function renderGroupLabel(
|
|
350
|
+
label: string,
|
|
351
|
+
_hasError: boolean,
|
|
352
|
+
_allDone: boolean,
|
|
353
|
+
theme: any,
|
|
354
|
+
): string {
|
|
355
|
+
// Header is always plain dim/bold; never gradient.
|
|
356
|
+
return theme.fg("dim", theme.bold(label));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Render the compact grouped layout for a subagent tool call.
|
|
361
|
+
*
|
|
362
|
+
* - Single mode: running `agentName` uses the Thinking gradient; completed
|
|
363
|
+
* and failed agents use green/red bullets.
|
|
364
|
+
* - Parallel mode: `Subagents` header + `└ agent` children with the same
|
|
365
|
+
* running/completed/failed treatment.
|
|
366
|
+
* - Chain mode: same grouped structure, but only running + completed steps
|
|
367
|
+
* appear (pending steps are hidden until they start).
|
|
368
|
+
*
|
|
369
|
+
* No `⏳`, no `[scope]`, no `parallel (N tasks)` — just bullets and names.
|
|
370
|
+
*/
|
|
371
|
+
export function renderSubagentLayout(
|
|
372
|
+
args: any,
|
|
373
|
+
results: SubAgentResult[],
|
|
374
|
+
theme: any,
|
|
375
|
+
): string {
|
|
376
|
+
const fg = theme.fg.bind(theme);
|
|
377
|
+
|
|
378
|
+
// --- Single mode ---
|
|
379
|
+
if (args.agent && args.task && !(args.tasks?.length > 0) && !(args.chain?.length > 0)) {
|
|
380
|
+
const status = agentStatus(results[0]);
|
|
381
|
+
return renderAgentLabel(status, args.agent, theme, results[0]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// --- Parallel mode ---
|
|
385
|
+
if (args.tasks && args.tasks.length > 0) {
|
|
386
|
+
const tasks = args.tasks as Array<{ agent: string }>;
|
|
387
|
+
const statuses = tasks.map((_, i) => agentStatus(results[i]));
|
|
388
|
+
const hasError = statuses.some((s) => s === "failed");
|
|
389
|
+
const allDone = statuses.every((s) => s !== "running");
|
|
390
|
+
const lines = [renderGroupLabel("Subagents", hasError, allDone, theme)];
|
|
391
|
+
for (const [i, t] of tasks.entries()) {
|
|
392
|
+
const prefix = i === 0 ? " └ " : " ";
|
|
393
|
+
lines.push(fg("dim", prefix) + renderAgentLabel(statuses[i], t.agent, theme, results[i]));
|
|
394
|
+
}
|
|
395
|
+
return lines.join("\n");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// --- Chain mode ---
|
|
399
|
+
if (args.chain && args.chain.length > 0) {
|
|
400
|
+
const chain = args.chain as Array<{ agent: string }>;
|
|
401
|
+
// Only show steps that have started (have a result entry).
|
|
402
|
+
const started = chain.slice(0, results.length);
|
|
403
|
+
const statuses = started.map((_, i) => agentStatus(results[i]));
|
|
404
|
+
const hasError = statuses.some((s) => s === "failed");
|
|
405
|
+
const allDone = statuses.length > 0 && statuses.every((s) => s !== "running");
|
|
406
|
+
const lines = [renderGroupLabel("Subagents", hasError, allDone, theme)];
|
|
407
|
+
for (const [i, step] of started.entries()) {
|
|
408
|
+
const prefix = i === 0 ? " └ " : " ";
|
|
409
|
+
lines.push(fg("dim", prefix) + renderAgentLabel(statuses[i], step.agent, theme, results[i]));
|
|
410
|
+
}
|
|
411
|
+
return lines.join("\n");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Fallback (should not reach here)
|
|
415
|
+
return fg("dim", "subagent");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Whether any agent in the layout is still running (flashing).
|
|
420
|
+
*/
|
|
421
|
+
export function anySubagentRunning(args: any, results: SubAgentResult[]): boolean {
|
|
422
|
+
if (args.agent && args.task && !(args.tasks?.length > 0) && !(args.chain?.length > 0)) {
|
|
423
|
+
return agentStatus(results[0]) === "running";
|
|
424
|
+
}
|
|
425
|
+
if (args.tasks && args.tasks.length > 0) {
|
|
426
|
+
return args.tasks.some((_t: any, i: number) => agentStatus(results[i]) === "running");
|
|
427
|
+
}
|
|
428
|
+
if (args.chain && args.chain.length > 0) {
|
|
429
|
+
return args.chain.slice(0, results.length).some((_s: any, i: number) => agentStatus(results[i]) === "running");
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Expanded view (Ctrl+O)
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Detailed per-agent output for the expanded view, wrapped in a
|
|
440
|
+
* subagentBg Box so it stays visually integrated with the collapsed row.
|
|
441
|
+
*/
|
|
442
|
+
export function renderSubagentExpanded(
|
|
443
|
+
details: { mode: "single" | "parallel" | "chain"; results: SubAgentResult[] },
|
|
444
|
+
theme: any,
|
|
445
|
+
): Component | undefined {
|
|
446
|
+
const fg = theme.fg.bind(theme);
|
|
447
|
+
const mdTheme = getMarkdownTheme();
|
|
448
|
+
const box = new Box(1, 0, (s: string) => (theme.bg as any)("subagentBg", s));
|
|
449
|
+
|
|
450
|
+
if (details.mode === "single" && details.results.length === 1) {
|
|
451
|
+
const inner = renderSingleResult(details.results[0], true, theme);
|
|
452
|
+
if (inner instanceof Container) {
|
|
453
|
+
box.addChild(inner);
|
|
454
|
+
} else if (inner instanceof Text) {
|
|
455
|
+
box.addChild(inner);
|
|
456
|
+
}
|
|
457
|
+
return box;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const container = new Container();
|
|
461
|
+
for (const r of details.results) {
|
|
462
|
+
const stepIcon = isFailedResult(r) ? fg("error", "✗") : fg("success", "✓");
|
|
463
|
+
container.addChild(new Text(`${stepIcon} ${fg("accent", r.agent)}`, 0, 0));
|
|
464
|
+
if (r.errorMessage) {
|
|
465
|
+
container.addChild(new Text(fg("error", `Error: ${r.errorMessage}`), 0, 0));
|
|
466
|
+
}
|
|
467
|
+
const finalOutput = getResultOutput(r);
|
|
468
|
+
if (finalOutput) {
|
|
469
|
+
container.addChild(new Spacer(1));
|
|
470
|
+
container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
|
|
471
|
+
}
|
|
472
|
+
const usageStr = formatUsageStats(r.usage, r.model);
|
|
473
|
+
if (usageStr) container.addChild(new Text(fg("dim", usageStr), 0, 0));
|
|
474
|
+
container.addChild(new Spacer(1));
|
|
475
|
+
}
|
|
476
|
+
const totalUsage = formatUsageStats(aggregateUsage(details.results));
|
|
477
|
+
if (totalUsage) {
|
|
478
|
+
container.addChild(new Text(fg("dim", `Total: ${totalUsage}`), 0, 0));
|
|
479
|
+
}
|
|
480
|
+
box.addChild(container);
|
|
481
|
+
return box;
|
|
482
|
+
}
|