diff --git a/.claude/skills/debug-standalone-agent-browser/SKILL.md b/.claude/skills/debug-standalone-agent-browser/SKILL.md index 55176113f..45ec1c7ec 100644 --- a/.claude/skills/debug-standalone-agent-browser/SKILL.md +++ b/.claude/skills/debug-standalone-agent-browser/SKILL.md @@ -1,6 +1,6 @@ --- name: debug-standalone-agent-browser -description: Use when debugging Dormouse standalone behavior through the browser-based agent-browser harness instead of Tauri. Covers launching a fresh `pnpm dev:standalone:ab` run, observing sidecar and in-browser logs together, driving the UI with `agent-browser`, clearing stale nested browser sessions, and timing agent-browser screencast/tab behavior. +description: Use when debugging Dormouse standalone behavior through the browser-based agent-browser harness instead of Tauri. Covers launching a fresh `pnpm innerdogfood` run, observing sidecar and in-browser logs together, driving the UI with `agent-browser`, clearing stale nested browser sessions, and timing agent-browser screencast/tab behavior. --- # Debug Standalone With Agent Browser @@ -15,7 +15,7 @@ Run from the repo root: DORMOUSE_BROWSER_DEV_AB_SESSION=dormouse-debug-$(date +%s) \ DORMOUSE_BROWSER_DEV_VITE_PORT=1550 \ DORMOUSE_BROWSER_DEV_HOST_PORT=1552 \ -pnpm dev:standalone:ab +pnpm innerdogfood ``` The harness: @@ -59,7 +59,7 @@ Stop any running harness with Ctrl-C (or `pkill -f dev-agent-browser.mjs`) befor ## Driving Dormouse -Use the outer harness session printed by `dev:standalone:ab`: +Use the outer harness session printed by `innerdogfood`: ```sh agent-browser --session snapshot -i diff --git a/AGENTS.md b/AGENTS.md index 51f339e03..06e574abb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,10 @@ pnpm install # install deps pnpm build # build lib, vscode extension, Pocket, and website ``` +**Inside Dormouse, run `innerdogfood`** — `dor ensure -- pnpm innerdogfood`, then +`dor ab --key innerdogfood open http://localhost:1420` +(`docs/specs/transport.md` → "Standalone browser-dev harness"). + **Open every PR as a draft.** Chromatic bills per snapshot and skips drafts, so marking a PR ready for review is what spends them. diff --git a/docs/specs/mouse-and-clipboard.md b/docs/specs/mouse-and-clipboard.md index 1e296070d..0fb23faa2 100644 --- a/docs/specs/mouse-and-clipboard.md +++ b/docs/specs/mouse-and-clipboard.md @@ -62,7 +62,7 @@ Selection is available whenever the terminal handles the mouse (§3.5, §6.1). ### 3.1 Initiating a Selection -- A click-and-drag in the terminal content area begins a selection; a ~4px movement threshold separates a plain click (which only shifts pane focus) from a drag. +- **Must begin selection after a click-and-drag crosses ~4px**; plain clicks shift pane focus or activate hyperlinks. **Must capture mouse presses on xterm’s screen immediately** (rationale). Pinned by `lib/src/lib/terminal-mouse-router.test.ts`. - On touch or pen, a primary pointer tap-and-drag takes the same path; non-primary touch pointers are ignored. - The selection draws as a single perimeter outline tracing the union of selected cells (§7 owns rendering). Color is `--color-focus-ring` (`docs/specs/theme.md`), with a hardcoded cornflower-blue final fallback in `SelectionOverlay.tsx`. - **A drag whose button comes up outside the webview iframe must still finalize**, by captured `pointerup` or the window-`mousemove` backstop (rationale). diff --git a/docs/specs/mouse-and-clipboard.rationale.md b/docs/specs/mouse-and-clipboard.rationale.md index 5491d2863..51279d5bd 100644 --- a/docs/specs/mouse-and-clipboard.rationale.md +++ b/docs/specs/mouse-and-clipboard.rationale.md @@ -4,6 +4,8 @@ ## 3.1 Initiating a Selection +**Why mouse capture targets xterm’s screen.** Reproduced with Codex CLI 0.153.4 in the Chromium innerdogfood harness (2026-09): Codex emitted valid OSC 8 links and xterm recognized their targets. Capturing on Dormouse's wrapper at pointerdown retargeted mouseup outside xterm's screen and cleared its hovered link, preventing activation. Capturing on xterm’s screen keeps the release on the link-handler path. Deferring capture until the drag threshold restored clicks too, but lost a selection whose first coalesced movement left the iframe; early screen capture preserves both behaviors. An innerdogfood iframe probe starting 2 px inside the top edge and moving directly to −30 px delivered the move and release to `.xterm-screen` and finalized the selection (Chromium, 2026-09). + **How a mouse-up outside the iframe still reaches us.** Capture is taken on mouse-down, and Chromium delivers the captured `pointerup` across the frame boundary even when the button comes up over host chrome. Engines that do not honor cross-frame capture deliver nothing, so a window `mousemove` reporting `buttons === 0` stands in for the missed mouse-up: a pointer still holding the button reports `buttons === 1`, so the heal cannot fire mid-drag, but it does need the pointer to re-enter the frame. Against double-finalizing, the captured-pointerup path defers to a macrotask and stands down if the compatibility mouseup for an *inside* release arrives first. ## 5.1 Detection diff --git a/docs/specs/standalone.md b/docs/specs/standalone.md index 6ea823831..b4e7bf4de 100644 --- a/docs/specs/standalone.md +++ b/docs/specs/standalone.md @@ -519,7 +519,7 @@ Source of truth: `init_log` / `read_update_log` in `standalone/src-tauri/src/lib Source of truth: `standalone/package.json` (package scripts), `standalone/src-tauri/tauri.conf.json` (`build`, `bundle.resources`), and the root -`package.json` for the `dev:standalone*` orchestration. +`package.json` for the `dev:standalone` and `innerdogfood` orchestration. - `stage` = `stage:dor-cli` (build + stage the dor CLI, `docs/specs/dor-cli.md`) plus `stage:sidecar-proxy` (`build-sidecar-proxy.mjs` bundles the @@ -538,7 +538,7 @@ Source of truth: `standalone/package.json` (package scripts), Frontend edits hot-reload, but changes to the sidecar, the staged dor CLI, or the bundled `lib/src/host/` sources need a manual re-stage and app restart — the dev loop does not watch them. -- `pnpm dev:standalone:ab` runs the sidecar + webview in a normal browser via the +- `pnpm innerdogfood` runs the sidecar + webview in a normal browser via the browser-dev harness instead of the Tauri WebView (`docs/specs/transport.md`, Standalone browser-dev harness). diff --git a/docs/specs/transport.md b/docs/specs/transport.md index bb00c2b30..ab26e160d 100644 --- a/docs/specs/transport.md +++ b/docs/specs/transport.md @@ -26,7 +26,7 @@ Optional booleans: ### Standalone browser-dev harness -`pnpm dev:standalone:ab` starts the standalone sidecar directly, a localhost-only HTTP bridge, and Vite with `VITE_DORMOUSE_BROWSER_DEV_HOST`, then opens the app URL in an `agent-browser` session. The browser build uses `BrowserSidecarAdapter` instead of `TauriAdapter` whenever that env var is present. +`pnpm innerdogfood` starts the standalone sidecar directly, a localhost-only HTTP bridge, and Vite with `VITE_DORMOUSE_BROWSER_DEV_HOST`, then opens the app URL in an `agent-browser` session. The browser build uses `BrowserSidecarAdapter` instead of `TauriAdapter` whenever that env var is present. The bridge is a transport shim over the same sidecar protocol, not a second PTY implementation: fire-and-forget commands `POST /__dormouse_dev_host/send`, request/response commands `POST /__dormouse_dev_host/invoke`, host→webview events as SSE on `GET /__dormouse_dev_host/events`, and browser console output mirrored to `POST /__dormouse_dev_host/console` so one terminal shows sidecar, Vite, and in-browser logs together. The Burrow rides it too, on the message names below ("Message protocol"), so the harness runs a real Burrow against a per-run temp state directory (`docs/specs/standalone.md` → "Burrow service"). diff --git a/lib/src/lib/mirrored-constants.test.ts b/lib/src/lib/mirrored-constants.test.ts index c422b832d..531a77b8c 100644 --- a/lib/src/lib/mirrored-constants.test.ts +++ b/lib/src/lib/mirrored-constants.test.ts @@ -141,7 +141,7 @@ describe('the pairing walkthrough mirrors the copy it clicks', () => { // Not copy but a log line: the harness prints where the Burrow keeps its // state and the walkthrough parses it back out. Rendered with a stand-in path // rather than compared as text, so the two only have to agree on what the - // line looks like — the `[dev:standalone:ab]` prefix `log()` adds is free to + // line looks like — the `[innerdogfood]` prefix `log()` adds is free to // change, and the capture group has to survive. it('parses the state directory the harness actually logs', () => { const harness = 'standalone/scripts/dev-agent-browser.mjs'; diff --git a/lib/src/lib/terminal-mouse-router.test.ts b/lib/src/lib/terminal-mouse-router.test.ts index aa16464eb..d94ef420b 100644 --- a/lib/src/lib/terminal-mouse-router.test.ts +++ b/lib/src/lib/terminal-mouse-router.test.ts @@ -34,6 +34,7 @@ class ListenerHost { class FakeElement extends ListenerHost { setPointerCapture = vi.fn(); releasePointerCapture = vi.fn(); + querySelector = vi.fn(); getBoundingClientRect(): Pick { return { left: 0, top: 0 }; @@ -86,6 +87,8 @@ const dims: TerminalOverlayDims = { function createHarness(windowHost: ListenerHost) { const element = new FakeElement(); + const screen = new FakeElement(); + element.querySelector.mockReturnValue(screen); const terminal = { cols: 80, clearSelection: vi.fn(), @@ -103,7 +106,7 @@ function createHarness(windowHost: ListenerHost) { getOverlayDims: () => dims, setSelectionBaseline: vi.fn(), }); - return { cleanup, element, terminal, windowHost }; + return { cleanup, element, screen, terminal, windowHost }; } let windowHost: ListenerHost; @@ -349,35 +352,63 @@ describe('terminal-mouse-router: override suppression', () => { return pointerEvent({ pointerType: 'mouse', ...overrides }); } - // Drives a left-button mouse press into an active selection drag (capture + - // pendingDrag created on press, drag begun once movement crosses threshold). + // Drives a left-button mouse press into an active selection drag (pendingDrag + // and screen capture created on press; drag begins once movement crosses threshold). function startMouseDrag(element: FakeElement) { element.emit('pointerdown', mousePointer({ clientX: 5, clientY: 5 })); element.emit('mousedown', mouseEvent({ clientX: 5, clientY: 5 })); windowHost.emit('mousemove', mouseEvent({ clientX: 25, clientY: 15 })); } - it('captures the mouse pointer on a left-button press over terminal-owned content', () => { - const { cleanup, element } = createHarness(windowHost); + it('captures a plain click on xterm’s screen without suppressing its hyperlink events', () => { + const { cleanup, element, screen } = createHarness(windowHost); + const down = mouseEvent(); + const up = mouseEvent(); - element.emit('pointerdown', mousePointer({ clientX: 5, clientY: 5 })); + element.emit('pointerdown', mousePointer()); + element.emit('mousedown', down); + windowHost.emit('mousemove', mouseEvent({ clientX: 6, buttons: 1 })); + windowHost.emit('pointerup', mousePointer({ clientX: 6 })); + windowHost.emit('mouseup', up); - expect(element.setPointerCapture).toHaveBeenCalledWith(1); + expect(element.setPointerCapture).not.toHaveBeenCalled(); + expect(element.querySelector).toHaveBeenCalledWith('.xterm-screen'); + expect(screen.setPointerCapture).toHaveBeenCalledWith(1); + expect(screen.releasePointerCapture).toHaveBeenCalledWith(1); + expect(down.stopPropagation).not.toHaveBeenCalled(); + expect(up.stopPropagation).not.toHaveBeenCalled(); + expect(getMouseSelectionState('t1').selection).toBeNull(); + cleanup(); + }); + + it('captures before the first move so a drag can begin outside the iframe', () => { + const { cleanup, element, screen } = createHarness(windowHost); + + element.emit('pointerdown', mousePointer({ clientY: 2 })); + element.emit('mousedown', mouseEvent({ clientY: 2 })); + expect(screen.setPointerCapture).toHaveBeenCalledWith(1); + expect(getMouseSelectionState('t1').selection).toBeNull(); + + windowHost.emit('mousemove', mouseEvent({ clientY: -20, buttons: 1 })); + expect(getMouseSelectionState('t1').selection).toMatchObject({ dragging: true }); cleanup(); }); it('does not capture the mouse pointer for non-left buttons', () => { - const { cleanup, element } = createHarness(windowHost); + const { cleanup, element, screen } = createHarness(windowHost); - element.emit('pointerdown', mousePointer({ button: 2 })); + element.emit('pointerdown', mousePointer({ button: 2, clientX: 5, clientY: 5 })); + element.emit('mousedown', mouseEvent({ button: 2, clientX: 5, clientY: 5 })); + windowHost.emit('mousemove', mouseEvent({ button: 2, clientX: 25, clientY: 15 })); expect(element.setPointerCapture).not.toHaveBeenCalled(); + expect(screen.setPointerCapture).not.toHaveBeenCalled(); cleanup(); }); it('finalizes a mouse drag from a captured pointerup when the button is released outside the iframe', () => { vi.useFakeTimers(); - const { cleanup, element } = createHarness(windowHost); + const { cleanup, element, screen } = createHarness(windowHost); startMouseDrag(element); expect(getMouseSelectionState('t1').selection).toMatchObject({ dragging: true }); @@ -385,7 +416,7 @@ describe('terminal-mouse-router: override suppression', () => { // Released outside the iframe: only the captured pointerup reaches us, never // the compatibility mouseup. The deferred finalize ends the drag in place. windowHost.emit('pointerup', mousePointer({ clientX: 500, clientY: 500 })); - expect(element.releasePointerCapture).toHaveBeenCalledWith(1); + expect(screen.releasePointerCapture).toHaveBeenCalledWith(1); expect(getMouseSelectionState('t1').selection).toMatchObject({ dragging: true }); vi.runAllTimers(); diff --git a/lib/src/lib/terminal-mouse-router.ts b/lib/src/lib/terminal-mouse-router.ts index cd3bc5dd5..a30864ccc 100644 --- a/lib/src/lib/terminal-mouse-router.ts +++ b/lib/src/lib/terminal-mouse-router.ts @@ -67,6 +67,10 @@ export function attachTerminalMouseRouter({ return { row: absRow, col, startedInScrollback }; }; + // xterm's linkifier listens on its screen, not our outer wrapper. Capture + // there so even a plain click's retargeted mouseup reaches the link handler. + // Take capture on pointerdown: the first move may already be outside the iframe. + const mouseCaptureElement = element.querySelector('.xterm-screen') ?? element; const DRAG_THRESHOLD_PX_SQ = 16; // Touch has no Alt key, so a double-tap-then-drag is how a block selection is // started on touch. A second touch within this window and distance of the @@ -82,7 +86,6 @@ export function attachTerminalMouseRouter({ button: number; clientX: number; clientY: number; - pointerId: number | null; touchLike: boolean; } | null = null; let activePointerId: number | null = null; @@ -112,7 +115,7 @@ export function attachTerminalMouseRouter({ const beginPendingDrag = ( ev: MouseEvent | PointerEvent, - opts: { pointerId: number | null; touchLike: boolean; block?: boolean }, + opts: { touchLike: boolean; block?: boolean }, ) => { const { state, cell, terminalOwns } = terminalOwnsEvent(ev); // Touch suppresses compatibility mousedown, so popup's mouse listener @@ -136,7 +139,6 @@ export function attachTerminalMouseRouter({ button: ev.button, clientX: ev.clientX, clientY: ev.clientY, - pointerId: opts.pointerId, touchLike: opts.touchLike, }; return true; @@ -216,26 +218,18 @@ export function attachTerminalMouseRouter({ consumePointerEvent(ev, true); return; } - beginPendingDrag(ev, { pointerId: null, touchLike: false }); + beginPendingDrag(ev, { touchLike: false }); }; const onPointerDown = (ev: PointerEvent) => { if (ev.pointerType === 'mouse') { - // Capture the mouse pointer for left-button presses on terminal-owned - // content so a selection drag released *outside* our iframe still reports - // back. Chromium delivers the captured pointerup across the frame boundary - // even when the button comes up over the host page, letting onWindowPointerUp - // finalize the drag immediately rather than waiting for the cursor to wander - // back in (the window-mousemove heal). Engines that don't honor cross-frame - // capture get no such pointerup and fall back to that heal. if (ev.button !== 0) return; - const { terminalOwns } = terminalOwnsEvent(ev); - if (!terminalOwns) return; + if (!terminalOwnsEvent(ev).terminalOwns) return; try { - element.setPointerCapture(ev.pointerId); + mouseCaptureElement.setPointerCapture(ev.pointerId); mouseDragPointerId = ev.pointerId; } catch { - // Best-effort continuity aid; the heal still covers us if capture is rejected. + // The window-mousemove heal still covers an outside release. mouseDragPointerId = null; } return; @@ -249,7 +243,7 @@ export function attachTerminalMouseRouter({ const doubleTap = lastTouchTap !== null && Date.now() - lastTouchTap.time <= DOUBLE_TAP_MS && dx * dx + dy * dy <= DOUBLE_TAP_DIST_PX_SQ; - const handled = beginPendingDrag(ev, { pointerId: ev.pointerId, touchLike: true, block: doubleTap }); + const handled = beginPendingDrag(ev, { touchLike: true, block: doubleTap }); if (!handled) return; activePointerId = ev.pointerId; suppressSyntheticMouseUntil = Date.now() + 800; @@ -306,7 +300,7 @@ export function attachTerminalMouseRouter({ mouseDragPointerId = null; // Capture auto-releases on pointerup, but be explicit. try { - element.releasePointerCapture(ev.pointerId); + mouseCaptureElement.releasePointerCapture(ev.pointerId); } catch { // already released } diff --git a/lib/src/lib/terminal-registry.alert.test.ts b/lib/src/lib/terminal-registry.alert.test.ts index 956fd3ab8..799e8bd0d 100644 --- a/lib/src/lib/terminal-registry.alert.test.ts +++ b/lib/src/lib/terminal-registry.alert.test.ts @@ -182,6 +182,10 @@ class MockElement { addEventListener(): void {} removeEventListener(): void {} + querySelector(): MockElement | null { + return null; + } + querySelectorAll(): MockElement[] { return []; } diff --git a/package.json b/package.json index 904f8071d..54d4f82a6 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "dev:canopy": "pnpm --filter canopy storybook", "dev:lib": "pnpm --filter dormouse-lib dev", "dev:standalone": "node scripts/free-dev-port.mjs && pnpm --filter dormouse-standalone tauri dev", - "dev:standalone:ab": "pnpm --filter dormouse-standalone dev:agent-browser", + "innerdogfood": "pnpm --filter dormouse-standalone innerdogfood", "dev:relay": "pnpm --filter dormouse-lib build:pocket && pnpm --filter relay dev", "dev:website": "pnpm --filter dormouse-website dev", "build:vscode": "pnpm --filter dormouse-lib build && pnpm --filter dormouse build:frontend && pnpm --filter dormouse build", diff --git a/scripts/pairing-walkthrough/README.md b/scripts/pairing-walkthrough/README.md index dd14bb421..ef51826fd 100644 --- a/scripts/pairing-walkthrough/README.md +++ b/scripts/pairing-walkthrough/README.md @@ -75,7 +75,7 @@ it. | # | Step | What happens | | --- | --- | --- | | 1 | `relay` | `pnpm dev:relay` with an isolated `DORMOUSE_STATE_DIR`, then waits for `:3000` to answer. | -| 2 | `burrow` | `pnpm dev:standalone:ab` with `DORMOUSE_REMOTE_CONNECT_SRC` pointed at that Relay, then waits for the app's first terminal. → `01-burrow-booted.png` | +| 2 | `burrow` | `pnpm innerdogfood` with `DORMOUSE_REMOTE_CONNECT_SRC` pointed at that Relay, then waits for the app's first terminal. → `01-burrow-booted.png` | | 3 | `settings` | Clicks the baseboard's Settings button and scrolls to Remote control. → `02-settings-open.png` | | 4 | `enroll` | Types the Relay URL, the setup password and the machine name into the real form, submits, and waits for **Connected**. → `03-enroll-form.png`, `04-enrolled.png` | | 5 | `qr` | Clicks **Set up a phone**, waits for the code, screenshots, crops to the QR, makes a camera-shaped Y4M, and decodes the crop to prove it is legible. → `qr-full.png`, `qr.png`, `qr.y4m`, `invitation-url.txt` | @@ -125,7 +125,7 @@ at the end. **Nothing is written into the repo.** ``` relay.log the Relay's whole stdout/stderr -burrow.log the dev:standalone:ab harness, sidecar and Vite +burrow.log the innerdogfood harness, sidecar and Vite 01-burrow-booted.png … one screenshot per UI step qr-full.png the Burrow webview at the moment the QR was measured qr.png the QR alone, cropped with a little padding diff --git a/scripts/pairing-walkthrough/ab.mjs b/scripts/pairing-walkthrough/ab.mjs index 9b9cdc0b0..4d6bba54f 100644 --- a/scripts/pairing-walkthrough/ab.mjs +++ b/scripts/pairing-walkthrough/ab.mjs @@ -3,7 +3,7 @@ * (`scripts/pairing-walkthrough/README.md`). * * One instance is one `--session`, which is one isolated browser. The Burrow runs - * in the session the `dev:standalone:ab` harness opened; the Pocket browser is a + * in the session the `innerdogfood` harness opened; the Pocket browser is a * second instance with its own session name, which is why this is a class rather * than a module of free functions. */ diff --git a/scripts/pairing-walkthrough/proc.mjs b/scripts/pairing-walkthrough/proc.mjs index 62e90d539..08c18a6cd 100644 --- a/scripts/pairing-walkthrough/proc.mjs +++ b/scripts/pairing-walkthrough/proc.mjs @@ -27,7 +27,7 @@ const started = []; * `logPath`, and hand back a handle whose `lines` the caller can poll. * * **Its own process group, always.** `pnpm dev:relay` and - * `pnpm dev:standalone:ab` each fan out into a tree (pnpm → node → vite → + * `pnpm innerdogfood` each fan out into a tree (pnpm → node → vite → * esbuild), and killing only the pnpm shim orphans everything under it. * `detached: true` plus a `process.kill(-pid)` at teardown takes the group. */ diff --git a/scripts/pairing-walkthrough/steps.mjs b/scripts/pairing-walkthrough/steps.mjs index f45ee719f..7cccf64ac 100644 --- a/scripts/pairing-walkthrough/steps.mjs +++ b/scripts/pairing-walkthrough/steps.mjs @@ -38,7 +38,7 @@ const SCAN_LABEL = 'Scan a setup code'; /** * The harness line that says where the Burrow keeps its enrollment + ACL. The - * `[dev:standalone:ab]` prefix `log()` adds is deliberately not matched: it is + * `[innerdogfood]` prefix `log()` adds is deliberately not matched: it is * there for a human reading interleaved output, so pinning it would make a * cosmetic log change break this run. * @@ -273,7 +273,7 @@ async function stepRelay(ctx) { } /** - * Boot the real Burrow in the `dev:standalone:ab` harness and wait for the app. + * Boot the real Burrow in the `innerdogfood` harness and wait for the app. * * `DORMOUSE_REMOTE_CONNECT_SRC` has to be set *here*, at launch, not later: the * harness re-runs `pnpm stage` on the way up, which is what bakes the allowed @@ -282,7 +282,7 @@ async function stepRelay(ctx) { */ async function stepBurrow(ctx) { const { repoRoot, opts } = ctx; - const handle = spawnLogged('pnpm', ['dev:standalone:ab'], { + const handle = spawnLogged('pnpm', ['innerdogfood'], { cwd: repoRoot, logPath: ctx.path('burrow.log'), prefix: 'burrow', diff --git a/standalone/package.json b/standalone/package.json index 350c2e851..53baf6395 100644 --- a/standalone/package.json +++ b/standalone/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "dev": "vite", - "dev:agent-browser": "pnpm run stage && node scripts/dev-agent-browser.mjs", + "innerdogfood": "pnpm run stage && node scripts/dev-agent-browser.mjs", "prebuild": "pnpm --filter remote-lib-common build", "build": "pnpm run stage && tsc -b && vite build", "stage": "pnpm run stage:dor-cli && pnpm run stage:sidecar-proxy", diff --git a/standalone/scripts/dev-agent-browser.mjs b/standalone/scripts/dev-agent-browser.mjs index 8f1af50e0..d7d0a89bf 100644 --- a/standalone/scripts/dev-agent-browser.mjs +++ b/standalone/scripts/dev-agent-browser.mjs @@ -57,7 +57,7 @@ let shuttingDown = false; let requestSeq = 0; function log(message) { - console.error(`[dev:standalone:ab] ${message}`); + console.error(`[innerdogfood] ${message}`); } function sendSse(res, event, data) {