@@ -117,8 +117,10 @@ interface ChildLike { // what agent.js's runCommand uses of a Node Chil
117117 plus ` opts.env ` (at most 512 variables and 256 KiB; the ones that did not fit are named in one note — a variable is
118118 skipped, not everything after it). Never ` ctx ` , keys, tokens or the ` vscode ` object.
119119* ` commit ` is asked ** once** , after the command ran to its end and before ` close ` . ` control.cancelled() ` turns true
120- when the run was stopped while the set was being checked: the review then applies nothing. Once its first write has
121- begun nothing interrupts it.
120+ as soon as the run is stopped (Stop, kill, ` dispose() ` ), ** and** when the shell gave up on the review after
121+ ` workers.commitMs ` . A review looks at it before each change: while the set is being checked a Stop applies nothing;
122+ while it is being applied what is already in the review stays, nothing more is started, and a change in flight is
123+ never interrupted. The shell does not wait for a review after a Stop: it settles the run at once (§5).
122124
123125` Change ` :
124126
@@ -138,7 +140,9 @@ host translates a relocated mount back before a change reaches the review. `befo
138140
139141``` ts
140142runtime .folders (): Array < { name : string ; root : string ; uri : string }> // live workspace folders, in order
141- runtime .mounts (): Array < { ... folder ; mount : string }> // where the engine sees each (README §4)
143+ runtime .mounts (): Array < { ... folder ; mount : string }> // where the engine sees each (§4 Mounts)
144+ runtime .resolve (path : string ): Uri | null // an ENGINE path (as a command prints it) -> the workspace Uri; null outside the mounts
145+ runtime .stat (path : string ): Promise < { type : ' file' | ' dir' , size : number } | null > // bounded by fsMs; null for nothing / outside / error / timeout
142146runtime .createSession (o ?: { cwd?: string }): Session // a persistent terminal session
143147runtime .onAgentRun (listener : (e : AgentRunEvent ) => void ): { dispose(): void } // every run_command, for the mirror tab
144148runtime .dispose (): void
@@ -190,11 +194,23 @@ The host checks that every `fs` path is inside a mounted folder and that the gen
190194sends after ` done ` , or from a terminated generation, is dropped.
191195
192196** Mounts.** The engine sees a folder at its real root when that is a path it can host. A root that is ` / ` , that is
193- under one of the engine's own top-level names (` /bin ` , ` /dev ` , ` /home ` , ` /proc ` , ` /tmp ` , ` /usr ` ), or that overlaps an
194- earlier folder is ** relocated** by the host (` planMounts ` , in ` runtime.js ` ) to ` /mnt/<slug> ` — or to ` /lc-mnt/<slug> ` ,
195- ` /lc-mnt-2/<slug> ` … when a folder is itself rooted at ` /mnt ` . The plan always terminates and never nests mounts. The
196- run's stderr names every relocation once, with the folder's name and path made safe. ` Change ` paths are translated back
197- (§3).
197+ under one of the engine's own top-level names (` /bin ` , ` /dev ` , ` /home ` , ` /proc ` , ` /tmp ` , ` /usr ` ), or that overlaps
198+ another folder is ** relocated** by the host (` planMounts ` , in ` runtime.js ` ) to ` <base>/<leaf> ` :
199+
200+ * ` base ` is ` /mnt ` — or ` /lc-mnt ` , ` /lc-mnt-2 ` … when a folder that stayed is at or above it;
201+ * ` leaf ` is the folder's name with every run of characters outside ` [A-Za-z0-9._-] ` replaced by ` _ ` , leading dots
202+ replaced by ` _ ` (so ` . ` and ` .. ` are ` _ ` ), at most 100 characters, ` -2 ` , ` -3 ` … on a clash; a folder with no name is
203+ called by the last segment of its path, else ` folder ` ; case is kept;
204+ * every folder that ** can** stay does, first; a moved folder never displaces one that is fine; the rest are placed in
205+ order.
206+
207+ The plan always terminates, never nests mounts, and is the ** engine's own algorithm** : the host hands it over in ` init `
208+ and the engine never moves a mount a second time (a fuzz test pins that the two agree). A run that gets a result from
209+ the engine names every relocation once on stderr, with the folder's name and path made safe; a run that was stopped
210+ before the engine said ` done ` says only that it was stopped. ` Change ` paths are translated back (§3).
211+
212+ The messages beyond the tables above — ` ready.mounts ` , ` ready.error ` , ` init.commands ` filtering (a wrapper is offered
213+ only when its original is), cap validation, ` done.stats ` and the rest — are additive and listed in ` engine/NOTES.md ` §6.
198214
199215## 5. Commit
200216
@@ -214,10 +230,19 @@ script that ran to its end, whatever its status; otherwise nothing is applied):
214230 ` workspace.fs ` — creates, then deletes, deepest paths first, ** never recursive and never to the trash** — and reports
215231 a partial application honestly (` N of M applied ` ).
216232
217- While a run is committing, a worker failure or ` dispose() ` does not settle it with a false "nothing was applied": it
218- ends when the commit does, and says how many changes landed. Every provider call has a deadline (` workers.fsMs ` , the
219- review ` workers.commitMs ` ); a call that outlives it is reported (` stopped at ` ), and a write that completes later still
220- lands — that is not cancellable, and the result says so.
233+ ** The commit is bounded.** Every provider call has a deadline (` workers.fsMs ` ). The terminal's read-only check has one
234+ budget in all (` workers.checkMs ` , which grows with the set: 60 s up to 120 entries, 500 ms an entry above, at most 120 s;
235+ the first call that is not answered in ` fsMs ` ends it): a set that could not be checked in time is ** refused whole**
236+ and nothing is applied. The part that writes — levelcode-ai's review and then the folders it leaves to the shell, or the
237+ terminal's own writes — has ` workers.commitMs ` in all. A call that outlives its deadline is reported (` stopped at ` );
238+ a write that completes later still lands — a provider call cannot be cancelled, and the result says so.
239+
240+ ** A Stop ends a commit at once.** Stop, Ctrl+C, a kill and ` dispose() ` during the commit give up every wait and start
241+ nothing more: a set that was only being checked applies nothing (` stopped before anything was applied ` ); one that was
242+ being written says how many changes were confirmed and that the one in flight may still land. The result keeps the
243+ notes the run already had (moved folders, the engine's notes, output cut, variables not kept) in front of its own. A
244+ worker that dies * after* ` done ` changes the wording of the result ("…, after the command had finished"), never what
245+ is on disk.
221246
222247Every write the engine attempts outside a mount, to a protected path or past a cap is ** dropped and recorded** in
223248` denied ` (never thrown: just-bash rejects ` exec() ` for any error from a write in a redirection and loses all output);
@@ -234,13 +259,18 @@ an unterminated quote / here-doc / trailing `\`, `|` or `&&` continues on the ne
234259no parser export in the browser build). A pasted chunk with a line end is inserted, never executed.
235260
236261** ` /tmp ` belongs to a terminal's worker.** It survives from one line to the next, and is lost when that worker is
237- replaced (idle for 60 s — twice that when ` /tmp ` holds files —, Ctrl+C, a deadline, a crash, eviction). The next line
238- says so once, with the number of files it held. An agent run never keeps ` /tmp ` .
262+ replaced (idle for 60 s — twice that when ` /tmp ` holds anything, folders included —, Ctrl+C, a deadline, a crash,
263+ eviction; at the worker cap the least recently used idle worker * without* ` /tmp ` content goes first). The next line says
264+ so once, with the number of files and folders it held (` done.stats.tmpEntries ` , else ` tmpFiles ` ). An agent run never
265+ keeps ` /tmp ` .
239266
240267Output to the terminal goes through the allow-list filter of ` src/terminal/output-filter.js ` , and ** every** string that
241268did not come from this extension's own constants is untrusted and goes through ` src/sanitize.js ` (§8). File
242269links in output are made only by ` src/terminal/links.js ` : a path that exists as a file under a mount, at most five per
243- line, 500 ms in all.
270+ line, 500 ms in all. A link is about the folder the line was ** printed in** (the end of its command, then its start; per
271+ run in the agent's tab): ` src/terminal/line-log.js ` remembers what a terminal printed — 400 lines of at most 4,000
272+ characters, memory only — so a ` cd ` does not move older links. Two identical lines printed in two folders cannot be
273+ told apart (the later wins): the workbench hands the provider a line's text, not its row.
244274
245275The agent's commands are mirrored into one read-only tab "LevelCode Agent" without taking the focus.
246276
@@ -257,11 +287,15 @@ The agent's commands are mirrored into one read-only tab "LevelCode Agent" witho
257287| bytes read per run (the script's) / served by the host in all | 16 MiB / 32 MiB | same |
258288| bytes staged per file / per run / entries | 1 MiB / 8 MiB / 2000 | 8 MiB / 32 MiB / 2000 |
259289| files a reviewed commit may change | 300 | 2000 |
260- | a file call to the provider / levelcode-ai's review | 60 s / 120 s | 60 s / – |
290+ | a file call to the provider | 60 s | 60 s |
291+ | the read-only check of a set (all of it) | – | 60 s up to 120 entries, 500 ms an entry above, at most 120 s |
292+ | the part of a commit that writes (all of it) | 120 s (the review, then the folders it leaves) | 120 s |
261293| live engine workers / idle before terminate | 4 / 60 s | same |
262294| output returned to the model | head+tail window of 8000 chars with the status line last | n/a |
263295| output shown in a terminal tab per run | 1 MiB, in 16 KiB macrotask chunks | same |
264296| completion | – | 200 names, 2 s |
297+ | what a terminal remembers for links | – | 400 lines of 4,000 characters |
298+ | an ` explanation ` or a note shown in the agent's tab | the first 4,800 characters scanned, 300 shown, at most 8 notes | – |
265299
266300The engine's own table (` engine/limits.js ` ) has 45 keys; ` LIMITS.engine ` above is the ones the shell sets, the engine's
267301own values the rest.
@@ -275,7 +309,10 @@ own values the rest.
275309 (` output-filter.js ` ; only OSC 633 from ` shell-integration.js ` , with a random nonce per terminal), a card only
276310 through ` forDisplay ` , a one-line note through ` forNote ` . ` sanitize.js ` makes control, format (` Cf ` ), bidi and
277311 unpaired-surrogate characters visible as ` ␛ ` / ` [U+XXXX] ` ; the one exception is a single ZWNJ/ZWJ between two
278- letters of the ** same** Arabic or Indic script, which ` forTerminal ` keeps and ` forDisplay ` still shows.
312+ letters of the ** same** Arabic or Indic script (and the joiners of an emoji sequence, VS16 with an emoji), which
313+ ` forTerminal ` keeps and ` forDisplay ` still shows. The line editor makes the same exception for what is ** typed or
314+ pasted** , and says once under the line what it did not type. Every function that sees untrusted text is linear
315+ (` web/test/unit/helpers/shell-terminal-timing.js ` : 23 shapes of 200,000 characters under 500 ms).
279316* A command never changes the protected paths (` .git ` , ` .vscode ` , ` .levelcode ` , ` .cursor ` , ` .husky ` ,
280317 ` .github/workflows ` , ` AGENTS.md ` , ` CLAUDE.md ` , ` .cursorrules ` , ` .windsurfrules ` ), whole-set refusal.
281318* The browser's settings are ** application** -scoped: a folder's ` .vscode/settings.json ` cannot change the cloud
0 commit comments