@researai/deepscientist 1.5.9 → 1.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -99
- package/assets/branding/connector-qq.png +0 -0
- package/assets/branding/connector-rokid.png +0 -0
- package/assets/branding/connector-weixin.png +0 -0
- package/assets/branding/projects.png +0 -0
- package/bin/ds.js +519 -63
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +338 -68
- package/docs/en/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +180 -4
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +66 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
- package/docs/en/11_LICENSE_AND_RISK.md +256 -0
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +446 -0
- package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +284 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +83 -0
- package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
- package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
- package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
- package/docs/images/weixin/weixin-settings-bind.svg +57 -0
- package/docs/zh/00_QUICK_START.md +345 -72
- package/docs/zh/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +181 -3
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +68 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
- package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +442 -0
- package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +285 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +129 -0
- package/install.sh +0 -34
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/annotations.py +343 -0
- package/src/deepscientist/artifact/arxiv.py +484 -37
- package/src/deepscientist/artifact/service.py +574 -108
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/monitor.py +7 -5
- package/src/deepscientist/bash_exec/service.py +93 -21
- package/src/deepscientist/bridges/builtins.py +2 -0
- package/src/deepscientist/bridges/connectors.py +447 -0
- package/src/deepscientist/channels/__init__.py +2 -0
- package/src/deepscientist/channels/builtins.py +3 -1
- package/src/deepscientist/channels/local.py +3 -3
- package/src/deepscientist/channels/qq.py +8 -8
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +14 -8
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +388 -0
- package/src/deepscientist/config/models.py +23 -2
- package/src/deepscientist/config/service.py +539 -67
- package/src/deepscientist/connector/__init__.py +4 -0
- package/src/deepscientist/connector/connector_profiles.py +481 -0
- package/src/deepscientist/connector/lingzhu_support.py +668 -0
- package/src/deepscientist/connector/qq_profiles.py +206 -0
- package/src/deepscientist/connector/weixin_support.py +663 -0
- package/src/deepscientist/connector_profiles.py +1 -374
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +165 -5
- package/src/deepscientist/daemon/api/router.py +13 -1
- package/src/deepscientist/daemon/app.py +1444 -67
- package/src/deepscientist/doctor.py +4 -5
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +135 -7
- package/src/deepscientist/prompts/builder.py +128 -11
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +23 -0
- package/src/deepscientist/quest/service.py +359 -74
- package/src/deepscientist/quest/stage_views.py +71 -5
- package/src/deepscientist/runners/codex.py +170 -19
- package/src/deepscientist/runners/runtime_overrides.py +6 -0
- package/src/deepscientist/shared.py +33 -14
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/qq.md +2 -1
- package/src/prompts/connectors/weixin.md +231 -0
- package/src/prompts/contracts/shared_interaction.md +4 -1
- package/src/prompts/system.md +61 -9
- package/src/skills/analysis-campaign/SKILL.md +46 -6
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
- package/src/skills/baseline/SKILL.md +1 -1
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +1 -1
- package/src/skills/finalize/SKILL.md +1 -1
- package/src/skills/idea/SKILL.md +1 -1
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/rebuttal/SKILL.md +74 -1
- package/src/skills/rebuttal/references/response-letter-template.md +55 -11
- package/src/skills/review/SKILL.md +118 -1
- package/src/skills/review/references/experiment-todo-template.md +23 -0
- package/src/skills/review/references/review-report-template.md +16 -0
- package/src/skills/review/references/revision-log-template.md +4 -0
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +168 -7
- package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-CnJcXynW.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-CB1YODQn.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-Ciz1gDaX.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BhmjNQRC.js} +37 -11
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-DmyHspXt.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-BMXKrDRk.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BTVYRGkm.js} +12 -12
- package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-CvcjJHXv.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DW2ej8Vk.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-CmlDxbhU.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DAjQZPSv.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C-nVAZb_.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.js → VNCViewer-D7-dIYon.js} +10 -10
- package/src/ui/dist/assets/bot-C_G4WtNI.js +21 -0
- package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
- package/src/ui/dist/assets/{code-BWAY76JP.js → code-Cd7WfiWq.js} +1 -1
- package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-B57zsL9y.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-DVoheLFq.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-B5kXFxZP.js} +1 -1
- package/src/ui/dist/assets/{image-D-NZM-6P.js → image-LLOjkMHF.js} +1 -1
- package/src/ui/dist/assets/{index-DGIYDuTv.css → index-BQG-1s2o.css} +40 -13
- package/src/ui/dist/assets/{index-DHZJ_0TI.js → index-C3r2iGrp.js} +12 -12
- package/src/ui/dist/assets/{index-7Chr1g9c.js → index-CLQauncb.js} +15050 -9561
- package/src/ui/dist/assets/index-Dxa2eYMY.js +25 -0
- package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-hOUOWbW2.js} +2 -2
- package/src/ui/dist/assets/{monaco-Cb2uKKe6.js → monaco-BGGAEii3.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DlEr1_y5.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-Bg72DGgT.js → popover-CWJbJuYY.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-CRJiucYO.js} +18 -77
- package/src/ui/dist/assets/select-CoHB7pvH.js +1690 -0
- package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-D5aJWR8J.js} +1 -1
- package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-DUK_mnkS.js} +2 -13
- package/src/ui/dist/assets/{trash-BvTgE5__.js → trash-ChU3SEE3.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-BrJBV3tY.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-C7Qqh-om.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.js → zoom-out-rtX0FKya.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
- package/src/ui/dist/assets/AutoFigurePlugin-C_wWw4AP.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-B0Dd8CxK.js +0 -158
- package/src/ui/dist/assets/bibtex-CKaefIN2.js +0 -189
- package/src/ui/dist/assets/file-utils-H2fjA46S.js +0 -109
- package/src/ui/dist/assets/message-square-BzjLiXir.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-C_mA6R0w.js +0 -108
|
@@ -153,8 +153,68 @@ class QuestStageViewBuilder:
|
|
|
153
153
|
return candidate
|
|
154
154
|
return self.quest_root
|
|
155
155
|
|
|
156
|
+
def _infer_stage_from_branch_name(self) -> str | None:
|
|
157
|
+
normalized = str(self.branch_name or "").strip().lower()
|
|
158
|
+
if not normalized:
|
|
159
|
+
return None
|
|
160
|
+
if normalized.startswith("analysis/"):
|
|
161
|
+
return "analysis"
|
|
162
|
+
if normalized.startswith("run/"):
|
|
163
|
+
return "experiment"
|
|
164
|
+
if normalized.startswith("idea/"):
|
|
165
|
+
return "idea"
|
|
166
|
+
if normalized.startswith("paper/") or normalized.startswith("write/"):
|
|
167
|
+
return "paper"
|
|
168
|
+
if normalized.startswith("baseline/"):
|
|
169
|
+
return "baseline"
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def _has_paper_state(self) -> bool:
|
|
173
|
+
paper_root = self._paper_root()
|
|
174
|
+
return bool(
|
|
175
|
+
self._paper_candidates()
|
|
176
|
+
or (paper_root / "selected_outline.json").exists()
|
|
177
|
+
or (paper_root / "draft.md").exists()
|
|
178
|
+
or self._paper_bundle_manifest()
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def _resolve_effective_stage_key(self) -> str:
|
|
182
|
+
normalized = normalize_stage_key(self.stage_key)
|
|
183
|
+
if normalized in {"baseline", "idea", "experiment", "analysis", "paper"}:
|
|
184
|
+
return normalized
|
|
185
|
+
if normalized != "general":
|
|
186
|
+
return normalized
|
|
187
|
+
|
|
188
|
+
inferred = self._infer_stage_from_branch_name()
|
|
189
|
+
if inferred:
|
|
190
|
+
return inferred
|
|
191
|
+
if self._analysis_stage_items(None):
|
|
192
|
+
return "analysis"
|
|
193
|
+
if self._experiment_stage_items():
|
|
194
|
+
return "experiment"
|
|
195
|
+
if self._idea_stage_items():
|
|
196
|
+
return "idea"
|
|
197
|
+
if self._has_paper_state():
|
|
198
|
+
return "paper"
|
|
199
|
+
if self._baseline_stage_items():
|
|
200
|
+
return "baseline"
|
|
201
|
+
return normalized
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def _artifact_detail(item: dict[str, Any] | None, payload: dict[str, Any]) -> dict[str, Any] | None:
|
|
205
|
+
if not isinstance(payload, dict) or not payload:
|
|
206
|
+
return None
|
|
207
|
+
record = dict(item or {})
|
|
208
|
+
return {
|
|
209
|
+
"artifact_id": payload.get("artifact_id") or payload.get("id"),
|
|
210
|
+
"artifact_kind": payload.get("kind"),
|
|
211
|
+
"artifact_path": record.get("path"),
|
|
212
|
+
"payload": payload,
|
|
213
|
+
}
|
|
214
|
+
|
|
156
215
|
def build(self) -> dict[str, Any]:
|
|
157
216
|
selection_type = str(self.selection.get("selection_type") or "").strip()
|
|
217
|
+
self.stage_key = self._resolve_effective_stage_key()
|
|
158
218
|
if selection_type == "branch_node" and self.stage_key not in {"experiment", "analysis", "paper"}:
|
|
159
219
|
return self._build_branch()
|
|
160
220
|
if self.stage_key == "baseline":
|
|
@@ -855,7 +915,8 @@ class QuestStageViewBuilder:
|
|
|
855
915
|
"draft_markdown": draft_markdown,
|
|
856
916
|
"literature_files": literature_files,
|
|
857
917
|
"decision_reason": payload.get("reason"),
|
|
858
|
-
}
|
|
918
|
+
},
|
|
919
|
+
"latest_artifact": self._artifact_detail(latest, payload),
|
|
859
920
|
},
|
|
860
921
|
lineage_intent=lineage_intent,
|
|
861
922
|
idea_draft_path=draft_md_rel_path,
|
|
@@ -1084,7 +1145,8 @@ class QuestStageViewBuilder:
|
|
|
1084
1145
|
else None,
|
|
1085
1146
|
"analysis_summary_path": self._relative_path_or_raw(analysis_summary_path),
|
|
1086
1147
|
"analysis_summary_markdown": analysis_summary_markdown,
|
|
1087
|
-
}
|
|
1148
|
+
},
|
|
1149
|
+
"latest_artifact": self._artifact_detail(latest_experiment_item or latest_idea_item, latest_experiment_payload or latest_idea_payload),
|
|
1088
1150
|
},
|
|
1089
1151
|
lineage_intent=lineage_intent,
|
|
1090
1152
|
idea_draft_path=idea_draft_rel_path,
|
|
@@ -1198,7 +1260,8 @@ class QuestStageViewBuilder:
|
|
|
1198
1260
|
"trace_summary": trace_summary,
|
|
1199
1261
|
"trace_markdown": trace_markdown,
|
|
1200
1262
|
"trace_actions": self._recent_trace_actions(),
|
|
1201
|
-
}
|
|
1263
|
+
},
|
|
1264
|
+
"latest_artifact": self._artifact_detail(latest, payload),
|
|
1202
1265
|
},
|
|
1203
1266
|
)
|
|
1204
1267
|
|
|
@@ -1388,10 +1451,12 @@ class QuestStageViewBuilder:
|
|
|
1388
1451
|
"todo_manifest_markdown": self._markdown_body_for_path(manifest.get("todo_manifest_path")),
|
|
1389
1452
|
"summary_path": self._relative_path_or_raw(summary_path) if summary_path else None,
|
|
1390
1453
|
"summary_markdown": summary_markdown,
|
|
1454
|
+
"manifest_payload": manifest,
|
|
1391
1455
|
"trace_summary": trace_summary,
|
|
1392
1456
|
"trace_markdown": self._trace_markdown(),
|
|
1393
1457
|
"trace_actions": self._recent_trace_actions(),
|
|
1394
|
-
}
|
|
1458
|
+
},
|
|
1459
|
+
"latest_artifact": self._artifact_detail(latest, latest_payload),
|
|
1395
1460
|
},
|
|
1396
1461
|
)
|
|
1397
1462
|
|
|
@@ -1532,6 +1597,7 @@ class QuestStageViewBuilder:
|
|
|
1532
1597
|
"latex_root_path": latex_root_rel,
|
|
1533
1598
|
"main_tex_path": main_tex_rel,
|
|
1534
1599
|
},
|
|
1535
|
-
}
|
|
1600
|
+
},
|
|
1601
|
+
"latest_artifact": self._artifact_detail(paper_items[-1] if paper_items else None, self._payload(paper_items[-1] if paper_items else {})),
|
|
1536
1602
|
},
|
|
1537
1603
|
)
|
|
@@ -19,6 +19,11 @@ from ..shared import append_jsonl, ensure_dir, generate_id, read_yaml, resolve_r
|
|
|
19
19
|
from ..web_search import extract_web_search_payload
|
|
20
20
|
from .base import RunRequest, RunResult
|
|
21
21
|
|
|
22
|
+
_TOOL_EVENT_ARGS_TEXT_LIMIT = 8_000
|
|
23
|
+
_TOOL_EVENT_OUTPUT_TEXT_LIMIT = 16_000
|
|
24
|
+
_MAX_QUEST_EVENT_JSON_BYTES = 2_000_000
|
|
25
|
+
_OVERSIZED_EVENT_PREVIEW_TEXT_LIMIT = 12_000
|
|
26
|
+
|
|
22
27
|
|
|
23
28
|
def _compact_text(value: object, *, limit: int = 1200) -> str:
|
|
24
29
|
if value is None:
|
|
@@ -35,6 +40,96 @@ def _compact_text(value: object, *, limit: int = 1200) -> str:
|
|
|
35
40
|
return text[: limit - 1].rstrip() + "…"
|
|
36
41
|
|
|
37
42
|
|
|
43
|
+
def _truncate_leaf_text(text: str, *, limit: int) -> str:
|
|
44
|
+
if limit <= 0 or len(text) <= limit:
|
|
45
|
+
return text
|
|
46
|
+
head = max(int(limit * 0.7), 256)
|
|
47
|
+
tail = max(limit - head - 64, 128)
|
|
48
|
+
omitted = max(len(text) - head - tail, 0)
|
|
49
|
+
return f"{text[:head].rstrip()}\n...[truncated {omitted} chars]...\n{text[-tail:].lstrip()}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _truncate_structured_value(value: object, *, string_limit: int) -> object:
|
|
53
|
+
if isinstance(value, str):
|
|
54
|
+
return _truncate_leaf_text(value.strip(), limit=string_limit)
|
|
55
|
+
if isinstance(value, list):
|
|
56
|
+
return [_truncate_structured_value(item, string_limit=string_limit) for item in value[:200]]
|
|
57
|
+
if isinstance(value, dict):
|
|
58
|
+
truncated: dict[object, object] = {}
|
|
59
|
+
for index, (key, item) in enumerate(value.items()):
|
|
60
|
+
if index >= 200:
|
|
61
|
+
truncated["__truncated__"] = f"truncated remaining {len(value) - 200} item(s)"
|
|
62
|
+
break
|
|
63
|
+
truncated[key] = _truncate_structured_value(item, string_limit=string_limit)
|
|
64
|
+
return truncated
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _structured_text(value: object, *, limit: int | None = None) -> str:
|
|
69
|
+
if value is None:
|
|
70
|
+
return ""
|
|
71
|
+
if isinstance(value, str):
|
|
72
|
+
return _truncate_leaf_text(value.strip(), limit=limit or len(value))
|
|
73
|
+
normalized_value = _truncate_structured_value(value, string_limit=max(limit or _TOOL_EVENT_OUTPUT_TEXT_LIMIT, 512))
|
|
74
|
+
try:
|
|
75
|
+
return json.dumps(normalized_value, ensure_ascii=False, indent=2)
|
|
76
|
+
except TypeError:
|
|
77
|
+
return _truncate_leaf_text(str(value), limit=limit or _TOOL_EVENT_OUTPUT_TEXT_LIMIT)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _encoded_json_size(value: object) -> int:
|
|
81
|
+
try:
|
|
82
|
+
return len(json.dumps(value, ensure_ascii=False).encode("utf-8"))
|
|
83
|
+
except Exception:
|
|
84
|
+
return len(str(value).encode("utf-8", errors="ignore"))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _compact_tool_event_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
88
|
+
if _encoded_json_size(payload) <= _MAX_QUEST_EVENT_JSON_BYTES:
|
|
89
|
+
return payload
|
|
90
|
+
|
|
91
|
+
compacted = dict(payload)
|
|
92
|
+
output_text = str(compacted.get("output") or "")
|
|
93
|
+
if output_text:
|
|
94
|
+
compacted["output_bytes"] = len(output_text.encode("utf-8", errors="ignore"))
|
|
95
|
+
compacted["output"] = _truncate_leaf_text(
|
|
96
|
+
output_text,
|
|
97
|
+
limit=_OVERSIZED_EVENT_PREVIEW_TEXT_LIMIT,
|
|
98
|
+
)
|
|
99
|
+
compacted["output_truncated"] = True
|
|
100
|
+
args_text = str(compacted.get("args") or "")
|
|
101
|
+
if args_text and _encoded_json_size(compacted) > _MAX_QUEST_EVENT_JSON_BYTES:
|
|
102
|
+
compacted["args"] = _truncate_leaf_text(args_text, limit=4_000)
|
|
103
|
+
compacted["args_truncated"] = True
|
|
104
|
+
if _encoded_json_size(compacted) > _MAX_QUEST_EVENT_JSON_BYTES:
|
|
105
|
+
metadata = compacted.get("metadata")
|
|
106
|
+
if isinstance(metadata, dict):
|
|
107
|
+
allowed_keys = {
|
|
108
|
+
"mcp_server",
|
|
109
|
+
"mcp_tool",
|
|
110
|
+
"bash_id",
|
|
111
|
+
"status",
|
|
112
|
+
"command",
|
|
113
|
+
"workdir",
|
|
114
|
+
"cwd",
|
|
115
|
+
"started_at",
|
|
116
|
+
"finished_at",
|
|
117
|
+
"exit_code",
|
|
118
|
+
"stop_reason",
|
|
119
|
+
"log_path",
|
|
120
|
+
}
|
|
121
|
+
compacted["metadata"] = {
|
|
122
|
+
key: metadata.get(key)
|
|
123
|
+
for key in allowed_keys
|
|
124
|
+
if key in metadata
|
|
125
|
+
}
|
|
126
|
+
compacted["metadata_truncated"] = True
|
|
127
|
+
if _encoded_json_size(compacted) > _MAX_QUEST_EVENT_JSON_BYTES:
|
|
128
|
+
compacted["output"] = _compact_text(compacted.get("output"), limit=2_000)
|
|
129
|
+
compacted["output_truncated"] = True
|
|
130
|
+
return compacted
|
|
131
|
+
|
|
132
|
+
|
|
38
133
|
def _iter_event_texts(event: dict[str, Any]) -> list[str]:
|
|
39
134
|
texts: list[str] = []
|
|
40
135
|
for key in ("text", "content", "message"):
|
|
@@ -184,7 +279,24 @@ def _tool_name(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
|
184
279
|
return "tool"
|
|
185
280
|
|
|
186
281
|
|
|
282
|
+
def _is_bash_exec_item(event: dict[str, Any], item: dict[str, Any]) -> bool:
|
|
283
|
+
server = str(item.get("server") or event.get("server") or "").strip()
|
|
284
|
+
tool = str(item.get("tool") or event.get("tool") or "").strip()
|
|
285
|
+
return server == "bash_exec" and tool == "bash_exec"
|
|
286
|
+
|
|
287
|
+
|
|
187
288
|
def _tool_args(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
289
|
+
if _is_bash_exec_item(event, item):
|
|
290
|
+
for value in (
|
|
291
|
+
item.get("arguments"),
|
|
292
|
+
event.get("arguments"),
|
|
293
|
+
item.get("input"),
|
|
294
|
+
event.get("input"),
|
|
295
|
+
):
|
|
296
|
+
text = _structured_text(value, limit=_TOOL_EVENT_ARGS_TEXT_LIMIT)
|
|
297
|
+
if text:
|
|
298
|
+
return text
|
|
299
|
+
return ""
|
|
188
300
|
for value in (
|
|
189
301
|
item.get("command"),
|
|
190
302
|
item.get("query"),
|
|
@@ -204,6 +316,21 @@ def _tool_args(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
|
204
316
|
|
|
205
317
|
|
|
206
318
|
def _tool_output(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
319
|
+
if _is_bash_exec_item(event, item):
|
|
320
|
+
for value in (
|
|
321
|
+
item.get("result"),
|
|
322
|
+
item.get("output"),
|
|
323
|
+
item.get("content"),
|
|
324
|
+
event.get("result"),
|
|
325
|
+
event.get("output"),
|
|
326
|
+
event.get("content"),
|
|
327
|
+
item.get("aggregated_output"),
|
|
328
|
+
event.get("aggregated_output"),
|
|
329
|
+
):
|
|
330
|
+
text = _structured_text(value, limit=_TOOL_EVENT_OUTPUT_TEXT_LIMIT)
|
|
331
|
+
if text:
|
|
332
|
+
return text
|
|
333
|
+
return ""
|
|
207
334
|
for value in (
|
|
208
335
|
item.get("aggregated_output"),
|
|
209
336
|
item.get("changes"),
|
|
@@ -253,10 +380,12 @@ def _mcp_tool_metadata(
|
|
|
253
380
|
metadata["workdir"] = arguments.get("workdir")
|
|
254
381
|
if isinstance(arguments.get("mode"), str):
|
|
255
382
|
metadata["mode"] = arguments.get("mode")
|
|
256
|
-
if
|
|
383
|
+
if arguments.get("timeout_seconds") is not None:
|
|
257
384
|
metadata["timeout_seconds"] = arguments.get("timeout_seconds")
|
|
258
385
|
if "comment" in arguments:
|
|
259
386
|
metadata["comment"] = arguments.get("comment")
|
|
387
|
+
if server == "bash_exec" and tool == "bash_exec" and isinstance(arguments.get("id"), str):
|
|
388
|
+
metadata["bash_id"] = arguments.get("id")
|
|
260
389
|
metadata["session_id"] = f"quest:{quest_id}"
|
|
261
390
|
metadata["agent_id"] = "pi"
|
|
262
391
|
metadata["agent_instance_id"] = run_id
|
|
@@ -266,12 +395,18 @@ def _mcp_tool_metadata(
|
|
|
266
395
|
for key in (
|
|
267
396
|
"bash_id",
|
|
268
397
|
"status",
|
|
398
|
+
"command",
|
|
399
|
+
"workdir",
|
|
400
|
+
"cwd",
|
|
401
|
+
"kind",
|
|
402
|
+
"comment",
|
|
269
403
|
"started_at",
|
|
270
404
|
"finished_at",
|
|
271
405
|
"exit_code",
|
|
272
406
|
"stop_reason",
|
|
273
407
|
"last_progress",
|
|
274
408
|
"log_path",
|
|
409
|
+
"watchdog_after_seconds",
|
|
275
410
|
):
|
|
276
411
|
if key in result_payload:
|
|
277
412
|
metadata[key] = result_payload.get(key)
|
|
@@ -310,7 +445,7 @@ def _tool_event(
|
|
|
310
445
|
"raw_event_type": event_type,
|
|
311
446
|
"created_at": created_at,
|
|
312
447
|
}
|
|
313
|
-
return {
|
|
448
|
+
return _compact_tool_event_payload({
|
|
314
449
|
"event_id": generate_id("evt"),
|
|
315
450
|
"type": "runner.tool_result",
|
|
316
451
|
"quest_id": quest_id,
|
|
@@ -324,7 +459,7 @@ def _tool_event(
|
|
|
324
459
|
"output": _tool_output(event, item),
|
|
325
460
|
"raw_event_type": event_type,
|
|
326
461
|
"created_at": created_at,
|
|
327
|
-
}
|
|
462
|
+
})
|
|
328
463
|
|
|
329
464
|
if item_type == "web_search":
|
|
330
465
|
tool_call_id = _tool_call_id(event, item)
|
|
@@ -348,7 +483,7 @@ def _tool_event(
|
|
|
348
483
|
"raw_event_type": event_type,
|
|
349
484
|
"created_at": created_at,
|
|
350
485
|
}
|
|
351
|
-
return {
|
|
486
|
+
return _compact_tool_event_payload({
|
|
352
487
|
"event_id": generate_id("evt"),
|
|
353
488
|
"type": "runner.tool_result",
|
|
354
489
|
"quest_id": quest_id,
|
|
@@ -363,13 +498,13 @@ def _tool_event(
|
|
|
363
498
|
"metadata": metadata,
|
|
364
499
|
"raw_event_type": event_type,
|
|
365
500
|
"created_at": created_at,
|
|
366
|
-
}
|
|
501
|
+
})
|
|
367
502
|
|
|
368
503
|
if item_type == "file_change":
|
|
369
504
|
tool_call_id = _tool_call_id(event, item)
|
|
370
505
|
tool_name = "file_change"
|
|
371
506
|
known_tool_names[tool_call_id] = tool_name
|
|
372
|
-
return {
|
|
507
|
+
return _compact_tool_event_payload({
|
|
373
508
|
"event_id": generate_id("evt"),
|
|
374
509
|
"type": "runner.tool_result",
|
|
375
510
|
"quest_id": quest_id,
|
|
@@ -382,7 +517,7 @@ def _tool_event(
|
|
|
382
517
|
"output": _tool_output(event, item),
|
|
383
518
|
"raw_event_type": event_type,
|
|
384
519
|
"created_at": created_at,
|
|
385
|
-
}
|
|
520
|
+
})
|
|
386
521
|
|
|
387
522
|
if item_type == "mcp_tool_call":
|
|
388
523
|
tool_call_id = _tool_call_id(event, item)
|
|
@@ -415,7 +550,7 @@ def _tool_event(
|
|
|
415
550
|
"raw_event_type": event_type,
|
|
416
551
|
"created_at": created_at,
|
|
417
552
|
}
|
|
418
|
-
return {
|
|
553
|
+
return _compact_tool_event_payload({
|
|
419
554
|
"event_id": generate_id("evt"),
|
|
420
555
|
"type": "runner.tool_result",
|
|
421
556
|
"quest_id": quest_id,
|
|
@@ -432,7 +567,7 @@ def _tool_event(
|
|
|
432
567
|
"metadata": metadata,
|
|
433
568
|
"raw_event_type": event_type,
|
|
434
569
|
"created_at": created_at,
|
|
435
|
-
}
|
|
570
|
+
})
|
|
436
571
|
|
|
437
572
|
if item_type in {"function_call", "custom_tool_call", "tool_call"} or "function_call" in event_type or "tool_call" in event_type:
|
|
438
573
|
tool_call_id = _tool_call_id(event, item)
|
|
@@ -456,7 +591,7 @@ def _tool_event(
|
|
|
456
591
|
if item_type in {"function_call_output", "custom_tool_call_output", "tool_result", "tool_call_output"} or "function_call_output" in event_type or "tool_result" in event_type:
|
|
457
592
|
tool_call_id = _tool_call_id(event, item)
|
|
458
593
|
tool_name = known_tool_names.get(tool_call_id) or _tool_name(event, item)
|
|
459
|
-
return {
|
|
594
|
+
return _compact_tool_event_payload({
|
|
460
595
|
"event_id": generate_id("evt"),
|
|
461
596
|
"type": "runner.tool_result",
|
|
462
597
|
"quest_id": quest_id,
|
|
@@ -470,7 +605,7 @@ def _tool_event(
|
|
|
470
605
|
"output": _tool_output(event, item),
|
|
471
606
|
"raw_event_type": event_type,
|
|
472
607
|
"created_at": created_at,
|
|
473
|
-
}
|
|
608
|
+
})
|
|
474
609
|
|
|
475
610
|
return None
|
|
476
611
|
|
|
@@ -531,6 +666,12 @@ class CodexRunner:
|
|
|
531
666
|
)
|
|
532
667
|
|
|
533
668
|
env = dict(**os.environ)
|
|
669
|
+
runner_env = runner_config.get("env") if isinstance(runner_config.get("env"), dict) else {}
|
|
670
|
+
for key, value in runner_env.items():
|
|
671
|
+
env_key = str(key or "").strip()
|
|
672
|
+
if not env_key or value is None:
|
|
673
|
+
continue
|
|
674
|
+
env[env_key] = str(value)
|
|
534
675
|
env["CODEX_HOME"] = str(codex_home)
|
|
535
676
|
env["DEEPSCIENTIST_HOME"] = str(self.home)
|
|
536
677
|
env["DS_HOME"] = str(self.home)
|
|
@@ -758,17 +899,25 @@ class CodexRunner:
|
|
|
758
899
|
workspace_root = request.worktree_root or request.quest_root
|
|
759
900
|
resolved_binary = resolve_runner_binary(self.binary, runner_name="codex")
|
|
760
901
|
resolved_runner_config = runner_config if isinstance(runner_config, dict) else self._load_runner_config()
|
|
902
|
+
profile = str(resolved_runner_config.get("profile") or "").strip()
|
|
903
|
+
normalized_model = str(request.model or "").strip()
|
|
761
904
|
command = [
|
|
762
905
|
resolved_binary or self.binary,
|
|
763
906
|
"--search",
|
|
764
|
-
"exec",
|
|
765
|
-
"--json",
|
|
766
|
-
"--cd",
|
|
767
|
-
str(workspace_root),
|
|
768
|
-
"--skip-git-repo-check",
|
|
769
|
-
"--model",
|
|
770
|
-
request.model,
|
|
771
907
|
]
|
|
908
|
+
if profile:
|
|
909
|
+
command.extend(["--profile", profile])
|
|
910
|
+
command.extend(
|
|
911
|
+
[
|
|
912
|
+
"exec",
|
|
913
|
+
"--json",
|
|
914
|
+
"--cd",
|
|
915
|
+
str(workspace_root),
|
|
916
|
+
"--skip-git-repo-check",
|
|
917
|
+
]
|
|
918
|
+
)
|
|
919
|
+
if normalized_model.lower() not in {"", "inherit", "default", "codex-default"}:
|
|
920
|
+
command.extend(["--model", normalized_model])
|
|
772
921
|
if request.approval_policy:
|
|
773
922
|
command.extend(["-c", f'approval_policy="{request.approval_policy}"'])
|
|
774
923
|
reasoning_effort = request.reasoning_effort
|
|
@@ -794,7 +943,9 @@ class CodexRunner:
|
|
|
794
943
|
runner_config: dict[str, Any] | None = None,
|
|
795
944
|
) -> Path:
|
|
796
945
|
target = ensure_dir(workspace_root / ".codex")
|
|
797
|
-
|
|
946
|
+
resolved_runner_config = runner_config if isinstance(runner_config, dict) else self._load_runner_config()
|
|
947
|
+
configured_home = str(resolved_runner_config.get("config_dir") or os.environ.get("CODEX_HOME") or str(Path.home() / ".codex"))
|
|
948
|
+
source = Path(configured_home).expanduser()
|
|
798
949
|
for filename in ("config.toml", "auth.json"):
|
|
799
950
|
source_path = source / filename
|
|
800
951
|
target_path = target / filename
|
|
@@ -20,6 +20,8 @@ def _as_bool_env(name: str) -> bool:
|
|
|
20
20
|
def codex_runtime_overrides() -> dict[str, str]:
|
|
21
21
|
approval_policy = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_APPROVAL_POLICY"))
|
|
22
22
|
sandbox_mode = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_SANDBOX_MODE"))
|
|
23
|
+
profile = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_PROFILE"))
|
|
24
|
+
model = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_MODEL"))
|
|
23
25
|
|
|
24
26
|
if _as_bool_env("DEEPSCIENTIST_CODEX_YOLO"):
|
|
25
27
|
approval_policy = approval_policy or "never"
|
|
@@ -30,6 +32,10 @@ def codex_runtime_overrides() -> dict[str, str]:
|
|
|
30
32
|
overrides["approval_policy"] = approval_policy
|
|
31
33
|
if sandbox_mode:
|
|
32
34
|
overrides["sandbox_mode"] = sandbox_mode
|
|
35
|
+
if profile:
|
|
36
|
+
overrides["profile"] = profile
|
|
37
|
+
if model:
|
|
38
|
+
overrides["model"] = model
|
|
33
39
|
return overrides
|
|
34
40
|
|
|
35
41
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections import deque
|
|
3
4
|
import hashlib
|
|
4
5
|
import json
|
|
5
6
|
import os
|
|
@@ -9,7 +10,7 @@ import subprocess
|
|
|
9
10
|
import sys
|
|
10
11
|
from datetime import UTC, datetime
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import Any
|
|
13
|
+
from typing import Any, Iterator
|
|
13
14
|
from uuid import uuid4
|
|
14
15
|
|
|
15
16
|
try:
|
|
@@ -90,21 +91,39 @@ def append_jsonl(path: Path, payload: dict[str, Any]) -> None:
|
|
|
90
91
|
handle.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
91
92
|
|
|
92
93
|
|
|
93
|
-
def
|
|
94
|
+
def iter_jsonl(path: Path | str) -> Iterator[dict[str, Any]]:
|
|
95
|
+
path = Path(path)
|
|
94
96
|
if not path.exists():
|
|
97
|
+
return
|
|
98
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
99
|
+
for raw_line in handle:
|
|
100
|
+
line = raw_line.strip()
|
|
101
|
+
if not line:
|
|
102
|
+
continue
|
|
103
|
+
try:
|
|
104
|
+
payload = json.loads(line)
|
|
105
|
+
except json.JSONDecodeError:
|
|
106
|
+
continue
|
|
107
|
+
if isinstance(payload, dict):
|
|
108
|
+
yield payload
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def read_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
112
|
+
return list(iter_jsonl(path))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def count_jsonl(path: Path | str) -> int:
|
|
116
|
+
return sum(1 for _ in iter_jsonl(path))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def read_jsonl_tail(path: Path | str, limit: int) -> list[dict[str, Any]]:
|
|
120
|
+
normalized_limit = max(int(limit or 0), 0)
|
|
121
|
+
if normalized_limit <= 0:
|
|
95
122
|
return []
|
|
96
|
-
items:
|
|
97
|
-
for
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
continue
|
|
101
|
-
try:
|
|
102
|
-
payload = json.loads(line)
|
|
103
|
-
except json.JSONDecodeError:
|
|
104
|
-
continue
|
|
105
|
-
if isinstance(payload, dict):
|
|
106
|
-
items.append(payload)
|
|
107
|
-
return items
|
|
123
|
+
items: deque[dict[str, Any]] = deque(maxlen=normalized_limit)
|
|
124
|
+
for payload in iter_jsonl(path):
|
|
125
|
+
items.append(payload)
|
|
126
|
+
return list(items)
|
|
108
127
|
|
|
109
128
|
|
|
110
129
|
def read_yaml(path: Path, default: Any = None) -> Any:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .connector.weixin_support import * # noqa: F401,F403
|
|
@@ -11,5 +11,7 @@
|
|
|
11
11
|
- lingzhu_safety_rule: request only actions that are clearly justified by the current quest and understandable to the human user
|
|
12
12
|
- lingzhu_text_rule: even when requesting `surface_actions`, always include a clear text explanation of what is happening and why
|
|
13
13
|
- lingzhu_reply_style_rule: for Lingzhu-facing user-visible text sent through `artifact.interact(...)`, keep the message clear, concise, respectful, and high-information-density
|
|
14
|
-
- lingzhu_reply_length_rule: for each Lingzhu-facing `artifact.interact(...)` message, normally
|
|
14
|
+
- lingzhu_reply_length_rule: for each Lingzhu-facing `artifact.interact(...)` message, normally keep the text within about 20 Chinese characters or one very short sentence unless the user explicitly asks for more detail
|
|
15
15
|
- lingzhu_summary_first_rule: in Lingzhu-facing `artifact.interact(...)` messages, usually give only the synopsis and key facts needed for the user's next decision or understanding; avoid long preambles, repetition, and low-signal detail
|
|
16
|
+
- lingzhu_task_gate_rule: only treat a Lingzhu user utterance as a new quest instruction when the text explicitly starts with `我现在的任务是`; otherwise assume the device is polling for queued progress or buffered replies
|
|
17
|
+
- lingzhu_poll_rule: when Lingzhu is polling rather than giving a new task, return only the buffered progress checkpoints or the latest short status; do not reinterpret the poll text as a fresh instruction
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
- qq_summary_first_rule: start with the conclusion the user cares about, then what it means, then the next action
|
|
11
11
|
- qq_progress_shape_rule: make the current task, the main difficulty or latest real progress, and the next concrete measure explicit whenever possible
|
|
12
12
|
- qq_eta_rule: for baseline reproduction, main experiments, analysis experiments, and other important long-running research phases, include a rough ETA for the next meaningful result or the next update; if uncertain, say that and still give the next check-in window
|
|
13
|
-
- qq_tool_call_keepalive_rule: for ordinary active work, prefer one concise QQ progress update after roughly
|
|
13
|
+
- qq_tool_call_keepalive_rule: for ordinary active work, prefer one concise QQ progress update after roughly 6 tool calls when there is already a human-meaningful delta, and do not let work drift beyond roughly 12 tool calls or about 8 minutes without a user-visible checkpoint
|
|
14
|
+
- qq_read_plan_keepalive_rule: if the active work is still mostly reading, comparison, or planning, do not wait too long for a "big result"; send a short QQ-facing checkpoint after about 5 consecutive tool calls if the user would otherwise see silence
|
|
14
15
|
- qq_internal_detail_rule: omit worker names, heartbeat timestamps, retry counters, pending/running/completed counts, file names, and monitor-window narration unless the user asked for them or the detail changes the recommended action
|
|
15
16
|
- qq_translation_rule: convert internal execution and file-management work into user value, such as saying the baseline record is now organized for easier later comparison instead of listing touched files
|
|
16
17
|
- qq_preflight_rule: before sending a QQ progress update, rewrite it if it still sounds like a monitoring log, execution diary, or file inventory
|