feat(coding-agent): allowed toggling workspace tree in system prompt

- Added `includeWorkspaceTree` configuration setting to optionally render the workspace directory tree.
- Configured the system prompt template and SDK to respect this toggle, allowing users to disable the tree to prevent prompt cache invalidation.
This commit is contained in:
can1357
2026-06-21 07:02:55 +02:00
parent 2eef88978b
commit 07c910210b
5 changed files with 38 additions and 4 deletions
@@ -967,6 +967,18 @@ export const SETTINGS_SCHEMA = {
},
},
includeWorkspaceTree: {
type: "boolean",
default: false,
ui: {
tab: "model",
group: "Prompt",
label: "Include Workspace Tree",
description:
"Render the workspace directory tree in the system prompt. WARNING: This can bust prompt caching across sessions when files are modified.",
},
},
personality: {
type: "enum",
values: ["default", "friendly", "pragmatic", "none"] as const,
@@ -29,6 +29,7 @@ Before making changes within these directories, you MUST read:
The context files above are loaded automatically. You NEVER `search`/`find` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise.
{{/ifAny}}
{{#if includeWorkspaceTree}}
{{#if workspaceTree.rendered}}
<workspace-tree>
Working directory layout (sorted by mtime, recent first; depth ≤ 3):
@@ -38,6 +39,7 @@ Working directory layout (sorted by mtime, recent first; depth ≤ 3):
{{/if}}
</workspace-tree>
{{/if}}
{{/if}}
Today is {{date}}, and the current working directory is '{{cwd}}'.
+8 -1
View File
@@ -796,6 +796,7 @@ export interface BuildSystemPromptOptions {
customPrompt?: string;
appendPrompt?: string;
inlineToolDescriptors?: boolean;
includeWorkspaceTree?: boolean;
}
/**
@@ -813,6 +814,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
contextFiles: options.contextFiles,
appendSystemPrompt: options.appendPrompt,
inlineToolDescriptors: options.inlineToolDescriptors,
includeWorkspaceTree: options.includeWorkspaceTree,
toolNames: options.tools?.map(tool => tool.name),
tools: toolMap ? buildSystemPromptToolMetadata(toolMap) : undefined,
});
@@ -1120,9 +1122,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
// startup does not perform a second recursive filesystem search. Subagents
// inherit the parent's resolved values via options.
const STARTUP_SCAN_DEADLINE_MS = 5000;
const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
? Promise.resolve(options.workspaceTree)
: logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }));
: includeWorkspaceTree
? logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }))
: Promise.resolve({ rootPath: cwd, rendered: "", truncated: false, totalLines: 0, agentsMdFiles: [] });
workspaceTreePromise.catch(() => {});
// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
@@ -2101,6 +2106,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
const eagerTasks = settings.get("task.eager") !== "default";
const eagerTasksAlways = settings.get("task.eager") === "always";
const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
const rebuildSystemPrompt = async (
toolNames: string[],
tools: Map<string, AgentTool>,
@@ -2194,6 +2200,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
taskBatch: settings.get("task.batch"),
secretsEnabled,
workspaceTree: workspaceTreePromise,
includeWorkspaceTree,
memoryRootEnabled: memoryBackend.id === "local",
model: settings.get("includeModelInPrompt") ? getActiveModelString() : undefined,
personality: agentKind === "sub" ? "none" : settings.get("personality"),
+15 -3
View File
@@ -421,6 +421,8 @@ export interface BuildSystemPromptOptions {
model?: string;
/** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
personality?: Personality;
/** Whether to include the workspace directory tree in the system prompt. Default: false */
includeWorkspaceTree?: boolean;
}
/** Result of building provider-facing system prompt messages. */
@@ -461,6 +463,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
memoryRootEnabled = false,
model,
personality = "default",
includeWorkspaceTree = false,
} = options;
const inlineToolDescriptors = providedInlineToolDescriptors ?? false;
const resolvedCwd = cwd ?? getProjectDir();
@@ -523,9 +526,17 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
const workspaceTreePromise =
providedWorkspaceTree !== undefined
? Promise.resolve(providedWorkspaceTree)
: logger.time("buildWorkspaceTree", () =>
buildWorkspaceTree(resolvedCwd, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }),
);
: includeWorkspaceTree
? logger.time("buildWorkspaceTree", () =>
buildWorkspaceTree(resolvedCwd, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }),
)
: Promise.resolve({
rootPath: resolvedCwd,
rendered: "",
truncated: false,
totalLines: 0,
agentsMdFiles: [],
});
const skillsPromise: Promise<Skill[]> =
providedSkills !== undefined
? Promise.resolve(providedSkills)
@@ -670,6 +681,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
secretsEnabled,
hasMemoryRoot: memoryRootEnabled,
hasObsidian: hasObsidian(),
includeWorkspaceTree,
};
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
const systemPrompt = [rendered];
@@ -121,6 +121,7 @@ describe("SYSTEM.md prompt assembly", () => {
rules: [],
toolNames: ["read"],
tools: READ_TOOL,
includeWorkspaceTree: true,
workspaceTree: {
rootPath: projectDir,
rendered: ".\n - nested/",