@researai/deepscientist 1.5.9 → 1.5.12
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 +112 -99
- 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 +519 -63
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +338 -68
- package/docs/en/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +180 -4
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +66 -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 +446 -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/15_CODEX_PROVIDER_SETUP.md +284 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +83 -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 +345 -72
- package/docs/zh/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +181 -3
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +68 -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 +442 -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/15_CODEX_PROVIDER_SETUP.md +285 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +129 -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/monitor.py +7 -5
- package/src/deepscientist/bash_exec/service.py +93 -21
- 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/local.py +3 -3
- package/src/deepscientist/channels/qq.py +8 -8
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +14 -8
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +388 -0
- package/src/deepscientist/config/models.py +23 -2
- package/src/deepscientist/config/service.py +539 -67
- 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 +1444 -67
- package/src/deepscientist/doctor.py +4 -5
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +135 -7
- package/src/deepscientist/prompts/builder.py +128 -11
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +23 -0
- package/src/deepscientist/quest/service.py +359 -74
- package/src/deepscientist/quest/stage_views.py +71 -5
- package/src/deepscientist/runners/codex.py +170 -19
- package/src/deepscientist/runners/runtime_overrides.py +6 -0
- package/src/deepscientist/shared.py +33 -14
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/qq.md +2 -1
- package/src/prompts/connectors/weixin.md +231 -0
- package/src/prompts/contracts/shared_interaction.md +4 -1
- package/src/prompts/system.md +61 -9
- package/src/skills/analysis-campaign/SKILL.md +46 -6
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
- package/src/skills/baseline/SKILL.md +1 -1
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +1 -1
- package/src/skills/finalize/SKILL.md +1 -1
- package/src/skills/idea/SKILL.md +1 -1
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/rebuttal/SKILL.md +74 -1
- package/src/skills/rebuttal/references/response-letter-template.md +55 -11
- package/src/skills/review/SKILL.md +118 -1
- package/src/skills/review/references/experiment-todo-template.md +23 -0
- package/src/skills/review/references/review-report-template.md +16 -0
- package/src/skills/review/references/revision-log-template.md +4 -0
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +168 -7
- package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-CnJcXynW.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-CB1YODQn.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-Ciz1gDaX.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BhmjNQRC.js} +37 -11
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-DmyHspXt.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-BMXKrDRk.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BTVYRGkm.js} +12 -12
- package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-CvcjJHXv.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DW2ej8Vk.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-CmlDxbhU.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DAjQZPSv.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C-nVAZb_.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.js → VNCViewer-D7-dIYon.js} +10 -10
- package/src/ui/dist/assets/bot-C_G4WtNI.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-Cd7WfiWq.js} +1 -1
- package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-B57zsL9y.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-DVoheLFq.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-B5kXFxZP.js} +1 -1
- package/src/ui/dist/assets/{image-D-NZM-6P.js → image-LLOjkMHF.js} +1 -1
- package/src/ui/dist/assets/{index-DGIYDuTv.css → index-BQG-1s2o.css} +40 -13
- package/src/ui/dist/assets/{index-DHZJ_0TI.js → index-C3r2iGrp.js} +12 -12
- package/src/ui/dist/assets/{index-7Chr1g9c.js → index-CLQauncb.js} +15050 -9561
- package/src/ui/dist/assets/index-Dxa2eYMY.js +25 -0
- package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-hOUOWbW2.js} +2 -2
- package/src/ui/dist/assets/{monaco-Cb2uKKe6.js → monaco-BGGAEii3.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DlEr1_y5.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-Bg72DGgT.js → popover-CWJbJuYY.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-CRJiucYO.js} +18 -77
- package/src/ui/dist/assets/select-CoHB7pvH.js +1690 -0
- package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-D5aJWR8J.js} +1 -1
- package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-DUK_mnkS.js} +2 -13
- package/src/ui/dist/assets/{trash-BvTgE5__.js → trash-ChU3SEE3.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-BrJBV3tY.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-C7Qqh-om.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.js → zoom-out-rtX0FKya.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
|
@@ -263,10 +263,7 @@ def _check_codex(config_manager: ConfigManager) -> dict[str, Any]:
|
|
|
263
263
|
ok=False,
|
|
264
264
|
summary="Codex CLI is not available to DeepScientist.",
|
|
265
265
|
errors=[f"Runner binary `{binary}` could not be resolved."],
|
|
266
|
-
guidance=
|
|
267
|
-
"Run `npm install -g @researai/deepscientist` again so the bundled Codex dependency is installed.",
|
|
268
|
-
"Then run `codex` once and complete login.",
|
|
269
|
-
],
|
|
266
|
+
guidance=config_manager._codex_missing_binary_guidance(codex_cfg),
|
|
270
267
|
details={"binary": binary},
|
|
271
268
|
)
|
|
272
269
|
|
|
@@ -285,7 +282,9 @@ def _check_codex(config_manager: ConfigManager) -> dict[str, Any]:
|
|
|
285
282
|
details={"resolved_binary": resolved_binary},
|
|
286
283
|
)
|
|
287
284
|
if not probe_guidance:
|
|
288
|
-
probe_guidance = [
|
|
285
|
+
probe_guidance = [
|
|
286
|
+
"Run `codex --login` (or `codex`) manually once and complete login, then retry `ds doctor`.",
|
|
287
|
+
]
|
|
289
288
|
return _make_check(
|
|
290
289
|
check_id="codex",
|
|
291
290
|
label="Codex CLI",
|
|
@@ -299,45 +299,41 @@ def _collect_branch_state(repo: Path) -> dict[str, dict[str, Any]]:
|
|
|
299
299
|
continue
|
|
300
300
|
state = branch_state[branch_name]
|
|
301
301
|
state.setdefault("branch", branch_name)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
state["
|
|
302
|
+
artifact_sort_key = _artifact_record_sort_key(record, path)
|
|
303
|
+
current_artifact_sort_key = state.get("_latest_artifact_sort_key")
|
|
304
|
+
if current_artifact_sort_key is None or artifact_sort_key > current_artifact_sort_key:
|
|
305
|
+
state["_latest_artifact_sort_key"] = artifact_sort_key
|
|
306
|
+
state["updated_at"] = record.get("updated_at") or record.get("created_at") or state.get("updated_at")
|
|
306
307
|
if record.get("idea_id"):
|
|
307
308
|
state["idea_id"] = record.get("idea_id")
|
|
308
309
|
if record.get("parent_branch"):
|
|
309
310
|
state["parent_branch"] = record.get("parent_branch")
|
|
310
311
|
if record.get("worktree_root"):
|
|
311
312
|
state["worktree_root"] = record.get("worktree_root")
|
|
312
|
-
|
|
313
|
+
resolved_run_result = _resolve_run_result_payload(repo, record)
|
|
314
|
+
latest_metric = extract_latest_metric(resolved_run_result if record.get("kind") == "run" else record)
|
|
313
315
|
if latest_metric is not None:
|
|
314
316
|
state["latest_metric"] = latest_metric
|
|
315
317
|
if record.get("kind") == "run":
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
"
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
"
|
|
329
|
-
"
|
|
330
|
-
"evaluation_summary": record.get("evaluation_summary")
|
|
331
|
-
or ((record.get("details") or {}) if isinstance(record.get("details"), dict) else {}).get("evaluation_summary")
|
|
332
|
-
or {},
|
|
333
|
-
"files_changed": record.get("files_changed") or [],
|
|
334
|
-
"evidence_paths": record.get("evidence_paths") or [],
|
|
335
|
-
"updated_at": record.get("updated_at"),
|
|
336
|
-
}
|
|
337
|
-
state["breakthrough"] = bool(((record.get("progress_eval") or {}).get("breakthrough")))
|
|
338
|
-
state["breakthrough_level"] = ((record.get("progress_eval") or {}).get("breakthrough_level"))
|
|
318
|
+
candidate_sort_key = _run_result_sort_key(record, resolved_run_result, path)
|
|
319
|
+
current_sort_key = state.get("_latest_result_sort_key")
|
|
320
|
+
if current_sort_key is None or candidate_sort_key > current_sort_key:
|
|
321
|
+
state["_latest_result_sort_key"] = candidate_sort_key
|
|
322
|
+
state["latest_result"] = resolved_run_result
|
|
323
|
+
state["run_id"] = resolved_run_result.get("run_id") or state.get("run_id")
|
|
324
|
+
state["run_kind"] = resolved_run_result.get("run_kind") or state.get("run_kind")
|
|
325
|
+
progress_eval = (
|
|
326
|
+
resolved_run_result.get("progress_eval")
|
|
327
|
+
if isinstance(resolved_run_result.get("progress_eval"), dict)
|
|
328
|
+
else {}
|
|
329
|
+
)
|
|
330
|
+
state["breakthrough"] = bool(progress_eval.get("breakthrough"))
|
|
331
|
+
state["breakthrough_level"] = progress_eval.get("breakthrough_level")
|
|
339
332
|
if record.get("summary") or record.get("message") or record.get("reason"):
|
|
340
|
-
|
|
333
|
+
current_summary_sort_key = state.get("_latest_summary_sort_key")
|
|
334
|
+
if current_summary_sort_key is None or artifact_sort_key > current_summary_sort_key:
|
|
335
|
+
state["_latest_summary_sort_key"] = artifact_sort_key
|
|
336
|
+
state["latest_summary"] = record.get("summary") or record.get("message") or record.get("reason")
|
|
341
337
|
state["recent_artifacts"].append(
|
|
342
338
|
{
|
|
343
339
|
"artifact_id": record.get("artifact_id"),
|
|
@@ -346,12 +342,107 @@ def _collect_branch_state(repo: Path) -> dict[str, dict[str, Any]]:
|
|
|
346
342
|
"reason": record.get("reason"),
|
|
347
343
|
"updated_at": record.get("updated_at"),
|
|
348
344
|
"status": record.get("status"),
|
|
345
|
+
"_sort_key": artifact_sort_key,
|
|
349
346
|
}
|
|
350
347
|
)
|
|
348
|
+
state["recent_artifacts"].sort(key=lambda item: item.get("_sort_key") or ("", 0, ""))
|
|
351
349
|
state["recent_artifacts"] = state["recent_artifacts"][-4:]
|
|
350
|
+
for state in branch_state.values():
|
|
351
|
+
latest_result = state.get("latest_result")
|
|
352
|
+
if isinstance(latest_result, dict):
|
|
353
|
+
result_metric = extract_latest_metric(latest_result)
|
|
354
|
+
if result_metric is not None:
|
|
355
|
+
state["latest_metric"] = result_metric
|
|
356
|
+
for item in state.get("recent_artifacts", []):
|
|
357
|
+
if isinstance(item, dict):
|
|
358
|
+
item.pop("_sort_key", None)
|
|
352
359
|
return branch_state
|
|
353
360
|
|
|
354
361
|
|
|
362
|
+
def _resolve_run_result_payload(repo: Path, record: dict[str, Any]) -> dict[str, Any]:
|
|
363
|
+
details = dict(record.get("details") or {}) if isinstance(record.get("details"), dict) else {}
|
|
364
|
+
paths = dict(record.get("paths") or {}) if isinstance(record.get("paths"), dict) else {}
|
|
365
|
+
result_payload: dict[str, Any] = {}
|
|
366
|
+
result_json_path = _resolve_result_json_path(repo, paths.get("result_json"))
|
|
367
|
+
if result_json_path and result_json_path.exists():
|
|
368
|
+
loaded = read_json(result_json_path, {})
|
|
369
|
+
if isinstance(loaded, dict):
|
|
370
|
+
result_payload = loaded
|
|
371
|
+
|
|
372
|
+
evaluation_summary = (
|
|
373
|
+
record.get("evaluation_summary")
|
|
374
|
+
or details.get("evaluation_summary")
|
|
375
|
+
or result_payload.get("evaluation_summary")
|
|
376
|
+
or {}
|
|
377
|
+
)
|
|
378
|
+
progress_eval = record.get("progress_eval")
|
|
379
|
+
if not isinstance(progress_eval, dict):
|
|
380
|
+
progress_eval = result_payload.get("progress_eval") if isinstance(result_payload.get("progress_eval"), dict) else {}
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
"run_id": record.get("run_id") or result_payload.get("run_id"),
|
|
384
|
+
"run_kind": record.get("run_kind") or result_payload.get("run_kind"),
|
|
385
|
+
"status": record.get("status") or result_payload.get("status"),
|
|
386
|
+
"summary": record.get("summary") or record.get("reason") or result_payload.get("summary"),
|
|
387
|
+
"verdict": record.get("verdict") or result_payload.get("verdict"),
|
|
388
|
+
"paths": paths or (result_payload.get("paths") if isinstance(result_payload.get("paths"), dict) else {}) or {},
|
|
389
|
+
"details": details,
|
|
390
|
+
"metrics_summary": record.get("metrics_summary") or result_payload.get("metrics_summary") or {},
|
|
391
|
+
"metric_rows": record.get("metric_rows") or result_payload.get("metric_rows") or [],
|
|
392
|
+
"metric_contract": record.get("metric_contract") or result_payload.get("metric_contract") or {},
|
|
393
|
+
"baseline_ref": record.get("baseline_ref") or result_payload.get("baseline_ref") or {},
|
|
394
|
+
"baseline_comparisons": record.get("baseline_comparisons") or result_payload.get("baseline_comparisons") or {},
|
|
395
|
+
"progress_eval": progress_eval or {},
|
|
396
|
+
"evaluation_summary": evaluation_summary or {},
|
|
397
|
+
"files_changed": record.get("files_changed") or result_payload.get("files_changed") or [],
|
|
398
|
+
"evidence_paths": record.get("evidence_paths") or result_payload.get("evidence_paths") or [],
|
|
399
|
+
"updated_at": record.get("updated_at") or result_payload.get("updated_at"),
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _resolve_result_json_path(repo: Path, raw_path: object) -> Path | None:
|
|
404
|
+
normalized = str(raw_path or "").strip()
|
|
405
|
+
if not normalized:
|
|
406
|
+
return None
|
|
407
|
+
candidate = Path(normalized).expanduser()
|
|
408
|
+
if candidate.is_absolute():
|
|
409
|
+
return candidate
|
|
410
|
+
return repo / candidate
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _artifact_record_sort_key(record: dict[str, Any], path: Path) -> tuple[str, int, str]:
|
|
414
|
+
updated_at = str(record.get("updated_at") or record.get("created_at") or "").strip()
|
|
415
|
+
try:
|
|
416
|
+
mtime_ns = path.stat().st_mtime_ns
|
|
417
|
+
except OSError:
|
|
418
|
+
mtime_ns = 0
|
|
419
|
+
return (updated_at, mtime_ns, str(path))
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _run_result_sort_key(record: dict[str, Any], payload: dict[str, Any], path: Path) -> tuple[int, str, int, str]:
|
|
423
|
+
quality = 0
|
|
424
|
+
if extract_latest_metric(payload):
|
|
425
|
+
quality += 8
|
|
426
|
+
if payload.get("baseline_comparisons"):
|
|
427
|
+
quality += 4
|
|
428
|
+
if payload.get("progress_eval"):
|
|
429
|
+
quality += 4
|
|
430
|
+
if payload.get("metrics_summary"):
|
|
431
|
+
quality += 3
|
|
432
|
+
if payload.get("metric_rows"):
|
|
433
|
+
quality += 3
|
|
434
|
+
if payload.get("verdict"):
|
|
435
|
+
quality += 2
|
|
436
|
+
paths = payload.get("paths") if isinstance(payload.get("paths"), dict) else {}
|
|
437
|
+
if paths.get("result_json"):
|
|
438
|
+
quality += 2
|
|
439
|
+
if paths.get("run_md"):
|
|
440
|
+
quality += 1
|
|
441
|
+
updated_at = str(payload.get("updated_at") or record.get("updated_at") or record.get("created_at") or "")
|
|
442
|
+
_, mtime_ns, path_str = _artifact_record_sort_key(record, path)
|
|
443
|
+
return (quality, updated_at, mtime_ns, path_str)
|
|
444
|
+
|
|
445
|
+
|
|
355
446
|
def _classify_ref(ref: str, state: dict[str, Any]) -> dict[str, str]:
|
|
356
447
|
run_id = str(state.get("run_id") or "")
|
|
357
448
|
run_kind = str(state.get("run_kind") or "")
|
|
@@ -1,182 +1 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import secrets
|
|
5
|
-
from typing import Any
|
|
6
|
-
from urllib.parse import urlparse
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
DEFAULT_LINGZHU_GATEWAY_PORT = 18789
|
|
10
|
-
DEFAULT_LINGZHU_LOCAL_HOST = "127.0.0.1"
|
|
11
|
-
DEFAULT_LINGZHU_AGENT_ID = "main"
|
|
12
|
-
DEFAULT_LINGZHU_SESSION_NAMESPACE = "lingzhu"
|
|
13
|
-
|
|
14
|
-
_AUTH_AK_SEGMENTS = (8, 4, 4, 4, 12)
|
|
15
|
-
_AUTH_AK_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def generate_lingzhu_auth_ak() -> str:
|
|
19
|
-
parts: list[str] = []
|
|
20
|
-
for segment_length in _AUTH_AK_SEGMENTS:
|
|
21
|
-
parts.append("".join(secrets.choice(_AUTH_AK_CHARS) for _ in range(segment_length)))
|
|
22
|
-
return "-".join(parts)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def lingzhu_local_host(config: dict[str, Any] | None) -> str:
|
|
26
|
-
value = str((config or {}).get("local_host") or DEFAULT_LINGZHU_LOCAL_HOST).strip()
|
|
27
|
-
return value or DEFAULT_LINGZHU_LOCAL_HOST
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def lingzhu_gateway_port(config: dict[str, Any] | None) -> int:
|
|
31
|
-
raw = (config or {}).get("gateway_port")
|
|
32
|
-
try:
|
|
33
|
-
value = int(raw)
|
|
34
|
-
except (TypeError, ValueError):
|
|
35
|
-
return DEFAULT_LINGZHU_GATEWAY_PORT
|
|
36
|
-
if value < 1 or value > 65535:
|
|
37
|
-
return DEFAULT_LINGZHU_GATEWAY_PORT
|
|
38
|
-
return value
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def normalize_public_base_url(value: Any) -> str | None:
|
|
42
|
-
text = str(value or "").strip()
|
|
43
|
-
if not text:
|
|
44
|
-
return None
|
|
45
|
-
parsed = urlparse(text)
|
|
46
|
-
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
47
|
-
return None
|
|
48
|
-
return text.rstrip("/")
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def lingzhu_local_base_url(config: dict[str, Any] | None) -> str:
|
|
52
|
-
return f"http://{lingzhu_local_host(config)}:{lingzhu_gateway_port(config)}"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def lingzhu_public_base_url(config: dict[str, Any] | None) -> str | None:
|
|
56
|
-
return normalize_public_base_url((config or {}).get("public_base_url"))
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def lingzhu_health_url(config: dict[str, Any] | None, *, public: bool = False) -> str | None:
|
|
60
|
-
base = lingzhu_public_base_url(config) if public else lingzhu_local_base_url(config)
|
|
61
|
-
if not base:
|
|
62
|
-
return None
|
|
63
|
-
return f"{base}/metis/agent/api/health"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def lingzhu_sse_url(config: dict[str, Any] | None, *, public: bool = False) -> str | None:
|
|
67
|
-
base = lingzhu_public_base_url(config) if public else lingzhu_local_base_url(config)
|
|
68
|
-
if not base:
|
|
69
|
-
return None
|
|
70
|
-
return f"{base}/metis/agent/api/sse"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def lingzhu_agent_id(config: dict[str, Any] | None) -> str:
|
|
74
|
-
value = str((config or {}).get("agent_id") or DEFAULT_LINGZHU_AGENT_ID).strip()
|
|
75
|
-
return value or DEFAULT_LINGZHU_AGENT_ID
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def lingzhu_probe_payload(
|
|
79
|
-
config: dict[str, Any] | None,
|
|
80
|
-
*,
|
|
81
|
-
message_id: str = "ds-lingzhu-probe-001",
|
|
82
|
-
text: str = "你好",
|
|
83
|
-
) -> dict[str, Any]:
|
|
84
|
-
return {
|
|
85
|
-
"message_id": message_id,
|
|
86
|
-
"agent_id": lingzhu_agent_id(config),
|
|
87
|
-
"message": [
|
|
88
|
-
{
|
|
89
|
-
"role": "user",
|
|
90
|
-
"type": "text",
|
|
91
|
-
"text": text,
|
|
92
|
-
}
|
|
93
|
-
],
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def lingzhu_generated_openclaw_config(config: dict[str, Any] | None) -> dict[str, Any]:
|
|
98
|
-
resolved = dict(config or {})
|
|
99
|
-
return {
|
|
100
|
-
"gateway": {
|
|
101
|
-
"port": lingzhu_gateway_port(resolved),
|
|
102
|
-
"http": {
|
|
103
|
-
"endpoints": {
|
|
104
|
-
"chatCompletions": {
|
|
105
|
-
"enabled": True,
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
"plugins": {
|
|
111
|
-
"entries": {
|
|
112
|
-
"lingzhu": {
|
|
113
|
-
"enabled": bool(resolved.get("enabled", False)),
|
|
114
|
-
"config": {
|
|
115
|
-
"authAk": str(resolved.get("auth_ak") or "").strip(),
|
|
116
|
-
"agentId": lingzhu_agent_id(resolved),
|
|
117
|
-
"includeMetadata": bool(resolved.get("include_metadata", True)),
|
|
118
|
-
"requestTimeoutMs": int(resolved.get("request_timeout_ms") or 60000),
|
|
119
|
-
"systemPrompt": str(resolved.get("system_prompt") or ""),
|
|
120
|
-
"defaultNavigationMode": str(resolved.get("default_navigation_mode") or "0"),
|
|
121
|
-
"enableFollowUp": bool(resolved.get("enable_follow_up", True)),
|
|
122
|
-
"followUpMaxCount": int(resolved.get("follow_up_max_count") or 3),
|
|
123
|
-
"maxImageBytes": int(resolved.get("max_image_bytes") or 5 * 1024 * 1024),
|
|
124
|
-
"sessionMode": str(resolved.get("session_mode") or "per_user"),
|
|
125
|
-
"sessionNamespace": str(
|
|
126
|
-
resolved.get("session_namespace") or DEFAULT_LINGZHU_SESSION_NAMESPACE
|
|
127
|
-
),
|
|
128
|
-
"autoReceiptAck": bool(resolved.get("auto_receipt_ack", True)),
|
|
129
|
-
"visibleProgressHeartbeat": bool(
|
|
130
|
-
resolved.get("visible_progress_heartbeat", True)
|
|
131
|
-
),
|
|
132
|
-
"visibleProgressHeartbeatSec": int(
|
|
133
|
-
resolved.get("visible_progress_heartbeat_sec") or 10
|
|
134
|
-
),
|
|
135
|
-
"debugLogging": bool(resolved.get("debug_logging", False)),
|
|
136
|
-
"debugLogPayloads": bool(resolved.get("debug_log_payloads", False)),
|
|
137
|
-
"debugLogDir": str(resolved.get("debug_log_dir") or ""),
|
|
138
|
-
"enableExperimentalNativeActions": bool(
|
|
139
|
-
resolved.get("enable_experimental_native_actions", False)
|
|
140
|
-
),
|
|
141
|
-
},
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def lingzhu_generated_openclaw_config_text(config: dict[str, Any] | None) -> str:
|
|
149
|
-
return json.dumps(lingzhu_generated_openclaw_config(config), indent=2, ensure_ascii=False)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def lingzhu_generated_curl(config: dict[str, Any] | None, *, text: str = "你好") -> str:
|
|
153
|
-
auth_ak = str((config or {}).get("auth_ak") or "").strip()
|
|
154
|
-
payload = lingzhu_probe_payload(config, text=text)
|
|
155
|
-
endpoint_url = lingzhu_sse_url(config) or ""
|
|
156
|
-
return (
|
|
157
|
-
f"curl -X POST '{endpoint_url}' \\\n"
|
|
158
|
-
f" --header 'Authorization: Bearer {auth_ak}' \\\n"
|
|
159
|
-
" --header 'Content-Type: application/json' \\\n"
|
|
160
|
-
f" --data '{json.dumps(payload, ensure_ascii=False)}'"
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def lingzhu_supported_commands(*, experimental_enabled: bool) -> list[str]:
|
|
165
|
-
commands = [
|
|
166
|
-
"take_photo",
|
|
167
|
-
"take_navigation",
|
|
168
|
-
"control_calendar",
|
|
169
|
-
"notify_agent_off",
|
|
170
|
-
]
|
|
171
|
-
if experimental_enabled:
|
|
172
|
-
commands.extend(
|
|
173
|
-
[
|
|
174
|
-
"send_notification",
|
|
175
|
-
"send_toast",
|
|
176
|
-
"speak_tts",
|
|
177
|
-
"start_video_record",
|
|
178
|
-
"stop_video_record",
|
|
179
|
-
"open_custom_view",
|
|
180
|
-
]
|
|
181
|
-
)
|
|
182
|
-
return commands
|
|
1
|
+
from .connector.lingzhu_support import * # noqa: F401,F403
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
from collections import deque
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
from mcp.server.fastmcp import FastMCP
|
|
@@ -95,6 +97,125 @@ def _build_default_bash_log_payload(log_text: str) -> dict[str, Any]:
|
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
|
|
100
|
+
def _stream_bash_log_summary(path: Path) -> tuple[list[str], int, list[str]]:
|
|
101
|
+
total = 0
|
|
102
|
+
full_lines: list[str] = []
|
|
103
|
+
head_lines: list[str] = []
|
|
104
|
+
tail_lines: deque[str] = deque(maxlen=DEFAULT_INLINE_BASH_LOG_TAIL_LINES)
|
|
105
|
+
with path.open("r", encoding="utf-8", errors="replace") as handle:
|
|
106
|
+
for raw_line in handle:
|
|
107
|
+
line = raw_line.rstrip("\n")
|
|
108
|
+
total += 1
|
|
109
|
+
if total <= DEFAULT_INLINE_BASH_LOG_LINE_LIMIT:
|
|
110
|
+
full_lines.append(line)
|
|
111
|
+
continue
|
|
112
|
+
if total == DEFAULT_INLINE_BASH_LOG_LINE_LIMIT + 1:
|
|
113
|
+
head_lines = full_lines[:DEFAULT_INLINE_BASH_LOG_HEAD_LINES]
|
|
114
|
+
tail_lines.extend(full_lines[-DEFAULT_INLINE_BASH_LOG_TAIL_LINES :])
|
|
115
|
+
full_lines = []
|
|
116
|
+
tail_lines.append(line)
|
|
117
|
+
return full_lines, total, list(head_lines or tail_lines)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _build_default_bash_log_payload_from_path(path: Path) -> dict[str, Any]:
|
|
121
|
+
if not path.exists():
|
|
122
|
+
return {
|
|
123
|
+
"log": "",
|
|
124
|
+
"log_line_count": 0,
|
|
125
|
+
"log_truncated": False,
|
|
126
|
+
}
|
|
127
|
+
full_lines, total, preview_seed = _stream_bash_log_summary(path)
|
|
128
|
+
if total <= DEFAULT_INLINE_BASH_LOG_LINE_LIMIT:
|
|
129
|
+
return {
|
|
130
|
+
"log": _join_bash_log_lines(full_lines),
|
|
131
|
+
"log_line_count": total,
|
|
132
|
+
"log_truncated": False,
|
|
133
|
+
}
|
|
134
|
+
with path.open("r", encoding="utf-8", errors="replace") as handle:
|
|
135
|
+
tail_lines: deque[str] = deque(maxlen=DEFAULT_INLINE_BASH_LOG_TAIL_LINES)
|
|
136
|
+
for raw_line in handle:
|
|
137
|
+
tail_lines.append(raw_line.rstrip("\n"))
|
|
138
|
+
omitted = total - DEFAULT_INLINE_BASH_LOG_HEAD_LINES - DEFAULT_INLINE_BASH_LOG_TAIL_LINES
|
|
139
|
+
marker = (
|
|
140
|
+
f"[... omitted {omitted} lines from the middle of this log. {LONG_BASH_LOG_HINT}]"
|
|
141
|
+
)
|
|
142
|
+
preview_lines = preview_seed[:DEFAULT_INLINE_BASH_LOG_HEAD_LINES] + [marker] + list(tail_lines)
|
|
143
|
+
return {
|
|
144
|
+
"log": _join_bash_log_lines(preview_lines),
|
|
145
|
+
"log_line_count": total,
|
|
146
|
+
"log_truncated": True,
|
|
147
|
+
"log_preview_head_lines": DEFAULT_INLINE_BASH_LOG_HEAD_LINES,
|
|
148
|
+
"log_preview_tail_lines": DEFAULT_INLINE_BASH_LOG_TAIL_LINES,
|
|
149
|
+
"log_preview_omitted_lines": omitted,
|
|
150
|
+
"log_read_hint": LONG_BASH_LOG_HINT,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _build_bash_log_window_from_path(path: Path, *, start: int | None = None, tail: int | None = None) -> dict[str, Any]:
|
|
155
|
+
if not path.exists():
|
|
156
|
+
return {
|
|
157
|
+
"log": "",
|
|
158
|
+
"log_line_count": 0,
|
|
159
|
+
"log_windowed": True,
|
|
160
|
+
"line_start": 1,
|
|
161
|
+
"line_end": 0,
|
|
162
|
+
"line_limit": _normalize_bash_log_window_size(tail),
|
|
163
|
+
"returned_line_count": 0,
|
|
164
|
+
"has_more_before": False,
|
|
165
|
+
"has_more_after": False,
|
|
166
|
+
"log_read_hint": LONG_BASH_LOG_HINT,
|
|
167
|
+
}
|
|
168
|
+
line_limit = _normalize_bash_log_window_size(tail)
|
|
169
|
+
if start is not None:
|
|
170
|
+
requested_start = max(1, int(start))
|
|
171
|
+
selected: list[str] = []
|
|
172
|
+
total = 0
|
|
173
|
+
with path.open("r", encoding="utf-8", errors="replace") as handle:
|
|
174
|
+
for raw_line in handle:
|
|
175
|
+
total += 1
|
|
176
|
+
if total < requested_start:
|
|
177
|
+
continue
|
|
178
|
+
if len(selected) < line_limit:
|
|
179
|
+
selected.append(raw_line.rstrip("\n"))
|
|
180
|
+
returned_count = len(selected)
|
|
181
|
+
line_start = requested_start if total else 1
|
|
182
|
+
line_end = requested_start + returned_count - 1 if returned_count else requested_start - 1
|
|
183
|
+
return {
|
|
184
|
+
"log": _join_bash_log_lines(selected),
|
|
185
|
+
"log_line_count": total,
|
|
186
|
+
"log_windowed": True,
|
|
187
|
+
"line_start": line_start,
|
|
188
|
+
"line_end": line_end,
|
|
189
|
+
"line_limit": line_limit,
|
|
190
|
+
"returned_line_count": returned_count,
|
|
191
|
+
"has_more_before": line_start > 1,
|
|
192
|
+
"has_more_after": line_end < total,
|
|
193
|
+
"log_read_hint": LONG_BASH_LOG_HINT,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
tail_lines: deque[str] = deque(maxlen=line_limit)
|
|
197
|
+
total = 0
|
|
198
|
+
with path.open("r", encoding="utf-8", errors="replace") as handle:
|
|
199
|
+
for raw_line in handle:
|
|
200
|
+
total += 1
|
|
201
|
+
tail_lines.append(raw_line.rstrip("\n"))
|
|
202
|
+
returned_count = len(tail_lines)
|
|
203
|
+
line_start = max(1, total - returned_count + 1) if total else 1
|
|
204
|
+
line_end = total
|
|
205
|
+
return {
|
|
206
|
+
"log": _join_bash_log_lines(list(tail_lines)),
|
|
207
|
+
"log_line_count": total,
|
|
208
|
+
"log_windowed": True,
|
|
209
|
+
"line_start": line_start,
|
|
210
|
+
"line_end": line_end,
|
|
211
|
+
"line_limit": line_limit,
|
|
212
|
+
"returned_line_count": returned_count,
|
|
213
|
+
"has_more_before": line_start > 1,
|
|
214
|
+
"has_more_after": False,
|
|
215
|
+
"log_read_hint": LONG_BASH_LOG_HINT,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
98
219
|
def build_memory_server(context: McpContext) -> FastMCP:
|
|
99
220
|
service = MemoryService(context.home)
|
|
100
221
|
server = FastMCP(
|
|
@@ -680,16 +801,23 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
680
801
|
@server.tool(
|
|
681
802
|
name="arxiv",
|
|
682
803
|
description=(
|
|
683
|
-
"
|
|
684
|
-
"Use
|
|
804
|
+
"Interact with the quest-local arXiv library. "
|
|
805
|
+
"Use mode='read' to read one paper by id with local-first automatic persistence, "
|
|
806
|
+
"or mode='list' to list the saved arXiv items for the current quest."
|
|
685
807
|
),
|
|
686
808
|
)
|
|
687
809
|
def arxiv(
|
|
688
|
-
paper_id: str,
|
|
810
|
+
paper_id: str | None = None,
|
|
811
|
+
mode: str = "read",
|
|
689
812
|
full_text: bool = False,
|
|
690
813
|
comment: str | dict[str, Any] | None = None,
|
|
691
814
|
) -> dict[str, Any]:
|
|
692
|
-
return service.arxiv(
|
|
815
|
+
return service.arxiv(
|
|
816
|
+
paper_id,
|
|
817
|
+
mode=mode,
|
|
818
|
+
full_text=full_text,
|
|
819
|
+
quest_root=context.require_quest_root(),
|
|
820
|
+
)
|
|
693
821
|
|
|
694
822
|
@server.tool(name="refresh_summary", description="Refresh SUMMARY.md from recent artifact state.")
|
|
695
823
|
def refresh_summary(
|
|
@@ -873,8 +1001,8 @@ def build_bash_exec_server(context: McpContext) -> FastMCP:
|
|
|
873
1001
|
export_log_to=export_log_to,
|
|
874
1002
|
)
|
|
875
1003
|
payload.update(
|
|
876
|
-
|
|
877
|
-
service.
|
|
1004
|
+
_build_bash_log_window_from_path(
|
|
1005
|
+
service.terminal_log_path(quest_root, bash_id),
|
|
878
1006
|
start=start,
|
|
879
1007
|
tail=tail if tail is not None else tail_limit,
|
|
880
1008
|
)
|
|
@@ -914,7 +1042,7 @@ def build_bash_exec_server(context: McpContext) -> FastMCP:
|
|
|
914
1042
|
export_log=export_log,
|
|
915
1043
|
export_log_to=export_log_to,
|
|
916
1044
|
)
|
|
917
|
-
payload.update(
|
|
1045
|
+
payload.update(_build_default_bash_log_payload_from_path(service.terminal_log_path(quest_root, bash_id)))
|
|
918
1046
|
return payload
|
|
919
1047
|
if normalized_mode == "kill":
|
|
920
1048
|
bash_id = service.resolve_session_id(quest_root, id)
|