Skip to content

fix(dsh-plugin): re-arm lazy tools from history after a plugin reload - #283

Merged
iuyo5678 merged 5 commits into
Tencent:mainfrom
drakeo338:fix/269-rearm-lazy-tools-after-plugin-reload
Sep 20, 2026
Merged

iuyo5678 merged 5 commits into
Tencent:mainfrom
drakeo338:fix/269-rearm-lazy-tools-after-plugin-reload

Conversation

@drakeo338

@drakeo338 drakeo338 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #269.

After browser-skill has been invoked successfully, reloading the plugin can leave browser_session unavailable. Startup discovery may miss existing sessions, and durable DSH tool results use a nested message shape that the previous history parser did not recognize.

This restores the browser tool suite from successful invocation history, including sessions discovered after startup. It supports both current nested tool-result messages and the earlier flat result shape, plus the newer snapshotEvents() API and the legacy events getter. Failed calls and mismatched call IDs do not reveal tools.

Each session's existing history is scanned once per plugin lifetime; subsequent events are processed incrementally. Pending calls are tracked separately by session and removed when they settle. History-read failures remain retryable. Failed registration retains the invocation proof and retries at the next turn, session entry, service discovery, or new successful skill invocation; ordinary streaming events keep folding without repeated registration attempts or warnings. Each session-discovery batch makes at most one registration attempt, and pending recovery skips session enumeration. The skill documents how to recover from an unknown browser tool after reload by invoking browser-skill again. Unload disposes listeners and state. The original contribution is retained, including its three regression cases; the post-reveal guard test now explicitly asserts the history getter was never called.

Validation

  • Node 22: pnpm lint passed, including formatting, style checks, plugin type checking, and all 328 plugin tests.
  • Plugin host/client and extension builds passed. Earlier local frontend validation of this PR also passed: extension type checking, 52 i18n tests, 55 VOM tests, and 1,866 extension tests with 103 configured skips.
  • Installed DSH 0.1.5-rc.2: invoked the official skill tool once, unloaded/reloaded the packaged plugin, then directly listed and started a browser session, read a local page snapshot, and stopped the session. The test uses deterministic model output with the real agent loop, message factories, session store, registry, plugin lifecycle, CLI, daemon, and Chromium extension. Exactly one skill invocation; no browser sessions left afterward.
  • The original main implementation and the initial PR implementation both failed the same DSH reload check.
  • Registration-failure regression: history, live tool result, and skill-gesture paths each process 20,000 subsequent streaming events with one failed registration attempt and one warning, then recover at the next turn. A real Cordis/SessionStore append-feed test covers the same retry boundary. Startup and service-injection regressions with 100 sessions assert one attempt per batch, retained invocation proof across repeated failures, and recovery even when the next batch is empty.

Streaming cost

Local macOS/Node 22 microbenchmark using real DSH 0.1.0-rc.6 Sessions. Three repetitions per case; values are median cumulative time for event appends plus listener processing, with no browser skill invocation:

Events Original main Initial PR implementation Updated implementation Updated history reads
5,000 16.55 ms 231.56 ms 16.26 ms 1
10,000 33.20 ms 879.13 ms 32.73 ms 1
20,000 65.35 ms 3,834.00 ms 65.78 ms 1

The updated implementation handled 100,000 events in 331.24 ms with one history read. After reveal, it performed zero history reads for 20,000 further events. This removes the repeated full-history traversal and copying; an initial scan remains proportional to the existing history length. These timings are local measurements, not guarantees for every host.

drakeo338 and others added 5 commits September 18, 2026 17:53
After a daemon restart that reloads the plugin, browser_* calls fail with
unknown tool "browser_session" until the model happens to invoke the skill
again, even though the session's durable history already proves it ran.

None of the three triggers covers that case. The live tools/result hook needs
a fresh invocation. session/created never fires for a session that already
exists. The boot scan runs once during apply(), and ctx.get("sessions") yields
nothing when the sessions service is registered after this plugin, so it covers
nothing at all.

session/event already receives the session and ignored it. Reading its history
when nothing else has revealed the suite closes the gap, and is guarded so the
scan stops once revealed — these events are frequent and re-deriving on each
one after the reveal is waste.

Keeps the reveal derived from durable history rather than adding a persisted
flag, so there is no new state to migrate or keep consistent.
Parse current durable tool results and support both session history APIs. Scan each session once, process later events incrementally, and retry recovery when services or history become available.

Cover official DSH messages, reloads, long conversations, failed reads, and explicit history access counts.
Retain successful invocation proof after failures and defer retries to turn/session boundaries or new skill invocations. Add regression coverage for sustained streaming and next-turn recovery.
@iuyo5678
iuyo5678 merged commit c6c888e into Tencent:main Sep 20, 2026
7 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.

[Bug][dsh-plugin] browser_* tools stay unregistered after a plugin reload — lazy reveal is not restored (unknown tool "browser_session")

3 participants