@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
@@ -9,6 +9,7 @@ from urllib.request import Request, urlopen
9
9
  from websockets.exceptions import ConnectionClosed
10
10
  from websockets.sync.client import connect as websocket_connect
11
11
 
12
+ from ..connector_runtime import format_conversation_id
12
13
  from ..shared import read_json, utc_now, write_json
13
14
 
14
15
 
@@ -20,15 +21,23 @@ class SlackSocketModeService:
20
21
  config: dict[str, Any],
21
22
  on_event: Callable[[dict[str, Any]], None],
22
23
  log: Callable[[str, str], None] | None = None,
24
+ profile_id: str | None = None,
25
+ profile_label: str | None = None,
26
+ encode_profile_id: bool = False,
23
27
  ) -> None:
24
28
  self.home = home
25
29
  self.config = config
26
30
  self.on_event = on_event
27
31
  self.log = log or self._default_log
32
+ self.profile_id = str(profile_id or "").strip() or None
33
+ self.profile_label = str(profile_label or "").strip() or None
34
+ self._encode_profile_id = bool(encode_profile_id and self.profile_id)
28
35
  self._thread: threading.Thread | None = None
29
36
  self._stop_event = threading.Event()
30
37
  self._connection = None
31
38
  self._root = home / "logs" / "connectors" / "slack"
39
+ if self.profile_id:
40
+ self._root = self._root / "profiles" / self.profile_id
32
41
  self._runtime_path = self._root / "runtime.json"
33
42
 
34
43
  def start(self) -> bool:
@@ -64,7 +73,7 @@ class SlackSocketModeService:
64
73
  self._thread = threading.Thread(
65
74
  target=self._run,
66
75
  daemon=True,
67
- name="deepscientist-slack-socket-mode",
76
+ name=f"deepscientist-slack-socket-mode-{self.profile_id or 'default'}",
68
77
  )
69
78
  self._thread.start()
70
79
  return True
@@ -208,14 +217,15 @@ class SlackSocketModeService:
208
217
  "sender_id": sender_id,
209
218
  "sender_name": str(event.get("username") or sender_id).strip(),
210
219
  "message_id": str(event.get("ts") or event.get("event_ts") or "").strip(),
211
- "conversation_id": f"slack:{chat_type}:{channel_id}",
220
+ "conversation_id": self._conversation_id(chat_type, channel_id),
221
+ "profile_id": self.profile_id,
222
+ "profile_label": self.profile_label,
212
223
  "text": normalized_text,
213
224
  "mentioned": mentioned,
214
225
  "raw_event": payload,
215
226
  }
216
227
 
217
- @staticmethod
218
- def _normalize_slash_command(payload: dict[str, Any]) -> dict[str, Any] | None:
228
+ def _normalize_slash_command(self, payload: dict[str, Any]) -> dict[str, Any] | None:
219
229
  channel_id = str(payload.get("channel_id") or "").strip()
220
230
  if not channel_id:
221
231
  return None
@@ -232,12 +242,22 @@ class SlackSocketModeService:
232
242
  "sender_id": sender_id,
233
243
  "sender_name": str(payload.get("user_name") or sender_id).strip(),
234
244
  "message_id": str(payload.get("trigger_id") or "").strip(),
235
- "conversation_id": f"slack:{chat_type}:{channel_id}",
245
+ "conversation_id": self._conversation_id(chat_type, channel_id),
246
+ "profile_id": self.profile_id,
247
+ "profile_label": self.profile_label,
236
248
  "text": combined,
237
249
  "mentioned": True,
238
250
  "raw_event": payload,
239
251
  }
240
252
 
253
+ def _conversation_id(self, chat_type: str, chat_id: str) -> str:
254
+ return format_conversation_id(
255
+ "slack",
256
+ chat_type,
257
+ chat_id,
258
+ profile_id=self.profile_id if self._encode_profile_id else None,
259
+ )
260
+
241
261
  @staticmethod
242
262
  def _infer_channel_type(channel_id: str) -> str:
243
263
  if str(channel_id).startswith("D"):
