@researai/deepscientist 1.5.0 → 1.5.1
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/AGENTS.md +26 -0
- package/README.md +19 -179
- package/assets/connectors/lingzhu/openclaw-bridge/README.md +124 -0
- package/assets/connectors/lingzhu/openclaw-bridge/index.ts +162 -0
- package/assets/connectors/lingzhu/openclaw-bridge/openclaw.plugin.json +145 -0
- package/assets/connectors/lingzhu/openclaw-bridge/package.json +35 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/cli.ts +180 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/config.ts +196 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/debug-log.ts +111 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/events.ts +4 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/http-handler.ts +1133 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/image-cache.ts +75 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/lingzhu-tools.ts +246 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/transform.ts +541 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/types.ts +131 -0
- package/assets/connectors/lingzhu/openclaw-bridge/tsconfig.json +14 -0
- package/assets/connectors/lingzhu/openclaw.lingzhu.config.template.json +39 -0
- package/bin/ds.js +233 -53
- package/docs/en/00_QUICK_START.md +134 -0
- package/docs/en/01_SETTINGS_REFERENCE.md +1104 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +404 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +325 -0
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +216 -0
- package/docs/en/05_TUI_GUIDE.md +141 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +679 -0
- package/docs/en/07_MEMORY_AND_MCP.md +253 -0
- package/docs/en/08_FIGURE_STYLE_GUIDE.md +97 -0
- package/docs/en/09_DOCTOR.md +108 -0
- package/docs/en/90_ARCHITECTURE.md +245 -0
- package/docs/en/91_DEVELOPMENT.md +195 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +29 -0
- package/docs/zh/00_QUICK_START.md +134 -0
- package/docs/zh/01_SETTINGS_REFERENCE.md +1137 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +414 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +324 -0
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +230 -0
- package/docs/zh/05_TUI_GUIDE.md +128 -0
- package/docs/zh/06_RUNTIME_AND_CANVAS.md +271 -0
- package/docs/zh/07_MEMORY_AND_MCP.md +235 -0
- package/docs/zh/08_FIGURE_STYLE_GUIDE.md +97 -0
- package/docs/zh/09_DOCTOR.md +112 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +29 -0
- package/install.sh +32 -8
- package/package.json +4 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/artifact/guidance.py +9 -2
- package/src/deepscientist/artifact/service.py +482 -22
- package/src/deepscientist/bash_exec/monitor.py +27 -5
- package/src/deepscientist/bash_exec/runtime.py +639 -0
- package/src/deepscientist/bash_exec/service.py +99 -16
- package/src/deepscientist/bridges/base.py +3 -0
- package/src/deepscientist/bridges/connectors.py +292 -13
- package/src/deepscientist/channels/qq.py +19 -2
- package/src/deepscientist/channels/relay.py +1 -0
- package/src/deepscientist/cli.py +32 -25
- package/src/deepscientist/config/models.py +28 -2
- package/src/deepscientist/config/service.py +201 -6
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +50 -5
- package/src/deepscientist/daemon/api/router.py +1 -0
- package/src/deepscientist/daemon/app.py +442 -15
- package/src/deepscientist/doctor.py +444 -0
- package/src/deepscientist/home.py +1 -0
- package/src/deepscientist/latex_runtime.py +17 -4
- package/src/deepscientist/lingzhu_support.py +182 -0
- package/src/deepscientist/mcp/server.py +49 -2
- package/src/deepscientist/prompts/builder.py +181 -58
- package/src/deepscientist/quest/layout.py +1 -0
- package/src/deepscientist/quest/service.py +63 -2
- package/src/deepscientist/quest/stage_views.py +19 -1
- package/src/deepscientist/runtime_tools/__init__.py +16 -0
- package/src/deepscientist/runtime_tools/builtins.py +19 -0
- package/src/deepscientist/runtime_tools/models.py +29 -0
- package/src/deepscientist/runtime_tools/registry.py +40 -0
- package/src/deepscientist/runtime_tools/service.py +59 -0
- package/src/deepscientist/runtime_tools/tinytex.py +25 -0
- package/src/deepscientist/tinytex.py +276 -0
- package/src/prompts/connectors/lingzhu.md +12 -0
- package/src/prompts/connectors/qq.md +121 -0
- package/src/prompts/system.md +177 -33
- package/src/skills/analysis-campaign/SKILL.md +22 -6
- package/src/skills/baseline/SKILL.md +5 -4
- package/src/skills/decision/SKILL.md +4 -3
- package/src/skills/experiment/SKILL.md +5 -4
- package/src/skills/finalize/SKILL.md +5 -4
- package/src/skills/idea/SKILL.md +5 -4
- package/src/skills/intake-audit/SKILL.md +277 -0
- package/src/skills/intake-audit/references/state-audit-template.md +41 -0
- package/src/skills/rebuttal/SKILL.md +407 -0
- package/src/skills/rebuttal/references/action-plan-template.md +63 -0
- package/src/skills/rebuttal/references/evidence-update-template.md +30 -0
- package/src/skills/rebuttal/references/response-letter-template.md +113 -0
- package/src/skills/rebuttal/references/review-matrix-template.md +55 -0
- package/src/skills/review/SKILL.md +293 -0
- package/src/skills/review/references/experiment-todo-template.md +29 -0
- package/src/skills/review/references/review-report-template.md +83 -0
- package/src/skills/review/references/revision-log-template.md +40 -0
- package/src/skills/scout/SKILL.md +5 -4
- package/src/skills/write/SKILL.md +7 -3
- package/src/tui/dist/components/WelcomePanel.js +17 -43
- package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -2
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-7v-dHngU.js → AiManusChatView-w5lF2Ttt.js} +109 -575
- package/src/ui/dist/assets/{AnalysisPlugin-B_Xmz-KE.js → AnalysisPlugin-DJOED79I.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-Cko-0tm1.js → AutoFigurePlugin-DaG61Y0M.js} +63 -8
- package/src/ui/dist/assets/{CliPlugin-BsU0ht7q.js → CliPlugin-CV4LqUB_.js} +43 -609
- package/src/ui/dist/assets/{CodeEditorPlugin-DcMMP0Rt.js → CodeEditorPlugin-DylfAea4.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BqoQ5QyY.js → CodeViewerPlugin-F7saY0LM.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-D7eHNhU6.js → DocViewerPlugin-COP0c7jf.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DLJN42T5.js → GitDiffViewerPlugin-CAS05pT9.js} +1 -1
- package/src/ui/dist/assets/{ImageViewerPlugin-gJMV7MOu.js → ImageViewerPlugin-Bco1CN_w.js} +5 -6
- package/src/ui/dist/assets/{LabCopilotPanel-B857sfxP.js → LabCopilotPanel-CvMlCD99.js} +12 -15
- package/src/ui/dist/assets/LabPlugin-BYankkE4.js +2676 -0
- package/src/ui/dist/assets/LabPlugin-D9jVIo0A.css +2698 -0
- package/src/ui/dist/assets/{LatexPlugin-DWKEo-Wj.js → LatexPlugin-LDSMR-t-.js} +16 -16
- package/src/ui/dist/assets/{MarkdownViewerPlugin-DBzoEmhv.js → MarkdownViewerPlugin-B7o80jgm.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DoHc-8vo.js → MarketplacePlugin-CM6ZOcpC.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-CKjKH-yS.js → NotebookEditor-Dc61cXmK.js} +3 -3
- package/src/ui/dist/assets/{PdfLoader-zFoL0VPo.js → PdfLoader-DWowuQwx.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-DXPaL9Nt.js → PdfMarkdownPlugin-BsJM1q_a.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-DhK8qCFp.js → PdfViewerPlugin-DB2eEEFQ.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-CdSi6krf.js → SearchPlugin-CraThSvt.js} +1 -1
- package/src/ui/dist/assets/{Stepper-V-WiDQJl.js → Stepper-CgocRTPq.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-hIs1Efiu.js → TextViewerPlugin-B1JGhKtd.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-DG8b0q2X.js → VNCViewer-CclFC7FM.js} +9 -10
- package/src/ui/dist/assets/{bibtex-HDac6fVW.js → bibtex-D3IKsMl7.js} +1 -1
- package/src/ui/dist/assets/{code-BnBeNxBc.js → code-BP37Xx0p.js} +1 -1
- package/src/ui/dist/assets/{file-content-IRQ3jHb8.js → file-content-BAJSu-9r.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-DZoQ9I6r.js → file-diff-panel-DUGeCTuy.js} +1 -1
- package/src/ui/dist/assets/{file-socket-BMCdLc-P.js → file-socket-CXc1Ojf7.js} +1 -1
- package/src/ui/dist/assets/{file-utils-CltILB3w.js → file-utils-2J21jt7M.js} +1 -1
- package/src/ui/dist/assets/{image-Boe6ffhu.js → image-CMMmgvcn.js} +1 -1
- package/src/ui/dist/assets/{index-BlplpvE1.js → index-BaVumsQT.js} +2 -2
- package/src/ui/dist/assets/{index-DZqJ-qAM.js → index-CWgMgpow.js} +60 -2154
- package/src/ui/dist/assets/{index-DO43pFZP.js → index-DmwmJmbW.js} +6372 -8434
- package/src/ui/dist/assets/{index-Bq2bvfkl.css → index-KGt-z-dD.css} +225 -2920
- package/src/ui/dist/assets/{index-2Zf65FZt.js → index-s7aHnNQ4.js} +1 -1
- package/src/ui/dist/assets/{message-square-mUHn_Ssb.js → message-square-CQRfX0Am.js} +1 -1
- package/src/ui/dist/assets/{monaco-fe0arNEU.js → monaco-B4TbdsrF.js} +1 -1
- package/src/ui/dist/assets/{popover-D_7i19qU.js → popover-B8Rokodk.js} +1 -1
- package/src/ui/dist/assets/{project-sync-DyVGrU7H.js → project-sync-D_i96KH4.js} +2 -8
- package/src/ui/dist/assets/{sigma-BzazRyxQ.js → sigma-D12PnzCN.js} +1 -1
- package/src/ui/dist/assets/{tooltip-DN_yjHFH.js → tooltip-B6YrI4aJ.js} +1 -1
- package/src/ui/dist/assets/trash-Bc8jGp0V.js +32 -0
- package/src/ui/dist/assets/{useCliAccess-DV2L2Qxy.js → useCliAccess-mXVCYSZ-.js} +12 -42
- package/src/ui/dist/assets/{useFileDiffOverlay-DyTj-p_V.js → useFileDiffOverlay-Bg6b9H9K.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-ozYHtUwq.js → wrap-text-Drh5GEnL.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BN9MUyCQ.js → zoom-out-CJj9DZLn.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/assets/fonts/Inter-Variable.ttf +0 -0
- package/assets/fonts/NotoSerifSC-Regular-C94HN_ZN.ttf +0 -0
- package/assets/fonts/NunitoSans-Variable.ttf +0 -0
- package/assets/fonts/Satoshi-Medium-ByP-Zb-9.woff2 +0 -0
- package/assets/fonts/SourceSans3-Variable.ttf +0 -0
- package/assets/fonts/ds-fonts.css +0 -83
- package/src/ui/dist/assets/Inter-Variable-VF2RPR_K.ttf +0 -0
- package/src/ui/dist/assets/LabPlugin-bL7rpic8.js +0 -43
- package/src/ui/dist/assets/NotoSerifSC-Regular-C94HN_ZN-C94HN_ZN.ttf +0 -0
- package/src/ui/dist/assets/NunitoSans-Variable-B_ZymHAd.ttf +0 -0
- package/src/ui/dist/assets/Satoshi-Medium-ByP-Zb-9-GkA34YXu.woff2 +0 -0
- package/src/ui/dist/assets/SourceSans3-Variable-CD-WOsSK.ttf +0 -0
- package/src/ui/dist/assets/info-CcsK_htA.js +0 -18
- package/src/ui/dist/assets/user-plus-BusDx-hF.js +0 -79
package/src/deepscientist/cli.py
CHANGED
|
@@ -14,12 +14,14 @@ from urllib.request import Request, urlopen
|
|
|
14
14
|
from .artifact import ArtifactService
|
|
15
15
|
from .config import ConfigManager
|
|
16
16
|
from .daemon import DaemonApp
|
|
17
|
+
from .doctor import render_doctor_report, run_doctor
|
|
17
18
|
from .home import default_home, ensure_home_layout, repo_root
|
|
18
19
|
from .memory import MemoryService
|
|
19
20
|
from .prompts import PromptBuilder
|
|
20
21
|
from .quest import QuestService
|
|
21
22
|
from .registries import BaselineRegistry
|
|
22
23
|
from .runners import CodexRunner, RunRequest, get_runner_factory, register_builtin_runners
|
|
24
|
+
from .runtime_tools import RuntimeToolService
|
|
23
25
|
from .runtime_logs import JsonlLogger
|
|
24
26
|
from .shared import ensure_dir, read_yaml
|
|
25
27
|
from .skills import SkillInstaller
|
|
@@ -76,8 +78,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
76
78
|
graph_parser = subparsers.add_parser("graph")
|
|
77
79
|
graph_parser.add_argument("quest_id")
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
metrics_parser.add_argument("target")
|
|
81
|
+
subparsers.add_parser("doctor", aliases=["docker"])
|
|
81
82
|
|
|
82
83
|
push_parser = subparsers.add_parser("push")
|
|
83
84
|
push_parser.add_argument("quest_id")
|
|
@@ -95,6 +96,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
95
96
|
baseline_attach.add_argument("--baseline-id", required=True)
|
|
96
97
|
baseline_attach.add_argument("--variant-id", default=None)
|
|
97
98
|
|
|
99
|
+
latex_parser = subparsers.add_parser("latex")
|
|
100
|
+
latex_subparsers = latex_parser.add_subparsers(dest="latex_command", required=True)
|
|
101
|
+
latex_subparsers.add_parser("status")
|
|
102
|
+
latex_subparsers.add_parser("install-runtime")
|
|
103
|
+
|
|
98
104
|
config_parser = subparsers.add_parser("config")
|
|
99
105
|
config_subparsers = config_parser.add_subparsers(dest="config_command", required=True)
|
|
100
106
|
config_show = config_subparsers.add_parser("show")
|
|
@@ -355,27 +361,10 @@ def graph_command(home: Path, quest_id: str) -> int:
|
|
|
355
361
|
return 0
|
|
356
362
|
|
|
357
363
|
|
|
358
|
-
def
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
payload = []
|
|
363
|
-
from .shared import read_json
|
|
364
|
-
|
|
365
|
-
for path in runs:
|
|
366
|
-
item = read_json(path, {})
|
|
367
|
-
payload.append(
|
|
368
|
-
{
|
|
369
|
-
"run_id": item.get("run_id"),
|
|
370
|
-
"run_kind": item.get("run_kind"),
|
|
371
|
-
"exit_code": item.get("exit_code"),
|
|
372
|
-
"summary": item.get("summary"),
|
|
373
|
-
}
|
|
374
|
-
)
|
|
375
|
-
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
376
|
-
return 0
|
|
377
|
-
print(json.dumps({"message": "Run-level metrics lookup is not implemented yet."}, ensure_ascii=False, indent=2))
|
|
378
|
-
return 0
|
|
364
|
+
def doctor_command(home: Path) -> int:
|
|
365
|
+
report = run_doctor(home, repo_root=repo_root())
|
|
366
|
+
sys.stdout.write(render_doctor_report(report))
|
|
367
|
+
return 0 if report.get("ok") else 1
|
|
379
368
|
|
|
380
369
|
|
|
381
370
|
def push_command(home: Path, quest_id: str) -> int:
|
|
@@ -415,6 +404,20 @@ def baseline_attach_command(home: Path, quest_id: str, baseline_id: str, variant
|
|
|
415
404
|
return 0 if result.get("ok") else 1
|
|
416
405
|
|
|
417
406
|
|
|
407
|
+
def latex_status_command(home: Path) -> int:
|
|
408
|
+
ensure_home_layout(home)
|
|
409
|
+
payload = RuntimeToolService(home).status("tinytex")
|
|
410
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
411
|
+
return 0 if payload.get("ok") else 1
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def latex_install_runtime_command(home: Path) -> int:
|
|
415
|
+
ensure_home_layout(home)
|
|
416
|
+
payload = RuntimeToolService(home).install("tinytex")
|
|
417
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
418
|
+
return 0 if payload.get("ok") else 1
|
|
419
|
+
|
|
420
|
+
|
|
418
421
|
def config_show_command(home: Path, name: str) -> int:
|
|
419
422
|
manager = ConfigManager(home)
|
|
420
423
|
text = manager.load_named_text(name, create_optional=True)
|
|
@@ -469,8 +472,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
469
472
|
return approve_command(home, args.quest_id, args.decision_id, args.reason)
|
|
470
473
|
if args.command == "graph":
|
|
471
474
|
return graph_command(home, args.quest_id)
|
|
472
|
-
if args.command
|
|
473
|
-
return
|
|
475
|
+
if args.command in {"doctor", "docker"}:
|
|
476
|
+
return doctor_command(home)
|
|
474
477
|
if args.command == "push":
|
|
475
478
|
return push_command(home, args.quest_id)
|
|
476
479
|
if args.command == "memory" and args.memory_command == "search":
|
|
@@ -479,6 +482,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
479
482
|
return baseline_list_command(home)
|
|
480
483
|
if args.command == "baseline" and args.baseline_command == "attach":
|
|
481
484
|
return baseline_attach_command(home, args.quest_id, args.baseline_id, args.variant_id)
|
|
485
|
+
if args.command == "latex" and args.latex_command == "status":
|
|
486
|
+
return latex_status_command(home)
|
|
487
|
+
if args.command == "latex" and args.latex_command == "install-runtime":
|
|
488
|
+
return latex_install_runtime_command(home)
|
|
482
489
|
if args.command == "config" and args.config_command == "show":
|
|
483
490
|
return config_show_command(home, args.name)
|
|
484
491
|
if args.command == "config" and args.config_command == "edit":
|
|
@@ -35,7 +35,7 @@ def default_config(home: Path) -> dict:
|
|
|
35
35
|
"host": "0.0.0.0",
|
|
36
36
|
"port": 20999,
|
|
37
37
|
"auto_open_browser": True,
|
|
38
|
-
"default_mode": "
|
|
38
|
+
"default_mode": "web",
|
|
39
39
|
},
|
|
40
40
|
"logging": {
|
|
41
41
|
"level": "info",
|
|
@@ -133,8 +133,9 @@ def default_connectors() -> dict:
|
|
|
133
133
|
"gateway_restart_on_config_change": True,
|
|
134
134
|
"auto_send_main_experiment_png": True,
|
|
135
135
|
"auto_send_analysis_summary_png": True,
|
|
136
|
-
"auto_send_slice_png":
|
|
136
|
+
"auto_send_slice_png": True,
|
|
137
137
|
"auto_send_paper_pdf": True,
|
|
138
|
+
"enable_markdown_send": False,
|
|
138
139
|
"enable_file_upload_experimental": False,
|
|
139
140
|
},
|
|
140
141
|
"telegram": {
|
|
@@ -257,6 +258,31 @@ def default_connectors() -> dict:
|
|
|
257
258
|
"groups": [],
|
|
258
259
|
"auto_bind_dm_to_active_quest": True,
|
|
259
260
|
},
|
|
261
|
+
"lingzhu": {
|
|
262
|
+
"enabled": False,
|
|
263
|
+
"transport": "openclaw_sse",
|
|
264
|
+
"local_host": "127.0.0.1",
|
|
265
|
+
"gateway_port": 18789,
|
|
266
|
+
"public_base_url": None,
|
|
267
|
+
"auth_ak": None,
|
|
268
|
+
"agent_id": "main",
|
|
269
|
+
"include_metadata": True,
|
|
270
|
+
"request_timeout_ms": 60000,
|
|
271
|
+
"system_prompt": "",
|
|
272
|
+
"default_navigation_mode": "0",
|
|
273
|
+
"enable_follow_up": True,
|
|
274
|
+
"follow_up_max_count": 3,
|
|
275
|
+
"max_image_bytes": 5242880,
|
|
276
|
+
"session_mode": "per_user",
|
|
277
|
+
"session_namespace": "lingzhu",
|
|
278
|
+
"auto_receipt_ack": True,
|
|
279
|
+
"visible_progress_heartbeat": True,
|
|
280
|
+
"visible_progress_heartbeat_sec": 10,
|
|
281
|
+
"debug_logging": False,
|
|
282
|
+
"debug_log_payloads": False,
|
|
283
|
+
"debug_log_dir": None,
|
|
284
|
+
"enable_experimental_native_actions": False,
|
|
285
|
+
},
|
|
260
286
|
}
|
|
261
287
|
|
|
262
288
|
|
|
@@ -5,10 +5,23 @@ import os
|
|
|
5
5
|
import subprocess
|
|
6
6
|
from copy import deepcopy
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from urllib.error import URLError
|
|
8
9
|
from urllib.request import Request, urlopen
|
|
9
10
|
|
|
10
11
|
from ..connector_runtime import infer_connector_transport
|
|
11
12
|
from ..home import repo_root
|
|
13
|
+
from ..lingzhu_support import (
|
|
14
|
+
lingzhu_agent_id,
|
|
15
|
+
lingzhu_generated_curl,
|
|
16
|
+
lingzhu_generated_openclaw_config_text,
|
|
17
|
+
lingzhu_gateway_port,
|
|
18
|
+
lingzhu_health_url,
|
|
19
|
+
lingzhu_local_base_url,
|
|
20
|
+
lingzhu_probe_payload,
|
|
21
|
+
lingzhu_public_base_url,
|
|
22
|
+
lingzhu_sse_url,
|
|
23
|
+
lingzhu_supported_commands,
|
|
24
|
+
)
|
|
12
25
|
from ..shared import read_json, read_text, read_yaml, resolve_runner_binary, run_command, sha256_text, utc_now, which, write_text, write_yaml
|
|
13
26
|
from .models import (
|
|
14
27
|
CONFIG_NAMES,
|
|
@@ -294,9 +307,17 @@ This page edits `~/DeepScientist/config/connectors.yaml` directly.
|
|
|
294
307
|
- readiness test exchanges `access_token` and probes `/gateway`
|
|
295
308
|
- active send targets use QQ user `openid` or group `group_openid`
|
|
296
309
|
- the settings page also surfaces recently discovered targets from runtime activity
|
|
297
|
-
-
|
|
298
|
-
- recommended
|
|
299
|
-
|
|
310
|
+
- milestone delivery toggles default to enabled; adjust them only if you want less outbound push
|
|
311
|
+
- the recommended first-run path is: save credentials -> send one QQ private message -> confirm `Detected OpenID` -> run a probe
|
|
312
|
+
|
|
313
|
+
### Lingzhu
|
|
314
|
+
|
|
315
|
+
- Lingzhu is configured as an OpenClaw companion endpoint, not as a full DeepScientist chat bridge
|
|
316
|
+
- keep `transport: openclaw_sse`
|
|
317
|
+
- set `gateway_port` to the OpenClaw HTTP gateway port, usually `18789`
|
|
318
|
+
- generate and save `auth_ak`, then fill the same Bearer token into the Lingzhu platform
|
|
319
|
+
- set `public_base_url` to a public IP or public domain before binding a real Rokid device
|
|
320
|
+
- readiness test first probes `GET /metis/agent/api/health`, then runs a minimal SSE smoke request against `POST /metis/agent/api/sse`
|
|
300
321
|
|
|
301
322
|
## Safety
|
|
302
323
|
|
|
@@ -352,7 +373,8 @@ This page edits `{home_text}/config/runners.yaml`.
|
|
|
352
373
|
## Recommended v1 choice
|
|
353
374
|
|
|
354
375
|
- keep `codex.enabled: true`
|
|
355
|
-
- keep `claude.enabled: false`
|
|
376
|
+
- keep `claude.enabled: false`
|
|
377
|
+
- `claude` remains TODO / reserved in the current open-source release and is not runnable yet
|
|
356
378
|
- keep `codex.model_reasoning_effort: xhigh` unless you explicitly want a lighter default
|
|
357
379
|
- keep `codex.retry_on_failure: true` so transient Codex failures can resume automatically
|
|
358
380
|
- keep retry timing near `1s / 2x / 8s max` unless you have a strong reason to slow recovery down
|
|
@@ -708,6 +730,40 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
708
730
|
errors.append("whatsapp: `provider: meta` requires `phone_number_id`.")
|
|
709
731
|
if not self._has_secret(config, "verify_token", "verify_token_env"):
|
|
710
732
|
errors.append("whatsapp: `provider: meta` requires `verify_token` or `verify_token_env`.")
|
|
733
|
+
elif name == "lingzhu":
|
|
734
|
+
if transport != "openclaw_sse":
|
|
735
|
+
errors.append("lingzhu: `transport` must stay `openclaw_sse`.")
|
|
736
|
+
if not str(config.get("local_host") or "").strip():
|
|
737
|
+
warnings.append("lingzhu: `local_host` is empty; DeepScientist will fall back to `127.0.0.1`.")
|
|
738
|
+
if not self._has_secret(config, "auth_ak", "auth_ak_env"):
|
|
739
|
+
errors.append("lingzhu: requires `auth_ak` for Bearer authentication.")
|
|
740
|
+
raw_gateway_port = str(config.get("gateway_port") or "").strip()
|
|
741
|
+
normalized_port = lingzhu_gateway_port(config)
|
|
742
|
+
if raw_gateway_port and str(normalized_port) != raw_gateway_port:
|
|
743
|
+
errors.append("lingzhu: `gateway_port` must be a valid TCP port between 1 and 65535.")
|
|
744
|
+
raw_public_base_url = str(config.get("public_base_url") or "").strip()
|
|
745
|
+
public_base_url = lingzhu_public_base_url(config)
|
|
746
|
+
if raw_public_base_url and public_base_url is None:
|
|
747
|
+
errors.append("lingzhu: `public_base_url` must be a valid `http://` or `https://` URL when set.")
|
|
748
|
+
raw_visible_progress_heartbeat_sec = str(
|
|
749
|
+
config.get("visible_progress_heartbeat_sec") or ""
|
|
750
|
+
).strip()
|
|
751
|
+
if raw_visible_progress_heartbeat_sec:
|
|
752
|
+
try:
|
|
753
|
+
visible_progress_heartbeat_sec = int(raw_visible_progress_heartbeat_sec)
|
|
754
|
+
except ValueError:
|
|
755
|
+
errors.append(
|
|
756
|
+
"lingzhu: `visible_progress_heartbeat_sec` must be an integer between 5 and 120."
|
|
757
|
+
)
|
|
758
|
+
else:
|
|
759
|
+
if visible_progress_heartbeat_sec < 5 or visible_progress_heartbeat_sec > 120:
|
|
760
|
+
errors.append(
|
|
761
|
+
"lingzhu: `visible_progress_heartbeat_sec` must be an integer between 5 and 120."
|
|
762
|
+
)
|
|
763
|
+
if not raw_public_base_url:
|
|
764
|
+
warnings.append(
|
|
765
|
+
"lingzhu: set `public_base_url` to a public IP or public domain before filling values into the Lingzhu platform."
|
|
766
|
+
)
|
|
711
767
|
|
|
712
768
|
if preferred_connector and preferred_connector not in enabled_connectors:
|
|
713
769
|
warnings.append(
|
|
@@ -872,12 +928,27 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
872
928
|
errors.append(str(gateway_payload.get("message") or "QQ gateway probe failed."))
|
|
873
929
|
else:
|
|
874
930
|
details["gateway_url"] = gateway_url
|
|
931
|
+
elif name == "lingzhu":
|
|
932
|
+
details.update(self._lingzhu_snapshot_details(config))
|
|
933
|
+
auth_ak = self._secret(config, "auth_ak", "auth_ak_env")
|
|
934
|
+
if not auth_ak:
|
|
935
|
+
errors.append("Lingzhu requires `auth_ak` before it can accept Bearer-authenticated requests.")
|
|
936
|
+
elif live:
|
|
937
|
+
health_probe = self._probe_lingzhu_health(config)
|
|
938
|
+
details["health_probe"] = health_probe
|
|
939
|
+
if not health_probe.get("ok", False):
|
|
940
|
+
errors.append(str(health_probe.get("message") or "Lingzhu health probe failed."))
|
|
941
|
+
else:
|
|
942
|
+
sse_probe = self._probe_lingzhu_sse(config)
|
|
943
|
+
details["sse_probe"] = sse_probe
|
|
944
|
+
if not sse_probe.get("ok", False):
|
|
945
|
+
errors.append(str(sse_probe.get("message") or "Lingzhu SSE probe failed."))
|
|
875
946
|
else:
|
|
876
947
|
warnings.append(f"No dedicated system test exists for connector `{name}`.")
|
|
877
948
|
except Exception as exc: # pragma: no cover - network-dependent
|
|
878
949
|
errors.append(str(exc))
|
|
879
950
|
|
|
880
|
-
if delivery_target:
|
|
951
|
+
if delivery_target and name != "lingzhu":
|
|
881
952
|
delivery_message = str(delivery_target.get("text") or "").strip()
|
|
882
953
|
chat_type = str(delivery_target.get("chat_type") or "direct").strip().lower()
|
|
883
954
|
chat_id = str(delivery_target.get("chat_id") or "").strip()
|
|
@@ -899,7 +970,9 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
899
970
|
warnings.append("Delivery test chat_type must be `direct` or `group`.")
|
|
900
971
|
elif not chat_id:
|
|
901
972
|
if name == "qq":
|
|
902
|
-
warnings.append(
|
|
973
|
+
warnings.append(
|
|
974
|
+
"QQ readiness is healthy, but no OpenID has been learned yet. Save credentials, then send one private QQ message so DeepScientist can auto-detect and save the `openid`."
|
|
975
|
+
)
|
|
903
976
|
else:
|
|
904
977
|
warnings.append("Delivery test is configured, but the target chat id is empty.")
|
|
905
978
|
elif errors:
|
|
@@ -1116,6 +1189,126 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1116
1189
|
return text
|
|
1117
1190
|
return text[: limit - 1].rstrip() + "…"
|
|
1118
1191
|
|
|
1192
|
+
def lingzhu_snapshot(self, config: dict | None = None) -> dict:
|
|
1193
|
+
resolved = dict(config or self.load_named_normalized("connectors").get("lingzhu") or {})
|
|
1194
|
+
snapshot: dict[str, object] = {
|
|
1195
|
+
"name": "lingzhu",
|
|
1196
|
+
"display_mode": "companion_config",
|
|
1197
|
+
"mode": "openclaw_companion",
|
|
1198
|
+
"transport": "openclaw_sse",
|
|
1199
|
+
"enabled": bool(resolved.get("enabled", False)),
|
|
1200
|
+
"relay_url": None,
|
|
1201
|
+
"main_chat_id": None,
|
|
1202
|
+
"last_conversation_id": None,
|
|
1203
|
+
"inbox_count": 0,
|
|
1204
|
+
"outbox_count": 0,
|
|
1205
|
+
"ignored_count": 0,
|
|
1206
|
+
"binding_count": 0,
|
|
1207
|
+
"target_count": 0,
|
|
1208
|
+
"recent_conversations": [],
|
|
1209
|
+
"recent_events": [],
|
|
1210
|
+
"discovered_targets": [],
|
|
1211
|
+
"details": self._lingzhu_snapshot_details(resolved),
|
|
1212
|
+
}
|
|
1213
|
+
if not snapshot["enabled"]:
|
|
1214
|
+
snapshot["connection_state"] = "disabled"
|
|
1215
|
+
snapshot["auth_state"] = "disabled"
|
|
1216
|
+
return snapshot
|
|
1217
|
+
snapshot["auth_state"] = "ready" if self._secret(resolved, "auth_ak", "auth_ak_env") else "missing_auth_ak"
|
|
1218
|
+
health_probe = self._probe_lingzhu_health(resolved, timeout=1.5)
|
|
1219
|
+
snapshot["details"]["health_probe"] = health_probe
|
|
1220
|
+
if health_probe.get("ok", False):
|
|
1221
|
+
snapshot["connection_state"] = "reachable"
|
|
1222
|
+
else:
|
|
1223
|
+
snapshot["connection_state"] = "offline"
|
|
1224
|
+
if health_probe.get("message"):
|
|
1225
|
+
snapshot["last_error"] = health_probe.get("message")
|
|
1226
|
+
return snapshot
|
|
1227
|
+
|
|
1228
|
+
def _lingzhu_snapshot_details(self, config: dict) -> dict:
|
|
1229
|
+
auth_ak = self._secret(config, "auth_ak", "auth_ak_env")
|
|
1230
|
+
return {
|
|
1231
|
+
"local_base_url": lingzhu_local_base_url(config),
|
|
1232
|
+
"health_url": lingzhu_health_url(config),
|
|
1233
|
+
"endpoint_url": lingzhu_sse_url(config),
|
|
1234
|
+
"public_base_url": lingzhu_public_base_url(config),
|
|
1235
|
+
"public_health_url": lingzhu_health_url(config, public=True),
|
|
1236
|
+
"public_endpoint_url": lingzhu_sse_url(config, public=True),
|
|
1237
|
+
"gateway_port": lingzhu_gateway_port(config),
|
|
1238
|
+
"agent_id": lingzhu_agent_id(config),
|
|
1239
|
+
"auth_ak_masked": self._mask_secret(auth_ak),
|
|
1240
|
+
"generated_curl": lingzhu_generated_curl({**config, "auth_ak": auth_ak}),
|
|
1241
|
+
"generated_openclaw_config": lingzhu_generated_openclaw_config_text({**config, "auth_ak": auth_ak}),
|
|
1242
|
+
"packaged_bridge_dir": "assets/connectors/lingzhu/openclaw-bridge",
|
|
1243
|
+
"packaged_template_path": "assets/connectors/lingzhu/openclaw.lingzhu.config.template.json",
|
|
1244
|
+
"supported_commands": lingzhu_supported_commands(
|
|
1245
|
+
experimental_enabled=bool(config.get("enable_experimental_native_actions", False))
|
|
1246
|
+
),
|
|
1247
|
+
"public_ip_required": True,
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
def _probe_lingzhu_health(self, config: dict, *, timeout: float = 5.0) -> dict:
|
|
1251
|
+
url = lingzhu_health_url(config)
|
|
1252
|
+
if not url:
|
|
1253
|
+
return {"ok": False, "message": "Lingzhu health URL is empty."}
|
|
1254
|
+
try:
|
|
1255
|
+
request = Request(url, method="GET", headers={"Accept": "application/json"})
|
|
1256
|
+
with urlopen(request, timeout=timeout) as response: # noqa: S310
|
|
1257
|
+
payload = json.loads(response.read().decode("utf-8", errors="replace") or "{}")
|
|
1258
|
+
status_code = response.status
|
|
1259
|
+
return {
|
|
1260
|
+
"ok": True,
|
|
1261
|
+
"status_code": status_code,
|
|
1262
|
+
"status": payload.get("status"),
|
|
1263
|
+
"payload": payload,
|
|
1264
|
+
}
|
|
1265
|
+
except URLError as exc:
|
|
1266
|
+
return {"ok": False, "message": str(exc.reason or exc)}
|
|
1267
|
+
except Exception as exc:
|
|
1268
|
+
return {"ok": False, "message": str(exc)}
|
|
1269
|
+
|
|
1270
|
+
def _probe_lingzhu_sse(self, config: dict, *, timeout: float = 8.0) -> dict:
|
|
1271
|
+
url = lingzhu_sse_url(config)
|
|
1272
|
+
auth_ak = self._secret(config, "auth_ak", "auth_ak_env")
|
|
1273
|
+
if not url:
|
|
1274
|
+
return {"ok": False, "message": "Lingzhu SSE URL is empty."}
|
|
1275
|
+
if not auth_ak:
|
|
1276
|
+
return {"ok": False, "message": "Lingzhu auth_ak is empty."}
|
|
1277
|
+
request = Request(
|
|
1278
|
+
url,
|
|
1279
|
+
method="POST",
|
|
1280
|
+
headers={
|
|
1281
|
+
"Accept": "text/event-stream",
|
|
1282
|
+
"Authorization": f"Bearer {auth_ak}",
|
|
1283
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1284
|
+
},
|
|
1285
|
+
data=json.dumps(lingzhu_probe_payload(config), ensure_ascii=False).encode("utf-8"),
|
|
1286
|
+
)
|
|
1287
|
+
try:
|
|
1288
|
+
with urlopen(request, timeout=timeout) as response: # noqa: S310
|
|
1289
|
+
preview = response.read(512).decode("utf-8", errors="replace")
|
|
1290
|
+
content_type = str(response.headers.get("Content-Type") or "")
|
|
1291
|
+
ok = "text/event-stream" in content_type or "event:" in preview or "data:" in preview
|
|
1292
|
+
return {
|
|
1293
|
+
"ok": ok,
|
|
1294
|
+
"content_type": content_type,
|
|
1295
|
+
"preview": self._compact_probe_text(preview, limit=512),
|
|
1296
|
+
"message": None if ok else "Lingzhu SSE probe did not return an event-stream payload.",
|
|
1297
|
+
}
|
|
1298
|
+
except URLError as exc:
|
|
1299
|
+
return {"ok": False, "message": str(exc.reason or exc)}
|
|
1300
|
+
except Exception as exc:
|
|
1301
|
+
return {"ok": False, "message": str(exc)}
|
|
1302
|
+
|
|
1303
|
+
@staticmethod
|
|
1304
|
+
def _mask_secret(value: str) -> str:
|
|
1305
|
+
text = str(value or "").strip()
|
|
1306
|
+
if not text:
|
|
1307
|
+
return ""
|
|
1308
|
+
if len(text) <= 8:
|
|
1309
|
+
return "*" * len(text)
|
|
1310
|
+
return f"{text[:4]}{'*' * (len(text) - 8)}{text[-4:]}"
|
|
1311
|
+
|
|
1119
1312
|
def _normalize_named_payload(self, name: str, payload: dict) -> dict:
|
|
1120
1313
|
if not isinstance(payload, dict):
|
|
1121
1314
|
return default_payload(name, self.home)
|
|
@@ -1145,6 +1338,8 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1145
1338
|
for legacy_key in ("mode", "relay_url", "relay_auth_token", "public_callback_url", "webhook_verify_signature"):
|
|
1146
1339
|
sanitized_payload.pop(legacy_key, None)
|
|
1147
1340
|
sanitized_payload["transport"] = "gateway_direct"
|
|
1341
|
+
elif connector_name == "lingzhu":
|
|
1342
|
+
sanitized_payload["transport"] = "openclaw_sse"
|
|
1148
1343
|
elif "transport" not in sanitized_payload:
|
|
1149
1344
|
inferred_transport = infer_connector_transport(connector_name, {**base, **sanitized_payload})
|
|
1150
1345
|
if inferred_transport:
|
|
@@ -59,6 +59,8 @@ def infer_connector_transport(name: str, config: dict[str, Any] | None) -> str:
|
|
|
59
59
|
):
|
|
60
60
|
return "legacy_meta_cloud"
|
|
61
61
|
return "local_session"
|
|
62
|
+
if normalized == "lingzhu":
|
|
63
|
+
return "openclaw_sse"
|
|
62
64
|
if relay_url and mode == "relay":
|
|
63
65
|
return "relay"
|
|
64
66
|
return "direct"
|
|
@@ -194,7 +194,7 @@ npm --prefix src/ui run build</pre>
|
|
|
194
194
|
return get_acp_bridge_status().as_dict()
|
|
195
195
|
|
|
196
196
|
def connectors(self) -> list[dict]:
|
|
197
|
-
return
|
|
197
|
+
return self.app.list_connector_statuses()
|
|
198
198
|
|
|
199
199
|
def baselines(self) -> list[dict]:
|
|
200
200
|
return self.app.artifact_service.baselines.list_entries()
|
|
@@ -234,6 +234,9 @@ npm --prefix src/ui run build</pre>
|
|
|
234
234
|
title = body.get("title", "").strip() or None
|
|
235
235
|
quest_id = body.get("quest_id", "").strip() or None
|
|
236
236
|
source = body.get("source", "").strip() or "web"
|
|
237
|
+
preferred_connector_conversation_id = (
|
|
238
|
+
str(body.get("preferred_connector_conversation_id") or "").strip() or None
|
|
239
|
+
)
|
|
237
240
|
requested_baseline_ref = body.get("requested_baseline_ref")
|
|
238
241
|
startup_contract = body.get("startup_contract")
|
|
239
242
|
auto_start = body.get("auto_start") is True
|
|
@@ -246,6 +249,7 @@ npm --prefix src/ui run build</pre>
|
|
|
246
249
|
title=title,
|
|
247
250
|
quest_id=quest_id,
|
|
248
251
|
source=source,
|
|
252
|
+
preferred_connector_conversation_id=preferred_connector_conversation_id,
|
|
249
253
|
requested_baseline_ref=requested_baseline_ref if isinstance(requested_baseline_ref, dict) else None,
|
|
250
254
|
startup_contract=startup_contract if isinstance(startup_contract, dict) else None,
|
|
251
255
|
)
|
|
@@ -577,6 +581,33 @@ npm --prefix src/ui run build</pre>
|
|
|
577
581
|
return 400, {"ok": False, "message": str(exc)}
|
|
578
582
|
return result
|
|
579
583
|
|
|
584
|
+
def terminal_attach(self, quest_id: str, session_id: str, body: dict | None = None) -> dict | tuple[int, dict]:
|
|
585
|
+
_unused = body or {}
|
|
586
|
+
quest_root = self.app.quest_service._quest_root(quest_id)
|
|
587
|
+
try:
|
|
588
|
+
session = self.app.bash_exec_service.get_session(quest_root, session_id)
|
|
589
|
+
except FileNotFoundError:
|
|
590
|
+
return 404, {"ok": False, "message": f"Unknown terminal session `{session_id}`."}
|
|
591
|
+
if str(session.get("kind") or "").lower() != "terminal":
|
|
592
|
+
return 400, {"ok": False, "message": "not_terminal_session"}
|
|
593
|
+
if str(session.get("status") or "").lower() in {"completed", "failed", "terminated"}:
|
|
594
|
+
return 409, {"ok": False, "message": "terminal_session_inactive", "session": session}
|
|
595
|
+
try:
|
|
596
|
+
token = self.app.bash_exec_service.issue_terminal_attach_token(quest_root, session_id)
|
|
597
|
+
except ValueError as exc:
|
|
598
|
+
return 409, {"ok": False, "message": str(exc), "session": session}
|
|
599
|
+
attach_port = self.app._terminal_attach_port
|
|
600
|
+
if not attach_port:
|
|
601
|
+
return 503, {"ok": False, "message": "terminal_attach_server_unavailable", "session": session}
|
|
602
|
+
return {
|
|
603
|
+
"ok": True,
|
|
604
|
+
"port": attach_port,
|
|
605
|
+
"path": "/terminal/attach",
|
|
606
|
+
"token": token["token"],
|
|
607
|
+
"expires_at": token["expires_at"],
|
|
608
|
+
"session": session,
|
|
609
|
+
}
|
|
610
|
+
|
|
580
611
|
def terminal_restore(self, quest_id: str, session_id: str, path: str) -> dict | tuple[int, dict]:
|
|
581
612
|
query = self.parse_query(path)
|
|
582
613
|
commands_raw = ((query.get("commands") or ["10"])[0] or "10").strip()
|
|
@@ -780,7 +811,13 @@ npm --prefix src/ui run build</pre>
|
|
|
780
811
|
query = self.parse_query(path)
|
|
781
812
|
revision = ((query.get("revision") or [""])[0] or "").strip() or None
|
|
782
813
|
mode = ((query.get("mode") or [""])[0] or "").strip() or None
|
|
783
|
-
|
|
814
|
+
profile = ((query.get("profile") or [""])[0] or "").strip() or None
|
|
815
|
+
return self.app.quest_service.explorer(
|
|
816
|
+
quest_id,
|
|
817
|
+
revision=revision,
|
|
818
|
+
mode=mode,
|
|
819
|
+
profile=profile,
|
|
820
|
+
)
|
|
784
821
|
|
|
785
822
|
def quest_search(self, quest_id: str, path: str) -> dict:
|
|
786
823
|
query = self.parse_query(path)
|
|
@@ -1287,9 +1324,17 @@ npm --prefix src/ui run build</pre>
|
|
|
1287
1324
|
return self.app.config_manager.test_named_text(body["name"], body["content"], live=bool(body.get("live", True)))
|
|
1288
1325
|
|
|
1289
1326
|
def asset(self, asset_path: str) -> tuple[int, dict, bytes]:
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1327
|
+
candidate_roots = [
|
|
1328
|
+
self.app.repo_root / "src" / "ui" / "public" / "assets",
|
|
1329
|
+
self.app.repo_root / "assets",
|
|
1330
|
+
]
|
|
1331
|
+
path = None
|
|
1332
|
+
for root in candidate_roots:
|
|
1333
|
+
candidate = resolve_within(root, asset_path)
|
|
1334
|
+
if candidate.exists() and candidate.is_file():
|
|
1335
|
+
path = candidate
|
|
1336
|
+
break
|
|
1337
|
+
if path is None:
|
|
1293
1338
|
return 404, {"Content-Type": "text/plain; charset=utf-8"}, b"Not Found"
|
|
1294
1339
|
mime_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
|
|
1295
1340
|
return 200, {"Content-Type": mime_type}, path.read_bytes()
|
|
@@ -44,6 +44,7 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
44
44
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/bash/sessions/(?P<bash_id>[^/]+)/stop$"), "bash_stop"),
|
|
45
45
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/terminal/session/ensure$"), "terminal_session_ensure"),
|
|
46
46
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/terminal/history$"), "terminal_history"),
|
|
47
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/terminal/sessions/(?P<session_id>[^/]+)/attach$"), "terminal_attach"),
|
|
47
48
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/terminal/sessions/(?P<session_id>[^/]+)/input$"), "terminal_input"),
|
|
48
49
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/terminal/sessions/(?P<session_id>[^/]+)/restore$"), "terminal_restore"),
|
|
49
50
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/terminal/sessions/(?P<session_id>[^/]+)/stream$"), "terminal_stream"),
|