@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
|
@@ -265,7 +265,8 @@ def _check_codex(config_manager: ConfigManager) -> dict[str, Any]:
|
|
|
265
265
|
errors=[f"Runner binary `{binary}` could not be resolved."],
|
|
266
266
|
guidance=[
|
|
267
267
|
"Run `npm install -g @researai/deepscientist` again so the bundled Codex dependency is installed.",
|
|
268
|
-
"
|
|
268
|
+
"If `codex` is still missing, install it explicitly with `npm install -g @openai/codex`.",
|
|
269
|
+
"Then run `codex --login` (or `codex`) once and complete login.",
|
|
269
270
|
],
|
|
270
271
|
details={"binary": binary},
|
|
271
272
|
)
|
|
@@ -285,7 +286,9 @@ def _check_codex(config_manager: ConfigManager) -> dict[str, Any]:
|
|
|
285
286
|
details={"resolved_binary": resolved_binary},
|
|
286
287
|
)
|
|
287
288
|
if not probe_guidance:
|
|
288
|
-
probe_guidance = [
|
|
289
|
+
probe_guidance = [
|
|
290
|
+
"Run `codex --login` (or `codex`) manually once and complete login, then retry `ds doctor`.",
|
|
291
|
+
]
|
|
289
292
|
return _make_check(
|
|
290
293
|
check_id="codex",
|
|
291
294
|
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
|
|
@@ -471,7 +471,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
471
471
|
context.require_quest_root(),
|
|
472
472
|
campaign_title=campaign_title,
|
|
473
473
|
campaign_goal=campaign_goal,
|
|
474
|
-
parent_run_id=parent_run_id
|
|
474
|
+
parent_run_id=parent_run_id,
|
|
475
475
|
slices=slices,
|
|
476
476
|
campaign_origin=campaign_origin,
|
|
477
477
|
selected_outline_ref=selected_outline_ref,
|
|
@@ -636,6 +636,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
636
636
|
summary: str | None = None,
|
|
637
637
|
baseline_kind: str | None = None,
|
|
638
638
|
metric_contract: dict[str, Any] | None = None,
|
|
639
|
+
metric_directions: dict[str, str] | None = None,
|
|
639
640
|
metrics_summary: dict[str, Any] | None = None,
|
|
640
641
|
primary_metric: dict[str, Any] | None = None,
|
|
641
642
|
auto_advance: bool = True,
|
|
@@ -651,6 +652,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
651
652
|
summary=summary,
|
|
652
653
|
baseline_kind=baseline_kind,
|
|
653
654
|
metric_contract=metric_contract,
|
|
655
|
+
metric_directions=metric_directions,
|
|
654
656
|
metrics_summary=metrics_summary,
|
|
655
657
|
primary_metric=primary_metric,
|
|
656
658
|
auto_advance=auto_advance,
|
|
@@ -678,16 +680,23 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
678
680
|
@server.tool(
|
|
679
681
|
name="arxiv",
|
|
680
682
|
description=(
|
|
681
|
-
"
|
|
682
|
-
"Use
|
|
683
|
+
"Interact with the quest-local arXiv library. "
|
|
684
|
+
"Use mode='read' to read one paper by id with local-first automatic persistence, "
|
|
685
|
+
"or mode='list' to list the saved arXiv items for the current quest."
|
|
683
686
|
),
|
|
684
687
|
)
|
|
685
688
|
def arxiv(
|
|
686
|
-
paper_id: str,
|
|
689
|
+
paper_id: str | None = None,
|
|
690
|
+
mode: str = "read",
|
|
687
691
|
full_text: bool = False,
|
|
688
692
|
comment: str | dict[str, Any] | None = None,
|
|
689
693
|
) -> dict[str, Any]:
|
|
690
|
-
return service.arxiv(
|
|
694
|
+
return service.arxiv(
|
|
695
|
+
paper_id,
|
|
696
|
+
mode=mode,
|
|
697
|
+
full_text=full_text,
|
|
698
|
+
quest_root=context.require_quest_root(),
|
|
699
|
+
)
|
|
691
700
|
|
|
692
701
|
@server.tool(name="refresh_summary", description="Refresh SUMMARY.md from recent artifact state.")
|
|
693
702
|
def refresh_summary(
|
|
@@ -283,6 +283,21 @@ class PromptBuilder:
|
|
|
283
283
|
"- qq_structured_delivery_rule: when you want native QQ markdown or native QQ image/file delivery, request it through artifact.interact(connector_hints=..., attachments=[...]) instead of inventing connector-specific inline tag syntax.",
|
|
284
284
|
]
|
|
285
285
|
)
|
|
286
|
+
elif connector == "weixin":
|
|
287
|
+
lines.extend(
|
|
288
|
+
[
|
|
289
|
+
"- weixin_surface_rule: Weixin is a concise operator surface, not a full artifact browser.",
|
|
290
|
+
"- weixin_default_mode: keep outbound replies concise, respectful, text-first, and progress-aware.",
|
|
291
|
+
"- weixin_length_rule: for ordinary Weixin progress replies, normally use only 2 to 4 short sentences, or 3 very short bullets at most.",
|
|
292
|
+
"- weixin_summary_first_rule: start with the user-facing conclusion, then the immediate meaning, then the next action.",
|
|
293
|
+
"- weixin_progress_shape_rule: make the current task, the main difficulty or latest real progress, and the next concrete next step explicit whenever possible.",
|
|
294
|
+
"- weixin_eta_rule: for important long-running phases, include a rough ETA or next check-in window when it is helpful and defensible.",
|
|
295
|
+
"- weixin_internal_detail_rule: do not proactively dump file inventories, path lists, retry counters, or monitor-log style telemetry unless the user asked for them or they explain a real risk.",
|
|
296
|
+
"- weixin_context_token_rule: reply continuity is managed by the runtime through `context_token`; do not invent your own reply token scheme.",
|
|
297
|
+
"- weixin_media_rule: when you want native Weixin image, video, or file delivery, request it through artifact.interact(..., attachments=[...]) with `connector_delivery={'weixin': {'media_kind': ...}}` instead of inventing connector-specific inline tag syntax.",
|
|
298
|
+
"- weixin_inbound_media_rule: inbound Weixin image, video, and file messages can arrive as quest-local attachments under `userfiles/weixin/...`; read those files when the user sent media.",
|
|
299
|
+
]
|
|
300
|
+
)
|
|
286
301
|
else:
|
|
287
302
|
lines.append("- connector_media_rule: if the active surface is not QQ, keep using the general artifact interaction discipline for milestone delivery.")
|
|
288
303
|
|
|
@@ -687,6 +702,8 @@ class PromptBuilder:
|
|
|
687
702
|
f"- delivery_mode: {'paper_required' if need_research_paper else 'algorithm_first'}",
|
|
688
703
|
"- idea_stage_rule: every accepted idea submission should normally create a new branch/worktree and a new user-visible research node.",
|
|
689
704
|
"- idea_draft_rule: before `artifact.submit_idea(...)`, first finish a concise durable Markdown draft for the chosen route; keep `idea.md` compact and `draft.md` richer.",
|
|
705
|
+
"- idea_literature_floor_rule: before writing or submitting a final selected idea, durably survey at least 5 and usually 5 to 10 related and usable papers; prioritize direct task-modeling or mechanism-neighbor work and only backfill with the closest adjacent translatable papers when necessary.",
|
|
706
|
+
"- idea_reference_rule: the final selected-idea draft should use one consistent standard citation format and include a `References` or `Bibliography` section for the survey-stage papers that actually shaped the motivation, mechanism, or claim boundary.",
|
|
690
707
|
"- lineage_rule: normal idea routing uses exactly two lineage intents: `continue_line` creates a child of the current active branch; `branch_alternative` creates a sibling-like branch from the current branch's parent foundation.",
|
|
691
708
|
"- revise_rule: `artifact.submit_idea(mode='revise', ...)` is maintenance-only compatibility for the same branch and should not be the default research-route mechanism.",
|
|
692
709
|
"- post_main_result_rule: after every `artifact.record_main_experiment(...)`, first interpret the measured result and only then choose the next route.",
|
|
@@ -806,6 +823,8 @@ class PromptBuilder:
|
|
|
806
823
|
"- idea_why_now_protocol: every serious idea candidate should answer why now or what changed, not just what the mechanism is",
|
|
807
824
|
"- idea_balance_protocol: when the search space is not tiny, carry at least one conservative route and one higher-upside route into the final comparison",
|
|
808
825
|
"- idea_pitch_protocol: before artifact.submit_idea(...), make the winner pass a two-sentence pitch, a strongest-objection check, and a concrete why-now statement",
|
|
826
|
+
"- idea_literature_floor_protocol: do not write or submit the final selected idea until the durable survey covers at least 5 and usually 5 to 10 related and usable papers; if fewer than 5 direct papers exist, document the shortage and use the closest adjacent translatable work instead of skipping the gate",
|
|
827
|
+
"- idea_reference_protocol: the final selected-idea draft should cite the survey-stage papers it actually uses and end with a standard-format `References` or `Bibliography` section",
|
|
809
828
|
"- experiment_milestone_protocol: immediately after artifact.record_main_experiment(...) writes the durable result, send a threaded milestone that explains what was run, the main result, whether primary performance improved / worsened / stayed mixed versus the active baseline or best prior anchor, whether the route still looks promising, and the exact next step",
|
|
810
829
|
"- analysis_milestone_protocol: immediately after a meaningful completed analysis-campaign synthesis or route-significant campaign checkpoint, send a threaded milestone that explains which campaign question or slice set just closed, whether the claim boundary became stronger / weaker / mixed, the main caveat, and the exact next route",
|
|
811
830
|
"- paper_milestone_protocol: immediately after a meaningful paper or draft milestone such as selected outline, evidence-complete draft, major revision package, or bundle-ready paper, send a threaded milestone that explains what document milestone is now complete, which claims are now supportable, what still needs strengthening, and the exact next revision or execution route",
|
|
@@ -952,7 +971,16 @@ class PromptBuilder:
|
|
|
952
971
|
attachment_root = active_workspace_root / "baselines" / "imported"
|
|
953
972
|
if attachment_root.exists():
|
|
954
973
|
attachments = [read_yaml(path, {}) for path in sorted(attachment_root.glob("*/attachment.yaml"))]
|
|
955
|
-
attachments = [
|
|
974
|
+
attachments = [
|
|
975
|
+
item
|
|
976
|
+
for item in attachments
|
|
977
|
+
if isinstance(item, dict)
|
|
978
|
+
and item
|
|
979
|
+
and (
|
|
980
|
+
not str(item.get("source_baseline_id") or "").strip()
|
|
981
|
+
or not self.baseline_registry.is_deleted(str(item.get("source_baseline_id") or "").strip())
|
|
982
|
+
)
|
|
983
|
+
]
|
|
956
984
|
if attachments:
|
|
957
985
|
attachment = max(
|
|
958
986
|
attachments,
|