@@ -321,6 +341,10 @@ class SlackSocketModeService:
321
341
  state = read_json(self._runtime_path, {}) or {}
322
342
  if not isinstance(state, dict):
323
343
  state = {}
344
+ if self.profile_id:
345
+ state["profile_id"] = self.profile_id
346
+ if self.profile_label:
347
+ state["profile_label"] = self.profile_label
324
348
  state.update(patch)
325
349
  write_json(self._runtime_path, state)
326
350
 
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import Any, Callable
7
7
  from urllib.request import Request, urlopen
8
8
 
9
+ from ..connector_runtime import format_conversation_id
9
10
  from ..shared import read_json, utc_now, write_json
10
11
 
11
12
 
@@ -17,14 +18,22 @@ class TelegramPollingService:
17
18
  config: dict[str, Any],
18
19
  on_event: Callable[[dict[str, Any]], None],
19
20
  log: Callable[[str, str], None] | None = None,
21
+ profile_id: str | None = None,
22
+ profile_label: str | None = None,
23
+ encode_profile_id: bool = False,
20
24
  ) -> None:
21
25
  self.home = home
22
26
  self.config = config
23
27
  self.on_event = on_event
24
28
  self.log = log or self._default_log
29
+ self.profile_id = str(profile_id or "").strip() or None
30
+ self.profile_label = str(profile_label or "").strip() or None
31
+ self._encode_profile_id = bool(encode_profile_id and self.profile_id)
25
32
  self._thread: threading.Thread | None = None
26
33
  self._stop_event = threading.Event()
27
34
  self._root = home / "logs" / "connectors" / "telegram"
35
+ if self.profile_id:
36
+ self._root = self._root / "profiles" / self.profile_id
28
37
  self._runtime_path = self._root / "runtime.json"
29
38
 
30
39
  def start(self) -> bool:
@@ -59,7 +68,7 @@ class TelegramPollingService:
59
68
  self._thread = threading.Thread(
60
69
  target=self._run,
61
70
  daemon=True,
62
- name="deepscientist-telegram-polling",
71
+ name=f"deepscientist-telegram-polling-{self.profile_id or 'default'}",
63
72
  )
64
73
  self._thread.start()
65
74
  return True
@@ -219,12 +228,22 @@ class TelegramPollingService:
219
228
  "sender_id": sender_id,
220
229
  "sender_name": sender_name,
221
230
  "message_id": str(message.get("message_id") or "").strip(),
222
- "conversation_id": f"telegram:{chat_type}:{chat_id}",
231
+ "conversation_id": self._conversation_id(chat_type, chat_id),
232
+ "profile_id": self.profile_id,
233
+ "profile_label": self.profile_label,
223
234
  "text": normalized_text,
224
235
  "mentioned": mentioned,
225
236
  "raw_event": update,
226
237
  }
227
238
 
239
+ def _conversation_id(self, chat_type: str, chat_id: str) -> str:
240
+ return format_conversation_id(
241
+ "telegram",
242
+ chat_type,
243
+ chat_id,
244
+ profile_id=self.profile_id if self._encode_profile_id else None,
245
+ )
246
+
228
247
  @staticmethod
229
248
  def _normalize_command_target(text: str, *, bot_name: str) -> str:
230
249
  cleaned = str(text or "").strip()
@@ -272,6 +291,10 @@ class TelegramPollingService:
272
291
  state = read_json(self._runtime_path, {}) or {}
273
292
  if not isinstance(state, dict):
274
293
  state = {}
294
+ if self.profile_id:
295
+ state["profile_id"] = self.profile_id
296
+ if self.profile_label:
297
+ state["profile_label"] = self.profile_label
275
298
  state.update(patch)
276
299
  write_json(self._runtime_path, state)
277
300
 
@@ -5,6 +5,7 @@ import threading
5
5
  from pathlib import Path
6
6
  from typing import Any, Callable
7
7
 
8
+ from ..connector_runtime import format_conversation_id, parse_conversation_id
8
9
  from ..shared import append_jsonl, ensure_dir, read_json, utc_now, write_json
