diff --git a/src/agents/bash-tools.process.ts b/src/agents/bash-tools.process.ts index f066cfc154c..3fa32438f57 100644 --- a/src/agents/bash-tools.process.ts +++ b/src/agents/bash-tools.process.ts @@ -32,6 +32,24 @@ type WritableStdin = { }; const DEFAULT_LOG_TAIL_LINES = 200; +function resolveLogSliceWindow(offset?: number, limit?: number) { + const usingDefaultTail = offset === undefined && limit === undefined; + const effectiveLimit = + typeof limit === "number" && Number.isFinite(limit) + ? limit + : usingDefaultTail + ? DEFAULT_LOG_TAIL_LINES + : undefined; + return { effectiveOffset: offset, effectiveLimit, usingDefaultTail }; +} + +function defaultTailNote(totalLines: number, usingDefaultTail: boolean) { + if (!usingDefaultTail || totalLines <= DEFAULT_LOG_TAIL_LINES) { + return ""; + } + return `\n\n[showing last ${DEFAULT_LOG_TAIL_LINES} of ${totalLines} lines; pass offset/limit to page]`; +} + const processSchema = Type.Object({ action: Type.String({ description: "Process action" }), sessionId: Type.Optional(Type.String({ description: "Session id for actions other than list" })), @@ -295,25 +313,15 @@ export function createProcessTool( details: { status: "failed" }, }; } - const effectiveOffset = params.offset; - const usingDefaultTail = params.offset === undefined && params.limit === undefined; - const effectiveLimit = - typeof params.limit === "number" && Number.isFinite(params.limit) - ? params.limit - : usingDefaultTail - ? DEFAULT_LOG_TAIL_LINES - : undefined; + const window = resolveLogSliceWindow(params.offset, params.limit); const { slice, totalLines, totalChars } = sliceLogLines( scopedSession.aggregated, - effectiveOffset, - effectiveLimit, + window.effectiveOffset, + window.effectiveLimit, ); - const defaultTailNote = - usingDefaultTail && totalLines > DEFAULT_LOG_TAIL_LINES - ? `\n\n[showing last ${DEFAULT_LOG_TAIL_LINES} of ${totalLines} lines; pass offset/limit to page]` - : ""; + const logDefaultTailNote = defaultTailNote(totalLines, window.usingDefaultTail); return { - content: [{ type: "text", text: (slice || "(no output yet)") + defaultTailNote }], + content: [{ type: "text", text: (slice || "(no output yet)") + logDefaultTailNote }], details: { status: scopedSession.exited ? "completed" : "running", sessionId: params.sessionId, @@ -326,27 +334,17 @@ export function createProcessTool( }; } if (scopedFinished) { - const effectiveOffset = params.offset; - const usingDefaultTail = params.offset === undefined && params.limit === undefined; - const effectiveLimit = - typeof params.limit === "number" && Number.isFinite(params.limit) - ? params.limit - : usingDefaultTail - ? DEFAULT_LOG_TAIL_LINES - : undefined; + const window = resolveLogSliceWindow(params.offset, params.limit); const { slice, totalLines, totalChars } = sliceLogLines( scopedFinished.aggregated, - effectiveOffset, - effectiveLimit, + window.effectiveOffset, + window.effectiveLimit, ); const status = scopedFinished.status === "completed" ? "completed" : "failed"; - const defaultTailNote = - usingDefaultTail && totalLines > DEFAULT_LOG_TAIL_LINES - ? `\n\n[showing last ${DEFAULT_LOG_TAIL_LINES} of ${totalLines} lines; pass offset/limit to page]` - : ""; + const logDefaultTailNote = defaultTailNote(totalLines, window.usingDefaultTail); return { content: [ - { type: "text", text: (slice || "(no output recorded)") + defaultTailNote }, + { type: "text", text: (slice || "(no output recorded)") + logDefaultTailNote }, ], details: { status, diff --git a/src/agents/pi-embedded-helpers/bootstrap.ts b/src/agents/pi-embedded-helpers/bootstrap.ts index 677c474b53d..8a86206ce6c 100644 --- a/src/agents/pi-embedded-helpers/bootstrap.ts +++ b/src/agents/pi-embedded-helpers/bootstrap.ts @@ -4,6 +4,7 @@ import path from "node:path"; import type { OpenClawConfig } from "../../config/config.js"; import type { WorkspaceBootstrapFile } from "../workspace.js"; import type { EmbeddedContextFile } from "./types.js"; +import { truncateUtf16Safe } from "../../utils.js"; type ContentBlockWithSignature = { thought_signature?: unknown; @@ -153,10 +154,10 @@ function clampToBudget(content: string, budget: number): string { return content; } if (budget <= 3) { - return content.slice(0, budget); + return truncateUtf16Safe(content, budget); } const safe = Math.max(1, budget - 1); - return `${content.slice(0, safe)}…`; + return `${truncateUtf16Safe(content, safe)}…`; } export async function ensureSessionHeader(params: {