@oh-my-pi/pi-coding-agent 14.9.9 → 15.0.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 +123 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +9 -9
- package/scripts/build-binary.ts +5 -0
- package/scripts/format-prompts.ts +1 -1
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +11 -29
- package/src/commands/acp.ts +24 -0
- package/src/commands/launch.ts +6 -4
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +13 -2
- package/src/config/model-resolver.ts +31 -4
- package/src/config/settings-schema.ts +102 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/edit/index.ts +22 -1
- package/src/edit/modes/patch.ts +10 -0
- package/src/edit/modes/replace.ts +3 -0
- package/src/edit/renderer.ts +17 -1
- package/src/eval/js/context-manager.ts +1 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +122 -50
- package/src/eval/js/shared/runtime.ts +31 -4
- package/src/eval/js/tool-bridge.ts +43 -21
- package/src/eval/py/executor.ts +5 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/runner.ts +55 -2
- package/src/extensibility/extensions/types.ts +98 -221
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +42 -1
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +9 -10
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/issue-pr-protocol.ts +577 -0
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +6 -3
- package/src/internal-urls/types.ts +22 -1
- package/src/main.ts +24 -11
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +412 -71
- package/src/modes/acp/acp-client-bridge.ts +152 -0
- package/src/modes/acp/acp-event-mapper.ts +180 -15
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/read-tool-group.ts +29 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +55 -4
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +27 -10
- package/src/modes/controllers/event-controller.ts +60 -18
- package/src/modes/controllers/extension-ui-controller.ts +8 -2
- package/src/modes/controllers/input-controller.ts +85 -39
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +675 -39
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/rpc-mode.ts +30 -88
- package/src/modes/runtime-init.ts +115 -0
- package/src/modes/theme/defaults/dark-poimandres.json +2 -0
- package/src/modes/theme/defaults/light-poimandres.json +2 -0
- package/src/modes/theme/theme.ts +18 -6
- package/src/modes/types.ts +20 -5
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +25 -6
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/agents/designer.md +5 -5
- package/src/prompts/agents/explore.md +7 -7
- package/src/prompts/agents/init.md +9 -9
- package/src/prompts/agents/librarian.md +14 -14
- package/src/prompts/agents/plan.md +4 -4
- package/src/prompts/agents/reviewer.md +5 -5
- package/src/prompts/agents/task.md +10 -10
- package/src/prompts/commands/orchestrate.md +2 -2
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/memories/consolidation.md +2 -2
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +2 -2
- package/src/prompts/system/custom-system-prompt.md +2 -2
- package/src/prompts/system/eager-todo.md +6 -6
- package/src/prompts/system/handoff-document.md +1 -1
- package/src/prompts/system/plan-mode-active.md +25 -24
- package/src/prompts/system/plan-mode-approved.md +4 -4
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +3 -3
- package/src/prompts/system/project-prompt.md +4 -4
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/subagent-yield-reminder.md +4 -4
- package/src/prompts/system/system-prompt.md +72 -71
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/tools/apply-patch.md +1 -1
- package/src/prompts/tools/ast-edit.md +3 -3
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/browser.md +3 -3
- package/src/prompts/tools/checkpoint.md +3 -3
- package/src/prompts/tools/find.md +3 -3
- package/src/prompts/tools/github.md +2 -5
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +104 -116
- package/src/prompts/tools/image-gen.md +3 -3
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +6 -6
- package/src/prompts/tools/read.md +8 -7
- package/src/prompts/tools/replace.md +5 -5
- package/src/prompts/tools/resolve.md +6 -5
- package/src/prompts/tools/retain.md +1 -1
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search.md +2 -2
- package/src/prompts/tools/ssh.md +2 -2
- package/src/prompts/tools/task.md +12 -6
- package/src/prompts/tools/web-search.md +2 -2
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +81 -17
- package/src/session/agent-session.ts +656 -125
- package/src/session/blob-store.ts +36 -3
- package/src/session/client-bridge.ts +81 -0
- package/src/session/compaction/errors.ts +31 -0
- package/src/session/compaction/index.ts +1 -0
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/acp-builtins.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +717 -116
- package/src/slash-commands/helpers/context-report.ts +39 -0
- package/src/slash-commands/helpers/format.ts +23 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +193 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +91 -0
- package/src/slash-commands/types.ts +126 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/executor.ts +27 -10
- package/src/task/index.ts +20 -1
- package/src/task/render.ts +27 -18
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +203 -6
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +21 -10
- package/src/tools/eval.ts +3 -1
- package/src/tools/fetch.ts +15 -4
- package/src/tools/find.ts +39 -39
- package/src/tools/gh-renderer.ts +0 -12
- package/src/tools/gh.ts +689 -182
- package/src/tools/github-cache.ts +548 -0
- package/src/tools/index.ts +25 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/output-meta.ts +176 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +605 -239
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/write.ts +67 -10
- package/src/tui/code-cell.ts +70 -2
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/gemini.ts +35 -95
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP-side `ClientBridge` implementation. Wraps `AgentSideConnection` so the
|
|
3
|
+
* `read`/`write`/`bash`/`edit` tools (and the permission gate in
|
|
4
|
+
* `AgentSession`) can route through the client when it advertises the
|
|
5
|
+
* relevant capabilities at `initialize` time.
|
|
6
|
+
*/
|
|
7
|
+
import type {
|
|
8
|
+
PermissionOption as AcpPermissionOption,
|
|
9
|
+
TerminalHandle as AcpTerminalHandle,
|
|
10
|
+
AgentSideConnection,
|
|
11
|
+
ClientCapabilities,
|
|
12
|
+
RequestPermissionRequest,
|
|
13
|
+
ToolCallUpdate,
|
|
14
|
+
} from "@agentclientprotocol/sdk";
|
|
15
|
+
import type {
|
|
16
|
+
ClientBridge,
|
|
17
|
+
ClientBridgeCapabilities,
|
|
18
|
+
ClientBridgeCreateTerminalParams,
|
|
19
|
+
ClientBridgePermissionOption,
|
|
20
|
+
ClientBridgePermissionOutcome,
|
|
21
|
+
ClientBridgePermissionToolCall,
|
|
22
|
+
ClientBridgeTerminalHandle,
|
|
23
|
+
} from "../../session/client-bridge";
|
|
24
|
+
|
|
25
|
+
export function createAcpClientBridge(
|
|
26
|
+
connection: AgentSideConnection,
|
|
27
|
+
sessionId: string,
|
|
28
|
+
clientCapabilities: ClientCapabilities | undefined,
|
|
29
|
+
): ClientBridge {
|
|
30
|
+
const capabilities: ClientBridgeCapabilities = {
|
|
31
|
+
readTextFile: clientCapabilities?.fs?.readTextFile === true,
|
|
32
|
+
writeTextFile: clientCapabilities?.fs?.writeTextFile === true,
|
|
33
|
+
terminal: clientCapabilities?.terminal === true,
|
|
34
|
+
// Permission requests are always usable on the connection; gating is
|
|
35
|
+
// the agent's policy choice rather than a client capability.
|
|
36
|
+
requestPermission: true,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const bridge: ClientBridge = { capabilities };
|
|
40
|
+
|
|
41
|
+
if (capabilities.readTextFile) {
|
|
42
|
+
bridge.readTextFile = async params => {
|
|
43
|
+
const response = await connection.readTextFile({
|
|
44
|
+
sessionId,
|
|
45
|
+
path: params.path,
|
|
46
|
+
...(typeof params.line === "number" ? { line: params.line } : {}),
|
|
47
|
+
...(typeof params.limit === "number" ? { limit: params.limit } : {}),
|
|
48
|
+
});
|
|
49
|
+
return response.content;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (capabilities.writeTextFile) {
|
|
54
|
+
bridge.writeTextFile = async params => {
|
|
55
|
+
await connection.writeTextFile({
|
|
56
|
+
sessionId,
|
|
57
|
+
path: params.path,
|
|
58
|
+
content: params.content,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (capabilities.terminal) {
|
|
64
|
+
bridge.createTerminal = (params: ClientBridgeCreateTerminalParams) =>
|
|
65
|
+
createTerminalHandle(connection, sessionId, params);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
bridge.requestPermission = (toolCall, options, signal) =>
|
|
69
|
+
requestPermission(connection, sessionId, toolCall, options, signal);
|
|
70
|
+
|
|
71
|
+
return bridge;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function createTerminalHandle(
|
|
75
|
+
connection: AgentSideConnection,
|
|
76
|
+
sessionId: string,
|
|
77
|
+
params: ClientBridgeCreateTerminalParams,
|
|
78
|
+
): Promise<ClientBridgeTerminalHandle> {
|
|
79
|
+
const handle = await connection.createTerminal({
|
|
80
|
+
sessionId,
|
|
81
|
+
command: params.command,
|
|
82
|
+
...(params.args ? { args: params.args } : {}),
|
|
83
|
+
...(params.env ? { env: params.env } : {}),
|
|
84
|
+
...(params.cwd ? { cwd: params.cwd } : {}),
|
|
85
|
+
...(typeof params.outputByteLimit === "number" ? { outputByteLimit: params.outputByteLimit } : {}),
|
|
86
|
+
});
|
|
87
|
+
return wrapTerminalHandle(handle);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function wrapTerminalHandle(handle: AcpTerminalHandle): ClientBridgeTerminalHandle {
|
|
91
|
+
return {
|
|
92
|
+
terminalId: handle.id,
|
|
93
|
+
async currentOutput() {
|
|
94
|
+
const out = await handle.currentOutput();
|
|
95
|
+
return {
|
|
96
|
+
output: out.output,
|
|
97
|
+
truncated: out.truncated,
|
|
98
|
+
exitStatus: out.exitStatus ?? null,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
async waitForExit() {
|
|
102
|
+
const status = await handle.waitForExit();
|
|
103
|
+
return { exitCode: status.exitCode ?? null, signal: status.signal ?? null };
|
|
104
|
+
},
|
|
105
|
+
async kill() {
|
|
106
|
+
await handle.kill();
|
|
107
|
+
},
|
|
108
|
+
async release() {
|
|
109
|
+
await handle.release();
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function requestPermission(
|
|
115
|
+
connection: AgentSideConnection,
|
|
116
|
+
sessionId: string,
|
|
117
|
+
toolCall: ClientBridgePermissionToolCall,
|
|
118
|
+
options: ClientBridgePermissionOption[],
|
|
119
|
+
signal: AbortSignal | undefined,
|
|
120
|
+
): Promise<ClientBridgePermissionOutcome> {
|
|
121
|
+
const update: ToolCallUpdate = {
|
|
122
|
+
toolCallId: toolCall.toolCallId,
|
|
123
|
+
title: toolCall.title,
|
|
124
|
+
...(toolCall.kind ? { kind: toolCall.kind as ToolCallUpdate["kind"] } : {}),
|
|
125
|
+
...(toolCall.rawInput !== undefined ? { rawInput: toolCall.rawInput } : {}),
|
|
126
|
+
...(toolCall.locations ? { locations: toolCall.locations } : {}),
|
|
127
|
+
};
|
|
128
|
+
const acpOptions: AcpPermissionOption[] = options.map(option => ({
|
|
129
|
+
optionId: option.optionId,
|
|
130
|
+
name: option.name,
|
|
131
|
+
kind: option.kind,
|
|
132
|
+
}));
|
|
133
|
+
const request: RequestPermissionRequest = {
|
|
134
|
+
sessionId,
|
|
135
|
+
toolCall: update,
|
|
136
|
+
options: acpOptions,
|
|
137
|
+
};
|
|
138
|
+
if (signal?.aborted) {
|
|
139
|
+
return { outcome: "cancelled" };
|
|
140
|
+
}
|
|
141
|
+
const response = await connection.requestPermission(request);
|
|
142
|
+
const outcome = response.outcome;
|
|
143
|
+
if (outcome.outcome === "cancelled") {
|
|
144
|
+
return { outcome: "cancelled" };
|
|
145
|
+
}
|
|
146
|
+
const matched = options.find(option => option.optionId === outcome.optionId);
|
|
147
|
+
return {
|
|
148
|
+
outcome: "selected",
|
|
149
|
+
optionId: outcome.optionId,
|
|
150
|
+
...(matched ? { kind: matched.kind } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -6,10 +6,24 @@ import type {
|
|
|
6
6
|
ToolKind,
|
|
7
7
|
} from "@agentclientprotocol/sdk";
|
|
8
8
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
9
|
+
import { resolveToCwd } from "../../tools/path-utils";
|
|
9
10
|
import type { TodoStatus } from "../../tools/todo-write";
|
|
10
11
|
|
|
12
|
+
interface MessageProgress {
|
|
13
|
+
textEmitted: boolean;
|
|
14
|
+
thoughtEmitted: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
interface AcpEventMapperOptions {
|
|
12
18
|
getMessageId?: (message: unknown) => string | undefined;
|
|
19
|
+
getMessageProgress?: (message: unknown) => MessageProgress | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Session cwd. Tool call locations sent to ACP clients must be absolute
|
|
22
|
+
* (the editor host needs them to open or focus files). When provided,
|
|
23
|
+
* the mapper resolves raw `path`/`file`/etc. args against this cwd
|
|
24
|
+
* before emitting `ToolCallLocation` entries.
|
|
25
|
+
*/
|
|
26
|
+
cwd?: string;
|
|
13
27
|
}
|
|
14
28
|
|
|
15
29
|
interface ContentArrayContainer {
|
|
@@ -127,6 +141,8 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
127
141
|
switch (event.type) {
|
|
128
142
|
case "message_update":
|
|
129
143
|
return mapAssistantMessageUpdate(event, sessionId, options);
|
|
144
|
+
case "message_end":
|
|
145
|
+
return mapAssistantMessageEnd(event, sessionId, options);
|
|
130
146
|
case "tool_execution_start": {
|
|
131
147
|
const update: SessionUpdate = {
|
|
132
148
|
sessionUpdate: "tool_call",
|
|
@@ -136,14 +152,16 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
136
152
|
status: "pending",
|
|
137
153
|
rawInput: event.args,
|
|
138
154
|
};
|
|
139
|
-
const locations = extractToolLocations(event.args);
|
|
155
|
+
const locations = extractToolLocations(event.args, options.cwd);
|
|
140
156
|
if (locations.length > 0) {
|
|
141
157
|
update.locations = locations;
|
|
142
158
|
}
|
|
143
159
|
return [toSessionNotification(sessionId, update)];
|
|
144
160
|
}
|
|
145
161
|
case "tool_execution_update": {
|
|
146
|
-
const
|
|
162
|
+
const terminalContent = extractTerminalToolCallContent(event.partialResult);
|
|
163
|
+
const otherContent = terminalContent.length > 0 ? [] : extractToolCallContent(event.partialResult);
|
|
164
|
+
const content = [...terminalContent, ...otherContent];
|
|
147
165
|
const update: SessionUpdate = {
|
|
148
166
|
sessionUpdate: "tool_call_update",
|
|
149
167
|
toolCallId: event.toolCallId,
|
|
@@ -153,10 +171,17 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
153
171
|
if (content.length > 0) {
|
|
154
172
|
update.content = content;
|
|
155
173
|
}
|
|
174
|
+
const locations = extractToolLocations(event.args, options.cwd);
|
|
175
|
+
if (locations.length > 0) {
|
|
176
|
+
update.locations = locations;
|
|
177
|
+
}
|
|
156
178
|
return [toSessionNotification(sessionId, update)];
|
|
157
179
|
}
|
|
158
180
|
case "tool_execution_end": {
|
|
159
|
-
const
|
|
181
|
+
const diffContent = extractDiffToolCallContent(event.result);
|
|
182
|
+
const terminalContent = extractTerminalToolCallContent(event.result);
|
|
183
|
+
const otherContent = extractToolCallContent(event.result);
|
|
184
|
+
const content = [...diffContent, ...terminalContent, ...otherContent];
|
|
160
185
|
const update: SessionUpdate = {
|
|
161
186
|
sessionUpdate: "tool_call_update",
|
|
162
187
|
toolCallId: event.toolCallId,
|
|
@@ -166,6 +191,10 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
166
191
|
if (content.length > 0) {
|
|
167
192
|
update.content = content;
|
|
168
193
|
}
|
|
194
|
+
const locations = extractToolLocationsFromResult(event.result, options.cwd);
|
|
195
|
+
if (locations.length > 0) {
|
|
196
|
+
update.locations = locations;
|
|
197
|
+
}
|
|
169
198
|
return [toSessionNotification(sessionId, update)];
|
|
170
199
|
}
|
|
171
200
|
case "todo_reminder": {
|
|
@@ -194,14 +223,31 @@ function mapAssistantMessageUpdate(
|
|
|
194
223
|
|
|
195
224
|
let sessionUpdate: "agent_message_chunk" | "agent_thought_chunk";
|
|
196
225
|
let text: string;
|
|
226
|
+
const progress = options.getMessageProgress?.(event.message);
|
|
197
227
|
switch (event.assistantMessageEvent.type) {
|
|
198
228
|
case "text_delta":
|
|
199
229
|
sessionUpdate = "agent_message_chunk";
|
|
200
230
|
text = event.assistantMessageEvent.delta;
|
|
231
|
+
if (text.length > 0 && progress) {
|
|
232
|
+
progress.textEmitted = true;
|
|
233
|
+
}
|
|
201
234
|
break;
|
|
202
235
|
case "thinking_delta":
|
|
203
236
|
sessionUpdate = "agent_thought_chunk";
|
|
204
237
|
text = event.assistantMessageEvent.delta;
|
|
238
|
+
if (text.length > 0 && progress) {
|
|
239
|
+
progress.thoughtEmitted = true;
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
case "done":
|
|
243
|
+
if (progress?.textEmitted) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
sessionUpdate = "agent_message_chunk";
|
|
247
|
+
text = extractAssistantMessageText(event.assistantMessageEvent.message);
|
|
248
|
+
if (text.length > 0 && progress) {
|
|
249
|
+
progress.textEmitted = true;
|
|
250
|
+
}
|
|
205
251
|
break;
|
|
206
252
|
case "error":
|
|
207
253
|
sessionUpdate = "agent_message_chunk";
|
|
@@ -224,6 +270,33 @@ function mapAssistantMessageUpdate(
|
|
|
224
270
|
];
|
|
225
271
|
}
|
|
226
272
|
|
|
273
|
+
function mapAssistantMessageEnd(
|
|
274
|
+
event: Extract<AgentSessionEvent, { type: "message_end" }>,
|
|
275
|
+
sessionId: string,
|
|
276
|
+
options: AcpEventMapperOptions,
|
|
277
|
+
): SessionNotification[] {
|
|
278
|
+
if (!isAssistantMessage(event.message)) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
const progress = options.getMessageProgress?.(event.message);
|
|
282
|
+
if (!progress || progress.textEmitted) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
const text = extractAssistantMessageText(event.message);
|
|
286
|
+
if (text.length === 0) {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
progress.textEmitted = true;
|
|
290
|
+
const messageId = options.getMessageId?.(event.message);
|
|
291
|
+
return [
|
|
292
|
+
toSessionNotification(sessionId, {
|
|
293
|
+
sessionUpdate: "agent_message_chunk",
|
|
294
|
+
content: { type: "text", text },
|
|
295
|
+
messageId,
|
|
296
|
+
}),
|
|
297
|
+
];
|
|
298
|
+
}
|
|
299
|
+
|
|
227
300
|
function toSessionNotification(sessionId: string, update: SessionUpdate): SessionNotification {
|
|
228
301
|
return { sessionId, update };
|
|
229
302
|
}
|
|
@@ -257,26 +330,104 @@ function buildToolTitle(toolName: string, args: unknown, intent: string | undefi
|
|
|
257
330
|
return toolName;
|
|
258
331
|
}
|
|
259
332
|
|
|
260
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Resolve a single raw path against cwd for an ACP location. When `cwd` is
|
|
335
|
+
* omitted we pass the value through unchanged (callers without session
|
|
336
|
+
* context, e.g. some legacy entry points and tests); the ACP-side caller
|
|
337
|
+
* always supplies cwd so notifications carry absolute paths.
|
|
338
|
+
*/
|
|
339
|
+
function toAcpLocationPath(value: string, cwd?: string): string {
|
|
340
|
+
if (!cwd) return value;
|
|
341
|
+
try {
|
|
342
|
+
return resolveToCwd(value, cwd);
|
|
343
|
+
} catch {
|
|
344
|
+
return value;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function extractToolLocations(args: unknown, cwd?: string): ToolCallLocation[] {
|
|
261
349
|
const locations: ToolCallLocation[] = [];
|
|
262
|
-
const
|
|
263
|
-
|
|
350
|
+
const seen = new Set<string>();
|
|
351
|
+
const pushPath = (raw: string | undefined) => {
|
|
352
|
+
if (!raw) return;
|
|
353
|
+
const path = toAcpLocationPath(raw, cwd);
|
|
354
|
+
if (seen.has(path)) return;
|
|
355
|
+
seen.add(path);
|
|
264
356
|
locations.push({ path });
|
|
265
|
-
}
|
|
357
|
+
};
|
|
266
358
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
359
|
+
pushPath(extractStringProperty<PathContainer>(args, "path"));
|
|
360
|
+
pushPath(extractStringProperty<OldPathContainer>(args, "oldPath"));
|
|
361
|
+
pushPath(extractStringProperty<NewPathContainer>(args, "newPath"));
|
|
271
362
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
locations.push({ path: newPath });
|
|
275
|
-
}
|
|
363
|
+
return locations;
|
|
364
|
+
}
|
|
276
365
|
|
|
366
|
+
/** Pull locations from a tool result's details (e.g. EditToolDetails.perFileResults[].path). */
|
|
367
|
+
function extractToolLocationsFromResult(result: unknown, cwd?: string): ToolCallLocation[] {
|
|
368
|
+
if (typeof result !== "object" || result === null) return [];
|
|
369
|
+
const details = (result as { details?: unknown }).details;
|
|
370
|
+
if (typeof details !== "object" || details === null) return [];
|
|
371
|
+
const direct = extractToolLocations(details, cwd);
|
|
372
|
+
const perFile = (details as { perFileResults?: unknown }).perFileResults;
|
|
373
|
+
if (!Array.isArray(perFile)) {
|
|
374
|
+
return direct;
|
|
375
|
+
}
|
|
376
|
+
const seen = new Set(direct.map(loc => loc.path));
|
|
377
|
+
const locations = [...direct];
|
|
378
|
+
for (const entry of perFile) {
|
|
379
|
+
const raw = extractStringProperty<PathContainer>(entry, "path");
|
|
380
|
+
if (!raw) continue;
|
|
381
|
+
const path = toAcpLocationPath(raw, cwd);
|
|
382
|
+
if (seen.has(path)) continue;
|
|
383
|
+
seen.add(path);
|
|
384
|
+
locations.push({ path });
|
|
385
|
+
}
|
|
277
386
|
return locations;
|
|
278
387
|
}
|
|
279
388
|
|
|
389
|
+
/** Emit a `diff` ToolCallContent for each per-file edit result that carries oldText/newText. */
|
|
390
|
+
function extractDiffToolCallContent(result: unknown): ToolCallContent[] {
|
|
391
|
+
if (typeof result !== "object" || result === null) return [];
|
|
392
|
+
const details = (result as { details?: unknown }).details;
|
|
393
|
+
if (typeof details !== "object" || details === null) return [];
|
|
394
|
+
const blocks: ToolCallContent[] = [];
|
|
395
|
+
const perFile = (details as { perFileResults?: unknown }).perFileResults;
|
|
396
|
+
const entries: unknown[] = Array.isArray(perFile) ? perFile : [details];
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
const block = buildDiffContent(entry);
|
|
399
|
+
if (block) blocks.push(block);
|
|
400
|
+
}
|
|
401
|
+
return blocks;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function buildDiffContent(entry: unknown): ToolCallContent | undefined {
|
|
405
|
+
if (typeof entry !== "object" || entry === null) return undefined;
|
|
406
|
+
const candidate = entry as { path?: unknown; oldText?: unknown; newText?: unknown; isError?: unknown };
|
|
407
|
+
if (candidate.isError === true) return undefined;
|
|
408
|
+
const path = typeof candidate.path === "string" && candidate.path.length > 0 ? candidate.path : undefined;
|
|
409
|
+
if (!path) return undefined;
|
|
410
|
+
const oldText = typeof candidate.oldText === "string" ? candidate.oldText : undefined;
|
|
411
|
+
const newText = typeof candidate.newText === "string" ? candidate.newText : undefined;
|
|
412
|
+
if (oldText === undefined && newText === undefined) return undefined;
|
|
413
|
+
return {
|
|
414
|
+
type: "diff",
|
|
415
|
+
path,
|
|
416
|
+
oldText: oldText ?? null,
|
|
417
|
+
newText: newText ?? "",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Emit a `terminal` ToolCallContent when a tool result carries a `details.terminalId` (e.g. bash routed through ACP terminal/*). */
|
|
422
|
+
function extractTerminalToolCallContent(result: unknown): ToolCallContent[] {
|
|
423
|
+
if (typeof result !== "object" || result === null) return [];
|
|
424
|
+
const details = (result as { details?: unknown }).details;
|
|
425
|
+
if (typeof details !== "object" || details === null) return [];
|
|
426
|
+
const terminalId = (details as { terminalId?: unknown }).terminalId;
|
|
427
|
+
if (typeof terminalId !== "string" || terminalId.length === 0) return [];
|
|
428
|
+
return [{ type: "terminal", terminalId }];
|
|
429
|
+
}
|
|
430
|
+
|
|
280
431
|
function extractToolCallContent(value: unknown): ToolCallContent[] {
|
|
281
432
|
const richContent = extractStructuredToolCallContent(value);
|
|
282
433
|
const fallbackText = extractReadableText(value);
|
|
@@ -479,6 +630,20 @@ function extractReadableText(value: unknown): string | undefined {
|
|
|
479
630
|
return normalizeText(serialized);
|
|
480
631
|
}
|
|
481
632
|
|
|
633
|
+
function extractAssistantMessageText(value: unknown): string {
|
|
634
|
+
if (typeof value !== "object" || value === null || !("content" in value)) {
|
|
635
|
+
return "";
|
|
636
|
+
}
|
|
637
|
+
const content = (value as ContentArrayContainer).content;
|
|
638
|
+
if (!Array.isArray(content)) {
|
|
639
|
+
return "";
|
|
640
|
+
}
|
|
641
|
+
return content
|
|
642
|
+
.map(block => extractStructuredText(block))
|
|
643
|
+
.filter((chunk): chunk is string => typeof chunk === "string" && chunk.length > 0)
|
|
644
|
+
.join("\n");
|
|
645
|
+
}
|
|
646
|
+
|
|
482
647
|
function extractStructuredText(value: unknown): string | undefined {
|
|
483
648
|
const text = extractStringProperty<TextLikeContent>(value, "text");
|
|
484
649
|
if (!text) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const ACP_TERMINAL_AUTH_FLAG = "--acp-terminal-auth";
|
|
2
|
+
|
|
3
|
+
export interface AcpTerminalAuthArgs {
|
|
4
|
+
args: string[];
|
|
5
|
+
terminalAuth: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function prepareAcpTerminalAuthArgs(rawArgs: readonly string[]): AcpTerminalAuthArgs {
|
|
9
|
+
const withoutAuthFlag: string[] = [];
|
|
10
|
+
let terminalAuth = false;
|
|
11
|
+
for (const arg of rawArgs) {
|
|
12
|
+
if (arg === ACP_TERMINAL_AUTH_FLAG) {
|
|
13
|
+
terminalAuth = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
withoutAuthFlag.push(arg);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!terminalAuth) {
|
|
20
|
+
return { args: withoutAuthFlag, terminalAuth: false };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const args: string[] = [];
|
|
24
|
+
for (let i = 0; i < withoutAuthFlag.length; i++) {
|
|
25
|
+
const arg = withoutAuthFlag[i];
|
|
26
|
+
if (arg === "--mode") {
|
|
27
|
+
i++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (arg.startsWith("--mode=")) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
args.push(arg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { args, terminalAuth: true };
|
|
37
|
+
}
|
|
@@ -3,8 +3,8 @@ import { Container, Image, ImageProtocol, Markdown, Spacer, TERMINAL, Text } fro
|
|
|
3
3
|
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { settings } from "../../config/settings";
|
|
5
5
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
6
|
+
import { isSilentAbort } from "../../session/messages";
|
|
6
7
|
import { resolveImageOptions } from "../../tools/render-utils";
|
|
7
|
-
import { convertToPng } from "../../utils/image-convert";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Component that renders a complete assistant message
|
|
@@ -76,14 +76,15 @@ export class AssistantMessageComponent extends Container {
|
|
|
76
76
|
const key = `${toolCallId}:${index}`;
|
|
77
77
|
if (this.#convertedKittyImages.has(key) || this.#kittyConversionsInFlight.has(key)) continue;
|
|
78
78
|
this.#kittyConversionsInFlight.add(key);
|
|
79
|
-
|
|
80
|
-
.
|
|
79
|
+
new Bun.Image(Buffer.from(image.data, "base64"))
|
|
80
|
+
.png()
|
|
81
|
+
.toBase64()
|
|
82
|
+
.then(data => {
|
|
81
83
|
this.#kittyConversionsInFlight.delete(key);
|
|
82
|
-
if (!converted) return;
|
|
83
84
|
this.#convertedKittyImages.set(key, {
|
|
84
85
|
type: "image",
|
|
85
|
-
data
|
|
86
|
-
mimeType:
|
|
86
|
+
data,
|
|
87
|
+
mimeType: "image/png",
|
|
87
88
|
});
|
|
88
89
|
if (this.#lastMessage) {
|
|
89
90
|
this.updateContent(this.#lastMessage);
|
|
@@ -184,7 +185,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
184
185
|
// But only if there are no tool calls (tool execution components will show the error)
|
|
185
186
|
const hasToolCalls = message.content.some(c => c.type === "toolCall");
|
|
186
187
|
if (!hasToolCalls) {
|
|
187
|
-
if (message.stopReason === "aborted") {
|
|
188
|
+
if (message.stopReason === "aborted" && !isSilentAbort(message.errorMessage)) {
|
|
188
189
|
const abortMessage =
|
|
189
190
|
message.errorMessage && message.errorMessage !== "Request was aborted"
|
|
190
191
|
? message.errorMessage
|
|
@@ -201,7 +202,12 @@ export class AssistantMessageComponent extends Container {
|
|
|
201
202
|
this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
|
-
if (
|
|
205
|
+
if (
|
|
206
|
+
message.errorMessage &&
|
|
207
|
+
!isSilentAbort(message.errorMessage) &&
|
|
208
|
+
message.stopReason !== "aborted" &&
|
|
209
|
+
message.stopReason !== "error"
|
|
210
|
+
) {
|
|
205
211
|
this.#contentContainer.addChild(new Spacer(1));
|
|
206
212
|
this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${message.errorMessage}`), 1, 0));
|
|
207
213
|
}
|
|
@@ -7,19 +7,23 @@ import {
|
|
|
7
7
|
Container,
|
|
8
8
|
Ellipsis,
|
|
9
9
|
ImageProtocol,
|
|
10
|
-
Loader,
|
|
11
|
-
Spacer,
|
|
10
|
+
type Loader,
|
|
12
11
|
TERMINAL,
|
|
13
12
|
Text,
|
|
14
13
|
type TUI,
|
|
15
14
|
truncateToWidth,
|
|
16
15
|
visibleWidth,
|
|
17
16
|
} from "@oh-my-pi/pi-tui";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
17
|
+
import { theme } from "../../modes/theme/theme";
|
|
18
|
+
import type { TruncationMeta } from "../../tools/output-meta";
|
|
20
19
|
import { getSixelLineMask, isSixelPassthroughEnabled, sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
21
|
-
import {
|
|
22
|
-
|
|
20
|
+
import {
|
|
21
|
+
buildExecutionFrame,
|
|
22
|
+
buildStatusFooter,
|
|
23
|
+
createCollapsedPreview,
|
|
24
|
+
type ExecutionStatus,
|
|
25
|
+
resolveExecutionStatus,
|
|
26
|
+
} from "./execution-shared";
|
|
23
27
|
|
|
24
28
|
// Preview line limit when not expanded (matches tool execution behavior)
|
|
25
29
|
const PREVIEW_LINES = 20;
|
|
@@ -31,7 +35,7 @@ const CHUNK_THROTTLE_MS = 50;
|
|
|
31
35
|
|
|
32
36
|
export class BashExecutionComponent extends Container {
|
|
33
37
|
#outputLines: string[] = [];
|
|
34
|
-
#status:
|
|
38
|
+
#status: ExecutionStatus = "running";
|
|
35
39
|
#exitCode: number | undefined = undefined;
|
|
36
40
|
#loader: Loader;
|
|
37
41
|
#truncation?: TruncationMeta;
|
|
@@ -50,34 +54,14 @@ export class BashExecutionComponent extends Container {
|
|
|
50
54
|
|
|
51
55
|
// Use dim border for excluded-from-context commands (!! prefix)
|
|
52
56
|
const colorKey = excludeFromContext ? "dim" : "bashMode";
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.addChild(new Spacer(1));
|
|
57
|
-
|
|
58
|
-
// Top border
|
|
59
|
-
this.addChild(new DynamicBorder(borderColor));
|
|
60
|
-
|
|
61
|
-
// Content container (holds dynamic content between borders)
|
|
62
|
-
this.#contentContainer = new Container();
|
|
63
|
-
this.addChild(this.#contentContainer);
|
|
57
|
+
const { contentContainer, loader } = buildExecutionFrame(this, ui, colorKey);
|
|
58
|
+
this.#contentContainer = contentContainer;
|
|
59
|
+
this.#loader = loader;
|
|
64
60
|
|
|
65
61
|
// Command header
|
|
66
62
|
this.#headerText = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);
|
|
67
63
|
this.#contentContainer.addChild(this.#headerText);
|
|
68
|
-
|
|
69
|
-
// Loader
|
|
70
|
-
this.#loader = new Loader(
|
|
71
|
-
ui,
|
|
72
|
-
spinner => theme.fg(colorKey, spinner),
|
|
73
|
-
text => theme.fg("muted", text),
|
|
74
|
-
`Running… (esc to cancel)`,
|
|
75
|
-
getSymbolTheme().spinnerFrames,
|
|
76
|
-
);
|
|
77
64
|
this.#contentContainer.addChild(this.#loader);
|
|
78
|
-
|
|
79
|
-
// Bottom border
|
|
80
|
-
this.addChild(new DynamicBorder(borderColor));
|
|
81
65
|
}
|
|
82
66
|
|
|
83
67
|
/**
|
|
@@ -130,11 +114,7 @@ export class BashExecutionComponent extends Container {
|
|
|
130
114
|
options?: { output?: string; truncation?: TruncationMeta },
|
|
131
115
|
): void {
|
|
132
116
|
this.#exitCode = exitCode;
|
|
133
|
-
this.#status = cancelled
|
|
134
|
-
? "cancelled"
|
|
135
|
-
: exitCode !== 0 && exitCode !== undefined && exitCode !== null
|
|
136
|
-
? "error"
|
|
137
|
-
: "complete";
|
|
117
|
+
this.#status = resolveExecutionStatus(exitCode, cancelled);
|
|
138
118
|
this.#truncation = options?.truncation;
|
|
139
119
|
if (options?.output !== undefined) {
|
|
140
120
|
this.#setOutput(options.output);
|
|
@@ -182,14 +162,7 @@ export class BashExecutionComponent extends Container {
|
|
|
182
162
|
} else {
|
|
183
163
|
// Use shared visual truncation utility, recomputed per render width
|
|
184
164
|
const styledOutput = previewLogicalLines.map(line => theme.fg("muted", line)).join("\n");
|
|
185
|
-
|
|
186
|
-
this.#contentContainer.addChild({
|
|
187
|
-
render: (width: number) => {
|
|
188
|
-
const { visualLines } = truncateToVisualLines(previewText, PREVIEW_LINES, width, 1);
|
|
189
|
-
return visualLines;
|
|
190
|
-
},
|
|
191
|
-
invalidate: () => {},
|
|
192
|
-
});
|
|
165
|
+
this.#contentContainer.addChild(createCollapsedPreview(`\n${styledOutput}`, PREVIEW_LINES));
|
|
193
166
|
}
|
|
194
167
|
}
|
|
195
168
|
|
|
@@ -197,26 +170,14 @@ export class BashExecutionComponent extends Container {
|
|
|
197
170
|
if (this.#status === "running") {
|
|
198
171
|
this.#contentContainer.addChild(this.#loader);
|
|
199
172
|
} else {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (this.#
|
|
208
|
-
statusParts.push(theme.fg("warning", "(cancelled)"));
|
|
209
|
-
} else if (this.#status === "error") {
|
|
210
|
-
statusParts.push(theme.fg("error", `(exit ${this.#exitCode})`));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (this.#truncation) {
|
|
214
|
-
statusParts.push(theme.fg("warning", formatTruncationMetaNotice(this.#truncation)));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (statusParts.length > 0) {
|
|
218
|
-
this.#contentContainer.addChild(new Text(`\n${statusParts.join("\n")}`, 1, 0));
|
|
219
|
-
}
|
|
173
|
+
const footer = buildStatusFooter({
|
|
174
|
+
status: this.#status,
|
|
175
|
+
exitCode: this.#exitCode,
|
|
176
|
+
truncation: this.#truncation,
|
|
177
|
+
hiddenLineCount,
|
|
178
|
+
suppressHiddenCount: hasSixelOutput,
|
|
179
|
+
});
|
|
180
|
+
if (footer) this.#contentContainer.addChild(footer);
|
|
220
181
|
}
|
|
221
182
|
}
|
|
222
183
|
|