9
10
 
10
11
 
@@ -16,14 +17,22 @@ class WhatsAppLocalSessionService:
16
17
  config: dict[str, Any],
17
18
  on_event: Callable[[dict[str, Any]], None],
18
19
  log: Callable[[str, str], None] | None = None,
20
+ profile_id: str | None = None,
21
+ profile_label: str | None = None,
22
+ encode_profile_id: bool = False,
19
23
  ) -> None:
20
24
  self.home = home
21
25
  self.config = config
22
26
  self.on_event = on_event
23
27
  self.log = log or self._default_log
28
+ self.profile_id = str(profile_id or "").strip() or None
29
+ self.profile_label = str(profile_label or "").strip() or None
30
+ self._encode_profile_id = bool(encode_profile_id and self.profile_id)
24
31
  self._thread: threading.Thread | None = None
25
32
  self._stop_event = threading.Event()
26
33
  self._root = home / "logs" / "connectors" / "whatsapp"
34
+ if self.profile_id:
35
+ self._root = self._root / "profiles" / self.profile_id
27
36
  self._runtime_path = self._root / "runtime.json"
28
37
  self._cursor_path = self._root / "local_session.cursor.json"
29
38
 
@@ -60,7 +69,7 @@ class WhatsAppLocalSessionService:
60
69
  self._thread = threading.Thread(
61
70
  target=self._run,
62
71
  daemon=True,
63
- name="deepscientist-whatsapp-local-session",
72
+ name=f"deepscientist-whatsapp-local-session-{self.profile_id or 'default'}",
64
73
  )
65
74
  self._thread.start()
66
75
  return True
@@ -165,11 +174,16 @@ class WhatsAppLocalSessionService:
165
174
  )
166
175
  write_json(self._cursor_path, {"offset": offset})
167
176
 
168
- @staticmethod
169
- def _normalize_entry(payload: dict[str, Any]) -> dict[str, Any] | None:
177
+ def _normalize_entry(self, payload: dict[str, Any]) -> dict[str, Any] | None:
170
178
  if isinstance(payload.get("normalized"), dict):
171
179
  normalized = dict(payload["normalized"])
172
180
  normalized.setdefault("raw_event", payload)
181
+ parsed = parse_conversation_id(normalized.get("conversation_id"))
182
+ chat_type = str((parsed or {}).get("chat_type") or normalized.get("chat_type") or "direct").strip().lower() or "direct"
183
+ chat_id = str((parsed or {}).get("chat_id") or normalized.get("group_id") or normalized.get("direct_id") or "").strip() or "unknown"
184
+ normalized["conversation_id"] = self._conversation_id(chat_type, chat_id)
185
+ normalized["profile_id"] = self.profile_id
186
+ normalized["profile_label"] = self.profile_label
173
187
  return normalized
174
188
  conversation_id = str(payload.get("conversation_id") or "").strip()
175
189
  chat_type = str(payload.get("chat_type") or "").strip().lower()
@@ -201,12 +215,22 @@ class WhatsAppLocalSessionService:
201
215
  "sender_id": sender_id or chat_id,
202
216
  "sender_name": sender_name or sender_id or chat_id,
203
217
  "message_id": message_id,
204
- "conversation_id": conversation_id or f"whatsapp:{chat_type}:{chat_id}",
218
+ "conversation_id": self._conversation_id(chat_type, chat_id),
219
+ "profile_id": self.profile_id,
220
+ "profile_label": self.profile_label,
205
221
  "text": text,
206
222
  "mentioned": False,
207
223
  "raw_event": payload,
208
224
  }
209
225
 
226
+ def _conversation_id(self, chat_type: str, chat_id: str) -> str:
227
+ return format_conversation_id(
228
+ "whatsapp",
229
+ chat_type,
230
+ chat_id,
231
+ profile_id=self.profile_id if self._encode_profile_id else None,
232
+ )
233
+
210
234
  def _session_dir(self) -> Path | None:
211
235
  raw = str(self.config.get("session_dir") or "").strip()
