@researai/deepscientist 1.5.11 → 1.5.12

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.
Files changed (102) hide show
  1. package/README.md +8 -8
  2. package/bin/ds.js +358 -61
  3. package/docs/en/00_QUICK_START.md +35 -3
  4. package/docs/en/01_SETTINGS_REFERENCE.md +11 -0
  5. package/docs/en/02_START_RESEARCH_GUIDE.md +68 -4
  6. package/docs/en/09_DOCTOR.md +28 -3
  7. package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +21 -2
  8. package/docs/en/15_CODEX_PROVIDER_SETUP.md +284 -0
  9. package/docs/en/README.md +4 -0
  10. package/docs/zh/00_QUICK_START.md +34 -2
  11. package/docs/zh/01_SETTINGS_REFERENCE.md +11 -0
  12. package/docs/zh/02_START_RESEARCH_GUIDE.md +69 -3
  13. package/docs/zh/09_DOCTOR.md +28 -1
  14. package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +21 -2
  15. package/docs/zh/15_CODEX_PROVIDER_SETUP.md +285 -0
  16. package/docs/zh/README.md +4 -1
  17. package/package.json +1 -1
  18. package/pyproject.toml +1 -1
  19. package/src/deepscientist/__init__.py +1 -1
  20. package/src/deepscientist/bash_exec/monitor.py +7 -5
  21. package/src/deepscientist/bash_exec/service.py +84 -21
  22. package/src/deepscientist/channels/local.py +3 -3
  23. package/src/deepscientist/channels/qq.py +7 -7
  24. package/src/deepscientist/channels/relay.py +7 -7
  25. package/src/deepscientist/channels/weixin_ilink.py +90 -19
  26. package/src/deepscientist/config/models.py +1 -0
  27. package/src/deepscientist/config/service.py +121 -20
  28. package/src/deepscientist/daemon/app.py +314 -6
  29. package/src/deepscientist/doctor.py +1 -5
  30. package/src/deepscientist/mcp/server.py +124 -3
  31. package/src/deepscientist/prompts/builder.py +113 -11
  32. package/src/deepscientist/quest/service.py +247 -31
  33. package/src/deepscientist/runners/codex.py +121 -22
  34. package/src/deepscientist/runners/runtime_overrides.py +6 -0
  35. package/src/deepscientist/shared.py +33 -14
  36. package/src/prompts/connectors/qq.md +2 -1
  37. package/src/prompts/connectors/weixin.md +2 -1
  38. package/src/prompts/contracts/shared_interaction.md +4 -1
  39. package/src/prompts/system.md +59 -9
  40. package/src/skills/analysis-campaign/SKILL.md +46 -6
  41. package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
  42. package/src/skills/baseline/SKILL.md +1 -1
  43. package/src/skills/decision/SKILL.md +1 -1
  44. package/src/skills/experiment/SKILL.md +1 -1
  45. package/src/skills/finalize/SKILL.md +1 -1
  46. package/src/skills/idea/SKILL.md +1 -1
  47. package/src/skills/intake-audit/SKILL.md +1 -1
  48. package/src/skills/rebuttal/SKILL.md +74 -1
  49. package/src/skills/rebuttal/references/response-letter-template.md +55 -11
  50. package/src/skills/review/SKILL.md +118 -1
  51. package/src/skills/review/references/experiment-todo-template.md +23 -0
  52. package/src/skills/review/references/review-report-template.md +16 -0
  53. package/src/skills/review/references/revision-log-template.md +4 -0
  54. package/src/skills/scout/SKILL.md +1 -1
  55. package/src/skills/write/SKILL.md +168 -7
  56. package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
  57. package/src/tui/package.json +1 -1
  58. package/src/ui/dist/assets/{AiManusChatView-D0mTXG4-.js → AiManusChatView-CnJcXynW.js} +12 -12
  59. package/src/ui/dist/assets/{AnalysisPlugin-Db0cTXxm.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
  60. package/src/ui/dist/assets/{CliPlugin-DrV8je02.js → CliPlugin-CB1YODQn.js} +9 -9
  61. package/src/ui/dist/assets/{CodeEditorPlugin-QXMSCH71.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
  62. package/src/ui/dist/assets/{CodeViewerPlugin-7hhtWj_E.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
  63. package/src/ui/dist/assets/{DocViewerPlugin-BWMSnRJe.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
  64. package/src/ui/dist/assets/{GitDiffViewerPlugin-7J9h9Vy_.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -20
  65. package/src/ui/dist/assets/{ImageViewerPlugin-CHJl_0lr.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
  66. package/src/ui/dist/assets/{LabCopilotPanel-1qSow1es.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
  67. package/src/ui/dist/assets/{LabPlugin-eQpPPCEp.js → LabPlugin-Ciz1gDaX.js} +2 -2
  68. package/src/ui/dist/assets/{LatexPlugin-BwRfi89Z.js → LatexPlugin-BhmjNQRC.js} +37 -11
  69. package/src/ui/dist/assets/{MarkdownViewerPlugin-836PVQWV.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
  70. package/src/ui/dist/assets/{MarketplacePlugin-C2y_556i.js → MarketplacePlugin-DmyHspXt.js} +3 -3
  71. package/src/ui/dist/assets/{NotebookEditor-DIX7Mlzu.js → NotebookEditor-BMXKrDRk.js} +1 -1
  72. package/src/ui/dist/assets/{NotebookEditor-BRzJbGsn.js → NotebookEditor-BTVYRGkm.js} +11 -11
  73. package/src/ui/dist/assets/{PdfLoader-DzRaTAlq.js → PdfLoader-CvcjJHXv.js} +1 -1
  74. package/src/ui/dist/assets/{PdfMarkdownPlugin-DZUfIUnp.js → PdfMarkdownPlugin-DW2ej8Vk.js} +2 -2
  75. package/src/ui/dist/assets/{PdfViewerPlugin-BwtICzue.js → PdfViewerPlugin-CmlDxbhU.js} +10 -10
  76. package/src/ui/dist/assets/{SearchPlugin-DHeIAMsx.js → SearchPlugin-DAjQZPSv.js} +1 -1
  77. package/src/ui/dist/assets/{TextViewerPlugin-C3tCmFox.js → TextViewerPlugin-C-nVAZb_.js} +5 -5
  78. package/src/ui/dist/assets/{VNCViewer-CQsKVm3t.js → VNCViewer-D7-dIYon.js} +10 -10
  79. package/src/ui/dist/assets/{bot-BEA2vWuK.js → bot-C_G4WtNI.js} +1 -1
  80. package/src/ui/dist/assets/{code-XfbSR8K2.js → code-Cd7WfiWq.js} +1 -1
  81. package/src/ui/dist/assets/{file-content-BjxNaIfy.js → file-content-B57zsL9y.js} +1 -1
  82. package/src/ui/dist/assets/{file-diff-panel-D_lLVQk0.js → file-diff-panel-DVoheLFq.js} +1 -1
  83. package/src/ui/dist/assets/{file-socket-D9x_5vlY.js → file-socket-B5kXFxZP.js} +1 -1
  84. package/src/ui/dist/assets/{image-BhWT33W1.js → image-LLOjkMHF.js} +1 -1
  85. package/src/ui/dist/assets/{index-Dqj-Mjb4.css → index-BQG-1s2o.css} +40 -2
  86. package/src/ui/dist/assets/{index--c4iXtuy.js → index-C3r2iGrp.js} +12 -12
  87. package/src/ui/dist/assets/{index-DZTZ8mWP.js → index-CLQauncb.js} +911 -120
  88. package/src/ui/dist/assets/{index-PJbSbPTy.js → index-Dxa2eYMY.js} +1 -1
  89. package/src/ui/dist/assets/{index-BDxipwrC.js → index-hOUOWbW2.js} +2 -2
  90. package/src/ui/dist/assets/{monaco-K8izTGgo.js → monaco-BGGAEii3.js} +1 -1
  91. package/src/ui/dist/assets/{pdf-effect-queue-DfBors6y.js → pdf-effect-queue-DlEr1_y5.js} +1 -1
  92. package/src/ui/dist/assets/{popover-yFK1J4fL.js → popover-CWJbJuYY.js} +1 -1
  93. package/src/ui/dist/assets/{project-sync-PENr2zcz.js → project-sync-CRJiucYO.js} +18 -4
  94. package/src/ui/dist/assets/{select-CAbJDfYv.js → select-CoHB7pvH.js} +2 -2
  95. package/src/ui/dist/assets/{sigma-DEuYJqTl.js → sigma-D5aJWR8J.js} +1 -1
  96. package/src/ui/dist/assets/{square-check-big-omoSUmcd.js → square-check-big-DUK_mnkS.js} +1 -1
  97. package/src/ui/dist/assets/{trash--F119N47.js → trash-ChU3SEE3.js} +1 -1
  98. package/src/ui/dist/assets/{useCliAccess-D31UR23I.js → useCliAccess-BrJBV3tY.js} +1 -1
  99. package/src/ui/dist/assets/{useFileDiffOverlay-BH6KcMzq.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
  100. package/src/ui/dist/assets/{wrap-text-CZ613PM5.js → wrap-text-C7Qqh-om.js} +1 -1
  101. package/src/ui/dist/assets/{zoom-out-BgDLAv3z.js → zoom-out-rtX0FKya.js} +1 -1
  102. package/src/ui/dist/index.html +2 -2
@@ -172,7 +172,11 @@ class ConfigManager:
172
172
 
173
173
  def save_named_payload(self, name: str, payload: dict) -> dict:
174
174
  prepared = self._prepare_payload_for_save(name, payload)
175
- return self.save_named_text(name, self.render_named_payload(name, prepared))
175
+ previous = self.load_named_normalized(name) if name in CONFIG_NAMES and self.path_for(name).exists() else default_payload(name, self.home)
176
+ result = self.save_named_text(name, self.render_named_payload(name, prepared))
177
+ if result.get("ok") and name == "runners":
178
+ self._invalidate_codex_bootstrap_state_if_runner_changed(previous, self.load_named_normalized("runners"))
179
+ return result
176
180
 
177
181
  def _prepare_payload_for_save(self, name: str, payload: dict) -> dict:
178
182
  prepared = deepcopy(payload) if isinstance(payload, dict) else {}
@@ -191,6 +195,35 @@ class ConfigManager:
191
195
  prepared["lingzhu"] = lingzhu
192
196
  return prepared
193
197
 
198
+ def _invalidate_codex_bootstrap_state_if_runner_changed(self, previous: dict, current: dict) -> None:
199
+ previous_codex = previous.get("codex") if isinstance(previous.get("codex"), dict) else {}
200
+ current_codex = current.get("codex") if isinstance(current.get("codex"), dict) else {}
201
+ tracked_keys = (
202
+ "binary",
203
+ "config_dir",
204
+ "profile",
205
+ "model",
206
+ "model_reasoning_effort",
207
+ "approval_policy",
208
+ "sandbox_mode",
209
+ "env",
210
+ )
211
+ if all(previous_codex.get(key) == current_codex.get(key) for key in tracked_keys):
212
+ return
213
+ config = self.load_named_normalized("config")
214
+ bootstrap = config.get("bootstrap") if isinstance(config.get("bootstrap"), dict) else {}
215
+ bootstrap["codex_ready"] = False
216
+ bootstrap["codex_last_checked_at"] = utc_now()
217
+ bootstrap["codex_last_result"] = {
218
+ "ok": False,
219
+ "summary": "Codex runner configuration changed. A new startup probe is required.",
220
+ "warnings": [],
221
+ "errors": [],
222
+ "guidance": [],
223
+ }
224
+ config["bootstrap"] = bootstrap
225
+ self.save_named_text("config", self.render_named_payload("config", config))
226
+
194
227
  def bind_qq_main_chat(self, *, profile_id: str | None = None, chat_id: str) -> dict:
195
228
  normalized_chat_id = str(chat_id or "").strip()
196
229
  if not normalized_chat_id:
@@ -451,6 +484,8 @@ This page edits `{home_text}/config/runners.yaml`.
451
484
  - keep `codex.enabled: true`
452
485
  - keep `claude.enabled: false`
453
486
  - `claude` remains TODO / reserved in the current open-source release and is not runnable yet
487
+ - set `codex.profile` only when your Codex CLI uses a named provider profile such as `m27`
488
+ - when you launch DeepScientist ad hoc with a provider profile, you can also use `ds --codex-profile <name>`
454
489
  - keep `codex.model_reasoning_effort: xhigh` unless you explicitly want a lighter default
455
490
  - keep `codex.retry_on_failure: true` so transient Codex failures can resume automatically
456
491
  - keep retry timing near `10s / 6x / 1800s max` so Codex backs off exponentially and the last retry waits about 30 minutes
@@ -462,7 +497,7 @@ The **Test** button checks:
462
497
 
463
498
  - whether the configured runner binaries are on PATH
464
499
  - whether disabled runners are intentionally skipped
465
- - for Codex, it also runs a real hello probe so login and first-run setup problems surface before quest execution
500
+ - for Codex, it also runs a real hello probe so login problems, profile misconfiguration, and first-run setup issues surface before quest execution
466
501
  - it does not simulate the full failure/retry loop, so use quest runtime logs when debugging recovery behavior
467
502
  """
468
503
  if name == "plugins":
@@ -1151,6 +1186,70 @@ Use **Test** when the file exposes runtime dependencies.
1151
1186
  return "gpt-5.4"
1152
1187
  return str(raw_model).strip()
1153
1188
 
1189
+ @staticmethod
1190
+ def _codex_profile_name(config: dict) -> str:
1191
+ raw_profile = config.get("profile")
1192
+ if raw_profile is None:
1193
+ return ""
1194
+ return str(raw_profile).strip()
1195
+
1196
+ @staticmethod
1197
+ def _codex_runner_env(config: dict) -> dict[str, str]:
1198
+ raw_env = config.get("env")
1199
+ if not isinstance(raw_env, dict):
1200
+ return {}
1201
+ resolved: dict[str, str] = {}
1202
+ for key, value in raw_env.items():
1203
+ env_key = str(key or "").strip()
1204
+ if not env_key or value is None:
1205
+ continue
1206
+ resolved[env_key] = str(value)
1207
+ return resolved
1208
+
1209
+ def _codex_missing_binary_guidance(self, config: dict) -> list[str]:
1210
+ profile = self._codex_profile_name(config)
1211
+ guidance = [
1212
+ "Run `npm install -g @researai/deepscientist` again so the bundled Codex dependency is installed.",
1213
+ "If `codex` is still missing, install it explicitly with `npm install -g @openai/codex`.",
1214
+ ]
1215
+ if profile:
1216
+ guidance.extend(
1217
+ [
1218
+ f"Then verify `codex --profile {profile}` works from a terminal before starting DeepScientist.",
1219
+ "If that profile uses a custom provider, make sure its API key and Base URL are configured in Codex first.",
1220
+ ]
1221
+ )
1222
+ else:
1223
+ guidance.append("Run `codex --login` (or `codex`) once and finish authentication before starting DeepScientist.")
1224
+ guidance.append("If you use a custom Codex path, set `runners.codex.binary` to that absolute executable path.")
1225
+ return guidance
1226
+
1227
+ def _codex_probe_failure_guidance(self, config: dict) -> tuple[list[str], list[str]]:
1228
+ profile = self._codex_profile_name(config)
1229
+ if profile:
1230
+ return (
1231
+ [
1232
+ f"Codex profile `{profile}` did not complete the startup hello probe successfully.",
1233
+ ],
1234
+ [
1235
+ f"Run `codex --profile {profile}` in a terminal and confirm that profile can start normally.",
1236
+ "If the profile uses a custom provider, make sure its API key, Base URL, and model configuration are available to Codex.",
1237
+ "If the provider expects the model from the Codex profile itself, set `model: inherit` in `~/DeepScientist/config/runners.yaml`.",
1238
+ "Then run `ds doctor` and start DeepScientist again.",
1239
+ ],
1240
+ )
1241
+ return (
1242
+ [
1243
+ "Run `codex --login` (or `codex`) once and complete login before starting DeepScientist.",
1244
+ ],
1245
+ [
1246
+ "Run `codex --login` (or `codex`) in a terminal and complete login or first-run setup.",
1247
+ "If `codex` is missing, install it explicitly with `npm install -g @openai/codex`.",
1248
+ "If the configured model is not available to your Codex account, update `~/DeepScientist/config/runners.yaml` and try again.",
1249
+ "Then run `ds doctor` and start DeepScientist again.",
1250
+ ],
1251
+ )
1252
+
1154
1253
  @staticmethod
1155
1254
  def _codex_model_unavailable(stdout_text: str, stderr_text: str) -> bool:
1156
1255
  haystack = f"{stdout_text}\n{stderr_text}".lower()
@@ -1172,6 +1271,7 @@ Use **Test** when the file exposes runtime dependencies.
1172
1271
  self,
1173
1272
  *,
1174
1273
  resolved_binary: str,
1274
+ profile: str,
1175
1275
  requested_model: str,
1176
1276
  approval_policy: str,
1177
1277
  reasoning_effort: str | None,
@@ -1180,12 +1280,18 @@ Use **Test** when the file exposes runtime dependencies.
1180
1280
  command = [
1181
1281
  resolved_binary,
1182
1282
  "--search",
1283
+ ]
1284
+ if profile:
1285
+ command.extend(["--profile", profile])
1286
+ command.extend(
1287
+ [
1183
1288
  "exec",
1184
1289
  "--json",
1185
1290
  "--cd",
1186
1291
  str(repo_root()),
1187
1292
  "--skip-git-repo-check",
1188
- ]
1293
+ ]
1294
+ )
1189
1295
  if not self._codex_should_inherit_model(requested_model):
1190
1296
  command.extend(["--model", requested_model])
1191
1297
  if approval_policy:
@@ -1217,6 +1323,7 @@ Use **Test** when the file exposes runtime dependencies.
1217
1323
  checked_at = utc_now()
1218
1324
  binary = str(config.get("binary") or "codex").strip() or "codex"
1219
1325
  resolved_binary = resolve_runner_binary(binary, runner_name="codex")
1326
+ profile = self._codex_profile_name(config)
1220
1327
  requested_model = self._codex_requested_model(config)
1221
1328
  raw_reasoning_effort = config.get("model_reasoning_effort")
1222
1329
  reasoning_effort = (
@@ -1228,6 +1335,7 @@ Use **Test** when the file exposes runtime dependencies.
1228
1335
  "binary": binary,
1229
1336
  "resolved_binary": resolved_binary,
1230
1337
  "config_dir": str(config.get("config_dir") or "~/.codex"),
1338
+ "profile": profile,
1231
1339
  "model": requested_model or "inherit",
1232
1340
  "requested_model": requested_model or "inherit",
1233
1341
  "effective_model": requested_model or "inherit",
@@ -1248,18 +1356,14 @@ Use **Test** when the file exposes runtime dependencies.
1248
1356
  "DeepScientist could not resolve the bundled or configured `codex` CLI.",
1249
1357
  ],
1250
1358
  "details": details,
1251
- "guidance": [
1252
- "Run `npm install -g @researai/deepscientist` again so the bundled Codex dependency is installed.",
1253
- "If `codex` is still missing, install it explicitly with `npm install -g @openai/codex`.",
1254
- "Run `codex --login` (or `codex`) once and finish authentication before starting DeepScientist.",
1255
- "If you use a custom Codex path, set `runners.codex.binary` to that absolute executable path.",
1256
- ],
1359
+ "guidance": self._codex_missing_binary_guidance(config),
1257
1360
  }
1258
1361
 
1259
1362
  approval_policy = str(config.get("approval_policy") or "on-request").strip()
1260
1363
  sandbox_mode = str(config.get("sandbox_mode") or "workspace-write").strip()
1261
1364
 
1262
1365
  env = os.environ.copy()
1366
+ env.update(self._codex_runner_env(config))
1263
1367
  config_dir = str(config.get("config_dir") or "~/.codex").strip()
1264
1368
  if config_dir:
1265
1369
  env["CODEX_HOME"] = str(Path(config_dir).expanduser())
@@ -1268,6 +1372,7 @@ Use **Test** when the file exposes runtime dependencies.
1268
1372
  def run_probe_once(model_for_command: str) -> tuple[list[str], subprocess.CompletedProcess[str] | None, subprocess.TimeoutExpired | None]:
1269
1373
  command = self._build_codex_probe_command(
1270
1374
  resolved_binary=resolved_binary,
1375
+ profile=profile,
1271
1376
  requested_model=model_for_command,
1272
1377
  approval_policy=approval_policy,
1273
1378
  reasoning_effort=reasoning_effort,
@@ -1304,14 +1409,13 @@ Use **Test** when the file exposes runtime dependencies.
1304
1409
  "warnings": [],
1305
1410
  "errors": [
1306
1411
  "Codex did not answer the startup hello probe within 90 seconds.",
1307
- "Run `codex --login` (or `codex`) manually, complete login, then retry DeepScientist.",
1412
+ *self._codex_probe_failure_guidance(config)[0],
1308
1413
  ],
1309
1414
  "details": details,
1310
1415
  "guidance": [
1311
- "Run `codex --login` (or `codex`) manually to finish interactive login or first-run setup.",
1416
+ *self._codex_probe_failure_guidance(config)[1],
1312
1417
  "If `codex` is missing on PATH, install it explicitly with `npm install -g @openai/codex`.",
1313
- "Confirm the configured model is available to your account. DeepScientist currently probes Codex with the configured runner model.",
1314
- "Run `ds doctor` after login, then start DeepScientist again.",
1418
+ "Confirm the configured model is available to your Codex setup. DeepScientist currently probes Codex with the configured runner model first.",
1315
1419
  ],
1316
1420
  }
1317
1421
 
@@ -1389,19 +1493,15 @@ Use **Test** when the file exposes runtime dependencies.
1389
1493
  warnings.append("Codex returned stderr during the startup probe.")
1390
1494
  if details.get("model_fallback_attempted") and not details.get("model_fallback_used"):
1391
1495
  warnings.append("DeepScientist also tried the current Codex default model, but that fallback probe did not succeed.")
1392
- errors.append("Run `codex --login` (or `codex`) once and complete login before starting DeepScientist.")
1496
+ errors.extend(self._codex_probe_failure_guidance(config)[0])
1497
+ failure_guidance = self._codex_probe_failure_guidance(config)[1]
1393
1498
  return {
1394
1499
  "ok": ok,
1395
1500
  "summary": "Codex startup probe completed." if ok else "Codex startup probe failed.",
1396
1501
  "warnings": warnings,
1397
1502
  "errors": errors,
1398
1503
  "details": details,
1399
- "guidance": [] if ok else [
1400
- "Run `codex --login` (or `codex`) in a terminal and complete login or first-run setup.",
1401
- "If `codex` is missing, install it explicitly with `npm install -g @openai/codex`.",
1402
- "If the configured model is not available to your Codex account, update `~/DeepScientist/config/runners.yaml` and try again.",
1403
- "Then run `ds doctor` and start DeepScientist again.",
1404
- ],
1504
+ "guidance": [] if ok else failure_guidance,
1405
1505
  }
1406
1506
 
1407
1507
  def _persist_codex_bootstrap_result(self, result: dict) -> None:
@@ -1418,6 +1518,7 @@ Use **Test** when the file exposes runtime dependencies.
1418
1518
  "guidance": list(result.get("guidance") or []),
1419
1519
  "binary": details.get("binary"),
1420
1520
  "resolved_binary": details.get("resolved_binary"),
1521
+ "profile": details.get("profile"),
1421
1522
  "model": details.get("model"),
1422
1523
  "requested_model": details.get("requested_model"),
1423
1524
  "effective_model": details.get("effective_model"),