@researai/deepscientist 1.5.13 → 1.5.15
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 +8 -0
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +134 -49
- package/docs/en/00_QUICK_START.md +2 -2
- package/docs/en/01_SETTINGS_REFERENCE.md +20 -4
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/05_TUI_GUIDE.md +466 -96
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +2 -0
- package/docs/en/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/en/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/en/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/en/README.md +8 -0
- package/docs/zh/00_QUICK_START.md +2 -2
- package/docs/zh/01_SETTINGS_REFERENCE.md +20 -4
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/05_TUI_GUIDE.md +465 -82
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +2 -0
- package/docs/zh/16_TELEGRAM_CONNECTOR_GUIDE.md +134 -0
- package/docs/zh/17_WHATSAPP_CONNECTOR_GUIDE.md +126 -0
- package/docs/zh/18_FEISHU_CONNECTOR_GUIDE.md +136 -0
- package/docs/zh/README.md +8 -0
- package/install.sh +2 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/artifact/charts.py +567 -0
- package/src/deepscientist/artifact/guidance.py +50 -10
- package/src/deepscientist/artifact/metrics.py +228 -5
- package/src/deepscientist/artifact/schemas.py +3 -0
- package/src/deepscientist/artifact/service.py +4004 -538
- package/src/deepscientist/bash_exec/models.py +23 -0
- package/src/deepscientist/bash_exec/monitor.py +147 -67
- package/src/deepscientist/bash_exec/runtime.py +218 -156
- package/src/deepscientist/bash_exec/service.py +79 -64
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/config/models.py +6 -3
- package/src/deepscientist/config/service.py +7 -2
- package/src/deepscientist/connector/lingzhu_support.py +23 -4
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +75 -4
- package/src/deepscientist/daemon/api/router.py +1 -0
- package/src/deepscientist/daemon/app.py +869 -236
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/diff.py +167 -1
- package/src/deepscientist/mcp/server.py +331 -21
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +275 -491
- package/src/deepscientist/quest/service.py +2336 -145
- package/src/deepscientist/quest/stage_views.py +305 -29
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +88 -5
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/deepscientist/shared.py +6 -1
- package/src/prompts/contracts/shared_interaction.md +13 -4
- package/src/prompts/system.md +984 -1985
- package/src/skills/analysis-campaign/SKILL.md +31 -2
- package/src/skills/analysis-campaign/references/artifact-orchestration.md +1 -1
- package/src/skills/analysis-campaign/references/writing-facing-slice-examples.md +65 -0
- package/src/skills/baseline/SKILL.md +267 -994
- package/src/skills/baseline/references/baseline-checklist-template.md +21 -32
- package/src/skills/baseline/references/baseline-plan-template.md +41 -57
- package/src/skills/decision/SKILL.md +19 -2
- package/src/skills/experiment/SKILL.md +8 -2
- package/src/skills/finalize/SKILL.md +18 -0
- package/src/skills/idea/SKILL.md +78 -0
- package/src/skills/idea/references/idea-generation-playbook.md +100 -0
- package/src/skills/idea/references/outline-seeding-example.md +60 -0
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/optimize/SKILL.md +1644 -0
- package/src/skills/rebuttal/SKILL.md +2 -1
- package/src/skills/review/SKILL.md +2 -1
- package/src/skills/write/SKILL.md +80 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +1445 -52
- package/src/tui/dist/components/Composer.js +1 -1
- package/src/tui/dist/components/ConfigScreen.js +190 -36
- package/src/tui/dist/components/GradientStatusText.js +1 -20
- package/src/tui/dist/components/InputPrompt.js +41 -32
- package/src/tui/dist/components/LoadingIndicator.js +1 -1
- package/src/tui/dist/components/Logo.js +61 -38
- package/src/tui/dist/components/MainContent.js +10 -3
- package/src/tui/dist/components/WelcomePanel.js +4 -12
- package/src/tui/dist/components/messages/AssistantMessage.js +1 -1
- package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -3
- package/src/tui/dist/components/messages/OperationMessage.js +1 -1
- package/src/tui/dist/index.js +28 -1
- package/src/tui/dist/layouts/DefaultAppLayout.js +3 -3
- package/src/tui/dist/lib/api.js +17 -0
- package/src/tui/dist/lib/connectors.js +261 -0
- package/src/tui/dist/semantic-colors.js +29 -19
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-CnJcXynW.js → AiManusChatView-DDjbFnbt.js} +12 -12
- package/src/ui/dist/assets/{AnalysisPlugin-DeyzPEhV.js → AnalysisPlugin-Yb5IdmaU.js} +1 -1
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +31037 -0
- package/src/ui/dist/assets/{CodeEditorPlugin-B-xicq1e.js → CodeEditorPlugin-C4D2TIkU.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DT54ysXa.js → CodeViewerPlugin-BVoNZIvC.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-DQtKT-VD.js → DocViewerPlugin-CLChbllo.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-hqHbCfnv.js → GitDiffViewerPlugin-C4xeFyFQ.js} +20 -20
- package/src/ui/dist/assets/{ImageViewerPlugin-OcVo33jV.js → ImageViewerPlugin-OiMUAcLi.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-DdGwhEUV.js → LabCopilotPanel-BjD2ThQF.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-Ciz1gDaX.js → LabPlugin-DQPg-NrB.js} +2 -2
- package/src/ui/dist/assets/{LatexPlugin-BhmjNQRC.js → LatexPlugin-CI05XAV9.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-BzdVH9Bx.js → MarkdownViewerPlugin-DpeBLYZf.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DmyHspXt.js → MarketplacePlugin-DolE58Q2.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-BTVYRGkm.js → NotebookEditor-7Qm2rSWD.js} +11 -11
- package/src/ui/dist/assets/{NotebookEditor-BMXKrDRk.js → NotebookEditor-C1kWaxKi.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-CvcjJHXv.js → PdfLoader-BfOHw8Zw.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-DW2ej8Vk.js → PdfMarkdownPlugin-BulDREv1.js} +2 -2
- package/src/ui/dist/assets/{PdfViewerPlugin-CmlDxbhU.js → PdfViewerPlugin-C-daaOaL.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-DAjQZPSv.js → SearchPlugin-CjpaiJ3A.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-C-nVAZb_.js → TextViewerPlugin-BxIyqPQC.js} +5 -5
- package/src/ui/dist/assets/{VNCViewer-D7-dIYon.js → VNCViewer-HAg9mF7M.js} +10 -10
- package/src/ui/dist/assets/{bot-C_G4WtNI.js → bot-0DYntytV.js} +1 -1
- package/src/ui/dist/assets/{code-Cd7WfiWq.js → code-B20Slj_w.js} +1 -1
- package/src/ui/dist/assets/{file-content-B57zsL9y.js → file-content-DT24KFma.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-DVoheLFq.js → file-diff-panel-DK13YPql.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B5kXFxZP.js → file-socket-B4T2o4nR.js} +1 -1
- package/src/ui/dist/assets/{image-LLOjkMHF.js → image-DSeR_sDS.js} +1 -1
- package/src/ui/dist/assets/{index-hOUOWbW2.js → index-BrFje2Uk.js} +2 -2
- package/src/ui/dist/assets/{index-Dxa2eYMY.js → index-BwRJaoTl.js} +1 -1
- package/src/ui/dist/assets/{index-CLQauncb.js → index-D_E4281X.js} +5418 -28620
- package/src/ui/dist/assets/{index-C3r2iGrp.js → index-DnYB3xb1.js} +12 -12
- package/src/ui/dist/assets/{index-BQG-1s2o.css → index-G7AcWcMu.css} +43 -2
- package/src/ui/dist/assets/{monaco-BGGAEii3.js → monaco-LExaAN3Y.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DlEr1_y5.js → pdf-effect-queue-BJk5okWJ.js} +1 -1
- package/src/ui/dist/assets/{popover-CWJbJuYY.js → popover-D3Gg_FoV.js} +1 -1
- package/src/ui/dist/assets/{project-sync-CRJiucYO.js → project-sync-C_ygLlVU.js} +1 -1
- package/src/ui/dist/assets/{select-CoHB7pvH.js → select-CpAK6uWm.js} +2 -2
- package/src/ui/dist/assets/{sigma-D5aJWR8J.js → sigma-DEccaSgk.js} +1 -1
- package/src/ui/dist/assets/{square-check-big-DUK_mnkS.js → square-check-big-uUfyVsbD.js} +1 -1
- package/src/ui/dist/assets/{trash-ChU3SEE3.js → trash-CXvwwSe8.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-BrJBV3tY.js → useCliAccess-Bnop4mgR.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-C2OQaVWc.js → useFileDiffOverlay-B8eUAX0I.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C7Qqh-om.js → wrap-text-9vbOBpkW.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-rtX0FKya.js → zoom-out-BgVMmOW4.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/uv.lock +1 -1
- package/src/ui/dist/assets/CliPlugin-CB1YODQn.js +0 -5905
|
@@ -3,9 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
|
-
import shlex
|
|
7
6
|
import shutil
|
|
8
|
-
import signal
|
|
9
7
|
import subprocess
|
|
10
8
|
import sys
|
|
11
9
|
import tempfile
|
|
@@ -17,7 +15,9 @@ from pathlib import Path
|
|
|
17
15
|
from typing import Any
|
|
18
16
|
|
|
19
17
|
from ..mcp.context import McpContext
|
|
18
|
+
from ..process_control import is_process_alive, process_session_popen_kwargs, terminate_process_ids
|
|
20
19
|
from ..shared import append_jsonl, ensure_dir, generate_id, iter_jsonl, read_json, read_jsonl, read_jsonl_tail, utc_now
|
|
20
|
+
from .shells import build_exec_shell_launch, build_terminal_shell_launch
|
|
21
21
|
from .runtime import TerminalRuntimeManager
|
|
22
22
|
|
|
23
23
|
BASH_STATUS_MARKER_PREFIX = "__DS_BASH_STATUS__"
|
|
@@ -114,26 +114,6 @@ def _session_sort_key(session: dict[str, Any]) -> tuple[str, str]:
|
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
def _is_process_alive(pid: object) -> bool:
|
|
118
|
-
if not isinstance(pid, int) or pid <= 0:
|
|
119
|
-
return False
|
|
120
|
-
proc_stat_path = Path("/proc") / str(pid) / "stat"
|
|
121
|
-
if proc_stat_path.exists():
|
|
122
|
-
try:
|
|
123
|
-
parts = proc_stat_path.read_text(encoding="utf-8").split()
|
|
124
|
-
except OSError:
|
|
125
|
-
parts = []
|
|
126
|
-
if len(parts) >= 3 and parts[2] == "Z":
|
|
127
|
-
return False
|
|
128
|
-
try:
|
|
129
|
-
os.kill(pid, 0)
|
|
130
|
-
except ProcessLookupError:
|
|
131
|
-
return False
|
|
132
|
-
except PermissionError:
|
|
133
|
-
return True
|
|
134
|
-
return True
|
|
135
|
-
|
|
136
|
-
|
|
137
117
|
def _parse_progress_marker(line: str) -> dict[str, Any] | None:
|
|
138
118
|
if not line.startswith(BASH_PROGRESS_PREFIX):
|
|
139
119
|
return None
|
|
@@ -518,20 +498,14 @@ class BashExecService:
|
|
|
518
498
|
return self._session_payload(quest_root, read_json(meta_path, meta) or meta)
|
|
519
499
|
monitor_pid = meta.get("monitor_pid")
|
|
520
500
|
process_pid = meta.get("process_pid")
|
|
521
|
-
if kind == "terminal" and
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
pass
|
|
528
|
-
elif isinstance(process_pid, int) and process_pid > 0:
|
|
529
|
-
try:
|
|
530
|
-
os.kill(process_pid, signal.SIGTERM)
|
|
531
|
-
except ProcessLookupError:
|
|
532
|
-
pass
|
|
501
|
+
if kind == "terminal" and is_process_alive(process_pid):
|
|
502
|
+
terminate_process_ids(
|
|
503
|
+
process_pid=process_pid if isinstance(process_pid, int) else None,
|
|
504
|
+
process_group_id=meta.get("process_group_id") if isinstance(meta.get("process_group_id"), int) else None,
|
|
505
|
+
force=False,
|
|
506
|
+
)
|
|
533
507
|
time.sleep(0.05)
|
|
534
|
-
if kind != "terminal" and (
|
|
508
|
+
if kind != "terminal" and (is_process_alive(process_pid) or is_process_alive(monitor_pid)):
|
|
535
509
|
return self._session_payload(quest_root, meta)
|
|
536
510
|
stop_reason = _normalize_string(meta.get("stop_reason"))
|
|
537
511
|
meta["status"] = "terminated" if stop_reason else "failed"
|
|
@@ -566,6 +540,10 @@ class BashExecService:
|
|
|
566
540
|
normalized_agent_instance_ids = {item for item in (agent_instance_ids or []) if item}
|
|
567
541
|
normalized_agent_ids = {item for item in (agent_ids or []) if item}
|
|
568
542
|
normalized_chat_session = _normalize_string(chat_session_id)
|
|
543
|
+
if normalized_status in {"running", "terminating"}:
|
|
544
|
+
summary = self.summary(quest_root)
|
|
545
|
+
if int(summary.get("running_count") or 0) <= 0:
|
|
546
|
+
return []
|
|
569
547
|
sessions: list[dict[str, Any]] = []
|
|
570
548
|
for bash_id in self._list_session_ids(quest_root):
|
|
571
549
|
try:
|
|
@@ -717,18 +695,11 @@ class BashExecService:
|
|
|
717
695
|
if runtime is not None:
|
|
718
696
|
runtime.stop(reason=request_payload["reason"], force=bool(force))
|
|
719
697
|
else:
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
except ProcessLookupError:
|
|
726
|
-
pass
|
|
727
|
-
elif isinstance(process_pid, int) and process_pid > 0:
|
|
728
|
-
try:
|
|
729
|
-
os.kill(process_pid, signal.SIGKILL if force else signal.SIGTERM)
|
|
730
|
-
except ProcessLookupError:
|
|
731
|
-
pass
|
|
698
|
+
terminate_process_ids(
|
|
699
|
+
process_pid=meta.get("process_pid") if isinstance(meta.get("process_pid"), int) else None,
|
|
700
|
+
process_group_id=meta.get("process_group_id") if isinstance(meta.get("process_group_id"), int) else None,
|
|
701
|
+
force=bool(force),
|
|
702
|
+
)
|
|
732
703
|
return self._session_payload(quest_root, meta)
|
|
733
704
|
|
|
734
705
|
def _build_initial_meta(
|
|
@@ -737,6 +708,7 @@ class BashExecService:
|
|
|
737
708
|
context: McpContext,
|
|
738
709
|
bash_id: str,
|
|
739
710
|
command: str,
|
|
711
|
+
launch_argv: list[str] | None,
|
|
740
712
|
mode: str,
|
|
741
713
|
cwd: Path,
|
|
742
714
|
workdir_display: str,
|
|
@@ -744,6 +716,8 @@ class BashExecService:
|
|
|
744
716
|
env_keys: list[str],
|
|
745
717
|
comment: str | dict[str, Any] | None = None,
|
|
746
718
|
kind: str = "exec",
|
|
719
|
+
shell_family: str | None = None,
|
|
720
|
+
shell_name: str | None = None,
|
|
747
721
|
) -> dict[str, Any]:
|
|
748
722
|
quest_root = context.require_quest_root().resolve()
|
|
749
723
|
session_id = _normalize_string(context.conversation_id) or f"quest:{context.quest_id or quest_root.name}"
|
|
@@ -766,6 +740,9 @@ class BashExecService:
|
|
|
766
740
|
"stopped_by_user_id": None,
|
|
767
741
|
"comment": comment,
|
|
768
742
|
"command": command,
|
|
743
|
+
"launch_argv": list(launch_argv or []),
|
|
744
|
+
"shell_family": shell_family,
|
|
745
|
+
"shell_name": shell_name,
|
|
769
746
|
"workdir": workdir_display,
|
|
770
747
|
"cwd": str(cwd),
|
|
771
748
|
"kind": kind,
|
|
@@ -808,7 +785,7 @@ class BashExecService:
|
|
|
808
785
|
stderr=monitor_log_handle,
|
|
809
786
|
cwd=str(quest_root),
|
|
810
787
|
env=monitor_env,
|
|
811
|
-
|
|
788
|
+
**process_session_popen_kwargs(hide_window=True),
|
|
812
789
|
)
|
|
813
790
|
finally:
|
|
814
791
|
monitor_log_handle.close()
|
|
@@ -833,10 +810,12 @@ class BashExecService:
|
|
|
833
810
|
session_dir = self.session_dir(quest_root, bash_id)
|
|
834
811
|
ensure_dir(session_dir)
|
|
835
812
|
env_payload = {str(key): str(value) for key, value in (env or {}).items() if value is not None}
|
|
813
|
+
launch = build_exec_shell_launch(command)
|
|
836
814
|
meta = self._build_initial_meta(
|
|
837
815
|
context=context,
|
|
838
816
|
bash_id=bash_id,
|
|
839
817
|
command=command,
|
|
818
|
+
launch_argv=launch.argv,
|
|
840
819
|
mode=mode,
|
|
841
820
|
cwd=cwd,
|
|
842
821
|
workdir_display=workdir_display,
|
|
@@ -844,6 +823,8 @@ class BashExecService:
|
|
|
844
823
|
env_keys=sorted(env_payload),
|
|
845
824
|
comment=comment,
|
|
846
825
|
kind="exec",
|
|
826
|
+
shell_family=launch.family,
|
|
827
|
+
shell_name=launch.shell_name,
|
|
847
828
|
)
|
|
848
829
|
self.terminal_log_path(quest_root, bash_id).touch()
|
|
849
830
|
self.log_path(quest_root, bash_id).touch()
|
|
@@ -880,10 +861,14 @@ class BashExecService:
|
|
|
880
861
|
cwd: Path,
|
|
881
862
|
workdir_display: str,
|
|
882
863
|
command: str,
|
|
864
|
+
launch_argv: list[str] | None,
|
|
883
865
|
source: str,
|
|
884
866
|
conversation_id: str | None,
|
|
885
867
|
user_id: str | None,
|
|
886
868
|
env_keys: list[str],
|
|
869
|
+
shell_family: str | None = None,
|
|
870
|
+
shell_name: str | None = None,
|
|
871
|
+
transport_preference: str | None = None,
|
|
887
872
|
) -> dict[str, Any]:
|
|
888
873
|
timestamp = utc_now()
|
|
889
874
|
session_id = _normalize_string(conversation_id) or f"quest:{quest_id}:terminal"
|
|
@@ -903,6 +888,10 @@ class BashExecService:
|
|
|
903
888
|
"stopped_by_user_id": None,
|
|
904
889
|
"label": label,
|
|
905
890
|
"command": command,
|
|
891
|
+
"launch_argv": list(launch_argv or []),
|
|
892
|
+
"shell_family": shell_family,
|
|
893
|
+
"shell_name": shell_name,
|
|
894
|
+
"transport_preference": transport_preference,
|
|
906
895
|
"workdir": workdir_display,
|
|
907
896
|
"cwd": str(cwd),
|
|
908
897
|
"kind": "terminal",
|
|
@@ -984,29 +973,49 @@ class BashExecService:
|
|
|
984
973
|
self.line_buffer_path(resolved_quest_root, bash_id),
|
|
985
974
|
{"buffer": "", "updated_at": utc_now()},
|
|
986
975
|
)
|
|
987
|
-
|
|
988
|
-
terminal_rc_path.write_text(
|
|
989
|
-
"\n".join(
|
|
990
|
-
[
|
|
991
|
-
"PS1='\\w$ '",
|
|
992
|
-
"PS2='> '",
|
|
993
|
-
'PROMPT_COMMAND=\'printf "__DS_TERMINAL_PROMPT__ cwd=%q ts=%s\\n" "$PWD" "$(date -u +%FT%TZ)" >> "${DS_TERMINAL_PROMPT_PATH}"\'',
|
|
994
|
-
"bind 'set enable-bracketed-paste off' >/dev/null 2>&1 || true",
|
|
995
|
-
"",
|
|
996
|
-
]
|
|
997
|
-
),
|
|
998
|
-
encoding="utf-8",
|
|
999
|
-
)
|
|
976
|
+
terminal_script_path = session_dir / ("terminal.ps1" if os.name == "nt" else "terminal.rc")
|
|
1000
977
|
stop_request = self.stop_request_path(resolved_quest_root, bash_id)
|
|
1001
978
|
if stop_request.exists():
|
|
1002
979
|
stop_request.unlink()
|
|
1003
980
|
|
|
1004
981
|
env_payload = {
|
|
1005
|
-
"TERM": "xterm-256color",
|
|
1006
|
-
"COLORTERM": "truecolor",
|
|
1007
982
|
"DS_TERMINAL_PROMPT_PATH": str(self.prompt_events_path(resolved_quest_root, bash_id)),
|
|
1008
983
|
}
|
|
1009
|
-
|
|
984
|
+
if os.name != "nt":
|
|
985
|
+
terminal_script_path.write_text(
|
|
986
|
+
"\n".join(
|
|
987
|
+
[
|
|
988
|
+
"PS1='\\w$ '",
|
|
989
|
+
"PS2='> '",
|
|
990
|
+
'PROMPT_COMMAND=\'printf "__DS_TERMINAL_PROMPT__ cwd_b64=%s ts=%s\\n" "$(printf "%s" "$PWD" | base64 | tr -d "\\n")" "$(date -u +%FT%TZ)" >> "${DS_TERMINAL_PROMPT_PATH}"\'',
|
|
991
|
+
"bind 'set enable-bracketed-paste off' >/dev/null 2>&1 || true",
|
|
992
|
+
"",
|
|
993
|
+
]
|
|
994
|
+
),
|
|
995
|
+
encoding="utf-8",
|
|
996
|
+
)
|
|
997
|
+
env_payload["TERM"] = "xterm-256color"
|
|
998
|
+
env_payload["COLORTERM"] = "truecolor"
|
|
999
|
+
else:
|
|
1000
|
+
terminal_script_path.write_text(
|
|
1001
|
+
"\n".join(
|
|
1002
|
+
[
|
|
1003
|
+
"$global:__dsPromptPath = $env:DS_TERMINAL_PROMPT_PATH",
|
|
1004
|
+
"function global:prompt {",
|
|
1005
|
+
" $cwdBytes = [System.Text.Encoding]::UTF8.GetBytes((Get-Location).Path)",
|
|
1006
|
+
" $cwdB64 = [Convert]::ToBase64String($cwdBytes)",
|
|
1007
|
+
' $ts = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")',
|
|
1008
|
+
' Add-Content -LiteralPath $global:__dsPromptPath -Value "__DS_TERMINAL_PROMPT__ cwd_b64=$cwdB64 ts=$ts"',
|
|
1009
|
+
' return "PS $((Get-Location).Path)> "',
|
|
1010
|
+
"}",
|
|
1011
|
+
"try { Set-PSReadLineOption -BellStyle None | Out-Null } catch {}",
|
|
1012
|
+
"",
|
|
1013
|
+
]
|
|
1014
|
+
),
|
|
1015
|
+
encoding="utf-8",
|
|
1016
|
+
)
|
|
1017
|
+
launch = build_terminal_shell_launch(terminal_script_path)
|
|
1018
|
+
command = " ".join(launch.argv)
|
|
1010
1019
|
resolved_label = _normalize_string(label) or previous_label
|
|
1011
1020
|
meta = self._build_terminal_meta(
|
|
1012
1021
|
quest_root=resolved_quest_root,
|
|
@@ -1016,10 +1025,14 @@ class BashExecService:
|
|
|
1016
1025
|
cwd=target_cwd,
|
|
1017
1026
|
workdir_display=workdir_display,
|
|
1018
1027
|
command=command,
|
|
1028
|
+
launch_argv=launch.argv,
|
|
1019
1029
|
source=source,
|
|
1020
1030
|
conversation_id=conversation_id,
|
|
1021
1031
|
user_id=user_id,
|
|
1022
1032
|
env_keys=sorted(env_payload),
|
|
1033
|
+
shell_family=launch.family,
|
|
1034
|
+
shell_name=launch.shell_name,
|
|
1035
|
+
transport_preference="pipe" if os.name == "nt" else "pty",
|
|
1023
1036
|
)
|
|
1024
1037
|
self._write_meta(resolved_quest_root, bash_id, meta)
|
|
1025
1038
|
append_jsonl(
|
|
@@ -1042,7 +1055,9 @@ class BashExecService:
|
|
|
1042
1055
|
prompt_events_path=self.prompt_events_path(resolved_quest_root, bash_id),
|
|
1043
1056
|
env_payload=env_payload,
|
|
1044
1057
|
command=command,
|
|
1058
|
+
launch_argv=launch.argv,
|
|
1045
1059
|
cwd=target_cwd,
|
|
1060
|
+
transport_preference="pipe" if os.name == "nt" else "pty",
|
|
1046
1061
|
)
|
|
1047
1062
|
meta["updated_at"] = utc_now()
|
|
1048
1063
|
self._write_meta(resolved_quest_root, bash_id, meta)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ShellLaunchSpec:
|
|
11
|
+
argv: list[str]
|
|
12
|
+
family: str
|
|
13
|
+
shell_name: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _resolve_windows_shell(*, interactive: bool) -> tuple[str, str]:
|
|
17
|
+
candidates: list[tuple[str, str]] = []
|
|
18
|
+
if not interactive:
|
|
19
|
+
candidates.extend(
|
|
20
|
+
[
|
|
21
|
+
("bash.exe", "bash"),
|
|
22
|
+
("bash", "bash"),
|
|
23
|
+
]
|
|
24
|
+
)
|
|
25
|
+
candidates.extend(
|
|
26
|
+
[
|
|
27
|
+
("pwsh", "powershell"),
|
|
28
|
+
("powershell.exe", "powershell"),
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
if not interactive:
|
|
32
|
+
candidates.append(("cmd.exe", "cmd"))
|
|
33
|
+
for binary, family in candidates:
|
|
34
|
+
resolved = shutil.which(binary)
|
|
35
|
+
if resolved:
|
|
36
|
+
return resolved, family
|
|
37
|
+
fallback = "cmd.exe" if not interactive else "powershell.exe"
|
|
38
|
+
return fallback, "cmd" if fallback == "cmd.exe" else "powershell"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_exec_shell_launch(command: str) -> ShellLaunchSpec:
|
|
42
|
+
normalized = str(command or "").strip()
|
|
43
|
+
if os.name != "nt":
|
|
44
|
+
return ShellLaunchSpec(
|
|
45
|
+
argv=["bash", "-lc", normalized],
|
|
46
|
+
family="bash",
|
|
47
|
+
shell_name="bash",
|
|
48
|
+
)
|
|
49
|
+
binary, family = _resolve_windows_shell(interactive=False)
|
|
50
|
+
if family == "bash":
|
|
51
|
+
return ShellLaunchSpec(
|
|
52
|
+
argv=[binary, "-lc", normalized],
|
|
53
|
+
family=family,
|
|
54
|
+
shell_name=Path(binary).name,
|
|
55
|
+
)
|
|
56
|
+
if family == "cmd":
|
|
57
|
+
return ShellLaunchSpec(
|
|
58
|
+
argv=[binary, "/d", "/s", "/c", normalized],
|
|
59
|
+
family=family,
|
|
60
|
+
shell_name=Path(binary).name,
|
|
61
|
+
)
|
|
62
|
+
return ShellLaunchSpec(
|
|
63
|
+
argv=[binary, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", normalized],
|
|
64
|
+
family=family,
|
|
65
|
+
shell_name=Path(binary).name,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def build_terminal_shell_launch(script_path: Path) -> ShellLaunchSpec:
|
|
70
|
+
if os.name != "nt":
|
|
71
|
+
return ShellLaunchSpec(
|
|
72
|
+
argv=["bash", "--noprofile", "--rcfile", str(script_path), "-i"],
|
|
73
|
+
family="bash",
|
|
74
|
+
shell_name="bash",
|
|
75
|
+
)
|
|
76
|
+
binary, family = _resolve_windows_shell(interactive=True)
|
|
77
|
+
if family == "powershell":
|
|
78
|
+
return ShellLaunchSpec(
|
|
79
|
+
argv=[binary, "-NoLogo", "-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass", "-File", str(script_path)],
|
|
80
|
+
family=family,
|
|
81
|
+
shell_name=Path(binary).name,
|
|
82
|
+
)
|
|
83
|
+
return ShellLaunchSpec(
|
|
84
|
+
argv=[binary, "/q", "/k", str(script_path)],
|
|
85
|
+
family=family,
|
|
86
|
+
shell_name=Path(binary).name,
|
|
87
|
+
)
|
|
@@ -18,8 +18,11 @@ from ..connector.weixin_support import (
|
|
|
18
18
|
WEIXIN_UPLOAD_MEDIA_FILE,
|
|
19
19
|
WEIXIN_UPLOAD_MEDIA_IMAGE,
|
|
20
20
|
WEIXIN_UPLOAD_MEDIA_VIDEO,
|
|
21
|
+
clear_weixin_context_send_state,
|
|
21
22
|
download_weixin_remote_attachment,
|
|
23
|
+
get_weixin_context_entry,
|
|
22
24
|
get_weixin_context_token,
|
|
25
|
+
mark_weixin_context_stale,
|
|
23
26
|
normalize_weixin_base_url,
|
|
24
27
|
normalize_weixin_cdn_base_url,
|
|
25
28
|
send_weixin_message,
|
|
@@ -558,10 +561,11 @@ class QQConnectorBridge(BaseConnectorBridge):
|
|
|
558
561
|
connector_delivery = item.get("connector_delivery") if isinstance(item.get("connector_delivery"), dict) else {}
|
|
559
562
|
qq_delivery = connector_delivery.get("qq") if isinstance(connector_delivery.get("qq"), dict) else {}
|
|
560
563
|
media_kind = str(qq_delivery.get("media_kind") or "").strip().lower()
|
|
564
|
+
allow_internal_auto_media = bool(qq_delivery.get("allow_internal_auto_media"))
|
|
561
565
|
if media_kind not in {"image", "file"}:
|
|
562
566
|
residual_items.append(item)
|
|
563
567
|
continue
|
|
564
|
-
if not native_enabled:
|
|
568
|
+
if not native_enabled and not allow_internal_auto_media:
|
|
565
569
|
issues.append(
|
|
566
570
|
{
|
|
567
571
|
"attachment_index": index,
|
|
@@ -781,7 +785,9 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
781
785
|
name = "weixin"
|
|
782
786
|
_MEDIA_ITEM_TYPES = {2, 4, 5}
|
|
783
787
|
_MEDIA_SEND_INITIAL_DELAY_SECONDS = 0.8
|
|
784
|
-
|
|
788
|
+
_TEXT_SEND_RETRY_DELAYS_SECONDS = (0.8, 1.5, 3.0)
|
|
789
|
+
_MEDIA_SEND_RETRY_DELAYS_SECONDS = (1.5, 3.0, 5.0)
|
|
790
|
+
_STALE_CONTEXT_SUPPRESSED_KINDS = {"progress", "milestone", "assistant", "summary", "ack"}
|
|
785
791
|
|
|
786
792
|
def deliver(self, payload: dict[str, Any], config: dict[str, Any]) -> dict[str, Any] | None:
|
|
787
793
|
return self.deliver_direct(payload, config)
|
|
@@ -800,14 +806,26 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
800
806
|
"error": "Weixin outbound target is empty.",
|
|
801
807
|
}
|
|
802
808
|
connector_root = self._connector_root(config)
|
|
809
|
+
kind = str(payload.get("kind") or "").strip().lower()
|
|
810
|
+
context_entry = get_weixin_context_entry(connector_root, to_user_id)
|
|
803
811
|
context_token = get_weixin_context_token(connector_root, to_user_id)
|
|
804
812
|
if not context_token:
|
|
813
|
+
if kind in self._STALE_CONTEXT_SUPPRESSED_KINDS:
|
|
814
|
+
return self._queued_context_wait_response(
|
|
815
|
+
reason=f"Weixin context_token is missing for `{to_user_id}`. Waiting for the next inbound message.",
|
|
816
|
+
)
|
|
805
817
|
return {
|
|
806
818
|
"ok": False,
|
|
807
819
|
"queued": False,
|
|
808
820
|
"transport": "weixin-ilink",
|
|
809
821
|
"error": f"Weixin context_token is missing for `{to_user_id}`. Wait for one inbound message first.",
|
|
810
822
|
}
|
|
823
|
+
if bool(context_entry.get("stale_context")) and kind in self._STALE_CONTEXT_SUPPRESSED_KINDS:
|
|
824
|
+
stale_since = str(context_entry.get("stale_since") or "").strip()
|
|
825
|
+
warning = "Weixin outbound is paused until the next inbound message refreshes context_token."
|
|
826
|
+
if stale_since:
|
|
827
|
+
warning = f"{warning} stale_since={stale_since}"
|
|
828
|
+
return self._queued_context_wait_response(reason=warning)
|
|
811
829
|
|
|
812
830
|
native_attachments, residual_attachments, warnings = self._partition_native_attachments(payload.get("attachments"))
|
|
813
831
|
rendered_text = self.render_text(payload.get("text"), residual_attachments)
|
|
@@ -895,6 +913,20 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
895
913
|
else:
|
|
896
914
|
warnings.append("Weixin outbound payload contained neither text nor sendable attachments.")
|
|
897
915
|
except Exception as exc:
|
|
916
|
+
if "ret=-2" in str(exc or "").lower():
|
|
917
|
+
mark_weixin_context_stale(
|
|
918
|
+
connector_root,
|
|
919
|
+
user_id=to_user_id,
|
|
920
|
+
error=str(exc),
|
|
921
|
+
kind=kind or None,
|
|
922
|
+
)
|
|
923
|
+
if kind in self._STALE_CONTEXT_SUPPRESSED_KINDS:
|
|
924
|
+
queued = self._queued_context_wait_response(
|
|
925
|
+
reason="Weixin send hit stale context and was deferred until the next inbound refresh.",
|
|
926
|
+
)
|
|
927
|
+
queued["parts"] = parts
|
|
928
|
+
queued["warnings"] = [*warnings, *(queued.get("warnings") or [])]
|
|
929
|
+
return queued
|
|
898
930
|
return {
|
|
899
931
|
"ok": False,
|
|
900
932
|
"queued": False,
|
|
@@ -909,6 +941,12 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
909
941
|
error_messages = [str(item.get("error") or "").strip() for item in failed if str(item.get("error") or "").strip()]
|
|
910
942
|
error_messages.extend(warnings)
|
|
911
943
|
last_success = succeeded[-1] if succeeded else {}
|
|
944
|
+
if succeeded:
|
|
945
|
+
clear_weixin_context_send_state(
|
|
946
|
+
connector_root,
|
|
947
|
+
user_id=to_user_id,
|
|
948
|
+
kind=kind or None,
|
|
949
|
+
)
|
|
912
950
|
return {
|
|
913
951
|
"ok": bool(succeeded),
|
|
914
952
|
"queued": False,
|
|
@@ -920,6 +958,17 @@ class WeixinConnectorBridge(BaseConnectorBridge):
|
|
|
920
958
|
"error": "; ".join(error_messages) if error_messages else None,
|
|
921
959
|
}
|
|
922
960
|
|
|
961
|
+
@staticmethod
|
|
962
|
+
def _queued_context_wait_response(*, reason: str) -> dict[str, Any]:
|
|
963
|
+
return {
|
|
964
|
+
"ok": False,
|
|
965
|
+
"queued": True,
|
|
966
|
+
"transport": "weixin-ilink",
|
|
967
|
+
"parts": [],
|
|
968
|
+
"warnings": [str(reason or "").strip()],
|
|
969
|
+
"error": None,
|
|
970
|
+
}
|
|
971
|
+
|
|
923
972
|
@staticmethod
|
|
924
973
|
def _partition_native_attachments(
|
|
925
974
|
attachments: Any,
|
|
@@ -22,7 +22,7 @@ def config_filename(name: str) -> str:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def default_system_enabled_connectors() -> dict[str, bool]:
|
|
25
|
-
return {name: name in {"qq", "weixin", "lingzhu"} for name in SYSTEM_CONNECTOR_NAMES}
|
|
25
|
+
return {name: name in {"qq", "weixin", "telegram", "feishu", "whatsapp", "lingzhu"} for name in SYSTEM_CONNECTOR_NAMES}
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def default_config(home: Path) -> dict:
|
|
@@ -98,8 +98,8 @@ def default_runners() -> dict:
|
|
|
98
98
|
"profile": "",
|
|
99
99
|
"model": "gpt-5.4",
|
|
100
100
|
"model_reasoning_effort": "xhigh",
|
|
101
|
-
"approval_policy": "
|
|
102
|
-
"sandbox_mode": "
|
|
101
|
+
"approval_policy": "never",
|
|
102
|
+
"sandbox_mode": "danger-full-access",
|
|
103
103
|
"retry_on_failure": True,
|
|
104
104
|
"retry_max_attempts": 5,
|
|
105
105
|
"retry_initial_backoff_sec": 10.0,
|
|
@@ -154,6 +154,9 @@ def default_connectors() -> dict:
|
|
|
154
154
|
"transport": "ilink_long_poll",
|
|
155
155
|
"bot_name": "DeepScientist",
|
|
156
156
|
"command_prefix": "/",
|
|
157
|
+
"auto_send_main_experiment_png": True,
|
|
158
|
+
"stale_replay_latest_limit": 5,
|
|
159
|
+
"stale_replay_interval_seconds": 2.0,
|
|
157
160
|
"base_url": "https://ilinkai.weixin.qq.com",
|
|
158
161
|
"cdn_base_url": "https://novac2c.cdn.weixin.qq.com/c2c",
|
|
159
162
|
"bot_type": "3",
|
|
@@ -48,6 +48,7 @@ from .models import (
|
|
|
48
48
|
ConfigFileInfo,
|
|
49
49
|
SYSTEM_CONNECTOR_NAMES,
|
|
50
50
|
config_filename,
|
|
51
|
+
default_system_enabled_connectors,
|
|
51
52
|
default_payload,
|
|
52
53
|
)
|
|
53
54
|
|
|
@@ -112,8 +113,9 @@ class ConfigManager:
|
|
|
112
113
|
config = self.load_runtime_config()
|
|
113
114
|
connectors = config.get("connectors") if isinstance(config.get("connectors"), dict) else {}
|
|
114
115
|
system_enabled = connectors.get("system_enabled") if isinstance(connectors.get("system_enabled"), dict) else {}
|
|
116
|
+
defaults = default_system_enabled_connectors()
|
|
115
117
|
return {
|
|
116
|
-
name: self._coerce_bool(system_enabled.get(name), default=name
|
|
118
|
+
name: self._coerce_bool(system_enabled.get(name), default=defaults.get(name, False))
|
|
117
119
|
for name in SYSTEM_CONNECTOR_NAMES
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -1207,7 +1209,10 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1207
1209
|
env_key = str(key or "").strip()
|
|
1208
1210
|
if not env_key or value is None:
|
|
1209
1211
|
continue
|
|
1210
|
-
|
|
1212
|
+
env_value = str(value)
|
|
1213
|
+
if env_value == "":
|
|
1214
|
+
continue
|
|
1215
|
+
resolved[env_key] = env_value
|
|
1211
1216
|
return resolved
|
|
1212
1217
|
|
|
1213
1218
|
def _prepare_codex_probe_home(
|
|
@@ -15,6 +15,9 @@ DEFAULT_LINGZHU_AGENT_ID = "main"
|
|
|
15
15
|
DEFAULT_LINGZHU_SESSION_NAMESPACE = "lingzhu"
|
|
16
16
|
DEFAULT_LINGZHU_TASK_PREFIX = "我现在的任务是"
|
|
17
17
|
DEFAULT_LINGZHU_PASSIVE_CHAT_TYPE = "passive"
|
|
18
|
+
_LINGZHU_COMMAND_PUNCTUATION = "::,,。.;;!!??、~~`'\"“”‘’()()【】[]《》<>"
|
|
19
|
+
_LINGZHU_COMMAND_PUNCT_TRANSLATION = str.maketrans({char: " " for char in _LINGZHU_COMMAND_PUNCTUATION})
|
|
20
|
+
_LINGZHU_PREFIX_SEPARATORS_CLASS = re.escape(_LINGZHU_COMMAND_PUNCTUATION) + r"\s"
|
|
18
21
|
|
|
19
22
|
_AUTH_AK_SEGMENTS = (8, 4, 4, 4, 12)
|
|
20
23
|
_AUTH_AK_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
@@ -237,12 +240,28 @@ def lingzhu_extract_user_text(messages: Any) -> str:
|
|
|
237
240
|
return "\n".join(parts).strip()
|
|
238
241
|
|
|
239
242
|
|
|
240
|
-
def
|
|
243
|
+
def lingzhu_normalize_command_text(text: Any) -> str:
|
|
241
244
|
normalized = str(text or "").strip()
|
|
242
|
-
if not normalized
|
|
245
|
+
if not normalized:
|
|
246
|
+
return ""
|
|
247
|
+
normalized = normalized.translate(_LINGZHU_COMMAND_PUNCT_TRANSLATION)
|
|
248
|
+
normalized = re.sub(r"\s+", " ", normalized)
|
|
249
|
+
return normalized.strip()
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def lingzhu_extract_task_text(text: Any) -> str | None:
|
|
253
|
+
raw_text = str(text or "").strip()
|
|
254
|
+
if not raw_text:
|
|
255
|
+
return None
|
|
256
|
+
prefix_pattern = r"^[{separators}]*{prefix}[{separators}]*".format(
|
|
257
|
+
separators=_LINGZHU_PREFIX_SEPARATORS_CLASS,
|
|
258
|
+
prefix="".join(f"{re.escape(char)}[{_LINGZHU_PREFIX_SEPARATORS_CLASS}]*" for char in DEFAULT_LINGZHU_TASK_PREFIX),
|
|
259
|
+
)
|
|
260
|
+
matched = re.match(prefix_pattern, raw_text)
|
|
261
|
+
if matched is None:
|
|
243
262
|
return None
|
|
244
|
-
remainder =
|
|
245
|
-
remainder = remainder.lstrip("
|
|
263
|
+
remainder = raw_text[matched.end() :].strip()
|
|
264
|
+
remainder = remainder.lstrip(_LINGZHU_COMMAND_PUNCTUATION + " \t\r\n")
|
|
246
265
|
return remainder or None
|
|
247
266
|
|
|
248
267
|
|