DSH HUB
首页插件商店插件包社区排行榜资源发布指南
插件源码
返回插件目录

JayYuen666 /

JayYuen666/dsh-ocr-review

已验证

这个插件还没有填写简介。

★ 0 Stars0 Forks0 IssuesN/A 社区评分0 已确认安装
查看 GitHub
README来源: main@7c27f566

@jayyuen66/dsh-ocr-review

English · 中文

What it does

  • Wraps the external ocr (open-code-review) CLI into 5 model tools plus 1 web settings card for dsh; the host validates tool arguments not at all, so every shape and type gate lives in this package.
  • The host half (host.ts) declares 6 .volatile() settings fields — as of 0.1.7 the namespace is implicit: the host projects the form from the profile entry id ocr-review and the plugin calls no settings.register.
  • It also registers 5 tools, one systemPrompt routing section (ocr-review-routing, order 1555) and 4 /_dsh/ocr-review/* endpoints.
  • The client half (src/client-entry.ts → client.js) is the settings card, taking provider/model from the llm-pi-ai namespace of the settings service.
  • It writes provider/model into the external ocr CLI's own ~/.opencodereview/config.json — as an api_key_cmd command, never as a plaintext key.

Prerequisites (external CLI)

  • This package imports no OCR library, it only spawns processes: ocr review / ocr scan / ocr delegate preview / ocr delegate rule / ocr session / ocr llm test.
  • Commands are built against the contract verified line-by-line on the v1.12.x source (header comments of lib/cli.ts and lib/parse.ts), shaped like ocr review --audience agent --format json --effort medium --output <temp file>.
  • Multi-value --exclude/--path are merged into one comma-separated flag, and --format only accepts json/text/sarif.
  • Command resolution order: the launcher bundled with the package first - @alibaba-group/open-code-review (an optionalDependencies entry, whose own optionalDependencies pick the platform binary) resolved to the absolute path of its bin/ocr.js - and only when that does not resolve does it fall back to a bare ocr on PATH. Both installation shapes keep working, so installing this plugin is enough to get a working tool without a separate brew / npm i -g step. Resolution is lazy and memoized once per process (the same shape dsh core uses to resolve @vscode/ripgrep); a failed resolve never throws at load time, which would take the whole plugin offline over one optional binary.
  • With neither path available the child exits 127 and the tool reports "ocr command not found", offering brew install open-code-review or npm i -g @alibaba-group/open-code-review.
  • Platforms: macOS and Linux are the targets. Process reaping (the reaper guard around ocr_review / ocr_scan) depends on a POSIX toolchain - bash traps and job control plus the process-group semantics of ps / pgrep / kill. None of that exists on Windows, so the package runs the bare command there (guarded by process.platform): no reaping, but it still works. The cost is that a hard host exit can leave an orphaned OCR process.
  • The provider list and the key status come from the official host channels ctx.settings.describe() (the value.providers of the llm-pi-ai entry) and ctx.credentials.
  • When either is missing the card shows the degradation reason while tools and settings keep working.
  • The host must be >=0.2.0-rc.2: declared in peerDependencies (the host checks it on plugin install from 0.1.7-rc; alpha.1 has no such gate yet) and in engines.dsh (same value, read by nothing).

Installation

dsh plugin --profile web add @jayyuen66/dsh-ocr-review
  • The packages are on the public npm registry, so installation needs no credentials.
  • The published files are only host.js, client.js, cordis.patch.yml and scripts (prepack rebuilds both bundles); source repository: see repository.url in package.json.
  • Runtime value dependencies are @jayyuen66/dsh-plugin-shared, @deepseek-ai/schemastery (the host's fork of schemastery — only it implements the 0.1.7 .volatile() resolution) and js-yaml (the last one used only by scripts/get-cred.mjs).

pnpm blocks the dependency script on install (ERR_PNPM_IGNORED_BUILDS)

This package keeps @alibaba-group/open-code-review in optionalDependencies, and upstream ships a postinstall (scripts/install.js). pnpm 10+ does not run dependency build scripts unless allowlisted, so the install ends like this:

Error: ERR_PNPM_IGNORED_BUILDS
  × installing dependencies
  ╰─▶ Ignored build scripts: @alibaba-group/open-code-review@1.12.11

Allowlisting is a one-time step, and only this package triggers it — no other plugin in this family depends on anything with a build script.

The straightforward fix is to allowlist it in the profile's pnpm-workspace.yaml (under <dsh data dir>/profiles/<profile>/):

allowBuilds:
  '@alibaba-group/open-code-review': true

Then re-run pnpm i. The approval persists in that profile under the exact package name and survives later failed installs.

The host also ships an approval flow of its own: a failed install surfaces "allow these scripts and retry" on the web plugin page, and an Agent can grant it for you through install_bundle's approvedBuilds once you have said so in the conversation. The host validates only the pending package names, not the conversational consent, so you must approve it explicitly.

About that postinstall: on the normal path it does nothing. The platform binary comes from upstream's optionalDependencies (@alibaba-group/ocr-<os>-<arch>, each carrying os / cpu fields so pnpm installs only the one matching your platform); install.js detects that, prints Binary provided by platform package, skipping download. and returns. Measured, ocr runs identically whether or not the script executes — the only difference is whether that code runs during install, and it only downloads when the platform package is missing, which under pnpm never happens.

If you would rather run nothing at install time, write '@alibaba-group/open-code-review': false instead: ocr still works (the launcher reads the binary straight out of the platform package directory), but the host's readPendingBuilds() only recognises entries whose value is set this to true or false, so once it is false the approval flow can no longer grant it and you maintain that entry by hand.

Enabling it in dsh

  • The in-package cordis.patch.yml declares - id: ocr-review / name: "@jayyuen66/dsh-ocr-review" and is pointed at by dsh.bundle.patch in package.json.
  • dsh plugin --profile web add/remove registers and removes it, then restart dsh.
  • The card needs a web profile (dsh.client.platform: web, immediately: true).
  • The four /_dsh/ocr-review/* endpoints hang off an inject(["webServer"]) child fiber: with no webServer (a TUI host) the child never activates, so the endpoints simply do not exist while tools and settings stay available.
    • On the real host webServer arrives about a second after this entry, which is why it has to be a dependency rather than a one-off ctx.get in apply.
  • Deployment defaults can go on the profile's config: line (cordis validates and fills defaults through the exported Config schema); precedence: runtime settings value > line config > built-in default.

Tools exposed to the model

Tool Arguments and constraints
ocr_review repo (absolute path, default = current session workspace), scope workspace/commit/branch (branch needs both from+to and is mutually exclusive with commit), effort, background, exclude (at most 50 entries), provider/model/resume (resume only with commit/branch — workspace reviews cannot be resumed), wait
Numeric group (arguments of ocr_review) concurrency/timeoutMinutes/maxTools/maxTokens/maxTokensBudget (mapped to --concurrency --timeout --max-tools --max-tokens --max-tokens-budget; non-integers or values below the floor are rejected; note that --max-tools values 1-49 are raised to 50 by OCR — its help text says min 50)
ocr_scan repo, path (at most 100 entries), exclude (50), batch none/by-language/by-directory, provider/model (native shared flags of scan_cmd, matching review), the same numeric group, wait; no git diff required
ocr_delegate_preview / ocr_delegate_rule The former: repo, scope/commit/from/to, exclude 50, background. The latter: repo, paths required, 1–200 entries, positional arguments after --, each escaped. Zero LLM cost on the OCR side, they return only the file list and the rule groups
ocr_session repo, action list/show/comments (whitelist, anything else errors), id, limit (clamped to 1–100, default 10); show/comments without an id falls back to list
Background and reaper wait: false (background start, no host deadline, reclaimed by a 2-hour backstop, no --output, poll results with ocr_session) and the host reaper guard apply only to these two minute-scale commands, review and scan - that subprocess is also an official job (see the job-surface section below); that change left the tool's JSON untouched (a later output-contract rework switched it to the direct canonical object, see below)

Settings

Field Values, defaults and use
effort low/medium/high, built-in default medium; used when ocr_review is called without an explicit effort
language 中文/English, default 中文; written as the top-level language key of the OCR config when a selection is applied
autoVerify default true; when true, a successful /select also runs ocr llm test --color never (capped at 90 seconds)
maxComments default 12, the number of comments kept in the summary; 0 = no truncation, non-integers and negatives fall back to 12
timeoutMinutes default 0 = request the host cap (the host clamps to min(request, shell.maxTimeoutMs)), >0 is a number of minutes
ocrConfigPath where the external ocr CLI config lives; left empty it derives os.homedir()/.opencodereview/config.json, ~ and ~/ prefixes are accepted, relative paths error out. A custom value must follow the <X>/.opencodereview/config.json layout: OCR has no file-level path override (it only ever looks at <HOME>/.opencodereview/config.json, config_cmd.go:92-99), so the plugin makes it effective by injecting HOME=<X> into the ocr subprocess (config and sessions are redirected together, and api_key_cmd carries the host-resolved data directory as get-cred's explicit first-tier locator); any path outside that layout is rejected outright at the setting layer rather than silently mismatching (the HOME injection is a POSIX assignment prefix; Windows has no such channel, so custom paths only take effect on macOS/Linux)

Three deployment-level tuning keys are deliberately not on the settings card — they are plain (non-volatile) fields of the exported Config, settable only on the profile's config: line and taking effect on restart:

Key Default Use
llmTestTimeoutMs 90000 Wall clock for ocr llm test (shared by autoVerify and the card's "test connection"); raise it on slow machines
ocrBackgroundMaxMs 7200000 The 2-hour backstop window for background reviews (jobs.wait to the deadline, then kill)
stdoutMaxBytes 400000 Executor stdout buffer cap, shared by foreground and background runs

Credentials and config files

Aspect Fact
Credential form in the config What lands in the config is a command, not a secret: api_key_cmd = node '<path of scripts/get-cred.mjs in this package>' '<REF>' '<dsh data dir>' (all three parts single-quote escaped; the third is the host's current resolveDshHome() result, so get-cred's fallback layers stay correct when HOME is redirected or the dsh home is set outside the env), and the REF name must match ^[A-Z0-9_]+$ or the command is refused
How writes happen Atomic replacements via the official @deepseek-ai/dsh-atomic-write: a random-suffixed .<hex>.tmp sibling created with wx, then rename, with the new inode carrying 0600 (parent directories 0700). The .bak backup is copied by this package before the rename and chmod-ed to 0600 (the official helper has no backup semantics). The whole read→decide→write span is serialized by the official withFileLock, so a short-lived config.json.lock appears in that directory during a write and is removed in finally; a lock left by a crashed holder is taken over via a pid liveness probe. Known inconsistency: contention beyond the official default 2s wait makes the write throw atomic-write: timed out waiting for the writer lock at …, and the endpoint answers 400 with that English text verbatim — it comes from the official package and bypasses lib/messages.ts, unlike every other user-visible error here. Before this swap the read-modify-write shared one event-loop tick, so this failure mode did not exist; mapping it would need an injectable wait budget plus a timeout test, so this package documents it instead of silently widening the surface. An existing config that is invalid JSON or whose top level is not an object aborts the write instead of "treating it as empty and overwriting"
POST migrate Replaces the plaintext api_key of custom provider entries with api_key_cmd and deletes the plaintext; entries whose apiKeyEnv is not found in settings are left untouched and listed in skippedUnknown; nothing is written when nothing was migrated
scripts/get-cred.mjs Spawned by the external ocr CLI itself and the only place in this package still reading host credential files. The data directory is resolved as explicit third argument > $DSH_HOME > ~/.dsh, and values are looked up in inherited process environment > refs.<REF> of <dsh data dir>/.credentials.yaml > <dsh data dir>/.env. The project .env of the reviewed repository is deliberately not read; when all three layers miss it exits non-zero and names every location it checked

Public endpoints

Route Method Input Success payload
/_dsh/ocr-review/providers GET — {providers (each with a hasKey boolean), current, getCredReady, source, csrf}, never containing a key
/_dsh/ocr-review/select POST body {provider, model}, both must hit the list returned by describe() {ok, applied, llmTest} (llmTest is null when autoVerify is off)
/_dsh/ocr-review/migrate POST — {ok, migrated, skippedUnknown}
/_dsh/ocr-review/test POST — {ok, output} (output is redacted first, then cut to 8000 characters)
Front guard of the three write endpoints — — The first statement of each handler is guardTrust (Host authority -> the sec-fetch-site allowlist -> a verbatim origin compare; failing it gives 403 with {ok:false,error:"untrusted host authority" | "cross-origin request rejected"}); 405 for non-POST carrying Allow: POST and {ok:false,error:"POST only"}; 403 for a missing or wrong x-ocr-csrf header; 413 above the 8KB body cap; 400 for an aborted stream. The token is regenerated on every plugin apply and the old one stops working

These four routes live and die with the plugin's injected child fiber. The official webServer.register hands back a disposer the caller must invoke, and it throws on a duplicate path outright (installed dsh-host-webserver/lib/index.js:177-184), while the route table belongs to the host-provided webServer instance and does not die when this plugin's fiber does. This package attaches all four disposers to the effect of the inject(["webServer"]) child fiber, so a hot reload (old fiber disposes, then a new apply runs) cannot collide on the path and kill the whole settings card, and disabling or uninstalling the plugin makes these endpoints unreachable again.

The background job surface (official ctx.jobs)

The external ocr subprocess started by wait: false is now also registered as an unowned job in the host's ctx.jobs (kind: "ocr-review", id ocr-review-N). What changed is who can see and stop it, not what the model receives:

Aspect Fact
Model observation surface Three more tools: the official job_list shows the review, job_output reads its stdout/stderr (the registry pulls from proc.observed on its own cadence - non-consuming, so it never steals bytes from the readOutput() cursor), and job_kill stops it. Results still come from the ocr_session record; the payload content is unchanged since the job-surface swap, and a later output-contract rework returns the canonical object directly (no more stringify-ing the receipt into a { text } wrapper)
Broken executor reader When the executor's reader throws, the official pump only writes one line to the host log and afterwards reads that channel as empty (measured) ⇒ this package writes one "this section is incomplete" note into the ring, exactly once, and stops knocking on the broken reader
Controller lifetime Attached only for the instant of start: the registry refuses start when no controller serves the owner, and on the web surface the host's own tool-jobs row is disabled at the host plane (each preset realm mounts its own), so an unowned job needs a controller of our own. We attach it and detach it immediately (measured: detaching restores the refusal, and read/kill/settlement never need it again) - a resident token would pry that gate open for a composition that deliberately has no job tools
Three cancellation paths An aborted tool call, a plugin unload, and the 2-hour backstop all call jobs.kill(id, undefined, reason) instead of killing the process around it, so the record moves to stopping and the reason lands in detail (measured: bypassing it left the row stuck at running forever). They may overlap - killing a stopping job still returns requested and proc.kill() is idempotent. Reasons are model-facing, so they come from the bilingual catalog
Nothing there may throw An error raised inside an abort listener is rethrown by Node via process.nextTick ⇒ uncaught ⇒ the whole host process exits (reproduced locally: Error: unknown job ... followed by EXIT=1). The official package does not cover for producers either - killJob calls job.cancel() bare (installed dsh-jobs-local:611-622, and only the teardown arm wraps each call in try/catch), and a reloaded registry throws for an unfamiliar id (same file :554-558). So two layers each catch once: cancel swallows the "process is already gone" family through killProc, stop catches the registry arm and falls back to killing our own process. One test per layer - deleting either layer turns it red
The 2-hour backstop We still give the external CLI no host timeoutMs (a long review would be cut off), but "no end at all" gained a victim with this adoption - the 10-slot unowned bucket is shared host-wide (measured: running and stopping both count, overflow refuses new starts), so one hung CLI parks a slot forever. The backstop uses the registry's own wait(id, ms) + kill, not a host timer and not a bare setTimeout
Unowned exposure Unowned means visible to every session - any caller can list/read/kill it. Known and accepted for this wave (owned jobs would need the dsh-agent registry, measured unavailable); rolling the adoption back is the only way to close it
Settled-record window The registry never recycles settled records on its own, and it outlives this plugin - so before each registration this package prunes the oldest settled ocr-review records down to 9 and only then starts, so the roster never holds more than 10 (each ring is capped at 256 KiB). At unload both jobs are done: live ones are killed through the registry, settled ones are removed - disabling this plugin means there is no "next apply" left to prune the window. Live and stopping records are never pruned: the registry answers overflow by refusing start (measured: the unowned bucket holds 10), not by killing somebody's review
No registry, no background When the host has not loaded dsh-jobs-local, wait: false throws an error naming the missing piece instead of starting a subprocess nobody can observe. Foreground runs (wait: true) are unaffected

Data and privacy

Aspect Fact
Process boundary The reviewed repository's paths and diff content go into the ocr child process, and OCR sends them to the LLM endpoint it has in its own config.json; that egress is the review feature itself and does not pass through the dsh host. Plaintext keys never appear in tool results or in /_dsh/*, and the value the host gets from resolve() is used only to confirm "it resolves right now" and is discarded on the spot
Redaction of raw output Raw OCR stdout/stderr (failure details, llm test, parse-failure echoes, session passthrough) all go through redactKeyMaterial() before leaving the host: bearer and assignment forms are redacted strictly, while the standalone-key form additionally demands random-string features so that file paths handed to the model are not erased as well
Places written This package writes to only two places: the external ocr CLI's config.json (plus its random-suffixed .tmp, the .bak backup, and the short-lived config.json.lock held only for the duration of one read-modify-write) and ocr-review-*/ocr-scan-* in the system temp directory (removed right after reading). Plaintext that predates a migration may still sit in the .bak produced by that same write (mode 0600); removing it for good means deleting that backup yourself

