@researai/deepscientist 1.5.14 → 1.5.16
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 +336 -90
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +816 -131
- package/docs/en/00_QUICK_START.md +36 -15
- package/docs/en/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +11 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/en/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/en/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/en/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/README.md +24 -0
- package/docs/zh/00_QUICK_START.md +36 -15
- package/docs/zh/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +11 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
- package/docs/zh/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/zh/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/zh/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/README.md +24 -0
- package/install.sh +2 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/charts.py +567 -0
- package/src/deepscientist/artifact/guidance.py +50 -10
- package/src/deepscientist/artifact/metrics.py +228 -5
- package/src/deepscientist/artifact/schemas.py +3 -0
- package/src/deepscientist/artifact/service.py +4276 -308
- package/src/deepscientist/bash_exec/models.py +23 -0
- package/src/deepscientist/bash_exec/monitor.py +147 -67
- package/src/deepscientist/bash_exec/runtime.py +218 -156
- package/src/deepscientist/bash_exec/service.py +309 -69
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +232 -0
- package/src/deepscientist/config/models.py +8 -4
- package/src/deepscientist/config/service.py +38 -11
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +199 -9
- package/src/deepscientist/daemon/api/router.py +5 -0
- package/src/deepscientist/daemon/app.py +1458 -289
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +296 -1
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +212 -5
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +501 -453
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +2539 -195
- package/src/deepscientist/quest/stage_views.py +177 -1
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +169 -31
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +24 -4
- package/src/prompts/system.md +921 -72
- package/src/prompts/system_copilot.md +43 -0
- package/src/skills/analysis-campaign/SKILL.md +32 -2
- package/src/skills/analysis-campaign/references/artifact-orchestration.md +1 -1
- package/src/skills/analysis-campaign/references/writing-facing-slice-examples.md +65 -0
- package/src/skills/baseline/SKILL.md +10 -0
- package/src/skills/decision/SKILL.md +27 -2
- package/src/skills/experiment/SKILL.md +16 -2
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +19 -0
- package/src/skills/idea/SKILL.md +79 -0
- package/src/skills/idea/references/idea-generation-playbook.md +100 -0
- package/src/skills/idea/references/outline-seeding-example.md +60 -0
- package/src/skills/intake-audit/SKILL.md +9 -1
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1645 -0
- package/src/skills/rebuttal/SKILL.md +3 -1
- package/src/skills/review/SKILL.md +3 -1
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +81 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +22 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
- package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
- package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
- package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
- package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
- package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
- package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
- package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
- package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
- package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
- package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
- package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
- package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
- package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
- package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
- package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
- package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
- package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
- package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
- package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
- package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DaF9Nge_.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-BSVx6dXE.js +0 -123
- package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
- package/src/ui/dist/assets/CodeEditorPlugin-DU9G0Tox.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-DoX_fI9l.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-C4FWIXuU.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-BgfFMgtf.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-tcPkfY_x.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-_dKV60Bf.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-Bje0ayoC.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CVsBzAln.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-xjmrqv_8.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-mMM2A8wP.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-3kVDSOBo.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/NotebookEditor-SoJ8X-MO.js +0 -84873
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfLoader-DElVuHl9.js +0 -25468
- package/src/ui/dist/assets/PdfMarkdownPlugin-Bq88XT4G.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-CsCXMo9S.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/SearchPlugin-oUPvy19k.js +0 -741
- package/src/ui/dist/assets/TextViewerPlugin-CRkT9yNy.js +0 -472
- package/src/ui/dist/assets/VNCViewer-BgbuvWhR.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-v_RASACv.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-5hC9d0VH.js +0 -17
- package/src/ui/dist/assets/file-content-D1PxfOrp.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DG1oT_Hj.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-BmdFYQlk.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-Dqe2X2tW.js +0 -18
- package/src/ui/dist/assets/index-BQG-1s2o.css +0 -12553
- package/src/ui/dist/assets/index-DVsMKK_y.js +0 -25
- package/src/ui/dist/assets/index-Duvz8Ip0.js +0 -159
- package/src/ui/dist/assets/index-Nt9hS4ck.js +0 -244829
- package/src/ui/dist/assets/index-RDlNXXx1.js +0 -120
- package/src/ui/dist/assets/monaco-DIXge1CP.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BBTTQaO-.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-BWlolyxo.js +0 -476
- package/src/ui/dist/assets/project-sync-BM5PkFH4.js +0 -297
- package/src/ui/dist/assets/select-D4dAtrA8.js +0 -1690
- package/src/ui/dist/assets/sigma-CKbE5jJT.js +0 -22
- package/src/ui/dist/assets/square-check-big-CZNGMgiB.js +0 -17
- package/src/ui/dist/assets/trash-DaB37xAz.js +0 -32
- package/src/ui/dist/assets/useCliAccess-C2OmAcWe.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-Dowd1Ij4.js +0 -53
- package/src/ui/dist/assets/wrap-text-BGjAhAUq.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-dMZQMXzc.js +0 -34
|
@@ -18,8 +18,11 @@ from ..connector.weixin_support import (
|
|
|
18
18
|
WEIXIN_UPLOAD_MEDIA_FILE,
|
|
19
19
|
WEIXIN_UPLOAD_MEDIA_IMAGE,
|
|
20
20
|
WEIXIN_UPLOAD_MEDIA_VIDEO,
|
|
21
|
+
clear_weixin_context_send_state,
|
|
21
22
|
download_weixin_remote_attachment,
|
|
23
|
+
get_weixin_context_entry,
|
|
22
24
|
get_weixin_context_token,
|
|
25
|
+
mark_weixin_context_stale,
|
|
23
26
|
normalize_weixin_base_url,
|
|
24
27
|
normalize_weixin_cdn_base_url,
|
|
25
28
|
send_weixin_message,
|
|
@@ -558,10 +561,11 @@ class QQConnectorBridge(BaseConnectorBridge):
|
|
|
558
561
|
connector_delivery = item.get("connector_delivery") if isinstance(item.get("connector_delivery"), dict) else {}
|
|
559
562
|
qq_delivery = connector_delivery.get("qq") if isinstance(connector_delivery.get("qq"), dict) else {}
|
|
560
563
|
media_kind = str(qq_delivery.get("media_kind") or "").strip().lower()
|
|
564
|
+
allow_internal_auto_media = bool(qq_delivery.get("allow_internal_auto_media"))
|
|
561
565
|
if media_kind not in {"image", "file"}:
|
|
562
566
|
residual_items.append(item)
|
|
563
567
|
continue
|
|
564
|
-
if not native_enabled:
|
|
568
|
+
if not native_enabled and not allow_internal_auto_media:
|
|
565
569
|
issues.append(
|
|
566
570
|
{
|
|
567
571
|
"attachment_index": index,
|
|
@@ -781,7 +785,9 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
781
785
|
name = "weixin"
|
|
782
786
|
_MEDIA_ITEM_TYPES = {2, 4, 5}
|
|
783
787
|
_MEDIA_SEND_INITIAL_DELAY_SECONDS = 0.8
|
|
784
|
-
|
|
788
|
+
_TEXT_SEND_RETRY_DELAYS_SECONDS = (0.8, 1.5, 3.0)
|
|
789
|
+
_MEDIA_SEND_RETRY_DELAYS_SECONDS = (1.5, 3.0, 5.0)
|
|
790
|
+
_STALE_CONTEXT_SUPPRESSED_KINDS = {"progress", "milestone", "assistant", "summary", "ack"}
|
|
785
791
|
|
|
786
792
|
def deliver(self, payload: dict[str, Any], config: dict[str, Any]) -> dict[str, Any] | None:
|
|
787
793
|
return self.deliver_direct(payload, config)
|
|
@@ -800,14 +806,26 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
800
806
|
"error": "Weixin outbound target is empty.",
|
|
801
807
|
}
|
|
802
808
|
connector_root = self._connector_root(config)
|
|
809
|
+
kind = str(payload.get("kind") or "").strip().lower()
|
|
810
|
+
context_entry = get_weixin_context_entry(connector_root, to_user_id)
|
|
803
811
|
context_token = get_weixin_context_token(connector_root, to_user_id)
|
|
804
812
|
if not context_token:
|
|
813
|
+
if kind in self._STALE_CONTEXT_SUPPRESSED_KINDS:
|
|
814
|
+
return self._queued_context_wait_response(
|
|
815
|
+
reason=f"Weixin context_token is missing for `{to_user_id}`. Waiting for the next inbound message.",
|
|
816
|
+
)
|
|
805
817
|
return {
|
|
806
818
|
"ok": False,
|
|
807
819
|
"queued": False,
|
|
808
820
|
"transport": "weixin-ilink",
|
|
809
821
|
"error": f"Weixin context_token is missing for `{to_user_id}`. Wait for one inbound message first.",
|
|
810
822
|
}
|
|
823
|
+
if bool(context_entry.get("stale_context")) and kind in self._STALE_CONTEXT_SUPPRESSED_KINDS:
|
|
824
|
+
stale_since = str(context_entry.get("stale_since") or "").strip()
|
|
825
|
+
warning = "Weixin outbound is paused until the next inbound message refreshes context_token."
|
|
826
|
+
if stale_since:
|
|
827
|
+
warning = f"{warning} stale_since={stale_since}"
|
|
828
|
+
return self._queued_context_wait_response(reason=warning)
|
|
811
829
|
|
|
812
830
|
native_attachments, residual_attachments, warnings = self._partition_native_attachments(payload.get("attachments"))
|
|
813
831
|
rendered_text = self.render_text(payload.get("text"), residual_attachments)
|
|
@@ -895,6 +913,20 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
895
913
|
else:
|
|
896
914
|
warnings.append("Weixin outbound payload contained neither text nor sendable attachments.")
|
|
897
915
|
except Exception as exc:
|
|
916
|
+
if "ret=-2" in str(exc or "").lower():
|
|
917
|
+
mark_weixin_context_stale(
|
|
918
|
+
connector_root,
|
|
919
|
+
user_id=to_user_id,
|
|
920
|
+
error=str(exc),
|
|
921
|
+
kind=kind or None,
|
|
922
|
+
)
|
|
923
|
+
if kind in self._STALE_CONTEXT_SUPPRESSED_KINDS:
|
|
924
|
+
queued = self._queued_context_wait_response(
|
|
925
|
+
reason="Weixin send hit stale context and was deferred until the next inbound refresh.",
|
|
926
|
+
)
|
|
927
|
+
queued["parts"] = parts
|
|
928
|
+
queued["warnings"] = [*warnings, *(queued.get("warnings") or [])]
|
|
929
|
+
return queued
|
|
898
930
|
return {
|
|
899
931
|
"ok": False,
|
|
900
932
|
"queued": False,
|
|
@@ -909,6 +941,12 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
909
941
|
error_messages = [str(item.get("error") or "").strip() for item in failed if str(item.get("error") or "").strip()]
|
|
910
942
|
error_messages.extend(warnings)
|
|
911
943
|
last_success = succeeded[-1] if succeeded else {}
|
|
944
|
+
if succeeded:
|
|
945
|
+
clear_weixin_context_send_state(
|
|
946
|
+
connector_root,
|
|
947
|
+
user_id=to_user_id,
|
|
948
|
+
kind=kind or None,
|
|
949
|
+
)
|
|
912
950
|
return {
|
|
913
951
|
"ok": bool(succeeded),
|
|
914
952
|
"queued": False,
|
|
@@ -920,6 +958,17 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
920
958
|
"error": "; ".join(error_messages) if error_messages else None,
|
|
921
959
|
}
|
|
922
960
|
|
|
961
|
+
@staticmethod
|
|
962
|
+
def _queued_context_wait_response(*, reason: str) -> dict[str, Any]:
|
|
963
|
+
return {
|
|
964
|
+
"ok": False,
|
|
965
|
+
"queued": True,
|
|
966
|
+
"transport": "weixin-ilink",
|
|
967
|
+
"parts": [],
|
|
968
|
+
"warnings": [str(reason or "").strip()],
|
|
969
|
+
"error": None,
|
|
970
|
+
}
|
|
971
|
+
|
|
923
972
|
@staticmethod
|
|
924
973
|
def _partition_native_attachments(
|
|
925
974
|
attachments: Any,
|
package/src/deepscientist/cli.py
CHANGED
|
@@ -25,23 +25,78 @@ from .registries import BaselineRegistry
|
|
|
25
25
|
from .runners import CodexRunner, RunRequest, get_runner_factory, register_builtin_runners
|
|
26
26
|
from .runtime_tools import RuntimeToolService
|
|
27
27
|
from .runtime_logs import JsonlLogger
|
|
28
|
-
from .shared import ensure_dir, read_yaml
|
|
28
|
+
from .shared import ensure_dir, read_json, read_yaml
|
|
29
29
|
from .skills import SkillInstaller
|
|
30
30
|
from .tui import watch_tui
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
class DeepScientistArgumentParser(argparse.ArgumentParser):
|
|
34
|
+
def error(self, message: str) -> None:
|
|
35
|
+
self.print_usage(sys.stderr)
|
|
36
|
+
self.exit(2, f"DeepScientist argument error: {message}\nRun `{self.prog} --help` for usage.\n")
|
|
37
|
+
|
|
38
|
+
|
|
33
39
|
def _local_ui_url(host: str, port: int) -> str:
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
normalized = str(host or "").strip()
|
|
41
|
+
connect_host = "127.0.0.1" if normalized in {"0.0.0.0", "::", "[::]", ""} else normalized
|
|
42
|
+
if connect_host.startswith("[") and connect_host.endswith("]"):
|
|
43
|
+
rendered_host = connect_host
|
|
44
|
+
elif ":" in connect_host:
|
|
45
|
+
rendered_host = f"[{connect_host}]"
|
|
46
|
+
else:
|
|
47
|
+
rendered_host = connect_host
|
|
48
|
+
return f"http://{rendered_host}:{port}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _parse_optional_bool(value: object) -> bool | None:
|
|
52
|
+
if isinstance(value, bool):
|
|
53
|
+
return value
|
|
54
|
+
normalized = str(value or "").strip().lower()
|
|
55
|
+
if not normalized:
|
|
56
|
+
return None
|
|
57
|
+
if normalized in {"1", "true", "yes", "on"}:
|
|
58
|
+
return True
|
|
59
|
+
if normalized in {"0", "false", "no", "off"}:
|
|
60
|
+
return False
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _daemon_request_headers(home: Path) -> dict[str, str]:
|
|
65
|
+
headers = {"Content-Type": "application/json"}
|
|
66
|
+
state = read_json(home / "runtime" / "daemon.json", {})
|
|
67
|
+
if not isinstance(state, dict):
|
|
68
|
+
return headers
|
|
69
|
+
if bool(state.get("auth_enabled")):
|
|
70
|
+
token = str(state.get("auth_token") or "").strip()
|
|
71
|
+
if token:
|
|
72
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
73
|
+
return headers
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _daemon_launch_url(home: Path, *, host: str, port: int) -> str:
|
|
77
|
+
state = read_json(home / "runtime" / "daemon.json", {})
|
|
78
|
+
if isinstance(state, dict):
|
|
79
|
+
launch_url = str(state.get("launch_url") or "").strip()
|
|
80
|
+
if launch_url:
|
|
81
|
+
return launch_url
|
|
82
|
+
return _local_ui_url(host, port)
|
|
36
83
|
|
|
37
84
|
|
|
38
85
|
def build_parser() -> argparse.ArgumentParser:
|
|
39
|
-
parser =
|
|
86
|
+
parser = DeepScientistArgumentParser(
|
|
87
|
+
prog="ds",
|
|
88
|
+
description="DeepScientist Core skeleton",
|
|
89
|
+
allow_abbrev=False,
|
|
90
|
+
)
|
|
40
91
|
parser.add_argument("--home", default=None, help="Override DeepScientist home")
|
|
41
92
|
parser.add_argument("--proxy", default=None, help="Explicit outbound HTTP/WS proxy, for example `http://127.0.0.1:7890`.")
|
|
42
93
|
parser.add_argument("--codex", default=None, help="Override the Codex executable path for this invocation.")
|
|
43
94
|
|
|
44
|
-
subparsers = parser.add_subparsers(
|
|
95
|
+
subparsers = parser.add_subparsers(
|
|
96
|
+
dest="command",
|
|
97
|
+
required=True,
|
|
98
|
+
parser_class=DeepScientistArgumentParser,
|
|
99
|
+
)
|
|
45
100
|
|
|
46
101
|
subparsers.add_parser("init")
|
|
47
102
|
|
|
@@ -60,12 +115,24 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
60
115
|
daemon_parser = subparsers.add_parser("daemon")
|
|
61
116
|
daemon_parser.add_argument("--host", default=None)
|
|
62
117
|
daemon_parser.add_argument("--port", type=int, default=None)
|
|
118
|
+
daemon_parser.add_argument("--auth", default=None)
|
|
119
|
+
daemon_parser.add_argument("--auth-token", default=None)
|
|
120
|
+
daemon_parser.add_argument(
|
|
121
|
+
"--prompt-version",
|
|
122
|
+
default=None,
|
|
123
|
+
help="Use `latest` managed prompts, an official historical prompt version such as `1.5.13`, or an exact backup id from `.codex/prompt_versions/` for this daemon session.",
|
|
124
|
+
)
|
|
63
125
|
|
|
64
126
|
run_parser = subparsers.add_parser("run")
|
|
65
127
|
run_parser.add_argument("skill_id")
|
|
66
128
|
run_parser.add_argument("--quest-id", required=True)
|
|
67
129
|
run_parser.add_argument("--message", required=True)
|
|
68
130
|
run_parser.add_argument("--model", default=None)
|
|
131
|
+
run_parser.add_argument(
|
|
132
|
+
"--prompt-version",
|
|
133
|
+
default=None,
|
|
134
|
+
help="Use `latest` managed prompts, an official historical prompt version such as `1.5.13`, or an exact backup id from `.codex/prompt_versions/` for this one-off run.",
|
|
135
|
+
)
|
|
69
136
|
|
|
70
137
|
ui_parser = subparsers.add_parser("ui")
|
|
71
138
|
ui_parser.add_argument("--mode", choices=("web", "tui", "both"), default="web")
|
|
@@ -186,7 +253,6 @@ def resume_command(home: Path, quest_id: str) -> int:
|
|
|
186
253
|
print(json.dumps(snapshot, ensure_ascii=False, indent=2))
|
|
187
254
|
return 0
|
|
188
255
|
|
|
189
|
-
|
|
190
256
|
def _daemon_control_quest(home: Path, quest_id: str, *, action: str) -> dict | None:
|
|
191
257
|
config = ConfigManager(home).load_named("config", create_optional=False)
|
|
192
258
|
ui_config = config.get("ui", {})
|
|
@@ -194,7 +260,7 @@ def _daemon_control_quest(home: Path, quest_id: str, *, action: str) -> dict | N
|
|
|
194
260
|
request = Request(
|
|
195
261
|
url,
|
|
196
262
|
data=json.dumps({"action": action, "source": "cli"}).encode("utf-8"),
|
|
197
|
-
headers=
|
|
263
|
+
headers=_daemon_request_headers(home),
|
|
198
264
|
method="POST",
|
|
199
265
|
)
|
|
200
266
|
try:
|
|
@@ -211,7 +277,7 @@ def _daemon_create_quest(home: Path, *, goal: str, quest_id: str | None) -> dict
|
|
|
211
277
|
request = Request(
|
|
212
278
|
url,
|
|
213
279
|
data=json.dumps({"goal": goal, "quest_id": quest_id, "source": "cli"}).encode("utf-8"),
|
|
214
|
-
headers=
|
|
280
|
+
headers=_daemon_request_headers(home),
|
|
215
281
|
method="POST",
|
|
216
282
|
)
|
|
217
283
|
try:
|
|
@@ -221,18 +287,37 @@ def _daemon_create_quest(home: Path, *, goal: str, quest_id: str | None) -> dict
|
|
|
221
287
|
return None
|
|
222
288
|
|
|
223
289
|
|
|
224
|
-
def daemon_command(
|
|
290
|
+
def daemon_command(
|
|
291
|
+
home: Path,
|
|
292
|
+
host: str | None,
|
|
293
|
+
port: int | None,
|
|
294
|
+
auth: str | None,
|
|
295
|
+
auth_token: str | None,
|
|
296
|
+
prompt_version: str | None,
|
|
297
|
+
) -> int:
|
|
225
298
|
ensure_home_layout(home)
|
|
226
299
|
config_manager = ConfigManager(home)
|
|
227
300
|
config_manager.ensure_files()
|
|
228
301
|
config = config_manager.load_named("config")
|
|
229
302
|
ui_config = config.get("ui", {})
|
|
230
|
-
daemon = DaemonApp(
|
|
303
|
+
daemon = DaemonApp(
|
|
304
|
+
home,
|
|
305
|
+
browser_auth_enabled=_parse_optional_bool(auth),
|
|
306
|
+
browser_auth_token=str(auth_token or "").strip() or None,
|
|
307
|
+
prompt_version_selection=str(prompt_version or "").strip() or None,
|
|
308
|
+
)
|
|
231
309
|
daemon.serve(host or ui_config.get("host", "0.0.0.0"), port or ui_config.get("port", 20999))
|
|
232
310
|
return 0
|
|
233
311
|
|
|
234
312
|
|
|
235
|
-
def run_command(
|
|
313
|
+
def run_command(
|
|
314
|
+
home: Path,
|
|
315
|
+
quest_id: str,
|
|
316
|
+
skill_id: str,
|
|
317
|
+
message: str,
|
|
318
|
+
model: str | None,
|
|
319
|
+
prompt_version: str | None,
|
|
320
|
+
) -> int:
|
|
236
321
|
ensure_home_layout(home)
|
|
237
322
|
config_manager = ConfigManager(home)
|
|
238
323
|
config_manager.ensure_files()
|
|
@@ -246,7 +331,11 @@ def run_command(home: Path, quest_id: str, skill_id: str, message: str, model: s
|
|
|
246
331
|
repo_root=repo_root(),
|
|
247
332
|
binary=codex_cfg.get("binary", "codex"),
|
|
248
333
|
logger=logger,
|
|
249
|
-
prompt_builder=PromptBuilder(
|
|
334
|
+
prompt_builder=PromptBuilder(
|
|
335
|
+
repo_root(),
|
|
336
|
+
home,
|
|
337
|
+
prompt_version_selection=str(prompt_version or "").strip() or None,
|
|
338
|
+
),
|
|
250
339
|
artifact_service=ArtifactService(home),
|
|
251
340
|
)
|
|
252
341
|
register_builtin_runners(codex_runner=runner)
|
|
@@ -322,19 +411,26 @@ def launch_ink_tui(home: Path, url: str) -> int:
|
|
|
322
411
|
)
|
|
323
412
|
)
|
|
324
413
|
return 1
|
|
325
|
-
|
|
414
|
+
state = read_json(home / "runtime" / "daemon.json", {})
|
|
415
|
+
args = [node_binary, str(entry), "--base-url", url]
|
|
416
|
+
if isinstance(state, dict) and bool(state.get("auth_enabled")):
|
|
417
|
+
token = str(state.get("auth_token") or "").strip()
|
|
418
|
+
if token:
|
|
419
|
+
args.extend(["--auth-token", token])
|
|
420
|
+
return subprocess.call(args)
|
|
326
421
|
|
|
327
422
|
|
|
328
423
|
def ui_command(home: Path, mode: str) -> int:
|
|
329
424
|
config = ConfigManager(home).load_named("config", create_optional=False)
|
|
330
425
|
host = config.get("ui", {}).get("host", "0.0.0.0")
|
|
331
426
|
port = config.get("ui", {}).get("port", 20999)
|
|
332
|
-
|
|
427
|
+
base_url = _local_ui_url(str(host), int(port))
|
|
428
|
+
launch_url = _daemon_launch_url(home, host=str(host), port=int(port))
|
|
333
429
|
if mode in {"web", "both"}:
|
|
334
|
-
webbrowser.open(
|
|
335
|
-
print(f"Opened {
|
|
430
|
+
webbrowser.open(launch_url)
|
|
431
|
+
print(f"Opened {launch_url}")
|
|
336
432
|
if mode in {"tui", "both"}:
|
|
337
|
-
return launch_ink_tui(home,
|
|
433
|
+
return launch_ink_tui(home, base_url)
|
|
338
434
|
return 0
|
|
339
435
|
|
|
340
436
|
|
|
@@ -492,9 +588,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
492
588
|
if args.command == "resume":
|
|
493
589
|
return resume_command(home, args.quest_id)
|
|
494
590
|
if args.command == "daemon":
|
|
495
|
-
return daemon_command(home, args.host, args.port)
|
|
591
|
+
return daemon_command(home, args.host, args.port, args.auth, args.auth_token, args.prompt_version)
|
|
496
592
|
if args.command == "run":
|
|
497
|
-
return run_command(home, args.quest_id, args.skill_id, args.message, args.model)
|
|
593
|
+
return run_command(home, args.quest_id, args.skill_id, args.message, args.model, args.prompt_version)
|
|
498
594
|
if args.command == "ui":
|
|
499
595
|
return ui_command(home, args.mode)
|
|
500
596
|
if args.command == "note":
|
|
@@ -2,12 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
|
+
import shutil
|
|
5
6
|
import subprocess
|
|
6
7
|
import tomllib
|
|
7
8
|
from functools import lru_cache
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .shared import ensure_dir, read_text, write_text
|
|
8
12
|
|
|
9
13
|
_MIN_XHIGH_SUPPORTED_VERSION = (0, 63, 0)
|
|
10
14
|
_CODEX_VERSION_PATTERN = re.compile(r"codex-cli\s+(\d+)\.(\d+)\.(\d+)", re.IGNORECASE)
|
|
15
|
+
_CODEX_HOME_SYNCED_FILES = ("config.toml", "auth.json")
|
|
16
|
+
_CODEX_HOME_SYNCED_DIRS = ("skills", "agents", "prompts")
|
|
17
|
+
_CODEX_HOME_QUEST_OVERLAY_DIRS = ("skills", "prompts")
|
|
11
18
|
|
|
12
19
|
|
|
13
20
|
def parse_codex_cli_version(text: str) -> tuple[int, int, int] | None:
|
|
@@ -115,3 +122,228 @@ def adapt_profile_only_provider_config(
|
|
|
115
122
|
f"{', '.join(injected_fields)} to the top level for Codex compatibility."
|
|
116
123
|
),
|
|
117
124
|
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _remove_tree_path(path: Path) -> None:
|
|
128
|
+
if not path.exists() and not path.is_symlink():
|
|
129
|
+
return
|
|
130
|
+
if path.is_symlink() or path.is_file():
|
|
131
|
+
path.unlink()
|
|
132
|
+
return
|
|
133
|
+
shutil.rmtree(path)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _overlay_file_sources(*roots: Path) -> dict[Path, Path]:
|
|
137
|
+
merged: dict[Path, Path] = {}
|
|
138
|
+
for root in roots:
|
|
139
|
+
if not root.exists() or not root.is_dir():
|
|
140
|
+
continue
|
|
141
|
+
for source_path in sorted(root.rglob("*")):
|
|
142
|
+
if not source_path.is_file():
|
|
143
|
+
continue
|
|
144
|
+
merged[source_path.relative_to(root)] = source_path
|
|
145
|
+
return merged
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _sync_overlay_directory(target_dir: Path, *source_dirs: Path) -> None:
|
|
149
|
+
desired_files = _overlay_file_sources(*source_dirs)
|
|
150
|
+
if not desired_files:
|
|
151
|
+
_remove_tree_path(target_dir)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
desired_dirs: set[Path] = {Path(".")}
|
|
155
|
+
for relative in desired_files:
|
|
156
|
+
parent = relative.parent
|
|
157
|
+
while True:
|
|
158
|
+
desired_dirs.add(parent)
|
|
159
|
+
if parent == Path("."):
|
|
160
|
+
break
|
|
161
|
+
parent = parent.parent
|
|
162
|
+
|
|
163
|
+
if target_dir.exists() or target_dir.is_symlink():
|
|
164
|
+
for existing_path in sorted(target_dir.rglob("*"), reverse=True):
|
|
165
|
+
relative = existing_path.relative_to(target_dir)
|
|
166
|
+
if existing_path.is_dir() and not existing_path.is_symlink():
|
|
167
|
+
if relative not in desired_dirs:
|
|
168
|
+
shutil.rmtree(existing_path)
|
|
169
|
+
continue
|
|
170
|
+
if relative not in desired_files:
|
|
171
|
+
existing_path.unlink()
|
|
172
|
+
|
|
173
|
+
ensure_dir(target_dir)
|
|
174
|
+
for relative in sorted(desired_dirs, key=lambda item: (len(item.parts), item.as_posix())):
|
|
175
|
+
if relative == Path("."):
|
|
176
|
+
continue
|
|
177
|
+
current = target_dir / relative
|
|
178
|
+
if current.exists() and (current.is_symlink() or current.is_file()):
|
|
179
|
+
current.unlink()
|
|
180
|
+
ensure_dir(current)
|
|
181
|
+
|
|
182
|
+
for relative, source_path in desired_files.items():
|
|
183
|
+
target_path = target_dir / relative
|
|
184
|
+
if target_path.exists() and target_path.is_dir() and not target_path.is_symlink():
|
|
185
|
+
shutil.rmtree(target_path)
|
|
186
|
+
ensure_dir(target_path.parent)
|
|
187
|
+
try:
|
|
188
|
+
same_path = source_path.resolve() == target_path.resolve()
|
|
189
|
+
except FileNotFoundError:
|
|
190
|
+
same_path = False
|
|
191
|
+
if same_path:
|
|
192
|
+
continue
|
|
193
|
+
shutil.copy2(source_path, target_path)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def materialize_codex_runtime_home(
|
|
197
|
+
*,
|
|
198
|
+
source_home: str | Path,
|
|
199
|
+
target_home: str | Path,
|
|
200
|
+
profile: str = "",
|
|
201
|
+
quest_codex_root: str | Path | None = None,
|
|
202
|
+
) -> str | None:
|
|
203
|
+
source_root = Path(source_home).expanduser()
|
|
204
|
+
target_root = ensure_dir(Path(target_home))
|
|
205
|
+
|
|
206
|
+
for filename in _CODEX_HOME_SYNCED_FILES:
|
|
207
|
+
source_path = source_root / filename
|
|
208
|
+
target_path = target_root / filename
|
|
209
|
+
if not source_path.exists():
|
|
210
|
+
_remove_tree_path(target_path)
|
|
211
|
+
continue
|
|
212
|
+
if target_path.exists() and target_path.is_dir() and not target_path.is_symlink():
|
|
213
|
+
shutil.rmtree(target_path)
|
|
214
|
+
ensure_dir(target_path.parent)
|
|
215
|
+
try:
|
|
216
|
+
same_path = source_path.resolve() == target_path.resolve()
|
|
217
|
+
except FileNotFoundError:
|
|
218
|
+
same_path = False
|
|
219
|
+
if not same_path:
|
|
220
|
+
shutil.copy2(source_path, target_path)
|
|
221
|
+
|
|
222
|
+
overlay_root = Path(quest_codex_root) if quest_codex_root is not None else None
|
|
223
|
+
for dirname in _CODEX_HOME_SYNCED_DIRS:
|
|
224
|
+
overlay_dir = overlay_root / dirname if overlay_root is not None and dirname in _CODEX_HOME_QUEST_OVERLAY_DIRS else None
|
|
225
|
+
source_dirs: list[Path] = [source_root / dirname]
|
|
226
|
+
if overlay_dir is not None:
|
|
227
|
+
source_dirs.append(overlay_dir)
|
|
228
|
+
_sync_overlay_directory(target_root / dirname, *source_dirs)
|
|
229
|
+
|
|
230
|
+
warning: str | None = None
|
|
231
|
+
config_path = target_root / "config.toml"
|
|
232
|
+
if profile and config_path.exists():
|
|
233
|
+
adapted_text, warning = adapt_profile_only_provider_config(read_text(config_path), profile=profile)
|
|
234
|
+
write_text(config_path, adapted_text)
|
|
235
|
+
return warning
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def provider_profile_metadata(
|
|
239
|
+
config_text: str,
|
|
240
|
+
*,
|
|
241
|
+
profile: str,
|
|
242
|
+
) -> dict[str, str | bool | None]:
|
|
243
|
+
normalized_profile = str(profile or "").strip()
|
|
244
|
+
if not normalized_profile or not str(config_text or "").strip():
|
|
245
|
+
return {
|
|
246
|
+
"provider": None,
|
|
247
|
+
"model": None,
|
|
248
|
+
"env_key": None,
|
|
249
|
+
"base_url": None,
|
|
250
|
+
"wire_api": None,
|
|
251
|
+
"requires_openai_auth": None,
|
|
252
|
+
}
|
|
253
|
+
try:
|
|
254
|
+
parsed = tomllib.loads(config_text)
|
|
255
|
+
except tomllib.TOMLDecodeError:
|
|
256
|
+
return {
|
|
257
|
+
"provider": None,
|
|
258
|
+
"model": None,
|
|
259
|
+
"env_key": None,
|
|
260
|
+
"base_url": None,
|
|
261
|
+
"wire_api": None,
|
|
262
|
+
"requires_openai_auth": None,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
profiles = parsed.get("profiles")
|
|
266
|
+
if not isinstance(profiles, dict):
|
|
267
|
+
return {
|
|
268
|
+
"provider": None,
|
|
269
|
+
"model": None,
|
|
270
|
+
"env_key": None,
|
|
271
|
+
"base_url": None,
|
|
272
|
+
"wire_api": None,
|
|
273
|
+
"requires_openai_auth": None,
|
|
274
|
+
}
|
|
275
|
+
profile_payload = profiles.get(normalized_profile)
|
|
276
|
+
if not isinstance(profile_payload, dict):
|
|
277
|
+
return {
|
|
278
|
+
"provider": None,
|
|
279
|
+
"model": None,
|
|
280
|
+
"env_key": None,
|
|
281
|
+
"base_url": None,
|
|
282
|
+
"wire_api": None,
|
|
283
|
+
"requires_openai_auth": None,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
model_provider = str(
|
|
287
|
+
profile_payload.get("model_provider")
|
|
288
|
+
or parsed.get("model_provider")
|
|
289
|
+
or ""
|
|
290
|
+
).strip() or None
|
|
291
|
+
model = str(
|
|
292
|
+
profile_payload.get("model")
|
|
293
|
+
or parsed.get("model")
|
|
294
|
+
or ""
|
|
295
|
+
).strip() or None
|
|
296
|
+
provider_payload = None
|
|
297
|
+
model_providers = parsed.get("model_providers")
|
|
298
|
+
if model_provider and isinstance(model_providers, dict):
|
|
299
|
+
candidate = model_providers.get(model_provider)
|
|
300
|
+
if isinstance(candidate, dict):
|
|
301
|
+
provider_payload = candidate
|
|
302
|
+
|
|
303
|
+
env_key = (
|
|
304
|
+
str(provider_payload.get("env_key") or "").strip()
|
|
305
|
+
if isinstance(provider_payload, dict)
|
|
306
|
+
else None
|
|
307
|
+
) or None
|
|
308
|
+
base_url = (
|
|
309
|
+
str(provider_payload.get("base_url") or "").strip()
|
|
310
|
+
if isinstance(provider_payload, dict)
|
|
311
|
+
else None
|
|
312
|
+
) or None
|
|
313
|
+
wire_api = (
|
|
314
|
+
str(provider_payload.get("wire_api") or "").strip()
|
|
315
|
+
if isinstance(provider_payload, dict)
|
|
316
|
+
else None
|
|
317
|
+
) or None
|
|
318
|
+
requires_openai_auth = (
|
|
319
|
+
bool(provider_payload.get("requires_openai_auth"))
|
|
320
|
+
if isinstance(provider_payload, dict) and "requires_openai_auth" in provider_payload
|
|
321
|
+
else None
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"provider": model_provider,
|
|
326
|
+
"model": model,
|
|
327
|
+
"env_key": env_key,
|
|
328
|
+
"base_url": base_url,
|
|
329
|
+
"wire_api": wire_api,
|
|
330
|
+
"requires_openai_auth": requires_openai_auth,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def provider_profile_metadata_from_home(
|
|
335
|
+
config_home: str | Path,
|
|
336
|
+
*,
|
|
337
|
+
profile: str,
|
|
338
|
+
) -> dict[str, str | bool | None]:
|
|
339
|
+
config_path = Path(config_home).expanduser() / "config.toml"
|
|
340
|
+
if not config_path.exists():
|
|
341
|
+
return {
|
|
342
|
+
"provider": None,
|
|
343
|
+
"model": None,
|
|
344
|
+
"env_key": None,
|
|
345
|
+
"base_url": None,
|
|
346
|
+
"wire_api": None,
|
|
347
|
+
"requires_openai_auth": None,
|
|
348
|
+
}
|
|
349
|
+
return provider_profile_metadata(config_path.read_text(encoding="utf-8"), profile=profile)
|
|
@@ -22,7 +22,7 @@ def config_filename(name: str) -> str:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def default_system_enabled_connectors() -> dict[str, bool]:
|
|
25
|
-
return {name: name in {"qq", "weixin", "lingzhu"} for name in SYSTEM_CONNECTOR_NAMES}
|
|
25
|
+
return {name: name in {"qq", "weixin", "telegram", "feishu", "whatsapp", "lingzhu"} for name in SYSTEM_CONNECTOR_NAMES}
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def default_config(home: Path) -> dict:
|
|
@@ -38,6 +38,7 @@ def default_config(home: Path) -> dict:
|
|
|
38
38
|
"ui": {
|
|
39
39
|
"host": "0.0.0.0",
|
|
40
40
|
"port": 20999,
|
|
41
|
+
"auth_enabled": False,
|
|
41
42
|
"auto_open_browser": True,
|
|
42
43
|
"default_mode": "web",
|
|
43
44
|
},
|
|
@@ -96,10 +97,10 @@ def default_runners() -> dict:
|
|
|
96
97
|
"binary": "codex",
|
|
97
98
|
"config_dir": "~/.codex",
|
|
98
99
|
"profile": "",
|
|
99
|
-
"model": "
|
|
100
|
+
"model": "inherit",
|
|
100
101
|
"model_reasoning_effort": "xhigh",
|
|
101
|
-
"approval_policy": "
|
|
102
|
-
"sandbox_mode": "
|
|
102
|
+
"approval_policy": "never",
|
|
103
|
+
"sandbox_mode": "danger-full-access",
|
|
103
104
|
"retry_on_failure": True,
|
|
104
105
|
"retry_max_attempts": 5,
|
|
105
106
|
"retry_initial_backoff_sec": 10.0,
|
|
@@ -154,6 +155,9 @@ def default_connectors() -> dict:
|
|
|
154
155
|
"transport": "ilink_long_poll",
|
|
155
156
|
"bot_name": "DeepScientist",
|
|
156
157
|
"command_prefix": "/",
|
|
158
|
+
"auto_send_main_experiment_png": True,
|
|
159
|
+
"stale_replay_latest_limit": 5,
|
|
160
|
+
"stale_replay_interval_seconds": 2.0,
|
|
157
161
|
"base_url": "https://ilinkai.weixin.qq.com",
|
|
158
162
|
"cdn_base_url": "https://novac2c.cdn.weixin.qq.com/c2c",
|
|
159
163
|
"bot_type": "3",
|