Skip to content

fix: restore default preset on uninstall and stop misclassifying wait_agent/multi-dispatch shapes (#881, #893) - #899

Closed
Y-fyan wants to merge 2 commits into
T-Auto:mainfrom
Y-fyan:fix/881-893
Closed

Y-fyan wants to merge 2 commits into
T-Auto:mainfrom
Y-fyan:fix/881-893

Conversation

@Y-fyan

@Y-fyan Y-fyan commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Closes #881
Closes #893

What was wrong

  1. Dangling selectedDefault: ops-ptc after disabling or uninstalling dsh-ops ([BUG] 卸载后会有配置残留导致Dsh歇逼 #881):

    • In live profiles (desktop, web, dsh-tui) where @deepseek-ai/dsh-hmr is active, disabling or removing dsh-ops runs inside hmr.runExclusive (executing in AsyncLocalStorage is true). When the dsh-ops fiber disposal called settings.update('agent-preset-registry', { selectedDefault }), ConfigEditor.edit invoked hmr.runExclusive again, which immediately rejected with Error: HMR transactions cannot be nested.
    • During PluginManager.removeBundle('dsh-ops'), <profile.dir>/package.json.lock is held across two hmr.runExclusive phases while ConfigEditor.edit acquires hmr.runExclusive before package.json.lock, causing lock contention/timeout if the restore enters hmr.runExclusive before the outer lock clears.
    • Offline removal via dsh-ops uninstall --profile <desktop|web|tui> delegates to dsh plugin remove dsh-ops while the host is not running, so runtime teardown never executes and selectedDefault: ops-ptc remains in <profile.dir>/cordis.patch.yml, causing every new session to fail with Unknown agent preset: ops-ptc.
  2. False-positive single operation shape reminder on wait_agent and dependent multi-dispatch programs ([BUG] batching shape 将 wait_agent 等待及有依赖的多次派发误判为 single operation #893):

    • WAITING in src/ptc/batching-guidance.ts did not recognize tools.wait_agent(...) / tools["wait_agent"](...), and violationsOf(shape) did not exclude shape.isWaiting from the shape violation.
    • violationsOf(shape) triggered on shape.dispatchSites >= 1 whenever a short program had no loop/Promise.all/branching, misclassifying sequential multi-dispatch scripts (such as a grep whose result determines the next read_file) as "The program ran a single operation".

The change

  • src/presets/ops-ptc/register.ts:
    • Exit the active HMR AsyncLocalStorage transaction (hmr.executing.exit) and wait for both hmr.operations and <profile.dir>/package.json.lock to settle before persisting default preset changes in forceDefault and restoreDefault.
    • Track the pre-force operator default per registry instance so remounts and subsequent boots that start with ops-ptc already saved still restore the operator's prior default or the registry's official default.
  • src/profile-default-repair.ts & src/profile-install.ts:
    • Add findDanglingDefault and repairDanglingDefault to remove a leftover selectedDefault: ops-ptc on the agent-preset-registry entry of <profile.dir>/cordis.patch.yml (preserving sibling keys/entries and leaving [] when the removed item was the only entry), and invoke repairDanglingDefault after dsh-ops uninstall succeeds.
  • src/ptc/batching-guidance.ts:
    • Match wait_agent only at real tools.* / tools["..."] dispatch sites (WAITING_TOOL = /^wait_agent$/u), ignoring comments and string literals.
    • Tighten the shape violation condition in violationsOf(shape) to shape.dispatchSites === 1 && !shape.hasBatchingStructure && !shape.hasBranching && !shape.isWaiting && shape.chars < THIN_CODE_CHARS.
  • Tests & Changelog:
    • Add regression checks in test/preset-registration.test.mjs, test/profile-temp.test.mjs, and test/batching-trigger.test.mjs, plus bilingual entries under ## [Unreleased] in docs/CHANGELOG.md.

Validation

  • One pull_request-triggered CI required run, URL below
  • npm run build, npm run typecheck, npm run check:size, and npm run validate:manifest pass
  • Reverting each implementation change turns its new test assertions red (preset-registration, profile-temp, batching-trigger)
  • docs/CHANGELOG.md ## [Unreleased] entry added (English and Chinese)
  • No dependency, package.json version, or optionalDependencies change; no model-facing schema/prompt change
$ npm run build
build: bundled 11 browser modules (92116 bytes)
build: generated PTC declarations for 18 capabilities (120358 bytes)
build: compiled src/ to lib/ and copied 60 assets

$ npm run typecheck
(exit 0)

$ npm run check:size
check-file-size: 821 code file(s) checked, all at or under 1000 lines (longest: native/crates/ops-search/src/lib.rs (928))

$ npm run validate:manifest
validate-manifest: all 92 checks passed

$ node test/run.mjs preset-registration
dsh-ops preset-registration: all 28 checks passed

$ node test/run.mjs profile-temp
dsh-ops profile-temp: all 13 checks passed

$ node test/run.mjs batching-trigger
dsh-ops batching-trigger: all 16 checks passed

$ gh run view 43425213828 --repo T-Auto/dsh-ops --json url,event,conclusion --jq '"\(.url) \(.event) \(.conclusion)"'
https://github.com/T-Auto/dsh-ops/actions/runs/43425213828 pull_request

Revert-red verification:

$ git checkout -- src/presets/ops-ptc/register.ts src/profile-install.ts && npm run build
$ node test/run.mjs preset-registration
  FAIL live HMR transactions and profile file locks do not block or reject default restoration (#881)
dsh-ops preset-registration: 1 of 28 check(s) failed

$ node test/run.mjs profile-temp
  FAIL uninstall repairs a dangling selectedDefault: ops-ptc in cordis.patch.yml (#881)
dsh-ops profile-temp: 1 of 13 check(s) failed

$ git checkout -- src/ptc/batching-guidance.ts && npm run build
$ node test/run.mjs batching-trigger
  FAIL waiting on an agent is a dependency barrier, read only from a real dispatch site (#893)
  FAIL only a single dispatch site earns the single-operation shape notice (#893)
dsh-ops batching-trigger: 2 of 16 check(s) failed

Environment

Linux x64, Node v22.22.1, based on upstream main commit e724444, local source checkout.

…e operation

Recognise tools.wait_agent (dot or bracket) at a real dispatch site as a wait, and require exactly one dispatch site before the shape notice claims a single operation. Plan and bypass checks and latch behaviour are unchanged. Fixes T-Auto#893.
@T-Auto

T-Auto commented Nov 13, 2026

Copy link
Copy Markdown
Owner

Thanks — the batching half of this PR is sound, and I verified it independently: both tools.wait_agent and tools["wait_agent"] are recognised from dispatch sites, a comment or string containing the name does not exempt a program, and the new tests do fail when reverted (the old code reports shape:true for both samples in #893). dispatchSites === 1 matches the notice wording.

The profile-repair half blocks merge: it is a small YAML editor rather than one narrowly-scoped deletion, and several shapes are handled wrongly or silently. I extracted src/profile-default-repair.ts from this branch and ran findDanglingDefault on the inputs below. These are observed results, not inferences.

1. A row is deleted, and it takes the following comment with it.

- id: other
  config:
    a: 1
- id: agent-preset-registry
  config:
    selectedDefault: ops-ptc
# keep: third row is legacy
- id: third
  config:
    b: 2

Removes indexes 3,4,5,6: the registry row and # keep: third row is legacy. That contradicts the module docstring ("Any other shape is left untouched and reported, never guessed at").

2. One extra level under config: deletes the wrong key.

- id: agent-preset-registry
  config:
    nested:
      selectedDefault: ops-ptc

Removes the selectedDefault line under nested:, leaving nested: empty. The "direct child of config" test is really "greater indentation".

3. A different scalar is treated as the leftover.
selectedDefault: ops-ptc#x (YAML value ops-ptc#x) removes the whole row; \s* before (?:#.*)? accepts a # that does not introduce a comment.

4. A BOM is silently clean. \uFEFF- id: agent-preset-registry ... returns [], so repairDanglingDefault reports {dangling:false, changed:false} — indistinguishable from a healthy file. The user still hits Unknown agent preset: ops-ptc, and nothing says so.

5. Two registry rows: only the first is repaired, where the host's own editor would clean every matching row.

Also worth fixing:

  • Uninstall can exit 1 because of the repair. Only ENOENT is tolerated; EACCES/EBUSY/rename failure propagates through profileCommand after the package is already gone. Wrap it and warn.
  • The repair races the profile lock. The host serialises patch edits with <profile.dir>/package.json.lock; this read-modify-rename does not take it. At minimum skip and report when the lock exists.
  • dryRun is unreachable — no caller passes it (profileCommand returns before spawning on dry-run). Remove it or wire a real preview.
  • waitForHostIdle gives up silently after 10 s and the caller then writes anyway — the write the helper exists to prevent. Log the expiry, or skip the write instead.
  • The new comment in batching-guidance.ts overclaims. It says a tool name in a comment or string never exempts a program: true for wait_agent, not yet for the older WAITING path, where // job_output( still sets isWaiting. Either derive both waits from dispatch sites or scope the comment and pin the counterexample.

A simpler shape that would pass review: delete only the selectedDefault: line (leaving config: empty is valid for the host, or drop the then-childless config: when it sits directly above); never delete a row, never touch comments; require exactly one level of indentation under config:; strip a BOM before scanning; return three states (clean / repaired / unrecognized) and warn on the third; skip when package.json.lock exists.

Two asks: could you split this PR, so the batching half (#893) can merge on its own while the profile half (#881) gets the rework above? If you would rather keep it together, the YAML half needs those fixes first. If you would prefer to focus on #881, say so and we can take #893 as a separate PR.

@Y-fyan

Y-fyan commented Nov 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — split into #938 (#893) and #940 (#881, with all review points addressed). Closing this combined PR.

@Y-fyan Y-fyan closed this Nov 14, 2026
@Y-fyan
Y-fyan deleted the fix/881-893 branch November 14, 2026 07:58
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] batching shape 将 wait_agent 等待及有依赖的多次派发误判为 single operation [BUG] 卸载后会有配置残留导致Dsh歇逼

2 participants