@researai/deepscientist 1.5.9 → 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/README.md +107 -94
- 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 +168 -9
- 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 +2 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/annotations.py +343 -0
- package/src/deepscientist/artifact/arxiv.py +484 -37
- package/src/deepscientist/artifact/service.py +574 -108
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/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 +165 -5
- package/src/deepscientist/daemon/api/router.py +13 -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 +11 -4
- package/src/deepscientist/prompts/builder.py +15 -0
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +23 -0
- package/src/deepscientist/quest/service.py +112 -43
- package/src/deepscientist/quest/stage_views.py +71 -5
- 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 +2 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-D0mTXG4-.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-Db0cTXxm.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-DrV8je02.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-QXMSCH71.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-7hhtWj_E.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-BWMSnRJe.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-7J9h9Vy_.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-CHJl_0lr.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-1qSow1es.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-eQpPPCEp.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BwRfi89Z.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-836PVQWV.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-C2y_556i.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BRzJbGsn.js} +12 -12
- package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-DIX7Mlzu.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-DzRaTAlq.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DZUfIUnp.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-BwtICzue.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DHeIAMsx.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C3tCmFox.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.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-BWAY76JP.js → code-XfbSR8K2.js} +1 -1
- package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-BjxNaIfy.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-D_lLVQk0.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-D9x_5vlY.js} +1 -1
- package/src/ui/dist/assets/{image-D-NZM-6P.js → image-BhWT33W1.js} +1 -1
- package/src/ui/dist/assets/{index-DHZJ_0TI.js → index--c4iXtuy.js} +12 -12
- package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-BDxipwrC.js} +2 -2
- package/src/ui/dist/assets/{index-7Chr1g9c.js → index-DZTZ8mWP.js} +14221 -9523
- package/src/ui/dist/assets/{index-DGIYDuTv.css → index-Dqj-Mjb4.css} +2 -13
- package/src/ui/dist/assets/index-PJbSbPTy.js +25 -0
- package/src/ui/dist/assets/{monaco-Cb2uKKe6.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-Bg72DGgT.js → popover-yFK1J4fL.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-PENr2zcz.js} +1 -74
- package/src/ui/dist/assets/select-CAbJDfYv.js +1690 -0
- package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-DEuYJqTl.js} +1 -1
- package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-omoSUmcd.js} +2 -13
- package/src/ui/dist/assets/{trash-BvTgE5__.js → trash--F119N47.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-D31UR23I.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-BH6KcMzq.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-CZ613PM5.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.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-C_wWw4AP.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-B0Dd8CxK.js +0 -158
- package/src/ui/dist/assets/bibtex-CKaefIN2.js +0 -189
- package/src/ui/dist/assets/file-utils-H2fjA46S.js +0 -109
- package/src/ui/dist/assets/message-square-BzjLiXir.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-C_mA6R0w.js +0 -108
|
@@ -5,6 +5,7 @@ import shutil
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from ..arxiv_library import ArxivLibraryService
|
|
8
9
|
from ..bridges import register_builtin_connector_bridges
|
|
9
10
|
from ..channels import get_channel_factory, register_builtin_channels
|
|
10
11
|
from ..config import ConfigManager
|
|
@@ -38,7 +39,7 @@ from ..shared import (
|
|
|
38
39
|
)
|
|
39
40
|
from ..quest import QuestService
|
|
40
41
|
from ..memory.frontmatter import dump_markdown_document, load_markdown_document
|
|
41
|
-
from .arxiv import read_arxiv_content
|
|
42
|
+
from .arxiv import fetch_arxiv_metadata, read_arxiv_content
|
|
42
43
|
from .guidance import build_guidance_for_record, guidance_summary
|
|
43
44
|
from .metrics import (
|
|
44
45
|
baseline_metric_lines,
|
|
@@ -96,16 +97,77 @@ class ArtifactService:
|
|
|
96
97
|
self.home = home
|
|
97
98
|
self.baselines = BaselineRegistry(home)
|
|
98
99
|
self.quest_service = QuestService(home)
|
|
100
|
+
self.arxiv_library = ArxivLibraryService()
|
|
99
101
|
|
|
100
102
|
@staticmethod
|
|
101
|
-
def _notification_text(value: object
|
|
102
|
-
text = str(value or "")
|
|
103
|
-
if not text:
|
|
103
|
+
def _notification_text(value: object) -> str | None:
|
|
104
|
+
text = str(value or "")
|
|
105
|
+
if not text.strip():
|
|
104
106
|
return None
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
normalized_lines: list[str] = []
|
|
108
|
+
for raw_line in text.replace("\r\n", "\n").replace("\r", "\n").split("\n"):
|
|
109
|
+
cleaned = re.sub(r"[ \t]+", " ", raw_line).strip()
|
|
110
|
+
if not cleaned:
|
|
111
|
+
if normalized_lines and normalized_lines[-1] != "":
|
|
112
|
+
normalized_lines.append("")
|
|
113
|
+
continue
|
|
114
|
+
normalized_lines.append(cleaned)
|
|
115
|
+
while normalized_lines and normalized_lines[-1] == "":
|
|
116
|
+
normalized_lines.pop()
|
|
117
|
+
rendered = "\n".join(normalized_lines).strip()
|
|
118
|
+
return rendered or None
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def _notification_block(cls, value: object) -> str | None:
|
|
122
|
+
if value is None:
|
|
123
|
+
return None
|
|
124
|
+
if isinstance(value, dict):
|
|
125
|
+
lines: list[str] = []
|
|
126
|
+
for key, item in value.items():
|
|
127
|
+
label = cls._format_route_label(key) or str(key).strip()
|
|
128
|
+
block = cls._notification_block(item)
|
|
129
|
+
if not label or not block:
|
|
130
|
+
continue
|
|
131
|
+
block_lines = block.splitlines()
|
|
132
|
+
if len(block_lines) == 1:
|
|
133
|
+
lines.append(f"- {label}: {block_lines[0]}")
|
|
134
|
+
continue
|
|
135
|
+
lines.append(f"- {label}:")
|
|
136
|
+
lines.extend(f" {line}" if line else "" for line in block_lines)
|
|
137
|
+
return "\n".join(lines).strip() or None
|
|
138
|
+
if isinstance(value, (list, tuple, set)):
|
|
139
|
+
lines = []
|
|
140
|
+
for item in value:
|
|
141
|
+
block = cls._notification_block(item)
|
|
142
|
+
if not block:
|
|
143
|
+
continue
|
|
144
|
+
block_lines = block.splitlines()
|
|
145
|
+
if not block_lines:
|
|
146
|
+
continue
|
|
147
|
+
lines.append(f"- {block_lines[0]}")
|
|
148
|
+
lines.extend(f" {line}" if line else "" for line in block_lines[1:])
|
|
149
|
+
return "\n".join(lines).strip() or None
|
|
150
|
+
return cls._notification_text(value)
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def _append_notification_section(cls, lines: list[str], label: str, value: object) -> None:
|
|
154
|
+
block = cls._notification_block(value)
|
|
155
|
+
if not block:
|
|
156
|
+
return
|
|
157
|
+
lines.extend(["", f"{label}:", block])
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _append_notification_file_section(lines: list[str], entries: list[tuple[str, str | None]]) -> None:
|
|
161
|
+
normalized = [
|
|
162
|
+
(label, str(path).strip())
|
|
163
|
+
for label, path in entries
|
|
164
|
+
if str(path or "").strip()
|
|
165
|
+
]
|
|
166
|
+
if not normalized:
|
|
167
|
+
return
|
|
168
|
+
lines.extend(["", "Files:"])
|
|
169
|
+
for label, path in normalized:
|
|
170
|
+
lines.append(f"- {label}: `{path}`")
|
|
109
171
|
|
|
110
172
|
def _normalize_evaluation_summary(self, payload: dict[str, Any] | None) -> dict[str, str] | None:
|
|
111
173
|
if not isinstance(payload, dict):
|
|
@@ -180,22 +242,21 @@ class ArtifactService:
|
|
|
180
242
|
) -> str:
|
|
181
243
|
lead = "is now active" if action == "create" else "was revised"
|
|
182
244
|
lines = [f"Idea `{idea_id}` {lead} on branch `{branch_name}`."]
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if self._notification_text(hypothesis):
|
|
188
|
-
lines.append(f"Hypothesis: {self._notification_text(hypothesis)}")
|
|
189
|
-
if self._notification_text(mechanism):
|
|
190
|
-
lines.append(f"Mechanism: {self._notification_text(mechanism)}")
|
|
245
|
+
self._append_notification_section(lines, "Title", title)
|
|
246
|
+
self._append_notification_section(lines, "Problem", problem)
|
|
247
|
+
self._append_notification_section(lines, "Hypothesis", hypothesis)
|
|
248
|
+
self._append_notification_section(lines, "Mechanism", mechanism)
|
|
191
249
|
if foundation_label:
|
|
192
|
-
|
|
250
|
+
self._append_notification_section(lines, "Foundation", foundation_label)
|
|
193
251
|
if next_target:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
lines
|
|
197
|
-
|
|
198
|
-
|
|
252
|
+
self._append_notification_section(lines, "Next route", self._format_route_label(next_target) or next_target)
|
|
253
|
+
self._append_notification_file_section(
|
|
254
|
+
lines,
|
|
255
|
+
[
|
|
256
|
+
("Idea doc", idea_md_rel_path),
|
|
257
|
+
("Draft", draft_md_rel_path),
|
|
258
|
+
],
|
|
259
|
+
)
|
|
199
260
|
return "\n".join(lines)
|
|
200
261
|
|
|
201
262
|
def _build_main_experiment_interaction_message(
|
|
@@ -217,6 +278,7 @@ class ArtifactService:
|
|
|
217
278
|
result_json_rel_path: str | None,
|
|
218
279
|
) -> str:
|
|
219
280
|
lines = [f"Main experiment `{run_id}` finished on branch `{branch_name}`."]
|
|
281
|
+
outcome_lines: list[str] = []
|
|
220
282
|
if primary_metric_id and primary_value is not None:
|
|
221
283
|
metric_text = f"{primary_metric_id}={self._format_metric_value(primary_value, decimals)}"
|
|
222
284
|
if primary_baseline is not None and primary_delta is not None:
|
|
@@ -224,27 +286,31 @@ class ArtifactService:
|
|
|
224
286
|
f", baseline={self._format_metric_value(primary_baseline, decimals)}, "
|
|
225
287
|
f"delta={self._format_metric_value(primary_delta, decimals)}"
|
|
226
288
|
)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
takeaway = (
|
|
230
|
-
self._notification_text((evaluation_summary or {}).get("takeaway"))
|
|
231
|
-
or self._notification_text(conclusion)
|
|
232
|
-
)
|
|
233
|
-
if takeaway:
|
|
234
|
-
lines.append(f"Takeaway: {takeaway}")
|
|
235
|
-
claim_update = self._notification_text((evaluation_summary or {}).get("claim_update"))
|
|
236
|
-
if claim_update:
|
|
237
|
-
lines.append(f"Claim update: {claim_update}")
|
|
289
|
+
outcome_lines.append(f"- Metric: {metric_text}")
|
|
290
|
+
outcome_lines.append(f"- Verdict: {self._format_route_label(verdict) or verdict}")
|
|
238
291
|
if self._notification_text(breakthrough_level):
|
|
239
|
-
|
|
292
|
+
outcome_lines.append(f"- Breakthrough level: {self._notification_text(breakthrough_level)}")
|
|
240
293
|
if recommended_next_route:
|
|
241
|
-
|
|
242
|
-
f"Recommended next route: {self._format_route_label(recommended_next_route) or recommended_next_route}"
|
|
294
|
+
outcome_lines.append(
|
|
295
|
+
f"- Recommended next route: {self._format_route_label(recommended_next_route) or recommended_next_route}"
|
|
243
296
|
)
|
|
244
|
-
if
|
|
245
|
-
lines.
|
|
246
|
-
|
|
247
|
-
lines
|
|
297
|
+
if outcome_lines:
|
|
298
|
+
lines.extend(["", "Outcome:", *outcome_lines])
|
|
299
|
+
self._append_notification_section(
|
|
300
|
+
lines,
|
|
301
|
+
"Conclusion",
|
|
302
|
+
self._notification_text(conclusion) or (evaluation_summary or {}).get("takeaway"),
|
|
303
|
+
)
|
|
304
|
+
normalized_evaluation_summary = self._normalize_evaluation_summary(evaluation_summary)
|
|
305
|
+
if normalized_evaluation_summary:
|
|
306
|
+
lines.extend(["", "Evaluation summary:", *self._evaluation_summary_markdown_lines(normalized_evaluation_summary)])
|
|
307
|
+
self._append_notification_file_section(
|
|
308
|
+
lines,
|
|
309
|
+
[
|
|
310
|
+
("Run log", run_md_rel_path),
|
|
311
|
+
("Result", result_json_rel_path),
|
|
312
|
+
],
|
|
313
|
+
)
|
|
248
314
|
return "\n".join(lines)
|
|
249
315
|
|
|
250
316
|
def _build_outline_interaction_message(
|
|
@@ -263,23 +329,24 @@ class ArtifactService:
|
|
|
263
329
|
) -> str:
|
|
264
330
|
verb = "selected" if action == "select" else "revised"
|
|
265
331
|
lines = [f"Paper outline `{outline_id}` was {verb} and promoted into the writing stage."]
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
332
|
+
self._append_notification_section(lines, "Title", title)
|
|
333
|
+
self._append_notification_section(lines, "Reason", selected_reason)
|
|
334
|
+
self._append_notification_section(lines, "Story", story)
|
|
335
|
+
self._append_notification_section(lines, "Research questions", research_questions)
|
|
336
|
+
self._append_notification_section(lines, "Experimental designs", experimental_designs)
|
|
337
|
+
self._append_notification_section(
|
|
338
|
+
lines,
|
|
339
|
+
"Next route",
|
|
340
|
+
"Continue writing on the paper branch, or launch outline-bound analysis if evidence is still missing.",
|
|
341
|
+
)
|
|
342
|
+
self._append_notification_file_section(
|
|
343
|
+
lines,
|
|
344
|
+
[
|
|
345
|
+
("Selected outline", selected_outline_rel_path),
|
|
346
|
+
("Selection note", outline_selection_rel_path),
|
|
347
|
+
("Revision record", revised_outline_rel_path),
|
|
348
|
+
],
|
|
349
|
+
)
|
|
283
350
|
return "\n".join(lines)
|
|
284
351
|
|
|
285
352
|
def _build_analysis_campaign_interaction_message(
|
|
@@ -293,20 +360,20 @@ class ArtifactService:
|
|
|
293
360
|
todo_manifest_rel_path: str | None,
|
|
294
361
|
) -> str:
|
|
295
362
|
lines = [f"Analysis campaign `{campaign_id}` is ready from parent branch `{parent_branch}`."]
|
|
296
|
-
|
|
297
|
-
lines.append(f"Goal: {self._notification_text(goal)}")
|
|
363
|
+
self._append_notification_section(lines, "Goal", goal)
|
|
298
364
|
if selected_outline_ref:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
f"
|
|
302
|
-
|
|
365
|
+
self._append_notification_section(lines, "Selected outline", f"`{selected_outline_ref}`")
|
|
366
|
+
next_slice_lines = [
|
|
367
|
+
f"- Slice: `{first_slice.get('slice_id')}`",
|
|
368
|
+
f"- Branch: `{first_slice.get('branch')}`",
|
|
369
|
+
]
|
|
303
370
|
if self._notification_text(first_slice.get("title")):
|
|
304
|
-
|
|
371
|
+
next_slice_lines.append(f"- Focus: {self._notification_text(first_slice.get('title'))}")
|
|
372
|
+
lines.extend(["", "Next slice:", *next_slice_lines])
|
|
305
373
|
requirement = self._notification_text(first_slice.get("must_not_simplify") or first_slice.get("goal"))
|
|
306
374
|
if requirement:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
lines.append(f"Todo manifest: `{todo_manifest_rel_path}`")
|
|
375
|
+
self._append_notification_section(lines, "Core requirement", requirement)
|
|
376
|
+
self._append_notification_file_section(lines, [("Todo manifest", todo_manifest_rel_path)])
|
|
310
377
|
return "\n".join(lines)
|
|
311
378
|
|
|
312
379
|
def _build_analysis_slice_interaction_message(
|
|
@@ -320,19 +387,22 @@ class ArtifactService:
|
|
|
320
387
|
mirror_rel_path: str | None,
|
|
321
388
|
) -> str:
|
|
322
389
|
lines = [f"Analysis slice `{slice_id}` from campaign `{campaign_id}` is complete."]
|
|
323
|
-
|
|
324
|
-
if
|
|
325
|
-
lines.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
390
|
+
normalized_evaluation_summary = self._normalize_evaluation_summary(evaluation_summary)
|
|
391
|
+
if normalized_evaluation_summary:
|
|
392
|
+
lines.extend(["", "Evaluation summary:", *self._evaluation_summary_markdown_lines(normalized_evaluation_summary)])
|
|
393
|
+
self._append_notification_section(lines, "Claim impact", claim_impact)
|
|
394
|
+
lines.extend(
|
|
395
|
+
[
|
|
396
|
+
"",
|
|
397
|
+
"Next slice:",
|
|
398
|
+
f"- Slice: `{next_slice.get('slice_id')}`",
|
|
399
|
+
f"- Branch: `{next_slice.get('branch')}`",
|
|
400
|
+
]
|
|
330
401
|
)
|
|
331
402
|
requirement = self._notification_text(next_slice.get("must_not_simplify") or next_slice.get("goal"))
|
|
332
403
|
if requirement:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
lines.append(f"Parent mirror: `{mirror_rel_path}`")
|
|
404
|
+
self._append_notification_section(lines, "Core requirement", requirement)
|
|
405
|
+
self._append_notification_file_section(lines, [("Parent mirror", mirror_rel_path)])
|
|
336
406
|
return "\n".join(lines)
|
|
337
407
|
|
|
338
408
|
def _build_analysis_complete_interaction_message(
|
|
@@ -345,33 +415,89 @@ class ArtifactService:
|
|
|
345
415
|
writing_worktree_rel_path: str | None,
|
|
346
416
|
) -> str:
|
|
347
417
|
lines = [f"Analysis campaign `{campaign_id}` is complete."]
|
|
348
|
-
|
|
349
|
-
strongest_takeaway = next(
|
|
350
|
-
(
|
|
351
|
-
self._notification_text(
|
|
352
|
-
((item.get("evaluation_summary") or {}) if isinstance(item.get("evaluation_summary"), dict) else {}).get(
|
|
353
|
-
"takeaway"
|
|
354
|
-
)
|
|
355
|
-
)
|
|
356
|
-
for item in completed_slices
|
|
357
|
-
if self._notification_text(
|
|
358
|
-
((item.get("evaluation_summary") or {}) if isinstance(item.get("evaluation_summary"), dict) else {}).get(
|
|
359
|
-
"takeaway"
|
|
360
|
-
)
|
|
361
|
-
)
|
|
362
|
-
),
|
|
363
|
-
None,
|
|
364
|
-
)
|
|
365
|
-
if strongest_takeaway:
|
|
366
|
-
lines.append(f"Main takeaway: {strongest_takeaway}")
|
|
367
|
-
if summary_rel_path:
|
|
368
|
-
lines.append(f"Summary: `{summary_rel_path}`")
|
|
418
|
+
overview_lines = [f"- Completed slices: {len(completed_slices)}"]
|
|
369
419
|
if writing_branch:
|
|
370
|
-
|
|
420
|
+
overview_lines.append(f"- Next route: writing is active on branch `{writing_branch}`")
|
|
371
421
|
if writing_worktree_rel_path:
|
|
372
|
-
|
|
422
|
+
overview_lines.append(f"- Writing workspace: `{writing_worktree_rel_path}`")
|
|
373
423
|
else:
|
|
374
|
-
|
|
424
|
+
overview_lines.append("- Next route: make the next durable decision from the merged analysis evidence.")
|
|
425
|
+
lines.extend(["", "Overview:", *overview_lines])
|
|
426
|
+
completed_slice_lines: list[str] = []
|
|
427
|
+
for item in completed_slices:
|
|
428
|
+
slice_id = str(item.get("slice_id") or "").strip() or "unknown"
|
|
429
|
+
title = self._notification_text(item.get("title"))
|
|
430
|
+
lead = f"- `{slice_id}`"
|
|
431
|
+
if title:
|
|
432
|
+
lead += f": {title}"
|
|
433
|
+
completed_slice_lines.append(lead)
|
|
434
|
+
takeaway = self._notification_text(
|
|
435
|
+
((item.get("evaluation_summary") or {}) if isinstance(item.get("evaluation_summary"), dict) else {}).get(
|
|
436
|
+
"takeaway"
|
|
437
|
+
)
|
|
438
|
+
)
|
|
439
|
+
if takeaway:
|
|
440
|
+
completed_slice_lines.append(f" Takeaway: {takeaway}")
|
|
441
|
+
claim_impact = self._notification_text(item.get("claim_impact"))
|
|
442
|
+
if claim_impact:
|
|
443
|
+
completed_slice_lines.append(f" Claim impact: {claim_impact}")
|
|
444
|
+
if completed_slice_lines:
|
|
445
|
+
lines.extend(["", "Completed slices:", *completed_slice_lines])
|
|
446
|
+
self._append_notification_file_section(lines, [("Summary", summary_rel_path)])
|
|
447
|
+
return "\n".join(lines)
|
|
448
|
+
|
|
449
|
+
def _build_paper_bundle_interaction_message(
|
|
450
|
+
self,
|
|
451
|
+
*,
|
|
452
|
+
title: str | None,
|
|
453
|
+
summary: str | None,
|
|
454
|
+
paper_branch: str | None,
|
|
455
|
+
source_branch: str | None,
|
|
456
|
+
source_run_id: str | None,
|
|
457
|
+
selected_outline_ref: str | None,
|
|
458
|
+
manifest_rel_path: str | None,
|
|
459
|
+
draft_rel_path: str | None,
|
|
460
|
+
writing_plan_rel_path: str | None,
|
|
461
|
+
references_rel_path: str | None,
|
|
462
|
+
claim_evidence_map_rel_path: str | None,
|
|
463
|
+
compile_report_rel_path: str | None,
|
|
464
|
+
pdf_rel_path: str | None,
|
|
465
|
+
latex_root_rel_path: str | None,
|
|
466
|
+
baseline_inventory_rel_path: str | None,
|
|
467
|
+
open_source_manifest_rel_path: str | None,
|
|
468
|
+
) -> str:
|
|
469
|
+
bundle_label = self._notification_text(title) or "paper"
|
|
470
|
+
lines = [f"Paper bundle `{bundle_label}` is ready on branch `{paper_branch or 'paper'}`."]
|
|
471
|
+
overview_lines: list[str] = []
|
|
472
|
+
if source_branch:
|
|
473
|
+
overview_lines.append(f"- Source branch: `{source_branch}`")
|
|
474
|
+
if source_run_id:
|
|
475
|
+
overview_lines.append(f"- Source run: `{source_run_id}`")
|
|
476
|
+
if selected_outline_ref:
|
|
477
|
+
overview_lines.append(f"- Selected outline: `{selected_outline_ref}`")
|
|
478
|
+
if overview_lines:
|
|
479
|
+
lines.extend(["", "Overview:", *overview_lines])
|
|
480
|
+
self._append_notification_section(lines, "Summary", summary)
|
|
481
|
+
self._append_notification_file_section(
|
|
482
|
+
lines,
|
|
483
|
+
[
|
|
484
|
+
("Bundle manifest", manifest_rel_path),
|
|
485
|
+
("Draft", draft_rel_path),
|
|
486
|
+
("Writing plan", writing_plan_rel_path),
|
|
487
|
+
("References", references_rel_path),
|
|
488
|
+
("Claim-evidence map", claim_evidence_map_rel_path),
|
|
489
|
+
("Compile report", compile_report_rel_path),
|
|
490
|
+
("PDF", pdf_rel_path),
|
|
491
|
+
("LaTeX root", latex_root_rel_path),
|
|
492
|
+
("Baseline inventory", baseline_inventory_rel_path),
|
|
493
|
+
("Open-source manifest", open_source_manifest_rel_path),
|
|
494
|
+
],
|
|
495
|
+
)
|
|
496
|
+
self._append_notification_section(
|
|
497
|
+
lines,
|
|
498
|
+
"Next route",
|
|
499
|
+
"Finalize the paper package, review the bundle artifacts, and publish or close the quest when ready.",
|
|
500
|
+
)
|
|
375
501
|
return "\n".join(lines)
|
|
376
502
|
|
|
377
503
|
def _load_metric_contract_payload(self, quest_root: Path, metric_contract_json_rel_path: str | None) -> dict[str, Any] | None:
|
|
@@ -2893,8 +3019,296 @@ class ArtifactService:
|
|
|
2893
3019
|
deduped.append(path)
|
|
2894
3020
|
return deduped
|
|
2895
3021
|
|
|
2896
|
-
|
|
2897
|
-
|
|
3022
|
+
@staticmethod
|
|
3023
|
+
def _arxiv_content_from_item(item: dict[str, Any]) -> str:
|
|
3024
|
+
title = str(item.get("title") or item.get("display_name") or item.get("arxiv_id") or "arXiv paper").strip()
|
|
3025
|
+
authors = [str(author).strip() for author in (item.get("authors") or []) if str(author).strip()]
|
|
3026
|
+
categories = [str(category).strip() for category in (item.get("categories") or []) if str(category).strip()]
|
|
3027
|
+
abstract = str(item.get("abstract") or "").strip() or "Abstract unavailable."
|
|
3028
|
+
overview = str(item.get("overview") or "").strip()
|
|
3029
|
+
lines = [f"# {title}", "", f"- paper_id: {str(item.get('arxiv_id') or '').strip()}"]
|
|
3030
|
+
if item.get("metadata_source"):
|
|
3031
|
+
lines.append(f"- metadata_source: {item['metadata_source']}")
|
|
3032
|
+
if item.get("summary_source"):
|
|
3033
|
+
lines.append(f"- summary_source: {item['summary_source']}")
|
|
3034
|
+
if authors:
|
|
3035
|
+
lines.append(f"- authors: {', '.join(authors)}")
|
|
3036
|
+
if categories:
|
|
3037
|
+
lines.append(f"- categories: {', '.join(categories)}")
|
|
3038
|
+
if item.get("published_at"):
|
|
3039
|
+
lines.append(f"- published_at: {item['published_at']}")
|
|
3040
|
+
if item.get("version") is not None:
|
|
3041
|
+
lines.append(f"- version: v{item['version']}")
|
|
3042
|
+
if overview:
|
|
3043
|
+
lines.extend(["", "## Summary", "", overview])
|
|
3044
|
+
if abstract and abstract != overview:
|
|
3045
|
+
lines.extend(["", "## Abstract", "", abstract])
|
|
3046
|
+
else:
|
|
3047
|
+
lines.extend(["", "## Abstract", "", abstract])
|
|
3048
|
+
return "\n".join(lines).strip()
|
|
3049
|
+
|
|
3050
|
+
@staticmethod
|
|
3051
|
+
def _arxiv_item_needs_refresh(item: dict[str, Any] | None) -> bool:
|
|
3052
|
+
if not isinstance(item, dict):
|
|
3053
|
+
return False
|
|
3054
|
+
title = str(item.get("title") or "").strip()
|
|
3055
|
+
lowered_title = title.lower()
|
|
3056
|
+
authors = item.get("authors") or []
|
|
3057
|
+
categories = item.get("categories") or []
|
|
3058
|
+
published_at = str(item.get("published_at") or "").strip()
|
|
3059
|
+
metadata_source = str(item.get("metadata_source") or "").strip()
|
|
3060
|
+
bibtex = str(item.get("bibtex") or "").strip()
|
|
3061
|
+
overview = str(item.get("overview") or "").strip()
|
|
3062
|
+
overview_markdown = str(item.get("overview_markdown") or "").strip()
|
|
3063
|
+
overview_source = str(item.get("overview_source") or "").strip()
|
|
3064
|
+
return (
|
|
3065
|
+
not title
|
|
3066
|
+
or title.startswith("#")
|
|
3067
|
+
or lowered_title.startswith("research paper analysis")
|
|
3068
|
+
or lowered_title.startswith("## research paper analysis")
|
|
3069
|
+
or not authors
|
|
3070
|
+
or not categories
|
|
3071
|
+
or not published_at
|
|
3072
|
+
or not metadata_source
|
|
3073
|
+
or not bibtex
|
|
3074
|
+
or (not overview and not overview_markdown and not overview_source)
|
|
3075
|
+
)
|
|
3076
|
+
|
|
3077
|
+
def _refresh_arxiv_item_metadata(self, quest_root: Path, item: dict[str, Any]) -> dict[str, Any]:
|
|
3078
|
+
arxiv_id = str(item.get("arxiv_id") or "").strip()
|
|
3079
|
+
if not arxiv_id:
|
|
3080
|
+
return item
|
|
3081
|
+
metadata = fetch_arxiv_metadata(arxiv_id)
|
|
3082
|
+
if not metadata.get("ok"):
|
|
3083
|
+
return item
|
|
3084
|
+
summary = read_arxiv_content(arxiv_id, full_text=False)
|
|
3085
|
+
summary_source = summary.get("summary_source") if summary.get("ok") else None
|
|
3086
|
+
overview_source = summary.get("overview_source") if summary.get("ok") else None
|
|
3087
|
+
return self.arxiv_library.upsert_item(
|
|
3088
|
+
quest_root,
|
|
3089
|
+
{
|
|
3090
|
+
**item,
|
|
3091
|
+
"arxiv_id": metadata.get("paper_id") or arxiv_id,
|
|
3092
|
+
"title": metadata.get("title") or item.get("title") or arxiv_id,
|
|
3093
|
+
"display_name": metadata.get("title") or item.get("display_name") or arxiv_id,
|
|
3094
|
+
"authors": metadata.get("authors") or item.get("authors") or [],
|
|
3095
|
+
"categories": metadata.get("categories") or item.get("categories") or [],
|
|
3096
|
+
"abstract": metadata.get("abstract") or item.get("abstract") or "",
|
|
3097
|
+
"published_at": metadata.get("published_at") or item.get("published_at") or "",
|
|
3098
|
+
"version": metadata.get("version") if metadata.get("version") is not None else item.get("version"),
|
|
3099
|
+
"primary_class": metadata.get("primary_class") or item.get("primary_class") or "",
|
|
3100
|
+
"metadata_source": metadata.get("metadata_source") or item.get("metadata_source"),
|
|
3101
|
+
"metadata_status": "ready",
|
|
3102
|
+
"overview": summary.get("overview") or item.get("overview") or "",
|
|
3103
|
+
"overview_markdown": summary.get("overview_markdown") or item.get("overview_markdown") or "",
|
|
3104
|
+
"summary_source": summary_source or item.get("summary_source"),
|
|
3105
|
+
"overview_source": overview_source or summary_source or item.get("overview_source"),
|
|
3106
|
+
"bibtex": metadata.get("bibtex") or item.get("bibtex"),
|
|
3107
|
+
"abs_url": metadata.get("abs_url") or item.get("abs_url"),
|
|
3108
|
+
"pdf_url": metadata.get("pdf_url") or item.get("pdf_url"),
|
|
3109
|
+
},
|
|
3110
|
+
)
|
|
3111
|
+
|
|
3112
|
+
@staticmethod
|
|
3113
|
+
def _arxiv_file_payload(quest_root: Path, item: dict[str, Any]) -> dict[str, Any]:
|
|
3114
|
+
relative = str(item.get("path") or "").strip()
|
|
3115
|
+
if not relative:
|
|
3116
|
+
return {}
|
|
3117
|
+
document_id = str(item.get("document_id") or f"questpath::{relative}").strip()
|
|
3118
|
+
return {
|
|
3119
|
+
"path": relative,
|
|
3120
|
+
"document_id": document_id,
|
|
3121
|
+
"pdf_rel_path": relative,
|
|
3122
|
+
"pdf_url": f"/api/quests/{quest_root.name}/documents/asset?document_id={document_id}",
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
def arxiv(
|
|
3126
|
+
self,
|
|
3127
|
+
paper_id: str | None = None,
|
|
3128
|
+
*,
|
|
3129
|
+
full_text: bool = False,
|
|
3130
|
+
mode: str = "read",
|
|
3131
|
+
quest_root: Path | None = None,
|
|
3132
|
+
) -> dict[str, Any]:
|
|
3133
|
+
normalized_mode = str(mode or "read").strip().lower() or "read"
|
|
3134
|
+
if normalized_mode == "list":
|
|
3135
|
+
if quest_root is None:
|
|
3136
|
+
return {
|
|
3137
|
+
"ok": False,
|
|
3138
|
+
"mode": "list",
|
|
3139
|
+
"error": "`quest_root` is required for `artifact.arxiv(mode='list')`.",
|
|
3140
|
+
}
|
|
3141
|
+
items = self.arxiv_library.list_items(quest_root)
|
|
3142
|
+
refreshed_any = False
|
|
3143
|
+
for item in items[:]:
|
|
3144
|
+
if not self._arxiv_item_needs_refresh(item):
|
|
3145
|
+
continue
|
|
3146
|
+
refreshed = self._refresh_arxiv_item_metadata(quest_root, item)
|
|
3147
|
+
if refreshed != item:
|
|
3148
|
+
refreshed_any = True
|
|
3149
|
+
if refreshed_any:
|
|
3150
|
+
items = self.arxiv_library.list_items(quest_root)
|
|
3151
|
+
return {
|
|
3152
|
+
"ok": True,
|
|
3153
|
+
"mode": "list",
|
|
3154
|
+
"items": items,
|
|
3155
|
+
"count": len(items),
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
if paper_id is None:
|
|
3159
|
+
return {
|
|
3160
|
+
"ok": False,
|
|
3161
|
+
"mode": normalized_mode,
|
|
3162
|
+
"error": "`paper_id` is required for `artifact.arxiv(mode='read')`.",
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
if quest_root is None:
|
|
3166
|
+
return {
|
|
3167
|
+
**read_arxiv_content(paper_id, full_text=full_text),
|
|
3168
|
+
"mode": normalized_mode,
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
entry = self.arxiv_library.mark_processing(quest_root, paper_id)
|
|
3172
|
+
cached_entry = self.arxiv_library.get_item(quest_root, paper_id)
|
|
3173
|
+
if cached_entry and self._arxiv_item_needs_refresh(cached_entry):
|
|
3174
|
+
refreshed = self._refresh_arxiv_item_metadata(quest_root, cached_entry)
|
|
3175
|
+
if refreshed:
|
|
3176
|
+
cached_entry = refreshed
|
|
3177
|
+
if (
|
|
3178
|
+
cached_entry
|
|
3179
|
+
and not full_text
|
|
3180
|
+
and cached_entry.get("abstract")
|
|
3181
|
+
and (cached_entry.get("summary_source") or cached_entry.get("metadata_status") == "pending")
|
|
3182
|
+
):
|
|
3183
|
+
paper_ref = str(cached_entry.get("arxiv_id") or paper_id).strip()
|
|
3184
|
+
self.arxiv_library.queue_pdf_download(quest_root, str(cached_entry.get("arxiv_id") or paper_id))
|
|
3185
|
+
return {
|
|
3186
|
+
"ok": True,
|
|
3187
|
+
"mode": normalized_mode,
|
|
3188
|
+
"paper_id": paper_ref,
|
|
3189
|
+
"requested_full_text": full_text,
|
|
3190
|
+
"content_mode": "abstract",
|
|
3191
|
+
"source": "quest_arxiv_library",
|
|
3192
|
+
"source_url": f"https://arxiv.org/abs/{paper_ref}",
|
|
3193
|
+
"title": cached_entry.get("title"),
|
|
3194
|
+
"authors": cached_entry.get("authors") or [],
|
|
3195
|
+
"categories": cached_entry.get("categories") or [],
|
|
3196
|
+
"abstract": cached_entry.get("abstract") or "",
|
|
3197
|
+
"overview": cached_entry.get("overview") or "",
|
|
3198
|
+
"overview_markdown": cached_entry.get("overview_markdown") or "",
|
|
3199
|
+
"summary_source": cached_entry.get("summary_source"),
|
|
3200
|
+
"overview_source": cached_entry.get("overview_source"),
|
|
3201
|
+
"metadata_source": cached_entry.get("metadata_source"),
|
|
3202
|
+
"published_at": cached_entry.get("published_at") or "",
|
|
3203
|
+
"version": cached_entry.get("version"),
|
|
3204
|
+
"primary_class": cached_entry.get("primary_class") or "",
|
|
3205
|
+
"bibtex": cached_entry.get("bibtex") or "",
|
|
3206
|
+
"status": cached_entry.get("status"),
|
|
3207
|
+
"metadata_status": cached_entry.get("metadata_status"),
|
|
3208
|
+
"abs_url": f"https://arxiv.org/abs/{paper_ref}",
|
|
3209
|
+
"pdf_url": f"https://arxiv.org/pdf/{paper_ref}.pdf",
|
|
3210
|
+
"content": self._arxiv_content_from_item(cached_entry),
|
|
3211
|
+
"attempts": [],
|
|
3212
|
+
**self._arxiv_file_payload(quest_root, cached_entry),
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
fetched = read_arxiv_content(str(entry.get("arxiv_id") or paper_id), full_text=full_text)
|
|
3216
|
+
if not fetched.get("ok"):
|
|
3217
|
+
normalized_id = str(entry.get("arxiv_id") or paper_id).strip()
|
|
3218
|
+
placeholder = self.arxiv_library.upsert_item(
|
|
3219
|
+
quest_root,
|
|
3220
|
+
{
|
|
3221
|
+
**(cached_entry or {}),
|
|
3222
|
+
"arxiv_id": normalized_id,
|
|
3223
|
+
"title": str((cached_entry or {}).get("title") or normalized_id).strip(),
|
|
3224
|
+
"display_name": str((cached_entry or {}).get("display_name") or normalized_id).strip(),
|
|
3225
|
+
"status": str((cached_entry or {}).get("status") or "processing").strip() or "processing",
|
|
3226
|
+
"metadata_status": "pending",
|
|
3227
|
+
"error": None,
|
|
3228
|
+
"pdf_rel_path": self.arxiv_library.pdf_relative_path(normalized_id),
|
|
3229
|
+
"abs_url": str((cached_entry or {}).get("abs_url") or f"https://arxiv.org/abs/{normalized_id}"),
|
|
3230
|
+
"pdf_url": str((cached_entry or {}).get("pdf_url") or f"https://arxiv.org/pdf/{normalized_id}.pdf"),
|
|
3231
|
+
},
|
|
3232
|
+
)
|
|
3233
|
+
self.arxiv_library.queue_pdf_download(
|
|
3234
|
+
quest_root,
|
|
3235
|
+
normalized_id,
|
|
3236
|
+
pdf_url=str(placeholder.get("pdf_url") or "").strip() or None,
|
|
3237
|
+
)
|
|
3238
|
+
latest = self.arxiv_library.get_item(quest_root, normalized_id) or placeholder
|
|
3239
|
+
return {
|
|
3240
|
+
"ok": True,
|
|
3241
|
+
"mode": normalized_mode,
|
|
3242
|
+
"paper_id": normalized_id,
|
|
3243
|
+
"requested_full_text": full_text,
|
|
3244
|
+
"content_mode": "pending",
|
|
3245
|
+
"source": "quest_arxiv_library_partial",
|
|
3246
|
+
"source_url": latest.get("abs_url") or f"https://arxiv.org/abs/{normalized_id}",
|
|
3247
|
+
"title": latest.get("title"),
|
|
3248
|
+
"authors": latest.get("authors") or [],
|
|
3249
|
+
"categories": latest.get("categories") or [],
|
|
3250
|
+
"abstract": latest.get("abstract") or "",
|
|
3251
|
+
"overview": latest.get("overview") or "",
|
|
3252
|
+
"overview_markdown": latest.get("overview_markdown") or "",
|
|
3253
|
+
"summary_source": latest.get("summary_source"),
|
|
3254
|
+
"overview_source": latest.get("overview_source"),
|
|
3255
|
+
"metadata_source": latest.get("metadata_source"),
|
|
3256
|
+
"published_at": latest.get("published_at") or "",
|
|
3257
|
+
"version": latest.get("version"),
|
|
3258
|
+
"primary_class": latest.get("primary_class") or "",
|
|
3259
|
+
"bibtex": latest.get("bibtex") or "",
|
|
3260
|
+
"status": latest.get("status"),
|
|
3261
|
+
"metadata_status": "pending",
|
|
3262
|
+
"metadata_pending": True,
|
|
3263
|
+
"message": "Metadata is temporarily unavailable. Open the arXiv link directly while DeepScientist retries later.",
|
|
3264
|
+
"abs_url": latest.get("abs_url") or f"https://arxiv.org/abs/{normalized_id}",
|
|
3265
|
+
"pdf_url": latest.get("pdf_url") or f"https://arxiv.org/pdf/{normalized_id}.pdf",
|
|
3266
|
+
"content": self._arxiv_content_from_item(latest),
|
|
3267
|
+
"attempts": fetched.get("attempts") or [],
|
|
3268
|
+
"guidance": fetched.get("guidance"),
|
|
3269
|
+
**self._arxiv_file_payload(quest_root, latest),
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
saved = self.arxiv_library.upsert_item(
|
|
3273
|
+
quest_root,
|
|
3274
|
+
{
|
|
3275
|
+
**(cached_entry or {}),
|
|
3276
|
+
"arxiv_id": fetched.get("paper_id") or str(entry.get("arxiv_id") or paper_id),
|
|
3277
|
+
"title": fetched.get("title") or cached_entry.get("title") if cached_entry else fetched.get("title"),
|
|
3278
|
+
"authors": fetched.get("authors") or (cached_entry.get("authors") if cached_entry else []),
|
|
3279
|
+
"categories": fetched.get("categories") or (cached_entry.get("categories") if cached_entry else []),
|
|
3280
|
+
"abstract": fetched.get("abstract") or (cached_entry.get("abstract") if cached_entry else ""),
|
|
3281
|
+
"overview": fetched.get("overview") or (cached_entry.get("overview") if cached_entry else ""),
|
|
3282
|
+
"overview_markdown": fetched.get("overview_markdown") or (cached_entry.get("overview_markdown") if cached_entry else ""),
|
|
3283
|
+
"summary_source": fetched.get("summary_source") or (cached_entry.get("summary_source") if cached_entry else None),
|
|
3284
|
+
"overview_source": fetched.get("overview_source") or (cached_entry.get("overview_source") if cached_entry else None),
|
|
3285
|
+
"metadata_source": fetched.get("metadata_source") or (cached_entry.get("metadata_source") if cached_entry else None),
|
|
3286
|
+
"published_at": fetched.get("published_at") or (cached_entry.get("published_at") if cached_entry else ""),
|
|
3287
|
+
"version": fetched.get("version") if fetched.get("version") is not None else (cached_entry.get("version") if cached_entry else None),
|
|
3288
|
+
"primary_class": fetched.get("primary_class") or (cached_entry.get("primary_class") if cached_entry else ""),
|
|
3289
|
+
"bibtex": fetched.get("bibtex") or (cached_entry.get("bibtex") if cached_entry else None),
|
|
3290
|
+
"abs_url": fetched.get("abs_url") or (cached_entry.get("abs_url") if cached_entry else None),
|
|
3291
|
+
"pdf_url": fetched.get("pdf_url") or (cached_entry.get("pdf_url") if cached_entry else None),
|
|
3292
|
+
"display_name": fetched.get("title") or fetched.get("paper_id") or str(entry.get("arxiv_id") or paper_id),
|
|
3293
|
+
"pdf_rel_path": self.arxiv_library.pdf_relative_path(str(fetched.get("paper_id") or entry.get("arxiv_id") or paper_id)),
|
|
3294
|
+
"status": "processing",
|
|
3295
|
+
"metadata_status": "ready",
|
|
3296
|
+
"error": None,
|
|
3297
|
+
},
|
|
3298
|
+
)
|
|
3299
|
+
self.arxiv_library.queue_pdf_download(
|
|
3300
|
+
quest_root,
|
|
3301
|
+
str(saved.get("arxiv_id") or paper_id),
|
|
3302
|
+
pdf_url=str(fetched.get("pdf_url") or "").strip() or None,
|
|
3303
|
+
)
|
|
3304
|
+
latest = self.arxiv_library.get_item(quest_root, str(saved.get("arxiv_id") or paper_id)) or saved
|
|
3305
|
+
return {
|
|
3306
|
+
**fetched,
|
|
3307
|
+
"mode": normalized_mode,
|
|
3308
|
+
"status": latest.get("status"),
|
|
3309
|
+
"metadata_status": latest.get("metadata_status"),
|
|
3310
|
+
**self._arxiv_file_payload(quest_root, latest),
|
|
3311
|
+
}
|
|
2898
3312
|
|
|
2899
3313
|
def record(
|
|
2900
3314
|
self,
|
|
@@ -5349,6 +5763,50 @@ class ArtifactService:
|
|
|
5349
5763
|
checkpoint=False,
|
|
5350
5764
|
workspace_root=workspace_root,
|
|
5351
5765
|
)
|
|
5766
|
+
interaction = self.interact(
|
|
5767
|
+
quest_root,
|
|
5768
|
+
kind="milestone",
|
|
5769
|
+
message=self._build_paper_bundle_interaction_message(
|
|
5770
|
+
title=str(manifest.get("title") or "").strip() or None,
|
|
5771
|
+
summary=str(manifest.get("summary") or "").strip() or None,
|
|
5772
|
+
paper_branch=paper_branch,
|
|
5773
|
+
source_branch=source_branch,
|
|
5774
|
+
source_run_id=source_run_id,
|
|
5775
|
+
selected_outline_ref=str(manifest.get("selected_outline_ref") or "").strip() or None,
|
|
5776
|
+
manifest_rel_path=self._workspace_relative(quest_root, manifest_path),
|
|
5777
|
+
draft_rel_path=str(manifest.get("draft_path") or "").strip() or None,
|
|
5778
|
+
writing_plan_rel_path=str(manifest.get("writing_plan_path") or "").strip() or None,
|
|
5779
|
+
references_rel_path=str(manifest.get("references_path") or "").strip() or None,
|
|
5780
|
+
claim_evidence_map_rel_path=str(manifest.get("claim_evidence_map_path") or "").strip() or None,
|
|
5781
|
+
compile_report_rel_path=str(manifest.get("compile_report_path") or "").strip() or None,
|
|
5782
|
+
pdf_rel_path=str(manifest.get("pdf_path") or "").strip() or None,
|
|
5783
|
+
latex_root_rel_path=str(manifest.get("latex_root_path") or "").strip() or None,
|
|
5784
|
+
baseline_inventory_rel_path=paper_inventory_rel,
|
|
5785
|
+
open_source_manifest_rel_path=str(manifest.get("open_source_manifest_path") or "").strip() or None,
|
|
5786
|
+
),
|
|
5787
|
+
deliver_to_bound_conversations=True,
|
|
5788
|
+
include_recent_inbound_messages=False,
|
|
5789
|
+
attachments=[
|
|
5790
|
+
{
|
|
5791
|
+
"kind": "paper_bundle",
|
|
5792
|
+
"title": manifest.get("title"),
|
|
5793
|
+
"paper_branch": paper_branch,
|
|
5794
|
+
"source_branch": source_branch,
|
|
5795
|
+
"source_run_id": source_run_id,
|
|
5796
|
+
"selected_outline_ref": manifest.get("selected_outline_ref"),
|
|
5797
|
+
"manifest_path": str(manifest_path),
|
|
5798
|
+
"draft_path": manifest.get("draft_path"),
|
|
5799
|
+
"writing_plan_path": manifest.get("writing_plan_path"),
|
|
5800
|
+
"references_path": manifest.get("references_path"),
|
|
5801
|
+
"claim_evidence_map_path": manifest.get("claim_evidence_map_path"),
|
|
5802
|
+
"compile_report_path": manifest.get("compile_report_path"),
|
|
5803
|
+
"pdf_path": manifest.get("pdf_path"),
|
|
5804
|
+
"latex_root_path": manifest.get("latex_root_path"),
|
|
5805
|
+
"baseline_inventory_path": str(baseline_inventory_path),
|
|
5806
|
+
"open_source_manifest_path": str(self._open_source_manifest_path(quest_root, workspace_root=workspace_root)),
|
|
5807
|
+
}
|
|
5808
|
+
],
|
|
5809
|
+
)
|
|
5352
5810
|
return {
|
|
5353
5811
|
"ok": True,
|
|
5354
5812
|
"manifest_path": str(manifest_path),
|
|
@@ -5356,6 +5814,7 @@ class ArtifactService:
|
|
|
5356
5814
|
"baseline_inventory_path": str(baseline_inventory_path),
|
|
5357
5815
|
"open_source_manifest_path": str(self._open_source_manifest_path(quest_root, workspace_root=workspace_root)),
|
|
5358
5816
|
"artifact": artifact,
|
|
5817
|
+
"interaction": interaction,
|
|
5359
5818
|
}
|
|
5360
5819
|
|
|
5361
5820
|
def record_analysis_slice(
|
|
@@ -6927,11 +7386,18 @@ class ArtifactService:
|
|
|
6927
7386
|
return f"run/{run_id or generate_id('run')}"
|
|
6928
7387
|
|
|
6929
7388
|
def _bound_conversations(self, quest_root: Path) -> list[str]:
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
7389
|
+
quest_id = self._quest_id(quest_root)
|
|
7390
|
+
sources = [
|
|
7391
|
+
self._normalize_conversation_id(str(item))
|
|
7392
|
+
for item in self.quest_service.binding_sources(quest_id)
|
|
7393
|
+
]
|
|
7394
|
+
authoritative_keys = {conversation_identity_key(item) for item in sources}
|
|
7395
|
+
connector_sources = [
|
|
7396
|
+
item
|
|
7397
|
+
for item in self._connector_bound_conversations(quest_id)
|
|
7398
|
+
if conversation_identity_key(item) in authoritative_keys
|
|
7399
|
+
]
|
|
7400
|
+
return self._dedupe_targets([*sources, *connector_sources])
|
|
6935
7401
|
|
|
6936
7402
|
def _connector_bound_conversations(self, quest_id: str) -> list[str]:
|
|
6937
7403
|
root = self.home / "logs" / "connectors"
|