@researai/deepscientist 1.5.14 → 1.5.15
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 +8 -0
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +134 -49
- package/docs/en/00_QUICK_START.md +2 -2
- package/docs/en/01_SETTINGS_REFERENCE.md +20 -4
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +2 -0
- 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/README.md +6 -0
- package/docs/zh/00_QUICK_START.md +2 -2
- package/docs/zh/01_SETTINGS_REFERENCE.md +20 -4
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +2 -0
- 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/README.md +6 -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/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 +3534 -191
- 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 +79 -64
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/config/models.py +6 -3
- package/src/deepscientist/config/service.py +7 -2
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +75 -4
- package/src/deepscientist/daemon/api/router.py +1 -0
- package/src/deepscientist/daemon/app.py +758 -206
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/diff.py +167 -1
- package/src/deepscientist/mcp/server.py +173 -5
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +267 -442
- package/src/deepscientist/quest/service.py +2255 -163
- package/src/deepscientist/quest/stage_views.py +171 -0
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +88 -5
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/prompts/contracts/shared_interaction.md +13 -4
- package/src/prompts/system.md +916 -72
- package/src/skills/analysis-campaign/SKILL.md +31 -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 +2 -0
- package/src/skills/decision/SKILL.md +19 -2
- package/src/skills/experiment/SKILL.md +8 -2
- package/src/skills/finalize/SKILL.md +18 -0
- package/src/skills/idea/SKILL.md +78 -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 +1 -1
- package/src/skills/optimize/SKILL.md +1644 -0
- package/src/skills/rebuttal/SKILL.md +2 -1
- package/src/skills/review/SKILL.md +2 -1
- package/src/skills/write/SKILL.md +80 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +3 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-DaF9Nge_.js → AiManusChatView-DDjbFnbt.js} +12 -12
- package/src/ui/dist/assets/{AnalysisPlugin-BSVx6dXE.js → AnalysisPlugin-Yb5IdmaU.js} +1 -1
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +31037 -0
- package/src/ui/dist/assets/{CodeEditorPlugin-DU9G0Tox.js → CodeEditorPlugin-C4D2TIkU.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DoX_fI9l.js → CodeViewerPlugin-BVoNZIvC.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-C4FWIXuU.js → DocViewerPlugin-CLChbllo.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-BgfFMgtf.js → GitDiffViewerPlugin-C4xeFyFQ.js} +20 -20
- package/src/ui/dist/assets/{ImageViewerPlugin-tcPkfY_x.js → ImageViewerPlugin-OiMUAcLi.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-_dKV60Bf.js → LabCopilotPanel-BjD2ThQF.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-Bje0ayoC.js → LabPlugin-DQPg-NrB.js} +2 -2
- package/src/ui/dist/assets/{LatexPlugin-CVsBzAln.js → LatexPlugin-CI05XAV9.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-xjmrqv_8.js → MarkdownViewerPlugin-DpeBLYZf.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-mMM2A8wP.js → MarketplacePlugin-DolE58Q2.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-3kVDSOBo.js → NotebookEditor-7Qm2rSWD.js} +11 -11
- package/src/ui/dist/assets/{NotebookEditor-SoJ8X-MO.js → NotebookEditor-C1kWaxKi.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-DElVuHl9.js → PdfLoader-BfOHw8Zw.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-Bq88XT4G.js → PdfMarkdownPlugin-BulDREv1.js} +2 -2
- package/src/ui/dist/assets/{PdfViewerPlugin-CsCXMo9S.js → PdfViewerPlugin-C-daaOaL.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-oUPvy19k.js → SearchPlugin-CjpaiJ3A.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-CRkT9yNy.js → TextViewerPlugin-BxIyqPQC.js} +5 -5
- package/src/ui/dist/assets/{VNCViewer-BgbuvWhR.js → VNCViewer-HAg9mF7M.js} +10 -10
- package/src/ui/dist/assets/{bot-v_RASACv.js → bot-0DYntytV.js} +1 -1
- package/src/ui/dist/assets/{code-5hC9d0VH.js → code-B20Slj_w.js} +1 -1
- package/src/ui/dist/assets/{file-content-D1PxfOrp.js → file-content-DT24KFma.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-DG1oT_Hj.js → file-diff-panel-DK13YPql.js} +1 -1
- package/src/ui/dist/assets/{file-socket-BmdFYQlk.js → file-socket-B4T2o4nR.js} +1 -1
- package/src/ui/dist/assets/{image-Dqe2X2tW.js → image-DSeR_sDS.js} +1 -1
- package/src/ui/dist/assets/{index-RDlNXXx1.js → index-BrFje2Uk.js} +2 -2
- package/src/ui/dist/assets/{index-DVsMKK_y.js → index-BwRJaoTl.js} +1 -1
- package/src/ui/dist/assets/{index-Nt9hS4ck.js → index-D_E4281X.js} +5007 -28514
- package/src/ui/dist/assets/{index-Duvz8Ip0.js → index-DnYB3xb1.js} +12 -12
- package/src/ui/dist/assets/{index-BQG-1s2o.css → index-G7AcWcMu.css} +43 -2
- package/src/ui/dist/assets/{monaco-DIXge1CP.js → monaco-LExaAN3Y.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-BBTTQaO-.js → pdf-effect-queue-BJk5okWJ.js} +1 -1
- package/src/ui/dist/assets/{popover-BWlolyxo.js → popover-D3Gg_FoV.js} +1 -1
- package/src/ui/dist/assets/{project-sync-BM5PkFH4.js → project-sync-C_ygLlVU.js} +1 -1
- package/src/ui/dist/assets/{select-D4dAtrA8.js → select-CpAK6uWm.js} +2 -2
- package/src/ui/dist/assets/{sigma-CKbE5jJT.js → sigma-DEccaSgk.js} +1 -1
- package/src/ui/dist/assets/{square-check-big-CZNGMgiB.js → square-check-big-uUfyVsbD.js} +1 -1
- package/src/ui/dist/assets/{trash-DaB37xAz.js → trash-CXvwwSe8.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-C2OmAcWe.js → useCliAccess-Bnop4mgR.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-Dowd1Ij4.js → useFileDiffOverlay-B8eUAX0I.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-BGjAhAUq.js → wrap-text-9vbOBpkW.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-dMZQMXzc.js → zoom-out-BgVMmOW4.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
|
@@ -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 {}
|
|
@@ -215,6 +235,8 @@ class QuestStageViewBuilder:
|
|
|
215
235
|
def build(self) -> dict[str, Any]:
|
|
216
236
|
selection_type = str(self.selection.get("selection_type") or "").strip()
|
|
217
237
|
self.stage_key = self._resolve_effective_stage_key()
|
|
238
|
+
if selection_type == "idea_candidate":
|
|
239
|
+
return self._build_idea_candidate()
|
|
218
240
|
if selection_type == "branch_node" and self.stage_key not in {"experiment", "analysis", "paper"}:
|
|
219
241
|
return self._build_branch()
|
|
220
242
|
if self.stage_key == "baseline":
|
|
@@ -280,6 +302,8 @@ class QuestStageViewBuilder:
|
|
|
280
302
|
normalized = [str(item).strip() for item in raw if str(item).strip()]
|
|
281
303
|
if normalized:
|
|
282
304
|
return normalized
|
|
305
|
+
if str(self.selection.get("selection_type") or "").strip() == "idea_candidate":
|
|
306
|
+
return self._idea_candidate_scope_paths()
|
|
283
307
|
if str(self.selection.get("selection_type") or "").strip() == "branch_node":
|
|
284
308
|
return self._branch_scope_paths()
|
|
285
309
|
defaults = {
|
|
@@ -693,6 +717,13 @@ class QuestStageViewBuilder:
|
|
|
693
717
|
"artifacts/reports",
|
|
694
718
|
]
|
|
695
719
|
|
|
720
|
+
def _idea_candidate_scope_paths(self) -> list[str]:
|
|
721
|
+
candidate_id = str(self.selection.get("selection_ref") or self.selection.get("idea_id") or "").strip()
|
|
722
|
+
return [
|
|
723
|
+
*( [f"memory/ideas/_candidates/{candidate_id}"] if candidate_id else []),
|
|
724
|
+
"artifacts/reports",
|
|
725
|
+
]
|
|
726
|
+
|
|
696
727
|
def _experiment_scope_paths(self, run_id: str | None) -> list[str]:
|
|
697
728
|
return [
|
|
698
729
|
*( [f"experiments/main/{run_id}"] if run_id else []),
|
|
@@ -917,6 +948,25 @@ class QuestStageViewBuilder:
|
|
|
917
948
|
items.append(item)
|
|
918
949
|
return items
|
|
919
950
|
|
|
951
|
+
def _idea_candidate_stage_items(self) -> list[dict[str, Any]]:
|
|
952
|
+
candidate_id = str(self.selection.get("selection_ref") or self.selection.get("idea_id") or "").strip()
|
|
953
|
+
if not candidate_id:
|
|
954
|
+
return []
|
|
955
|
+
items: list[dict[str, Any]] = []
|
|
956
|
+
for item in self.artifacts:
|
|
957
|
+
payload = self._payload(item)
|
|
958
|
+
if str(payload.get("kind") or "").strip() != "idea":
|
|
959
|
+
continue
|
|
960
|
+
if str(payload.get("idea_id") or "").strip() != candidate_id:
|
|
961
|
+
continue
|
|
962
|
+
flow_type = str(payload.get("flow_type") or "").strip()
|
|
963
|
+
protocol_step = str(payload.get("protocol_step") or "").strip()
|
|
964
|
+
details = dict(payload.get("details") or {}) if isinstance(payload.get("details"), dict) else {}
|
|
965
|
+
submission_mode = str(details.get("submission_mode") or payload.get("submission_mode") or "").strip().lower()
|
|
966
|
+
if flow_type == "idea_submission" and (protocol_step == "candidate" or submission_mode == "candidate"):
|
|
967
|
+
items.append(item)
|
|
968
|
+
return items
|
|
969
|
+
|
|
920
970
|
def _build_idea(self) -> dict[str, Any]:
|
|
921
971
|
idea_items = self._idea_stage_items()
|
|
922
972
|
latest = idea_items[-1] if idea_items else None
|
|
@@ -944,6 +994,8 @@ class QuestStageViewBuilder:
|
|
|
944
994
|
draft_md_rel_path = self._relative_path_or_raw(draft_md_path)
|
|
945
995
|
draft_markdown = self._markdown_body_for_path(draft_md_path)
|
|
946
996
|
lineage_intent = str(payload.get("lineage_intent") or details.get("lineage_intent") or "").strip() or None
|
|
997
|
+
selection_scores = details.get("selection_scores")
|
|
998
|
+
selection_score_summary = _selection_score_summary(selection_scores)
|
|
947
999
|
note = (
|
|
948
1000
|
str(payload.get("summary") or payload.get("reason") or "").strip()
|
|
949
1001
|
or "No durable idea submission has been recorded yet."
|
|
@@ -987,6 +1039,11 @@ class QuestStageViewBuilder:
|
|
|
987
1039
|
_field("Problem", details.get("problem") or "Not recorded"),
|
|
988
1040
|
_field("Hypothesis", details.get("hypothesis") or "Not recorded"),
|
|
989
1041
|
_field("Mechanism", details.get("mechanism") or "Not recorded"),
|
|
1042
|
+
_field("Method Brief", details.get("method_brief") or "Not recorded"),
|
|
1043
|
+
_field("Selection Scores", selection_score_summary or "Not recorded"),
|
|
1044
|
+
_field("Mechanism Family", details.get("mechanism_family") or "Not recorded"),
|
|
1045
|
+
_field("Change Layer", details.get("change_layer") or "Not recorded"),
|
|
1046
|
+
_field("Source Lens", details.get("source_lens") or "Not recorded"),
|
|
990
1047
|
_field("Expected Gain", details.get("expected_gain") or "Not recorded"),
|
|
991
1048
|
_field("Risks", details.get("risks") or "Not recorded"),
|
|
992
1049
|
_field("Evidence Paths", details.get("evidence_paths") or "Not recorded"),
|
|
@@ -1009,6 +1066,11 @@ class QuestStageViewBuilder:
|
|
|
1009
1066
|
"problem": details.get("problem"),
|
|
1010
1067
|
"hypothesis": details.get("hypothesis"),
|
|
1011
1068
|
"mechanism": details.get("mechanism"),
|
|
1069
|
+
"method_brief": details.get("method_brief"),
|
|
1070
|
+
"selection_scores": selection_scores or None,
|
|
1071
|
+
"mechanism_family": details.get("mechanism_family"),
|
|
1072
|
+
"change_layer": details.get("change_layer"),
|
|
1073
|
+
"source_lens": details.get("source_lens"),
|
|
1012
1074
|
"expected_gain": details.get("expected_gain"),
|
|
1013
1075
|
"risks": details.get("risks") or [],
|
|
1014
1076
|
"evidence_paths": details.get("evidence_paths") or [],
|
|
@@ -1028,6 +1090,101 @@ class QuestStageViewBuilder:
|
|
|
1028
1090
|
subviews=["overview", "details", "draft"] if draft_markdown else ["overview", "details"],
|
|
1029
1091
|
)
|
|
1030
1092
|
|
|
1093
|
+
def _build_idea_candidate(self) -> dict[str, Any]:
|
|
1094
|
+
candidate_items = self._idea_candidate_stage_items()
|
|
1095
|
+
latest = candidate_items[-1] if candidate_items else None
|
|
1096
|
+
payload = self._payload(latest or {})
|
|
1097
|
+
details = dict(payload.get("details") or {}) if isinstance(payload.get("details"), dict) else {}
|
|
1098
|
+
candidate_id = str(self.selection.get("selection_ref") or payload.get("idea_id") or "candidate").strip() or "candidate"
|
|
1099
|
+
title_text = (
|
|
1100
|
+
str(details.get("title") or self.selection.get("label") or candidate_id).strip() or candidate_id
|
|
1101
|
+
)
|
|
1102
|
+
paths = dict(payload.get("paths") or {}) if isinstance(payload.get("paths"), dict) else {}
|
|
1103
|
+
candidate_root = paths.get("candidate_root") or str(self.quest_root / "memory" / "ideas" / "_candidates" / candidate_id)
|
|
1104
|
+
idea_md_path = paths.get("idea_md") or str(Path(candidate_root) / "idea.md")
|
|
1105
|
+
draft_md_path = paths.get("idea_draft_md") or details.get("idea_draft_path") or str(Path(candidate_root) / "draft.md")
|
|
1106
|
+
idea_markdown = self._markdown_body_for_path(idea_md_path)
|
|
1107
|
+
draft_markdown = self._markdown_body_for_path(draft_md_path)
|
|
1108
|
+
idea_md_rel_path = self._relative_path_or_raw(idea_md_path)
|
|
1109
|
+
draft_md_rel_path = self._relative_path_or_raw(draft_md_path)
|
|
1110
|
+
candidate_root_rel_path = self._relative_path_or_raw(candidate_root)
|
|
1111
|
+
selection_scores = details.get("selection_scores")
|
|
1112
|
+
selection_score_summary = _selection_score_summary(selection_scores)
|
|
1113
|
+
note = (
|
|
1114
|
+
str(payload.get("summary") or payload.get("reason") or self.selection.get("summary") or "").strip()
|
|
1115
|
+
or "No durable candidate brief summary has been recorded yet."
|
|
1116
|
+
)
|
|
1117
|
+
lineage_intent = str(payload.get("lineage_intent") or details.get("lineage_intent") or "").strip() or None
|
|
1118
|
+
parent_branch = str(payload.get("parent_branch") or details.get("parent_branch") or self.selection.get("branch_name") or "").strip() or None
|
|
1119
|
+
foundation_reason = str(payload.get("foundation_reason") or details.get("foundation_reason") or "").strip() or None
|
|
1120
|
+
return self._base_payload(
|
|
1121
|
+
title=f"Candidate Brief · {title_text}",
|
|
1122
|
+
note=note,
|
|
1123
|
+
status=str(payload.get("status") or "candidate").strip() or "candidate",
|
|
1124
|
+
tags=[
|
|
1125
|
+
"candidate-brief",
|
|
1126
|
+
details.get("mechanism_family") or "",
|
|
1127
|
+
details.get("change_layer") or "",
|
|
1128
|
+
details.get("source_lens") or "",
|
|
1129
|
+
lineage_intent or "",
|
|
1130
|
+
],
|
|
1131
|
+
overview=[
|
|
1132
|
+
_field("Candidate ID", candidate_id),
|
|
1133
|
+
_field("Parent Branch", parent_branch or "Not recorded"),
|
|
1134
|
+
_field("Next Target", details.get("next_target") or "optimize"),
|
|
1135
|
+
_field("Candidate Root", candidate_root_rel_path or candidate_root),
|
|
1136
|
+
],
|
|
1137
|
+
key_facts=[
|
|
1138
|
+
_field("Problem", details.get("problem") or "Not recorded"),
|
|
1139
|
+
_field("Hypothesis", details.get("hypothesis") or "Not recorded"),
|
|
1140
|
+
_field("Mechanism", details.get("mechanism") or "Not recorded"),
|
|
1141
|
+
_field("Method Brief", details.get("method_brief") or "Not recorded"),
|
|
1142
|
+
_field("Selection Scores", selection_score_summary or "Not recorded"),
|
|
1143
|
+
_field("Mechanism Family", details.get("mechanism_family") or "Not recorded"),
|
|
1144
|
+
_field("Change Layer", details.get("change_layer") or "Not recorded"),
|
|
1145
|
+
_field("Source Lens", details.get("source_lens") or "Not recorded"),
|
|
1146
|
+
_field("Expected Gain", details.get("expected_gain") or "Not recorded"),
|
|
1147
|
+
_field("Foundation Reason", foundation_reason or "Not recorded"),
|
|
1148
|
+
],
|
|
1149
|
+
key_files=self._dedupe_files(
|
|
1150
|
+
[
|
|
1151
|
+
self._file_entry(candidate_root, label="Candidate Root", description="Branchless candidate brief workspace.", expected_kind="directory"),
|
|
1152
|
+
self._file_entry(idea_md_path, label="Candidate Markdown", description="Durable candidate brief document."),
|
|
1153
|
+
self._file_entry(draft_md_path, label="Candidate Draft", description="Long-form candidate brief draft."),
|
|
1154
|
+
]
|
|
1155
|
+
),
|
|
1156
|
+
history=self._artifact_history(candidate_items),
|
|
1157
|
+
details={
|
|
1158
|
+
"idea": {
|
|
1159
|
+
"idea_id": candidate_id,
|
|
1160
|
+
"title": title_text,
|
|
1161
|
+
"problem": details.get("problem"),
|
|
1162
|
+
"hypothesis": details.get("hypothesis"),
|
|
1163
|
+
"mechanism": details.get("mechanism"),
|
|
1164
|
+
"method_brief": details.get("method_brief"),
|
|
1165
|
+
"selection_scores": selection_scores or None,
|
|
1166
|
+
"mechanism_family": details.get("mechanism_family"),
|
|
1167
|
+
"change_layer": details.get("change_layer"),
|
|
1168
|
+
"source_lens": details.get("source_lens"),
|
|
1169
|
+
"expected_gain": details.get("expected_gain"),
|
|
1170
|
+
"next_target": details.get("next_target") or "optimize",
|
|
1171
|
+
"lineage_intent": lineage_intent,
|
|
1172
|
+
"parent_branch": parent_branch,
|
|
1173
|
+
"candidate_root": candidate_root_rel_path or candidate_root,
|
|
1174
|
+
"idea_path": idea_md_rel_path,
|
|
1175
|
+
"idea_markdown": idea_markdown,
|
|
1176
|
+
"draft_path": draft_md_rel_path,
|
|
1177
|
+
"draft_markdown": draft_markdown,
|
|
1178
|
+
"decision_reason": payload.get("reason"),
|
|
1179
|
+
},
|
|
1180
|
+
"latest_artifact": self._artifact_detail(latest, payload),
|
|
1181
|
+
},
|
|
1182
|
+
lineage_intent=lineage_intent,
|
|
1183
|
+
idea_draft_path=draft_md_rel_path,
|
|
1184
|
+
draft_available=bool(draft_markdown),
|
|
1185
|
+
subviews=["overview", "details", "draft"] if draft_markdown else ["overview", "details"],
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1031
1188
|
def _build_branch(self) -> dict[str, Any]:
|
|
1032
1189
|
idea_items = [
|
|
1033
1190
|
item
|
|
@@ -1050,6 +1207,8 @@ class QuestStageViewBuilder:
|
|
|
1050
1207
|
idea_title = str(latest_idea_details.get("title") or "").strip() or None
|
|
1051
1208
|
idea_problem = str(latest_idea_details.get("problem") or "").strip() or None
|
|
1052
1209
|
next_target = str(latest_idea_details.get("next_target") or "").strip() or None
|
|
1210
|
+
selection_scores = latest_idea_details.get("selection_scores")
|
|
1211
|
+
selection_score_summary = _selection_score_summary(selection_scores)
|
|
1053
1212
|
lineage_intent = str(
|
|
1054
1213
|
latest_idea_payload.get("lineage_intent")
|
|
1055
1214
|
or latest_idea_details.get("lineage_intent")
|
|
@@ -1169,6 +1328,11 @@ class QuestStageViewBuilder:
|
|
|
1169
1328
|
key_facts=[
|
|
1170
1329
|
_field("Idea Title", idea_title or "Not recorded"),
|
|
1171
1330
|
_field("Idea Problem", idea_problem or "Not recorded"),
|
|
1331
|
+
_field("Method Brief", latest_idea_details.get("method_brief") or "Not recorded"),
|
|
1332
|
+
_field("Selection Scores", selection_score_summary or "Not recorded"),
|
|
1333
|
+
_field("Mechanism Family", latest_idea_details.get("mechanism_family") or "Not recorded"),
|
|
1334
|
+
_field("Change Layer", latest_idea_details.get("change_layer") or "Not recorded"),
|
|
1335
|
+
_field("Source Lens", latest_idea_details.get("source_lens") or "Not recorded"),
|
|
1172
1336
|
_field("Foundation", foundation_label or "Current head"),
|
|
1173
1337
|
_field("Foundation Reason", foundation_reason or "Not recorded"),
|
|
1174
1338
|
_field("Next Target", next_target or "Not recorded"),
|
|
@@ -1224,6 +1388,11 @@ class QuestStageViewBuilder:
|
|
|
1224
1388
|
"lineage_intent": lineage_intent,
|
|
1225
1389
|
"idea_title": idea_title,
|
|
1226
1390
|
"idea_problem": idea_problem,
|
|
1391
|
+
"method_brief": latest_idea_details.get("method_brief"),
|
|
1392
|
+
"selection_scores": selection_scores or None,
|
|
1393
|
+
"mechanism_family": latest_idea_details.get("mechanism_family"),
|
|
1394
|
+
"change_layer": latest_idea_details.get("change_layer"),
|
|
1395
|
+
"source_lens": latest_idea_details.get("source_lens"),
|
|
1227
1396
|
"next_target": next_target,
|
|
1228
1397
|
"idea_draft_path": idea_draft_rel_path,
|
|
1229
1398
|
"idea_draft_markdown": idea_draft_markdown,
|
|
@@ -1579,11 +1748,13 @@ class QuestStageViewBuilder:
|
|
|
1579
1748
|
for path in candidates
|
|
1580
1749
|
],
|
|
1581
1750
|
self._file_entry(paper_root / "selected_outline.json", label="Selected Outline", description="Chosen paper outline."),
|
|
1751
|
+
self._file_entry(paper_root / "outline" / "manifest.json", label="Outline Manifest", description="Author-facing paper outline manifest."),
|
|
1582
1752
|
self._file_entry(paper_root / "outline_selection.md", label="Outline Selection Note", description="Outline selection rationale."),
|
|
1583
1753
|
self._file_entry(paper_root / "draft.md", label="Draft Markdown", description="Current paper draft."),
|
|
1584
1754
|
self._file_entry(paper_root / "writing_plan.md", label="Writing Plan", description="Paper writing plan."),
|
|
1585
1755
|
self._file_entry(paper_root / "references.bib", label="References", description="Bibliography file."),
|
|
1586
1756
|
self._file_entry(paper_root / "claim_evidence_map.json", label="Claim-Evidence Map", description="Claim to evidence mapping."),
|
|
1757
|
+
self._file_entry(paper_root / "paper_line_state.json", label="Paper Line State", description="Derived summary state for the active paper line."),
|
|
1587
1758
|
self._file_entry(paper_root / "baseline_inventory.json", label="Baseline Inventory", description="Canonical and supplementary baseline inventory for writing."),
|
|
1588
1759
|
self._file_entry(paper_root / "build" / "compile_report.json", label="Compile Report", description="Paper build/compile report."),
|
|
1589
1760
|
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
|
|
@@ -24,6 +24,50 @@ _TOOL_EVENT_ARGS_TEXT_LIMIT = 8_000
|
|
|
24
24
|
_TOOL_EVENT_OUTPUT_TEXT_LIMIT = 16_000
|
|
25
25
|
_MAX_QUEST_EVENT_JSON_BYTES = 2_000_000
|
|
26
26
|
_OVERSIZED_EVENT_PREVIEW_TEXT_LIMIT = 12_000
|
|
27
|
+
_BUILTIN_MCP_TOOL_APPROVALS: dict[str, tuple[str, ...]] = {
|
|
28
|
+
"memory": (
|
|
29
|
+
"write",
|
|
30
|
+
"read",
|
|
31
|
+
"search",
|
|
32
|
+
"list_recent",
|
|
33
|
+
"promote_to_global",
|
|
34
|
+
),
|
|
35
|
+
"artifact": (
|
|
36
|
+
"record",
|
|
37
|
+
"checkpoint",
|
|
38
|
+
"prepare_branch",
|
|
39
|
+
"activate_branch",
|
|
40
|
+
"submit_idea",
|
|
41
|
+
"list_research_branches",
|
|
42
|
+
"resolve_runtime_refs",
|
|
43
|
+
"get_paper_contract_health",
|
|
44
|
+
"get_quest_state",
|
|
45
|
+
"get_global_status",
|
|
46
|
+
"get_method_scoreboard",
|
|
47
|
+
"get_optimization_frontier",
|
|
48
|
+
"read_quest_documents",
|
|
49
|
+
"get_conversation_context",
|
|
50
|
+
"get_analysis_campaign",
|
|
51
|
+
"record_main_experiment",
|
|
52
|
+
"create_analysis_campaign",
|
|
53
|
+
"submit_paper_outline",
|
|
54
|
+
"list_paper_outlines",
|
|
55
|
+
"submit_paper_bundle",
|
|
56
|
+
"record_analysis_slice",
|
|
57
|
+
"publish_baseline",
|
|
58
|
+
"attach_baseline",
|
|
59
|
+
"confirm_baseline",
|
|
60
|
+
"waive_baseline",
|
|
61
|
+
"arxiv",
|
|
62
|
+
"refresh_summary",
|
|
63
|
+
"render_git_graph",
|
|
64
|
+
"interact",
|
|
65
|
+
"complete_quest",
|
|
66
|
+
),
|
|
67
|
+
"bash_exec": (
|
|
68
|
+
"bash_exec",
|
|
69
|
+
),
|
|
70
|
+
}
|
|
27
71
|
|
|
28
72
|
|
|
29
73
|
def _compact_text(value: object, *, limit: int = 1200) -> str:
|
|
@@ -322,9 +366,11 @@ def _tool_output(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
|
322
366
|
item.get("result"),
|
|
323
367
|
item.get("output"),
|
|
324
368
|
item.get("content"),
|
|
369
|
+
item.get("error"),
|
|
325
370
|
event.get("result"),
|
|
326
371
|
event.get("output"),
|
|
327
372
|
event.get("content"),
|
|
373
|
+
event.get("error"),
|
|
328
374
|
item.get("aggregated_output"),
|
|
329
375
|
event.get("aggregated_output"),
|
|
330
376
|
):
|
|
@@ -338,11 +384,13 @@ def _tool_output(event: dict[str, Any], item: dict[str, Any]) -> str:
|
|
|
338
384
|
item.get("output"),
|
|
339
385
|
item.get("result"),
|
|
340
386
|
item.get("content"),
|
|
387
|
+
item.get("error"),
|
|
341
388
|
event.get("aggregated_output"),
|
|
342
389
|
event.get("changes"),
|
|
343
390
|
event.get("output"),
|
|
344
391
|
event.get("result"),
|
|
345
392
|
event.get("content"),
|
|
393
|
+
event.get("error"),
|
|
346
394
|
):
|
|
347
395
|
text = _compact_text(value, limit=1200)
|
|
348
396
|
if text:
|
|
@@ -642,6 +690,8 @@ class CodexRunner:
|
|
|
642
690
|
user_message=request.message,
|
|
643
691
|
model=request.model,
|
|
644
692
|
turn_reason=request.turn_reason,
|
|
693
|
+
turn_intent=request.turn_intent,
|
|
694
|
+
turn_mode=request.turn_mode,
|
|
645
695
|
retry_context=request.retry_context,
|
|
646
696
|
)
|
|
647
697
|
write_text(run_root / "prompt.md", prompt)
|
|
@@ -663,6 +713,8 @@ class CodexRunner:
|
|
|
663
713
|
"workspace_root": str(workspace_root),
|
|
664
714
|
"cwd": str(workspace_root),
|
|
665
715
|
"turn_reason": request.turn_reason,
|
|
716
|
+
"turn_intent": request.turn_intent,
|
|
717
|
+
"turn_mode": request.turn_mode,
|
|
666
718
|
},
|
|
667
719
|
)
|
|
668
720
|
|
|
@@ -672,7 +724,10 @@ class CodexRunner:
|
|
|
672
724
|
env_key = str(key or "").strip()
|
|
673
725
|
if not env_key or value is None:
|
|
674
726
|
continue
|
|
675
|
-
|
|
727
|
+
env_value = str(value)
|
|
728
|
+
if env_value == "":
|
|
729
|
+
continue
|
|
730
|
+
env[env_key] = env_value
|
|
676
731
|
env["CODEX_HOME"] = str(codex_home)
|
|
677
732
|
env["DEEPSCIENTIST_HOME"] = str(self.home)
|
|
678
733
|
env["DS_HOME"] = str(self.home)
|
|
@@ -681,6 +736,8 @@ class CodexRunner:
|
|
|
681
736
|
env["DS_WORKTREE_ROOT"] = str(workspace_root)
|
|
682
737
|
env["DS_RUN_ID"] = request.run_id
|
|
683
738
|
env["DS_TURN_REASON"] = request.turn_reason
|
|
739
|
+
env["DS_TURN_INTENT"] = request.turn_intent
|
|
740
|
+
env["DS_TURN_MODE"] = request.turn_mode
|
|
684
741
|
quest_yaml = read_yaml(request.quest_root / "quest.yaml", {})
|
|
685
742
|
env["DS_ACTIVE_ANCHOR"] = str(quest_yaml.get("active_anchor", "baseline"))
|
|
686
743
|
env["DS_CONVERSATION_ID"] = f"quest:{request.quest_id}"
|
|
@@ -740,6 +797,13 @@ class CodexRunner:
|
|
|
740
797
|
timestamp = utc_now()
|
|
741
798
|
append_jsonl(history_events, {"timestamp": timestamp, "event": payload})
|
|
742
799
|
append_jsonl(stdout_events, {"timestamp": timestamp, "line": line})
|
|
800
|
+
try:
|
|
801
|
+
self.artifact_service.quest_service.schedule_projection_refresh(
|
|
802
|
+
request.quest_root,
|
|
803
|
+
kinds=("details",),
|
|
804
|
+
)
|
|
805
|
+
except Exception:
|
|
806
|
+
pass
|
|
743
807
|
tool_event = _tool_event(
|
|
744
808
|
payload,
|
|
745
809
|
quest_id=request.quest_id,
|
|
@@ -810,6 +874,14 @@ class CodexRunner:
|
|
|
810
874
|
}
|
|
811
875
|
write_json(run_root / "result.json", result_payload)
|
|
812
876
|
write_json(history_root / "meta.json", result_payload)
|
|
877
|
+
try:
|
|
878
|
+
self.artifact_service.quest_service.schedule_projection_refresh(
|
|
879
|
+
request.quest_root,
|
|
880
|
+
kinds=("details",),
|
|
881
|
+
throttle_seconds=0.0,
|
|
882
|
+
)
|
|
883
|
+
except Exception:
|
|
884
|
+
pass
|
|
813
885
|
self.logger.log(
|
|
814
886
|
"info",
|
|
815
887
|
"runner.codex.completed",
|
|
@@ -822,6 +894,7 @@ class CodexRunner:
|
|
|
822
894
|
request.quest_root,
|
|
823
895
|
{
|
|
824
896
|
"kind": "run",
|
|
897
|
+
"status": "completed" if exit_code == 0 else "failed",
|
|
825
898
|
"run_id": request.run_id,
|
|
826
899
|
"run_kind": request.skill_id,
|
|
827
900
|
"model": request.model,
|
|
@@ -954,10 +1027,11 @@ class CodexRunner:
|
|
|
954
1027
|
for filename in ("config.toml", "auth.json"):
|
|
955
1028
|
source_path = source / filename
|
|
956
1029
|
target_path = target / filename
|
|
957
|
-
if
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1030
|
+
if not source_path.exists():
|
|
1031
|
+
continue
|
|
1032
|
+
if source_path.resolve() == target_path.resolve():
|
|
1033
|
+
continue
|
|
1034
|
+
shutil.copy2(source_path, target_path)
|
|
961
1035
|
config_path = target / "config.toml"
|
|
962
1036
|
if profile and config_path.exists():
|
|
963
1037
|
adapted_text, _ = adapt_profile_only_provider_config(read_text(config_path), profile=profile)
|
|
@@ -1043,6 +1117,7 @@ class CodexRunner:
|
|
|
1043
1117
|
args = ["-m", "deepscientist.mcp.server", "--namespace", name]
|
|
1044
1118
|
lines = [
|
|
1045
1119
|
f"[mcp_servers.{name}]",
|
|
1120
|
+
'transport = "stdio"',
|
|
1046
1121
|
f'command = "{sys.executable}"',
|
|
1047
1122
|
f"args = [{', '.join(json.dumps(item) for item in args)}]",
|
|
1048
1123
|
]
|
|
@@ -1057,6 +1132,14 @@ class CodexRunner:
|
|
|
1057
1132
|
)
|
|
1058
1133
|
for key, value in env.items():
|
|
1059
1134
|
lines.append(f"{key} = {json.dumps(value)}")
|
|
1135
|
+
for tool_name in _BUILTIN_MCP_TOOL_APPROVALS.get(name, ()):
|
|
1136
|
+
lines.extend(
|
|
1137
|
+
[
|
|
1138
|
+
"",
|
|
1139
|
+
f"[mcp_servers.{name}.tools.{tool_name}]",
|
|
1140
|
+
'approval_mode = "approve"',
|
|
1141
|
+
]
|
|
1142
|
+
)
|
|
1060
1143
|
return "\n".join(lines)
|
|
1061
1144
|
|
|
1062
1145
|
def _load_runner_config(self) -> dict[str, Any]:
|
|
@@ -17,6 +17,18 @@ def _as_bool_env(name: str) -> bool:
|
|
|
17
17
|
return value.lower() in {"1", "true", "yes", "on", "y"}
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _as_optional_bool_env(name: str) -> bool | None:
|
|
21
|
+
value = _as_text(os.environ.get(name))
|
|
22
|
+
if value is None:
|
|
23
|
+
return None
|
|
24
|
+
normalized = value.lower()
|
|
25
|
+
if normalized in {"1", "true", "yes", "on", "y"}:
|
|
26
|
+
return True
|
|
27
|
+
if normalized in {"0", "false", "no", "off", "n"}:
|
|
28
|
+
return False
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
20
32
|
def codex_runtime_overrides() -> dict[str, str]:
|
|
21
33
|
binary = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_BINARY") or os.environ.get("DS_CODEX_BINARY"))
|
|
22
34
|
approval_policy = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_APPROVAL_POLICY"))
|
|
@@ -24,9 +36,13 @@ def codex_runtime_overrides() -> dict[str, str]:
|
|
|
24
36
|
profile = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_PROFILE"))
|
|
25
37
|
model = _as_text(os.environ.get("DEEPSCIENTIST_CODEX_MODEL"))
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
yolo_enabled = _as_optional_bool_env("DEEPSCIENTIST_CODEX_YOLO")
|
|
40
|
+
if yolo_enabled is True:
|
|
28
41
|
approval_policy = approval_policy or "never"
|
|
29
42
|
sandbox_mode = sandbox_mode or "danger-full-access"
|
|
43
|
+
elif yolo_enabled is False:
|
|
44
|
+
approval_policy = approval_policy or "on-request"
|
|
45
|
+
sandbox_mode = sandbox_mode or "workspace-write"
|
|
30
46
|
|
|
31
47
|
overrides: dict[str, str] = {}
|
|
32
48
|
if binary:
|
|
@@ -6,12 +6,21 @@ This shared contract is injected once per turn and applies across the stage and
|
|
|
6
6
|
|
|
7
7
|
- Treat `artifact.interact(...)` as the main long-lived communication thread across TUI, web, and bound connectors.
|
|
8
8
|
- If `artifact.interact(...)` returns queued user requirements, treat them as the highest-priority user instruction bundle before continuing the current stage or companion-skill task.
|
|
9
|
-
- Immediately follow any non-empty mailbox poll with another `artifact.interact(...)` update that confirms receipt; if the request is directly answerable, answer there
|
|
9
|
+
- Immediately follow any non-empty mailbox poll with another `artifact.interact(...)` update that confirms receipt; if the request is directly answerable, answer there with `kind='answer'`, otherwise say the current subtask is paused, give a short plan plus nearest report-back point, and handle that request first.
|
|
10
|
+
- If you are explicitly answering or continuing a specific prior interaction thread, use `reply_to_interaction_id` instead of assuming the runtime will always infer the right target.
|
|
10
11
|
- Stage-kickoff rule: after entering any stage or companion skill, send one `artifact.interact(kind='progress', reply_mode='threaded', ...)` update within the first 3 tool calls of substantial work.
|
|
11
12
|
- Reading/planning keepalive rule: if you spend 5 consecutive tool calls on reading, searching, comparison, or planning without a user-visible update, send one concise checkpoint even if the route is not finalized yet.
|
|
13
|
+
- Visibility-bound rule: do not drift beyond roughly 12 tool calls or about 8 minutes without a user-visible update when the user-visible state has materially changed.
|
|
12
14
|
- Subtask-boundary rule: send a user-visible update whenever the active subtask changes materially, especially across intake -> audit, audit -> experiment planning, experiment planning -> run launch, run result -> drafting, or drafting -> review/rebuttal.
|
|
13
|
-
- Emit `artifact.interact(kind='progress', reply_mode='threaded', ...)` when there is real user-visible progress: a meaningful checkpoint, route-shaping update, or a concise keepalive
|
|
15
|
+
- Emit `artifact.interact(kind='progress', reply_mode='threaded', ...)` when there is real user-visible progress: a meaningful checkpoint, route-shaping update, blocker, recovery, or a concise keepalive when silence would otherwise hide a meaningful change. Do not reflexively send another progress update if the user-visible state is unchanged.
|
|
14
16
|
- Keep progress updates chat-like and easy to understand: say what changed, what it means, and what happens next.
|
|
15
|
-
-
|
|
17
|
+
- Keep the tone respectful and easy to understand. In Chinese, natural respectful phrasing is good; in English, keep a polite professional tone.
|
|
18
|
+
- Assume the user may not know the codebase or internal runtime objects. Explain progress in beginner-friendly task language before technical detail.
|
|
19
|
+
- If there are `2-3` options, tradeoffs, or next steps, prefer a short numbered list instead of a dense block of prose.
|
|
20
|
+
- If a key distinction is quantitative and the number is known, include the number or one short concrete example instead of only saying `better`, `slower`, or `more stable`.
|
|
21
|
+
- Default to plain-language summaries. Do not mention file paths, file names, artifact ids, branch/worktree ids, session ids, raw commands, or raw logs unless the user asks or needs them to act. First translate them into user-facing meaning such as baseline record, draft, experiment result, or supplementary run.
|
|
22
|
+
- When the user is plainly asking a direct question, answer it directly in plain language before resuming background stage work.
|
|
16
23
|
- Use `reply_mode='blocking'` only for real user decisions that cannot be resolved from local evidence.
|
|
17
|
-
-
|
|
24
|
+
- Keep `deliver_to_bound_conversations=True` for normal user-visible continuity. If `delivery_results` or `attachment_issues` show that requested delivery failed, treat that as a real failure and adapt instead of assuming the user already received the message or file.
|
|
25
|
+
- Use `dedupe_key`, `suppress_if_unchanged`, and `min_interval_seconds` only to suppress repeated unchanged `progress` updates, not to suppress a real answer or milestone.
|
|
26
|
+
- For any blocking decision request, provide 1 to 3 concrete options, put the recommended option first, and explain for each option: what it means, how strongly you recommend it, its likely impact on speed / quality / cost / risk, and when it is preferable. Make the user's reply format obvious and wait up to 1 day when feasible. If the blocker is a missing external credential or secret that only the user can provide, keep the quest waiting, ask the user to supply it or choose an alternative, and do not self-resolve; if resumed without that credential and no other work is possible, a long low-frequency wait such as `bash_exec(command='sleep 3600', mode='await', timeout_seconds=3700)` is acceptable. Otherwise choose the best option yourself and notify the user of the chosen option if the timeout expires.
|