@researai/deepscientist 1.5.6 → 1.5.8
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/README.md +32 -0
- package/bin/ds.js +274 -18
- package/docs/en/07_MEMORY_AND_MCP.md +40 -3
- package/docs/en/99_ACKNOWLEDGEMENTS.md +1 -0
- package/docs/zh/07_MEMORY_AND_MCP.md +40 -3
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +1 -0
- package/install.sh +34 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +1 -0
- package/src/deepscientist/artifact/metrics.py +813 -80
- package/src/deepscientist/artifact/schemas.py +1 -0
- package/src/deepscientist/artifact/service.py +1101 -99
- package/src/deepscientist/bash_exec/monitor.py +1 -1
- package/src/deepscientist/bash_exec/service.py +17 -9
- package/src/deepscientist/channels/qq.py +17 -0
- package/src/deepscientist/channels/relay.py +16 -0
- package/src/deepscientist/cli.py +1 -1
- package/src/deepscientist/config/models.py +12 -6
- package/src/deepscientist/config/service.py +75 -2
- package/src/deepscientist/connector_profiles.py +34 -6
- package/src/deepscientist/daemon/api/handlers.py +290 -15
- package/src/deepscientist/daemon/api/router.py +2 -0
- package/src/deepscientist/daemon/app.py +521 -23
- package/src/deepscientist/gitops/diff.py +6 -10
- package/src/deepscientist/mcp/server.py +188 -39
- package/src/deepscientist/prompts/builder.py +71 -22
- package/src/deepscientist/qq_profiles.py +19 -9
- package/src/deepscientist/quest/layout.py +1 -0
- package/src/deepscientist/quest/service.py +83 -34
- package/src/deepscientist/quest/stage_views.py +74 -29
- package/src/deepscientist/runners/codex.py +32 -14
- package/src/deepscientist/runners/runtime_overrides.py +46 -0
- package/src/deepscientist/skills/installer.py +7 -0
- package/src/prompts/connectors/qq.md +1 -1
- package/src/prompts/contracts/shared_interaction.md +14 -0
- package/src/prompts/system.md +134 -30
- package/src/skills/analysis-campaign/SKILL.md +34 -8
- package/src/skills/analysis-campaign/references/campaign-checklist-template.md +41 -0
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +68 -0
- package/src/skills/baseline/SKILL.md +145 -32
- package/src/skills/baseline/references/baseline-checklist-template.md +57 -0
- package/src/skills/baseline/references/baseline-plan-template.md +105 -0
- package/src/skills/decision/SKILL.md +12 -8
- package/src/skills/experiment/SKILL.md +51 -9
- package/src/skills/experiment/references/main-experiment-checklist-template.md +52 -0
- package/src/skills/experiment/references/main-experiment-plan-template.md +79 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +3 -8
- package/src/skills/idea/SKILL.md +2 -8
- package/src/skills/intake-audit/SKILL.md +2 -8
- package/src/skills/rebuttal/SKILL.md +2 -8
- package/src/skills/review/SKILL.md +2 -8
- package/src/skills/scout/SKILL.md +2 -8
- package/src/skills/write/SKILL.md +52 -16
- package/src/skills/write/templates/DEEPSCIENTIST_NOTES.md +21 -0
- package/src/skills/write/templates/README.md +408 -0
- package/src/skills/write/templates/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/write/templates/aaai2026/README.md +534 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bib +111 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bst +1493 -0
- package/src/skills/write/templates/aaai2026/aaai2026.sty +315 -0
- package/src/skills/write/templates/acl/README.md +50 -0
- package/src/skills/write/templates/acl/acl.sty +312 -0
- package/src/skills/write/templates/acl/acl_latex.tex +377 -0
- package/src/skills/write/templates/acl/acl_lualatex.tex +101 -0
- package/src/skills/write/templates/acl/acl_natbib.bst +1940 -0
- package/src/skills/write/templates/acl/anthology.bib.txt +26 -0
- package/src/skills/write/templates/acl/custom.bib +70 -0
- package/src/skills/write/templates/acl/formatting.md +326 -0
- package/src/skills/write/templates/asplos2027/main.tex +459 -0
- package/src/skills/write/templates/asplos2027/references.bib +135 -0
- package/src/skills/write/templates/colm2025/README.md +3 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bib +11 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bst +1440 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.sty +218 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.tex +305 -0
- package/src/skills/write/templates/colm2025/fancyhdr.sty +485 -0
- package/src/skills/write/templates/colm2025/math_commands.tex +508 -0
- package/src/skills/write/templates/colm2025/natbib.sty +1246 -0
- package/src/skills/write/templates/iclr2026/fancyhdr.sty +485 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/src/skills/write/templates/iclr2026/math_commands.tex +508 -0
- package/src/skills/write/templates/iclr2026/natbib.sty +1246 -0
- package/src/skills/write/templates/icml2026/algorithm.sty +79 -0
- package/src/skills/write/templates/icml2026/algorithmic.sty +201 -0
- package/src/skills/write/templates/icml2026/example_paper.bib +75 -0
- package/src/skills/write/templates/icml2026/example_paper.tex +662 -0
- package/src/skills/write/templates/icml2026/fancyhdr.sty +864 -0
- package/src/skills/write/templates/icml2026/icml2026.bst +1443 -0
- package/src/skills/write/templates/icml2026/icml2026.sty +767 -0
- package/src/skills/write/templates/neurips2025/Makefile +36 -0
- package/src/skills/write/templates/neurips2025/extra_pkgs.tex +53 -0
- package/src/skills/write/templates/neurips2025/main.tex +38 -0
- package/src/skills/write/templates/neurips2025/neurips.sty +382 -0
- package/src/skills/write/templates/nsdi2027/main.tex +426 -0
- package/src/skills/write/templates/nsdi2027/references.bib +151 -0
- package/src/skills/write/templates/nsdi2027/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/osdi2026/main.tex +429 -0
- package/src/skills/write/templates/osdi2026/references.bib +150 -0
- package/src/skills/write/templates/osdi2026/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/sosp2026/main.tex +532 -0
- package/src/skills/write/templates/sosp2026/references.bib +148 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BGLArZRn.js → AiManusChatView-m2FNtwbn.js} +110 -14
- package/src/ui/dist/assets/{AnalysisPlugin-BgDGSigG.js → AnalysisPlugin-BMTF8EGL.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-B65HD7L4.js → AutoFigurePlugin-DxPdMUNb.js} +5 -5
- package/src/ui/dist/assets/{CliPlugin-CUqgsFHC.js → CliPlugin-BEOWgxCI.js} +9 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-CF5EdvaS.js → CodeEditorPlugin-BCXvjqmb.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DEeU063D.js → CodeViewerPlugin-DaJcy3nD.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-Df-FuDlZ.js → DocViewerPlugin-ByfeIq4K.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-RAnNaRxM.js → GitDiffViewerPlugin-Cksf3VZ-.js} +830 -86
- package/src/ui/dist/assets/{ImageViewerPlugin-DXJ0ZJGg.js → ImageViewerPlugin-CFz-OsTS.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BlO-sKsj.js → LabCopilotPanel-CJ1cJzoX.js} +10 -10
- package/src/ui/dist/assets/{LabPlugin-BajPZW5v.js → LabPlugin-BF3dVJwa.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-F1OEol8D.js → LatexPlugin-DDkwZ6Sj.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-MhUupqwT.js → MarkdownViewerPlugin-HAuvurcT.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DxhIEsv0.js → MarketplacePlugin-BtoTYy2C.js} +3 -3
- package/src/ui/dist/assets/{index-B-2scqCJ.js → NotebookEditor-CSJYx7b-.js} +12 -155
- package/src/ui/dist/assets/{NotebookEditor-q7TkhewC.js → NotebookEditor-DQgRezm_.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-B8ZOTKFc.js → PdfLoader-DPa_-fv6.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-xFPvzvWh.js → PdfMarkdownPlugin-BZpXOEjm.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-EjEcsIB8.js → PdfViewerPlugin-BT8a6wGR.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-ixY-1lgW.js → SearchPlugin-D_blveZi.js} +1 -1
- package/src/ui/dist/assets/{Stepper-gYFK2Pgz.js → Stepper-DH2k75Vo.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-Cym6pv_n.js → TextViewerPlugin-Btx0M3hX.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-BPmIHcmK.js → VNCViewer-DImJO4rO.js} +9 -9
- package/src/ui/dist/assets/{bibtex-Btv6Wi7f.js → bibtex-B-Hqu0Sg.js} +1 -1
- package/src/ui/dist/assets/{code-BlG7g85c.js → code-BUfXGJSl.js} +1 -1
- package/src/ui/dist/assets/{file-content-DBT5OfTZ.js → file-content-VqamwI3X.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-BWXYzqHk.js → file-diff-panel-C_wOoS7a.js} +1 -1
- package/src/ui/dist/assets/{file-socket-wDlx6byM.js → file-socket-D2bTuMVP.js} +1 -1
- package/src/ui/dist/assets/{file-utils-Ba3nJmH0.js → file-utils--zJCPN1i.js} +1 -1
- package/src/ui/dist/assets/{image-BwtCyguk.js → image-BZkGJ4mM.js} +1 -1
- package/src/ui/dist/assets/{index-CfRpE209.js → index-CxkvSeKw.js} +2 -2
- package/src/ui/dist/assets/{index-DcqvKzeJ.js → index-D9QIGcmc.js} +1 -1
- package/src/ui/dist/assets/{index-DpMZw8aM.css → index-DXZ1daiJ.css} +163 -34
- package/src/ui/dist/assets/index-DdRW6RMJ.js +159 -0
- package/src/ui/dist/assets/{index-Bz5AaWL7.js → index-DjggJovS.js} +2948 -1565
- package/src/ui/dist/assets/{message-square-BnlyWVH0.js → message-square-FUIPIhU2.js} +1 -1
- package/src/ui/dist/assets/{monaco-CXe0pAVe.js → monaco-DHMc7kKM.js} +1 -1
- package/src/ui/dist/assets/{popover-BCHmVhHj.js → popover-B85oCgCS.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Brk6kaOD.js → project-sync-DOMCcPac.js} +1 -1
- package/src/ui/dist/assets/{sigma-D72eSUep.js → sigma-BO2rQrl3.js} +1 -1
- package/src/ui/dist/assets/{tooltip-BMWd0dqX.js → tooltip-B1OspAkx.js} +1 -1
- package/src/ui/dist/assets/{trash-BIt_eWIS.js → trash-BsVEH_dV.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-N1hkTRrR.js → useCliAccess-b8L6JuZm.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-DPRPv6rv.js → useFileDiffOverlay-BY7uA9hV.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-E5-UheyP.js → wrap-text-BwyVuUIK.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-D4TR-ZZ_.js → zoom-out-RDpLugQP.js} +1 -1
- package/src/ui/dist/index.html +5 -2
- /package/src/ui/dist/assets/{index-CccQYZjX.css → NotebookEditor-CccQYZjX.css} +0 -0
|
@@ -448,7 +448,7 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
448
448
|
_terminate_process(process, process_group_id)
|
|
449
449
|
stop_requested = True
|
|
450
450
|
|
|
451
|
-
if
|
|
451
|
+
if output_fd is not None and process.poll() is None:
|
|
452
452
|
cursor_payload = read_json(input_cursor_path, {}) or {}
|
|
453
453
|
offset = int(cursor_payload.get("offset") or 0)
|
|
454
454
|
input_entries = read_jsonl(input_path)
|
|
@@ -555,6 +555,7 @@ class BashExecService:
|
|
|
555
555
|
before_seq: int | None = None,
|
|
556
556
|
after_seq: int | None = None,
|
|
557
557
|
order: str = "asc",
|
|
558
|
+
prefer_visible: bool = False,
|
|
558
559
|
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
559
560
|
if not self.meta_path(quest_root, bash_id).exists():
|
|
560
561
|
raise FileNotFoundError(f"Unknown bash session `{bash_id}`.")
|
|
@@ -579,9 +580,16 @@ class BashExecService:
|
|
|
579
580
|
entries = [entry for entry in entries if int(entry.get("seq") or 0) > normalized_after]
|
|
580
581
|
if normalized_before is not None:
|
|
581
582
|
entries = [entry for entry in entries if int(entry.get("seq") or 0) < normalized_before]
|
|
583
|
+
selection_pool = entries
|
|
584
|
+
if prefer_visible:
|
|
585
|
+
visible_entries = [
|
|
586
|
+
entry for entry in entries if str(entry.get("stream") or "") not in {"system", "prompt"}
|
|
587
|
+
]
|
|
588
|
+
if visible_entries:
|
|
589
|
+
selection_pool = visible_entries
|
|
582
590
|
normalized_limit = max(1, limit)
|
|
583
|
-
truncated = len(
|
|
584
|
-
selected =
|
|
591
|
+
truncated = len(selection_pool) > normalized_limit
|
|
592
|
+
selected = selection_pool[-normalized_limit:]
|
|
585
593
|
if order == "desc":
|
|
586
594
|
selected = list(reversed(selected))
|
|
587
595
|
tail_start_seq = int(selected[0].get("seq") or 0) if selected else None
|
|
@@ -1025,15 +1033,14 @@ class BashExecService:
|
|
|
1025
1033
|
if not normalized_data:
|
|
1026
1034
|
raise ValueError("terminal_input_required")
|
|
1027
1035
|
session = self.reconcile_session(quest_root, bash_id)
|
|
1028
|
-
if _normalize_string(session.get("kind")).lower() != "terminal":
|
|
1029
|
-
raise ValueError("not_terminal_session")
|
|
1030
1036
|
status = _normalize_string(session.get("status")).lower()
|
|
1031
1037
|
if status in TERMINAL_STATUSES:
|
|
1032
1038
|
raise ValueError("terminal_session_inactive")
|
|
1033
1039
|
runtime = self._terminal_runtime_manager.get_runtime(quest_root, bash_id)
|
|
1034
|
-
if runtime is None:
|
|
1040
|
+
if runtime is None and _normalize_string(session.get("kind")).lower() == "terminal":
|
|
1035
1041
|
raise ValueError("terminal_runtime_inactive")
|
|
1036
|
-
runtime
|
|
1042
|
+
if runtime is not None:
|
|
1043
|
+
runtime.write_input(normalized_data)
|
|
1037
1044
|
|
|
1038
1045
|
entry = {
|
|
1039
1046
|
"input_id": generate_id("tin"),
|
|
@@ -1081,9 +1088,10 @@ class BashExecService:
|
|
|
1081
1088
|
return True
|
|
1082
1089
|
|
|
1083
1090
|
def issue_terminal_attach_token(self, quest_root: Path, bash_id: str, *, ttl_seconds: int = 60) -> dict[str, Any]:
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1091
|
+
session = self.reconcile_session(quest_root, bash_id)
|
|
1092
|
+
status = _normalize_string(session.get("status")).lower()
|
|
1093
|
+
if status in TERMINAL_STATUSES:
|
|
1094
|
+
raise ValueError("terminal_session_inactive")
|
|
1087
1095
|
token = self._terminal_runtime_manager.issue_attach_token(
|
|
1088
1096
|
quest_root,
|
|
1089
1097
|
bash_id,
|
|
@@ -293,6 +293,20 @@ class QQRelayChannel(BaseChannel):
|
|
|
293
293
|
def matches_profile(item: dict[str, Any], profile_id: str) -> bool:
|
|
294
294
|
item_profile_id = str(item.get("profile_id") or "").strip()
|
|
295
295
|
return item_profile_id == profile_id or (not item_profile_id and len(profiles) == 1)
|
|
296
|
+
|
|
297
|
+
def count_profile_records(path: Path, profile_id: str) -> int:
|
|
298
|
+
total = 0
|
|
299
|
+
for raw in read_jsonl(path):
|
|
300
|
+
if not isinstance(raw, dict):
|
|
301
|
+
continue
|
|
302
|
+
record_profile_id = str(raw.get("profile_id") or "").strip()
|
|
303
|
+
if not record_profile_id:
|
|
304
|
+
parsed = parse_conversation_id(str(raw.get("conversation_id") or "").strip())
|
|
305
|
+
record_profile_id = str((parsed or {}).get("profile_id") or "").strip()
|
|
306
|
+
if record_profile_id == profile_id or (not record_profile_id and len(profiles) == 1):
|
|
307
|
+
total += 1
|
|
308
|
+
return total
|
|
309
|
+
|
|
296
310
|
profile_snapshots = []
|
|
297
311
|
for profile in profiles:
|
|
298
312
|
profile_id = str(profile.get("profile_id") or "").strip()
|
|
@@ -342,6 +356,9 @@ class QQRelayChannel(BaseChannel):
|
|
|
342
356
|
"discovered_targets": profile_targets,
|
|
343
357
|
"recent_conversations": profile_recent_conversations,
|
|
344
358
|
"bindings": profile_bindings,
|
|
359
|
+
"inbox_count": count_profile_records(self.inbox_path, profile_id),
|
|
360
|
+
"outbox_count": count_profile_records(self.outbox_path, profile_id),
|
|
361
|
+
"ignored_count": count_profile_records(self.ignored_path, profile_id),
|
|
345
362
|
"target_count": len(profile_targets),
|
|
346
363
|
"binding_count": len(profile_bindings),
|
|
347
364
|
"last_error": gateway_state.get("last_error") if isinstance(gateway_state, dict) else None,
|
|
@@ -282,6 +282,19 @@ class GenericRelayChannel(BaseChannel):
|
|
|
282
282
|
item_profile_id = str(item.get("profile_id") or "").strip()
|
|
283
283
|
return item_profile_id == profile_id or (not item_profile_id and len(profiles) == 1)
|
|
284
284
|
|
|
285
|
+
def count_profile_records(path: Path, profile_id: str) -> int:
|
|
286
|
+
total = 0
|
|
287
|
+
for raw in read_jsonl(path):
|
|
288
|
+
if not isinstance(raw, dict):
|
|
289
|
+
continue
|
|
290
|
+
record_profile_id = str(raw.get("profile_id") or "").strip()
|
|
291
|
+
if not record_profile_id:
|
|
292
|
+
parsed = parse_conversation_id(str(raw.get("conversation_id") or "").strip())
|
|
293
|
+
record_profile_id = str((parsed or {}).get("profile_id") or "").strip()
|
|
294
|
+
if record_profile_id == profile_id or (not record_profile_id and len(profiles) == 1):
|
|
295
|
+
total += 1
|
|
296
|
+
return total
|
|
297
|
+
|
|
285
298
|
profile_snapshots = []
|
|
286
299
|
for profile in profiles:
|
|
287
300
|
profile_id = str(profile.get("profile_id") or "").strip()
|
|
@@ -322,6 +335,9 @@ class GenericRelayChannel(BaseChannel):
|
|
|
322
335
|
"discovered_targets": profile_targets,
|
|
323
336
|
"recent_conversations": profile_recent_conversations,
|
|
324
337
|
"bindings": profile_bindings,
|
|
338
|
+
"inbox_count": count_profile_records(self.inbox_path, profile_id),
|
|
339
|
+
"outbox_count": count_profile_records(self.outbox_path, profile_id),
|
|
340
|
+
"ignored_count": count_profile_records(self.ignored_path, profile_id),
|
|
325
341
|
"target_count": len(profile_targets),
|
|
326
342
|
"binding_count": len(profile_bindings),
|
|
327
343
|
"last_error": profile_runtime_state.get("last_error") if isinstance(profile_runtime_state, dict) else None,
|
package/src/deepscientist/cli.py
CHANGED
|
@@ -236,7 +236,7 @@ def run_command(home: Path, quest_id: str, skill_id: str, message: str, model: s
|
|
|
236
236
|
config_manager = ConfigManager(home)
|
|
237
237
|
config_manager.ensure_files()
|
|
238
238
|
config = config_manager.load_named("config")
|
|
239
|
-
runners = config_manager.
|
|
239
|
+
runners = config_manager.load_runners_config()
|
|
240
240
|
quest_root = home / "quests" / quest_id
|
|
241
241
|
codex_cfg = runners.get("codex", {})
|
|
242
242
|
logger = JsonlLogger(home / "logs", level=config.get("logging", {}).get("level", "info"))
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
CONFIG_NAMES = ("config", "runners", "connectors", "plugins", "mcp_servers")
|
|
7
7
|
REQUIRED_CONFIG_NAMES = ("config", "runners", "connectors")
|
|
8
8
|
OPTIONAL_CONFIG_NAMES = ("plugins", "mcp_servers")
|
|
9
|
+
SYSTEM_CONNECTOR_NAMES = ("qq", "telegram", "discord", "slack", "feishu", "whatsapp", "lingzhu")
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclass(frozen=True)
|
|
@@ -20,6 +21,10 @@ def config_filename(name: str) -> str:
|
|
|
20
21
|
return f"{name}.yaml"
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
def default_system_enabled_connectors() -> dict[str, bool]:
|
|
25
|
+
return {name: name == "qq" for name in SYSTEM_CONNECTOR_NAMES}
|
|
26
|
+
|
|
27
|
+
|
|
23
28
|
def default_config(home: Path) -> dict:
|
|
24
29
|
return {
|
|
25
30
|
"home": str(home),
|
|
@@ -65,6 +70,7 @@ def default_config(home: Path) -> dict:
|
|
|
65
70
|
"auto_ack": True,
|
|
66
71
|
"milestone_push": True,
|
|
67
72
|
"direct_chat_enabled": True,
|
|
73
|
+
"system_enabled": default_system_enabled_connectors(),
|
|
68
74
|
},
|
|
69
75
|
"cloud": {
|
|
70
76
|
"enabled": False,
|
|
@@ -128,7 +134,7 @@ def default_connectors() -> dict:
|
|
|
128
134
|
"profiles": [],
|
|
129
135
|
"app_id": None,
|
|
130
136
|
"app_secret": None,
|
|
131
|
-
"app_secret_env":
|
|
137
|
+
"app_secret_env": None,
|
|
132
138
|
"bot_name": "DeepScientist",
|
|
133
139
|
"command_prefix": "/",
|
|
134
140
|
"main_chat_id": None,
|
|
@@ -149,7 +155,7 @@ def default_connectors() -> dict:
|
|
|
149
155
|
"bot_name": "DeepScientist",
|
|
150
156
|
"command_prefix": "/",
|
|
151
157
|
"bot_token": None,
|
|
152
|
-
"bot_token_env":
|
|
158
|
+
"bot_token_env": None,
|
|
153
159
|
"dm_policy": "pairing",
|
|
154
160
|
"allow_from": [],
|
|
155
161
|
"group_policy": "open",
|
|
@@ -165,7 +171,7 @@ def default_connectors() -> dict:
|
|
|
165
171
|
"bot_name": "DeepScientist",
|
|
166
172
|
"command_prefix": "/",
|
|
167
173
|
"bot_token": None,
|
|
168
|
-
"bot_token_env":
|
|
174
|
+
"bot_token_env": None,
|
|
169
175
|
"application_id": None,
|
|
170
176
|
"dm_policy": "pairing",
|
|
171
177
|
"allow_from": [],
|
|
@@ -183,10 +189,10 @@ def default_connectors() -> dict:
|
|
|
183
189
|
"bot_name": "DeepScientist",
|
|
184
190
|
"command_prefix": "/",
|
|
185
191
|
"bot_token": None,
|
|
186
|
-
"bot_token_env":
|
|
192
|
+
"bot_token_env": None,
|
|
187
193
|
"bot_user_id": None,
|
|
188
194
|
"app_token": None,
|
|
189
|
-
"app_token_env":
|
|
195
|
+
"app_token_env": None,
|
|
190
196
|
"dm_policy": "pairing",
|
|
191
197
|
"allow_from": [],
|
|
192
198
|
"group_policy": "open",
|
|
@@ -203,7 +209,7 @@ def default_connectors() -> dict:
|
|
|
203
209
|
"command_prefix": "/",
|
|
204
210
|
"app_id": None,
|
|
205
211
|
"app_secret": None,
|
|
206
|
-
"app_secret_env":
|
|
212
|
+
"app_secret_env": None,
|
|
207
213
|
"api_base_url": "https://open.feishu.cn",
|
|
208
214
|
"dm_policy": "pairing",
|
|
209
215
|
"allow_from": [],
|
|
@@ -30,12 +30,14 @@ from ..qq_profiles import (
|
|
|
30
30
|
qq_profile_label,
|
|
31
31
|
)
|
|
32
32
|
from ..network import urlopen_with_proxy as urlopen
|
|
33
|
+
from ..runners.runtime_overrides import apply_codex_runtime_overrides, apply_runners_runtime_overrides
|
|
33
34
|
from ..shared import read_json, read_text, read_yaml, resolve_runner_binary, run_command, sha256_text, utc_now, which, write_text, write_yaml
|
|
34
35
|
from .models import (
|
|
35
36
|
CONFIG_NAMES,
|
|
36
37
|
OPTIONAL_CONFIG_NAMES,
|
|
37
38
|
REQUIRED_CONFIG_NAMES,
|
|
38
39
|
ConfigFileInfo,
|
|
40
|
+
SYSTEM_CONNECTOR_NAMES,
|
|
39
41
|
config_filename,
|
|
40
42
|
default_payload,
|
|
41
43
|
)
|
|
@@ -91,6 +93,36 @@ class ConfigManager:
|
|
|
91
93
|
def load_named_normalized(self, name: str, create_optional: bool = False) -> dict:
|
|
92
94
|
return self._normalize_named_payload(name, self.load_named(name, create_optional=create_optional))
|
|
93
95
|
|
|
96
|
+
def load_runners_config(self) -> dict:
|
|
97
|
+
return apply_runners_runtime_overrides(self.load_named_normalized("runners"))
|
|
98
|
+
|
|
99
|
+
def load_runtime_config(self) -> dict:
|
|
100
|
+
return self.load_named_normalized("config")
|
|
101
|
+
|
|
102
|
+
def system_connector_gates(self) -> dict[str, bool]:
|
|
103
|
+
config = self.load_runtime_config()
|
|
104
|
+
connectors = config.get("connectors") if isinstance(config.get("connectors"), dict) else {}
|
|
105
|
+
system_enabled = connectors.get("system_enabled") if isinstance(connectors.get("system_enabled"), dict) else {}
|
|
106
|
+
return {
|
|
107
|
+
name: self._coerce_bool(system_enabled.get(name), default=name == "qq")
|
|
108
|
+
for name in SYSTEM_CONNECTOR_NAMES
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def system_enabled_connector_names(self) -> list[str]:
|
|
112
|
+
gates = self.system_connector_gates()
|
|
113
|
+
return [name for name in SYSTEM_CONNECTOR_NAMES if gates.get(name, False)]
|
|
114
|
+
|
|
115
|
+
def is_connector_system_enabled(self, name: str) -> bool:
|
|
116
|
+
normalized = str(name or "").strip().lower()
|
|
117
|
+
if not normalized:
|
|
118
|
+
return False
|
|
119
|
+
if normalized == "local":
|
|
120
|
+
return True
|
|
121
|
+
gates = self.system_connector_gates()
|
|
122
|
+
if normalized in gates:
|
|
123
|
+
return gates[normalized]
|
|
124
|
+
return True
|
|
125
|
+
|
|
94
126
|
def load_named_text(self, name: str, create_optional: bool = False) -> str:
|
|
95
127
|
path = self.path_for(name)
|
|
96
128
|
if create_optional and name in OPTIONAL_CONFIG_NAMES and not path.exists():
|
|
@@ -1039,9 +1071,16 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1039
1071
|
)
|
|
1040
1072
|
|
|
1041
1073
|
def _probe_codex_runner(self, config: dict) -> dict:
|
|
1074
|
+
config = apply_codex_runtime_overrides(config)
|
|
1042
1075
|
checked_at = utc_now()
|
|
1043
1076
|
binary = str(config.get("binary") or "codex").strip() or "codex"
|
|
1044
1077
|
resolved_binary = resolve_runner_binary(binary, runner_name="codex")
|
|
1078
|
+
raw_reasoning_effort = config.get("model_reasoning_effort")
|
|
1079
|
+
reasoning_effort = (
|
|
1080
|
+
str(raw_reasoning_effort).strip()
|
|
1081
|
+
if raw_reasoning_effort is not None and str(raw_reasoning_effort).strip()
|
|
1082
|
+
else ("xhigh" if raw_reasoning_effort is None else None)
|
|
1083
|
+
)
|
|
1045
1084
|
details: dict[str, object] = {
|
|
1046
1085
|
"binary": binary,
|
|
1047
1086
|
"resolved_binary": resolved_binary,
|
|
@@ -1049,7 +1088,7 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1049
1088
|
"model": str(config.get("model") or "gpt-5.4"),
|
|
1050
1089
|
"approval_policy": str(config.get("approval_policy") or "on-request"),
|
|
1051
1090
|
"sandbox_mode": str(config.get("sandbox_mode") or "workspace-write"),
|
|
1052
|
-
"reasoning_effort":
|
|
1091
|
+
"reasoning_effort": reasoning_effort,
|
|
1053
1092
|
"checked_at": checked_at,
|
|
1054
1093
|
}
|
|
1055
1094
|
if not resolved_binary:
|
|
@@ -1082,7 +1121,6 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1082
1121
|
approval_policy = str(config.get("approval_policy") or "on-request").strip()
|
|
1083
1122
|
if approval_policy:
|
|
1084
1123
|
command.extend(["-c", f'approval_policy="{approval_policy}"'])
|
|
1085
|
-
reasoning_effort = str(config.get("model_reasoning_effort") or "xhigh").strip()
|
|
1086
1124
|
if reasoning_effort:
|
|
1087
1125
|
command.extend(["-c", f'model_reasoning_effort="{reasoning_effort}"'])
|
|
1088
1126
|
sandbox_mode = str(config.get("sandbox_mode") or "workspace-write").strip()
|
|
@@ -1373,6 +1411,8 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1373
1411
|
normalized = self._deep_merge(defaults, payload)
|
|
1374
1412
|
bootstrap = normalized.get("bootstrap") if isinstance(normalized.get("bootstrap"), dict) else {}
|
|
1375
1413
|
raw_bootstrap = payload.get("bootstrap") if isinstance(payload.get("bootstrap"), dict) else {}
|
|
1414
|
+
connectors = normalized.get("connectors") if isinstance(normalized.get("connectors"), dict) else {}
|
|
1415
|
+
raw_connectors = payload.get("connectors") if isinstance(payload.get("connectors"), dict) else {}
|
|
1376
1416
|
default_locale = str(defaults.get("default_locale") or "").strip()
|
|
1377
1417
|
current_locale = str(normalized.get("default_locale") or "").strip()
|
|
1378
1418
|
locale_source = str(raw_bootstrap.get("locale_source") or "").strip().lower()
|
|
@@ -1396,6 +1436,25 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1396
1436
|
bootstrap["locale_initialized_at"] = bootstrap.get("locale_initialized_at")
|
|
1397
1437
|
bootstrap["locale_initialized_browser_locale"] = bootstrap.get("locale_initialized_browser_locale")
|
|
1398
1438
|
normalized["bootstrap"] = bootstrap
|
|
1439
|
+
raw_system_enabled = raw_connectors.get("system_enabled") if isinstance(raw_connectors.get("system_enabled"), dict) else {}
|
|
1440
|
+
default_system_enabled = (
|
|
1441
|
+
defaults.get("connectors", {}).get("system_enabled")
|
|
1442
|
+
if isinstance(defaults.get("connectors"), dict)
|
|
1443
|
+
else {}
|
|
1444
|
+
)
|
|
1445
|
+
current_system_enabled = (
|
|
1446
|
+
connectors.get("system_enabled")
|
|
1447
|
+
if isinstance(connectors.get("system_enabled"), dict)
|
|
1448
|
+
else {}
|
|
1449
|
+
)
|
|
1450
|
+
connectors["system_enabled"] = {
|
|
1451
|
+
name: self._coerce_bool(
|
|
1452
|
+
raw_system_enabled.get(name, current_system_enabled.get(name)),
|
|
1453
|
+
default=bool(default_system_enabled.get(name, False)),
|
|
1454
|
+
)
|
|
1455
|
+
for name in SYSTEM_CONNECTOR_NAMES
|
|
1456
|
+
}
|
|
1457
|
+
normalized["connectors"] = connectors
|
|
1399
1458
|
return normalized
|
|
1400
1459
|
|
|
1401
1460
|
@staticmethod
|
|
@@ -1408,6 +1467,20 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1408
1467
|
return False
|
|
1409
1468
|
return abs(initial - 1.0) < 1e-9 and abs(multiplier - 2.0) < 1e-9 and abs(max_backoff - 8.0) < 1e-9
|
|
1410
1469
|
|
|
1470
|
+
@staticmethod
|
|
1471
|
+
def _coerce_bool(value: object, *, default: bool = False) -> bool:
|
|
1472
|
+
if value is None:
|
|
1473
|
+
return default
|
|
1474
|
+
if isinstance(value, bool):
|
|
1475
|
+
return value
|
|
1476
|
+
if isinstance(value, str):
|
|
1477
|
+
normalized = value.strip().lower()
|
|
1478
|
+
if normalized in {"1", "true", "yes", "on", "y"}:
|
|
1479
|
+
return True
|
|
1480
|
+
if normalized in {"0", "false", "no", "off", "n", ""}:
|
|
1481
|
+
return False
|
|
1482
|
+
return bool(value)
|
|
1483
|
+
|
|
1411
1484
|
def _normalize_plugins_payload(self, payload: dict) -> dict:
|
|
1412
1485
|
normalized = deepcopy(payload)
|
|
1413
1486
|
if "load_paths" not in normalized and isinstance(normalized.get("search_paths"), list):
|
|
@@ -10,6 +10,13 @@ from .shared import slugify
|
|
|
10
10
|
PROFILEABLE_CONNECTOR_NAMES = ("telegram", "discord", "slack", "feishu", "whatsapp")
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _normalize_secret_pair(payload: dict[str, Any], direct_key: str, env_key: str) -> None:
|
|
14
|
+
direct = _as_text(payload.get(direct_key))
|
|
15
|
+
env_name = _as_text(payload.get(env_key))
|
|
16
|
+
payload[direct_key] = direct
|
|
17
|
+
payload[env_key] = None if direct else env_name
|
|
18
|
+
|
|
19
|
+
|
|
13
20
|
CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
14
21
|
"telegram": {
|
|
15
22
|
"profile_id_prefix": "telegram-profile",
|
|
@@ -35,7 +42,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
35
42
|
"transport": "polling",
|
|
36
43
|
"bot_name": "DeepScientist",
|
|
37
44
|
"bot_token": None,
|
|
38
|
-
"bot_token_env":
|
|
45
|
+
"bot_token_env": None,
|
|
39
46
|
},
|
|
40
47
|
"profile_fields": (
|
|
41
48
|
"enabled",
|
|
@@ -47,6 +54,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
47
54
|
"migration_keys": ("bot_token",),
|
|
48
55
|
"label_fields": ("bot_name",),
|
|
49
56
|
"id_fields": ("bot_name",),
|
|
57
|
+
"secret_pairs": (("bot_token", "bot_token_env"),),
|
|
50
58
|
},
|
|
51
59
|
"discord": {
|
|
52
60
|
"profile_id_prefix": "discord-profile",
|
|
@@ -74,7 +82,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
74
82
|
"transport": "gateway",
|
|
75
83
|
"bot_name": "DeepScientist",
|
|
76
84
|
"bot_token": None,
|
|
77
|
-
"bot_token_env":
|
|
85
|
+
"bot_token_env": None,
|
|
78
86
|
"application_id": None,
|
|
79
87
|
},
|
|
80
88
|
"profile_fields": (
|
|
@@ -88,6 +96,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
88
96
|
"migration_keys": ("bot_token", "application_id"),
|
|
89
97
|
"label_fields": ("bot_name", "application_id"),
|
|
90
98
|
"id_fields": ("application_id", "bot_name"),
|
|
99
|
+
"secret_pairs": (("bot_token", "bot_token_env"),),
|
|
91
100
|
},
|
|
92
101
|
"slack": {
|
|
93
102
|
"profile_id_prefix": "slack-profile",
|
|
@@ -116,10 +125,10 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
116
125
|
"transport": "socket_mode",
|
|
117
126
|
"bot_name": "DeepScientist",
|
|
118
127
|
"bot_token": None,
|
|
119
|
-
"bot_token_env":
|
|
128
|
+
"bot_token_env": None,
|
|
120
129
|
"bot_user_id": None,
|
|
121
130
|
"app_token": None,
|
|
122
|
-
"app_token_env":
|
|
131
|
+
"app_token_env": None,
|
|
123
132
|
},
|
|
124
133
|
"profile_fields": (
|
|
125
134
|
"enabled",
|
|
@@ -134,6 +143,10 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
134
143
|
"migration_keys": ("bot_token", "bot_user_id", "app_token"),
|
|
135
144
|
"label_fields": ("bot_name", "bot_user_id"),
|
|
136
145
|
"id_fields": ("bot_user_id", "bot_name"),
|
|
146
|
+
"secret_pairs": (
|
|
147
|
+
("bot_token", "bot_token_env"),
|
|
148
|
+
("app_token", "app_token_env"),
|
|
149
|
+
),
|
|
137
150
|
},
|
|
138
151
|
"feishu": {
|
|
139
152
|
"profile_id_prefix": "feishu-profile",
|
|
@@ -162,7 +175,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
162
175
|
"bot_name": "DeepScientist",
|
|
163
176
|
"app_id": None,
|
|
164
177
|
"app_secret": None,
|
|
165
|
-
"app_secret_env":
|
|
178
|
+
"app_secret_env": None,
|
|
166
179
|
"api_base_url": "https://open.feishu.cn",
|
|
167
180
|
},
|
|
168
181
|
"profile_fields": (
|
|
@@ -177,6 +190,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
177
190
|
"migration_keys": ("app_id", "app_secret"),
|
|
178
191
|
"label_fields": ("bot_name", "app_id"),
|
|
179
192
|
"id_fields": ("app_id", "bot_name"),
|
|
193
|
+
"secret_pairs": (("app_secret", "app_secret_env"),),
|
|
180
194
|
},
|
|
181
195
|
"whatsapp": {
|
|
182
196
|
"profile_id_prefix": "whatsapp-profile",
|
|
@@ -213,6 +227,7 @@ CONNECTOR_PROFILE_SPECS: dict[str, dict[str, Any]] = {
|
|
|
213
227
|
"migration_keys": ("session_dir",),
|
|
214
228
|
"label_fields": ("bot_name",),
|
|
215
229
|
"id_fields": ("bot_name",),
|
|
230
|
+
"secret_pairs": (),
|
|
216
231
|
},
|
|
217
232
|
}
|
|
218
233
|
|
|
@@ -270,10 +285,17 @@ def normalize_connector_config(connector_name: str, config: dict[str, Any] | Non
|
|
|
270
285
|
if key in payload
|
|
271
286
|
}
|
|
272
287
|
shared["profiles"] = []
|
|
288
|
+
for direct_key, env_key in spec.get("secret_pairs", ()):
|
|
289
|
+
_normalize_secret_pair(shared, direct_key, env_key)
|
|
273
290
|
|
|
274
291
|
raw_profiles = payload.get("profiles")
|
|
275
292
|
items = list(raw_profiles) if isinstance(raw_profiles, list) else []
|
|
276
|
-
|
|
293
|
+
has_direct_migration_value = any(_as_text(payload.get(key)) for key in spec["migration_keys"])
|
|
294
|
+
has_env_only_secret = bool(payload.get("enabled")) and any(
|
|
295
|
+
_as_text(payload.get(env_key))
|
|
296
|
+
for _, env_key in spec.get("secret_pairs", ())
|
|
297
|
+
)
|
|
298
|
+
if not items and (has_direct_migration_value or has_env_only_secret):
|
|
277
299
|
items = [{key: payload.get(key) for key in spec["profile_fields"]}]
|
|
278
300
|
|
|
279
301
|
used_ids: set[str] = set()
|
|
@@ -297,6 +319,8 @@ def normalize_connector_config(connector_name: str, config: dict[str, Any] | Non
|
|
|
297
319
|
current["transport"] = infer_connector_transport(connector_name, current)
|
|
298
320
|
if "mode" in spec["profile_defaults"] or current.get("mode") is not None:
|
|
299
321
|
current["mode"] = _as_text(current.get("mode")) or str(spec["profile_defaults"].get("mode") or "")
|
|
322
|
+
for direct_key, env_key in spec.get("secret_pairs", ()):
|
|
323
|
+
_normalize_secret_pair(current, direct_key, env_key)
|
|
300
324
|
current["profile_id"] = _unique_profile_id(
|
|
301
325
|
_profile_seed(connector_name, current, index=index),
|
|
302
326
|
prefix=str(spec["profile_id_prefix"]),
|
|
@@ -309,6 +333,10 @@ def normalize_connector_config(connector_name: str, config: dict[str, Any] | Non
|
|
|
309
333
|
if len(profiles) == 1:
|
|
310
334
|
for key in spec["profile_fields"]:
|
|
311
335
|
shared[key] = profiles[0].get(key)
|
|
336
|
+
elif len(profiles) > 1:
|
|
337
|
+
for direct_key, env_key in spec.get("secret_pairs", ()):
|
|
338
|
+
shared[direct_key] = None
|
|
339
|
+
shared[env_key] = None
|
|
312
340
|
return shared
|
|
313
341
|
|
|
314
342
|
|