@researai/deepscientist 1.5.14 → 1.5.16
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 +336 -90
- package/assets/branding/logo-raster.png +0 -0
- package/bin/ds.js +816 -131
- package/docs/en/00_QUICK_START.md +36 -15
- package/docs/en/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +11 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
- 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/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/README.md +24 -0
- package/docs/zh/00_QUICK_START.md +36 -15
- package/docs/zh/01_SETTINGS_REFERENCE.md +53 -4
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +19 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +11 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +20 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +65 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
- 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/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/README.md +24 -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/acp/envelope.py +6 -0
- 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 +4276 -308
- 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 +309 -69
- package/src/deepscientist/bash_exec/shells.py +87 -0
- package/src/deepscientist/bridges/connectors.py +51 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +232 -0
- package/src/deepscientist/config/models.py +8 -4
- package/src/deepscientist/config/service.py +38 -11
- package/src/deepscientist/connector/weixin_support.py +122 -1
- package/src/deepscientist/daemon/api/handlers.py +199 -9
- package/src/deepscientist/daemon/api/router.py +5 -0
- package/src/deepscientist/daemon/app.py +1458 -289
- package/src/deepscientist/doctor.py +51 -0
- package/src/deepscientist/file_lock.py +48 -0
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +296 -1
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +212 -5
- package/src/deepscientist/process_control.py +161 -0
- package/src/deepscientist/prompts/builder.py +501 -453
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +2539 -195
- package/src/deepscientist/quest/stage_views.py +177 -1
- package/src/deepscientist/runners/base.py +2 -0
- package/src/deepscientist/runners/codex.py +169 -31
- package/src/deepscientist/runners/runtime_overrides.py +17 -1
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +24 -4
- package/src/prompts/system.md +921 -72
- package/src/prompts/system_copilot.md +43 -0
- package/src/skills/analysis-campaign/SKILL.md +32 -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 +10 -0
- package/src/skills/decision/SKILL.md +27 -2
- package/src/skills/experiment/SKILL.md +16 -2
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +19 -0
- package/src/skills/idea/SKILL.md +79 -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 +9 -1
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1645 -0
- package/src/skills/rebuttal/SKILL.md +3 -1
- package/src/skills/review/SKILL.md +3 -1
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +81 -12
- package/src/skills/write/references/outline-evidence-contract-example.md +107 -0
- package/src/tui/dist/app/AppContainer.js +22 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
- package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
- package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
- package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
- package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
- package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
- package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
- package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
- package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
- package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
- package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
- package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
- package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
- package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
- package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
- package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
- package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
- package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
- package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
- package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
- package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DaF9Nge_.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-BSVx6dXE.js +0 -123
- package/src/ui/dist/assets/CliPlugin-C9gzJX41.js +0 -5905
- package/src/ui/dist/assets/CodeEditorPlugin-DU9G0Tox.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-DoX_fI9l.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-C4FWIXuU.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-BgfFMgtf.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-tcPkfY_x.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-_dKV60Bf.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-Bje0ayoC.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CVsBzAln.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-xjmrqv_8.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-mMM2A8wP.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-3kVDSOBo.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/NotebookEditor-SoJ8X-MO.js +0 -84873
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfLoader-DElVuHl9.js +0 -25468
- package/src/ui/dist/assets/PdfMarkdownPlugin-Bq88XT4G.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-CsCXMo9S.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/SearchPlugin-oUPvy19k.js +0 -741
- package/src/ui/dist/assets/TextViewerPlugin-CRkT9yNy.js +0 -472
- package/src/ui/dist/assets/VNCViewer-BgbuvWhR.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-v_RASACv.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-5hC9d0VH.js +0 -17
- package/src/ui/dist/assets/file-content-D1PxfOrp.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DG1oT_Hj.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-BmdFYQlk.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-Dqe2X2tW.js +0 -18
- package/src/ui/dist/assets/index-BQG-1s2o.css +0 -12553
- package/src/ui/dist/assets/index-DVsMKK_y.js +0 -25
- package/src/ui/dist/assets/index-Duvz8Ip0.js +0 -159
- package/src/ui/dist/assets/index-Nt9hS4ck.js +0 -244829
- package/src/ui/dist/assets/index-RDlNXXx1.js +0 -120
- package/src/ui/dist/assets/monaco-DIXge1CP.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BBTTQaO-.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-BWlolyxo.js +0 -476
- package/src/ui/dist/assets/project-sync-BM5PkFH4.js +0 -297
- package/src/ui/dist/assets/select-D4dAtrA8.js +0 -1690
- package/src/ui/dist/assets/sigma-CKbE5jJT.js +0 -22
- package/src/ui/dist/assets/square-check-big-CZNGMgiB.js +0 -17
- package/src/ui/dist/assets/trash-DaB37xAz.js +0 -32
- package/src/ui/dist/assets/useCliAccess-C2OmAcWe.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-Dowd1Ij4.js +0 -53
- package/src/ui/dist/assets/wrap-text-BGjAhAUq.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-dMZQMXzc.js +0 -34
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class AttachToken:
|
|
11
|
+
token: str
|
|
12
|
+
quest_root: Path
|
|
13
|
+
bash_id: str
|
|
14
|
+
expires_at: float
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(slots=True)
|
|
18
|
+
class TerminalClient:
|
|
19
|
+
client_id: str
|
|
20
|
+
send_text: Any
|
|
21
|
+
send_binary: Any
|
|
22
|
+
close: Any
|
|
23
|
+
send_lock: threading.Lock
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
import codecs
|
|
4
5
|
import json
|
|
5
6
|
import os
|
|
6
|
-
import pty
|
|
7
7
|
import select
|
|
8
8
|
import shlex
|
|
9
|
-
import signal
|
|
10
9
|
import subprocess
|
|
11
10
|
import sys
|
|
11
|
+
import threading
|
|
12
12
|
import time
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
|
+
if os.name != "nt": # pragma: no cover - exercised on POSIX
|
|
17
|
+
import pty
|
|
18
|
+
else: # pragma: no cover - exercised on Windows
|
|
19
|
+
pty = None
|
|
20
|
+
|
|
21
|
+
from ..process_control import process_session_popen_kwargs, terminate_subprocess
|
|
16
22
|
from .service import (
|
|
17
23
|
BASH_CARRIAGE_RETURN_PREFIX,
|
|
18
24
|
BASH_PROGRESS_PREFIX,
|
|
@@ -22,6 +28,7 @@ from .service import (
|
|
|
22
28
|
_coerce_session_status,
|
|
23
29
|
_parse_progress_marker,
|
|
24
30
|
)
|
|
31
|
+
from .shells import build_exec_shell_launch
|
|
25
32
|
from ..shared import append_jsonl, ensure_dir, iter_jsonl, read_json, read_jsonl, utc_now
|
|
26
33
|
|
|
27
34
|
DEFAULT_STOP_GRACE_SECONDS = 5
|
|
@@ -179,39 +186,21 @@ def _status_marker(meta: dict[str, Any], *, status: str, exit_code: int | None,
|
|
|
179
186
|
|
|
180
187
|
|
|
181
188
|
def _terminate_process(process: subprocess.Popen[bytes], process_group_id: int | None) -> None:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return
|
|
189
|
-
else:
|
|
190
|
-
process.terminate()
|
|
191
|
-
deadline = time.monotonic() + DEFAULT_STOP_GRACE_SECONDS
|
|
192
|
-
while time.monotonic() < deadline:
|
|
193
|
-
if process.poll() is not None:
|
|
194
|
-
return
|
|
195
|
-
time.sleep(0.1)
|
|
196
|
-
if isinstance(process_group_id, int) and process_group_id > 0:
|
|
197
|
-
try:
|
|
198
|
-
os.killpg(process_group_id, signal.SIGKILL)
|
|
199
|
-
except ProcessLookupError:
|
|
200
|
-
return
|
|
201
|
-
elif process.poll() is None:
|
|
202
|
-
process.kill()
|
|
189
|
+
terminate_subprocess(
|
|
190
|
+
process,
|
|
191
|
+
process_group_id=process_group_id,
|
|
192
|
+
force=False,
|
|
193
|
+
grace_seconds=DEFAULT_STOP_GRACE_SECONDS,
|
|
194
|
+
)
|
|
203
195
|
|
|
204
196
|
|
|
205
197
|
def _terminate_process_force(process: subprocess.Popen[bytes], process_group_id: int | None) -> None:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return
|
|
213
|
-
else:
|
|
214
|
-
process.kill()
|
|
198
|
+
terminate_subprocess(
|
|
199
|
+
process,
|
|
200
|
+
process_group_id=process_group_id,
|
|
201
|
+
force=True,
|
|
202
|
+
grace_seconds=DEFAULT_STOP_GRACE_SECONDS,
|
|
203
|
+
)
|
|
215
204
|
|
|
216
205
|
|
|
217
206
|
def _drain_buffer(
|
|
@@ -266,6 +255,12 @@ def _parse_terminal_prompt_marker(line: str) -> dict[str, str] | None:
|
|
|
266
255
|
|
|
267
256
|
|
|
268
257
|
def _format_terminal_prompt(meta: dict[str, Any], cwd_value: str) -> str:
|
|
258
|
+
cwd_b64 = str(cwd_value or "").strip()
|
|
259
|
+
if meta.get("shell_family") == "powershell" and cwd_b64:
|
|
260
|
+
try:
|
|
261
|
+
cwd_value = base64.b64decode(cwd_b64.encode("ascii")).decode("utf-8")
|
|
262
|
+
except Exception:
|
|
263
|
+
cwd_value = cwd_b64
|
|
269
264
|
quest_root = Path(str(meta.get("quest_root") or ".")).expanduser().resolve()
|
|
270
265
|
cwd_path = Path(str(cwd_value or quest_root)).expanduser().resolve()
|
|
271
266
|
home = Path.home().expanduser().resolve()
|
|
@@ -274,6 +269,8 @@ def _format_terminal_prompt(meta: dict[str, Any], cwd_value: str) -> str:
|
|
|
274
269
|
display = "~" if relative == "." else f"~/{relative}"
|
|
275
270
|
except ValueError:
|
|
276
271
|
display = str(cwd_path)
|
|
272
|
+
if str(meta.get("shell_family") or "").strip().lower() == "powershell":
|
|
273
|
+
return f"PS {display}> "
|
|
277
274
|
return f"{display}$ "
|
|
278
275
|
|
|
279
276
|
|
|
@@ -330,7 +327,19 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
330
327
|
prompt_marker = _parse_terminal_prompt_marker(line) if session_kind == "terminal" else None
|
|
331
328
|
if prompt_marker is not None:
|
|
332
329
|
prompt_ts = str(prompt_marker.get("ts") or utc_now())
|
|
333
|
-
|
|
330
|
+
prompt_cwd_raw = str(
|
|
331
|
+
prompt_marker.get("cwd_b64")
|
|
332
|
+
or prompt_marker.get("cwd")
|
|
333
|
+
or meta.get("cwd")
|
|
334
|
+
or cwd
|
|
335
|
+
)
|
|
336
|
+
if prompt_marker.get("cwd_b64"):
|
|
337
|
+
try:
|
|
338
|
+
prompt_cwd = base64.b64decode(prompt_cwd_raw.encode("ascii")).decode("utf-8")
|
|
339
|
+
except Exception:
|
|
340
|
+
prompt_cwd = prompt_cwd_raw
|
|
341
|
+
else:
|
|
342
|
+
prompt_cwd = prompt_cwd_raw
|
|
334
343
|
update_meta(cwd=prompt_cwd, last_prompt_at=prompt_ts)
|
|
335
344
|
seq += 1
|
|
336
345
|
_append_jsonl(
|
|
@@ -338,7 +347,7 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
338
347
|
{
|
|
339
348
|
"seq": seq,
|
|
340
349
|
"stream": "prompt",
|
|
341
|
-
"line": _format_terminal_prompt(meta,
|
|
350
|
+
"line": _format_terminal_prompt(meta, prompt_cwd_raw),
|
|
342
351
|
"timestamp": prompt_ts,
|
|
343
352
|
},
|
|
344
353
|
)
|
|
@@ -374,39 +383,49 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
374
383
|
|
|
375
384
|
master_fd: int | None = None
|
|
376
385
|
slave_fd: int | None = None
|
|
377
|
-
|
|
386
|
+
output_stream: Any = None
|
|
378
387
|
process: subprocess.Popen[bytes] | None = None
|
|
388
|
+
pipe_chunks: list[bytes] = []
|
|
389
|
+
pipe_chunks_lock = threading.Lock()
|
|
390
|
+
pipe_reader_done = threading.Event()
|
|
391
|
+
pipe_reader_thread: threading.Thread | None = None
|
|
379
392
|
try:
|
|
380
|
-
|
|
393
|
+
launch_argv = [
|
|
394
|
+
str(item)
|
|
395
|
+
for item in (meta.get("launch_argv") or [])
|
|
396
|
+
if str(item).strip()
|
|
397
|
+
] or build_exec_shell_launch(command).argv
|
|
398
|
+
using_pty = os.name != "nt" and pty is not None
|
|
381
399
|
try:
|
|
400
|
+
if not using_pty:
|
|
401
|
+
raise OSError("pty_unavailable")
|
|
382
402
|
master_fd, slave_fd = pty.openpty()
|
|
383
403
|
process = subprocess.Popen(
|
|
384
|
-
|
|
404
|
+
launch_argv,
|
|
385
405
|
cwd=str(cwd),
|
|
386
406
|
env=env_payload,
|
|
387
407
|
stdin=slave_fd,
|
|
388
408
|
stdout=slave_fd,
|
|
389
409
|
stderr=slave_fd,
|
|
390
|
-
|
|
410
|
+
**process_session_popen_kwargs(hide_window=True),
|
|
391
411
|
)
|
|
392
412
|
os.close(slave_fd)
|
|
393
413
|
slave_fd = None
|
|
394
|
-
output_fd = master_fd
|
|
395
414
|
except OSError:
|
|
396
415
|
using_pty = False
|
|
397
416
|
process = subprocess.Popen(
|
|
398
|
-
|
|
417
|
+
launch_argv,
|
|
399
418
|
cwd=str(cwd),
|
|
400
419
|
env=env_payload,
|
|
401
|
-
stdin=subprocess.
|
|
420
|
+
stdin=subprocess.PIPE,
|
|
402
421
|
stdout=subprocess.PIPE,
|
|
403
422
|
stderr=subprocess.STDOUT,
|
|
404
|
-
|
|
423
|
+
**process_session_popen_kwargs(hide_window=True),
|
|
405
424
|
)
|
|
406
425
|
if process.stdout is None:
|
|
407
426
|
raise RuntimeError("bash_exec_missing_stdout_pipe")
|
|
408
|
-
|
|
409
|
-
process_group_id = os.getpgid(process.pid)
|
|
427
|
+
output_stream = process.stdout
|
|
428
|
+
process_group_id = process.pid if os.name == "nt" else os.getpgid(process.pid)
|
|
410
429
|
update_meta(
|
|
411
430
|
monitor_pid=os.getpid(),
|
|
412
431
|
process_pid=process.pid,
|
|
@@ -420,6 +439,24 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
420
439
|
buffer = ""
|
|
421
440
|
deadline = time.monotonic() + int(timeout_seconds) if isinstance(timeout_seconds, int) and timeout_seconds > 0 else None
|
|
422
441
|
stop_requested = False
|
|
442
|
+
if not using_pty and output_stream is not None:
|
|
443
|
+
def _pipe_reader() -> None:
|
|
444
|
+
try:
|
|
445
|
+
while True:
|
|
446
|
+
chunk = output_stream.read(4096)
|
|
447
|
+
if not chunk:
|
|
448
|
+
break
|
|
449
|
+
with pipe_chunks_lock:
|
|
450
|
+
pipe_chunks.append(chunk)
|
|
451
|
+
finally:
|
|
452
|
+
pipe_reader_done.set()
|
|
453
|
+
|
|
454
|
+
pipe_reader_thread = threading.Thread(
|
|
455
|
+
target=_pipe_reader,
|
|
456
|
+
name=f"bash-exec-monitor-{meta.get('bash_id')}",
|
|
457
|
+
daemon=True,
|
|
458
|
+
)
|
|
459
|
+
pipe_reader_thread.start()
|
|
423
460
|
|
|
424
461
|
while True:
|
|
425
462
|
if not stop_requested and stop_request_path.exists():
|
|
@@ -448,7 +485,7 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
448
485
|
_terminate_process(process, process_group_id)
|
|
449
486
|
stop_requested = True
|
|
450
487
|
|
|
451
|
-
if
|
|
488
|
+
if ((using_pty and master_fd is not None) or process.stdin is not None) and process.poll() is None:
|
|
452
489
|
cursor_payload = read_json(input_cursor_path, {}) or {}
|
|
453
490
|
offset = int(cursor_payload.get("offset") or 0)
|
|
454
491
|
total_input_entries = sum(1 for _ in iter_jsonl(input_path))
|
|
@@ -459,7 +496,11 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
459
496
|
raw_data = str(entry.get("data") or "")
|
|
460
497
|
if raw_data:
|
|
461
498
|
try:
|
|
462
|
-
|
|
499
|
+
if using_pty and master_fd is not None:
|
|
500
|
+
os.write(master_fd, raw_data.encode("utf-8"))
|
|
501
|
+
elif process.stdin is not None:
|
|
502
|
+
process.stdin.write(raw_data.encode("utf-8"))
|
|
503
|
+
process.stdin.flush()
|
|
463
504
|
except OSError:
|
|
464
505
|
break
|
|
465
506
|
offset += 1
|
|
@@ -471,13 +512,28 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
471
512
|
},
|
|
472
513
|
)
|
|
473
514
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
515
|
+
if using_pty and master_fd is not None:
|
|
516
|
+
ready, _unused_w, _unused_x = select.select([master_fd], [], [], TERMINAL_IO_POLL_SECONDS)
|
|
517
|
+
if ready:
|
|
518
|
+
try:
|
|
519
|
+
chunk = os.read(master_fd, 4096)
|
|
520
|
+
except OSError:
|
|
521
|
+
chunk = b""
|
|
522
|
+
if chunk:
|
|
523
|
+
buffer += decoder.decode(chunk)
|
|
524
|
+
buffer = _drain_buffer(
|
|
525
|
+
buffer,
|
|
526
|
+
append_line,
|
|
527
|
+
flush_partial=session_kind == "terminal",
|
|
528
|
+
carriage_mode="stream" if session_kind == "terminal" else "marker",
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
drained: list[bytes] = []
|
|
532
|
+
with pipe_chunks_lock:
|
|
533
|
+
if pipe_chunks:
|
|
534
|
+
drained = list(pipe_chunks)
|
|
535
|
+
pipe_chunks.clear()
|
|
536
|
+
for chunk in drained:
|
|
481
537
|
buffer += decoder.decode(chunk)
|
|
482
538
|
buffer = _drain_buffer(
|
|
483
539
|
buffer,
|
|
@@ -485,23 +541,42 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
485
541
|
flush_partial=session_kind == "terminal",
|
|
486
542
|
carriage_mode="stream" if session_kind == "terminal" else "marker",
|
|
487
543
|
)
|
|
544
|
+
if not drained:
|
|
545
|
+
time.sleep(TERMINAL_IO_POLL_SECONDS)
|
|
488
546
|
if process.poll() is not None:
|
|
489
547
|
break
|
|
490
548
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
buffer
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
549
|
+
if using_pty and master_fd is not None:
|
|
550
|
+
while True:
|
|
551
|
+
try:
|
|
552
|
+
chunk = os.read(master_fd, 4096)
|
|
553
|
+
except OSError:
|
|
554
|
+
chunk = b""
|
|
555
|
+
if not chunk:
|
|
556
|
+
break
|
|
557
|
+
buffer += decoder.decode(chunk)
|
|
558
|
+
buffer = _drain_buffer(
|
|
559
|
+
buffer,
|
|
560
|
+
append_line,
|
|
561
|
+
flush_partial=session_kind == "terminal",
|
|
562
|
+
carriage_mode="stream" if session_kind == "terminal" else "marker",
|
|
563
|
+
)
|
|
564
|
+
else:
|
|
565
|
+
if pipe_reader_thread is not None:
|
|
566
|
+
pipe_reader_thread.join(timeout=1)
|
|
567
|
+
drained = []
|
|
568
|
+
with pipe_chunks_lock:
|
|
569
|
+
if pipe_chunks:
|
|
570
|
+
drained = list(pipe_chunks)
|
|
571
|
+
pipe_chunks.clear()
|
|
572
|
+
for chunk in drained:
|
|
573
|
+
buffer += decoder.decode(chunk)
|
|
574
|
+
buffer = _drain_buffer(
|
|
575
|
+
buffer,
|
|
576
|
+
append_line,
|
|
577
|
+
flush_partial=session_kind == "terminal",
|
|
578
|
+
carriage_mode="stream" if session_kind == "terminal" else "marker",
|
|
579
|
+
)
|
|
505
580
|
buffer += decoder.decode(b"", final=True)
|
|
506
581
|
if buffer:
|
|
507
582
|
append_line(buffer, stream="partial" if session_kind == "terminal" else "stdout")
|
|
@@ -535,6 +610,11 @@ def run_monitor(session_dir: Path) -> int:
|
|
|
535
610
|
os.close(slave_fd)
|
|
536
611
|
except OSError:
|
|
537
612
|
pass
|
|
613
|
+
if process is not None and process.stdin is not None:
|
|
614
|
+
try:
|
|
615
|
+
process.stdin.close()
|
|
616
|
+
except OSError:
|
|
617
|
+
pass
|
|
538
618
|
if process is not None and process.stdout is not None:
|
|
539
619
|
try:
|
|
540
620
|
process.stdout.close()
|