@researai/deepscientist 1.5.7 → 1.5.9
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 +8 -4
- package/bin/ds.js +224 -9
- package/docs/en/00_QUICK_START.md +2 -2
- package/docs/en/07_MEMORY_AND_MCP.md +40 -3
- package/docs/en/99_ACKNOWLEDGEMENTS.md +1 -0
- package/docs/zh/00_QUICK_START.md +2 -2
- package/docs/zh/07_MEMORY_AND_MCP.md +40 -3
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +1 -0
- package/install.sh +34 -0
- package/package.json +2 -2
- package/pyproject.toml +2 -2
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +1 -0
- package/src/deepscientist/artifact/metrics.py +814 -83
- package/src/deepscientist/artifact/schemas.py +1 -0
- package/src/deepscientist/artifact/service.py +2001 -229
- package/src/deepscientist/bash_exec/monitor.py +1 -1
- package/src/deepscientist/bash_exec/service.py +17 -9
- package/src/deepscientist/channels/qq.py +17 -0
- package/src/deepscientist/channels/relay.py +16 -0
- package/src/deepscientist/config/models.py +6 -0
- package/src/deepscientist/config/service.py +70 -2
- package/src/deepscientist/daemon/api/handlers.py +414 -14
- package/src/deepscientist/daemon/api/router.py +4 -0
- package/src/deepscientist/daemon/app.py +292 -21
- package/src/deepscientist/gitops/diff.py +6 -10
- package/src/deepscientist/mcp/server.py +191 -40
- package/src/deepscientist/prompts/builder.py +65 -19
- package/src/deepscientist/quest/node_traces.py +129 -2
- package/src/deepscientist/quest/service.py +140 -34
- package/src/deepscientist/quest/stage_views.py +175 -33
- package/src/deepscientist/registries/baseline.py +56 -4
- package/src/deepscientist/runners/codex.py +1 -1
- package/src/prompts/connectors/qq.md +1 -1
- package/src/prompts/contracts/shared_interaction.md +14 -0
- package/src/prompts/system.md +113 -32
- package/src/skills/analysis-campaign/SKILL.md +10 -14
- package/src/skills/baseline/SKILL.md +51 -38
- package/src/skills/baseline/references/baseline-plan-template.md +2 -0
- package/src/skills/decision/SKILL.md +12 -8
- package/src/skills/experiment/SKILL.md +28 -16
- package/src/skills/experiment/references/main-experiment-plan-template.md +2 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +3 -8
- package/src/skills/idea/SKILL.md +18 -8
- 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/intake-audit/SKILL.md +2 -8
- package/src/skills/rebuttal/SKILL.md +2 -8
- package/src/skills/review/SKILL.md +2 -8
- package/src/skills/scout/SKILL.md +2 -8
- package/src/skills/write/SKILL.md +53 -17
- package/src/skills/write/templates/DEEPSCIENTIST_NOTES.md +21 -0
- package/src/skills/write/templates/README.md +408 -0
- package/src/skills/write/templates/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/write/templates/aaai2026/README.md +534 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bib +111 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bst +1493 -0
- package/src/skills/write/templates/aaai2026/aaai2026.sty +315 -0
- package/src/skills/write/templates/acl/README.md +50 -0
- package/src/skills/write/templates/acl/acl.sty +312 -0
- package/src/skills/write/templates/acl/acl_latex.tex +377 -0
- package/src/skills/write/templates/acl/acl_lualatex.tex +101 -0
- package/src/skills/write/templates/acl/acl_natbib.bst +1940 -0
- package/src/skills/write/templates/acl/anthology.bib.txt +26 -0
- package/src/skills/write/templates/acl/custom.bib +70 -0
- package/src/skills/write/templates/acl/formatting.md +326 -0
- package/src/skills/write/templates/asplos2027/main.tex +459 -0
- package/src/skills/write/templates/asplos2027/references.bib +135 -0
- package/src/skills/write/templates/colm2025/README.md +3 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bib +11 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bst +1440 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.sty +218 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.tex +305 -0
- package/src/skills/write/templates/colm2025/fancyhdr.sty +485 -0
- package/src/skills/write/templates/colm2025/math_commands.tex +508 -0
- package/src/skills/write/templates/colm2025/natbib.sty +1246 -0
- package/src/skills/write/templates/iclr2026/fancyhdr.sty +485 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/src/skills/write/templates/iclr2026/math_commands.tex +508 -0
- package/src/skills/write/templates/iclr2026/natbib.sty +1246 -0
- package/src/skills/write/templates/icml2026/algorithm.sty +79 -0
- package/src/skills/write/templates/icml2026/algorithmic.sty +201 -0
- package/src/skills/write/templates/icml2026/example_paper.bib +75 -0
- package/src/skills/write/templates/icml2026/example_paper.tex +662 -0
- package/src/skills/write/templates/icml2026/fancyhdr.sty +864 -0
- package/src/skills/write/templates/icml2026/icml2026.bst +1443 -0
- package/src/skills/write/templates/icml2026/icml2026.sty +767 -0
- package/src/skills/write/templates/neurips2025/Makefile +36 -0
- package/src/skills/write/templates/neurips2025/extra_pkgs.tex +53 -0
- package/src/skills/write/templates/neurips2025/main.tex +38 -0
- package/src/skills/write/templates/neurips2025/neurips.sty +382 -0
- package/src/skills/write/templates/nsdi2027/main.tex +426 -0
- package/src/skills/write/templates/nsdi2027/references.bib +151 -0
- package/src/skills/write/templates/nsdi2027/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/osdi2026/main.tex +429 -0
- package/src/skills/write/templates/osdi2026/references.bib +150 -0
- package/src/skills/write/templates/osdi2026/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/sosp2026/main.tex +532 -0
- package/src/skills/write/templates/sosp2026/references.bib +148 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BS3V4ZOk.js → AiManusChatView-BKZ103sn.js} +110 -14
- package/src/ui/dist/assets/{AnalysisPlugin-DLPXQsmr.js → AnalysisPlugin-mTTzGAlK.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-C-Fr9knQ.js → AutoFigurePlugin-C_wWw4AP.js} +5 -5
- package/src/ui/dist/assets/{CliPlugin-Dd8AHzFg.js → CliPlugin-BH58n3GY.js} +9 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-Dg-RepTl.js → CodeEditorPlugin-BKGRUH7e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-D2J_3nyt.js → CodeViewerPlugin-BMADwFWJ.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ChRLLKNb.js → DocViewerPlugin-ZOnTIHLN.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DgHfcved.js → GitDiffViewerPlugin-CQ7h1Djm.js} +830 -86
- package/src/ui/dist/assets/{ImageViewerPlugin-C89GZMBy.js → ImageViewerPlugin-GVS5MsnC.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BUfIwUcb.js → LabCopilotPanel-BZNv1JML.js} +10 -10
- package/src/ui/dist/assets/{LabPlugin-zvUmQUMq.js → LabPlugin-TWcJsdQA.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-C1SSNuWp.js → LatexPlugin-DIjHiR2x.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D2Mf5tU5.js → MarkdownViewerPlugin-D3ooGAH0.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-CF4LgiS2.js → MarketplacePlugin-DfVfE9hN.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-BM7Bgwlv.js → NotebookEditor-DDl0_Mc0.js} +1 -1
- package/src/ui/dist/assets/{index-Be0NAmh8.js → NotebookEditor-s8JhzuX1.js} +12 -155
- package/src/ui/dist/assets/{PdfLoader-Bc5qfD-Z.js → PdfLoader-C2Sf6SJM.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-sh1-IRcp.js → PdfMarkdownPlugin-CXFLoIsa.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-C_a7CpWG.js → PdfViewerPlugin-BYTmz2fK.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-L4z3HcLf.js → SearchPlugin-CjWBI1O9.js} +1 -1
- package/src/ui/dist/assets/{Stepper-Dk4aQ3fN.js → Stepper-B0Dd8CxK.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-BsNtlKVo.js → TextViewerPlugin-DdOBU3-S.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-BpeDcZ5_.js → VNCViewer-B8HGgLwQ.js} +9 -9
- package/src/ui/dist/assets/{bibtex-C4QI-bbj.js → bibtex-CKaefIN2.js} +1 -1
- package/src/ui/dist/assets/{code-DuMINRsg.js → code-BWAY76JP.js} +1 -1
- package/src/ui/dist/assets/{file-content-C3N-432K.js → file-content-C1NwU5oQ.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CffQ4ZMg.js → file-diff-panel-CywslwB9.js} +1 -1
- package/src/ui/dist/assets/{file-socket-CRH59PCO.js → file-socket-B4kzuOBQ.js} +1 -1
- package/src/ui/dist/assets/{file-utils-vYGtW2mI.js → file-utils-H2fjA46S.js} +1 -1
- package/src/ui/dist/assets/{image-DBVGaooo.js → image-D-NZM-6P.js} +1 -1
- package/src/ui/dist/assets/{index-B1P6hQRJ.js → index-7Chr1g9c.js} +3734 -1862
- package/src/ui/dist/assets/{index-DjSFDmgB.js → index-BdM1Gqfr.js} +2 -2
- package/src/ui/dist/assets/{index-BpjYH9Vg.js → index-CDxNdQdz.js} +1 -1
- package/src/ui/dist/assets/{index-Do9N28uB.css → index-DGIYDuTv.css} +163 -34
- package/src/ui/dist/assets/index-DHZJ_0TI.js +159 -0
- package/src/ui/dist/assets/{message-square-BsPDBhiY.js → message-square-BzjLiXir.js} +1 -1
- package/src/ui/dist/assets/{monaco-BTkdPojV.js → monaco-Cb2uKKe6.js} +1 -1
- package/src/ui/dist/assets/{popover-cWjCk-vc.js → popover-Bg72DGgT.js} +1 -1
- package/src/ui/dist/assets/{project-sync-CXn530xb.js → project-sync-Ce_0BglY.js} +1 -1
- package/src/ui/dist/assets/{sigma-04Jr12jg.js → sigma-DPaACDrh.js} +1 -1
- package/src/ui/dist/assets/{tooltip-BdVDl0G5.js → tooltip-C_mA6R0w.js} +1 -1
- package/src/ui/dist/assets/{trash-CB_GlQyC.js → trash-BvTgE5__.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-BL932NwS.js → useCliAccess-CgPeMOwP.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-B2WK7Tvq.js → useFileDiffOverlay-xPhz7P5B.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-YC68g12z.js → wrap-text-C3Un3YQr.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-C0RJvFiJ.js → zoom-out-BgxLa0Ri.js} +1 -1
- package/src/ui/dist/index.html +5 -2
- /package/src/ui/dist/assets/{index-CccQYZjX.css → NotebookEditor-CccQYZjX.css} +0 -0
|
@@ -39,6 +39,7 @@ from ..connector_profiles import (
|
|
|
39
39
|
)
|
|
40
40
|
from ..connector_runtime import conversation_identity_key, format_conversation_id, normalize_conversation_id, parse_conversation_id
|
|
41
41
|
from ..config import ConfigManager
|
|
42
|
+
from ..config.models import SYSTEM_CONNECTOR_NAMES
|
|
42
43
|
from ..home import repo_root
|
|
43
44
|
from ..memory import MemoryService
|
|
44
45
|
from ..network import urlopen_with_proxy as urlopen
|
|
@@ -80,6 +81,7 @@ class DaemonApp:
|
|
|
80
81
|
self.daemon_managed_by = str(os.environ.get("DS_DAEMON_MANAGED_BY") or "manual").strip() or "manual"
|
|
81
82
|
self.repo_root = repo_root()
|
|
82
83
|
self.config_manager = ConfigManager(home)
|
|
84
|
+
self.runtime_config = self.config_manager.load_runtime_config()
|
|
83
85
|
self.runners_config = self.config_manager.load_runners_config()
|
|
84
86
|
self.connectors_config = self.config_manager.load_named_normalized("connectors")
|
|
85
87
|
self.skill_installer = SkillInstaller(self.repo_root, home)
|
|
@@ -90,7 +92,7 @@ class DaemonApp:
|
|
|
90
92
|
self.bash_exec_service = BashExecService(home)
|
|
91
93
|
self.team_service = SingleTeamService(home)
|
|
92
94
|
self.cloud_service = CloudLinkService(home)
|
|
93
|
-
config = self.
|
|
95
|
+
config = self.runtime_config
|
|
94
96
|
skill_config = config.get("skills") if isinstance(config.get("skills"), dict) else {}
|
|
95
97
|
self.skill_sync_summary = self.skill_installer.ensure_release_sync(
|
|
96
98
|
installed_version=__version__,
|
|
@@ -145,9 +147,13 @@ class DaemonApp:
|
|
|
145
147
|
|
|
146
148
|
def list_connector_statuses(self) -> list[dict[str, object]]:
|
|
147
149
|
title_by_quest = self._quest_titles_by_id()
|
|
148
|
-
items = [
|
|
150
|
+
items = [
|
|
151
|
+
self._augment_connector_status(channel.status(), title_by_quest=title_by_quest)
|
|
152
|
+
for name, channel in self.channels.items()
|
|
153
|
+
if name == "local" or self._is_connector_system_enabled(name)
|
|
154
|
+
]
|
|
149
155
|
lingzhu_config = self.connectors_config.get("lingzhu")
|
|
150
|
-
if isinstance(lingzhu_config, dict):
|
|
156
|
+
if isinstance(lingzhu_config, dict) and self._is_connector_system_enabled("lingzhu"):
|
|
151
157
|
items.append(self._augment_connector_status(self.config_manager.lingzhu_snapshot(lingzhu_config), title_by_quest=title_by_quest))
|
|
152
158
|
return items
|
|
153
159
|
|
|
@@ -346,6 +352,8 @@ class DaemonApp:
|
|
|
346
352
|
continue
|
|
347
353
|
if connector_name not in self.channels:
|
|
348
354
|
continue
|
|
355
|
+
if not self._is_connector_system_enabled(connector_name):
|
|
356
|
+
continue
|
|
349
357
|
if parsed is not None and str(parsed.get("connector") or "").strip().lower() != connector_name:
|
|
350
358
|
continue
|
|
351
359
|
normalized_by_connector[connector_name] = {
|
|
@@ -459,19 +467,203 @@ class DaemonApp:
|
|
|
459
467
|
b"Terminal attach token is invalid or expired.",
|
|
460
468
|
)
|
|
461
469
|
if runtime is None:
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
470
|
+
try:
|
|
471
|
+
session = self.bash_exec_service.get_session(attach_token.quest_root, attach_token.bash_id)
|
|
472
|
+
except FileNotFoundError:
|
|
473
|
+
return Response(
|
|
474
|
+
404,
|
|
475
|
+
"Not Found",
|
|
476
|
+
Headers({"Content-Type": "text/plain; charset=utf-8"}),
|
|
477
|
+
b"Terminal session is no longer available.",
|
|
478
|
+
)
|
|
479
|
+
status = str(session.get("status") or "").strip().lower()
|
|
480
|
+
kind = str(session.get("kind") or "").strip().lower()
|
|
481
|
+
if status in {"completed", "failed", "terminated"} or kind not in {"exec"}:
|
|
482
|
+
return Response(
|
|
483
|
+
409,
|
|
484
|
+
"Conflict",
|
|
485
|
+
Headers({"Content-Type": "text/plain; charset=utf-8"}),
|
|
486
|
+
b"Terminal runtime is no longer active.",
|
|
487
|
+
)
|
|
468
488
|
setattr(connection, "_ds_terminal_attach_token", token)
|
|
469
489
|
return None
|
|
470
490
|
|
|
491
|
+
def _handle_logged_terminal_attach_connection(
|
|
492
|
+
self,
|
|
493
|
+
connection: ServerConnection,
|
|
494
|
+
*,
|
|
495
|
+
attach_token,
|
|
496
|
+
send_lock: threading.Lock,
|
|
497
|
+
) -> None:
|
|
498
|
+
session = self.bash_exec_service.get_session(attach_token.quest_root, attach_token.bash_id)
|
|
499
|
+
stop_event = threading.Event()
|
|
500
|
+
|
|
501
|
+
with send_lock:
|
|
502
|
+
connection.send(
|
|
503
|
+
json.dumps(
|
|
504
|
+
{
|
|
505
|
+
"type": "ready",
|
|
506
|
+
"bash_id": attach_token.bash_id,
|
|
507
|
+
"status": session.get("status"),
|
|
508
|
+
"cwd": session.get("cwd"),
|
|
509
|
+
"workdir": session.get("workdir"),
|
|
510
|
+
},
|
|
511
|
+
ensure_ascii=False,
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
def _encode_exec_log_entry(entry: dict[str, Any]) -> bytes:
|
|
516
|
+
line = str(entry.get("line") or "")
|
|
517
|
+
stream = str(entry.get("stream") or "").strip().lower()
|
|
518
|
+
if line.startswith("__DS_PROGRESS__") or line.startswith("__DS_BASH_STATUS__"):
|
|
519
|
+
return b""
|
|
520
|
+
if line.startswith("__DS_BASH_CR__"):
|
|
521
|
+
payload = line[len("__DS_BASH_CR__") :].lstrip()
|
|
522
|
+
return f"\r\x1b[K{payload}".encode("utf-8", errors="replace")
|
|
523
|
+
if stream in {"prompt", "partial"}:
|
|
524
|
+
return line.encode("utf-8", errors="replace")
|
|
525
|
+
if stream == "system" and not line.strip():
|
|
526
|
+
return b""
|
|
527
|
+
return f"{line}\n".encode("utf-8", errors="replace")
|
|
528
|
+
|
|
529
|
+
def _relay_output() -> None:
|
|
530
|
+
last_seq = 0
|
|
531
|
+
try:
|
|
532
|
+
entries, meta = self.bash_exec_service.read_log_entries(
|
|
533
|
+
attach_token.quest_root,
|
|
534
|
+
attach_token.bash_id,
|
|
535
|
+
limit=2000,
|
|
536
|
+
order="asc",
|
|
537
|
+
)
|
|
538
|
+
if isinstance(meta.get("latest_seq"), int):
|
|
539
|
+
last_seq = int(meta["latest_seq"])
|
|
540
|
+
elif entries:
|
|
541
|
+
last_seq = max(int(item.get("seq") or 0) for item in entries)
|
|
542
|
+
for entry in entries:
|
|
543
|
+
payload = _encode_exec_log_entry(entry)
|
|
544
|
+
if not payload:
|
|
545
|
+
continue
|
|
546
|
+
with send_lock:
|
|
547
|
+
connection.send(payload)
|
|
548
|
+
|
|
549
|
+
while not stop_event.is_set():
|
|
550
|
+
entries, _meta = self.bash_exec_service.read_log_entries(
|
|
551
|
+
attach_token.quest_root,
|
|
552
|
+
attach_token.bash_id,
|
|
553
|
+
limit=400,
|
|
554
|
+
after_seq=last_seq,
|
|
555
|
+
order="asc",
|
|
556
|
+
)
|
|
557
|
+
for entry in entries:
|
|
558
|
+
last_seq = max(last_seq, int(entry.get("seq") or 0))
|
|
559
|
+
payload = _encode_exec_log_entry(entry)
|
|
560
|
+
if not payload:
|
|
561
|
+
continue
|
|
562
|
+
with send_lock:
|
|
563
|
+
connection.send(payload)
|
|
564
|
+
current = self.bash_exec_service.get_session(
|
|
565
|
+
attach_token.quest_root,
|
|
566
|
+
attach_token.bash_id,
|
|
567
|
+
)
|
|
568
|
+
status = str(current.get("status") or "").strip().lower()
|
|
569
|
+
if status in {"completed", "failed", "terminated"}:
|
|
570
|
+
with send_lock:
|
|
571
|
+
connection.send(
|
|
572
|
+
json.dumps(
|
|
573
|
+
{
|
|
574
|
+
"type": "exit",
|
|
575
|
+
"bash_id": attach_token.bash_id,
|
|
576
|
+
"status": current.get("status"),
|
|
577
|
+
"exit_code": current.get("exit_code"),
|
|
578
|
+
"stop_reason": current.get("stop_reason"),
|
|
579
|
+
"finished_at": current.get("finished_at"),
|
|
580
|
+
},
|
|
581
|
+
ensure_ascii=False,
|
|
582
|
+
)
|
|
583
|
+
)
|
|
584
|
+
return
|
|
585
|
+
time.sleep(TERMINAL_STREAM_IDLE_SLEEP_SECONDS)
|
|
586
|
+
except Exception as exc:
|
|
587
|
+
if stop_event.is_set():
|
|
588
|
+
return
|
|
589
|
+
try:
|
|
590
|
+
with send_lock:
|
|
591
|
+
connection.send(
|
|
592
|
+
json.dumps({"type": "error", "message": str(exc)}, ensure_ascii=False)
|
|
593
|
+
)
|
|
594
|
+
except Exception:
|
|
595
|
+
pass
|
|
596
|
+
|
|
597
|
+
relay_thread = threading.Thread(
|
|
598
|
+
target=_relay_output,
|
|
599
|
+
daemon=True,
|
|
600
|
+
name=f"exec-attach-{attach_token.bash_id}",
|
|
601
|
+
)
|
|
602
|
+
relay_thread.start()
|
|
603
|
+
try:
|
|
604
|
+
while True:
|
|
605
|
+
try:
|
|
606
|
+
message = connection.recv()
|
|
607
|
+
except ConnectionClosed:
|
|
608
|
+
break
|
|
609
|
+
if message is None:
|
|
610
|
+
break
|
|
611
|
+
if isinstance(message, bytes):
|
|
612
|
+
self.bash_exec_service.append_terminal_input(
|
|
613
|
+
attach_token.quest_root,
|
|
614
|
+
attach_token.bash_id,
|
|
615
|
+
data=message.decode("utf-8", errors="replace"),
|
|
616
|
+
source="web-pty",
|
|
617
|
+
)
|
|
618
|
+
continue
|
|
619
|
+
try:
|
|
620
|
+
payload = json.loads(message)
|
|
621
|
+
except json.JSONDecodeError:
|
|
622
|
+
continue
|
|
623
|
+
if not isinstance(payload, dict):
|
|
624
|
+
continue
|
|
625
|
+
message_type = str(payload.get("type") or "").strip().lower()
|
|
626
|
+
if message_type == "input":
|
|
627
|
+
self.bash_exec_service.append_terminal_input(
|
|
628
|
+
attach_token.quest_root,
|
|
629
|
+
attach_token.bash_id,
|
|
630
|
+
data=str(payload.get("data") or ""),
|
|
631
|
+
source="web-pty",
|
|
632
|
+
)
|
|
633
|
+
continue
|
|
634
|
+
if message_type == "binary_input":
|
|
635
|
+
raw = str(payload.get("data") or "")
|
|
636
|
+
if raw:
|
|
637
|
+
self.bash_exec_service.append_terminal_input(
|
|
638
|
+
attach_token.quest_root,
|
|
639
|
+
attach_token.bash_id,
|
|
640
|
+
data=base64.b64decode(raw).decode("utf-8", errors="replace"),
|
|
641
|
+
source="web-pty",
|
|
642
|
+
)
|
|
643
|
+
continue
|
|
644
|
+
if message_type == "resize":
|
|
645
|
+
cols = int(payload.get("cols") or 0)
|
|
646
|
+
rows = int(payload.get("rows") or 0)
|
|
647
|
+
self.bash_exec_service.resize_terminal_session(
|
|
648
|
+
attach_token.quest_root,
|
|
649
|
+
attach_token.bash_id,
|
|
650
|
+
cols=cols,
|
|
651
|
+
rows=rows,
|
|
652
|
+
)
|
|
653
|
+
continue
|
|
654
|
+
if message_type == "detach":
|
|
655
|
+
break
|
|
656
|
+
if message_type == "ping":
|
|
657
|
+
with send_lock:
|
|
658
|
+
connection.send(json.dumps({"type": "pong"}, ensure_ascii=False))
|
|
659
|
+
finally:
|
|
660
|
+
stop_event.set()
|
|
661
|
+
relay_thread.join(timeout=1)
|
|
662
|
+
|
|
471
663
|
def _handle_terminal_attach_connection(self, connection: ServerConnection) -> None:
|
|
472
664
|
token_value = str(getattr(connection, "_ds_terminal_attach_token", "") or "").strip()
|
|
473
665
|
attach_token, runtime = self.bash_exec_service.consume_terminal_attach_token(token_value)
|
|
474
|
-
if attach_token is None
|
|
666
|
+
if attach_token is None:
|
|
475
667
|
try:
|
|
476
668
|
connection.close(code=1011, reason="terminal_attach_unavailable")
|
|
477
669
|
except Exception:
|
|
@@ -479,6 +671,31 @@ class DaemonApp:
|
|
|
479
671
|
return
|
|
480
672
|
|
|
481
673
|
send_lock = threading.Lock()
|
|
674
|
+
if runtime is None:
|
|
675
|
+
try:
|
|
676
|
+
self._handle_logged_terminal_attach_connection(
|
|
677
|
+
connection,
|
|
678
|
+
attach_token=attach_token,
|
|
679
|
+
send_lock=send_lock,
|
|
680
|
+
)
|
|
681
|
+
except Exception as exc:
|
|
682
|
+
try:
|
|
683
|
+
with send_lock:
|
|
684
|
+
connection.send(
|
|
685
|
+
json.dumps(
|
|
686
|
+
{"type": "error", "message": str(exc)},
|
|
687
|
+
ensure_ascii=False,
|
|
688
|
+
)
|
|
689
|
+
)
|
|
690
|
+
except Exception:
|
|
691
|
+
pass
|
|
692
|
+
finally:
|
|
693
|
+
try:
|
|
694
|
+
connection.close()
|
|
695
|
+
except Exception:
|
|
696
|
+
pass
|
|
697
|
+
return
|
|
698
|
+
|
|
482
699
|
client = TerminalClient(
|
|
483
700
|
client_id=generate_id("tclient"),
|
|
484
701
|
send_text=connection.send,
|
|
@@ -635,6 +852,43 @@ class DaemonApp:
|
|
|
635
852
|
factory = get_channel_factory(name)
|
|
636
853
|
return factory(home=self.home, app=self, config=self.connectors_config.get(name, {}))
|
|
637
854
|
|
|
855
|
+
def _system_enabled_connector_names(self) -> set[str]:
|
|
856
|
+
connectors = self.runtime_config.get("connectors") if isinstance(self.runtime_config.get("connectors"), dict) else {}
|
|
857
|
+
system_enabled = connectors.get("system_enabled") if isinstance(connectors.get("system_enabled"), dict) else {}
|
|
858
|
+
return {
|
|
859
|
+
name
|
|
860
|
+
for name in SYSTEM_CONNECTOR_NAMES
|
|
861
|
+
if bool(system_enabled.get(name, name == "qq"))
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
def _is_connector_system_enabled(self, connector_name: str) -> bool:
|
|
865
|
+
normalized = str(connector_name or "").strip().lower()
|
|
866
|
+
if normalized == "local":
|
|
867
|
+
return True
|
|
868
|
+
enabled = self._system_enabled_connector_names()
|
|
869
|
+
if normalized in enabled:
|
|
870
|
+
return True
|
|
871
|
+
if normalized in SYSTEM_CONNECTOR_NAMES:
|
|
872
|
+
return False
|
|
873
|
+
return True
|
|
874
|
+
|
|
875
|
+
def reload_runtime_config(self, *, restart_background: bool = True) -> dict[str, object]:
|
|
876
|
+
previous_enabled = self._system_enabled_connector_names()
|
|
877
|
+
self.runtime_config = self.config_manager.load_runtime_config()
|
|
878
|
+
logging_config = self.runtime_config.get("logging") if isinstance(self.runtime_config.get("logging"), dict) else {}
|
|
879
|
+
self.logger.level = str(logging_config.get("level") or "info").strip().lower() or "info"
|
|
880
|
+
enabled = self._system_enabled_connector_names()
|
|
881
|
+
restarted = False
|
|
882
|
+
if restart_background and self._server is not None and enabled != previous_enabled:
|
|
883
|
+
self._stop_background_connectors()
|
|
884
|
+
self._start_background_connectors()
|
|
885
|
+
restarted = True
|
|
886
|
+
return {
|
|
887
|
+
"ok": True,
|
|
888
|
+
"system_enabled_connectors": sorted(enabled),
|
|
889
|
+
"restarted_background_connectors": restarted,
|
|
890
|
+
}
|
|
891
|
+
|
|
638
892
|
def reload_connectors_config(self, *, restart_background: bool = True) -> dict[str, object]:
|
|
639
893
|
self.connectors_config = self.config_manager.load_named_normalized("connectors")
|
|
640
894
|
register_builtin_channels(home=self.home, connectors_config=self.connectors_config)
|
|
@@ -650,7 +904,11 @@ class DaemonApp:
|
|
|
650
904
|
return {
|
|
651
905
|
"ok": True,
|
|
652
906
|
"connectors": sorted(
|
|
653
|
-
name
|
|
907
|
+
name
|
|
908
|
+
for name, config in self.connectors_config.items()
|
|
909
|
+
if not str(name).startswith("_")
|
|
910
|
+
and isinstance(config, dict)
|
|
911
|
+
and self._is_connector_system_enabled(str(name))
|
|
654
912
|
),
|
|
655
913
|
}
|
|
656
914
|
|
|
@@ -671,8 +929,7 @@ class DaemonApp:
|
|
|
671
929
|
}
|
|
672
930
|
|
|
673
931
|
def _preferred_locale(self) -> str:
|
|
674
|
-
|
|
675
|
-
return str(config.get("default_locale") or "en-US").lower()
|
|
932
|
+
return str(self.runtime_config.get("default_locale") or "en-US").lower()
|
|
676
933
|
|
|
677
934
|
def _polite_copy(self, *, zh: str, en: str) -> str:
|
|
678
935
|
return zh if self._preferred_locale().startswith("zh") else en
|
|
@@ -741,7 +998,7 @@ class DaemonApp:
|
|
|
741
998
|
exclude_conversation_id: str | None = None,
|
|
742
999
|
preferred_connector_conversation_id: str | None = None,
|
|
743
1000
|
requested_connector_bindings: list[dict[str, object]] | None = None,
|
|
744
|
-
force_connector_rebind: bool =
|
|
1001
|
+
force_connector_rebind: bool = True,
|
|
745
1002
|
requested_baseline_ref: dict[str, object] | None = None,
|
|
746
1003
|
startup_contract: dict[str, object] | None = None,
|
|
747
1004
|
) -> dict:
|
|
@@ -2666,6 +2923,13 @@ class DaemonApp:
|
|
|
2666
2923
|
}
|
|
2667
2924
|
|
|
2668
2925
|
def handle_connector_inbound(self, connector_name: str, body: dict) -> dict:
|
|
2926
|
+
if not self._is_connector_system_enabled(connector_name):
|
|
2927
|
+
return {
|
|
2928
|
+
"ok": True,
|
|
2929
|
+
"accepted": False,
|
|
2930
|
+
"reason": "system_disabled",
|
|
2931
|
+
"message": f"Connector `{connector_name}` is disabled at the system level.",
|
|
2932
|
+
}
|
|
2669
2933
|
channel = self._channel_with_bindings(connector_name)
|
|
2670
2934
|
ingested = channel.ingest(body)
|
|
2671
2935
|
if not ingested.get("accepted", False):
|
|
@@ -3330,6 +3594,8 @@ class DaemonApp:
|
|
|
3330
3594
|
for connector_name, channel in self.channels.items():
|
|
3331
3595
|
if connector_name == "local":
|
|
3332
3596
|
continue
|
|
3597
|
+
if not self._is_connector_system_enabled(connector_name):
|
|
3598
|
+
continue
|
|
3333
3599
|
connector_config = self.connectors_config.get(connector_name, {})
|
|
3334
3600
|
if not isinstance(connector_config, dict):
|
|
3335
3601
|
continue
|
|
@@ -3523,6 +3789,8 @@ class DaemonApp:
|
|
|
3523
3789
|
}
|
|
3524
3790
|
|
|
3525
3791
|
def _maybe_auto_bind_connector_conversation(self, connector_name: str, conversation_id: str) -> dict | None:
|
|
3792
|
+
if not self._is_connector_system_enabled(connector_name):
|
|
3793
|
+
return None
|
|
3526
3794
|
connector_config = self.connectors_config.get(connector_name, {})
|
|
3527
3795
|
if not isinstance(connector_config, dict):
|
|
3528
3796
|
return None
|
|
@@ -3746,6 +4014,8 @@ class DaemonApp:
|
|
|
3746
4014
|
)
|
|
3747
4015
|
|
|
3748
4016
|
def _profiled_connector_configs(self, connector_name: str) -> list[tuple[str, str | None, dict[str, Any]]]:
|
|
4017
|
+
if not self._is_connector_system_enabled(connector_name):
|
|
4018
|
+
return []
|
|
3749
4019
|
connector_config = self.connectors_config.get(connector_name, {})
|
|
3750
4020
|
if not isinstance(connector_config, dict):
|
|
3751
4021
|
return []
|
|
@@ -3763,7 +4033,7 @@ class DaemonApp:
|
|
|
3763
4033
|
|
|
3764
4034
|
def _start_background_connectors(self) -> None:
|
|
3765
4035
|
qq_config = self.connectors_config.get("qq", {})
|
|
3766
|
-
if isinstance(qq_config, dict) and not self._qq_gateways:
|
|
4036
|
+
if self._is_connector_system_enabled("qq") and isinstance(qq_config, dict) and not self._qq_gateways:
|
|
3767
4037
|
profiles = list_qq_profiles(qq_config)
|
|
3768
4038
|
encode_profile_id = len(profiles) > 1
|
|
3769
4039
|
for profile in profiles:
|
|
@@ -3783,7 +4053,7 @@ class DaemonApp:
|
|
|
3783
4053
|
)
|
|
3784
4054
|
if gateway.start():
|
|
3785
4055
|
self._qq_gateways[profile_id] = gateway
|
|
3786
|
-
if not self._telegram_polling:
|
|
4056
|
+
if self._is_connector_system_enabled("telegram") and not self._telegram_polling:
|
|
3787
4057
|
for profile_id, profile_label, profile_config in self._profiled_connector_configs("telegram"):
|
|
3788
4058
|
polling = TelegramPollingService(
|
|
3789
4059
|
home=self.home,
|
|
@@ -3801,7 +4071,7 @@ class DaemonApp:
|
|
|
3801
4071
|
)
|
|
3802
4072
|
if polling.start():
|
|
3803
4073
|
self._telegram_polling[profile_id] = polling
|
|
3804
|
-
if not self._slack_socket:
|
|
4074
|
+
if self._is_connector_system_enabled("slack") and not self._slack_socket:
|
|
3805
4075
|
for profile_id, profile_label, profile_config in self._profiled_connector_configs("slack"):
|
|
3806
4076
|
slack = SlackSocketModeService(
|
|
3807
4077
|
home=self.home,
|
|
@@ -3819,7 +4089,7 @@ class DaemonApp:
|
|
|
3819
4089
|
)
|
|
3820
4090
|
if slack.start():
|
|
3821
4091
|
self._slack_socket[profile_id] = slack
|
|
3822
|
-
if not self._discord_gateway:
|
|
4092
|
+
if self._is_connector_system_enabled("discord") and not self._discord_gateway:
|
|
3823
4093
|
for profile_id, profile_label, profile_config in self._profiled_connector_configs("discord"):
|
|
3824
4094
|
discord = DiscordGatewayService(
|
|
3825
4095
|
home=self.home,
|
|
@@ -3837,7 +4107,7 @@ class DaemonApp:
|
|
|
3837
4107
|
)
|
|
3838
4108
|
if discord.start():
|
|
3839
4109
|
self._discord_gateway[profile_id] = discord
|
|
3840
|
-
if not self._feishu_long_connection:
|
|
4110
|
+
if self._is_connector_system_enabled("feishu") and not self._feishu_long_connection:
|
|
3841
4111
|
for profile_id, profile_label, profile_config in self._profiled_connector_configs("feishu"):
|
|
3842
4112
|
feishu = FeishuLongConnectionService(
|
|
3843
4113
|
home=self.home,
|
|
@@ -3855,7 +4125,7 @@ class DaemonApp:
|
|
|
3855
4125
|
)
|
|
3856
4126
|
if feishu.start():
|
|
3857
4127
|
self._feishu_long_connection[profile_id] = feishu
|
|
3858
|
-
if not self._whatsapp_local_session:
|
|
4128
|
+
if self._is_connector_system_enabled("whatsapp") and not self._whatsapp_local_session:
|
|
3859
4129
|
for profile_id, profile_label, profile_config in self._profiled_connector_configs("whatsapp"):
|
|
3860
4130
|
whatsapp = WhatsAppLocalSessionService(
|
|
3861
4131
|
home=self.home,
|
|
@@ -4486,6 +4756,7 @@ class DaemonApp:
|
|
|
4486
4756
|
"git_commit",
|
|
4487
4757
|
"git_diff_file",
|
|
4488
4758
|
"git_commit_file",
|
|
4759
|
+
"file_change_diff",
|
|
4489
4760
|
"explorer",
|
|
4490
4761
|
"quest_search",
|
|
4491
4762
|
"node_traces",
|
|
@@ -4498,7 +4769,7 @@ class DaemonApp:
|
|
|
4498
4769
|
payload = result(**params, path=self.path)
|
|
4499
4770
|
elif method == "GET":
|
|
4500
4771
|
payload = result(**params) if params else result()
|
|
4501
|
-
elif route_name in {"document_open", "document_asset_upload", "chat", "command", "quest_control", "config_save", "quest_create", "quest_baseline_binding", "run_create", "qq_inbound", "connector_inbound", "docs_open", "admin_shutdown", "bash_stop", "quest_settings", "quest_bindings", "quest_delete", "terminal_session_ensure", "terminal_attach", "terminal_input", "stage_view", "latex_init", "latex_compile", "system_update_action"}:
|
|
4772
|
+
elif route_name in {"document_open", "document_asset_upload", "chat", "command", "quest_control", "config_save", "quest_create", "quest_baseline_binding", "run_create", "qq_inbound", "connector_inbound", "docs_open", "admin_shutdown", "bash_stop", "quest_settings", "quest_bindings", "quest_delete", "quest_layout_update", "terminal_session_ensure", "terminal_attach", "terminal_input", "stage_view", "latex_init", "latex_compile", "system_update_action"}:
|
|
4502
4773
|
payload = result(**params, body=body)
|
|
4503
4774
|
elif route_name == "config_validate":
|
|
4504
4775
|
payload = result(body)
|
|
@@ -4,6 +4,7 @@ from collections import defaultdict
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
from ..artifact.metrics import extract_latest_metric
|
|
7
8
|
from ..shared import read_json, run_command
|
|
8
9
|
from .service import branch_exists, current_branch, head_commit
|
|
9
10
|
|
|
@@ -308,16 +309,9 @@ def _collect_branch_state(repo: Path) -> dict[str, dict[str, Any]]:
|
|
|
308
309
|
state["parent_branch"] = record.get("parent_branch")
|
|
309
310
|
if record.get("worktree_root"):
|
|
310
311
|
state["worktree_root"] = record.get("worktree_root")
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
state["latest_metric"] =
|
|
314
|
-
"key": ((record.get("progress_eval") or {}).get("primary_metric_id")) or key,
|
|
315
|
-
"value": record["metrics_summary"].get(
|
|
316
|
-
((record.get("progress_eval") or {}).get("primary_metric_id")) or key,
|
|
317
|
-
record["metrics_summary"].get(key),
|
|
318
|
-
),
|
|
319
|
-
"delta_vs_baseline": ((record.get("progress_eval") or {}).get("delta_vs_baseline")),
|
|
320
|
-
}
|
|
312
|
+
latest_metric = extract_latest_metric(record)
|
|
313
|
+
if latest_metric is not None:
|
|
314
|
+
state["latest_metric"] = latest_metric
|
|
321
315
|
if record.get("kind") == "run":
|
|
322
316
|
state["latest_result"] = {
|
|
323
317
|
"run_id": record.get("run_id"),
|
|
@@ -365,6 +359,8 @@ def _classify_ref(ref: str, state: dict[str, Any]) -> dict[str, str]:
|
|
|
365
359
|
return {"branch_kind": "quest", "tier": "major", "mode": "ideas"}
|
|
366
360
|
if ref.startswith("idea/"):
|
|
367
361
|
return {"branch_kind": "idea", "tier": "major", "mode": "ideas"}
|
|
362
|
+
if ref.startswith("paper/"):
|
|
363
|
+
return {"branch_kind": "paper", "tier": "major", "mode": "ideas"}
|
|
368
364
|
if ref.startswith("analysis/") or run_id.startswith("analysis") or run_kind == "analysis-campaign":
|
|
369
365
|
return {"branch_kind": "analysis", "tier": "minor", "mode": "analysis"}
|
|
370
366
|
return {"branch_kind": "implementation", "tier": "major", "mode": "ideas"}
|