You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(agent): multi-root workspace support — resolve paths across ALL folders
The agent's tool layer resolved every path against workspaceFolders[0] and
its sandbox check rejected anything outside it, so folders added to the
workspace (File > Add Folder) were invisible AND unreachable — while
list_files (multi-root aware via findFiles/asRelativePath) kept showing
folder-name-prefixed paths the other tools then failed to resolve.
- resolveWorkspacePath(): shared resolver; sandbox = the UNION of workspace
folders, read LIVE per call so a folder added mid-session works on the
very next tool call. Accepts the asRelativePath folder-name prefix
convention; probes other folders for reads; create-mode targets the
named folder, else the first.
- search: sweeps every folder, prefixing hits with the folder name so
results feed straight back into read_file/edit_file.
- run_command: optional "folder" input picks the working directory.
- system prompt: names all workspace folders per run; errors hint the
addressable folder names.
- reviewSession applyEdit/applyDelete: use the SAME resolver so the agent
and the Keep/Undo pipeline can never disagree about where a file lives.
Tests: test/workspacePaths.test.js (14 cases incl. a literal repro of the
mid-session Add Folder bug and folder-escape rejections).
Copy file name to clipboardExpand all lines: extensions/levelcode-ai/agent.js
+73-19Lines changed: 73 additions & 19 deletions
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,7 @@ const SYSTEM_BASE = [
27
27
'- Use delete_file to remove an existing file (e.g. during a refactor). To RENAME/move a file, write_file the new path then delete_file the old one. Deletions are reviewable (Keep/Undo) and restorable from the per-turn checkpoint.',
28
28
'- Your file edits are APPLIED IMMEDIATELY and the user reviews them afterward in the editor with Keep/Undo — do NOT wait for approval, and do NOT re-edit a file you just edited. Only run_command still needs approval; if the user skips a command, adapt or stop.',
29
29
'- Commands that do NOT exit on their own (dev servers, file watchers, tail -f) MUST be run with run_command background:true — it returns immediately so you keep working instead of hanging. After starting one, call read_command_output with the returned id to watch for a readiness/port line (e.g. "listening on :3000") before you test against it. Use a normal foreground run_command for things that finish (builds, installs, tests, git, curl). This pairs with verification: bring the app up in the background, confirm it serves, fix, repeat.',
30
-
'- Paths are relative to the workspace root.',
30
+
'- Paths are relative to the workspace root. In a MULTI-ROOT workspace (several top-level folders), paths from list_files/search are prefixed with the folder name (e.g. "thin.ly/app/models/link.rb") — use them exactly as shown; an unprefixed path resolves against the first folder. To create a file in a specific folder, prefix its name. run_command accepts an optional "folder" to pick which folder it runs in.',
31
31
'- For a multi-step goal, call update_plan FIRST with a short checklist (3-8 short items, all "pending"), then call it again to set an item "in_progress" when you start it and "done" when finished. Skip the plan for trivial single-step goals.',
32
32
'- If the goal truly depends on a decision only the user can make (tech stack, scope, where to create files, must-have features), call ask_user ONCE with concise multiple-choice questions (a short header + 2-4 concrete options each) INSTEAD of writing the questions as prose. Then act on their answers and do not ask again. Do NOT ask about things you can reasonably decide yourself — prefer a sensible default and proceed.',
33
33
'- You have SKILLS — short expert playbooks for common task types, listed under "Available skills" below with a name and a one-line description. When the user\'s goal clearly matches a skill\'s description (e.g. reviewing a diff, fixing one reported bug, writing tests), call use_skill with that exact name FIRST, before other tools, and follow the steps it returns. Pick at most one; if nothing clearly fits, just proceed normally. Do not mention skills to the user.',
{name: 'read_file',description: 'Read a workspace file (path relative to the workspace root).',input_schema: {type: 'object',properties: {path: {type: 'string'}},required: ['path']}},
40
+
{name: 'read_file',description: 'Read a workspace file (path relative to the workspace root; in a multi-root workspace use the folder-name prefix exactly as list_files shows it).',input_schema: {type: 'object',properties: {path: {type: 'string'}},required: ['path']}},
41
41
{name: 'search',description: 'Search file contents for a literal string. Returns file:line snippets.',input_schema: {type: 'object',properties: {query: {type: 'string'}},required: ['query']}},
42
42
{name: 'update_plan',description: 'Declare or update your task checklist for a multi-step goal. Pass the FULL list each time, each item with a status. Call it once up front (all pending), then again to mark an item in_progress when you start it and done when finished. Skip for trivial single-step goals.',input_schema: {type: 'object',properties: {todos: {type: 'array',items: {type: 'object',properties: {title: {type: 'string'},status: {type: 'string',enum: ['pending','in_progress','done']}},required: ['title','status']}}},required: ['todos']}},
43
43
{name: 'edit_file',description: 'Make a targeted edit to an EXISTING file: replace an exact, unique snippet (old_str) with new_str. Applied immediately; the user reviews it with Keep/Undo. old_str must appear exactly once — include enough surrounding context to be unique.',input_schema: {type: 'object',properties: {path: {type: 'string'},old_str: {type: 'string'},new_str: {type: 'string'}},required: ['path','old_str','new_str']}},
44
44
{name: 'write_file',description: 'Create a new file (or fully overwrite a short one) with the COMPLETE content. For edits to existing files, prefer edit_file. Applied immediately; the user reviews it with Keep/Undo.',input_schema: {type: 'object',properties: {path: {type: 'string'},content: {type: 'string'}},required: ['path','content']}},
45
45
{name: 'delete_file',description: 'Delete an EXISTING workspace file (e.g. removing a file during a refactor). Applied immediately; the user reviews it with Keep/Undo, and the per-turn checkpoint can restore it. To RENAME or move a file: write_file the new path, then delete_file the old one.',input_schema: {type: 'object',properties: {path: {type: 'string'}},required: ['path']}},
46
-
{name: 'run_command',description: 'Run a shell command in the workspace root. Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.',input_schema: {type: 'object',properties: {command: {type: 'string'},explanation: {type: 'string'},background: {type: 'boolean',description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.'}},required: ['command']}},
46
+
{name: 'run_command',description: 'Run a shell command in the workspace root (or a named workspace folder via "folder" in multi-root workspaces). Requires approval. Pass background:true for commands that do not exit on their own (servers, watchers) so the agent is not blocked — it returns immediately and you read progress later with read_command_output.',input_schema: {type: 'object',properties: {command: {type: 'string'},explanation: {type: 'string'},folder: {type: 'string',description: 'multi-root workspaces only: the workspace folder NAME to run in; defaults to the first folder'},background: {type: 'boolean',description: 'true = start it and keep working without waiting (dev servers, watchers, tail -f). Returns immediately with an id; poll read_command_output for its output/status.'}},required: ['command']}},
47
47
{name: 'read_command_output',description: 'Read recent output + status of a command started with run_command background:true. Returns a status header ([running on :3000] / [exited 0] / [stopped]) followed by the latest output lines. Poll this to wait for a server to become ready before testing against it.',input_schema: {type: 'object',properties: {id: {type: 'string',description: 'the id returned by a background run_command'},lines: {type: 'number',description: 'max recent output lines to return (default 80, max 400)'}},required: ['id']}},
48
48
{name: 'ask_user',description: 'Ask the user one or more multiple-choice questions when the goal genuinely depends on a decision only they can make (tech stack, scope, where to put files, must-have features). The user picks by CLICKING — do NOT write questions as prose. Ask ONCE up front with all your questions, then proceed with the answers and never re-ask. Prefer sensible defaults over asking; only ask when a wrong guess would waste real work.',input_schema: {type: 'object',properties: {questions: {type: 'array',items: {type: 'object',properties: {header: {type: 'string',description: 'a 1-3 word tag for the question'},question: {type: 'string'},multiSelect: {type: 'boolean',description: 'true if several options can be picked at once'},options: {type: 'array',items: {type: 'object',properties: {label: {type: 'string'},description: {type: 'string'}},required: ['label']}}},required: ['question','options']}}},required: ['questions']}},
49
49
{name: 'use_skill',description: 'Load an expert playbook (SKILL.md) for a task type, chosen from the "Available skills" list in your system prompt. Returns the skill\'s step-by-step instructions as the tool result — then follow them. Read-only and instant (no approval). Call it once, early, when the goal matches a skill\'s description.',input_schema: {type: 'object',properties: {name: {type: 'string',description: 'the exact skill name from the Available skills list'}},required: ['name']}}
@@ -132,6 +132,11 @@ function makeDiff(oldStr, newStr) {
return'Started in the background as id "'+runId+'" — it keeps running while you continue. Use read_command_output with this id to watch its output; wait for a readiness/port line before testing against it. Do not start it again.';
@@ -405,7 +454,12 @@ async function runAgent(ctx) {
405
454
if(!root){ctx.post({type: 'agentError',message: 'Open a folder first — the agent works on your workspace.'});ctx.post({type: 'agentDone',reason: 'error'});return;}
406
455
ctx.root=root;
407
456
// M6.5 implicit skills: build the system prompt ONCE per run — append the tiny name+description menu.
// Multi-root: name every workspace folder so the model addresses them by prefix from turn one.
458
+
constwsFolders=workspaceFolderList();
459
+
constmultiRootNote=wsFolders.length>1
460
+
? '\n\nWorkspace folders (multi-root — prefix paths with the folder name): '+wsFolders.map((f)=>f.name).join(', ')+'. The first folder ("'+wsFolders[0].name+'") is the default for unprefixed paths and run_command.'
0 commit comments