212
236
  if not raw:
@@ -217,6 +241,10 @@ class WhatsAppLocalSessionService:
217
241
  state = read_json(self._runtime_path, {}) or {}
218
242
  if not isinstance(state, dict):
219
243
  state = {}
244
+ if self.profile_id:
245
+ state["profile_id"] = self.profile_id
246
+ if self.profile_label:
247
+ state["profile_label"] = self.profile_label
220
248
  state.update(patch)
221
249
  write_json(self._runtime_path, state)
222
250
 
@@ -17,6 +17,7 @@ from .daemon import DaemonApp
17
17
  from .doctor import render_doctor_report, run_doctor
18
18
  from .home import default_home, ensure_home_layout, repo_root
19
19
  from .memory import MemoryService
20
+ from .migration import migrate_deepscientist_root
20
21
  from .prompts import PromptBuilder
21
22
  from .quest import QuestService
22
23
  from .registries import BaselineRegistry
@@ -109,6 +110,9 @@ def build_parser() -> argparse.ArgumentParser:
109
110
  config_edit.add_argument("name", choices=("config", "runners", "connectors", "plugins", "mcp_servers"))
110
111
  config_subparsers.add_parser("validate")
111
112
 
113
+ migrate_parser = subparsers.add_parser("migrate")
114
+ migrate_parser.add_argument("target")
115
+
112
116
  return parser
113
117
 
114
118
 
@@ -445,6 +449,27 @@ def config_validate_command(home: Path) -> int:
445
449
  return 0
446
450
 
447
451
 
452
+ def migrate_command(home: Path, target: str) -> int:
453
+ try:
454
+ payload = migrate_deepscientist_root(home, Path(target))
455
+ except ValueError as exc:
456
+ print(
457
+ json.dumps(
458
+ {
459
+ "ok": False,
460
+ "source": str(home.expanduser().resolve()),
461
+ "target": str(Path(target).expanduser().resolve()),
462
+ "message": str(exc),
463
+ },
464
+ ensure_ascii=False,
465
+ indent=2,
466
+ )
467
+ )
468
+ return 1
469
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
470
+ return 0
471
+
472
+
448
473
  def main(argv: list[str] | None = None) -> int:
449
474
  parser = build_parser()
450
475
  args = parser.parse_args(argv)
@@ -492,6 +517,8 @@ def main(argv: list[str] | None = None) -> int:
492
517
  return config_edit_command(home, args.name)
493
518
  if args.command == "config" and args.config_command == "validate":
494
519
  return config_validate_command(home)
520
+ if args.command == "migrate":
521
+ return migrate_command(home, args.target)
495
522
  parser.error(f"Unknown command: {args.command}")
496
523
  return 1
497
524
 
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass
4
4
  from pathlib import Path
5
5
 
6
-
7
6
  CONFIG_NAMES = ("config", "runners", "connectors", "plugins", "mcp_servers")
8
7
  REQUIRED_CONFIG_NAMES = ("config", "runners", "connectors")
9
8
  OPTIONAL_CONFIG_NAMES = ("plugins", "mcp_servers")
@@ -122,6 +121,7 @@ def default_connectors() -> dict:
122
121
  "qq": {
123
122
  "enabled": False,
124
123
  "transport": "gateway_direct",
124
+ "profiles": [],
125
125
  "app_id": None,
126
126
  "app_secret": None,
127
127
  "app_secret_env": "QQ_APP_SECRET",
@@ -140,17 +140,12 @@ def default_connectors() -> dict:
140
140
  },
141
141
  "telegram": {
142
142
  "enabled": False,
143
+ "profiles": [],
143
144
  "transport": "polling",
144
- "mode": "relay",
145
145
  "bot_name": "DeepScientist",
146
146
  "command_prefix": "/",
147
147
  "bot_token": None,
148
148
  "bot_token_env": "TELEGRAM_BOT_TOKEN",
149
- "webhook_secret": None,
150
- "webhook_secret_env": "TELEGRAM_WEBHOOK_SECRET",
151
- "public_callback_url": None,
152
- "relay_url": None,
153
- "relay_auth_token": None,
154
149
  "dm_policy": "pairing",
155
150
  "allow_from": [],
156
151
  "group_policy": "open",
@@ -161,18 +156,13 @@ def default_connectors() -> dict:
161
156
  },
