@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
|
@@ -77,7 +77,6 @@ class ApiHandlers:
|
|
|
77
77
|
"productApis": False,
|
|
78
78
|
"socketIo": False,
|
|
79
79
|
"notifications": False,
|
|
80
|
-
"broadcasts": False,
|
|
81
80
|
"points": False,
|
|
82
81
|
"arxiv": True,
|
|
83
82
|
"cliFrontend": False,
|
|
@@ -208,9 +207,31 @@ npm --prefix src/ui run build</pre>
|
|
|
208
207
|
def connectors_availability(self) -> dict:
|
|
209
208
|
return self.app.connector_availability_summary()
|
|
210
209
|
|
|
210
|
+
def weixin_login_qr_start(self, body: dict | None = None) -> dict:
|
|
211
|
+
payload = body if isinstance(body, dict) else {}
|
|
212
|
+
return self.app.start_weixin_login_qr(force=bool(payload.get("force")))
|
|
213
|
+
|
|
214
|
+
def weixin_login_qr_wait(self, body: dict | None = None) -> dict:
|
|
215
|
+
payload = body if isinstance(body, dict) else {}
|
|
216
|
+
return self.app.wait_weixin_login_qr(
|
|
217
|
+
session_key=str(payload.get("session_key") or "").strip(),
|
|
218
|
+
timeout_ms=int(payload.get("timeout_ms") or 1_500),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def lingzhu_health(self) -> dict:
|
|
222
|
+
return self.app.lingzhu_health_payload()
|
|
223
|
+
|
|
211
224
|
def baselines(self) -> list[dict]:
|
|
212
225
|
return self.app.artifact_service.baselines.list_entries()
|
|
213
226
|
|
|
227
|
+
def baseline_delete(self, baseline_id: str) -> dict | tuple[int, dict]:
|
|
228
|
+
try:
|
|
229
|
+
return self.app.artifact_service.delete_baseline(baseline_id)
|
|
230
|
+
except FileNotFoundError as exc:
|
|
231
|
+
return 404, {"ok": False, "message": str(exc), "baseline_id": baseline_id}
|
|
232
|
+
except ValueError as exc:
|
|
233
|
+
return 400, {"ok": False, "message": str(exc), "baseline_id": baseline_id}
|
|
234
|
+
|
|
214
235
|
def qq_bindings(self) -> list[dict]:
|
|
215
236
|
return self.app.list_qq_bindings()
|
|
216
237
|
|
|
@@ -257,6 +278,18 @@ npm --prefix src/ui run build</pre>
|
|
|
257
278
|
"no",
|
|
258
279
|
"off",
|
|
259
280
|
}
|
|
281
|
+
auto_bind_latest_connectors_raw = body.get("auto_bind_latest_connectors")
|
|
282
|
+
if auto_bind_latest_connectors_raw is None:
|
|
283
|
+
auto_bind_latest_connectors = True
|
|
284
|
+
else:
|
|
285
|
+
auto_bind_latest_connectors = bool(auto_bind_latest_connectors_raw) and str(
|
|
286
|
+
auto_bind_latest_connectors_raw
|
|
287
|
+
).strip().lower() not in {
|
|
288
|
+
"0",
|
|
289
|
+
"false",
|
|
290
|
+
"no",
|
|
291
|
+
"off",
|
|
292
|
+
}
|
|
260
293
|
requested_baseline_ref = body.get("requested_baseline_ref")
|
|
261
294
|
startup_contract = body.get("startup_contract")
|
|
262
295
|
auto_start = body.get("auto_start") is True
|
|
@@ -281,6 +314,7 @@ npm --prefix src/ui run build</pre>
|
|
|
281
314
|
preferred_connector_conversation_id=preferred_connector_conversation_id,
|
|
282
315
|
requested_connector_bindings=requested_connector_bindings,
|
|
283
316
|
force_connector_rebind=force_connector_rebind,
|
|
317
|
+
auto_bind_latest_connectors=auto_bind_latest_connectors,
|
|
284
318
|
requested_baseline_ref=requested_baseline_ref if isinstance(requested_baseline_ref, dict) else None,
|
|
285
319
|
startup_contract=startup_contract if isinstance(startup_contract, dict) else None,
|
|
286
320
|
)
|
|
@@ -408,6 +442,7 @@ npm --prefix src/ui run build</pre>
|
|
|
408
442
|
}
|
|
409
443
|
|
|
410
444
|
def quest_bindings(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
445
|
+
previous_external = self.app._quest_external_binding(quest_id)
|
|
411
446
|
requested_bindings = (
|
|
412
447
|
[dict(item) for item in body.get("bindings") if isinstance(item, dict)]
|
|
413
448
|
if isinstance(body.get("bindings"), list)
|
|
@@ -418,10 +453,24 @@ npm --prefix src/ui run build</pre>
|
|
|
418
453
|
force_raw = body.get("force")
|
|
419
454
|
force = bool(force_raw) and str(force_raw).strip().lower() not in {"0", "false", "no", "off"}
|
|
420
455
|
if requested_bindings:
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
456
|
+
result = self.app.update_quest_bindings(quest_id, requested_bindings, force=force)
|
|
457
|
+
elif connector_name:
|
|
458
|
+
result = self.app.update_quest_connector_binding(quest_id, connector_name, conversation_id, force=force)
|
|
459
|
+
else:
|
|
460
|
+
result = self.app.update_quest_binding(quest_id, conversation_id, force=force)
|
|
461
|
+
if isinstance(result, tuple):
|
|
462
|
+
return result
|
|
463
|
+
current_external = self.app._quest_external_binding(quest_id)
|
|
464
|
+
transition = self.app._binding_transition_summary(
|
|
465
|
+
quest_id=quest_id,
|
|
466
|
+
previous_conversation_id=previous_external,
|
|
467
|
+
current_conversation_id=current_external,
|
|
468
|
+
)
|
|
469
|
+
self.app._announce_binding_transition(transition, notify_new=True, notify_old=True)
|
|
470
|
+
return {
|
|
471
|
+
**result,
|
|
472
|
+
"binding_transition": transition,
|
|
473
|
+
}
|
|
425
474
|
|
|
426
475
|
def quest_session(self, quest_id: str) -> dict:
|
|
427
476
|
snapshot = self.app.quest_service.snapshot_fast(quest_id)
|
|
@@ -721,6 +770,28 @@ npm --prefix src/ui run build</pre>
|
|
|
721
770
|
def workflow(self, quest_id: str) -> dict:
|
|
722
771
|
return self.app.quest_service.workflow(quest_id)
|
|
723
772
|
|
|
773
|
+
def quest_layout(self, quest_id: str) -> dict:
|
|
774
|
+
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
775
|
+
payload = self.app.quest_service.read_lab_canvas_state(quest_root)
|
|
776
|
+
return {
|
|
777
|
+
"layout_json": payload.get("layout_json") if isinstance(payload.get("layout_json"), dict) else {},
|
|
778
|
+
"updated_at": payload.get("updated_at"),
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
def quest_layout_update(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
782
|
+
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
783
|
+
raw_layout = body.get("layout_json")
|
|
784
|
+
if raw_layout is not None and not isinstance(raw_layout, dict):
|
|
785
|
+
return 400, {"ok": False, "message": "`layout_json` must be an object."}
|
|
786
|
+
payload = self.app.quest_service.update_lab_canvas_state(
|
|
787
|
+
quest_root,
|
|
788
|
+
layout_json=dict(raw_layout or {}),
|
|
789
|
+
)
|
|
790
|
+
return {
|
|
791
|
+
"layout_json": payload.get("layout_json") if isinstance(payload.get("layout_json"), dict) else {},
|
|
792
|
+
"updated_at": payload.get("updated_at"),
|
|
793
|
+
}
|
|
794
|
+
|
|
724
795
|
def node_traces(self, quest_id: str, path: str) -> dict:
|
|
725
796
|
query = self.parse_query(path)
|
|
726
797
|
selection_type = ((query.get("selection_type") or [""])[0] or "").strip() or None
|
|
@@ -754,6 +825,14 @@ npm --prefix src/ui run build</pre>
|
|
|
754
825
|
payload["active_workspace_ref"] = active_workspace_branch
|
|
755
826
|
payload["research_head_ref"] = research_head_branch
|
|
756
827
|
payload["workspace_mode"] = str(research_state.get("workspace_mode") or "quest").strip() or "quest"
|
|
828
|
+
quest_data = self.app.quest_service.read_quest_yaml(quest_root)
|
|
829
|
+
active_anchor = str(quest_data.get("active_anchor") or "").strip().lower()
|
|
830
|
+
active_analysis_campaign_id = str(research_state.get("active_analysis_campaign_id") or "").strip() or None
|
|
831
|
+
current_workspace_branch = str(research_state.get("current_workspace_branch") or "").strip() or None
|
|
832
|
+
workspace_mode = str(research_state.get("workspace_mode") or "").strip().lower() or "quest"
|
|
833
|
+
paper_parent_branch = str(research_state.get("paper_parent_branch") or "").strip() or None
|
|
834
|
+
paper_parent_run_id = str(research_state.get("paper_parent_run_id") or "").strip() or None
|
|
835
|
+
next_pending_slice_id = str(research_state.get("next_pending_slice_id") or "").strip() or None
|
|
757
836
|
try:
|
|
758
837
|
branch_summary = self.app.artifact_service.list_research_branches(quest_root)
|
|
759
838
|
except Exception:
|
|
@@ -763,6 +842,96 @@ npm --prefix src/ui run build</pre>
|
|
|
763
842
|
for item in (branch_summary.get("branches") or [])
|
|
764
843
|
if str(item.get("branch_name") or "").strip()
|
|
765
844
|
}
|
|
845
|
+
active_campaign = {}
|
|
846
|
+
if active_analysis_campaign_id:
|
|
847
|
+
try:
|
|
848
|
+
active_campaign = self.app.artifact_service.get_analysis_campaign(
|
|
849
|
+
quest_root,
|
|
850
|
+
campaign_id=active_analysis_campaign_id,
|
|
851
|
+
)
|
|
852
|
+
except Exception:
|
|
853
|
+
active_campaign = {}
|
|
854
|
+
campaign_parent_branch = (
|
|
855
|
+
str(active_campaign.get("parent_branch") or "").strip() or None
|
|
856
|
+
if isinstance(active_campaign, dict)
|
|
857
|
+
else None
|
|
858
|
+
)
|
|
859
|
+
campaign_slices = [
|
|
860
|
+
dict(item)
|
|
861
|
+
for item in ((active_campaign or {}).get("slices") or [])
|
|
862
|
+
if isinstance(item, dict)
|
|
863
|
+
]
|
|
864
|
+
campaign_total_slices = len(campaign_slices)
|
|
865
|
+
campaign_completed_slices = sum(
|
|
866
|
+
1 for item in campaign_slices if str(item.get("status") or "").strip().lower() == "completed"
|
|
867
|
+
)
|
|
868
|
+
slice_by_branch = {
|
|
869
|
+
str(item.get("branch") or "").strip(): item
|
|
870
|
+
for item in campaign_slices
|
|
871
|
+
if str(item.get("branch") or "").strip()
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
def resolve_workflow_state(ref: str, summary: dict[str, object] | None) -> dict[str, object]:
|
|
875
|
+
branch_kind = self.app.artifact_service._branch_kind_from_name(ref)
|
|
876
|
+
has_main_result = bool((summary or {}).get("has_main_result"))
|
|
877
|
+
workflow_state: dict[str, object] = {
|
|
878
|
+
"analysis_state": "none",
|
|
879
|
+
"writing_state": "not_ready",
|
|
880
|
+
"analysis_campaign_id": active_analysis_campaign_id,
|
|
881
|
+
"total_slices": campaign_total_slices or None,
|
|
882
|
+
"completed_slices": campaign_completed_slices or None,
|
|
883
|
+
"next_pending_slice_id": next_pending_slice_id,
|
|
884
|
+
"paper_parent_branch": paper_parent_branch,
|
|
885
|
+
"paper_parent_run_id": paper_parent_run_id,
|
|
886
|
+
"status_reason": None,
|
|
887
|
+
}
|
|
888
|
+
if branch_kind == "analysis":
|
|
889
|
+
slice_entry = slice_by_branch.get(ref)
|
|
890
|
+
slice_status = str((slice_entry or {}).get("status") or "pending").strip().lower() or "pending"
|
|
891
|
+
if slice_status == "completed":
|
|
892
|
+
workflow_state["analysis_state"] = "completed"
|
|
893
|
+
workflow_state["status_reason"] = "Analysis slice completed."
|
|
894
|
+
elif ref == current_workspace_branch or str((slice_entry or {}).get("slice_id") or "").strip() == next_pending_slice_id:
|
|
895
|
+
workflow_state["analysis_state"] = "active"
|
|
896
|
+
workflow_state["status_reason"] = (
|
|
897
|
+
f"Analysis {campaign_completed_slices}/{campaign_total_slices} done"
|
|
898
|
+
if campaign_total_slices
|
|
899
|
+
else "Analysis slice active."
|
|
900
|
+
)
|
|
901
|
+
else:
|
|
902
|
+
workflow_state["analysis_state"] = "pending"
|
|
903
|
+
workflow_state["status_reason"] = "Analysis slice pending."
|
|
904
|
+
return workflow_state
|
|
905
|
+
if branch_kind == "paper":
|
|
906
|
+
if ref == current_workspace_branch and workspace_mode == "paper":
|
|
907
|
+
workflow_state["writing_state"] = "completed" if active_anchor == "finalize" else "active"
|
|
908
|
+
workflow_state["status_reason"] = (
|
|
909
|
+
"Writing finalized." if active_anchor == "finalize" else "Writing workspace active."
|
|
910
|
+
)
|
|
911
|
+
else:
|
|
912
|
+
workflow_state["writing_state"] = "ready"
|
|
913
|
+
workflow_state["status_reason"] = "Writing workspace prepared."
|
|
914
|
+
return workflow_state
|
|
915
|
+
if campaign_parent_branch and ref == campaign_parent_branch:
|
|
916
|
+
workflow_state["analysis_state"] = "completed" if next_pending_slice_id is None else "active"
|
|
917
|
+
if has_main_result:
|
|
918
|
+
workflow_state["writing_state"] = "ready" if next_pending_slice_id is None else "blocked_by_analysis"
|
|
919
|
+
workflow_state["status_reason"] = (
|
|
920
|
+
"Analysis complete. Ready for writing."
|
|
921
|
+
if next_pending_slice_id is None
|
|
922
|
+
else (
|
|
923
|
+
f"Analysis {campaign_completed_slices}/{campaign_total_slices} done"
|
|
924
|
+
+ (f" · next: {next_pending_slice_id}" if next_pending_slice_id else "")
|
|
925
|
+
)
|
|
926
|
+
)
|
|
927
|
+
return workflow_state
|
|
928
|
+
if has_main_result:
|
|
929
|
+
workflow_state["writing_state"] = "ready"
|
|
930
|
+
workflow_state["status_reason"] = "Main experiment recorded. Ready for writing."
|
|
931
|
+
return workflow_state
|
|
932
|
+
workflow_state["status_reason"] = "Awaiting main experiment result."
|
|
933
|
+
return workflow_state
|
|
934
|
+
|
|
766
935
|
for node in payload.get("nodes", []):
|
|
767
936
|
ref = str(node.get("ref") or "").strip()
|
|
768
937
|
if not ref:
|
|
@@ -770,6 +939,7 @@ npm --prefix src/ui run build</pre>
|
|
|
770
939
|
summary = branch_summary_by_name.get(ref)
|
|
771
940
|
node["active_workspace"] = ref == active_workspace_branch
|
|
772
941
|
node["research_head"] = ref == research_head_branch
|
|
942
|
+
node["workflow_state"] = resolve_workflow_state(ref, summary if isinstance(summary, dict) else None)
|
|
773
943
|
if not isinstance(summary, dict):
|
|
774
944
|
continue
|
|
775
945
|
node["branch_no"] = summary.get("branch_no")
|
|
@@ -784,6 +954,7 @@ npm --prefix src/ui run build</pre>
|
|
|
784
954
|
node["idea_draft_path"] = summary.get("idea_draft_path")
|
|
785
955
|
node["latest_main_experiment"] = summary.get("latest_main_experiment")
|
|
786
956
|
node["experiment_count"] = summary.get("experiment_count")
|
|
957
|
+
node["has_main_result"] = summary.get("has_main_result")
|
|
787
958
|
return payload
|
|
788
959
|
|
|
789
960
|
def git_log(self, quest_id: str, path: str) -> dict:
|
|
@@ -983,6 +1154,125 @@ npm --prefix src/ui run build</pre>
|
|
|
983
1154
|
def runs(self, quest_id: str) -> list[dict]:
|
|
984
1155
|
return self.app.quest_service.snapshot(quest_id).get("recent_runs", [])
|
|
985
1156
|
|
|
1157
|
+
def arxiv_list(self, path: str = "") -> dict | tuple[int, dict]:
|
|
1158
|
+
query = self.parse_query(path)
|
|
1159
|
+
quest_id = ((query.get("project_id") or [""])[0] or "").strip()
|
|
1160
|
+
if not quest_id:
|
|
1161
|
+
return 400, {"ok": False, "message": "`project_id` is required."}
|
|
1162
|
+
quest_root = self.app.quest_service._quest_root(quest_id)
|
|
1163
|
+
return self.app.artifact_service.arxiv(mode="list", quest_root=quest_root)
|
|
1164
|
+
|
|
1165
|
+
def arxiv_import(self, body: dict | None = None) -> dict | tuple[int, dict]:
|
|
1166
|
+
body = body or {}
|
|
1167
|
+
quest_id = str(body.get("project_id") or "").strip()
|
|
1168
|
+
paper_id = str(body.get("arxiv_id") or "").strip()
|
|
1169
|
+
if not quest_id:
|
|
1170
|
+
return 400, {"ok": False, "message": "`project_id` is required."}
|
|
1171
|
+
if not paper_id:
|
|
1172
|
+
return 400, {"ok": False, "message": "`arxiv_id` is required."}
|
|
1173
|
+
quest_root = self.app.quest_service._quest_root(quest_id)
|
|
1174
|
+
result = self.app.artifact_service.arxiv(
|
|
1175
|
+
paper_id,
|
|
1176
|
+
mode="read",
|
|
1177
|
+
full_text=False,
|
|
1178
|
+
quest_root=quest_root,
|
|
1179
|
+
)
|
|
1180
|
+
if not result.get("ok"):
|
|
1181
|
+
return 400, result
|
|
1182
|
+
return {
|
|
1183
|
+
"status": str(result.get("status") or "processing"),
|
|
1184
|
+
"metadata_status": str(result.get("metadata_status") or ""),
|
|
1185
|
+
"metadata_pending": bool(result.get("metadata_pending")),
|
|
1186
|
+
"title": str(result.get("title") or ""),
|
|
1187
|
+
"message": str(result.get("message") or ""),
|
|
1188
|
+
"abs_url": str(result.get("abs_url") or ""),
|
|
1189
|
+
"file_id": str(result.get("file_id") or ""),
|
|
1190
|
+
"document_id": str(result.get("document_id") or ""),
|
|
1191
|
+
"arxiv_id": str(result.get("paper_id") or paper_id),
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
def annotations_file(self, file_id: str, path: str = "") -> dict | tuple[int, dict]:
|
|
1195
|
+
try:
|
|
1196
|
+
return self.app.annotation_service.list_annotations(file_id)
|
|
1197
|
+
except FileNotFoundError as exc:
|
|
1198
|
+
return 404, {"ok": False, "message": str(exc), "file_id": file_id}
|
|
1199
|
+
except ValueError as exc:
|
|
1200
|
+
return 400, {"ok": False, "message": str(exc), "file_id": file_id}
|
|
1201
|
+
|
|
1202
|
+
def annotations_project(self, project_id: str, path: str = "") -> dict | tuple[int, dict]:
|
|
1203
|
+
query = self.parse_query(path)
|
|
1204
|
+
search_query = ((query.get("q") or [""])[0] or "").strip() or None
|
|
1205
|
+
color = ((query.get("color") or [""])[0] or "").strip() or None
|
|
1206
|
+
tag = ((query.get("tag") or [""])[0] or "").strip() or None
|
|
1207
|
+
page_raw = ((query.get("page") or [""])[0] or "").strip()
|
|
1208
|
+
limit_raw = ((query.get("limit") or ["100"])[0] or "100").strip()
|
|
1209
|
+
try:
|
|
1210
|
+
page = int(page_raw) if page_raw else None
|
|
1211
|
+
except ValueError:
|
|
1212
|
+
page = None
|
|
1213
|
+
try:
|
|
1214
|
+
limit = max(1, min(int(limit_raw), 500))
|
|
1215
|
+
except ValueError:
|
|
1216
|
+
limit = 100
|
|
1217
|
+
try:
|
|
1218
|
+
return self.app.annotation_service.search_annotations(
|
|
1219
|
+
project_id,
|
|
1220
|
+
query=search_query,
|
|
1221
|
+
color=color,
|
|
1222
|
+
tag=tag,
|
|
1223
|
+
page=page,
|
|
1224
|
+
limit=limit,
|
|
1225
|
+
)
|
|
1226
|
+
except FileNotFoundError as exc:
|
|
1227
|
+
return 404, {"ok": False, "message": str(exc), "project_id": project_id}
|
|
1228
|
+
|
|
1229
|
+
def annotation_create(self, body: dict) -> dict | tuple[int, dict]:
|
|
1230
|
+
file_id = str(body.get("file_id") or "").strip()
|
|
1231
|
+
if not file_id:
|
|
1232
|
+
return 400, {"ok": False, "message": "`file_id` is required."}
|
|
1233
|
+
try:
|
|
1234
|
+
return self.app.annotation_service.create_annotation(
|
|
1235
|
+
file_id=file_id,
|
|
1236
|
+
position=body.get("position"),
|
|
1237
|
+
content=body.get("content"),
|
|
1238
|
+
comment=body.get("comment"),
|
|
1239
|
+
kind=body.get("kind"),
|
|
1240
|
+
color=body.get("color"),
|
|
1241
|
+
tags=body.get("tags"),
|
|
1242
|
+
)
|
|
1243
|
+
except FileNotFoundError as exc:
|
|
1244
|
+
return 404, {"ok": False, "message": str(exc), "file_id": file_id}
|
|
1245
|
+
except ValueError as exc:
|
|
1246
|
+
return 400, {"ok": False, "message": str(exc), "file_id": file_id}
|
|
1247
|
+
|
|
1248
|
+
def annotation_detail(self, annotation_id: str) -> dict | tuple[int, dict]:
|
|
1249
|
+
try:
|
|
1250
|
+
return self.app.annotation_service.get_annotation(annotation_id)
|
|
1251
|
+
except FileNotFoundError as exc:
|
|
1252
|
+
return 404, {"ok": False, "message": str(exc), "annotation_id": annotation_id}
|
|
1253
|
+
|
|
1254
|
+
def annotation_update(self, annotation_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
1255
|
+
try:
|
|
1256
|
+
return self.app.annotation_service.update_annotation(
|
|
1257
|
+
annotation_id,
|
|
1258
|
+
comment=body.get("comment") if "comment" in body else None,
|
|
1259
|
+
kind=body.get("kind") if "kind" in body else None,
|
|
1260
|
+
position=body.get("position") if "position" in body else None,
|
|
1261
|
+
content=body.get("content") if "content" in body else None,
|
|
1262
|
+
color=body.get("color") if "color" in body else None,
|
|
1263
|
+
tags=body.get("tags") if "tags" in body else None,
|
|
1264
|
+
)
|
|
1265
|
+
except FileNotFoundError as exc:
|
|
1266
|
+
return 404, {"ok": False, "message": str(exc), "annotation_id": annotation_id}
|
|
1267
|
+
except ValueError as exc:
|
|
1268
|
+
return 400, {"ok": False, "message": str(exc), "annotation_id": annotation_id}
|
|
1269
|
+
|
|
1270
|
+
def annotation_delete(self, annotation_id: str) -> dict | tuple[int, dict]:
|
|
1271
|
+
try:
|
|
1272
|
+
return self.app.annotation_service.delete_annotation(annotation_id)
|
|
1273
|
+
except FileNotFoundError as exc:
|
|
1274
|
+
return 404, {"ok": False, "message": str(exc), "annotation_id": annotation_id}
|
|
1275
|
+
|
|
986
1276
|
def quest_memory(self, quest_id: str) -> list[dict]:
|
|
987
1277
|
quest_service = self._fresh_quest_service()
|
|
988
1278
|
return self._fresh_memory_service().list_cards(
|
|
@@ -6,7 +6,9 @@ import re
|
|
|
6
6
|
ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
7
7
|
("GET", re.compile(r"^/$"), "root"),
|
|
8
8
|
("GET", re.compile(r"^/ui/(?P<ui_path>.+)$"), "ui_asset"),
|
|
9
|
-
("GET", re.compile(r"^/
|
|
9
|
+
("GET", re.compile(r"^/metis/agent/api/health$"), "lingzhu_health"),
|
|
10
|
+
("POST", re.compile(r"^/metis/agent/api/sse$"), "lingzhu_sse"),
|
|
11
|
+
("GET", re.compile(r"^/(?P<spa_path>(?!api(?:/|$)|metis(?:/|$)|ui(?:/|$)|assets(?:/|$)).+)$"), "spa_root"),
|
|
10
12
|
("GET", re.compile(r"^/api/health$"), "health"),
|
|
11
13
|
("GET", re.compile(r"^/api/system/update$"), "system_update"),
|
|
12
14
|
("POST", re.compile(r"^/api/system/update$"), "system_update_action"),
|
|
@@ -15,12 +17,15 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
15
17
|
("GET", re.compile(r"^/api/acp/status$"), "acp_status"),
|
|
16
18
|
("GET", re.compile(r"^/api/connectors$"), "connectors"),
|
|
17
19
|
("GET", re.compile(r"^/api/connectors/availability$"), "connectors_availability"),
|
|
20
|
+
("POST", re.compile(r"^/api/connectors/weixin/login/qr/start$"), "weixin_login_qr_start"),
|
|
21
|
+
("POST", re.compile(r"^/api/connectors/weixin/login/qr/wait$"), "weixin_login_qr_wait"),
|
|
18
22
|
("GET", re.compile(r"^/api/connectors/qq/bindings$"), "qq_bindings"),
|
|
19
23
|
("POST", re.compile(r"^/api/connectors/qq/inbound$"), "qq_inbound"),
|
|
20
24
|
("DELETE", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/profiles/(?P<profile_id>[^/]+)$"), "connector_profile_delete"),
|
|
21
25
|
("GET", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/bindings$"), "connector_bindings"),
|
|
22
26
|
("POST", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/inbound$"), "connector_inbound"),
|
|
23
27
|
("GET", re.compile(r"^/api/baselines$"), "baselines"),
|
|
28
|
+
("DELETE", re.compile(r"^/api/baselines/(?P<baseline_id>[^/]+)$"), "baseline_delete"),
|
|
24
29
|
("GET", re.compile(r"^/api/quests$"), "quests"),
|
|
25
30
|
("GET", re.compile(r"^/api/quest-id/next$"), "quest_next_id"),
|
|
26
31
|
("POST", re.compile(r"^/api/quests$"), "quest_create"),
|
|
@@ -35,6 +40,8 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
35
40
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/events$"), "quest_events"),
|
|
36
41
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/artifacts$"), "quest_artifacts"),
|
|
37
42
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/workflow$"), "workflow"),
|
|
43
|
+
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/layout$"), "quest_layout"),
|
|
44
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/layout$"), "quest_layout_update"),
|
|
38
45
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/node-traces$"), "node_traces"),
|
|
39
46
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/node-traces/(?P<node_ref>.+)$"), "node_trace"),
|
|
40
47
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/stage-view$"), "stage_view"),
|
|
@@ -74,6 +81,14 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
74
81
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/commands$"), "command"),
|
|
75
82
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/control$"), "quest_control"),
|
|
76
83
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/runs$"), "run_create"),
|
|
84
|
+
("GET", re.compile(r"^/api/v1/arxiv/list$"), "arxiv_list"),
|
|
85
|
+
("POST", re.compile(r"^/api/v1/arxiv/import$"), "arxiv_import"),
|
|
86
|
+
("GET", re.compile(r"^/api/v1/annotations/file/(?P<file_id>.+)$"), "annotations_file"),
|
|
87
|
+
("GET", re.compile(r"^/api/v1/annotations/project/(?P<project_id>[^/]+)$"), "annotations_project"),
|
|
88
|
+
("POST", re.compile(r"^/api/v1/annotations/?$"), "annotation_create"),
|
|
89
|
+
("GET", re.compile(r"^/api/v1/annotations/(?P<annotation_id>[^/]+)$"), "annotation_detail"),
|
|
90
|
+
("PATCH", re.compile(r"^/api/v1/annotations/(?P<annotation_id>[^/]+)$"), "annotation_update"),
|
|
91
|
+
("DELETE", re.compile(r"^/api/v1/annotations/(?P<annotation_id>[^/]+)$"), "annotation_delete"),
|
|
77
92
|
("POST", re.compile(r"^/api/v1/projects/(?P<project_id>[^/]+)/latex/init$"), "latex_init"),
|
|
78
93
|
("POST", re.compile(r"^/api/v1/projects/(?P<project_id>[^/]+)/latex/(?P<folder_id>[^/]+)/compile$"), "latex_compile"),
|
|
79
94
|
("GET", re.compile(r"^/api/v1/projects/(?P<project_id>[^/]+)/latex/(?P<folder_id>[^/]+)/builds$"), "latex_builds"),
|