@researai/deepscientist 1.5.8 → 1.5.11
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/LICENSE +186 -21
- package/README.md +108 -95
- 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 +172 -13
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +308 -70
- package/docs/en/01_SETTINGS_REFERENCE.md +3 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +112 -0
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +41 -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 +427 -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/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +79 -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 +315 -74
- package/docs/zh/01_SETTINGS_REFERENCE.md +3 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +112 -0
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +41 -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 +423 -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/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +126 -0
- package/install.sh +0 -34
- package/package.json +3 -3
- package/pyproject.toml +2 -2
- 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/metrics.py +1 -3
- package/src/deepscientist/artifact/service.py +1347 -111
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/service.py +9 -0
- 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/qq.py +1 -1
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +7 -1
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +317 -0
- package/src/deepscientist/config/models.py +22 -2
- package/src/deepscientist/config/service.py +431 -60
- 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 +295 -5
- package/src/deepscientist/daemon/api/router.py +16 -1
- package/src/deepscientist/daemon/app.py +1130 -61
- package/src/deepscientist/doctor.py +5 -2
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +14 -5
- package/src/deepscientist/prompts/builder.py +29 -1
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +152 -2
- package/src/deepscientist/quest/service.py +169 -43
- package/src/deepscientist/quest/stage_views.py +172 -9
- package/src/deepscientist/registries/baseline.py +56 -4
- package/src/deepscientist/runners/codex.py +55 -3
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/weixin.md +230 -0
- package/src/prompts/system.md +9 -0
- package/src/skills/idea/SKILL.md +16 -0
- package/src/skills/idea/references/literature-survey-template.md +24 -0
- package/src/skills/idea/references/related-work-playbook.md +4 -0
- package/src/skills/idea/references/selection-gate.md +9 -0
- package/src/skills/write/SKILL.md +1 -1
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-m2FNtwbn.js → AiManusChatView-D0mTXG4-.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-BMTF8EGL.js → AnalysisPlugin-Db0cTXxm.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BEOWgxCI.js → CliPlugin-DrV8je02.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BCXvjqmb.js → CodeEditorPlugin-QXMSCH71.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DaJcy3nD.js → CodeViewerPlugin-7hhtWj_E.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ByfeIq4K.js → DocViewerPlugin-BWMSnRJe.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-Cksf3VZ-.js → GitDiffViewerPlugin-7J9h9Vy_.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-CFz-OsTS.js → ImageViewerPlugin-CHJl_0lr.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-CJ1cJzoX.js → LabCopilotPanel-1qSow1es.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-BF3dVJwa.js → LabPlugin-eQpPPCEp.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DDkwZ6Sj.js → LatexPlugin-BwRfi89Z.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-HAuvurcT.js → MarkdownViewerPlugin-836PVQWV.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-BtoTYy2C.js → MarketplacePlugin-C2y_556i.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-CSJYx7b-.js → NotebookEditor-BRzJbGsn.js} +12 -12
- package/src/ui/dist/assets/{NotebookEditor-DQgRezm_.js → NotebookEditor-DIX7Mlzu.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-DPa_-fv6.js → PdfLoader-DzRaTAlq.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-BZpXOEjm.js → PdfMarkdownPlugin-DZUfIUnp.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BT8a6wGR.js → PdfViewerPlugin-BwtICzue.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-D_blveZi.js → SearchPlugin-DHeIAMsx.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-Btx0M3hX.js → TextViewerPlugin-C3tCmFox.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-DImJO4rO.js → VNCViewer-CQsKVm3t.js} +10 -10
- package/src/ui/dist/assets/bot-BEA2vWuK.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-BUfXGJSl.js → code-XfbSR8K2.js} +1 -1
- package/src/ui/dist/assets/{file-content-VqamwI3X.js → file-content-BjxNaIfy.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-C_wOoS7a.js → file-diff-panel-D_lLVQk0.js} +1 -1
- package/src/ui/dist/assets/{file-socket-D2bTuMVP.js → file-socket-D9x_5vlY.js} +1 -1
- package/src/ui/dist/assets/{image-BZkGJ4mM.js → image-BhWT33W1.js} +1 -1
- package/src/ui/dist/assets/{index-DdRW6RMJ.js → index--c4iXtuy.js} +12 -12
- package/src/ui/dist/assets/{index-CxkvSeKw.js → index-BDxipwrC.js} +2 -2
- package/src/ui/dist/assets/{index-DjggJovS.js → index-DZTZ8mWP.js} +14934 -9613
- package/src/ui/dist/assets/{index-DXZ1daiJ.css → index-Dqj-Mjb4.css} +2 -13
- package/src/ui/dist/assets/index-PJbSbPTy.js +25 -0
- package/src/ui/dist/assets/{monaco-DHMc7kKM.js → monaco-K8izTGgo.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DfBors6y.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-B85oCgCS.js → popover-yFK1J4fL.js} +1 -1
- package/src/ui/dist/assets/{project-sync-DOMCcPac.js → project-sync-PENr2zcz.js} +1 -74
- package/src/ui/dist/assets/select-CAbJDfYv.js +1690 -0
- package/src/ui/dist/assets/{sigma-BO2rQrl3.js → sigma-DEuYJqTl.js} +1 -1
- package/src/ui/dist/assets/{index-D9QIGcmc.js → square-check-big-omoSUmcd.js} +2 -13
- package/src/ui/dist/assets/{trash-BsVEH_dV.js → trash--F119N47.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-b8L6JuZm.js → useCliAccess-D31UR23I.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-BY7uA9hV.js → useFileDiffOverlay-BH6KcMzq.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-BwyVuUIK.js → wrap-text-CZ613PM5.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-RDpLugQP.js → zoom-out-BgDLAv3z.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-DxPdMUNb.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-DH2k75Vo.js +0 -158
- package/src/ui/dist/assets/bibtex-B-Hqu0Sg.js +0 -189
- package/src/ui/dist/assets/file-utils--zJCPN1i.js +0 -109
- package/src/ui/dist/assets/message-square-FUIPIhU2.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-B1OspAkx.js +0 -108
|
@@ -19,9 +19,10 @@ except ImportError: # pragma: no cover
|
|
|
19
19
|
|
|
20
20
|
from ..artifact.metrics import build_metrics_timeline, extract_latest_metric
|
|
21
21
|
from ..config import ConfigManager
|
|
22
|
-
from ..connector_runtime import conversation_identity_key, normalize_conversation_id
|
|
22
|
+
from ..connector_runtime import conversation_identity_key, normalize_conversation_id, parse_conversation_id
|
|
23
23
|
from ..gitops import current_branch, export_git_graph, head_commit, init_repo
|
|
24
24
|
from ..home import repo_root
|
|
25
|
+
from ..registries import BaselineRegistry
|
|
25
26
|
from ..shared import append_jsonl, ensure_dir, generate_id, read_json, read_jsonl, read_text, read_yaml, resolve_within, run_command, sha256_text, slugify, utc_now, write_json, write_text, write_yaml
|
|
26
27
|
from ..skills import SkillInstaller
|
|
27
28
|
from ..web_search import extract_web_search_payload
|
|
@@ -48,6 +49,7 @@ class QuestService:
|
|
|
48
49
|
self.home = home
|
|
49
50
|
self.quests_root = home / "quests"
|
|
50
51
|
self.skill_installer = skill_installer
|
|
52
|
+
self.baseline_registry = BaselineRegistry(home)
|
|
51
53
|
self._file_cache_lock = threading.Lock()
|
|
52
54
|
self._file_cache: dict[str, dict[str, Any]] = {}
|
|
53
55
|
self._jsonl_cache_lock = threading.Lock()
|
|
@@ -62,6 +64,35 @@ class QuestService:
|
|
|
62
64
|
def _quest_root(self, quest_id: str) -> Path:
|
|
63
65
|
return self.quests_root / quest_id
|
|
64
66
|
|
|
67
|
+
def _normalized_binding_sources(self, sources: list[Any] | None) -> list[str]:
|
|
68
|
+
local_present = False
|
|
69
|
+
external_source: str | None = None
|
|
70
|
+
for raw in sources or []:
|
|
71
|
+
normalized = self._normalize_binding_source(raw)
|
|
72
|
+
if not normalized:
|
|
73
|
+
continue
|
|
74
|
+
if normalized == "local:default":
|
|
75
|
+
local_present = True
|
|
76
|
+
continue
|
|
77
|
+
parsed = parse_conversation_id(normalized)
|
|
78
|
+
connector = str((parsed or {}).get("connector") or "").strip().lower()
|
|
79
|
+
if connector == "local":
|
|
80
|
+
local_present = True
|
|
81
|
+
continue
|
|
82
|
+
external_source = normalized
|
|
83
|
+
if external_source:
|
|
84
|
+
return ["local:default", external_source]
|
|
85
|
+
if local_present:
|
|
86
|
+
return ["local:default"]
|
|
87
|
+
return ["local:default"]
|
|
88
|
+
|
|
89
|
+
def _binding_sources_payload(self, quest_root: Path) -> dict[str, list[str]]:
|
|
90
|
+
bindings_path = quest_root / ".ds" / "bindings.json"
|
|
91
|
+
payload = read_json(bindings_path, {"sources": ["local:default"]})
|
|
92
|
+
raw_sources = payload.get("sources") if isinstance(payload, dict) else ["local:default"]
|
|
93
|
+
sources = self._normalized_binding_sources(raw_sources if isinstance(raw_sources, list) else ["local:default"])
|
|
94
|
+
return {"sources": sources}
|
|
95
|
+
|
|
65
96
|
def preferred_locale(self, quest_root: Path | None = None) -> str:
|
|
66
97
|
if quest_root is not None:
|
|
67
98
|
try:
|
|
@@ -116,6 +147,10 @@ class QuestService:
|
|
|
116
147
|
def _research_state_path(quest_root: Path) -> Path:
|
|
117
148
|
return quest_root / ".ds" / "research_state.json"
|
|
118
149
|
|
|
150
|
+
@staticmethod
|
|
151
|
+
def _lab_canvas_state_path(quest_root: Path) -> Path:
|
|
152
|
+
return quest_root / ".ds" / "lab_canvas_state.json"
|
|
153
|
+
|
|
119
154
|
def _default_research_state(self, quest_root: Path) -> dict[str, Any]:
|
|
120
155
|
return {
|
|
121
156
|
"version": 1,
|
|
@@ -138,6 +173,18 @@ class QuestService:
|
|
|
138
173
|
"updated_at": utc_now(),
|
|
139
174
|
}
|
|
140
175
|
|
|
176
|
+
def _default_lab_canvas_state(self, quest_root: Path) -> dict[str, Any]:
|
|
177
|
+
return {
|
|
178
|
+
"version": 1,
|
|
179
|
+
"layout_json": {
|
|
180
|
+
"branch": {},
|
|
181
|
+
"event": {},
|
|
182
|
+
"stage": {},
|
|
183
|
+
"preferences": {},
|
|
184
|
+
},
|
|
185
|
+
"updated_at": utc_now(),
|
|
186
|
+
}
|
|
187
|
+
|
|
141
188
|
def read_research_state(self, quest_root: Path) -> dict[str, Any]:
|
|
142
189
|
self._initialize_runtime_files(quest_root)
|
|
143
190
|
defaults = self._default_research_state(quest_root)
|
|
@@ -172,6 +219,39 @@ class QuestService:
|
|
|
172
219
|
current[key] = str(value) if isinstance(value, Path) else value
|
|
173
220
|
return self.write_research_state(quest_root, current)
|
|
174
221
|
|
|
222
|
+
def read_lab_canvas_state(self, quest_root: Path) -> dict[str, Any]:
|
|
223
|
+
self._initialize_runtime_files(quest_root)
|
|
224
|
+
defaults = self._default_lab_canvas_state(quest_root)
|
|
225
|
+
payload = self._read_cached_json(self._lab_canvas_state_path(quest_root), defaults)
|
|
226
|
+
if not isinstance(payload, dict):
|
|
227
|
+
payload = defaults
|
|
228
|
+
merged = {**defaults, **payload}
|
|
229
|
+
layout_json = dict(merged.get("layout_json") or {}) if isinstance(merged.get("layout_json"), dict) else {}
|
|
230
|
+
for key in ("branch", "event", "stage", "preferences"):
|
|
231
|
+
if not isinstance(layout_json.get(key), dict):
|
|
232
|
+
layout_json[key] = {}
|
|
233
|
+
merged["layout_json"] = layout_json
|
|
234
|
+
return merged
|
|
235
|
+
|
|
236
|
+
def update_lab_canvas_state(
|
|
237
|
+
self,
|
|
238
|
+
quest_root: Path,
|
|
239
|
+
*,
|
|
240
|
+
layout_json: dict[str, Any] | None = None,
|
|
241
|
+
) -> dict[str, Any]:
|
|
242
|
+
current = self.read_lab_canvas_state(quest_root)
|
|
243
|
+
normalized_layout = dict(layout_json or {}) if isinstance(layout_json, dict) else {}
|
|
244
|
+
for key in ("branch", "event", "stage", "preferences"):
|
|
245
|
+
if not isinstance(normalized_layout.get(key), dict):
|
|
246
|
+
normalized_layout[key] = {}
|
|
247
|
+
payload = {
|
|
248
|
+
**current,
|
|
249
|
+
"layout_json": normalized_layout,
|
|
250
|
+
"updated_at": utc_now(),
|
|
251
|
+
}
|
|
252
|
+
write_json(self._lab_canvas_state_path(quest_root), payload)
|
|
253
|
+
return payload
|
|
254
|
+
|
|
175
255
|
def workspace_roots(self, quest_root: Path) -> list[Path]:
|
|
176
256
|
roots: list[Path] = [quest_root]
|
|
177
257
|
state = self.read_research_state(quest_root)
|
|
@@ -305,6 +385,9 @@ class QuestService:
|
|
|
305
385
|
continue
|
|
306
386
|
seen_paths.add(key)
|
|
307
387
|
payload = self._read_cached_yaml(path, {})
|
|
388
|
+
baseline_id = str(payload.get("source_baseline_id") or "").strip() if isinstance(payload, dict) else ""
|
|
389
|
+
if baseline_id and self.baseline_registry.is_deleted(baseline_id):
|
|
390
|
+
continue
|
|
308
391
|
if isinstance(payload, dict) and payload:
|
|
309
392
|
attachments.append(payload)
|
|
310
393
|
if not attachments:
|
|
@@ -684,7 +767,7 @@ class QuestService:
|
|
|
684
767
|
"last_artifact_interact_at": runtime_state.get("last_artifact_interact_at"),
|
|
685
768
|
"last_delivered_batch_id": runtime_state.get("last_delivered_batch_id"),
|
|
686
769
|
"last_delivered_at": runtime_state.get("last_delivered_at"),
|
|
687
|
-
"bound_conversations":
|
|
770
|
+
"bound_conversations": self._binding_sources_payload(quest_root).get("sources") or ["local:default"],
|
|
688
771
|
"created_at": quest_yaml.get("created_at"),
|
|
689
772
|
"updated_at": quest_yaml.get("updated_at"),
|
|
690
773
|
"branch": research_state.get("current_workspace_branch") or research_state.get("research_head_branch"),
|
|
@@ -1039,7 +1122,7 @@ class QuestService:
|
|
|
1039
1122
|
"last_artifact_interact_at": runtime_state.get("last_artifact_interact_at"),
|
|
1040
1123
|
"last_delivered_batch_id": runtime_state.get("last_delivered_batch_id"),
|
|
1041
1124
|
"last_delivered_at": runtime_state.get("last_delivered_at"),
|
|
1042
|
-
"bound_conversations":
|
|
1125
|
+
"bound_conversations": self._binding_sources_payload(quest_root).get("sources") or ["local:default"],
|
|
1043
1126
|
"created_at": quest_yaml.get("created_at"),
|
|
1044
1127
|
"updated_at": quest_yaml.get("updated_at"),
|
|
1045
1128
|
"branch": current_branch(workspace_root),
|
|
@@ -1308,61 +1391,30 @@ class QuestService:
|
|
|
1308
1391
|
def bind_source(self, quest_id: str, source: str) -> dict:
|
|
1309
1392
|
quest_root = self._quest_root(quest_id)
|
|
1310
1393
|
bindings_path = quest_root / ".ds" / "bindings.json"
|
|
1311
|
-
bindings =
|
|
1394
|
+
bindings = self._binding_sources_payload(quest_root)
|
|
1312
1395
|
normalized_source = self._normalize_binding_source(source)
|
|
1313
|
-
|
|
1314
|
-
changed =
|
|
1315
|
-
replaced = False
|
|
1316
|
-
sources: list[str] = []
|
|
1317
|
-
for item in list(bindings.get("sources") or []):
|
|
1318
|
-
existing = self._normalize_binding_source(str(item))
|
|
1319
|
-
if conversation_identity_key(existing) == normalized_key:
|
|
1320
|
-
if not replaced:
|
|
1321
|
-
sources.append(normalized_source)
|
|
1322
|
-
replaced = True
|
|
1323
|
-
if existing != normalized_source:
|
|
1324
|
-
changed = True
|
|
1325
|
-
else:
|
|
1326
|
-
changed = True
|
|
1327
|
-
continue
|
|
1328
|
-
sources.append(existing)
|
|
1329
|
-
if existing != item:
|
|
1330
|
-
changed = True
|
|
1331
|
-
if not replaced:
|
|
1332
|
-
sources.append(normalized_source)
|
|
1333
|
-
changed = True
|
|
1396
|
+
next_sources = self._normalized_binding_sources([*(bindings.get("sources") or []), normalized_source])
|
|
1397
|
+
changed = list(bindings.get("sources") or []) != next_sources
|
|
1334
1398
|
if changed:
|
|
1335
|
-
bindings["sources"] =
|
|
1399
|
+
bindings["sources"] = next_sources
|
|
1336
1400
|
write_json(bindings_path, bindings)
|
|
1337
1401
|
return bindings
|
|
1338
1402
|
|
|
1339
1403
|
def binding_sources(self, quest_id: str) -> list[str]:
|
|
1340
1404
|
quest_root = self._quest_root(quest_id)
|
|
1341
|
-
|
|
1342
|
-
bindings = read_json(bindings_path, {"sources": ["local:default"]})
|
|
1343
|
-
sources = [self._normalize_binding_source(item) for item in (bindings.get("sources") or [])]
|
|
1344
|
-
return [item for item in sources if item]
|
|
1405
|
+
return list(self._binding_sources_payload(quest_root).get("sources") or ["local:default"])
|
|
1345
1406
|
|
|
1346
1407
|
def set_binding_sources(self, quest_id: str, sources: list[str]) -> dict:
|
|
1347
1408
|
quest_root = self._quest_root(quest_id)
|
|
1348
1409
|
bindings_path = quest_root / ".ds" / "bindings.json"
|
|
1349
|
-
|
|
1350
|
-
ordered: list[str] = []
|
|
1351
|
-
seen: set[str] = set()
|
|
1352
|
-
for item in normalized_sources:
|
|
1353
|
-
key = conversation_identity_key(item)
|
|
1354
|
-
if not item or key in seen:
|
|
1355
|
-
continue
|
|
1356
|
-
seen.add(key)
|
|
1357
|
-
ordered.append(item)
|
|
1358
|
-
payload = {"sources": ordered}
|
|
1410
|
+
payload = {"sources": self._normalized_binding_sources(sources)}
|
|
1359
1411
|
write_json(bindings_path, payload)
|
|
1360
1412
|
return payload
|
|
1361
1413
|
|
|
1362
1414
|
def unbind_source(self, quest_id: str, source: str) -> dict:
|
|
1363
1415
|
quest_root = self._quest_root(quest_id)
|
|
1364
1416
|
bindings_path = quest_root / ".ds" / "bindings.json"
|
|
1365
|
-
bindings =
|
|
1417
|
+
bindings = self._binding_sources_payload(quest_root)
|
|
1366
1418
|
normalized_source = self._normalize_binding_source(source)
|
|
1367
1419
|
normalized_key = conversation_identity_key(normalized_source)
|
|
1368
1420
|
changed = False
|
|
@@ -1375,8 +1427,11 @@ class QuestService:
|
|
|
1375
1427
|
sources.append(existing)
|
|
1376
1428
|
if existing != item:
|
|
1377
1429
|
changed = True
|
|
1430
|
+
normalized_sources = self._normalized_binding_sources(sources)
|
|
1431
|
+
if normalized_sources != list(bindings.get("sources") or []):
|
|
1432
|
+
changed = True
|
|
1378
1433
|
if changed:
|
|
1379
|
-
bindings["sources"] =
|
|
1434
|
+
bindings["sources"] = normalized_sources
|
|
1380
1435
|
write_json(bindings_path, bindings)
|
|
1381
1436
|
return bindings
|
|
1382
1437
|
|
|
@@ -1770,6 +1825,12 @@ class QuestService:
|
|
|
1770
1825
|
resolved_selection = dict(selection or {})
|
|
1771
1826
|
selection_ref = str(resolved_selection.get("selection_ref") or "").strip()
|
|
1772
1827
|
selection_type = str(resolved_selection.get("selection_type") or "stage_node").strip() or None
|
|
1828
|
+
if (
|
|
1829
|
+
selection_type == "branch_node"
|
|
1830
|
+
and selection_ref
|
|
1831
|
+
and not str(resolved_selection.get("branch_name") or "").strip()
|
|
1832
|
+
):
|
|
1833
|
+
resolved_selection["branch_name"] = selection_ref
|
|
1773
1834
|
trace = None
|
|
1774
1835
|
if selection_ref:
|
|
1775
1836
|
try:
|
|
@@ -2026,7 +2087,18 @@ class QuestService:
|
|
|
2026
2087
|
if document_id.startswith(("questpath::", "memory::"))
|
|
2027
2088
|
else workspace_root
|
|
2028
2089
|
)
|
|
2029
|
-
|
|
2090
|
+
try:
|
|
2091
|
+
path, writable, scope, source_kind = self._resolve_document(resolution_root, document_id)
|
|
2092
|
+
except FileNotFoundError:
|
|
2093
|
+
legacy_relative = None
|
|
2094
|
+
if document_id.startswith("path::"):
|
|
2095
|
+
legacy_relative = document_id.split("::", 1)[1].lstrip("/")
|
|
2096
|
+
if legacy_relative and legacy_relative.startswith("literature/arxiv/"):
|
|
2097
|
+
path, writable, scope, source_kind = self._resolve_document(
|
|
2098
|
+
quest_root, f"questpath::{legacy_relative}"
|
|
2099
|
+
)
|
|
2100
|
+
else:
|
|
2101
|
+
raise
|
|
2030
2102
|
renderer_hint, mime_type = self._renderer_hint_for(path)
|
|
2031
2103
|
is_text = self._is_text_document(path, mime_type, renderer_hint)
|
|
2032
2104
|
content = read_text(path) if is_text else ""
|
|
@@ -2417,6 +2489,9 @@ class QuestService:
|
|
|
2417
2489
|
research_state_path = self._research_state_path(quest_root)
|
|
2418
2490
|
if not research_state_path.exists():
|
|
2419
2491
|
write_json(research_state_path, self._default_research_state(quest_root))
|
|
2492
|
+
lab_canvas_state_path = self._lab_canvas_state_path(quest_root)
|
|
2493
|
+
if not lab_canvas_state_path.exists():
|
|
2494
|
+
write_json(lab_canvas_state_path, self._default_lab_canvas_state(quest_root))
|
|
2420
2495
|
agent_status_path = self._agent_status_path(quest_root)
|
|
2421
2496
|
if not agent_status_path.exists():
|
|
2422
2497
|
write_json(agent_status_path, self._default_agent_status(quest_root))
|
|
@@ -3492,7 +3567,35 @@ def _tool_name(event: dict, item: dict) -> str:
|
|
|
3492
3567
|
return "tool"
|
|
3493
3568
|
|
|
3494
3569
|
|
|
3570
|
+
def _structured_text(value: object) -> str:
|
|
3571
|
+
if value is None:
|
|
3572
|
+
return ""
|
|
3573
|
+
if isinstance(value, str):
|
|
3574
|
+
return value.strip()
|
|
3575
|
+
try:
|
|
3576
|
+
return json.dumps(value, ensure_ascii=False, indent=2)
|
|
3577
|
+
except TypeError:
|
|
3578
|
+
return str(value)
|
|
3579
|
+
|
|
3580
|
+
|
|
3581
|
+
def _is_bash_exec_item(event: dict, item: dict) -> bool:
|
|
3582
|
+
server = str(item.get("server") or event.get("server") or "").strip()
|
|
3583
|
+
tool = str(item.get("tool") or event.get("tool") or "").strip()
|
|
3584
|
+
return server == "bash_exec" and tool == "bash_exec"
|
|
3585
|
+
|
|
3586
|
+
|
|
3495
3587
|
def _tool_args(event: dict, item: dict) -> str:
|
|
3588
|
+
if _is_bash_exec_item(event, item):
|
|
3589
|
+
for value in (
|
|
3590
|
+
item.get("arguments"),
|
|
3591
|
+
event.get("arguments"),
|
|
3592
|
+
item.get("input"),
|
|
3593
|
+
event.get("input"),
|
|
3594
|
+
):
|
|
3595
|
+
text = _structured_text(value)
|
|
3596
|
+
if text:
|
|
3597
|
+
return text
|
|
3598
|
+
return ""
|
|
3496
3599
|
for value in (
|
|
3497
3600
|
item.get("command"),
|
|
3498
3601
|
item.get("query"),
|
|
@@ -3512,6 +3615,21 @@ def _tool_args(event: dict, item: dict) -> str:
|
|
|
3512
3615
|
|
|
3513
3616
|
|
|
3514
3617
|
def _tool_output(event: dict, item: dict) -> str:
|
|
3618
|
+
if _is_bash_exec_item(event, item):
|
|
3619
|
+
for value in (
|
|
3620
|
+
item.get("result"),
|
|
3621
|
+
item.get("output"),
|
|
3622
|
+
item.get("content"),
|
|
3623
|
+
event.get("result"),
|
|
3624
|
+
event.get("output"),
|
|
3625
|
+
event.get("content"),
|
|
3626
|
+
item.get("aggregated_output"),
|
|
3627
|
+
event.get("aggregated_output"),
|
|
3628
|
+
):
|
|
3629
|
+
text = _structured_text(value)
|
|
3630
|
+
if text:
|
|
3631
|
+
return text
|
|
3632
|
+
return ""
|
|
3515
3633
|
for value in (
|
|
3516
3634
|
item.get("aggregated_output"),
|
|
3517
3635
|
item.get("changes"),
|
|
@@ -3554,17 +3672,25 @@ def _mcp_tool_metadata(*, quest_id: str, run_id: str, server: str, tool: str, it
|
|
|
3554
3672
|
for key in ("command", "workdir", "mode", "timeout_seconds", "comment"):
|
|
3555
3673
|
if key in arguments:
|
|
3556
3674
|
metadata[key] = arguments.get(key)
|
|
3675
|
+
if server == "bash_exec" and tool == "bash_exec" and isinstance(arguments.get("id"), str):
|
|
3676
|
+
metadata["bash_id"] = arguments.get("id")
|
|
3557
3677
|
result_payload = _mcp_result_payload(item)
|
|
3558
3678
|
if server == "bash_exec" and tool == "bash_exec":
|
|
3559
3679
|
for key in (
|
|
3560
3680
|
"bash_id",
|
|
3561
3681
|
"status",
|
|
3682
|
+
"command",
|
|
3683
|
+
"workdir",
|
|
3684
|
+
"cwd",
|
|
3685
|
+
"kind",
|
|
3686
|
+
"comment",
|
|
3562
3687
|
"started_at",
|
|
3563
3688
|
"finished_at",
|
|
3564
3689
|
"exit_code",
|
|
3565
3690
|
"stop_reason",
|
|
3566
3691
|
"last_progress",
|
|
3567
3692
|
"log_path",
|
|
3693
|
+
"watchdog_after_seconds",
|
|
3568
3694
|
):
|
|
3569
3695
|
if key in result_payload:
|
|
3570
3696
|
metadata[key] = result_payload.get(key)
|
|
@@ -153,8 +153,69 @@ 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()
|
|
218
|
+
if selection_type == "branch_node" and self.stage_key not in {"experiment", "analysis", "paper"}:
|
|
158
219
|
return self._build_branch()
|
|
159
220
|
if self.stage_key == "baseline":
|
|
160
221
|
return self._build_baseline()
|
|
@@ -332,6 +393,52 @@ class QuestStageViewBuilder:
|
|
|
332
393
|
text = str(body or "").strip()
|
|
333
394
|
return text or None
|
|
334
395
|
|
|
396
|
+
def _recent_trace_actions(self, *, limit: int = 6) -> list[dict[str, Any]]:
|
|
397
|
+
raw_actions = self.trace.get("actions") if isinstance(self.trace, dict) else []
|
|
398
|
+
if not isinstance(raw_actions, list) or not raw_actions:
|
|
399
|
+
return []
|
|
400
|
+
normalized: list[dict[str, Any]] = []
|
|
401
|
+
for item in raw_actions[-limit:]:
|
|
402
|
+
if not isinstance(item, dict):
|
|
403
|
+
continue
|
|
404
|
+
summary = _compact(item.get("summary") or item.get("output") or item.get("args"), limit=1000)
|
|
405
|
+
normalized.append(
|
|
406
|
+
{
|
|
407
|
+
"action_id": item.get("action_id"),
|
|
408
|
+
"title": item.get("title") or item.get("tool_name") or item.get("raw_event_type") or item.get("kind"),
|
|
409
|
+
"summary": summary,
|
|
410
|
+
"status": item.get("status"),
|
|
411
|
+
"created_at": item.get("created_at"),
|
|
412
|
+
"tool_name": item.get("tool_name"),
|
|
413
|
+
"artifact_kind": item.get("artifact_kind"),
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
return normalized
|
|
417
|
+
|
|
418
|
+
def _trace_summary(self) -> str | None:
|
|
419
|
+
if not isinstance(self.trace, dict):
|
|
420
|
+
return None
|
|
421
|
+
return _compact(
|
|
422
|
+
self.trace.get("summary")
|
|
423
|
+
or self.selection.get("summary")
|
|
424
|
+
or self.trace.get("title"),
|
|
425
|
+
limit=600,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def _trace_markdown(self, *, limit: int = 5) -> str | None:
|
|
429
|
+
items = self._recent_trace_actions(limit=limit)
|
|
430
|
+
if not items:
|
|
431
|
+
return None
|
|
432
|
+
lines: list[str] = []
|
|
433
|
+
for item in items:
|
|
434
|
+
title = str(item.get("title") or "Trace").strip() or "Trace"
|
|
435
|
+
summary = str(item.get("summary") or "").strip()
|
|
436
|
+
if summary:
|
|
437
|
+
lines.append(f"- **{title}**: {summary}")
|
|
438
|
+
else:
|
|
439
|
+
lines.append(f"- **{title}**")
|
|
440
|
+
return "\n".join(lines) if lines else None
|
|
441
|
+
|
|
335
442
|
def _file_entry(
|
|
336
443
|
self,
|
|
337
444
|
raw_path: object,
|
|
@@ -728,6 +835,8 @@ class QuestStageViewBuilder:
|
|
|
728
835
|
or self.snapshot.get("active_idea_draft_path")
|
|
729
836
|
or str(Path(worktree_root) / "memory" / "ideas" / idea_id / "draft.md")
|
|
730
837
|
)
|
|
838
|
+
idea_markdown = self._markdown_body_for_path(idea_md_path)
|
|
839
|
+
idea_md_rel_path = self._relative_path_or_raw(idea_md_path)
|
|
731
840
|
draft_md_rel_path = self._relative_path_or_raw(draft_md_path)
|
|
732
841
|
draft_markdown = self._markdown_body_for_path(draft_md_path)
|
|
733
842
|
lineage_intent = str(payload.get("lineage_intent") or details.get("lineage_intent") or "").strip() or None
|
|
@@ -800,10 +909,14 @@ class QuestStageViewBuilder:
|
|
|
800
909
|
"risks": details.get("risks") or [],
|
|
801
910
|
"evidence_paths": details.get("evidence_paths") or [],
|
|
802
911
|
"lineage_intent": lineage_intent,
|
|
912
|
+
"idea_path": idea_md_rel_path,
|
|
913
|
+
"idea_markdown": idea_markdown,
|
|
803
914
|
"draft_path": draft_md_rel_path,
|
|
804
915
|
"draft_markdown": draft_markdown,
|
|
805
916
|
"literature_files": literature_files,
|
|
806
|
-
|
|
917
|
+
"decision_reason": payload.get("reason"),
|
|
918
|
+
},
|
|
919
|
+
"latest_artifact": self._artifact_detail(latest, payload),
|
|
807
920
|
},
|
|
808
921
|
lineage_intent=lineage_intent,
|
|
809
922
|
idea_draft_path=draft_md_rel_path,
|
|
@@ -869,6 +982,7 @@ class QuestStageViewBuilder:
|
|
|
869
982
|
if isinstance(latest_experiment_payload.get("paths"), dict)
|
|
870
983
|
else {}
|
|
871
984
|
)
|
|
985
|
+
latest_run_markdown = self._markdown_body_for_path(latest_experiment_paths.get("run_md"))
|
|
872
986
|
latest_result_payload = (
|
|
873
987
|
read_json(Path(str(latest_experiment_paths.get("result_json"))), {})
|
|
874
988
|
if str(latest_experiment_paths.get("result_json") or "").strip()
|
|
@@ -904,6 +1018,7 @@ class QuestStageViewBuilder:
|
|
|
904
1018
|
if str(analysis_manifest.get("campaign_id") or "").strip()
|
|
905
1019
|
else None
|
|
906
1020
|
)
|
|
1021
|
+
analysis_summary_markdown = self._markdown_body_for_path(analysis_summary_path)
|
|
907
1022
|
|
|
908
1023
|
branch_items = [
|
|
909
1024
|
item
|
|
@@ -1008,6 +1123,11 @@ class QuestStageViewBuilder:
|
|
|
1008
1123
|
"next_target": next_target,
|
|
1009
1124
|
"idea_draft_path": idea_draft_rel_path,
|
|
1010
1125
|
"idea_draft_markdown": idea_draft_markdown,
|
|
1126
|
+
"idea_hypothesis": latest_idea_details.get("hypothesis"),
|
|
1127
|
+
"idea_mechanism": latest_idea_details.get("mechanism"),
|
|
1128
|
+
"idea_expected_gain": latest_idea_details.get("expected_gain"),
|
|
1129
|
+
"idea_risks": latest_idea_details.get("risks") or [],
|
|
1130
|
+
"decision_reason": latest_idea_payload.get("reason"),
|
|
1011
1131
|
"latest_main_experiment": {
|
|
1012
1132
|
"run_id": latest_run_id,
|
|
1013
1133
|
"summary": latest_experiment_payload.get("summary"),
|
|
@@ -1017,11 +1137,16 @@ class QuestStageViewBuilder:
|
|
|
1017
1137
|
"progress_eval": latest_progress_eval,
|
|
1018
1138
|
"evaluation_summary": latest_evaluation_summary,
|
|
1019
1139
|
"run_md_path": latest_experiment_paths.get("run_md"),
|
|
1140
|
+
"run_markdown": latest_run_markdown,
|
|
1020
1141
|
"result_json_path": latest_experiment_paths.get("result_json"),
|
|
1142
|
+
"result_payload": latest_result_payload,
|
|
1021
1143
|
}
|
|
1022
1144
|
if latest_run_id
|
|
1023
1145
|
else None,
|
|
1024
|
-
|
|
1146
|
+
"analysis_summary_path": self._relative_path_or_raw(analysis_summary_path),
|
|
1147
|
+
"analysis_summary_markdown": analysis_summary_markdown,
|
|
1148
|
+
},
|
|
1149
|
+
"latest_artifact": self._artifact_detail(latest_experiment_item or latest_idea_item, latest_experiment_payload or latest_idea_payload),
|
|
1025
1150
|
},
|
|
1026
1151
|
lineage_intent=lineage_intent,
|
|
1027
1152
|
idea_draft_path=idea_draft_rel_path,
|
|
@@ -1057,8 +1182,17 @@ class QuestStageViewBuilder:
|
|
|
1057
1182
|
baseline_ref = payload.get("baseline_ref") or result_payload.get("baseline_ref") or {}
|
|
1058
1183
|
evaluation_summary = _evaluation_summary(payload.get("evaluation_summary") or result_payload.get("evaluation_summary"))
|
|
1059
1184
|
run_id = str(payload.get("run_id") or "pending").strip() or "pending"
|
|
1185
|
+
run_markdown = self._markdown_body_for_path(paths.get("run_md"))
|
|
1186
|
+
trace_summary = self._trace_summary()
|
|
1187
|
+
trace_markdown = self._trace_markdown()
|
|
1060
1188
|
note = (
|
|
1061
|
-
str(
|
|
1189
|
+
str(
|
|
1190
|
+
payload.get("summary")
|
|
1191
|
+
or result_payload.get("conclusion")
|
|
1192
|
+
or (progress_eval or {}).get("reason")
|
|
1193
|
+
or trace_summary
|
|
1194
|
+
or ""
|
|
1195
|
+
).strip()
|
|
1062
1196
|
or "No durable main experiment result has been recorded yet."
|
|
1063
1197
|
)
|
|
1064
1198
|
title = f"Experiment · {run_id}"
|
|
@@ -1119,8 +1253,15 @@ class QuestStageViewBuilder:
|
|
|
1119
1253
|
"metrics_summary": metrics_summary,
|
|
1120
1254
|
"progress_eval": progress_eval,
|
|
1121
1255
|
"evaluation_summary": evaluation_summary,
|
|
1256
|
+
"run_path": self._relative_path_or_raw(paths.get("run_md")),
|
|
1257
|
+
"run_markdown": run_markdown,
|
|
1258
|
+
"result_json_path": self._relative_path_or_raw(paths.get("result_json")),
|
|
1122
1259
|
"result_payload": result_payload,
|
|
1123
|
-
|
|
1260
|
+
"trace_summary": trace_summary,
|
|
1261
|
+
"trace_markdown": trace_markdown,
|
|
1262
|
+
"trace_actions": self._recent_trace_actions(),
|
|
1263
|
+
},
|
|
1264
|
+
"latest_artifact": self._artifact_detail(latest, payload),
|
|
1124
1265
|
},
|
|
1125
1266
|
)
|
|
1126
1267
|
|
|
@@ -1145,7 +1286,9 @@ class QuestStageViewBuilder:
|
|
|
1145
1286
|
flow_type = str(payload.get("flow_type") or "").strip()
|
|
1146
1287
|
if flow_type not in {"analysis_campaign", "analysis_slice"}:
|
|
1147
1288
|
continue
|
|
1148
|
-
if campaign_id
|
|
1289
|
+
if campaign_id:
|
|
1290
|
+
if str(payload.get("campaign_id") or "").strip() == campaign_id:
|
|
1291
|
+
items.append(item)
|
|
1149
1292
|
continue
|
|
1150
1293
|
if self._branch_matches(payload, allow_parent=True, include_unscoped=False):
|
|
1151
1294
|
items.append(item)
|
|
@@ -1228,13 +1371,23 @@ class QuestStageViewBuilder:
|
|
|
1228
1371
|
"plan_path": item.get("plan_path"),
|
|
1229
1372
|
"result_path": item.get("result_path"),
|
|
1230
1373
|
"mirror_path": item.get("mirror_path"),
|
|
1374
|
+
"plan_markdown": self._markdown_body_for_path(item.get("plan_path")),
|
|
1375
|
+
"result_markdown": self._markdown_body_for_path(item.get("result_path")),
|
|
1376
|
+
"mirror_markdown": self._markdown_body_for_path(item.get("mirror_path")),
|
|
1231
1377
|
}
|
|
1232
1378
|
)
|
|
1233
1379
|
title = f"Analysis · {campaign_id}"
|
|
1380
|
+
trace_summary = self._trace_summary()
|
|
1234
1381
|
note = (
|
|
1235
|
-
str(latest_payload.get("summary") or manifest.get("goal") or "").strip()
|
|
1382
|
+
str(latest_payload.get("summary") or manifest.get("goal") or trace_summary or "").strip()
|
|
1236
1383
|
or "No durable analysis campaign has been created yet."
|
|
1237
1384
|
)
|
|
1385
|
+
summary_path = (
|
|
1386
|
+
self.quest_root / "experiments" / "analysis-results" / campaign_id / "SUMMARY.md"
|
|
1387
|
+
if campaign_id != "pending"
|
|
1388
|
+
else None
|
|
1389
|
+
)
|
|
1390
|
+
summary_markdown = self._markdown_body_for_path(summary_path) if summary_path else None
|
|
1238
1391
|
key_files = self._dedupe_files(
|
|
1239
1392
|
[
|
|
1240
1393
|
self._file_entry(manifest.get("_manifest_path"), label="Campaign Manifest", description="Structured analysis campaign manifest."),
|
|
@@ -1293,8 +1446,17 @@ class QuestStageViewBuilder:
|
|
|
1293
1446
|
"summary": latest_payload.get("summary"),
|
|
1294
1447
|
"manifest_path": manifest.get("_manifest_path"),
|
|
1295
1448
|
"charter_path": manifest.get("charter_path"),
|
|
1449
|
+
"charter_markdown": self._markdown_body_for_path(manifest.get("charter_path")),
|
|
1296
1450
|
"todo_manifest_path": manifest.get("todo_manifest_path"),
|
|
1297
|
-
|
|
1451
|
+
"todo_manifest_markdown": self._markdown_body_for_path(manifest.get("todo_manifest_path")),
|
|
1452
|
+
"summary_path": self._relative_path_or_raw(summary_path) if summary_path else None,
|
|
1453
|
+
"summary_markdown": summary_markdown,
|
|
1454
|
+
"manifest_payload": manifest,
|
|
1455
|
+
"trace_summary": trace_summary,
|
|
1456
|
+
"trace_markdown": self._trace_markdown(),
|
|
1457
|
+
"trace_actions": self._recent_trace_actions(),
|
|
1458
|
+
},
|
|
1459
|
+
"latest_artifact": self._artifact_detail(latest, latest_payload),
|
|
1298
1460
|
},
|
|
1299
1461
|
)
|
|
1300
1462
|
|
|
@@ -1435,6 +1597,7 @@ class QuestStageViewBuilder:
|
|
|
1435
1597
|
"latex_root_path": latex_root_rel,
|
|
1436
1598
|
"main_tex_path": main_tex_rel,
|
|
1437
1599
|
},
|
|
1438
|
-
}
|
|
1600
|
+
},
|
|
1601
|
+
"latest_artifact": self._artifact_detail(paper_items[-1] if paper_items else None, self._payload(paper_items[-1] if paper_items else {})),
|
|
1439
1602
|
},
|
|
1440
1603
|
)
|