@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
|
@@ -62,6 +62,26 @@ def _field(label: str, value: object, *, tone: str = "default") -> dict[str, Any
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
def _selection_score_summary(value: object) -> str | None:
|
|
66
|
+
if not isinstance(value, dict):
|
|
67
|
+
return None
|
|
68
|
+
parts: list[str] = []
|
|
69
|
+
for key, raw in value.items():
|
|
70
|
+
name = str(key or "").strip()
|
|
71
|
+
if not name:
|
|
72
|
+
continue
|
|
73
|
+
if isinstance(raw, float):
|
|
74
|
+
rendered = f"{raw:.4f}".rstrip("0").rstrip(".")
|
|
75
|
+
else:
|
|
76
|
+
rendered = str(raw).strip()
|
|
77
|
+
if not rendered:
|
|
78
|
+
continue
|
|
79
|
+
parts.append(f"{name}={rendered}")
|
|
80
|
+
if len(parts) >= 4:
|
|
81
|
+
break
|
|
82
|
+
return " · ".join(parts) or None
|
|
83
|
+
|
|
84
|
+
|
|
65
85
|
def _evaluation_summary(value: object) -> dict[str, Any]:
|
|
66
86
|
if not isinstance(value, dict):
|
|
67
87
|
return {}
|
|
@@ -214,8 +234,11 @@ class QuestStageViewBuilder:
|
|
|
214
234
|
|
|
215
235
|
def build(self) -> dict[str, Any]:
|
|
216
236
|
selection_type = str(self.selection.get("selection_type") or "").strip()
|
|
237
|
+
explicit_stage_key = str(self.selection.get("stage_key") or "").strip()
|
|
217
238
|
self.stage_key = self._resolve_effective_stage_key()
|
|
218
|
-
if selection_type == "
|
|
239
|
+
if selection_type == "idea_candidate":
|
|
240
|
+
return self._build_idea_candidate()
|
|
241
|
+
if selection_type == "branch_node" and not explicit_stage_key:
|
|
219
242
|
return self._build_branch()
|
|
220
243
|
if self.stage_key == "baseline":
|
|
221
244
|
return self._build_baseline()
|
|
@@ -280,6 +303,8 @@ class QuestStageViewBuilder:
|
|
|
280
303
|
normalized = [str(item).strip() for item in raw if str(item).strip()]
|
|
281
304
|
if normalized:
|
|
282
305
|
return normalized
|
|
306
|
+
if str(self.selection.get("selection_type") or "").strip() == "idea_candidate":
|
|
307
|
+
return self._idea_candidate_scope_paths()
|
|
283
308
|
if str(self.selection.get("selection_type") or "").strip() == "branch_node":
|
|
284
309
|
return self._branch_scope_paths()
|
|
285
310
|
defaults = {
|
|
@@ -693,6 +718,13 @@ class QuestStageViewBuilder:
|
|
|
693
718
|
"artifacts/reports",
|
|
694
719
|
]
|
|
695
720
|
|
|
721
|
+
def _idea_candidate_scope_paths(self) -> list[str]:
|
|
722
|
+
candidate_id = str(self.selection.get("selection_ref") or self.selection.get("idea_id") or "").strip()
|
|
723
|
+
return [
|
|
724
|
+
*( [f"memory/ideas/_candidates/{candidate_id}"] if candidate_id else []),
|
|
725
|
+
"artifacts/reports",
|
|
726
|
+
]
|
|
727
|
+
|
|
696
728
|
def _experiment_scope_paths(self, run_id: str | None) -> list[str]:
|
|
697
729
|
return [
|
|
698
730
|
*( [f"experiments/main/{run_id}"] if run_id else []),
|
|
@@ -917,6 +949,25 @@ class QuestStageViewBuilder:
|
|
|
917
949
|
items.append(item)
|
|
918
950
|
return items
|
|
919
951
|
|
|
952
|
+
def _idea_candidate_stage_items(self) -> list[dict[str, Any]]:
|
|
953
|
+
candidate_id = str(self.selection.get("selection_ref") or self.selection.get("idea_id") or "").strip()
|
|
954
|
+
if not candidate_id:
|
|
955
|
+
return []
|
|
956
|
+
items: list[dict[str, Any]] = []
|
|
957
|
+
for item in self.artifacts:
|
|
958
|
+
payload = self._payload(item)
|
|
959
|
+
if str(payload.get("kind") or "").strip() != "idea":
|
|
960
|
+
continue
|
|
961
|
+
if str(payload.get("idea_id") or "").strip() != candidate_id:
|
|
962
|
+
continue
|
|
963
|
+
flow_type = str(payload.get("flow_type") or "").strip()
|
|
964
|
+
protocol_step = str(payload.get("protocol_step") or "").strip()
|
|
965
|
+
details = dict(payload.get("details") or {}) if isinstance(payload.get("details"), dict) else {}
|
|
966
|
+
submission_mode = str(details.get("submission_mode") or payload.get("submission_mode") or "").strip().lower()
|
|
967
|
+
if flow_type == "idea_submission" and (protocol_step == "candidate" or submission_mode == "candidate"):
|
|
968
|
+
items.append(item)
|
|
969
|
+
return items
|
|
970
|
+
|
|
920
971
|
def _build_idea(self) -> dict[str, Any]:
|
|
921
972
|
idea_items = self._idea_stage_items()
|
|
922
973
|
latest = idea_items[-1] if idea_items else None
|
|
@@ -944,6 +995,8 @@ class QuestStageViewBuilder:
|
|
|
944
995
|
draft_md_rel_path = self._relative_path_or_raw(draft_md_path)
|
|
945
996
|
draft_markdown = self._markdown_body_for_path(draft_md_path)
|
|
946
997
|
lineage_intent = str(payload.get("lineage_intent") or details.get("lineage_intent") or "").strip() or None
|
|
998
|
+
selection_scores = details.get("selection_scores")
|
|
999
|
+
selection_score_summary = _selection_score_summary(selection_scores)
|
|
947
1000
|
note = (
|
|
948
1001
|
str(payload.get("summary") or payload.get("reason") or "").strip()
|
|
949
1002
|
or "No durable idea submission has been recorded yet."
|
|
@@ -987,6 +1040,11 @@ class QuestStageViewBuilder:
|
|
|
987
1040
|
_field("Problem", details.get("problem") or "Not recorded"),
|
|
988
1041
|
_field("Hypothesis", details.get("hypothesis") or "Not recorded"),
|
|
989
1042
|
_field("Mechanism", details.get("mechanism") or "Not recorded"),
|
|
1043
|
+
_field("Method Brief", details.get("method_brief") or "Not recorded"),
|
|
1044
|
+
_field("Selection Scores", selection_score_summary or "Not recorded"),
|
|
1045
|
+
_field("Mechanism Family", details.get("mechanism_family") or "Not recorded"),
|
|
1046
|
+
_field("Change Layer", details.get("change_layer") or "Not recorded"),
|
|
1047
|
+
_field("Source Lens", details.get("source_lens") or "Not recorded"),
|
|
990
1048
|
_field("Expected Gain", details.get("expected_gain") or "Not recorded"),
|
|
991
1049
|
_field("Risks", details.get("risks") or "Not recorded"),
|
|
992
1050
|
_field("Evidence Paths", details.get("evidence_paths") or "Not recorded"),
|
|
@@ -1009,6 +1067,11 @@ class QuestStageViewBuilder:
|
|
|
1009
1067
|
"problem": details.get("problem"),
|
|
1010
1068
|
"hypothesis": details.get("hypothesis"),
|
|
1011
1069
|
"mechanism": details.get("mechanism"),
|
|
1070
|
+
"method_brief": details.get("method_brief"),
|
|
1071
|
+
"selection_scores": selection_scores or None,
|
|
1072
|
+
"mechanism_family": details.get("mechanism_family"),
|
|
1073
|
+
"change_layer": details.get("change_layer"),
|
|
1074
|
+
"source_lens": details.get("source_lens"),
|
|
1012
1075
|
"expected_gain": details.get("expected_gain"),
|
|
1013
1076
|
"risks": details.get("risks") or [],
|
|
1014
1077
|
"evidence_paths": details.get("evidence_paths") or [],
|
|
@@ -1028,6 +1091,101 @@ class QuestStageViewBuilder:
|
|
|
1028
1091
|
subviews=["overview", "details", "draft"] if draft_markdown else ["overview", "details"],
|
|
1029
1092
|
)
|
|
1030
1093
|
|
|
1094
|
+
def _build_idea_candidate(self) -> dict[str, Any]:
|
|
1095
|
+
candidate_items = self._idea_candidate_stage_items()
|
|
1096
|
+
latest = candidate_items[-1] if candidate_items else None
|
|
1097
|
+
payload = self._payload(latest or {})
|
|
1098
|
+
details = dict(payload.get("details") or {}) if isinstance(payload.get("details"), dict) else {}
|
|
1099
|
+
candidate_id = str(self.selection.get("selection_ref") or payload.get("idea_id") or "candidate").strip() or "candidate"
|
|
1100
|
+
title_text = (
|
|
1101
|
+
str(details.get("title") or self.selection.get("label") or candidate_id).strip() or candidate_id
|
|
1102
|
+
)
|
|
1103
|
+
paths = dict(payload.get("paths") or {}) if isinstance(payload.get("paths"), dict) else {}
|
|
1104
|
+
candidate_root = paths.get("candidate_root") or str(self.quest_root / "memory" / "ideas" / "_candidates" / candidate_id)
|
|
1105
|
+
idea_md_path = paths.get("idea_md") or str(Path(candidate_root) / "idea.md")
|
|
1106
|
+
draft_md_path = paths.get("idea_draft_md") or details.get("idea_draft_path") or str(Path(candidate_root) / "draft.md")
|
|
1107
|
+
idea_markdown = self._markdown_body_for_path(idea_md_path)
|
|
1108
|
+
draft_markdown = self._markdown_body_for_path(draft_md_path)
|
|
1109
|
+
idea_md_rel_path = self._relative_path_or_raw(idea_md_path)
|
|
1110
|
+
draft_md_rel_path = self._relative_path_or_raw(draft_md_path)
|
|
1111
|
+
candidate_root_rel_path = self._relative_path_or_raw(candidate_root)
|
|
1112
|
+
selection_scores = details.get("selection_scores")
|
|
1113
|
+
selection_score_summary = _selection_score_summary(selection_scores)
|
|
1114
|
+
note = (
|
|
1115
|
+
str(payload.get("summary") or payload.get("reason") or self.selection.get("summary") or "").strip()
|
|
1116
|
+
or "No durable candidate brief summary has been recorded yet."
|
|
1117
|
+
)
|
|
1118
|
+
lineage_intent = str(payload.get("lineage_intent") or details.get("lineage_intent") or "").strip() or None
|
|
1119
|
+
parent_branch = str(payload.get("parent_branch") or details.get("parent_branch") or self.selection.get("branch_name") or "").strip() or None
|
|
1120
|
+
foundation_reason = str(payload.get("foundation_reason") or details.get("foundation_reason") or "").strip() or None
|
|
1121
|
+
return self._base_payload(
|
|
1122
|
+
title=f"Candidate Brief · {title_text}",
|
|
1123
|
+
note=note,
|
|
1124
|
+
status=str(payload.get("status") or "candidate").strip() or "candidate",
|
|
1125
|
+
tags=[
|
|
1126
|
+
"candidate-brief",
|
|
1127
|
+
details.get("mechanism_family") or "",
|
|
1128
|
+
details.get("change_layer") or "",
|
|
1129
|
+
details.get("source_lens") or "",
|
|
1130
|
+
lineage_intent or "",
|
|
1131
|
+
],
|
|
1132
|
+
overview=[
|
|
1133
|
+
_field("Candidate ID", candidate_id),
|
|
1134
|
+
_field("Parent Branch", parent_branch or "Not recorded"),
|
|
1135
|
+
_field("Next Target", details.get("next_target") or "optimize"),
|
|
1136
|
+
_field("Candidate Root", candidate_root_rel_path or candidate_root),
|
|
1137
|
+
],
|
|
1138
|
+
key_facts=[
|
|
1139
|
+
_field("Problem", details.get("problem") or "Not recorded"),
|
|
1140
|
+
_field("Hypothesis", details.get("hypothesis") or "Not recorded"),
|
|
1141
|
+
_field("Mechanism", details.get("mechanism") or "Not recorded"),
|
|
1142
|
+
_field("Method Brief", details.get("method_brief") or "Not recorded"),
|
|
1143
|
+
_field("Selection Scores", selection_score_summary or "Not recorded"),
|
|
1144
|
+
_field("Mechanism Family", details.get("mechanism_family") or "Not recorded"),
|
|
1145
|
+
_field("Change Layer", details.get("change_layer") or "Not recorded"),
|
|
1146
|
+
_field("Source Lens", details.get("source_lens") or "Not recorded"),
|
|
1147
|
+
_field("Expected Gain", details.get("expected_gain") or "Not recorded"),
|
|
1148
|
+
_field("Foundation Reason", foundation_reason or "Not recorded"),
|
|
1149
|
+
],
|
|
1150
|
+
key_files=self._dedupe_files(
|
|
1151
|
+
[
|
|
1152
|
+
self._file_entry(candidate_root, label="Candidate Root", description="Branchless candidate brief workspace.", expected_kind="directory"),
|
|
1153
|
+
self._file_entry(idea_md_path, label="Candidate Markdown", description="Durable candidate brief document."),
|
|
1154
|
+
self._file_entry(draft_md_path, label="Candidate Draft", description="Long-form candidate brief draft."),
|
|
1155
|
+
]
|
|
1156
|
+
),
|
|
1157
|
+
history=self._artifact_history(candidate_items),
|
|
1158
|
+
details={
|
|
1159
|
+
"idea": {
|
|
1160
|
+
"idea_id": candidate_id,
|
|
1161
|
+
"title": title_text,
|
|
1162
|
+
"problem": details.get("problem"),
|
|
1163
|
+
"hypothesis": details.get("hypothesis"),
|
|
1164
|
+
"mechanism": details.get("mechanism"),
|
|
1165
|
+
"method_brief": details.get("method_brief"),
|
|
1166
|
+
"selection_scores": selection_scores or None,
|
|
1167
|
+
"mechanism_family": details.get("mechanism_family"),
|
|
1168
|
+
"change_layer": details.get("change_layer"),
|
|
1169
|
+
"source_lens": details.get("source_lens"),
|
|
1170
|
+
"expected_gain": details.get("expected_gain"),
|
|
1171
|
+
"next_target": details.get("next_target") or "optimize",
|
|
1172
|
+
"lineage_intent": lineage_intent,
|
|
1173
|
+
"parent_branch": parent_branch,
|
|
1174
|
+
"candidate_root": candidate_root_rel_path or candidate_root,
|
|
1175
|
+
"idea_path": idea_md_rel_path,
|
|
1176
|
+
"idea_markdown": idea_markdown,
|
|
1177
|
+
"draft_path": draft_md_rel_path,
|
|
1178
|
+
"draft_markdown": draft_markdown,
|
|
1179
|
+
"decision_reason": payload.get("reason"),
|
|
1180
|
+
},
|
|
1181
|
+
"latest_artifact": self._artifact_detail(latest, payload),
|
|
1182
|
+
},
|
|
1183
|
+
lineage_intent=lineage_intent,
|
|
1184
|
+
idea_draft_path=draft_md_rel_path,
|
|
1185
|
+
draft_available=bool(draft_markdown),
|
|
1186
|
+
subviews=["overview", "details", "draft"] if draft_markdown else ["overview", "details"],
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1031
1189
|
def _build_branch(self) -> dict[str, Any]:
|
|
1032
1190
|
idea_items = [
|
|
1033
1191
|
item
|
|
@@ -1050,6 +1208,8 @@ class QuestStageViewBuilder:
|
|
|
1050
1208
|
idea_title = str(latest_idea_details.get("title") or "").strip() or None
|
|
1051
1209
|
idea_problem = str(latest_idea_details.get("problem") or "").strip() or None
|
|
1052
1210
|
next_target = str(latest_idea_details.get("next_target") or "").strip() or None
|
|
1211
|
+
selection_scores = latest_idea_details.get("selection_scores")
|
|
1212
|
+
selection_score_summary = _selection_score_summary(selection_scores)
|
|
1053
1213
|
lineage_intent = str(
|
|
1054
1214
|
latest_idea_payload.get("lineage_intent")
|
|
1055
1215
|
or latest_idea_details.get("lineage_intent")
|
|
@@ -1129,11 +1289,15 @@ class QuestStageViewBuilder:
|
|
|
1129
1289
|
for item in self.artifacts
|
|
1130
1290
|
if self._branch_matches(self._payload(item), allow_parent=True, include_unscoped=False)
|
|
1131
1291
|
]
|
|
1292
|
+
latest_branch_payload = self._payload(branch_items[-1] if branch_items else {})
|
|
1132
1293
|
note = (
|
|
1133
1294
|
str(
|
|
1134
1295
|
latest_experiment_payload.get("summary")
|
|
1135
1296
|
or latest_idea_payload.get("summary")
|
|
1136
1297
|
or latest_idea_payload.get("reason")
|
|
1298
|
+
or latest_branch_payload.get("summary")
|
|
1299
|
+
or latest_branch_payload.get("message")
|
|
1300
|
+
or latest_branch_payload.get("reason")
|
|
1137
1301
|
or self.trace.get("summary")
|
|
1138
1302
|
or self.selection.get("summary")
|
|
1139
1303
|
or ""
|
|
@@ -1169,6 +1333,11 @@ class QuestStageViewBuilder:
|
|
|
1169
1333
|
key_facts=[
|
|
1170
1334
|
_field("Idea Title", idea_title or "Not recorded"),
|
|
1171
1335
|
_field("Idea Problem", idea_problem or "Not recorded"),
|
|
1336
|
+
_field("Method Brief", latest_idea_details.get("method_brief") or "Not recorded"),
|
|
1337
|
+
_field("Selection Scores", selection_score_summary or "Not recorded"),
|
|
1338
|
+
_field("Mechanism Family", latest_idea_details.get("mechanism_family") or "Not recorded"),
|
|
1339
|
+
_field("Change Layer", latest_idea_details.get("change_layer") or "Not recorded"),
|
|
1340
|
+
_field("Source Lens", latest_idea_details.get("source_lens") or "Not recorded"),
|
|
1172
1341
|
_field("Foundation", foundation_label or "Current head"),
|
|
1173
1342
|
_field("Foundation Reason", foundation_reason or "Not recorded"),
|
|
1174
1343
|
_field("Next Target", next_target or "Not recorded"),
|
|
@@ -1224,6 +1393,11 @@ class QuestStageViewBuilder:
|
|
|
1224
1393
|
"lineage_intent": lineage_intent,
|
|
1225
1394
|
"idea_title": idea_title,
|
|
1226
1395
|
"idea_problem": idea_problem,
|
|
1396
|
+
"method_brief": latest_idea_details.get("method_brief"),
|
|
1397
|
+
"selection_scores": selection_scores or None,
|
|
1398
|
+
"mechanism_family": latest_idea_details.get("mechanism_family"),
|
|
1399
|
+
"change_layer": latest_idea_details.get("change_layer"),
|
|
1400
|
+
"source_lens": latest_idea_details.get("source_lens"),
|
|
1227
1401
|
"next_target": next_target,
|
|
1228
1402
|
"idea_draft_path": idea_draft_rel_path,
|
|
1229
1403
|
"idea_draft_markdown": idea_draft_markdown,
|
|
@@ -1579,11 +1753,13 @@ class QuestStageViewBuilder:
|
|
|
1579
1753
|
for path in candidates
|
|
1580
1754
|
],
|
|
1581
1755
|
self._file_entry(paper_root / "selected_outline.json", label="Selected Outline", description="Chosen paper outline."),
|
|
1756
|
+
self._file_entry(paper_root / "outline" / "manifest.json", label="Outline Manifest", description="Author-facing paper outline manifest."),
|
|
1582
1757
|
self._file_entry(paper_root / "outline_selection.md", label="Outline Selection Note", description="Outline selection rationale."),
|
|
1583
1758
|
self._file_entry(paper_root / "draft.md", label="Draft Markdown", description="Current paper draft."),
|
|
1584
1759
|
self._file_entry(paper_root / "writing_plan.md", label="Writing Plan", description="Paper writing plan."),
|
|
1585
1760
|
self._file_entry(paper_root / "references.bib", label="References", description="Bibliography file."),
|
|
1586
1761
|
self._file_entry(paper_root / "claim_evidence_map.json", label="Claim-Evidence Map", description="Claim to evidence mapping."),
|
|
1762
|
+
self._file_entry(paper_root / "paper_line_state.json", label="Paper Line State", description="Derived summary state for the active paper line."),
|
|
1587
1763
|
self._file_entry(paper_root / "baseline_inventory.json", label="Baseline Inventory", description="Canonical and supplementary baseline inventory for writing."),
|
|
1588
1764
|
self._file_entry(paper_root / "build" / "compile_report.json", label="Compile Report", description="Paper build/compile report."),
|
|
1589
1765
|
self._file_entry(paper_root / "paper_bundle_manifest.json", label="Bundle Manifest", description="Final paper bundle manifest."),
|
|
@@ -17,6 +17,8 @@ class RunRequest:
|
|
|
17
17
|
approval_policy: str
|
|
18
18
|
sandbox_mode: str
|
|
19
19
|
turn_reason: str = "user_message"
|
|
20
|
+
turn_intent: str = "continue_stage"
|
|
21
|
+
turn_mode: str = "stage_execution"
|
|
20
22
|
reasoning_effort: str | None = None
|
|
21
23
|
turn_id: str | None = None
|
|
22
24
|
attempt_index: int = 1
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import signal
|
|
6
|
-
import shutil
|
|
7
6
|
import subprocess
|
|
8
7
|
import sys
|
|
9
8
|
import threading
|
|
@@ -11,12 +10,16 @@ from pathlib import Path
|
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
13
12
|
from ..artifact import ArtifactService
|
|
14
|
-
from ..codex_cli_compat import
|
|
13
|
+
from ..codex_cli_compat import (
|
|
14
|
+
materialize_codex_runtime_home,
|
|
15
|
+
normalize_codex_reasoning_effort,
|
|
16
|
+
provider_profile_metadata_from_home,
|
|
17
|
+
)
|
|
15
18
|
from ..config import ConfigManager
|
|
16
19
|
from ..gitops import export_git_graph
|
|
17
20
|
from ..prompts import PromptBuilder
|
|
18
21
|
from ..runtime_logs import JsonlLogger
|
|
19
|
-
from ..shared import append_jsonl, ensure_dir, generate_id,
|
|
22
|
+
from ..shared import append_jsonl, ensure_dir, generate_id, read_yaml, resolve_runner_binary, utc_now, write_json, write_text
|
|
20
23
|
from ..web_search import extract_web_search_payload
|
|
21
24
|
from .base import RunRequest, RunResult
|
|
22
25
|
|
|
@@ -24,6 +27,55 @@ _TOOL_EVENT_ARGS_TEXT_LIMIT = 8_000
|
|
|
24
27
|
_TOOL_EVENT_OUTPUT_TEXT_LIMIT = 16_000
|
|
25
28
|
_MAX_QUEST_EVENT_JSON_BYTES = 2_000_000
|
|
26
29
|
_OVERSIZED_EVENT_PREVIEW_TEXT_LIMIT = 12_000
|
|
30
|
+
_BUILTIN_MCP_TOOL_APPROVALS: dict[str, tuple[str, ...]] = {
|
|
31
|
+
"memory": (
|
|
32
|
+
"write",
|
|
33
|
+
"read",
|
|
34
|
+
"search",
|
|
35
|
+
"list_recent",
|
|
36
|
+
"promote_to_global",
|
|
37
|
+
),
|
|
38
|
+
"artifact": (
|
|
39
|
+
"record",
|
|
40
|
+
"checkpoint",
|
|
41
|
+
"prepare_branch",
|
|
42
|
+
"activate_branch",
|
|
43
|
+
"submit_idea",
|
|
44
|
+
"list_research_branches",
|
|
45
|
+
"resolve_runtime_refs",
|
|
46
|
+
"get_paper_contract_health",
|
|
47
|
+
"get_quest_state",
|
|
48
|
+
"get_global_status",
|
|
49
|
+
"get_method_scoreboard",
|
|
50
|
+
"get_optimization_frontier",
|
|
51
|
+
"read_quest_documents",
|
|
52
|
+
"get_conversation_context",
|
|
53
|
+
"get_analysis_campaign",
|
|
54
|
+
"record_main_experiment",
|
|
55
|
+
"create_analysis_campaign",
|
|
56
|
+
"submit_paper_outline",
|
|
57
|
+
"list_paper_outlines",
|
|
58
|
+
"submit_paper_bundle",
|
|
59
|
+
"record_analysis_slice",
|
|
60
|
+
"publish_baseline",
|
|
61
|
+
"attach_baseline",
|
|
62
|
+
"confirm_baseline",
|
|
63
|
+
"waive_baseline",
|
|
64
|
+
"arxiv",
|
|
65
|
+
"refresh_summary",
|
|
66
|
+
"render_git_graph",
|
|
67
|
+
"interact",
|
|
68
|
+
"complete_quest",
|
|
69
|
+
),
|
|
70
|
+
"bash_exec": (
|
|
71
|
+
"bash_exec",
|
|
72
|
+
),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_PROVIDER_ENV_CONFLICT_KEYS = (
|
|
76
|
+
"OPENAI_API_KEY",
|
|
77
|
+
"OPENAI_BASE_URL",
|
|
78
|
+
)
|
|
27
79
|
|
|
28
80
|
|
|
29
81
|
def _compact_text(value: object, *, limit: int = 1200) -> str:
|
|
@@ -151,7 +203,9 @@ def _iter_event_texts(event: dict[str, Any]) -> list[str]:
|
|
|
151
203
|
if isinstance(value, str) and value.strip():
|
|
152
204
|
texts.append(value)
|
|
153
205
|
delta = event.get("delta")
|
|
154
|
-
if isinstance(delta,
|
|
206
|
+
if isinstance(delta, str) and delta.strip():
|
|
207
|
+
texts.append(delta)
|
|
208
|
+
elif isinstance(delta, dict):
|
|
155
209
|
for key in ("text", "content"):
|
|
156
210
|
value = delta.get(key)
|
|
157
211
|
if isinstance(value, str) and value.strip():
|
|
@@ -178,6 +232,36 @@ def _web_search_text_payload(item: dict[str, Any]) -> str:
|
|
|
178
232
|
return _compact_text(payload, limit=2400)
|
|
179
233
|
|
|
180
234
|
|
|
235
|
+
def _message_stream_id(event: dict[str, Any], item: dict[str, Any], *, run_id: str, kind: str) -> str:
|
|
236
|
+
for value in (
|
|
237
|
+
event.get("stream_id"),
|
|
238
|
+
item.get("stream_id"),
|
|
239
|
+
event.get("message_id"),
|
|
240
|
+
item.get("message_id"),
|
|
241
|
+
event.get("item_id"),
|
|
242
|
+
item.get("id"),
|
|
243
|
+
event.get("output_item_id"),
|
|
244
|
+
event.get("response_id"),
|
|
245
|
+
):
|
|
246
|
+
if value:
|
|
247
|
+
return str(value)
|
|
248
|
+
normalized_kind = str(kind or "message").strip().lower() or "message"
|
|
249
|
+
return f"{run_id}:{normalized_kind}"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _message_id(event: dict[str, Any], item: dict[str, Any], *, stream_id: str) -> str:
|
|
253
|
+
for value in (
|
|
254
|
+
event.get("message_id"),
|
|
255
|
+
item.get("message_id"),
|
|
256
|
+
event.get("item_id"),
|
|
257
|
+
item.get("id"),
|
|
258
|
+
event.get("output_item_id"),
|
|
259
|
+
):
|
|
260
|
+
if value:
|
|
261
|
+
return str(value)
|
|
262
|
+
return stream_id
|
|
263
|
+
|
|
264
|
+
|
|
181
265
|
def _message_events(
|
|
182
266
|
event: dict[str, Any],
|
|
183
267
|
*,
|
|
@@ -194,6 +278,8 @@ def _message_events(
|
|
|
194
278
|
|
|
195
279
|
if item_type == "agent_message":
|
|
196
280
|
texts = _dedupe_texts(_iter_event_texts(event))
|
|
281
|
+
stream_id = _message_stream_id(event, item, run_id=run_id, kind="assistant")
|
|
282
|
+
message_id = _message_id(event, item, stream_id=stream_id)
|
|
197
283
|
for text in texts:
|
|
198
284
|
quest_events.append(
|
|
199
285
|
{
|
|
@@ -204,6 +290,8 @@ def _message_events(
|
|
|
204
290
|
"source": "codex",
|
|
205
291
|
"skill_id": skill_id,
|
|
206
292
|
"text": text,
|
|
293
|
+
"stream_id": stream_id,
|
|
294
|
+
"message_id": message_id,
|
|
207
295
|
"created_at": created_at,
|
|
208
296
|
}
|
|
209
297
|
)
|
|
@@ -211,6 +299,8 @@ def _message_events(
|
|
|
211
299
|
|
|
212
300
|
if item_type in {"reasoning", "reasoning_summary"} or "reasoning" in event_type:
|
|
213
301
|
texts = _dedupe_texts(_iter_event_texts(event))
|
|
302
|
+
stream_id = _message_stream_id(event, item, run_id=run_id, kind=item_type or "reasoning")
|
|
303
|
+
message_id = _message_id(event, item, stream_id=stream_id)
|
|
214
304
|
for text in texts:
|
|
215
305
|
quest_events.append(
|
|
216
306
|
{
|
|
@@ -221,6 +311,8 @@ def _message_events(
|
|
|
221
311
|
"source": "codex",
|
|
222
312
|
"skill_id": skill_id,
|
|
223
313
|
"text": text,
|
|
314
|
+
"stream_id": stream_id,
|
|
315
|
+
"message_id": message_id,
|
|
224
316
|
"kind": item_type or "reasoning",
|
|
225
317
|
"created_at": created_at,
|
|
226
318
|
}
|
|
@@ -234,6 +326,8 @@ def _message_events(
|
|
|
234
326
|
return [], []
|
|
235
327
|
|
|
236
328
|
texts = _dedupe_texts(_iter_event_texts(event))
|
|
329
|
+
stream_id = _message_stream_id(event, item, run_id=run_id, kind="assistant")
|
|
330
|
+
message_id = _message_id(event, item, stream_id=stream_id)
|
|
237
331
|
for text in texts:
|
|
238
332
|
quest_events.append(
|
|
239
333
|
{
|
|
@@ -244,6 +338,8 @@ def _message_events(
|
|
|
244
338
|
"source": "codex",
|
|
245
339
|
"skill_id": skill_id,
|
|
246
340
|
"text": text,
|
|
341
|
+
"stream_id": stream_id,
|
|
342
|
+
"message_id": message_id,
|
|
247
343
|
"created_at": created_at,
|
|
248
344
|
}
|
|
249
345
|
)
|
|
@@ -322,9 +418,11 @@ def _tool_output(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
|
322
418
|
item.get("result"),
|
|
323
419
|
item.get("output"),
|
|
324
420
|
item.get("content"),
|
|
421
|
+
item.get("error"),
|
|
325
422
|
event.get("result"),
|
|
326
423
|
event.get("output"),
|
|
327
424
|
event.get("content"),
|
|
425
|
+
event.get("error"),
|
|
328
426
|
item.get("aggregated_output"),
|
|
329
427
|
event.get("aggregated_output"),
|
|
330
428
|
):
|
|
@@ -338,11 +436,13 @@ def _tool_output(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
|
338
436
|
item.get("output"),
|
|
339
437
|
item.get("result"),
|
|
340
438
|
item.get("content"),
|
|
439
|
+
item.get("error"),
|
|
341
440
|
event.get("aggregated_output"),
|
|
342
441
|
event.get("changes"),
|
|
343
442
|
event.get("output"),
|
|
344
443
|
event.get("result"),
|
|
345
444
|
event.get("content"),
|
|
445
|
+
event.get("error"),
|
|
346
446
|
):
|
|
347
447
|
text = _compact_text(value, limit=1200)
|
|
348
448
|
if text:
|
|
@@ -642,6 +742,8 @@ class CodexRunner:
|
|
|
642
742
|
user_message=request.message,
|
|
643
743
|
model=request.model,
|
|
644
744
|
turn_reason=request.turn_reason,
|
|
745
|
+
turn_intent=request.turn_intent,
|
|
746
|
+
turn_mode=request.turn_mode,
|
|
645
747
|
retry_context=request.retry_context,
|
|
646
748
|
)
|
|
647
749
|
write_text(run_root / "prompt.md", prompt)
|
|
@@ -663,6 +765,8 @@ class CodexRunner:
|
|
|
663
765
|
"workspace_root": str(workspace_root),
|
|
664
766
|
"cwd": str(workspace_root),
|
|
665
767
|
"turn_reason": request.turn_reason,
|
|
768
|
+
"turn_intent": request.turn_intent,
|
|
769
|
+
"turn_mode": request.turn_mode,
|
|
666
770
|
},
|
|
667
771
|
)
|
|
668
772
|
|
|
@@ -672,8 +776,12 @@ class CodexRunner:
|
|
|
672
776
|
env_key = str(key or "").strip()
|
|
673
777
|
if not env_key or value is None:
|
|
674
778
|
continue
|
|
675
|
-
|
|
779
|
+
env_value = str(value)
|
|
780
|
+
if env_value == "":
|
|
781
|
+
continue
|
|
782
|
+
env[env_key] = env_value
|
|
676
783
|
env["CODEX_HOME"] = str(codex_home)
|
|
784
|
+
env = self._sanitize_provider_env(env, runner_config=runner_config)
|
|
677
785
|
env["DEEPSCIENTIST_HOME"] = str(self.home)
|
|
678
786
|
env["DS_HOME"] = str(self.home)
|
|
679
787
|
env["DS_QUEST_ID"] = request.quest_id
|
|
@@ -681,6 +789,8 @@ class CodexRunner:
|
|
|
681
789
|
env["DS_WORKTREE_ROOT"] = str(workspace_root)
|
|
682
790
|
env["DS_RUN_ID"] = request.run_id
|
|
683
791
|
env["DS_TURN_REASON"] = request.turn_reason
|
|
792
|
+
env["DS_TURN_INTENT"] = request.turn_intent
|
|
793
|
+
env["DS_TURN_MODE"] = request.turn_mode
|
|
684
794
|
quest_yaml = read_yaml(request.quest_root / "quest.yaml", {})
|
|
685
795
|
env["DS_ACTIVE_ANCHOR"] = str(quest_yaml.get("active_anchor", "baseline"))
|
|
686
796
|
env["DS_CONVERSATION_ID"] = f"quest:{request.quest_id}"
|
|
@@ -740,6 +850,13 @@ class CodexRunner:
|
|
|
740
850
|
timestamp = utc_now()
|
|
741
851
|
append_jsonl(history_events, {"timestamp": timestamp, "event": payload})
|
|
742
852
|
append_jsonl(stdout_events, {"timestamp": timestamp, "line": line})
|
|
853
|
+
try:
|
|
854
|
+
self.artifact_service.quest_service.schedule_projection_refresh(
|
|
855
|
+
request.quest_root,
|
|
856
|
+
kinds=("details",),
|
|
857
|
+
)
|
|
858
|
+
except Exception:
|
|
859
|
+
pass
|
|
743
860
|
tool_event = _tool_event(
|
|
744
861
|
payload,
|
|
745
862
|
quest_id=request.quest_id,
|
|
@@ -810,6 +927,14 @@ class CodexRunner:
|
|
|
810
927
|
}
|
|
811
928
|
write_json(run_root / "result.json", result_payload)
|
|
812
929
|
write_json(history_root / "meta.json", result_payload)
|
|
930
|
+
try:
|
|
931
|
+
self.artifact_service.quest_service.schedule_projection_refresh(
|
|
932
|
+
request.quest_root,
|
|
933
|
+
kinds=("details",),
|
|
934
|
+
throttle_seconds=0.0,
|
|
935
|
+
)
|
|
936
|
+
except Exception:
|
|
937
|
+
pass
|
|
813
938
|
self.logger.log(
|
|
814
939
|
"info",
|
|
815
940
|
"runner.codex.completed",
|
|
@@ -822,6 +947,7 @@ class CodexRunner:
|
|
|
822
947
|
request.quest_root,
|
|
823
948
|
{
|
|
824
949
|
"kind": "run",
|
|
950
|
+
"status": "completed" if exit_code == 0 else "failed",
|
|
825
951
|
"run_id": request.run_id,
|
|
826
952
|
"run_kind": request.skill_id,
|
|
827
953
|
"model": request.model,
|
|
@@ -902,6 +1028,8 @@ class CodexRunner:
|
|
|
902
1028
|
resolved_runner_config = runner_config if isinstance(runner_config, dict) else self._load_runner_config()
|
|
903
1029
|
profile = str(resolved_runner_config.get("profile") or "").strip()
|
|
904
1030
|
normalized_model = str(request.model or "").strip()
|
|
1031
|
+
if profile and normalized_model.lower() not in {"", "inherit", "default", "codex-default"}:
|
|
1032
|
+
normalized_model = "inherit"
|
|
905
1033
|
command = [
|
|
906
1034
|
resolved_binary or self.binary,
|
|
907
1035
|
"--search",
|
|
@@ -946,35 +1074,16 @@ class CodexRunner:
|
|
|
946
1074
|
run_id: str,
|
|
947
1075
|
runner_config: dict[str, Any] | None = None,
|
|
948
1076
|
) -> Path:
|
|
949
|
-
target = ensure_dir(workspace_root / ".codex")
|
|
1077
|
+
target = ensure_dir(workspace_root / ".ds" / "codex-home")
|
|
950
1078
|
resolved_runner_config = runner_config if isinstance(runner_config, dict) else self._load_runner_config()
|
|
951
1079
|
configured_home = str(resolved_runner_config.get("config_dir") or os.environ.get("CODEX_HOME") or str(Path.home() / ".codex"))
|
|
952
1080
|
profile = str(resolved_runner_config.get("profile") or "").strip()
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
continue
|
|
960
|
-
shutil.copy2(source_path, target_path)
|
|
961
|
-
config_path = target / "config.toml"
|
|
962
|
-
if profile and config_path.exists():
|
|
963
|
-
adapted_text, _ = adapt_profile_only_provider_config(read_text(config_path), profile=profile)
|
|
964
|
-
write_text(config_path, adapted_text)
|
|
965
|
-
ensure_dir(target / "skills")
|
|
966
|
-
quest_skills_root = quest_root / ".codex" / "skills"
|
|
967
|
-
if quest_skills_root.exists():
|
|
968
|
-
for source_path in sorted(quest_skills_root.rglob("*")):
|
|
969
|
-
relative = source_path.relative_to(quest_skills_root)
|
|
970
|
-
target_path = target / "skills" / relative
|
|
971
|
-
if source_path.is_dir():
|
|
972
|
-
ensure_dir(target_path)
|
|
973
|
-
continue
|
|
974
|
-
if source_path.resolve() == target_path.resolve():
|
|
975
|
-
continue
|
|
976
|
-
ensure_dir(target_path.parent)
|
|
977
|
-
shutil.copy2(source_path, target_path)
|
|
1081
|
+
materialize_codex_runtime_home(
|
|
1082
|
+
source_home=configured_home,
|
|
1083
|
+
target_home=target,
|
|
1084
|
+
profile=profile,
|
|
1085
|
+
quest_codex_root=quest_root / ".codex",
|
|
1086
|
+
)
|
|
978
1087
|
self._inject_built_in_mcp(
|
|
979
1088
|
target,
|
|
980
1089
|
quest_root=quest_root,
|
|
@@ -1043,6 +1152,7 @@ class CodexRunner:
|
|
|
1043
1152
|
args = ["-m", "deepscientist.mcp.server", "--namespace", name]
|
|
1044
1153
|
lines = [
|
|
1045
1154
|
f"[mcp_servers.{name}]",
|
|
1155
|
+
'transport = "stdio"',
|
|
1046
1156
|
f'command = "{sys.executable}"',
|
|
1047
1157
|
f"args = [{', '.join(json.dumps(item) for item in args)}]",
|
|
1048
1158
|
]
|
|
@@ -1057,6 +1167,14 @@ class CodexRunner:
|
|
|
1057
1167
|
)
|
|
1058
1168
|
for key, value in env.items():
|
|
1059
1169
|
lines.append(f"{key} = {json.dumps(value)}")
|
|
1170
|
+
for tool_name in _BUILTIN_MCP_TOOL_APPROVALS.get(name, ()):
|
|
1171
|
+
lines.extend(
|
|
1172
|
+
[
|
|
1173
|
+
"",
|
|
1174
|
+
f"[mcp_servers.{name}.tools.{tool_name}]",
|
|
1175
|
+
'approval_mode = "approve"',
|
|
1176
|
+
]
|
|
1177
|
+
)
|
|
1060
1178
|
return "\n".join(lines)
|
|
1061
1179
|
|
|
1062
1180
|
def _load_runner_config(self) -> dict[str, Any]:
|
|
@@ -1074,3 +1192,23 @@ class CodexRunner:
|
|
|
1074
1192
|
except (TypeError, ValueError):
|
|
1075
1193
|
return None
|
|
1076
1194
|
return timeout if timeout > 0 else None
|
|
1195
|
+
|
|
1196
|
+
@staticmethod
|
|
1197
|
+
def _sanitize_provider_env(
|
|
1198
|
+
env: dict[str, str],
|
|
1199
|
+
*,
|
|
1200
|
+
runner_config: dict[str, Any] | None = None,
|
|
1201
|
+
) -> dict[str, str]:
|
|
1202
|
+
resolved_runner_config = runner_config if isinstance(runner_config, dict) else {}
|
|
1203
|
+
profile = str(resolved_runner_config.get("profile") or "").strip()
|
|
1204
|
+
config_home = str(resolved_runner_config.get("config_dir") or env.get("CODEX_HOME") or "").strip()
|
|
1205
|
+
if not profile or not config_home:
|
|
1206
|
+
return env
|
|
1207
|
+
metadata = provider_profile_metadata_from_home(config_home, profile=profile)
|
|
1208
|
+
requires_openai_auth = metadata.get("requires_openai_auth")
|
|
1209
|
+
if requires_openai_auth is not False:
|
|
1210
|
+
return env
|
|
1211
|
+
sanitized = dict(env)
|
|
1212
|
+
for key in _PROVIDER_ENV_CONFLICT_KEYS:
|
|
1213
|
+
sanitized.pop(key, None)
|
|
1214
|
+
return sanitized
|