npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@argszero/cordis-plugin-steer-preempt

v0.2.0

Published

Steer and new-output preemption for dsh: a new user input (next-step steer) or new output from the awaited job interrupts a blocking job_output(wait: true) read immediately instead of letting it block up to the job-wait cap, so the agent loop reaches its

Readme

@argszero/cordis-plugin-steer-preempt

Steer preemption for the DeepSeek Harness (dsh). When the agent blocks in a job_output(wait: true) read, two things a model routinely waits for are not wake sources for the wait:

  1. a new user message — a steer queued as a next-step input — is normally ignored until the wait expires;
  2. new job output — a streaming task that prints a progress line still leaves the call blocked for the whole timeout.

The tool-jobs wait cap is 600,000 ms, so a user's "stop, switch tasks" can sit unread for ten minutes, and a "check the progress" read returns nothing until the job ends. This plugin makes both interrupt the blocking wait early so the loop reaches its step boundary and honors the steer — and so the model sees the output it was waiting for. The background job is never killed.

The gaps it closes

Agent.steer() (and the session-controller's insert path) sends the input to inbox.nextStep. The running step never looks at that queue — it is consumed only at the next step boundary (preStep claim in the agent loop). A blocking job read holds the step for up to the wait cap, so the steer is not processed until the wait expires on its own. See deepseek-ai/deepseek-harness discussion #6030.

The wait's only wake sources are job settlement and its own timeout, while the call is most often used for exactly the opposite: a mid-run progress check. Discussion #6285 reports output appearing 0.44 s into a wait whose call still blocks the full timeout.

| Mechanism | Behavior | |---|---| | Agent.steer() / next-step input | only queues into inbox.nextStep; consumed at the next step boundary | | job_output(wait: true) | blocks up to the configured cap (max 600,000 ms), honoring exec.signal | | Agent.cancel() | aborts the phase the same tick (too heavy: kills the turn) | | this plugin | aborts only the tool-visible derived signal when a steer is pending or the job has produced output → wait returns early → step boundary arrives → steer is honored / output is read |

How it works

This plugin is a community-side fix that needs no harness patch. It registers a tools/execute around-wrapper (the same seam the in-tree guard/timeout-policy uses):

  1. Only blocking reads (job_output with wait: true) are wrapped; every other tool call delegates with zero overhead.
  2. For a blocking read, the wrapper swaps in a derived AbortSignal for the duration of the dispatch (the registry fuses it with the caller's signal, so a real turn cancel keeps its semantics — the caller signal is never touched).
  3. A monitor polls the agent's public inbox.nextStep queue (read-only — it never consumes; the loop's preStep claim owns consumption). When a user steer has been pending for a short settle grace (default 1 s), it aborts the derived signal. The trigger is a per-message message.source.kind === 'user' test, not a bare queue-length check: the nextStep queue also carries plugin-injected notifications (agent.inject, e.g. a job-B completion notice while the model waits on job A, with source.kind === 'plugin'). A plugin notice is not user input, so it must not fire a user-steer preemption.
  4. The same monitor watches the job's output: the target job's read returning text — held for the same settle grace — arms the output trigger.
  5. The abort makes jobs.wait reject ('wait aborted'); the wait logic uncounts the waiter but leaves the job running. The registry converts the tool throw into an error result.
  6. The wrapper then replaces that error result with a structured STEER_PREEMPTED / JOB_OUTPUT_RESUMED result telling the model the job is still running — so it finishes its turn and the steer is consumed at the step boundary, or it acts on the output it just received.

A job that settles inside the grace window still returns its real result through the normal path — only an actual abort is ever replaced.

The output trigger's one real constraint

The job registry's output cursor is one-way: read returns whatever was produced since the previous read and moves the cursor, and onJobsChanged fires only on visible-set changes (registration, stopping, settlement, removal) — never on an append. There is no non-consuming way to ask whether new output exists.

So the output trigger is the consuming read, which has one consequence worth knowing: the delta it observes is handed to the model as part of the resumed result. That is not a workaround, it is the property that makes the trigger worth having — the moment the plugin notices output, that output is on its way to the model, and nothing is discarded. A wait that observes nothing never reads.

Because the observation is what fires the trigger, an early return on the very first line of output is normal and cannot be tuned down: there is no "how interesting is this output" knob, since discarding a delta to keep waiting would throw away the only copy of it.

Verified against packages/core/tools/src/index.ts, packages/jobs/jobs-local/src/index.ts, packages/jobs/tool-jobs/src/index.ts and packages/core/agent/src/runtime-types.ts on dsh 0.1.5-rc.2 / c291e7961a. (v0.1 was verified on 0.1.5-alpha.1; the platform surface is unchanged.)

Install

Mount the plugin in your profile's bundle (see the dsh bundle/preset docs), or drop it into your plugins directory:

npm install @argszero/cordis-plugin-steer-preempt
# cordis.patch.yml overlay
- insert:
    - id: steer-preempt
      name: '@argszero/cordis-plugin-steer-preempt'

Or use the shipped patch by adding the package to your bundle dependencies.

Configuration

No config is required. Optional tuning (via a set layer):

| Field | Default | Meaning | |---|---|---| | pollMs | 200 | Poll interval for the pending next-step queue and the observed output while a blocking read is active | | graceMs | 1000 | Settle grace: after a trigger is observed, how long to let a nearly-done job finish before aborting | | deliver | 'wrap' | How the output trigger delivers its early return: 'wrap' (structured JOB_OUTPUT_RESUMED carrying the observed delta), 'cut' (leave the tool's own early-return result), 'off' (disable the output trigger) | | resumeTimeoutMs | 5000 | Bounded wait re-armed in 'wrap' mode after an output resume, so the model resumes with a bounded wait instead of the full original budget. Never exceeds the caller's own timeout_ms |

The steer trigger is unaffected by deliver: a user message always wins and always produces STEER_PREEMPTED.

Changelog

  • v0.2.0: output trigger (#6285). A blocking job_output(wait: true) now returns early when the job produces output, not only when it settles or times out. The observed delta is handed to the model in a structured JOB_OUTPUT_RESUMED result (delivery mode is explicit — see deliver), the job keeps running, and a settle inside the grace window still wins. There is deliberately no "how interesting is this output" knob: a sensitivity threshold could only be honored by consuming a delta and discarding it, which is strictly worse than handing the model the text it was waiting for.
  • v0.1.2: peer range fix. >=0.1.2 matched no published version — every dsh release is a prerelease and a semver comparator only admits prereleases sharing its own major.minor.patch tuple, so installation failed outright with ETARGET. The range is now >=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 (one comparator per supported tuple line — the single-comparator form still rejects the 0.1.5 line with ERESOLVE). A test/peer-range.test.js regression prevents both forms from returning.
  • v0.1.1: preemption trigger narrowed from nextStep.length > 0 to a per-message message.source.kind === 'user' test. Fixes a false preemption when a plugin-injected notification (agent.inject, e.g. a job-B completion notice with source.kind === 'plugin') sits in the same nextStep queue as a real user steer. Regression tests added. (Community feedback, discussion #6030.)
  • v0.1.0: initial release — preempt a blocking job_output(wait: true) read when a user steer is queued.

What it does not do

  • It does not kill or cancel the background job — the job keeps running and a later job_output read returns its state.
  • It does not consume the steer — the loop's step boundary still owns that.
  • It does not replace a successful wait result — only an abort-shaped error that this plugin's own trigger caused.
  • It does not re-arm the tool's own wait after a resume — a 'wrap' resume reports the wait's remaining budget so the model can decide whether to keep waiting, and the job's completion notice still arrives on its own.
  • It targets job_output(wait: true) only (the reported gaps). Other tools that honor exec.signal can be added later.

Relationship to in-tree guards

| Guard | Hook | Catches | |---|---|---| | guard/timeout-policy | tools/execute | a tool call exceeding a declared timeoutMs | | guard/repeat-tool-reminder | tools/post-execute | the model repeating the same tool-call chain | | this plugin | tools/execute | a blocking wait that a user steer (or new job output) should preempt |

License

MIT