prompt: inject full ignore-aware file list at boot (gitignore-respected, capped 5000)
ci / check (macos-latest) (push) Canceled after 0s
ci / check (ubuntu-latest) (push) Canceled after 0s
ci / check (windows-latest) (push) Canceled after 0s

walk() at boot -> Session.workspaceFiles -> systemPrompt Environment
section + subagent system prompts. read_many_files still on-demand,
but the agent now sees the tree without a tool call.
This commit is contained in:
asepharyana
2026-09-09 14:19:29 +07:00
parent b7c3490993
commit 0bdf642672
5 changed files with 44 additions and 2 deletions
+10
View File
@@ -170,6 +170,14 @@ const promptHistory = await store.loadHistory();
const installedPlugins = has('--no-plugins') ? { plugins: [], errors: [] } : await registry.loadInstalledPlugins();
// Full ignore-aware file list injected into the system prompt at boot.
// Same walk as `@` completion (5000 cap, .gitignore + .shiroignore, skips .git/node_modules),
// so a filtered repo stays small even though it's "full".
let workspaceFiles: string[] = [];
try {
for await (const rel of walk({ limit: 5000 })) workspaceFiles.push(rel);
} catch {}
/** Installed entries, as `kind:name`, so the registry list can mark what is already here. */
async function installedNames(): Promise<Set<string>> {
const names = new Set<string>();
@@ -321,6 +329,7 @@ const session = new Session({
skills,
plugins,
agent: agentVariant,
...(workspaceFiles.length > 0 ? { workspaceFiles } : {}),
...(cfg.toolSets ? { toolSets: cfg.toolSets } : {}),
...(cfg.permission ? { permissions: cfg.permission } : {}),
// Headless has no one to answer, so the tool is withheld rather than left to hang.
@@ -343,6 +352,7 @@ const session = new Session({
model: languageModel ?? unconfiguredModel,
subagentModel,
subagentModelId: cfg.subagentModel,
...(workspaceFiles.length > 0 ? { workspaceFiles } : {}),
onUsage: (u) => recordSubagent(u),
...(headless ? {} : { report: subagents.emit }),
// A worker's writes go through the parent's rules and the parent's
+3 -1
View File
@@ -20,6 +20,8 @@ export type PromptParts = {
canAsk?: boolean;
/** MCP server names — listed by name only so their schemas cost nothing until mcp_call. */
mcpServers?: readonly string[];
/** Ignore-aware workspace file list injected at boot (gitignore-respected, capped). */
workspaceFiles?: readonly string[];
};
type ToolDoc = { name: string; line: string };
@@ -215,7 +217,7 @@ export function systemPrompt(parts: PromptParts): string {
Environment
- Workspace root: ${cwd}
- Platform: ${process.platform}
- Paths are resolved inside the workspace. Anything outside it is refused.
- Paths are resolved inside the workspace. Anything outside it is refused.${parts.workspaceFiles && parts.workspaceFiles.length > 0 ? `\n- Workspace files (${parts.workspaceFiles.length}, gitignore-respected, capped 5000):\n${parts.workspaceFiles.join('\n')}` : ''}
Tools available to you now
${renderTools(toolNames)}${mcpServers.length > 0 ? `\n\nMCP servers (${mcpServers.length}): ${mcpServers.join(', ')} — tools are NOT in the prompt. Use mcp_list to see what each exposes, mcp_inspect for a tool\'s schema, then mcp_call to run it. Each mcp_call needs approval like a built-in.` : ''}
+3
View File
@@ -98,6 +98,8 @@ export type SessionOptions = {
learnerModel?: LanguageModel;
/** Emit learner notices to the UI. */
onNotice?: (text: string) => void;
/** Ignore-aware file list injected into the system prompt at boot; gitignore-respected. */
workspaceFiles?: readonly string[];
/** Disable background auto-learn (tests). */
disableAutoLearn?: boolean;
};
@@ -487,6 +489,7 @@ export class Session {
availableTools: this.activeTools(),
canAsk: this.opts.ask !== undefined && this.activeTools().includes('ask'),
...(this.mcpServerNamesForPrompt() ? { mcpServers: this.mcpServerNamesForPrompt() } : {}),
...(this.opts.workspaceFiles && this.opts.workspaceFiles.length > 0 ? { workspaceFiles: this.opts.workspaceFiles } : {}),
});
}
+9 -1
View File
@@ -103,6 +103,11 @@ Rules:
it said, and anything you could not finish. The parent cannot see your transcript.`,
};
function withWorkspaceFiles(base: string, workspaceFiles?: readonly string[]): string {
if (!workspaceFiles || workspaceFiles.length === 0) return base;
return `${base}\n\nWorkspace files (${workspaceFiles.length}, gitignore-respected, capped 5000):\n${workspaceFiles.join('\n')}`;
}
/** One line of detail for the panel: the argument that identifies the call. */
const summarize = (input: unknown): string => {
if (input === null || typeof input !== 'object') return String(input);
@@ -148,6 +153,7 @@ async function runOne(
model: LanguageModel;
subagentModel?: LanguageModel;
cwd?: string;
workspaceFiles?: readonly string[];
maxSteps?: number;
report?: SubagentReporter;
approve?: SubagentApproval;
@@ -170,7 +176,7 @@ async function runOne(
const model = flavour === 'explore' ? (opts.subagentModel ?? opts.model) : opts.model;
const result = streamText({
model,
system: PROMPTS[flavour](opts.cwd ?? process.cwd()),
system: withWorkspaceFiles(PROMPTS[flavour](opts.cwd ?? process.cwd()), opts.workspaceFiles),
messages: [{ role: 'user', content: spec.prompt }],
tools: TOOLS[flavour],
stopWhen: isStepCount(opts.maxSteps ?? 20),
@@ -236,6 +242,7 @@ export function createTaskTool(opts: {
/** Its id, so the parent can price the subagent's spend separately. */
subagentModelId?: string;
cwd?: string;
workspaceFiles?: readonly string[];
maxSteps?: number;
report?: SubagentReporter;
/** Parent-owned approval for a worker's gated calls. Omit to disable `worker`. */
@@ -304,6 +311,7 @@ export function createTaskTool(opts: {
model: opts.model,
subagentModel: opts.subagentModel,
cwd: opts.cwd,
workspaceFiles: opts.workspaceFiles,
maxSteps: opts.maxSteps,
report: opts.report,
approve: opts.approve,