@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +15 -5
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +18 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +4 -0
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-registry.ts +25 -2
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +20 -2
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/llm-bridge.ts +8 -3
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +9 -7
- package/src/memories/index.ts +12 -5
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +23 -0
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +124 -119
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +169 -94
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +18 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +11 -37
- package/src/session/agent-session.ts +82 -6
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +80 -78
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +81 -62
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../extensibility/plugins/marketplace";
|
|
22
22
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
23
23
|
import type { InteractiveModeContext } from "../modes/types";
|
|
24
|
+
import type { FreshSessionResult } from "../session/agent-session";
|
|
24
25
|
import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
25
26
|
import { getChangelogPath, parseChangelog } from "../utils/changelog";
|
|
26
27
|
import { buildContextReportText } from "./helpers/context-report";
|
|
@@ -52,6 +53,11 @@ function refreshStatusLine(ctx: InteractiveModeContext): void {
|
|
|
52
53
|
ctx.ui.requestRender();
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
function formatFreshSessionResult(result: FreshSessionResult): string {
|
|
57
|
+
const stateLabel = result.closedProviderSessions === 1 ? "provider state" : "provider states";
|
|
58
|
+
return `Fresh provider session started (${result.closedProviderSessions} ${stateLabel} pruned).`;
|
|
59
|
+
}
|
|
60
|
+
|
|
55
61
|
const shutdownHandlerTui = (_command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime): SlashCommandResult => {
|
|
56
62
|
runtime.ctx.editor.setText("");
|
|
57
63
|
void runtime.ctx.shutdown();
|
|
@@ -770,6 +776,25 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
770
776
|
await runtime.ctx.handleClearCommand();
|
|
771
777
|
},
|
|
772
778
|
},
|
|
779
|
+
{
|
|
780
|
+
name: "fresh",
|
|
781
|
+
description: "Reset provider stream state without changing the local transcript",
|
|
782
|
+
handle: async (_command, runtime) => {
|
|
783
|
+
const result = runtime.session.freshSession();
|
|
784
|
+
if (!result) {
|
|
785
|
+
await runtime.output(
|
|
786
|
+
"Wait for the current response to finish or abort it before refreshing provider state.",
|
|
787
|
+
);
|
|
788
|
+
return commandConsumed();
|
|
789
|
+
}
|
|
790
|
+
await runtime.output(formatFreshSessionResult(result));
|
|
791
|
+
return commandConsumed();
|
|
792
|
+
},
|
|
793
|
+
handleTui: async (_command, runtime) => {
|
|
794
|
+
runtime.ctx.editor.setText("");
|
|
795
|
+
await runtime.ctx.handleFreshCommand();
|
|
796
|
+
},
|
|
797
|
+
},
|
|
773
798
|
{
|
|
774
799
|
name: "drop",
|
|
775
800
|
description: "Delete the current session and start a new one",
|
|
@@ -868,6 +893,17 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
868
893
|
await runtime.ctx.handleBtwCommand(question);
|
|
869
894
|
},
|
|
870
895
|
},
|
|
896
|
+
{
|
|
897
|
+
name: "tan",
|
|
898
|
+
description: "Run a full background agent on tangential work",
|
|
899
|
+
inlineHint: "<work>",
|
|
900
|
+
allowArgs: true,
|
|
901
|
+
handleTui: async (command, runtime) => {
|
|
902
|
+
const work = command.text.slice(`/${command.name}`.length).trim();
|
|
903
|
+
runtime.ctx.editor.setText("");
|
|
904
|
+
await runtime.ctx.handleTanCommand(work);
|
|
905
|
+
},
|
|
906
|
+
},
|
|
871
907
|
{
|
|
872
908
|
name: "omfg",
|
|
873
909
|
description: "Forge a TTSR rule from a complaint to stop a recurring behavior",
|
|
@@ -890,15 +926,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
890
926
|
runtime.ctx.editor.setText("");
|
|
891
927
|
},
|
|
892
928
|
},
|
|
893
|
-
{
|
|
894
|
-
name: "background",
|
|
895
|
-
aliases: ["bg"],
|
|
896
|
-
description: "Detach UI and continue running in background",
|
|
897
|
-
handleTui: (_command, runtime) => {
|
|
898
|
-
runtime.ctx.editor.setText("");
|
|
899
|
-
runtime.handleBackgroundCommand();
|
|
900
|
-
},
|
|
901
|
-
},
|
|
902
929
|
{
|
|
903
930
|
name: "debug",
|
|
904
931
|
description: "Open debug tools selector",
|
|
@@ -71,15 +71,13 @@ export interface SlashCommandRuntime {
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Runtime visible to TUI-only handlers (`handleTui`). Carries the interactive
|
|
74
|
-
* mode context
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* from `ctx`.
|
|
74
|
+
* mode context. Intentionally narrower than `SlashCommandRuntime` so existing
|
|
75
|
+
* callers can keep building it from just `{ ctx }`; when the TUI dispatcher
|
|
76
|
+
* needs to invoke a `handle` (no `handleTui` override), it synthesizes a
|
|
77
|
+
* `SlashCommandRuntime` from `ctx`.
|
|
79
78
|
*/
|
|
80
79
|
export interface TuiSlashCommandRuntime {
|
|
81
80
|
ctx: InteractiveModeContext;
|
|
82
|
-
handleBackgroundCommand: () => void;
|
|
83
81
|
}
|
|
84
82
|
|
|
85
83
|
/** Unified slash-command spec consumed by both TUI and ACP dispatchers. */
|
package/src/task/executor.ts
CHANGED
|
@@ -488,7 +488,7 @@ function getUsageTokens(usage: unknown): number {
|
|
|
488
488
|
/**
|
|
489
489
|
* Create proxy tools that reuse the parent's MCP connections.
|
|
490
490
|
*/
|
|
491
|
-
function createMCPProxyTools(mcpManager: MCPManager): CustomTool[] {
|
|
491
|
+
export function createMCPProxyTools(mcpManager: MCPManager): CustomTool[] {
|
|
492
492
|
return mcpManager.getTools().map(tool => {
|
|
493
493
|
const mcpTool = tool as { mcpToolName?: string; mcpServerName?: string };
|
|
494
494
|
return {
|
|
@@ -538,7 +538,10 @@ function createMCPProxyTools(mcpManager: MCPManager): CustomTool[] {
|
|
|
538
538
|
});
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
function createSubagentSettings(
|
|
541
|
+
export function createSubagentSettings(
|
|
542
|
+
baseSettings: Settings,
|
|
543
|
+
overrides?: Partial<Record<SettingPath, unknown>>,
|
|
544
|
+
): Settings {
|
|
542
545
|
const snapshot: Partial<Record<SettingPath, unknown>> = {};
|
|
543
546
|
for (const key of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
|
|
544
547
|
snapshot[key] = baseSettings.get(key);
|
package/src/task/index.ts
CHANGED
|
@@ -275,6 +275,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
275
275
|
readonly strict = true;
|
|
276
276
|
readonly loadMode = "discoverable";
|
|
277
277
|
readonly renderResult = renderResult;
|
|
278
|
+
// Suppress the streaming call preview once a (partial or final) result exists
|
|
279
|
+
// so the task renders as ONE block that transitions in place — not a pending
|
|
280
|
+
// call frame stacked above the result frame. Mirrors `taskToolRenderer`.
|
|
281
|
+
readonly mergeCallAndResult = true;
|
|
278
282
|
readonly #discoveredAgents: AgentDefinition[];
|
|
279
283
|
readonly #blockedAgent: string | undefined;
|
|
280
284
|
|
package/src/task/render.ts
CHANGED
|
@@ -6,18 +6,20 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
9
|
-
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { Container, Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
11
11
|
import { settings } from "../config/settings";
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
14
|
-
import
|
|
14
|
+
import { shimmerEnabled, shimmerText } from "../modes/theme/shimmer";
|
|
15
|
+
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
15
16
|
import {
|
|
16
17
|
formatBadge,
|
|
17
18
|
formatDuration,
|
|
18
19
|
formatMoreItems,
|
|
19
20
|
formatStatusIcon,
|
|
20
21
|
replaceTabs,
|
|
22
|
+
type ToolUIStatus,
|
|
21
23
|
truncateToWidth,
|
|
22
24
|
} from "../tools/render-utils";
|
|
23
25
|
import {
|
|
@@ -28,7 +30,8 @@ import {
|
|
|
28
30
|
type ReportFindingDetails,
|
|
29
31
|
type SubmitReviewDetails,
|
|
30
32
|
} from "../tools/review";
|
|
31
|
-
import {
|
|
33
|
+
import { framedBlock, renderStatusLine } from "../tui";
|
|
34
|
+
import { repairDoubleEncodedJsonString } from "./repair-args";
|
|
32
35
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
33
36
|
import type { AgentProgress, SingleResult, TaskItem, TaskParams, TaskToolDetails } from "./types";
|
|
34
37
|
|
|
@@ -506,28 +509,20 @@ function formatOutputInline(data: unknown, theme: Theme, maxWidth = 80): string
|
|
|
506
509
|
* preview. The args stream in token by token, so the array grows over time and
|
|
507
510
|
* trailing entries may be partially parsed — every field access is defensive.
|
|
508
511
|
*/
|
|
509
|
-
function renderTaskItemLines(
|
|
510
|
-
tasks: TaskItem[] | undefined,
|
|
511
|
-
contPrefix: string,
|
|
512
|
-
expanded: boolean,
|
|
513
|
-
theme: Theme,
|
|
514
|
-
): string[] {
|
|
512
|
+
function renderTaskItemLines(tasks: TaskItem[] | undefined, expanded: boolean, theme: Theme): string[] {
|
|
515
513
|
const items = tasks ?? [];
|
|
516
514
|
if (items.length === 0) return [];
|
|
517
515
|
|
|
518
|
-
const
|
|
519
|
-
const last = theme.fg("dim", theme.tree.last);
|
|
516
|
+
const bullet = theme.fg("dim", "•");
|
|
520
517
|
const cap = expanded ? items.length : Math.min(items.length, 12);
|
|
521
518
|
const truncated = cap < items.length;
|
|
522
519
|
|
|
523
520
|
const lines: string[] = [];
|
|
524
521
|
for (let i = 0; i < cap; i++) {
|
|
525
522
|
const task = items[i] as Partial<TaskItem> | undefined;
|
|
526
|
-
const isLastLine = !truncated && i === items.length - 1;
|
|
527
|
-
const connector = isLastLine ? last : branch;
|
|
528
523
|
const rawId = task?.id?.trim();
|
|
529
524
|
const idLabel = rawId ? formatTaskId(rawId) : `#${i + 1}`;
|
|
530
|
-
let line = `${
|
|
525
|
+
let line = `${bullet} ${theme.fg("accent", theme.bold(idLabel))}`;
|
|
531
526
|
const desc = task?.description?.trim();
|
|
532
527
|
if (desc) {
|
|
533
528
|
line += `: ${theme.fg("muted", truncateToWidth(replaceTabs(desc), 64))}`;
|
|
@@ -535,11 +530,42 @@ function renderTaskItemLines(
|
|
|
535
530
|
lines.push(line);
|
|
536
531
|
}
|
|
537
532
|
if (truncated) {
|
|
538
|
-
lines.push(`${
|
|
533
|
+
lines.push(`${bullet} ${theme.fg("dim", formatMoreItems(items.length - cap, "agent"))}`);
|
|
539
534
|
}
|
|
540
535
|
return lines;
|
|
541
536
|
}
|
|
542
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Build the shared-context section (the `# Goal / # Constraints` background
|
|
540
|
+
* passed to every subagent). Rendered in both the streaming call preview and
|
|
541
|
+
* the merged result frame so the brief stays visible for the whole task
|
|
542
|
+
* lifecycle — not just until the first progress snapshot replaces the call view.
|
|
543
|
+
*/
|
|
544
|
+
type TaskRenderSection = { lines: string[] };
|
|
545
|
+
type ContextSectionRenderer = (width: number) => TaskRenderSection;
|
|
546
|
+
|
|
547
|
+
// Default output-block layout is: left border + one-cell content inset + right
|
|
548
|
+
// border. Render markdown at that inner width so the output block does not need
|
|
549
|
+
// to rewrap already-rendered context lines.
|
|
550
|
+
const CONTEXT_FRAME_INSET = 3;
|
|
551
|
+
|
|
552
|
+
function contextMarkdownWidth(frameWidth: number): number {
|
|
553
|
+
return Math.max(1, frameWidth - CONTEXT_FRAME_INSET);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function createContextSectionRenderer(args: TaskParams | undefined, theme: Theme): ContextSectionRenderer | undefined {
|
|
557
|
+
// `renderResult` receives the raw tool args (unlike `renderCall`, which is
|
|
558
|
+
// fed through `repairTaskParams`), so undo any per-field double-encoding here
|
|
559
|
+
// too. The repair is idempotent on already-clean text.
|
|
560
|
+
const context = repairDoubleEncodedJsonString(args?.context ?? "").trim();
|
|
561
|
+
if (!context) return undefined;
|
|
562
|
+
|
|
563
|
+
const markdown = new Markdown(context, 0, 0, getMarkdownTheme(), {
|
|
564
|
+
color: text => theme.fg("muted", text),
|
|
565
|
+
});
|
|
566
|
+
return width => ({ lines: markdown.render(contextMarkdownWidth(width)) });
|
|
567
|
+
}
|
|
568
|
+
|
|
543
569
|
/**
|
|
544
570
|
* Render the tool call arguments.
|
|
545
571
|
*/
|
|
@@ -548,44 +574,34 @@ export function renderCall(
|
|
|
548
574
|
options: RenderResultOptions & { renderContext?: { hasResult?: boolean } },
|
|
549
575
|
theme: Theme,
|
|
550
576
|
): Component {
|
|
551
|
-
const lines: string[] = [];
|
|
552
|
-
lines.push(renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme));
|
|
553
|
-
|
|
554
|
-
const context = (args.context ?? "").trim();
|
|
555
|
-
const hasContext = context.length > 0;
|
|
556
|
-
const branch = theme.fg("dim", theme.tree.branch);
|
|
557
|
-
const last = theme.fg("dim", theme.tree.last);
|
|
558
|
-
const vertical = theme.fg("dim", theme.tree.vertical);
|
|
559
577
|
const showIsolated = "isolated" in args && args.isolated === true;
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
lines
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// the call args stream in. Once a result snapshot exists, `renderResult`
|
|
578
|
-
// draws the same agents as progress/result lines (id + description), so
|
|
579
|
-
// emitting the preview here would render every task twice.
|
|
580
|
-
if (!options.renderContext?.hasResult) {
|
|
581
|
-
lines.push(...renderTaskItemLines(args.tasks, tasksContPrefix, options.expanded, theme));
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (showIsolated) {
|
|
585
|
-
lines.push(` ${last} ${theme.fg("dim", "Isolated")}: ${theme.fg("muted", "true")}`);
|
|
586
|
-
}
|
|
578
|
+
const header = renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme);
|
|
579
|
+
const contextSectionRenderer = createContextSectionRenderer(args, theme);
|
|
580
|
+
return framedBlock(theme, width => {
|
|
581
|
+
const sections: Array<{ label?: string; lines: string[]; separator?: boolean }> = [];
|
|
582
|
+
|
|
583
|
+
if (contextSectionRenderer) sections.push(contextSectionRenderer(width));
|
|
584
|
+
|
|
585
|
+
// The per-task preview list only exists to surface dispatched agents while
|
|
586
|
+
// the call args stream in. Once a result snapshot exists, `renderResult`
|
|
587
|
+
// draws the same agents as progress/result lines, so showing the Tasks
|
|
588
|
+
// section here would just repeat the count the result frame already shows.
|
|
589
|
+
if (!options.renderContext?.hasResult) {
|
|
590
|
+
sections.push({
|
|
591
|
+
separator: true,
|
|
592
|
+
lines: renderTaskItemLines(args.tasks, options.expanded, theme),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
587
595
|
|
|
588
|
-
|
|
596
|
+
return {
|
|
597
|
+
header,
|
|
598
|
+
headerMeta: showIsolated ? "isolated" : undefined,
|
|
599
|
+
sections,
|
|
600
|
+
state: "pending",
|
|
601
|
+
borderColor: "borderMuted",
|
|
602
|
+
width,
|
|
603
|
+
};
|
|
604
|
+
});
|
|
589
605
|
}
|
|
590
606
|
|
|
591
607
|
/**
|
|
@@ -593,14 +609,13 @@ export function renderCall(
|
|
|
593
609
|
*/
|
|
594
610
|
function renderAgentProgress(
|
|
595
611
|
progress: AgentProgress,
|
|
596
|
-
|
|
612
|
+
prefix: string,
|
|
613
|
+
continuePrefix: string,
|
|
597
614
|
expanded: boolean,
|
|
598
615
|
theme: Theme,
|
|
599
616
|
spinnerFrame?: number,
|
|
600
617
|
): string[] {
|
|
601
618
|
const lines: string[] = [];
|
|
602
|
-
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
603
|
-
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
604
619
|
|
|
605
620
|
const icon = getStatusIcon(progress.status, theme, spinnerFrame);
|
|
606
621
|
const iconColor =
|
|
@@ -614,11 +629,24 @@ function renderAgentProgress(
|
|
|
614
629
|
const description = progress.description?.trim();
|
|
615
630
|
const displayId = formatTaskId(progress.id);
|
|
616
631
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
617
|
-
|
|
632
|
+
const indent = prefix ? `${prefix} ` : "";
|
|
633
|
+
let statusLine: string;
|
|
634
|
+
if (progress.status === "running") {
|
|
635
|
+
const bullet = theme.fg("accent", "•");
|
|
636
|
+
const name = shimmerEnabled()
|
|
637
|
+
? shimmerText(displayId, theme)
|
|
638
|
+
: theme.fg("accent", description ? theme.bold(displayId) : displayId);
|
|
639
|
+
statusLine = `${indent}${bullet} ${name}`;
|
|
640
|
+
if (description) {
|
|
641
|
+
statusLine += theme.fg("accent", `: ${description}`);
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
statusLine = `${indent}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
|
|
645
|
+
}
|
|
618
646
|
|
|
619
647
|
// Show retry-blocked badge so the parent immediately sees that a child
|
|
620
648
|
// is sleeping on a provider 429, not silently progressing. Wins over the
|
|
621
|
-
// generic running
|
|
649
|
+
// generic running marker because "we're waiting on a quota window" is
|
|
622
650
|
// the operationally meaningful state.
|
|
623
651
|
if (progress.retryState && progress.status === "running") {
|
|
624
652
|
statusLine += ` ${formatBadge("retrying", "warning", theme)}`;
|
|
@@ -867,10 +895,14 @@ function renderFindings(
|
|
|
867
895
|
/**
|
|
868
896
|
* Render final result for a single agent.
|
|
869
897
|
*/
|
|
870
|
-
function renderAgentResult(
|
|
898
|
+
function renderAgentResult(
|
|
899
|
+
result: SingleResult,
|
|
900
|
+
prefix: string,
|
|
901
|
+
continuePrefix: string,
|
|
902
|
+
expanded: boolean,
|
|
903
|
+
theme: Theme,
|
|
904
|
+
): string[] {
|
|
871
905
|
const lines: string[] = [];
|
|
872
|
-
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
873
|
-
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
874
906
|
|
|
875
907
|
const { warning: missingCompleteWarning, rest: outputWithoutWarning } = extractMissingYieldWarning(result.output);
|
|
876
908
|
const aborted = result.aborted ?? false;
|
|
@@ -899,7 +931,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
899
931
|
const description = result.description?.trim();
|
|
900
932
|
const displayId = formatTaskId(result.id);
|
|
901
933
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
902
|
-
let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)} ${formatBadge(
|
|
934
|
+
let statusLine = `${prefix ? `${prefix} ` : ""}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)} ${formatBadge(
|
|
903
935
|
statusText,
|
|
904
936
|
iconColor,
|
|
905
937
|
theme,
|
|
@@ -1043,101 +1075,123 @@ export function renderResult(
|
|
|
1043
1075
|
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails },
|
|
1044
1076
|
options: RenderResultOptions,
|
|
1045
1077
|
theme: Theme,
|
|
1078
|
+
args?: TaskParams,
|
|
1046
1079
|
): Component {
|
|
1047
1080
|
const fallbackText = result.content.find(c => c.type === "text")?.text ?? "";
|
|
1048
1081
|
const details = result.details;
|
|
1082
|
+
const contextSectionRenderer = createContextSectionRenderer(args, theme);
|
|
1049
1083
|
|
|
1050
1084
|
if (!details) {
|
|
1051
1085
|
const text = result.content.find(c => c.type === "text")?.text || "";
|
|
1052
|
-
|
|
1086
|
+
const header = renderStatusLine({ icon: "success", title: "Task" }, theme);
|
|
1087
|
+
return framedBlock(theme, width => ({
|
|
1088
|
+
header,
|
|
1089
|
+
sections: [
|
|
1090
|
+
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
|
|
1091
|
+
...(text ? [{ separator: true, lines: [theme.fg("dim", truncateToWidth(text, width))] }] : []),
|
|
1092
|
+
],
|
|
1093
|
+
state: "success",
|
|
1094
|
+
borderColor: "borderMuted",
|
|
1095
|
+
width,
|
|
1096
|
+
}));
|
|
1053
1097
|
}
|
|
1054
1098
|
|
|
1055
|
-
|
|
1099
|
+
const hasResults = Boolean(details.results && details.results.length > 0);
|
|
1100
|
+
const aborted = hasResults && details.results.some(r => r.aborted);
|
|
1101
|
+
const failed = hasResults && details.results.some(r => !r.aborted && r.exitCode !== 0);
|
|
1102
|
+
const mergeFailed = hasResults && details.results.some(r => !r.aborted && r.exitCode === 0 && Boolean(r.error));
|
|
1103
|
+
const isError = aborted || failed;
|
|
1104
|
+
const agentCount = hasResults ? details.results.length : (details.progress?.length ?? 0);
|
|
1105
|
+
const icon: ToolUIStatus = options.isPartial ? "running" : isError ? "error" : mergeFailed ? "warning" : "success";
|
|
1106
|
+
const header = renderStatusLine(
|
|
1107
|
+
{
|
|
1108
|
+
icon,
|
|
1109
|
+
title: "Task",
|
|
1110
|
+
meta: agentCount > 0 ? [`${agentCount} ${agentCount === 1 ? "agent" : "agents"}`] : undefined,
|
|
1111
|
+
},
|
|
1112
|
+
theme,
|
|
1113
|
+
);
|
|
1056
1114
|
|
|
1057
|
-
return {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const key = new Hasher()
|
|
1061
|
-
.bool(expanded)
|
|
1062
|
-
.bool(isPartial)
|
|
1063
|
-
.u32(spinnerFrame ?? 0)
|
|
1064
|
-
.u32(width)
|
|
1065
|
-
.digest();
|
|
1066
|
-
if (cached?.key === key) return cached.lines;
|
|
1067
|
-
|
|
1068
|
-
const lines: string[] = [];
|
|
1069
|
-
|
|
1070
|
-
const shouldRenderProgress =
|
|
1071
|
-
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1072
|
-
if (shouldRenderProgress && details.progress) {
|
|
1073
|
-
details.progress.forEach((progress, i) => {
|
|
1074
|
-
const isLast = i === details.progress!.length - 1;
|
|
1075
|
-
lines.push(...renderAgentProgress(progress, isLast, expanded, theme, spinnerFrame));
|
|
1076
|
-
});
|
|
1077
|
-
} else if (details.results && details.results.length > 0) {
|
|
1078
|
-
details.results.forEach((res, i) => {
|
|
1079
|
-
const isLast = i === details.results.length - 1;
|
|
1080
|
-
lines.push(...renderAgentResult(res, isLast, expanded, theme));
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
1084
|
-
const mergeFailedCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && r.error).length;
|
|
1085
|
-
const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && !r.error).length;
|
|
1086
|
-
const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
|
|
1087
|
-
let summary = `${theme.fg("dim", "Total:")} `;
|
|
1088
|
-
if (abortedCount > 0) {
|
|
1089
|
-
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
1090
|
-
if (successCount > 0 || mergeFailedCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
1091
|
-
}
|
|
1092
|
-
if (successCount > 0) {
|
|
1093
|
-
summary += theme.fg("success", `${successCount} succeeded`);
|
|
1094
|
-
if (mergeFailedCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
1095
|
-
}
|
|
1096
|
-
if (mergeFailedCount > 0) {
|
|
1097
|
-
summary += theme.fg("warning", `${mergeFailedCount} merge failed`);
|
|
1098
|
-
if (failCount > 0) summary += theme.sep.dot;
|
|
1099
|
-
}
|
|
1100
|
-
if (failCount > 0) {
|
|
1101
|
-
summary += theme.fg("error", `${failCount} failed`);
|
|
1102
|
-
}
|
|
1103
|
-
summary += `${theme.sep.dot}${theme.fg("dim", formatDuration(details.totalDurationMs))}`;
|
|
1104
|
-
lines.push(summary);
|
|
1105
|
-
}
|
|
1115
|
+
return framedBlock(theme, width => {
|
|
1116
|
+
const { expanded, isPartial, spinnerFrame } = options;
|
|
1117
|
+
const lines: string[] = [];
|
|
1106
1118
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1119
|
+
const shouldRenderProgress =
|
|
1120
|
+
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1121
|
+
if (shouldRenderProgress && details.progress) {
|
|
1122
|
+
details.progress.forEach(progress => {
|
|
1123
|
+
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame));
|
|
1124
|
+
});
|
|
1125
|
+
} else if (details.results && details.results.length > 0) {
|
|
1126
|
+
details.results.forEach(res => {
|
|
1127
|
+
lines.push(...renderAgentResult(res, "", " ", expanded, theme));
|
|
1128
|
+
});
|
|
1113
1129
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1130
|
+
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
1131
|
+
const mergeFailedCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && r.error).length;
|
|
1132
|
+
const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && !r.error).length;
|
|
1133
|
+
const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
|
|
1134
|
+
const summaryParts: string[] = [];
|
|
1135
|
+
if (abortedCount > 0) summaryParts.push(theme.fg("error", `${abortedCount} aborted`));
|
|
1136
|
+
if (successCount > 0) summaryParts.push(theme.fg("success", `${successCount} succeeded`));
|
|
1137
|
+
if (mergeFailedCount > 0) summaryParts.push(theme.fg("warning", `${mergeFailedCount} merge failed`));
|
|
1138
|
+
if (failCount > 0) summaryParts.push(theme.fg("error", `${failCount} failed`));
|
|
1139
|
+
summaryParts.push(theme.fg("dim", formatDuration(details.totalDurationMs)));
|
|
1140
|
+
// Wrap the run summary in the theme's bracket glyphs (dim chrome, colored
|
|
1141
|
+
// counts) to match the bash tool's `[Wall: … | Exit: …]` footer.
|
|
1142
|
+
lines.push(
|
|
1143
|
+
theme.fg("dim", theme.format.bracketLeft) +
|
|
1144
|
+
summaryParts.join(theme.fg("dim", theme.sep.dot)) +
|
|
1145
|
+
theme.fg("dim", theme.format.bracketRight),
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const state = isPartial ? "running" : isError ? "error" : mergeFailed ? "warning" : "success";
|
|
1150
|
+
const borderColor = isError ? "error" : "borderMuted";
|
|
1151
|
+
|
|
1152
|
+
if (lines.length === 0) {
|
|
1153
|
+
const text = fallbackText.trim() ? fallbackText : "No results";
|
|
1154
|
+
return {
|
|
1155
|
+
header,
|
|
1156
|
+
sections: [
|
|
1157
|
+
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
|
|
1158
|
+
{ separator: true, lines: [theme.fg("dim", truncateToWidth(text, width))] },
|
|
1159
|
+
],
|
|
1160
|
+
state,
|
|
1161
|
+
borderColor,
|
|
1162
|
+
width,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (fallbackText.trim()) {
|
|
1167
|
+
const summaryLines = fallbackText.split("\n");
|
|
1168
|
+
const markerIndex = summaryLines.findIndex(
|
|
1169
|
+
line =>
|
|
1170
|
+
line.includes("<system-notification>") ||
|
|
1171
|
+
line.startsWith("Applied patches:") ||
|
|
1172
|
+
line.startsWith("No changes to apply."),
|
|
1173
|
+
);
|
|
1174
|
+
if (markerIndex >= 0) {
|
|
1175
|
+
const extra = summaryLines.slice(markerIndex);
|
|
1176
|
+
for (const line of extra) {
|
|
1177
|
+
if (!line.trim()) continue;
|
|
1178
|
+
lines.push(theme.fg("dim", line));
|
|
1128
1179
|
}
|
|
1129
1180
|
}
|
|
1181
|
+
}
|
|
1130
1182
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1183
|
+
while (lines.length > 0 && lines[0].trim() === "") lines.shift();
|
|
1184
|
+
return {
|
|
1185
|
+
header,
|
|
1186
|
+
sections: [
|
|
1187
|
+
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
|
|
1188
|
+
...(lines.length > 0 ? [{ separator: true, lines }] : []),
|
|
1189
|
+
],
|
|
1190
|
+
state,
|
|
1191
|
+
borderColor,
|
|
1192
|
+
width,
|
|
1193
|
+
};
|
|
1194
|
+
});
|
|
1141
1195
|
}
|
|
1142
1196
|
|
|
1143
1197
|
function isTaskToolDetails(value: unknown): value is TaskToolDetails {
|
|
@@ -1149,13 +1203,23 @@ function isTaskToolDetails(value: unknown): value is TaskToolDetails {
|
|
|
1149
1203
|
);
|
|
1150
1204
|
}
|
|
1151
1205
|
|
|
1206
|
+
// Nested subagent snapshots sit one or more levels below the frame border, so
|
|
1207
|
+
// they keep tree guides to convey depth (the parent prepends its own continue
|
|
1208
|
+
// prefix). Only the top-level agent list drops guides (the frame is its box).
|
|
1209
|
+
function nestedMarkers(isLast: boolean, theme: Theme): { prefix: string; continuePrefix: string } {
|
|
1210
|
+
return {
|
|
1211
|
+
prefix: isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch),
|
|
1212
|
+
continuePrefix: isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `,
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1152
1216
|
function renderNestedTaskResults(detailsList: TaskToolDetails[], expanded: boolean, theme: Theme): string[] {
|
|
1153
1217
|
const lines: string[] = [];
|
|
1154
1218
|
for (const details of detailsList) {
|
|
1155
1219
|
if (!details.results || details.results.length === 0) continue;
|
|
1156
1220
|
details.results.forEach((result, index) => {
|
|
1157
|
-
const
|
|
1158
|
-
lines.push(...renderAgentResult(result,
|
|
1221
|
+
const { prefix, continuePrefix } = nestedMarkers(index === details.results.length - 1, theme);
|
|
1222
|
+
lines.push(...renderAgentResult(result, prefix, continuePrefix, expanded, theme));
|
|
1159
1223
|
});
|
|
1160
1224
|
}
|
|
1161
1225
|
return lines;
|
|
@@ -1177,16 +1241,16 @@ function renderNestedTaskTree(
|
|
|
1177
1241
|
const hasResults = Boolean(details.results && details.results.length > 0);
|
|
1178
1242
|
if (hasResults) {
|
|
1179
1243
|
details.results.forEach((result, index) => {
|
|
1180
|
-
const
|
|
1181
|
-
lines.push(...renderAgentResult(result,
|
|
1244
|
+
const { prefix, continuePrefix } = nestedMarkers(index === details.results.length - 1, theme);
|
|
1245
|
+
lines.push(...renderAgentResult(result, prefix, continuePrefix, expanded, theme));
|
|
1182
1246
|
});
|
|
1183
1247
|
continue;
|
|
1184
1248
|
}
|
|
1185
1249
|
const inflight = details.progress;
|
|
1186
1250
|
if (inflight && inflight.length > 0) {
|
|
1187
1251
|
inflight.forEach((prog, index) => {
|
|
1188
|
-
const
|
|
1189
|
-
lines.push(...renderAgentProgress(prog,
|
|
1252
|
+
const { prefix, continuePrefix } = nestedMarkers(index === inflight.length - 1, theme);
|
|
1253
|
+
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame));
|
|
1190
1254
|
});
|
|
1191
1255
|
}
|
|
1192
1256
|
}
|
|
@@ -1207,4 +1271,5 @@ subprocessToolRegistry.register<TaskToolDetails>("task", {
|
|
|
1207
1271
|
export const taskToolRenderer = {
|
|
1208
1272
|
renderCall,
|
|
1209
1273
|
renderResult,
|
|
1274
|
+
mergeCallAndResult: true,
|
|
1210
1275
|
};
|