@researai/deepscientist 1.5.1 → 1.5.3

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 (116) hide show
  1. package/README.md +69 -1
  2. package/bin/ds.js +2239 -153
  3. package/docs/en/00_QUICK_START.md +60 -20
  4. package/docs/en/01_SETTINGS_REFERENCE.md +20 -20
  5. package/docs/en/02_START_RESEARCH_GUIDE.md +11 -11
  6. package/docs/en/03_QQ_CONNECTOR_GUIDE.md +10 -10
  7. package/docs/en/05_TUI_GUIDE.md +1 -1
  8. package/docs/en/09_DOCTOR.md +48 -4
  9. package/docs/en/90_ARCHITECTURE.md +4 -2
  10. package/docs/zh/00_QUICK_START.md +60 -20
  11. package/docs/zh/01_SETTINGS_REFERENCE.md +21 -21
  12. package/docs/zh/02_START_RESEARCH_GUIDE.md +19 -19
  13. package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +10 -10
  14. package/docs/zh/05_TUI_GUIDE.md +1 -1
  15. package/docs/zh/09_DOCTOR.md +46 -4
  16. package/install.sh +125 -8
  17. package/package.json +2 -1
  18. package/pyproject.toml +1 -1
  19. package/src/deepscientist/__init__.py +6 -1
  20. package/src/deepscientist/artifact/service.py +553 -26
  21. package/src/deepscientist/bash_exec/monitor.py +23 -4
  22. package/src/deepscientist/bash_exec/runtime.py +3 -0
  23. package/src/deepscientist/bash_exec/service.py +132 -4
  24. package/src/deepscientist/bridges/base.py +10 -19
  25. package/src/deepscientist/channels/discord_gateway.py +25 -2
  26. package/src/deepscientist/channels/feishu_long_connection.py +41 -3
  27. package/src/deepscientist/channels/qq.py +524 -64
  28. package/src/deepscientist/channels/qq_gateway.py +22 -3
  29. package/src/deepscientist/channels/relay.py +429 -90
  30. package/src/deepscientist/channels/slack_socket.py +29 -5
  31. package/src/deepscientist/channels/telegram_polling.py +25 -2
  32. package/src/deepscientist/channels/whatsapp_local_session.py +32 -4
  33. package/src/deepscientist/cli.py +27 -0
  34. package/src/deepscientist/config/models.py +6 -40
  35. package/src/deepscientist/config/service.py +165 -156
  36. package/src/deepscientist/connector_profiles.py +346 -0
  37. package/src/deepscientist/connector_runtime.py +88 -43
  38. package/src/deepscientist/daemon/api/handlers.py +65 -11
  39. package/src/deepscientist/daemon/api/router.py +4 -2
  40. package/src/deepscientist/daemon/app.py +772 -219
  41. package/src/deepscientist/doctor.py +69 -2
  42. package/src/deepscientist/gitops/diff.py +3 -0
  43. package/src/deepscientist/home.py +25 -2
  44. package/src/deepscientist/mcp/context.py +3 -1
  45. package/src/deepscientist/mcp/server.py +66 -7
  46. package/src/deepscientist/migration.py +114 -0
  47. package/src/deepscientist/prompts/builder.py +71 -3
  48. package/src/deepscientist/qq_profiles.py +186 -0
  49. package/src/deepscientist/quest/layout.py +1 -0
  50. package/src/deepscientist/quest/service.py +70 -12
  51. package/src/deepscientist/quest/stage_views.py +46 -0
  52. package/src/deepscientist/runners/codex.py +2 -0
  53. package/src/deepscientist/shared.py +44 -17
  54. package/src/prompts/connectors/lingzhu.md +3 -0
  55. package/src/prompts/connectors/qq.md +42 -2
  56. package/src/prompts/system.md +123 -10
  57. package/src/skills/analysis-campaign/SKILL.md +35 -6
  58. package/src/skills/baseline/SKILL.md +73 -32
  59. package/src/skills/decision/SKILL.md +4 -3
  60. package/src/skills/experiment/SKILL.md +28 -6
  61. package/src/skills/finalize/SKILL.md +5 -2
  62. package/src/skills/idea/SKILL.md +2 -2
  63. package/src/skills/intake-audit/SKILL.md +2 -2
  64. package/src/skills/rebuttal/SKILL.md +4 -2
  65. package/src/skills/review/SKILL.md +4 -2
  66. package/src/skills/scout/SKILL.md +2 -2
  67. package/src/skills/write/SKILL.md +2 -2
  68. package/src/tui/package.json +1 -1
  69. package/src/ui/dist/assets/{AiManusChatView-w5lF2Ttt.js → AiManusChatView-qzChi9uh.js} +67 -94
  70. package/src/ui/dist/assets/{AnalysisPlugin-DJOED79I.js → AnalysisPlugin-CcC_-UqN.js} +1 -1
  71. package/src/ui/dist/assets/{AutoFigurePlugin-DaG61Y0M.js → AutoFigurePlugin-DD8LkJLe.js} +5 -5
  72. package/src/ui/dist/assets/{CliPlugin-CV4LqUB_.js → CliPlugin-DJJFfVmW.js} +17 -110
  73. package/src/ui/dist/assets/{CodeEditorPlugin-DylfAea4.js → CodeEditorPlugin-CrjkHNLh.js} +8 -8
  74. package/src/ui/dist/assets/{CodeViewerPlugin-F7saY0LM.js → CodeViewerPlugin-obnD6G5R.js} +5 -5
  75. package/src/ui/dist/assets/{DocViewerPlugin-COP0c7jf.js → DocViewerPlugin-DB9SUQVd.js} +3 -3
  76. package/src/ui/dist/assets/{GitDiffViewerPlugin-CAS05pT9.js → GitDiffViewerPlugin-DZLlNlD2.js} +1 -1
  77. package/src/ui/dist/assets/{ImageViewerPlugin-Bco1CN_w.js → ImageViewerPlugin-BGwfDZ0Y.js} +5 -5
  78. package/src/ui/dist/assets/{LabCopilotPanel-CvMlCD99.js → LabCopilotPanel-dfLptQcR.js} +10 -10
  79. package/src/ui/dist/assets/{LabPlugin-BYankkE4.js → LabPlugin-CeGjAl3A.js} +1 -1
  80. package/src/ui/dist/assets/{LatexPlugin-LDSMR-t-.js → LatexPlugin-BBJ7kd1V.js} +7 -7
  81. package/src/ui/dist/assets/{MarkdownViewerPlugin-B7o80jgm.js → MarkdownViewerPlugin-DKZi7BcB.js} +4 -4
  82. package/src/ui/dist/assets/{MarketplacePlugin-CM6ZOcpC.js → MarketplacePlugin-C_k-9jD0.js} +3 -3
  83. package/src/ui/dist/assets/{NotebookEditor-Dc61cXmK.js → NotebookEditor-4R88_BMO.js} +1 -1
  84. package/src/ui/dist/assets/{PdfLoader-DWowuQwx.js → PdfLoader-DwEFQLrw.js} +1 -1
  85. package/src/ui/dist/assets/{PdfMarkdownPlugin-BsJM1q_a.js → PdfMarkdownPlugin-D-jdsqF8.js} +3 -3
  86. package/src/ui/dist/assets/{PdfViewerPlugin-DB2eEEFQ.js → PdfViewerPlugin-CmeBGDY0.js} +10 -10
  87. package/src/ui/dist/assets/{SearchPlugin-CraThSvt.js → SearchPlugin-Dlz2WKJ4.js} +1 -1
  88. package/src/ui/dist/assets/{Stepper-CgocRTPq.js → Stepper-ClOgzWM3.js} +1 -1
  89. package/src/ui/dist/assets/{TextViewerPlugin-B1JGhKtd.js → TextViewerPlugin-DDQWxibk.js} +4 -4
  90. package/src/ui/dist/assets/{VNCViewer-CclFC7FM.js → VNCViewer-CJXT0Nm8.js} +9 -9
  91. package/src/ui/dist/assets/{bibtex-D3IKsMl7.js → bibtex-DLr4Rtk4.js} +1 -1
  92. package/src/ui/dist/assets/{code-BP37Xx0p.js → code-DgKK408Y.js} +1 -1
  93. package/src/ui/dist/assets/{file-content-BAJSu-9r.js → file-content-6HBqQnvQ.js} +1 -1
  94. package/src/ui/dist/assets/{file-diff-panel-DUGeCTuy.js → file-diff-panel-Dhu0TbBM.js} +1 -1
  95. package/src/ui/dist/assets/{file-socket-CXc1Ojf7.js → file-socket-CP3iwVZG.js} +1 -1
  96. package/src/ui/dist/assets/{file-utils-2J21jt7M.js → file-utils-BsS-Aw68.js} +1 -1
  97. package/src/ui/dist/assets/{image-CMMmgvcn.js → image-ByeK-Zcv.js} +1 -1
  98. package/src/ui/dist/assets/{index-DmwmJmbW.js → index-BLjo5--a.js} +33610 -31016
  99. package/src/ui/dist/assets/{index-CWgMgpow.js → index-BdsE0uRz.js} +11 -11
  100. package/src/ui/dist/assets/{index-s7aHnNQ4.js → index-C-eX-N6A.js} +1 -1
  101. package/src/ui/dist/assets/{index-KGt-z-dD.css → index-CuQhlrR-.css} +2747 -2
  102. package/src/ui/dist/assets/{index-BaVumsQT.js → index-DyremSIv.js} +2 -2
  103. package/src/ui/dist/assets/{message-square-CQRfX0Am.js → message-square-DnagiLnc.js} +1 -1
  104. package/src/ui/dist/assets/{monaco-B4TbdsrF.js → monaco-4kBFeprs.js} +1 -1
  105. package/src/ui/dist/assets/{popover-B8Rokodk.js → popover-hRCXZzs2.js} +1 -1
  106. package/src/ui/dist/assets/{project-sync-D_i96KH4.js → project-sync-O_85YuP6.js} +1 -1
  107. package/src/ui/dist/assets/{sigma-D12PnzCN.js → sigma-DvKopSnL.js} +1 -1
  108. package/src/ui/dist/assets/{tooltip-B6YrI4aJ.js → tooltip-BmlPc6kc.js} +1 -1
  109. package/src/ui/dist/assets/{trash-Bc8jGp0V.js → trash-n-UvdZFR.js} +1 -1
  110. package/src/ui/dist/assets/{useCliAccess-mXVCYSZ-.js → useCliAccess-WDd3_wIh.js} +1 -1
  111. package/src/ui/dist/assets/{useFileDiffOverlay-Bg6b9H9K.js → useFileDiffOverlay-rXLIL2NF.js} +1 -1
  112. package/src/ui/dist/assets/{wrap-text-Drh5GEnL.js → wrap-text-qIYQ4a_W.js} +1 -1
  113. package/src/ui/dist/assets/{zoom-out-CJj9DZLn.js → zoom-out-fZXCEFsy.js} +1 -1
  114. package/src/ui/dist/index.html +2 -2
  115. package/uv.lock +1155 -0
  116. package/src/ui/dist/assets/LabPlugin-D9jVIo0A.css +0 -2698
