mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 03:31:23 +00:00
feat: add /context prompt breakdown
This commit is contained in:
@@ -250,6 +250,7 @@ export async function runEmbeddedPiAgent(
|
||||
provider,
|
||||
model: model.id,
|
||||
},
|
||||
systemPromptReport: attempt.systemPromptReport,
|
||||
error: { kind, message: errorText },
|
||||
},
|
||||
};
|
||||
@@ -404,6 +405,7 @@ export async function runEmbeddedPiAgent(
|
||||
durationMs: Date.now() - started,
|
||||
agentMeta,
|
||||
aborted,
|
||||
systemPromptReport: attempt.systemPromptReport,
|
||||
},
|
||||
didSendViaMessagingTool: attempt.didSendViaMessagingTool,
|
||||
messagingToolSentTexts: attempt.messagingToolSentTexts,
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
loadWorkspaceSkillEntries,
|
||||
resolveSkillsPromptForRun,
|
||||
} from "../../skills.js";
|
||||
import { buildSystemPromptReport } from "../../system-prompt-report.js";
|
||||
import { filterBootstrapFilesForSession, loadWorkspaceBootstrapFiles } from "../../workspace.js";
|
||||
|
||||
import { isAbortError } from "../abort.js";
|
||||
@@ -62,6 +63,7 @@ import {
|
||||
resolveExecToolDefaults,
|
||||
resolveUserTimezone,
|
||||
} from "../utils.js";
|
||||
import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
|
||||
|
||||
import type { EmbeddedRunAttemptParams, EmbeddedRunAttemptResult } from "./types.js";
|
||||
|
||||
@@ -198,6 +200,28 @@ export async function runEmbeddedAttempt(
|
||||
userTime,
|
||||
contextFiles,
|
||||
});
|
||||
const systemPromptReport = buildSystemPromptReport({
|
||||
source: "run",
|
||||
generatedAt: Date.now(),
|
||||
sessionId: params.sessionId,
|
||||
sessionKey: params.sessionKey,
|
||||
provider: params.provider,
|
||||
model: params.modelId,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
bootstrapMaxChars: resolveBootstrapMaxChars(params.config),
|
||||
sandbox: (() => {
|
||||
const runtime = resolveSandboxRuntimeStatus({
|
||||
cfg: params.config,
|
||||
sessionKey: params.sessionKey ?? params.sessionId,
|
||||
});
|
||||
return { mode: runtime.mode, sandboxed: runtime.sandboxed };
|
||||
})(),
|
||||
systemPrompt: appendPrompt,
|
||||
bootstrapFiles,
|
||||
injectedFiles: contextFiles,
|
||||
skillsPrompt,
|
||||
tools,
|
||||
});
|
||||
const systemPrompt = createSystemPromptOverride(appendPrompt);
|
||||
|
||||
const sessionLock = await acquireSessionWriteLock({
|
||||
@@ -427,6 +451,7 @@ export async function runEmbeddedAttempt(
|
||||
timedOut,
|
||||
promptError,
|
||||
sessionIdUsed,
|
||||
systemPromptReport,
|
||||
messagesSnapshot,
|
||||
assistantTexts,
|
||||
toolMetas: toolMetasNormalized,
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ExecElevatedDefaults } from "../../bash-tools.js";
|
||||
import type { MessagingToolSend } from "../../pi-embedded-messaging.js";
|
||||
import type { BlockReplyChunking } from "../../pi-embedded-subscribe.js";
|
||||
import type { SkillSnapshot } from "../../skills.js";
|
||||
import type { SessionSystemPromptReport } from "../../../config/sessions/types.js";
|
||||
|
||||
type AuthStorage = ReturnType<typeof discoverAuthStorage>;
|
||||
type ModelRegistry = ReturnType<typeof discoverModels>;
|
||||
@@ -65,6 +66,7 @@ export type EmbeddedRunAttemptResult = {
|
||||
timedOut: boolean;
|
||||
promptError: unknown;
|
||||
sessionIdUsed: string;
|
||||
systemPromptReport?: SessionSystemPromptReport;
|
||||
messagesSnapshot: AgentMessage[];
|
||||
assistantTexts: string[];
|
||||
toolMetas: Array<{ toolName: string; meta?: string }>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { MessagingToolSend } from "../pi-embedded-messaging.js";
|
||||
import type { SessionSystemPromptReport } from "../../config/sessions/types.js";
|
||||
|
||||
export type EmbeddedPiAgentMeta = {
|
||||
sessionId: string;
|
||||
@@ -17,6 +18,7 @@ export type EmbeddedPiRunMeta = {
|
||||
durationMs: number;
|
||||
agentMeta?: EmbeddedPiAgentMeta;
|
||||
aborted?: boolean;
|
||||
systemPromptReport?: SessionSystemPromptReport;
|
||||
error?: {
|
||||
kind: "context_overflow" | "compaction_failure";
|
||||
message: string;
|
||||
|
||||
150
src/agents/system-prompt-report.ts
Normal file
150
src/agents/system-prompt-report.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
|
||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
||||
import type { SessionSystemPromptReport } from "../config/sessions/types.js";
|
||||
|
||||
function extractBetween(
|
||||
input: string,
|
||||
startMarker: string,
|
||||
endMarker: string,
|
||||
): { text: string; found: boolean } {
|
||||
const start = input.indexOf(startMarker);
|
||||
if (start === -1) return { text: "", found: false };
|
||||
const end = input.indexOf(endMarker, start + startMarker.length);
|
||||
if (end === -1) return { text: input.slice(start), found: true };
|
||||
return { text: input.slice(start, end), found: true };
|
||||
}
|
||||
|
||||
function parseSkillBlocks(skillsPrompt: string): Array<{ name: string; blockChars: number }> {
|
||||
const prompt = skillsPrompt.trim();
|
||||
if (!prompt) return [];
|
||||
const blocks = Array.from(prompt.matchAll(/<skill>[\s\S]*?<\/skill>/gi)).map(
|
||||
(match) => match[0] ?? "",
|
||||
);
|
||||
return blocks
|
||||
.map((block) => {
|
||||
const name =
|
||||
block.match(/<name>\s*([^<]+?)\s*<\/name>/i)?.[1]?.trim() || "(unknown)";
|
||||
return { name, blockChars: block.length };
|
||||
})
|
||||
.filter((b) => b.blockChars > 0);
|
||||
}
|
||||
|
||||
function buildInjectedWorkspaceFiles(params: {
|
||||
bootstrapFiles: WorkspaceBootstrapFile[];
|
||||
injectedFiles: EmbeddedContextFile[];
|
||||
bootstrapMaxChars: number;
|
||||
}): SessionSystemPromptReport["injectedWorkspaceFiles"] {
|
||||
const injectedByName = new Map(params.injectedFiles.map((f) => [f.path, f.content]));
|
||||
return params.bootstrapFiles.map((file) => {
|
||||
const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
|
||||
const injected = injectedByName.get(file.name);
|
||||
const injectedChars = injected ? injected.length : 0;
|
||||
const truncated = !file.missing && rawChars > params.bootstrapMaxChars;
|
||||
return {
|
||||
name: file.name,
|
||||
path: file.path,
|
||||
missing: file.missing,
|
||||
rawChars,
|
||||
injectedChars,
|
||||
truncated,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function buildToolsEntries(tools: AgentTool[]): SessionSystemPromptReport["tools"]["entries"] {
|
||||
return tools.map((tool) => {
|
||||
const name = tool.name;
|
||||
const summary = tool.description?.trim() || tool.label?.trim() || "";
|
||||
const summaryChars = summary.length;
|
||||
const schemaChars = (() => {
|
||||
if (!tool.parameters || typeof tool.parameters !== "object") return 0;
|
||||
try {
|
||||
return JSON.stringify(tool.parameters).length;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
const propertiesCount = (() => {
|
||||
const schema =
|
||||
tool.parameters && typeof tool.parameters === "object"
|
||||
? (tool.parameters as Record<string, unknown>)
|
||||
: null;
|
||||
const props = schema && typeof schema.properties === "object" ? schema.properties : null;
|
||||
if (!props || typeof props !== "object") return null;
|
||||
return Object.keys(props as Record<string, unknown>).length;
|
||||
})();
|
||||
return { name, summaryChars, schemaChars, propertiesCount };
|
||||
});
|
||||
}
|
||||
|
||||
function extractToolListText(systemPrompt: string): string {
|
||||
const markerA = "Tool names are case-sensitive. Call tools exactly as listed.\n";
|
||||
const markerB = "\nTOOLS.md does not control tool availability; it is user guidance for how to use external tools.";
|
||||
const extracted = extractBetween(systemPrompt, markerA, markerB);
|
||||
if (!extracted.found) return "";
|
||||
return extracted.text.replace(markerA, "").trim();
|
||||
}
|
||||
|
||||
export function buildSystemPromptReport(params: {
|
||||
source: SessionSystemPromptReport["source"];
|
||||
generatedAt: number;
|
||||
sessionId?: string;
|
||||
sessionKey?: string;
|
||||
provider?: string;
|
||||
model?: string;
|
||||
workspaceDir?: string;
|
||||
bootstrapMaxChars: number;
|
||||
sandbox?: SessionSystemPromptReport["sandbox"];
|
||||
systemPrompt: string;
|
||||
bootstrapFiles: WorkspaceBootstrapFile[];
|
||||
injectedFiles: EmbeddedContextFile[];
|
||||
skillsPrompt: string;
|
||||
tools: AgentTool[];
|
||||
}): SessionSystemPromptReport {
|
||||
const systemPrompt = params.systemPrompt.trim();
|
||||
const projectContext = extractBetween(
|
||||
systemPrompt,
|
||||
"\n# Project Context\n",
|
||||
"\n## Silent Replies\n",
|
||||
);
|
||||
const projectContextChars = projectContext.text.length;
|
||||
const toolListText = extractToolListText(systemPrompt);
|
||||
const toolListChars = toolListText.length;
|
||||
const toolsEntries = buildToolsEntries(params.tools);
|
||||
const toolsSchemaChars = toolsEntries.reduce((sum, t) => sum + (t.schemaChars ?? 0), 0);
|
||||
const skillsEntries = parseSkillBlocks(params.skillsPrompt);
|
||||
|
||||
return {
|
||||
source: params.source,
|
||||
generatedAt: params.generatedAt,
|
||||
sessionId: params.sessionId,
|
||||
sessionKey: params.sessionKey,
|
||||
provider: params.provider,
|
||||
model: params.model,
|
||||
workspaceDir: params.workspaceDir,
|
||||
bootstrapMaxChars: params.bootstrapMaxChars,
|
||||
sandbox: params.sandbox,
|
||||
systemPrompt: {
|
||||
chars: systemPrompt.length,
|
||||
projectContextChars,
|
||||
nonProjectContextChars: Math.max(0, systemPrompt.length - projectContextChars),
|
||||
},
|
||||
injectedWorkspaceFiles: buildInjectedWorkspaceFiles({
|
||||
bootstrapFiles: params.bootstrapFiles,
|
||||
injectedFiles: params.injectedFiles,
|
||||
bootstrapMaxChars: params.bootstrapMaxChars,
|
||||
}),
|
||||
skills: {
|
||||
promptChars: params.skillsPrompt.length,
|
||||
entries: skillsEntries,
|
||||
},
|
||||
tools: {
|
||||
listChars: toolListChars,
|
||||
schemaChars: toolsSchemaChars,
|
||||
entries: toolsEntries,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user