@jayyuen66/dsh-ocr-review 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -21,7 +21,6 @@ const UI_MESSAGES = {
21
21
  save: "保存",
22
22
  saving: "保存中…",
23
23
  revert: "撤销",
24
- saveFailed: "保存失败:",
25
24
  saveRefused: "未写入(宿主拒收或跳过):{fields}",
26
25
  loadProvidersFailed: "加载 providers 失败:{error}(远程面未就绪?重启 dsh 后重试)",
27
26
  remoteUnavailable: "远程服务不可用:宿主没有挂上本插件的设置通道。",
@@ -74,6 +73,7 @@ const UI_MESSAGES = {
74
73
  mcpSaving: "保存中…",
75
74
  mcpSaved: "MCP servers 已保存",
76
75
  mcpLoadFailed: "读取 MCP 配置失败:",
76
+ mcpNotLoaded: "还没读到当前 MCP 配置,先等读取完成(失败时先修好读取)再保存",
77
77
  chatGptTitle: "ChatGPT(OCR auth)",
78
78
  chatGptHint: "登录态由 ocr auth 管理;本卡片只读查询状态,并在登录后把 provider 切到 chatgpt。",
79
79
  chatGptCheck: "检查登录态",
@@ -91,7 +91,7 @@ const UI_MESSAGES = {
91
91
  binaryUnknown: "状态未取到:刷新本页,或确认 dsh 的 web 服务已起来",
92
92
  binaryBundled: "随本插件分发的兜底入口已就位:{path}({channel})",
93
93
  binaryPath: "用 PATH 上的全局安装:{path}({channel})",
94
- binaryNone: "未找到 ocr:PATH 与随包兜底两条路都没有可执行候选。装法与版本看下一行(由插件按核对版本给出)",
94
+ binaryNone: "未找到 ocr:PATH 与随包兜底两条路都没有可执行候选",
95
95
  channelBrew: "brew formula",
96
96
  channelNpm: "npm 全局包",
97
97
  channelUnknown: "装法未识别",
@@ -122,7 +122,6 @@ const UI_MESSAGES = {
122
122
  save: "Save",
123
123
  saving: "Saving…",
124
124
  revert: "Revert",
125
- saveFailed: "save failed: ",
126
125
  saveRefused: "not written (host refused or skipped): {fields}",
127
126
  loadProvidersFailed: "failed to load providers: {error} (remote face not ready? restart dsh and retry)",
128
127
  remoteUnavailable: "remote service unavailable: the host did not mount this plugin's settings channel",
@@ -175,6 +174,7 @@ const UI_MESSAGES = {
175
174
  mcpSaving: "Saving…",
176
175
  mcpSaved: "MCP servers saved",
177
176
  mcpLoadFailed: "Failed to read the MCP config: ",
177
+ mcpNotLoaded: "The current MCP config has not loaded yet; wait for the read (or fix it) before saving",
178
178
  chatGptTitle: "ChatGPT (OCR auth)",
179
179
  chatGptHint: "Sign-in is managed by ocr auth; this card only reports the status and switches OCR's provider to chatgpt after you sign in.",
180
180
  chatGptCheck: "Check sign-in",
@@ -192,7 +192,7 @@ const UI_MESSAGES = {
192
192
  binaryUnknown: "status not reported: reload this page, or check that the dsh web server is up",
193
193
  binaryBundled: "bundled fallback entry in place: {path} ({channel})",
194
194
  binaryPath: "using the global install on PATH: {path} ({channel})",
195
- binaryNone: "ocr not found: neither PATH nor the bundled fallback has an executable candidate. The install command and version are on the next line (this plugin states the version its contract was verified against)",
195
+ binaryNone: "ocr not found: neither PATH nor the bundled fallback has an executable candidate",
196
196
  channelBrew: "brew formula",
197
197
  channelNpm: "npm global package",
198
198
  channelUnknown: "install method unrecognised",
@@ -201,7 +201,7 @@ const UI_MESSAGES = {
201
201
  versionDiverged: "this machine reports {actual}, which is not the verified {expected}: comment wording and severity may drift between versions",
202
202
  badgeDiverged: "ocr version differs",
203
203
  detectCli: "Detect version",
204
- detecting: "Detecting...",
204
+ detecting: "Detecting…",
205
205
  probeVersion: "ocr version: {version}",
206
206
  probeReason: "no version read: {reason}",
207
207
  badgeBundled: "ocr from bundled fallback",
@@ -827,7 +827,10 @@ const SELECT_SCHEMA = zod.z.object({
827
827
  model: zod.z.string({ error: SELECT_MESSAGE })
828
828
  }, { error: "select request must be an object" });
829
829
  const MCP_SAVE_MESSAGE = "mcp save request requires a string json";
830
- const MCP_SAVE_SCHEMA = zod.z.object({ json: zod.z.string({ error: MCP_SAVE_MESSAGE }) }, { error: MCP_SAVE_MESSAGE });
830
+ const MCP_SAVE_SCHEMA = zod.z.object({
831
+ json: zod.z.string({ error: MCP_SAVE_MESSAGE }),
832
+ revision: zod.z.string().optional()
833
+ }, { error: MCP_SAVE_MESSAGE });
831
834
  const CHAT_GPT_MESSAGE = "chatgpt request requires a string model";
832
835
  const CHAT_GPT_SCHEMA = zod.z.object({ model: zod.z.string({ error: CHAT_GPT_MESSAGE }) }, { error: CHAT_GPT_MESSAGE });
833
836
  /** 把宿主那九个方法挂到客户端远程面上的描述符(`id` 的形状照生成器:`<包>#<命名空间>/<方法>`)。
@@ -953,9 +956,10 @@ const OCR_DESCRIPTORS = [
953
956
  const isRemoteMounter = (value) => e$3(value) && typeof value["$mount"] === "function";
954
957
  /** 同上:命名空间服务(挂载之后才有,缺它说明网关没认这枚贡献)。 */
955
958
  const isOcrNamespace = (value) => e$3(value) && typeof value["providers"] === "function" && typeof value["select"] === "function" && typeof value["migrate"] === "function" && typeof value["test"] === "function" && typeof value["probe"] === "function" && typeof value["mcp"] === "function" && typeof value["saveMcp"] === "function" && typeof value["auth"] === "function" && typeof value["chatGpt"] === "function";
956
- /** 网关回执 → 值;载体/网关失败抛(卡片那层按「请求失败」显示)。 */
959
+ /** 网关回执 → 值;载体/网关失败抛(卡片那层按「请求失败」显示)。
960
+ * 机器码放 `cause`:卡片只展示 message,`REMOTE_...` 这类内部标识不是给用户看的。 */
957
961
  function unwrap(result) {
958
- if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
962
+ if (!result.ok) throw new Error(result.error.message, { cause: { code: result.error.code } });
959
963
  return result.value;
960
964
  }
961
965
  /**
@@ -971,7 +975,7 @@ function buildOcrReader(ctx) {
971
975
  let mounted;
972
976
  const remoteOf = () => {
973
977
  const remote = ctx.get?.("remote", false);
974
- if (!isRemoteMounter(remote)) throw new Error("remote 未提供:当前部署没有远程读面(每次读取都会重新解析)");
978
+ if (!isRemoteMounter(remote)) throw new Error("remote 未提供:当前部署没有远程读面");
975
979
  return remote;
976
980
  };
977
981
  const namespaceOf = async () => {
@@ -987,7 +991,10 @@ function buildOcrReader(ctx) {
987
991
  throw error;
988
992
  }
989
993
  const namespace = ctx.get?.(OCR_SERVICE_KEY, false);
990
- if (!isOcrNamespace(namespace)) throw new Error(`${OCR_SERVICE_KEY} 未提供:宿主网关没有挂上这枚命名空间`);
994
+ if (!isOcrNamespace(namespace)) {
995
+ mounted = void 0;
996
+ throw new Error(`${OCR_SERVICE_KEY} 未提供:宿主网关没有挂上这枚命名空间`);
997
+ }
991
998
  return namespace;
992
999
  };
993
1000
  return {
@@ -1012,8 +1019,11 @@ function buildOcrReader(ctx) {
1012
1019
  async mcp() {
1013
1020
  return unwrap(await (await namespaceOf()).mcp());
1014
1021
  },
1015
- async saveMcp(json) {
1016
- return unwrap(await (await namespaceOf()).saveMcp({ json }));
1022
+ async saveMcp(json, revision) {
1023
+ return unwrap(await (await namespaceOf()).saveMcp(revision === void 0 ? { json } : {
1024
+ json,
1025
+ revision
1026
+ }));
1017
1027
  },
1018
1028
  async auth() {
1019
1029
  return unwrap(await (await namespaceOf()).auth());
@@ -1253,14 +1263,13 @@ function currentConfigText(t, current) {
1253
1263
  keyPart: t(current.keyIsDynamic ? "dynamicKey" : "plainKeyWarning")
1254
1264
  });
1255
1265
  }
1256
- /** 装法枚举到文案键:查表而不是嵌套三元(本仓 lint 禁嵌套三元),查不到就是「未识别」那一档。 */
1257
- const CHANNEL_KEYS = {
1258
- brew: "channelBrew",
1259
- npm: "channelNpm"
1260
- };
1261
- /** 那份 CLI 是怎么装上的,翻成人话。判不出来源(含 host 没报这一档)不猜。 */
1266
+ /** 那份 CLI 是怎么装上的,翻成人话。判不出来源(含 host 没报这一档)不猜。
1267
+ * 逐个比对已知值而不是查一张普通对象表:`channel` 来自 wire,只校验了「是字符串」,
1268
+ * 用属性查表时 `"toString"`、`"constructor"` 一类会命中原型上的成员,把函数当文案键传下去。 */
1262
1269
  function channelText(t, channel) {
1263
- return t(CHANNEL_KEYS[channel ?? ""] ?? "channelUnknown");
1270
+ if (channel === "brew") return t("channelBrew");
1271
+ if (channel === "npm") return t("channelNpm");
1272
+ return t("channelUnknown");
1264
1273
  }
1265
1274
  /**
1266
1275
  * 外部 CLI 可得性那一行的文本。三档来源与「宿主没报」四种说法,判据全在 host 侧
@@ -1719,7 +1728,7 @@ function buildMcpAdmin(props) {
1719
1728
  const textarea = (0, react.createElement)("textarea", {
1720
1729
  "data-field": "mcpJson",
1721
1730
  value: props.mcpJson,
1722
- disabled: !props.writable || props.mcpBusy || !props.available,
1731
+ disabled: !props.writable || props.mcpBusy || !props.available || !props.mcpReady,
1723
1732
  "aria-label": t("mcpTitle"),
1724
1733
  className: "dsu-input dsu-textarea dsu-mono ocr-adminTextarea",
1725
1734
  onChange: (event) => {
@@ -1730,7 +1739,7 @@ function buildMcpAdmin(props) {
1730
1739
  type: "button",
1731
1740
  className: "dsu-btn",
1732
1741
  "data-field": "mcpSave",
1733
- disabled: !props.writable || props.mcpBusy || !props.available,
1742
+ disabled: !props.writable || props.mcpBusy || !props.available || !props.mcpReady,
1734
1743
  onClick: props.onMcpSave
1735
1744
  }, props.mcpBusy ? t("mcpSaving") : t("mcpSave"));
1736
1745
  const message = props.mcpMsg === null ? null : (0, react.createElement)("p", { className: "dsu-hint" }, props.mcpMsg);
@@ -1825,7 +1834,8 @@ function readerPost(reader, t) {
1825
1834
  }
1826
1835
  };
1827
1836
  }
1828
- /** POST select:把选中的 provider/model 写进 OCR 配置,回执里按 autoVerify 决定是否带 llm test。 */
1837
+ /** POST select:把选中的 provider/model 写进 OCR 配置,回执里按 autoVerify 决定是否带 llm test。
1838
+ * 导出与 `runProbe` 同因:卡片上的动作以纯函数形态可测(回执着色与文案都在这里定)。 */
1829
1839
  async function runApplySelection(deps) {
1830
1840
  const { t, effectiveProvider, effectiveModel } = deps;
1831
1841
  if (!effectiveProvider || !effectiveModel) {
@@ -1844,10 +1854,12 @@ async function runApplySelection(deps) {
1844
1854
  })).json();
1845
1855
  if (i$1(selJson, "ok") === true) {
1846
1856
  let testText;
1857
+ let verified = true;
1847
1858
  const llmTestRaw = i$1(selJson, "llmTest");
1848
1859
  if (e$3(llmTestRaw)) {
1849
1860
  const testOutput = strOf(i$1(llmTestRaw, "output")).slice(0, 400);
1850
- testText = i$1(llmTestRaw, "ok") === true ? t("llmTestPassed") : `${t("llmTestFailed")}${testOutput}`;
1861
+ verified = i$1(llmTestRaw, "ok") === true;
1862
+ testText = verified ? t("llmTestPassed") : `${t("llmTestFailed")}${testOutput}`;
1851
1863
  } else testText = t("notVerified");
1852
1864
  const appliedText = t("applied", {
1853
1865
  provider: effectiveProvider,
@@ -1855,7 +1867,7 @@ async function runApplySelection(deps) {
1855
1867
  test: testText
1856
1868
  });
1857
1869
  deps.setActionMsg({
1858
- ok: true,
1870
+ ok: verified,
1859
1871
  text: appliedText
1860
1872
  });
1861
1873
  deps.refresh();
@@ -1888,7 +1900,9 @@ async function runMigrateKeys(deps) {
1888
1900
  const migrated = Array.isArray(migratedVal) ? migratedVal.filter((item) => typeof item === "string") : [];
1889
1901
  const skipped = Array.isArray(skippedVal) ? skippedVal.filter((item) => typeof item === "string") : [];
1890
1902
  const ok = i$1(json, "ok") === true;
1891
- const head = ok ? (migrated.length > 0 ? t("migratedPrefix") + migrated.join(", ") : t("noPlainKey")) + (skipped.length > 0 ? t("skippedPrefix") + skipped.join(", ") : "") : t("migrateFailed");
1903
+ const migratedText = migrated.length > 0 ? t("migratedPrefix") + migrated.join(", ") : t("noPlainKey");
1904
+ const skippedText = skipped.length > 0 ? t("skippedPrefix") + skipped.join(", ") : "";
1905
+ const head = ok ? migratedText + skippedText : t("migrateFailed");
1892
1906
  deps.setActionMsg({
1893
1907
  ok,
1894
1908
  text: head
@@ -2026,16 +2040,14 @@ function buildProviderPanel(deps) {
2026
2040
  const { actionMsg } = deps;
2027
2041
  return [buildProviderSection(deps), actionMsg ? (0, react.createElement)("p", { className: statusClass(actionMsg.ok) }, actionMsg.text) : null];
2028
2042
  }
2029
- /** 管理区的状态与动作(MCP 配置 + ChatGPT 登录态/切换);OcrCard 只负责摆位置。 */
2030
- function useAdminPanel(reader, t) {
2043
+ /** MCP 配置编辑器的状态与动作:读、写,以及「没读成功不许写」的判据。 */
2044
+ function useMcpPanel(reader, t) {
2031
2045
  const [mcpJson, setMcpJson] = (0, react.useState)("{}");
2032
2046
  const [mcpBusy, setMcpBusy] = (0, react.useState)(false);
2033
2047
  const [mcpMsg, setMcpMsg] = (0, react.useState)(null);
2034
- const [authOutput, setAuthOutput] = (0, react.useState)(null);
2035
- const [authBusy, setAuthBusy] = (0, react.useState)(false);
2036
- const [authMsg, setAuthMsg] = (0, react.useState)(null);
2037
- const [authModel, setAuthModel] = (0, react.useState)("");
2038
- const [loginCommand, setLoginCommand] = (0, react.useState)("ocr auth login --provider chatgpt");
2048
+ const [mcpReady, setMcpReady] = (0, react.useState)(false);
2049
+ const mcpDirtyRef = (0, react.useRef)(false);
2050
+ const mcpRevisionRef = (0, react.useRef)(void 0);
2039
2051
  const loadMcp = (0, react.useCallback)(async () => {
2040
2052
  if (reader === void 0) {
2041
2053
  setMcpMsg(t("adminUnavailable"));
@@ -2043,10 +2055,17 @@ function useAdminPanel(reader, t) {
2043
2055
  }
2044
2056
  try {
2045
2057
  const reply = await reader.mcp();
2046
- if ("status" in reply) setMcpMsg(reply.error);
2047
- else setMcpJson(reply.json);
2058
+ if ("status" in reply) {
2059
+ setMcpMsg(reply.error);
2060
+ setMcpReady(false);
2061
+ } else {
2062
+ if (!mcpDirtyRef.current) setMcpJson(reply.json);
2063
+ mcpRevisionRef.current = reply.revision;
2064
+ setMcpReady(true);
2065
+ }
2048
2066
  } catch (error) {
2049
2067
  setMcpMsg(`${t("mcpLoadFailed")}${String(error)}`);
2068
+ setMcpReady(false);
2050
2069
  }
2051
2070
  }, [reader, t]);
2052
2071
  (0, react.useEffect)(() => {
@@ -2057,17 +2076,48 @@ function useAdminPanel(reader, t) {
2057
2076
  setMcpMsg(t("adminUnavailable"));
2058
2077
  return;
2059
2078
  }
2079
+ if (!mcpReady) {
2080
+ setMcpMsg(t("mcpNotLoaded"));
2081
+ return;
2082
+ }
2060
2083
  setMcpBusy(true);
2061
2084
  setMcpMsg(null);
2062
2085
  try {
2063
- const reply = await reader.saveMcp(mcpJson);
2064
- setMcpMsg("status" in reply ? reply.error : t("mcpSaved"));
2086
+ const reply = await reader.saveMcp(mcpJson, mcpRevisionRef.current);
2087
+ if ("status" in reply) setMcpMsg(reply.error);
2088
+ else {
2089
+ mcpDirtyRef.current = false;
2090
+ mcpRevisionRef.current = reply.revision;
2091
+ setMcpMsg(t("mcpSaved"));
2092
+ }
2065
2093
  } catch (error) {
2066
2094
  setMcpMsg(String(error));
2067
2095
  } finally {
2068
2096
  setMcpBusy(false);
2069
2097
  }
2070
2098
  }
2099
+ return {
2100
+ mcpJson,
2101
+ mcpReady,
2102
+ mcpBusy,
2103
+ mcpMsg,
2104
+ onMcpChange: (value) => {
2105
+ mcpDirtyRef.current = true;
2106
+ setMcpJson(value);
2107
+ },
2108
+ onMcpSave: () => {
2109
+ saveMcp();
2110
+ }
2111
+ };
2112
+ }
2113
+ /** 管理区的状态与动作(MCP 配置 + ChatGPT 登录态/切换);OcrCard 只负责摆位置。 */
2114
+ function useAdminPanel(reader, t) {
2115
+ const mcp = useMcpPanel(reader, t);
2116
+ const [authOutput, setAuthOutput] = (0, react.useState)(null);
2117
+ const [authBusy, setAuthBusy] = (0, react.useState)(false);
2118
+ const [authMsg, setAuthMsg] = (0, react.useState)(null);
2119
+ const [authModel, setAuthModel] = (0, react.useState)("");
2120
+ const [loginCommand, setLoginCommand] = (0, react.useState)("ocr auth login --provider chatgpt");
2071
2121
  async function checkAuth() {
2072
2122
  if (reader === void 0) {
2073
2123
  setAuthMsg(t("adminUnavailable"));
@@ -2107,18 +2157,12 @@ function useAdminPanel(reader, t) {
2107
2157
  }
2108
2158
  return {
2109
2159
  available: reader !== void 0,
2110
- mcpJson,
2111
- mcpBusy,
2112
- mcpMsg,
2160
+ ...mcp,
2113
2161
  authOutput,
2114
2162
  authBusy,
2115
2163
  authMsg,
2116
2164
  authModel,
2117
2165
  loginCommand,
2118
- onMcpChange: setMcpJson,
2119
- onMcpSave: () => {
2120
- saveMcp();
2121
- },
2122
2166
  onAuthModelChange: setAuthModel,
2123
2167
  onCheckAuth: () => {
2124
2168
  checkAuth();
@@ -2140,10 +2184,19 @@ function OcrCard(props) {
2140
2184
  const [busy, setBusy] = (0, react.useState)(null);
2141
2185
  const [actionMsg, setActionMsg] = (0, react.useState)(null);
2142
2186
  const [probeMsg, setProbeMsg] = (0, react.useState)(null);
2143
- const refresh = (0, react.useCallback)(async () => refreshProviders(props.reader, t, {
2144
- setData,
2145
- setFetchError
2146
- }), [props.reader, t]);
2187
+ const refreshSeq = (0, react.useRef)(0);
2188
+ const refresh = (0, react.useCallback)(async () => {
2189
+ refreshSeq.current += 1;
2190
+ const seq = refreshSeq.current;
2191
+ return refreshProviders(props.reader, t, {
2192
+ setData: (payload) => {
2193
+ if (seq === refreshSeq.current) setData(payload);
2194
+ },
2195
+ setFetchError: (message) => {
2196
+ if (seq === refreshSeq.current) setFetchError(message);
2197
+ }
2198
+ });
2199
+ }, [props.reader, t]);
2147
2200
  (0, react.useEffect)(() => {
2148
2201
  refresh();
2149
2202
  }, [refresh]);
@@ -2323,6 +2376,7 @@ exports.inject = inject;
2323
2376
  exports.isOwnSubject = isOwnSubject;
2324
2377
  exports.loadCliStatus = loadCliStatus;
2325
2378
  exports.providerOptionLabel = providerOptionLabel;
2379
+ exports.runApplySelection = runApplySelection;
2326
2380
  exports.runProbe = runProbe;
2327
2381
  exports.saveSettingsDraft = saveSettingsDraft;
2328
2382
  return module.exports;
package/docs/DETAIL.md CHANGED
@@ -4,10 +4,10 @@ Runs the external `ocr` command line over a change set, reports the comments it
4
4
 
5
5
  ## What the card changes
6
6
 
7
- - **Model** — `ocrProvider` and `ocrModel` are not stored settings: they project the host's model namespace, and entries without a usable key are shown but disabled. `chatGptModel` reflects the external CLI's own login state, which this plugin reads but does not own.
8
- - **Review parameters** — `effort` (`auto`/`low`/`medium`/`high`) is how many review passes run: deeper review costs more time. `language` chooses Chinese or English output. `autoVerify` (on) runs `ocr llm test` after you pick a provider and model, so a wrong choice fails at pick time rather than at first review. `maxComments` (default 12; 0 means all, including comments that cannot be anchored to a line). `timeoutMinutes` (0 requests the host's ceiling; a positive value is minutes, still clamped by the host). `ocrConfigPath` — empty means `~/.opencodereview/config.json`; a non-empty value must be `<dir>/.opencodereview/config.json` and is honoured by redirecting `HOME` (POSIX only).
7
+ - **Model** — `ocrProvider` and `ocrModel` are not stored settings: they project the host's model namespace, and entries without a usable key are shown but disabled. `chatGptModel` is a slug this plugin writes into the CLI's own config (provider `chatgpt`, with `api_key` / `api_key_cmd` removed); the credentials behind that provider come from the CLI's own login state, which the plugin only queries (`ocr auth`) and never stores.
8
+ - **Review parameters** — `effort` (`auto`/`low`/`medium`/`high`) is how many review passes run: deeper review costs more time. `language` chooses Chinese or English output. `autoVerify` (on) runs `ocr llm test` after you pick a provider and model, so a wrong choice fails at pick time rather than at first review. `maxComments` (default 12; 0 means all, including comments that cannot be anchored to a line). `timeoutMinutes` (0 requests the host's ceiling; a positive value is minutes, still clamped by the host). `ocrConfigPath` — empty means `~/.opencodereview/config.json`; a non-empty value must be `<dir>/.opencodereview/config.json` and is honoured by redirecting `HOME`; on Windows the value passes the shape check but the redirection does not happen, and the CLI keeps its default path.
9
9
 
10
- Six of these are read per call, so a change applies on the next review without restarting the host.
10
+ `effort`, `maxComments`, `timeoutMinutes` and `ocrConfigPath` are read per call, so a change applies on the next review without restarting the host. `language` and `autoVerify` are read when a provider/model selection is applied: `language` is written into the CLI config at that moment, and `autoVerify` only decides whether `ocr llm test` runs then.
11
11
 
12
12
  ## Two separate things called MCP
13
13
 
@@ -15,11 +15,11 @@ The card's MCP-servers editor writes the **external `ocr` tool's own** configura
15
15
 
16
16
  ## Restart-only values
17
17
 
18
- `llmTestTimeoutMs` (90000), `startupVerify` (on: the endpoint self-check runs at load), `ocrBackgroundMaxMs` (0 = no backstop window), `stdoutMaxBytes` (400000), `ocrNoAutoUpdate` (false: upstream auto-upgrade stays on unless you say otherwise) and `ocrInstallHint` (`both`/`brew`/`npm`). Set them in the `config:` block of this plugin's row in `cordis.patch.yml`.
18
+ `llmTestTimeoutMs` (90000), `startupVerify` (on: the load-time endpoint self-check and the one CLI version measurement both run at apply — the switch gates any process spawn at load), `ocrBackgroundMaxMs` (0 = no backstop window), `stdoutMaxBytes` (400000), `ocrNoAutoUpdate` (false: upstream auto-upgrade stays on unless you say otherwise) and `ocrInstallHint` (`both`/`brew`/`npm`). Set them in the `config:` block of this plugin's row in `cordis.patch.yml`.
19
19
 
20
20
  ## What it needs from outside
21
21
 
22
- The `ocr` command line, expected at version **1.13.3**; the expectation is declared in code and mirrored in the package's manifest and READMEs, so a mismatch is reported rather than papered over. Install paths are `brew install open-code-review` or `npm i -g @alibaba-group/open-code-review@1.13.3`. A missing binary is reported as "command not found"; the case where a launcher exists but the binary does not says exactly that. The version probe always forces `OCR_NO_UPDATE=1` so probing cannot trigger an upgrade.
22
+ The `ocr` command line, expected at version **1.13.4**; the expectation is declared once as `ocrCliVersion` in `package.json`, and the code constant plus both READMEs mirror that string, so a mismatch is reported (as a hint, never a hard failure) rather than papered over. Install paths are `brew install open-code-review` or `npm i -g @alibaba-group/open-code-review@1.13.4`. With no candidate on PATH at all, the pre-check reports "no ocr candidate on either path" plus an install hint; "command not found" (exit 127) is the different case where a candidate exists but the binary cannot actually be run. The version probe always forces `OCR_NO_UPDATE=1` so probing cannot trigger an upgrade.
23
23
 
24
24
  API credentials are not stored here: the plugin writes a `api_key_cmd` that calls a helper script, so the key is fetched at run time instead of landing in a config file. If that script is missing, the card warns rather than sending an unauthenticated request.
25
25
 
package/docs/DETAIL.zh.md CHANGED
@@ -4,10 +4,10 @@
4
4
 
5
5
  ## 设置卡上改的是什么
6
6
 
7
- - **模型** — `ocrProvider` 与 `ocrModel` 不是本插件存的值:它们是宿主模型命名空间的投影,没有可用密钥的条目照样列出但禁用。`chatGptModel` 反映的是外部命令行自己的登录态,本插件读它但不拥有它。
8
- - **审查参数** — `effort`(`auto`/`low`/`medium`/`high`)是跑几轮审查,越深越费时。`language` 选中文或英文输出。`autoVerify`(默认开)在选完提供方与模型后跑一次 `ocr llm test`,选错当场就知道,而不是等第一次评审失败。`maxComments`(默认 12;0 表示全部,包括钉不到行上的意见)。`timeoutMinutes`(0 表示向宿主要上限;正数是分钟数,仍由宿主收口)。`ocrConfigPath`——留空就是 `~/.opencodereview/config.json`;填了就必须是 `<目录>/.opencodereview/config.json` 的形状,生效方式是重定向 `HOME`(仅 POSIX)。
7
+ - **模型** — `ocrProvider` 与 `ocrModel` 不是本插件存的值:它们是宿主模型命名空间的投影,没有可用密钥的条目照样列出但禁用。`chatGptModel` 是本插件写进外部命令行自己配置的 model slug(provider 置为 `chatgpt`,同时删掉 `api_key` / `api_key_cmd`);该提供方背后的凭据来自命令行自己的登录态,本插件只查询 `ocr auth`,不保存。
8
+ - **审查参数** — `effort`(`auto`/`low`/`medium`/`high`)是跑几轮审查,越深越费时。`language` 选中文或英文输出。`autoVerify`(默认开)在选完提供方与模型后跑一次 `ocr llm test`,选错当场就知道,而不是等第一次评审失败。`maxComments`(默认 12;0 表示全部,包括钉不到行上的意见)。`timeoutMinutes`(0 表示向宿主要上限;正数是分钟数,仍由宿主收口)。`ocrConfigPath`——留空就是 `~/.opencodereview/config.json`;填了就必须是 `<目录>/.opencodereview/config.json` 的形状,生效方式是重定向 `HOME`;Windows 上值能通过形状校验但不会重定向,命令行仍读默认路径。
9
9
 
10
- 其中六项每次调用现读,改完下一次评审生效,不需要重启宿主。
10
+ `effort`、`maxComments`、`timeoutMinutes` 与 `ocrConfigPath` 每次调用现读,改完下一次评审生效,不需要重启宿主。`language` 与 `autoVerify` 在应用一次 provider/model 选择时读取:`language` 当场写进命令行配置,`autoVerify` 只决定那时是否跑 `ocr llm test`。
11
11
 
12
12
  ## 两处都叫 MCP,但不是同一件事
13
13
 
@@ -15,11 +15,11 @@
15
15
 
16
16
  ## 要重启的值
17
17
 
18
- `llmTestTimeoutMs`(90000)、`startupVerify`(默认开:装载时自检端点)、`ocrBackgroundMaxMs`(0 表示不设兜底窗口)、`stdoutMaxBytes`(400000)、`ocrNoAutoUpdate`(默认 false,即保留上游的自动升级,除非你明确关掉)、`ocrInstallHint`(`both`/`brew`/`npm`)。写在本插件注册行的 `config:` 块,改值随重启生效。
18
+ `llmTestTimeoutMs`(90000)、`startupVerify`(默认开:装载时自检端点)、`ocrBackgroundMaxMs`(0 表示不设兜底窗口)、`stdoutMaxBytes`(400000)、`ocrNoAutoUpdate`(默认 false,即保留上游的自动升级,除非你明确关掉)、`ocrInstallHint`(`both`/`brew`/`npm`)。写在本插件于 `cordis.patch.yml` 注册行的 `config:` 块,改值随重启生效。
19
19
 
20
20
  ## 需要外部提供什么
21
21
 
22
- `ocr` 命令行,期望版本 **1.13.3**;这个期望写在代码里,并镜像到包清单与两份 README,所以版本不符是被报出来的,不是被掩盖的。装法是 `brew install open-code-review` 或 `npm i -g @alibaba-group/open-code-review@1.13.3`。命令行不存在时按「找不到命令」报;只有垫片没有真二进制时说的也是这一件。版本探测一律强制 `OCR_NO_UPDATE=1`,以免探测顺手触发升级。
22
+ `ocr` 命令行,期望版本 **1.13.4**;这个期望唯一声明在 `package.json` 的 `ocrCliVersion`,代码常量与两份 README 镜像同一个字符串,版本不符只作为提示报出,从不中断评审。装法是 `brew install open-code-review` 或 `npm i -g @alibaba-group/open-code-review@1.13.4`。PATH 上一个候选都没有时,预检报「no ocr candidate on either path」并附安装提示;「command not found」(exit 127)是另一种情况:候选在,但真二进制跑不起来。版本探测一律强制 `OCR_NO_UPDATE=1`,以免探测顺手触发升级。
23
23
 
24
24
  API 凭据不存这里:本插件写的是一个 `api_key_cmd`,运行时的密钥由那支辅助脚本现取,不进配置文件。脚本缺失时卡上给出告警,而不是发一次没有鉴权的请求。
25
25