FAQ

Symptom Verdict
Exit 0 taken for a successful review Exit 0 is not a successful review: any status outside success/complete/partial/completed_with_warnings/completed_with_errors/skipped is reported as failure ({"error": …} is of that kind; partial is one of the manifest terminal states — some files failed but coverage remains, and the comments still stand), and a parse failure echoes the first 500 redacted UTF-16 code units of the raw output (an emoji takes 2)
Whether the summary covered everything It covers everything OCR produced only when droppedCount and invalidCommentCount are both 0, otherwise raise maxComments (or set 0) and re-check; stdout has its own 400000-byte cap, which is why review results always go through the --output temp file
Background processes when dsh exits When dsh exits in any way (including kill -9 followed by launchd adoption), the OCR process tree of review/scan is taken down by the reaper guard inside the command with TERM→1s→KILL; cancelling the tool call terminates the corresponding background process too

Development

  • npm run check is the single gate to run before publishing: typecheck (both tsconfigs) + oxlint + rebuild both bundles + npm test (vitest with coverage) + oxfmt --check.
  • npm test runs with --coverage under a 100% global threshold (lines/statements/branches/functions) — any line or branch not pinned by a test turns the suite red by design.
  • npm run build / npm run build:client produce the published host.js / client.js. After editing host.ts, lib/* or src/* they must be rebuilt (test/build-host.test.ts pins bundle freshness against the sources, so a stale bundle fails the suite). Never hand-edit the bundles.
  • The plugin source is TypeScript loaded directly by the host's cordis loader (Node ≥ 22.18 type stripping); the bundles exist for the published npm surface.

License

MIT — see LICENSE.

—/ 5

暂无评分

已验证 DSH bundle

Commit 7c27f5667d4b

社区评论

还没有评论,来写第一条。

DSH HUB

社区维护的 DSH 插件索引。不是 GitHub 或 DeepSeek AI 的官方产品。

社区资源API关于