






















@@ -20,6 +20,7 @@ import type { ExecApprovalDecision } from "../infra/exec-approvals.js";
2020import type { PluginHookAfterToolCallEvent } from "../plugins/types.js";
2121import { createLazyImportLoader } from "../shared/lazy-promise.js";
2222import { normalizeOptionalLowercaseString, readStringValue } from "../shared/string-coerce.js";
23+import { truncateUtf16Safe } from "../utils.js";
2324import type { ApplyPatchSummary } from "./apply-patch.js";
2425import type { ExecToolDetails } from "./bash-tools.exec-types.js";
2526import { parseExecApprovalResultText } from "./exec-approval-result.js";
@@ -87,11 +88,50 @@ type ToolStartRecord = {
87888889/** Track tool execution start data for after_tool_call hook. */
8990const toolStartData = new Map<string, ToolStartRecord>();
91+const EXEC_OUTPUT_DELTA_MIN_INTERVAL_MS = 250;
92+const LIVE_COMMAND_OUTPUT_MAX_CHARS = 64 * 1024;
93+type ExecOutputDeltaEmission = {
94+emittedAt: number;
95+};
96+const execOutputDeltaEmissions = new Map<string, ExecOutputDeltaEmission>();
90979198function buildToolStartKey(runId: string, toolCallId: string): string {
9299return `${runId}:${toolCallId}`;
93100}
94101102+function buildExecOutputDeltaKey(runId: string, toolCallId: string): string {
103+return `${runId}:${toolCallId}`;
104+}
105+106+function shouldEmitExecOutputDelta(params: {
107+runId: string;
108+toolCallId: string;
109+output: string;
110+now?: number;
111+}): boolean {
112+const key = buildExecOutputDeltaKey(params.runId, params.toolCallId);
113+const now = params.now ?? Date.now();
114+const previous = execOutputDeltaEmissions.get(key);
115+if (!previous) {
116+execOutputDeltaEmissions.set(key, {
117+emittedAt: now,
118+});
119+return true;
120+}
121+const elapsedMs = now - previous.emittedAt;
122+if (elapsedMs < EXEC_OUTPUT_DELTA_MIN_INTERVAL_MS) {
123+return false;
124+}
125+execOutputDeltaEmissions.set(key, {
126+emittedAt: now,
127+});
128+return true;
129+}
130+131+function clearExecOutputDeltaEmission(runId: string, toolCallId: string): void {
132+execOutputDeltaEmissions.delete(buildExecOutputDeltaKey(runId, toolCallId));
133+}
134+95135export function countActiveToolExecutions(runId: string): number {
96136const prefix = `${runId}:`;
97137let count = 0;
@@ -189,6 +229,39 @@ function readExecToolDetails(result: unknown): ExecToolDetails | null {
189229return details as ExecToolDetails;
190230}
191231232+function readExecOutputText(result: unknown): string | undefined {
233+const details = readToolResultDetailsRecord(result);
234+if (typeof details?.aggregated === "string") {
235+return details.aggregated;
236+}
237+return extractToolResultText(result);
238+}
239+240+function limitLiveCommandOutput(output: string): string {
241+if (output.length <= LIVE_COMMAND_OUTPUT_MAX_CHARS) {
242+return output;
243+}
244+const tail = truncateUtf16Safe(
245+output.slice(-LIVE_COMMAND_OUTPUT_MAX_CHARS),
246+LIVE_COMMAND_OUTPUT_MAX_CHARS,
247+);
248+return `[openclaw: live command output truncated to last ${tail.length} of ${output.length} chars]\n${tail}`;
249+}
250+251+function limitExecToolResultForLiveEvent(result: unknown): unknown {
252+const details = readToolResultDetailsRecord(result);
253+if (!details || typeof details.aggregated !== "string") {
254+return result;
255+}
256+return {
257+ ...(result as Record<string, unknown>),
258+details: {
259+ ...details,
260+aggregated: limitLiveCommandOutput(details.aggregated),
261+},
262+};
263+}
264+192265function readApplyPatchSummary(result: unknown): ApplyPatchSummary | null {
193266const details = readToolResultDetailsRecord(result);
194267const summary =
@@ -741,6 +814,19 @@ export function handleToolExecutionUpdate(
741814const toolName = normalizeToolName(evt.toolName);
742815const toolCallId = evt.toolCallId;
743816const partial = evt.partialResult;
817+if (isExecToolName(toolName)) {
818+const output = readExecOutputText(partial);
819+if (
820+output &&
821+!shouldEmitExecOutputDelta({
822+runId: ctx.params.runId,
823+ toolCallId,
824+ output,
825+})
826+) {
827+return;
828+}
829+}
744830const sanitized = sanitizeToolResult(partial);
745831emitAgentEvent({
746832runId: ctx.params.runId,
@@ -772,11 +858,8 @@ export function handleToolExecutionUpdate(
772858},
773859});
774860if (isExecToolName(toolName)) {
775-const execDetails = readExecToolDetails(sanitized);
776-const output =
777-execDetails && "aggregated" in execDetails
778- ? execDetails.aggregated
779- : extractToolResultText(sanitized);
861+const rawOutput = readExecOutputText(sanitized);
862+const output = rawOutput ? limitLiveCommandOutput(rawOutput) : undefined;
780863const commandData: AgentItemEventData = {
781864itemId: buildCommandItemId(toolCallId),
782865phase: "update",
@@ -829,9 +912,13 @@ export async function handleToolExecutionEnd(
829912const result = evt.result;
830913const isToolError = isError || isToolResultError(result);
831914const sanitizedResult = sanitizeToolResult(result);
915+const liveEventResult = isExecToolName(toolName)
916+ ? limitExecToolResultForLiveEvent(sanitizedResult)
917+ : sanitizedResult;
832918const toolStartKey = buildToolStartKey(runId, toolCallId);
833919const startData = toolStartData.get(toolStartKey);
834920toolStartData.delete(toolStartKey);
921+clearExecOutputDeltaEmission(runId, toolCallId);
835922const callSummary = ctx.state.toolMetaById.get(toolCallId);
836923const completedMutatingAction = !isToolError && Boolean(callSummary?.mutatingAction);
837924const meta = callSummary?.meta;
@@ -934,7 +1021,7 @@ export async function handleToolExecutionEnd(
9341021 toolCallId,
9351022 meta,
9361023isError: isToolError,
937-result: sanitizedResult,
1024+result: liveEventResult,
9381025},
9391026});
9401027const endedAt = Date.now();
@@ -1027,10 +1114,11 @@ export async function handleToolExecutionEnd(
10271114}),
10281115});
10291116} else {
1030-const output =
1117+const rawOutput =
10311118execDetails && "aggregated" in execDetails
10321119 ? execDetails.aggregated
10331120 : extractToolResultText(sanitizedResult);
1121+const output = rawOutput ? limitLiveCommandOutput(rawOutput) : undefined;
10341122const commandStatus =
10351123execDetails?.status === "failed" || isToolError ? "failed" : "completed";
10361124emitTrackedItemEvent(ctx, {
@@ -1075,8 +1163,8 @@ export async function handleToolExecutionEnd(
10751163data: outputData,
10761164});
107711651078-if (typeof output === "string") {
1079-const parsedApprovalResult = parseExecApprovalResultText(output);
1166+if (typeof rawOutput === "string") {
1167+const parsedApprovalResult = parseExecApprovalResultText(rawOutput);
10801168if (parsedApprovalResult.kind === "denied") {
10811169const approvalData: AgentApprovalEventData = {
10821170phase: "resolved",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。