162
157
  "discord": {
163
158
  "enabled": False,
159
+ "profiles": [],
164
160
  "transport": "gateway",
165
- "mode": "relay",
166
161
  "bot_name": "DeepScientist",
167
162
  "command_prefix": "/",
168
163
  "bot_token": None,
169
164
  "bot_token_env": "DISCORD_BOT_TOKEN",
170
165
  "application_id": None,
171
- "public_key": None,
172
- "public_key_env": "DISCORD_PUBLIC_KEY",
173
- "public_interactions_url": None,
174
- "relay_url": None,
175
- "relay_auth_token": None,
176
166
  "dm_policy": "pairing",
177
167
  "allow_from": [],
178
168
  "group_policy": "open",
@@ -184,8 +174,8 @@ def default_connectors() -> dict:
184
174
  },
185
175
  "slack": {
186
176
  "enabled": False,
177
+ "profiles": [],
187
178
  "transport": "socket_mode",
188
- "mode": "relay",
189
179
  "bot_name": "DeepScientist",
190
180
  "command_prefix": "/",
191
181
  "bot_token": None,
@@ -193,11 +183,6 @@ def default_connectors() -> dict:
193
183
  "bot_user_id": None,
194
184
  "app_token": None,
195
185
  "app_token_env": "SLACK_APP_TOKEN",
196
- "signing_secret": None,
197
- "signing_secret_env": "SLACK_SIGNING_SECRET",
198
- "public_callback_url": None,
199
- "relay_url": None,
200
- "relay_auth_token": None,
201
186
  "dm_policy": "pairing",
202
187
  "allow_from": [],
203
188
  "group_policy": "open",
@@ -208,21 +193,14 @@ def default_connectors() -> dict:
208
193
  },
209
194
  "feishu": {
210
195
  "enabled": False,
196
+ "profiles": [],
211
197
  "transport": "long_connection",
212
- "mode": "relay",
213
198
  "bot_name": "DeepScientist",
214
199
  "command_prefix": "/",
215
200
  "app_id": None,
216
201
  "app_secret": None,
217
202
  "app_secret_env": "FEISHU_APP_SECRET",
218
- "verification_token": None,
219
- "verification_token_env": "FEISHU_VERIFICATION_TOKEN",
220
- "encrypt_key": None,
221
- "encrypt_key_env": "FEISHU_ENCRYPT_KEY",
222
203
  "api_base_url": "https://open.feishu.cn",
223
- "public_callback_url": None,
224
- "relay_url": None,
225
- "relay_auth_token": None,
226
204
  "dm_policy": "pairing",
227
205
  "allow_from": [],
228
206
  "group_policy": "open",
@@ -233,24 +211,12 @@ def default_connectors() -> dict:
233
211
  },
234
212
  "whatsapp": {
235
213
  "enabled": False,
214
+ "profiles": [],
236
215
  "transport": "local_session",
237
- "mode": "relay",
238
216
  "bot_name": "DeepScientist",
239
217
  "command_prefix": "/",
240
218
  "auth_method": "qr_browser",
241
219
  "session_dir": "~/.deepscientist/connectors/whatsapp",
242
- "provider": "relay",
243
- "access_token": None,
244
- "access_token_env": "WHATSAPP_ACCESS_TOKEN",
245
- "phone_number_id": None,
246
- "business_account_id": None,
247
- "verify_token": None,
248
- "verify_token_env": "WHATSAPP_VERIFY_TOKEN",
249
- "api_base_url": "https://graph.facebook.com",
250
- "api_version": "v21.0",
251
- "public_callback_url": None,
252
- "relay_url": None,
253
- "relay_auth_token": None,
254
220
  "dm_policy": "pairing",
255
221
  "allow_from": [],
256
222
  "group_policy": "allowlist",