@@ -10,6 +10,7 @@ from urllib.parse import parse_qs, unquote
10
10
 
11
11
  from ...acp import OptionalACPBridge, build_session_descriptor, build_session_update, get_acp_bridge_status
12
12
  from ...bash_exec.service import DEFAULT_TERMINAL_SESSION_ID
13
+ from ... import __version__ as DEEPSCIENTIST_VERSION
13
14
  from ...gitops import commit_detail, compare_refs, diff_file_between_refs, diff_file_for_commit, export_git_graph, list_branch_canvas, log_ref_history
14
15
  from ...memory import MemoryService
15
16
  from ...quest import QuestService
@@ -71,6 +72,7 @@ class ApiHandlers:
71
72
  def _inject_ui_runtime(self, payload: str) -> str:
72
73
  runtime_payload = {
73
74
  "surface": "quest",
75
+ "version": DEEPSCIENTIST_VERSION,
74
76
  "supports": {
75
77
  "productApis": False,
76
78
  "socketIo": False,
@@ -160,6 +162,13 @@ npm --prefix src/ui run build</pre>
160
162
  "sessions": self.app.sessions.snapshot(),
161
163
  }
162
164
 
165
+ def system_update(self) -> dict:
166
+ return self.app.system_update_status()
167
+
168
+ def system_update_action(self, body: dict) -> dict:
169
+ action = str(body.get("action") or "").strip().lower()
170
+ return self.app.request_system_update(action=action)
171
+
163
172
  def cli_health(self) -> dict:
164
173
  online_channels = [
165
174
  channel.status()
@@ -196,19 +205,12 @@ npm --prefix src/ui run build</pre>
196
205
  def connectors(self) -> list[dict]:
197
206
  return self.app.list_connector_statuses()
198
207
 
208
+ def connectors_availability(self) -> dict:
209
+ return self.app.connector_availability_summary()
210
+
199
211
  def baselines(self) -> list[dict]:
200
212
  return self.app.artifact_service.baselines.list_entries()
201
213
 
202
- def bridge_webhook(self, connector: str, *, method: str, path: str, raw_body: bytes, headers: dict[str, str], body: dict) -> tuple[int, dict, bytes | str] | dict:
203
- return self.app.handle_bridge_webhook(
204
- connector,
205
- method=method,
206
- path=path,
207
- raw_body=raw_body,
208
- headers=headers,
209
- body=body,
210
- )
211
-
212
214
  def qq_bindings(self) -> list[dict]:
213
215
  return self.app.list_qq_bindings()
214
216
 
@@ -237,12 +239,33 @@ npm --prefix src/ui run build</pre>
237
239
  preferred_connector_conversation_id = (
238
240
  str(body.get("preferred_connector_conversation_id") or "").strip() or None
239
241
  )
242
+ requested_connector_bindings = (
243
+ [dict(item) for item in body.get("requested_connector_bindings") if isinstance(item, dict)]
244
+ if isinstance(body.get("requested_connector_bindings"), list)
245
+ else []
246
+ )
247
+ force_connector_rebind_raw = body.get("force_connector_rebind")
248
+ force_connector_rebind = bool(force_connector_rebind_raw) and str(force_connector_rebind_raw).strip().lower() not in {
249
+ "0",
250
+ "false",
251
+ "no",
252
+ "off",
253
+ }
240
254
  requested_baseline_ref = body.get("requested_baseline_ref")
241
255
  startup_contract = body.get("startup_contract")
242
256
  auto_start = body.get("auto_start") is True
243
257
  initial_message = str(body.get("initial_message") or "").strip()
244
258
  if not goal:
245
259
  return {"ok": False, "message": "Quest goal is required."}
260
+ if requested_connector_bindings and not force_connector_rebind:
261
+ conflicts = self.app.preview_connector_binding_conflicts(requested_connector_bindings)
262
+ if conflicts:
263
+ return 409, {
264
+ "ok": False,
265
+ "conflict": True,
266
+ "message": "One or more selected connector targets are already bound to another quest.",
267
+ "conflicts": conflicts,
268
+ }
246
269
  try:
247
270
  snapshot = self.app.create_quest(
248
271
  goal=goal,
@@ -250,6 +273,8 @@ npm --prefix src/ui run build</pre>
250
273
  quest_id=quest_id,
251
274
  source=source,
252
275
  preferred_connector_conversation_id=preferred_connector_conversation_id,
276
+ requested_connector_bindings=requested_connector_bindings,
277
+ force_connector_rebind=force_connector_rebind,
253
278
  requested_baseline_ref=requested_baseline_ref if isinstance(requested_baseline_ref, dict) else None,
254
279
  startup_contract=startup_contract if isinstance(startup_contract, dict) else None,
255
280
  )
@@ -377,9 +402,19 @@ npm --prefix src/ui run build</pre>
377
402
  }
378
403
 
379
404
  def quest_bindings(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
405
+ requested_bindings = (
406
+ [dict(item) for item in body.get("bindings") if isinstance(item, dict)]
407
+ if isinstance(body.get("bindings"), list)
408
+ else []
409
+ )
380
410
  conversation_id = str(body.get("conversation_id") or body.get("source") or "").strip() or None
411
+ connector_name = str(body.get("connector") or "").strip() or None
381
412
  force_raw = body.get("force")
382
413
  force = bool(force_raw) and str(force_raw).strip().lower() not in {"0", "false", "no", "off"}
414
+ if requested_bindings:
415
+ return self.app.update_quest_bindings(quest_id, requested_bindings, force=force)
416
+ if connector_name:
417
+ return self.app.update_quest_connector_binding(quest_id, connector_name, conversation_id, force=force)
383
418
  return self.app.update_quest_binding(quest_id, conversation_id, force=force)
384
419
 
385
420
  def quest_session(self, quest_id: str) -> dict:
@@ -394,12 +429,20 @@ npm --prefix src/ui run build</pre>
394
429
  def quest_events(self, quest_id: str, path: str) -> dict:
395
430
  query = self.parse_query(path)
396
431
  after = int((query.get("after") or ["0"])[0] or "0")
432
+ before_raw = ((query.get("before") or [""])[0] or "").strip()
433
+ before = int(before_raw) if before_raw.isdigit() else None
397
434
  limit = int((query.get("limit") or ["200"])[0] or "200")
398
435
  tail_raw = ((query.get("tail") or ["0"])[0] or "0").strip().lower()
399
436
  tail = tail_raw in {"1", "true", "yes", "on"}
400
437
  format_name = ((query.get("format") or ["both"])[0] or "both").lower()
401
438
  session_id = ((query.get("session_id") or [f"quest:{quest_id}"])[0] or f"quest:{quest_id}")
402
- payload = self._fresh_quest_service().events(quest_id, after=after, limit=limit, tail=tail)
439
+ payload = self._fresh_quest_service().events(
440
+ quest_id,
441
+ after=after,
442
+ before=before,
443
+ limit=limit,
444
+ tail=tail,
445
+ )
403
446
  if format_name in {"acp", "both"}:
404
447
  payload["acp_updates"] = [
405
448
  build_session_update(
@@ -438,6 +481,7 @@ npm --prefix src/ui run build</pre>
438
481
  def bash_sessions(self, quest_id: str, path: str) -> list[dict]:
439
482
  query = self.parse_query(path)
440
483
  status = ((query.get("status") or [""])[0] or "").strip() or None
484
+ kind = ((query.get("kind") or [""])[0] or "").strip() or None
441
485
  chat_session_id = ((query.get("chat_session_id") or [""])[0] or "").strip() or None
442
486
  limit_raw = ((query.get("limit") or ["200"])[0] or "200").strip()
443
487
  try:
@@ -458,6 +502,7 @@ npm --prefix src/ui run build</pre>
458
502
  return self.app.bash_exec_service.list_sessions(
459
503
  quest_root,
460
504
  status=status,
505
+ kind=kind,
461
506
  agent_ids=agent_ids or None,
462
507
  agent_instance_ids=agent_instance_ids or None,
463
508
  chat_session_id=chat_session_id,
@@ -475,12 +520,14 @@ npm --prefix src/ui run build</pre>
475
520
  query = self.parse_query(path)
476
521
  limit_raw = ((query.get("limit") or ["200"])[0] or "200").strip()
477
522
  before_seq_raw = ((query.get("before_seq") or [""])[0] or "").strip()
523
+ after_seq_raw = ((query.get("after_seq") or [""])[0] or "").strip()
478
524
  order = ((query.get("order") or ["asc"])[0] or "asc").strip().lower()
479
525
  try:
480
526
  limit = max(1, min(int(limit_raw), 1000))
481
527
  except ValueError:
482
528
  limit = 200
483
529
  before_seq = int(before_seq_raw) if before_seq_raw.isdigit() else None
530
+ after_seq = int(after_seq_raw) if after_seq_raw.isdigit() else None
484
531
  quest_root = self.app.quest_service._quest_root(quest_id)
485
532
  try:
486
533
  entries, meta = self.app.bash_exec_service.read_log_entries(
@@ -488,6 +535,7 @@ npm --prefix src/ui run build</pre>
488
535
  bash_id,
489
536
  limit=limit,
490
537
  before_seq=before_seq,
538
+ after_seq=after_seq,
491
539
  order=order,
492
540
  )
493
541
  except FileNotFoundError:
@@ -504,15 +552,21 @@ npm --prefix src/ui run build</pre>
504
552
 
505
553
  def bash_stop(self, quest_id: str, bash_id: str, body: dict) -> dict | tuple[int, dict]:
506
554
  quest_root = self.app.quest_service._quest_root(quest_id)
555
+ wait = bool(body.get("wait"))
556
+ timeout_seconds_raw = body.get("timeout_seconds")
557
+ timeout_seconds = timeout_seconds_raw if isinstance(timeout_seconds_raw, int) and timeout_seconds_raw > 0 else None
507
558
  try:
508
559
  session = self.app.bash_exec_service.request_stop(
509
560
  quest_root,
510
561
  bash_id,
511
562
  reason=str(body.get("reason") or "").strip() or None,
512
563
  user_id="web-react",
564
+ force=bool(body.get("force")),
513
565
  )
514
566
  except FileNotFoundError:
515
567
  return 404, {"success": False, "status": "not_found"}
568
+ if wait:
569
+ session = self.app.bash_exec_service.wait_for_session(quest_root, bash_id, timeout_seconds=timeout_seconds)
516
570
  return {
517
571
  "success": True,
518
572
  "status": session.get("status"),
@@ -8,12 +8,13 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
8
8
  ("GET", re.compile(r"^/ui/(?P<ui_path>.+)$"), "ui_asset"),
9
9
  ("GET", re.compile(r"^/(?P<spa_path>(?!api(?:/|$)|ui(?:/|$)|assets(?:/|$)).+)$"), "spa_root"),
10
10
  ("GET", re.compile(r"^/api/health$"), "health"),
11
+ ("GET", re.compile(r"^/api/system/update$"), "system_update"),
12
+ ("POST", re.compile(r"^/api/system/update$"), "system_update_action"),
11
13
  ("GET", re.compile(r"^/api/v1/health/cli$"), "cli_health"),
12
14
  ("POST", re.compile(r"^/api/admin/shutdown$"), "admin_shutdown"),
13
15
  ("GET", re.compile(r"^/api/acp/status$"), "acp_status"),
14
16
  ("GET", re.compile(r"^/api/connectors$"), "connectors"),
15
- ("GET", re.compile(r"^/api/bridges/(?P<connector>[^/]+)/webhook$"), "bridge_webhook"),
16
- ("POST", re.compile(r"^/api/bridges/(?P<connector>[^/]+)/webhook$"), "bridge_webhook"),
17
+ ("GET", re.compile(r"^/api/connectors/availability$"), "connectors_availability"),
17
18
  ("GET", re.compile(r"^/api/connectors/qq/bindings$"), "qq_bindings"),
18
19
  ("POST", re.compile(r"^/api/connectors/qq/inbound$"), "qq_inbound"),
19
20
  ("GET", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/bindings$"), "connector_bindings"),
@@ -28,6 +29,7 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
28
29
  ("DELETE", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/baseline-binding$"), "quest_baseline_unbind"),
29
30
  ("PATCH", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/settings$"), "quest_settings"),
30
31
  ("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/bindings$"), "quest_bindings"),
32
+ ("PUT", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/bindings$"), "quest_bindings"),
31
33
  ("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/session$"), "quest_session"),
32
34
  ("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/events$"), "quest_events"),
33
35
  ("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/artifacts$"), "quest_artifacts"),