@researai/deepscientist 1.5.9 → 1.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -94
- package/assets/branding/connector-qq.png +0 -0
- package/assets/branding/connector-rokid.png +0 -0
- package/assets/branding/connector-weixin.png +0 -0
- package/assets/branding/projects.png +0 -0
- package/bin/ds.js +168 -9
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +308 -70
- package/docs/en/01_SETTINGS_REFERENCE.md +3 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +112 -0
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +41 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
- package/docs/en/11_LICENSE_AND_RISK.md +256 -0
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +427 -0
- package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +79 -0
- package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
- package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
- package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
- package/docs/images/weixin/weixin-settings-bind.svg +57 -0
- package/docs/zh/00_QUICK_START.md +315 -74
- package/docs/zh/01_SETTINGS_REFERENCE.md +3 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +112 -0
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +41 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
- package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +423 -0
- package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +126 -0
- package/install.sh +0 -34
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/annotations.py +343 -0
- package/src/deepscientist/artifact/arxiv.py +484 -37
- package/src/deepscientist/artifact/service.py +574 -108
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/service.py +9 -0
- package/src/deepscientist/bridges/builtins.py +2 -0
- package/src/deepscientist/bridges/connectors.py +447 -0
- package/src/deepscientist/channels/__init__.py +2 -0
- package/src/deepscientist/channels/builtins.py +3 -1
- package/src/deepscientist/channels/qq.py +1 -1
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +7 -1
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +317 -0
- package/src/deepscientist/config/models.py +22 -2
- package/src/deepscientist/config/service.py +431 -60
- package/src/deepscientist/connector/__init__.py +4 -0
- package/src/deepscientist/connector/connector_profiles.py +481 -0
- package/src/deepscientist/connector/lingzhu_support.py +668 -0
- package/src/deepscientist/connector/qq_profiles.py +206 -0
- package/src/deepscientist/connector/weixin_support.py +663 -0
- package/src/deepscientist/connector_profiles.py +1 -374
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +165 -5
- package/src/deepscientist/daemon/api/router.py +13 -1
- package/src/deepscientist/daemon/app.py +1130 -61
- package/src/deepscientist/doctor.py +5 -2
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +11 -4
- package/src/deepscientist/prompts/builder.py +15 -0
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +23 -0
- package/src/deepscientist/quest/service.py +112 -43
- package/src/deepscientist/quest/stage_views.py +71 -5
- package/src/deepscientist/runners/codex.py +55 -3
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/weixin.md +230 -0
- package/src/prompts/system.md +2 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-D0mTXG4-.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-Db0cTXxm.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-DrV8je02.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-QXMSCH71.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-7hhtWj_E.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-BWMSnRJe.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-7J9h9Vy_.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-CHJl_0lr.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-1qSow1es.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-eQpPPCEp.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BwRfi89Z.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-836PVQWV.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-C2y_556i.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BRzJbGsn.js} +12 -12
- package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-DIX7Mlzu.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-DzRaTAlq.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DZUfIUnp.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-BwtICzue.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DHeIAMsx.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C3tCmFox.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.js → VNCViewer-CQsKVm3t.js} +10 -10
- package/src/ui/dist/assets/bot-BEA2vWuK.js +21 -0
- package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
- package/src/ui/dist/assets/{code-BWAY76JP.js → code-XfbSR8K2.js} +1 -1
- package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-BjxNaIfy.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-D_lLVQk0.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-D9x_5vlY.js} +1 -1
- package/src/ui/dist/assets/{image-D-NZM-6P.js → image-BhWT33W1.js} +1 -1
- package/src/ui/dist/assets/{index-DHZJ_0TI.js → index--c4iXtuy.js} +12 -12
- package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-BDxipwrC.js} +2 -2
- package/src/ui/dist/assets/{index-7Chr1g9c.js → index-DZTZ8mWP.js} +14221 -9523
- package/src/ui/dist/assets/{index-DGIYDuTv.css → index-Dqj-Mjb4.css} +2 -13
- package/src/ui/dist/assets/index-PJbSbPTy.js +25 -0
- package/src/ui/dist/assets/{monaco-Cb2uKKe6.js → monaco-K8izTGgo.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DfBors6y.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-Bg72DGgT.js → popover-yFK1J4fL.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-PENr2zcz.js} +1 -74
- package/src/ui/dist/assets/select-CAbJDfYv.js +1690 -0
- package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-DEuYJqTl.js} +1 -1
- package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-omoSUmcd.js} +2 -13
- package/src/ui/dist/assets/{trash-BvTgE5__.js → trash--F119N47.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-D31UR23I.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-BH6KcMzq.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-CZ613PM5.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.js → zoom-out-BgDLAv3z.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
- package/src/ui/dist/assets/AutoFigurePlugin-C_wWw4AP.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-B0Dd8CxK.js +0 -158
- package/src/ui/dist/assets/bibtex-CKaefIN2.js +0 -189
- package/src/ui/dist/assets/file-utils-H2fjA46S.js +0 -109
- package/src/ui/dist/assets/message-square-BzjLiXir.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-C_mA6R0w.js +0 -108
|
@@ -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
|
|
@@ -680,16 +680,23 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
680
680
|
@server.tool(
|
|
681
681
|
name="arxiv",
|
|
682
682
|
description=(
|
|
683
|
-
"
|
|
684
|
-
"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."
|
|
685
686
|
),
|
|
686
687
|
)
|
|
687
688
|
def arxiv(
|
|
688
|
-
paper_id: str,
|
|
689
|
+
paper_id: str | None = None,
|
|
690
|
+
mode: str = "read",
|
|
689
691
|
full_text: bool = False,
|
|
690
692
|
comment: str | dict[str, Any] | None = None,
|
|
691
693
|
) -> dict[str, Any]:
|
|
692
|
-
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
|
+
)
|
|
693
700
|
|
|
694
701
|
@server.tool(name="refresh_summary", description="Refresh SUMMARY.md from recent artifact state.")
|
|
695
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
|
|
|
@@ -1,196 +1 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from copy import deepcopy
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from .shared import slugify
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
QQ_PROFILE_ID_PREFIX = "qq-profile"
|
|
10
|
-
def default_qq_profile() -> dict[str, Any]:
|
|
11
|
-
return {
|
|
12
|
-
"profile_id": None,
|
|
13
|
-
"enabled": True,
|
|
14
|
-
"app_id": None,
|
|
15
|
-
"app_secret": None,
|
|
16
|
-
"app_secret_env": None,
|
|
17
|
-
"bot_name": "DeepScientist",
|
|
18
|
-
"main_chat_id": None,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _as_text(value: Any) -> str | None:
|
|
23
|
-
text = str(value or "").strip()
|
|
24
|
-
return text or None
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _normalize_secret_pair(payload: dict[str, Any], direct_key: str, env_key: str) -> None:
|
|
28
|
-
direct = _as_text(payload.get(direct_key))
|
|
29
|
-
env_name = _as_text(payload.get(env_key))
|
|
30
|
-
payload[direct_key] = direct
|
|
31
|
-
payload[env_key] = None if direct else env_name
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _profile_id_seed(*, profile_id: Any, app_id: Any, bot_name: Any, index: int) -> str:
|
|
35
|
-
explicit = _as_text(profile_id)
|
|
36
|
-
if explicit:
|
|
37
|
-
return explicit
|
|
38
|
-
app_text = _as_text(app_id)
|
|
39
|
-
if app_text:
|
|
40
|
-
return f"qq-{app_text}"
|
|
41
|
-
bot_text = slugify(str(bot_name or "").strip(), default="")
|
|
42
|
-
if bot_text:
|
|
43
|
-
return f"{QQ_PROFILE_ID_PREFIX}-{bot_text}"
|
|
44
|
-
return f"{QQ_PROFILE_ID_PREFIX}-{index:03d}"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _unique_profile_id(seed: str, *, used: set[str]) -> str:
|
|
48
|
-
base = slugify(seed, default=QQ_PROFILE_ID_PREFIX)
|
|
49
|
-
candidate = base
|
|
50
|
-
suffix = 2
|
|
51
|
-
while candidate in used:
|
|
52
|
-
candidate = f"{base}-{suffix}"
|
|
53
|
-
suffix += 1
|
|
54
|
-
used.add(candidate)
|
|
55
|
-
return candidate
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def list_qq_profiles(config: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
59
|
-
normalized = normalize_qq_connector_config(config)
|
|
60
|
-
profiles = normalized.get("profiles")
|
|
61
|
-
return [dict(item) for item in profiles] if isinstance(profiles, list) else []
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def find_qq_profile(
|
|
65
|
-
config: dict[str, Any] | None,
|
|
66
|
-
*,
|
|
67
|
-
profile_id: str | None = None,
|
|
68
|
-
app_id: str | None = None,
|
|
69
|
-
) -> dict[str, Any] | None:
|
|
70
|
-
normalized_profile_id = _as_text(profile_id)
|
|
71
|
-
normalized_app_id = _as_text(app_id)
|
|
72
|
-
for profile in list_qq_profiles(config):
|
|
73
|
-
if normalized_profile_id and str(profile.get("profile_id") or "").strip() == normalized_profile_id:
|
|
74
|
-
return profile
|
|
75
|
-
if normalized_app_id and str(profile.get("app_id") or "").strip() == normalized_app_id:
|
|
76
|
-
return profile
|
|
77
|
-
return None
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def merge_qq_profile_config(shared_config: dict[str, Any] | None, profile: dict[str, Any]) -> dict[str, Any]:
|
|
81
|
-
normalized = normalize_qq_connector_config(shared_config)
|
|
82
|
-
merged = deepcopy(normalized)
|
|
83
|
-
merged.pop("profiles", None)
|
|
84
|
-
app_secret = _as_text(profile.get("app_secret"))
|
|
85
|
-
app_secret_env = _as_text(profile.get("app_secret_env"))
|
|
86
|
-
merged.update(
|
|
87
|
-
{
|
|
88
|
-
"profile_id": str(profile.get("profile_id") or "").strip() or None,
|
|
89
|
-
"app_id": _as_text(profile.get("app_id")),
|
|
90
|
-
"app_secret": app_secret,
|
|
91
|
-
"app_secret_env": None if app_secret else app_secret_env,
|
|
92
|
-
"bot_name": _as_text(profile.get("bot_name")) or str(normalized.get("bot_name") or "DeepScientist"),
|
|
93
|
-
"main_chat_id": _as_text(profile.get("main_chat_id")),
|
|
94
|
-
"enabled": bool(normalized.get("enabled", False)) and bool(profile.get("enabled", True)),
|
|
95
|
-
"transport": "gateway_direct",
|
|
96
|
-
}
|
|
97
|
-
)
|
|
98
|
-
return merged
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def qq_profile_label(profile: dict[str, Any] | None) -> str:
|
|
102
|
-
if not isinstance(profile, dict):
|
|
103
|
-
return "QQ"
|
|
104
|
-
bot_name = _as_text(profile.get("bot_name"))
|
|
105
|
-
app_id = _as_text(profile.get("app_id"))
|
|
106
|
-
if bot_name and app_id:
|
|
107
|
-
return f"{bot_name} · {app_id}"
|
|
108
|
-
if bot_name:
|
|
109
|
-
return bot_name
|
|
110
|
-
if app_id:
|
|
111
|
-
return f"QQ · {app_id}"
|
|
112
|
-
return "QQ"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def normalize_qq_connector_config(config: dict[str, Any] | None) -> dict[str, Any]:
|
|
116
|
-
payload = deepcopy(config or {})
|
|
117
|
-
shared_defaults = {
|
|
118
|
-
"enabled": False,
|
|
119
|
-
"transport": "gateway_direct",
|
|
120
|
-
"app_id": None,
|
|
121
|
-
"app_secret": None,
|
|
122
|
-
"app_secret_env": None,
|
|
123
|
-
"bot_name": "DeepScientist",
|
|
124
|
-
"command_prefix": "/",
|
|
125
|
-
"main_chat_id": None,
|
|
126
|
-
"require_at_in_groups": True,
|
|
127
|
-
"auto_bind_dm_to_active_quest": True,
|
|
128
|
-
"gateway_restart_on_config_change": True,
|
|
129
|
-
"auto_send_main_experiment_png": True,
|
|
130
|
-
"auto_send_analysis_summary_png": True,
|
|
131
|
-
"auto_send_slice_png": True,
|
|
132
|
-
"auto_send_paper_pdf": True,
|
|
133
|
-
"enable_markdown_send": False,
|
|
134
|
-
"enable_file_upload_experimental": False,
|
|
135
|
-
"profiles": [],
|
|
136
|
-
}
|
|
137
|
-
shared = {**shared_defaults, **payload}
|
|
138
|
-
shared["transport"] = "gateway_direct"
|
|
139
|
-
shared["command_prefix"] = _as_text(shared.get("command_prefix")) or "/"
|
|
140
|
-
shared["bot_name"] = _as_text(shared.get("bot_name")) or "DeepScientist"
|
|
141
|
-
_normalize_secret_pair(shared, "app_secret", "app_secret_env")
|
|
142
|
-
|
|
143
|
-
raw_profiles = payload.get("profiles")
|
|
144
|
-
items = list(raw_profiles) if isinstance(raw_profiles, list) else []
|
|
145
|
-
legacy_profile_seed = {
|
|
146
|
-
"app_id": payload.get("app_id"),
|
|
147
|
-
"app_secret": payload.get("app_secret"),
|
|
148
|
-
"app_secret_env": payload.get("app_secret_env"),
|
|
149
|
-
"bot_name": payload.get("bot_name"),
|
|
150
|
-
"main_chat_id": payload.get("main_chat_id"),
|
|
151
|
-
}
|
|
152
|
-
if not items:
|
|
153
|
-
has_direct_profile_seed = any(_as_text(legacy_profile_seed.get(key)) for key in ("app_id", "app_secret", "main_chat_id"))
|
|
154
|
-
has_env_profile_seed = bool(payload.get("enabled")) and bool(_as_text(legacy_profile_seed.get("app_secret_env")))
|
|
155
|
-
if has_direct_profile_seed or has_env_profile_seed:
|
|
156
|
-
items = [legacy_profile_seed]
|
|
157
|
-
|
|
158
|
-
profiles: list[dict[str, Any]] = []
|
|
159
|
-
used_ids: set[str] = set()
|
|
160
|
-
for index, raw in enumerate(items, start=1):
|
|
161
|
-
if not isinstance(raw, dict):
|
|
162
|
-
continue
|
|
163
|
-
current = {**default_qq_profile(), **raw}
|
|
164
|
-
current["enabled"] = bool(current.get("enabled", True))
|
|
165
|
-
current["app_id"] = _as_text(current.get("app_id"))
|
|
166
|
-
current["app_secret"] = _as_text(current.get("app_secret"))
|
|
167
|
-
current["app_secret_env"] = _as_text(current.get("app_secret_env")) or shared["app_secret_env"]
|
|
168
|
-
_normalize_secret_pair(current, "app_secret", "app_secret_env")
|
|
169
|
-
current["bot_name"] = _as_text(current.get("bot_name")) or shared["bot_name"]
|
|
170
|
-
current["main_chat_id"] = _as_text(current.get("main_chat_id"))
|
|
171
|
-
current["profile_id"] = _unique_profile_id(
|
|
172
|
-
_profile_id_seed(
|
|
173
|
-
profile_id=current.get("profile_id"),
|
|
174
|
-
app_id=current.get("app_id"),
|
|
175
|
-
bot_name=current.get("bot_name"),
|
|
176
|
-
index=index,
|
|
177
|
-
),
|
|
178
|
-
used=used_ids,
|
|
179
|
-
)
|
|
180
|
-
profiles.append(current)
|
|
181
|
-
|
|
182
|
-
shared["profiles"] = profiles
|
|
183
|
-
if len(profiles) == 1:
|
|
184
|
-
mirror = profiles[0]
|
|
185
|
-
shared["app_id"] = mirror.get("app_id")
|
|
186
|
-
shared["app_secret"] = mirror.get("app_secret")
|
|
187
|
-
shared["app_secret_env"] = mirror.get("app_secret_env")
|
|
188
|
-
shared["bot_name"] = mirror.get("bot_name")
|
|
189
|
-
shared["main_chat_id"] = mirror.get("main_chat_id")
|
|
190
|
-
else:
|
|
191
|
-
shared["app_id"] = None
|
|
192
|
-
shared["app_secret"] = None
|
|
193
|
-
shared["app_secret_env"] = None
|
|
194
|
-
shared["main_chat_id"] = None
|
|
195
|
-
|
|
196
|
-
return shared
|
|
1
|
+
from .connector.qq_profiles import * # noqa: F401,F403
|