Skip to content

fix(ai): runTool's dbg was out of scope — the auto-preview never opened - #93

Merged
ndemianc merged 1 commit into
developfrom
fix/agent-runtool-dbg-scope
Oct 3, 2026
Merged

ndemianc merged 1 commit into
developfrom
fix/agent-runtool-dbg-scope

Conversation

@ndemianc

@ndemianc ndemianc commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

When the agent starts a dev server, the built-in browser is supposed to open on its own. It doesn't: the handler that detects the address throws one statement before it would open the tab.

runTool logs through a bare dbg(...), but dbg was only ever declared inside runAgent — a different function. An out-of-scope name is not an error until the line runs, so nothing failed until a background command printed a local address:

entry.previewUrl = url;
dbg('preview.detected', { id: runId, url: url });   // ReferenceError: dbg is not defined
Promise.resolve(ctx.openPreview(url))…              // never reached

The three call sites

call where what the missing name did
preview.detected run_command's stdout handler threw before openPreview, and escaped into a live stream handler
preview.rejected the .catch on that openPreview promise the handler meant to absorb a rejection threw instead
bg.error the catch of the fire-and-forget background launcher turned a handled start failure into an unhandled rejection

The two preview.* calls arrived with auto-preview (b438b6c, 738e85f). bg.error is older — out of scope since background commands landed (3fc2eb9).

The fix

One line at the top of runTool, matching runAgent:

const dbg = ctx.dbg || (() => {});

runAgent already hands its ctx straight through (runTool(tu, ctx)) and the host puts dbg on that ctx, so both functions now log to the one sink. The call sites are untouched.

The test

test/agentRunCommand.test.js is the first test that executes this path. The neighbouring agent tests assert from source, and an out-of-scope name is invisible to a regex and to node --check — only running the line finds it.

It drives the real runAgent → runTool → run_command → runCommand → onChunk chain with three things replaced: vscode (as workspacePaths.test.js does), the provider (two scripted turns), and child_process.spawn (a fake child the test feeds stdout into — no process is started). runTool isn't exported, and going in through runAgent is the point anyway: it proves the logger the host passes is the one runTool ends up with. No export was added for the test.

Four cases: the reported path, the .catch site, the bg.error site, and a host that passes no dbg. The bg.error case uses fault injection (a stop registry whose set throws): runCommand resolves even when spawn itself throws, so that catch is purely defensive and there is no natural way into it.

Verification

  • 41 suites, 0 failures; node --check extensions/levelcode-ai/agent.js passes.
  • Fails before the fix with ReferenceError: dbg is not defined at onChunk (agent.js:444), through cap from the stdout data handler.
  • Mutation-checked. Breaking each call site alone fails its own case; dropping the || (() => {}) fallback fails the no-logger case; a private no-op that ignores ctx.dbg fails the logging assertion.
  • tsc --checkJs over extension.js reported exactly three TS2304 — these calls — before, and none after. Nothing else in its output moved: the remaining 104 diagnostics are the pre-existing ones (missing vscode/Node types, property-shape complaints).
  • A real child process, in a throwaway script outside the suite: on the old code openPreview is never called and the ReferenceError surfaces from the stream handler; on the new code it is called with http://localhost:5173/, with the address split across two real stdout chunks.

Worth knowing before merging

This turns auto-preview on for the first time. Every release from v0.9.2 to v1.2.0 carries all three out-of-scope calls, and this path is openPreview's only caller, so no shipped build can have opened a tab. After this, the host's openPreview runs in a real session for the first time; the intent is a Simple Browser tab beside the chat whenever the agent starts a server in the background. levelcode.ai.preview.autoOpen (default true) turns it off.

Not covered by CI, and not run here: the built editor. The last hop — simpleBrowser.api.open actually showing the tab — needs a packaged app and a live agent run, the same gap #35 called out. Worth one manual check before the next release.

The same static check over every .js file in extensions/, tools/ and scripts/ finds no other undefined name; what is left is Node globals without @types/node.

runTool logs through a bare dbg(...) in three places, but dbg was only ever
declared inside runAgent — a different function. Each call was a ReferenceError
the moment its line ran:

  preview.detected  run_command's stdout handler. It threw BEFORE
                    ctx.openPreview(url) was reached, so the built-in browser
                    never opened when the agent started a dev server — and the
                    exception escaped into a live stream handler.
  preview.rejected  the .catch on that same openPreview promise.
  bg.error          the catch of the fire-and-forget background launcher, where
                    it turned a HANDLED start failure into an unhandled rejection.

The fix is one line: runTool resolves its own logger from ctx.dbg, exactly as
runAgent does. runAgent already hands its ctx straight through, so both log to
the one sink the host passes. The call sites are untouched.

The two preview calls arrived with auto-preview (b438b6c, 738e85f); bg.error is
older, out of scope since background commands landed (3fc2eb9). Every release
from v0.9.2 to v1.2.0 carries all three, and this path is openPreview's only
caller, so no shipped build can have opened a tab — this is the change that
turns auto-preview on. Nothing caught it because an out-of-scope name is
invisible to `node --check` and to the source-regex tests, there is no
type-check gate, and no test executed this path.

test/agentRunCommand.test.js now does. It runs the real
runAgent -> runTool -> run_command -> runCommand -> onChunk chain with vscode,
the provider and child_process.spawn replaced (no process is started), and
covers all three call sites plus a host that passes no dbg. runTool is not
exported, so going in through runAgent also proves the logger the host passes
is the one runTool ends up with.

Verified: 41 suites, 0 failures. On the old code the new test fails with
"dbg is not defined" at onChunk (agent.js:444). Mutation-checked: breaking each
call site alone, dropping the no-op fallback, or swapping in a private no-op
each fails its own case. tsc --checkJs over extension.js reported exactly three
TS2304 (these calls) before and none after; nothing else in its output moved.

Not verified: the built editor. The last hop — simpleBrowser.api.open actually
showing the tab — needs a packaged app and a live agent run.
Copilot AI balanced review requested due to automatic review settings October 3, 2026 05:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The targeted fix is correct and its affected runtime paths are comprehensively tested.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes auto-preview failures by making runTool use the host-provided debug logger.

Changes:

  • Resolves dbg within runTool, with a no-op fallback.
  • Adds execution tests covering preview and background-command error paths.
File Description
extensions/​levelcode-ai/​agent.js Fixes the out-of-scope logger reference.
extensions/​levelcode-ai/​test/​agentRunCommand.test.js Tests preview opening, failures, and optional logging.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@ndemianc
ndemianc merged commit 4fbcc54 into develop Oct 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants