@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
|
@@ -4,13 +4,17 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import subprocess
|
|
6
6
|
import tempfile
|
|
7
|
-
from shutil import copy2
|
|
8
7
|
from copy import deepcopy
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
from urllib.error import URLError
|
|
11
10
|
from urllib.request import Request
|
|
12
11
|
|
|
13
|
-
from ..codex_cli_compat import
|
|
12
|
+
from ..codex_cli_compat import (
|
|
13
|
+
adapt_profile_only_provider_config,
|
|
14
|
+
materialize_codex_runtime_home,
|
|
15
|
+
normalize_codex_reasoning_effort,
|
|
16
|
+
provider_profile_metadata_from_home,
|
|
17
|
+
)
|
|
14
18
|
from ..connector.connector_profiles import PROFILEABLE_CONNECTOR_NAMES, list_connector_profiles, normalize_connector_config
|
|
15
19
|
from ..connector_runtime import build_discovered_target, infer_connector_transport
|
|
16
20
|
from ..home import repo_root
|
|
@@ -48,6 +52,7 @@ from .models import (
|
|
|
48
52
|
ConfigFileInfo,
|
|
49
53
|
SYSTEM_CONNECTOR_NAMES,
|
|
50
54
|
config_filename,
|
|
55
|
+
default_system_enabled_connectors,
|
|
51
56
|
default_payload,
|
|
52
57
|
)
|
|
53
58
|
|
|
@@ -112,8 +117,9 @@ class ConfigManager:
|
|
|
112
117
|
config = self.load_runtime_config()
|
|
113
118
|
connectors = config.get("connectors") if isinstance(config.get("connectors"), dict) else {}
|
|
114
119
|
system_enabled = connectors.get("system_enabled") if isinstance(connectors.get("system_enabled"), dict) else {}
|
|
120
|
+
defaults = default_system_enabled_connectors()
|
|
115
121
|
return {
|
|
116
|
-
name: self._coerce_bool(system_enabled.get(name), default=name
|
|
122
|
+
name: self._coerce_bool(system_enabled.get(name), default=defaults.get(name, False))
|
|
117
123
|
for name in SYSTEM_CONNECTOR_NAMES
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -1190,6 +1196,14 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1190
1196
|
return "gpt-5.4"
|
|
1191
1197
|
return str(raw_model).strip()
|
|
1192
1198
|
|
|
1199
|
+
@staticmethod
|
|
1200
|
+
def _codex_effective_model(config: dict) -> str:
|
|
1201
|
+
requested = ConfigManager._codex_requested_model(config)
|
|
1202
|
+
profile = ConfigManager._codex_profile_name(config)
|
|
1203
|
+
if profile and not ConfigManager._codex_should_inherit_model(requested):
|
|
1204
|
+
return "inherit"
|
|
1205
|
+
return requested
|
|
1206
|
+
|
|
1193
1207
|
@staticmethod
|
|
1194
1208
|
def _codex_profile_name(config: dict) -> str:
|
|
1195
1209
|
raw_profile = config.get("profile")
|
|
@@ -1207,7 +1221,10 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1207
1221
|
env_key = str(key or "").strip()
|
|
1208
1222
|
if not env_key or value is None:
|
|
1209
1223
|
continue
|
|
1210
|
-
|
|
1224
|
+
env_value = str(value)
|
|
1225
|
+
if env_value == "":
|
|
1226
|
+
continue
|
|
1227
|
+
resolved[env_key] = env_value
|
|
1211
1228
|
return resolved
|
|
1212
1229
|
|
|
1213
1230
|
def _prepare_codex_probe_home(
|
|
@@ -1228,10 +1245,11 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1228
1245
|
|
|
1229
1246
|
temp_home = tempfile.TemporaryDirectory(prefix="ds-codex-probe-")
|
|
1230
1247
|
temp_root = Path(temp_home.name)
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1248
|
+
materialize_codex_runtime_home(
|
|
1249
|
+
source_home=expanded,
|
|
1250
|
+
target_home=temp_root,
|
|
1251
|
+
profile=profile,
|
|
1252
|
+
)
|
|
1235
1253
|
write_text(temp_root / "config.toml", adapted_text)
|
|
1236
1254
|
return str(temp_root), warning, temp_home
|
|
1237
1255
|
|
|
@@ -1356,6 +1374,7 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1356
1374
|
resolved_binary = resolve_runner_binary(binary, runner_name="codex")
|
|
1357
1375
|
profile = self._codex_profile_name(config)
|
|
1358
1376
|
requested_model = self._codex_requested_model(config)
|
|
1377
|
+
effective_model = self._codex_effective_model(config)
|
|
1359
1378
|
raw_reasoning_effort = config.get("model_reasoning_effort")
|
|
1360
1379
|
requested_reasoning_effort = (
|
|
1361
1380
|
str(raw_reasoning_effort).strip()
|
|
@@ -1371,9 +1390,9 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1371
1390
|
"resolved_binary": resolved_binary,
|
|
1372
1391
|
"config_dir": str(config.get("config_dir") or "~/.codex"),
|
|
1373
1392
|
"profile": profile,
|
|
1374
|
-
"model":
|
|
1393
|
+
"model": effective_model or "inherit",
|
|
1375
1394
|
"requested_model": requested_model or "inherit",
|
|
1376
|
-
"effective_model":
|
|
1395
|
+
"effective_model": effective_model or "inherit",
|
|
1377
1396
|
"approval_policy": str(config.get("approval_policy") or "on-request"),
|
|
1378
1397
|
"sandbox_mode": str(config.get("sandbox_mode") or "workspace-write"),
|
|
1379
1398
|
"reasoning_effort": reasoning_effort,
|
|
@@ -1411,9 +1430,17 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1411
1430
|
env["CODEX_HOME"] = prepared_home
|
|
1412
1431
|
if profile_config_warning:
|
|
1413
1432
|
compatibility_warnings.append(profile_config_warning)
|
|
1433
|
+
metadata = provider_profile_metadata_from_home(env.get("CODEX_HOME") or config_dir, profile=profile)
|
|
1434
|
+
if metadata.get("requires_openai_auth") is False:
|
|
1435
|
+
env.pop("OPENAI_API_KEY", None)
|
|
1436
|
+
env.pop("OPENAI_BASE_URL", None)
|
|
1414
1437
|
prompt = "Reply with exactly HELLO."
|
|
1415
1438
|
if reasoning_effort_warning:
|
|
1416
1439
|
compatibility_warnings.append(reasoning_effort_warning)
|
|
1440
|
+
if profile and effective_model == "inherit" and not self._codex_should_inherit_model(requested_model):
|
|
1441
|
+
compatibility_warnings.append(
|
|
1442
|
+
f"Codex profile `{profile}` is provider-backed. DeepScientist is probing it with `model: inherit`."
|
|
1443
|
+
)
|
|
1417
1444
|
base_warnings: list[str] = list(compatibility_warnings)
|
|
1418
1445
|
|
|
1419
1446
|
def run_probe_once(model_for_command: str) -> tuple[list[str], subprocess.CompletedProcess[str] | None, subprocess.TimeoutExpired | None]:
|
|
@@ -1440,7 +1467,7 @@ Use **Test** when the file exposes runtime dependencies.
|
|
|
1440
1467
|
return command, None, exc
|
|
1441
1468
|
return command, result, None
|
|
1442
1469
|
|
|
1443
|
-
command, result, timeout_error = run_probe_once(
|
|
1470
|
+
command, result, timeout_error = run_probe_once(effective_model)
|
|
1444
1471
|
if timeout_error is not None:
|
|
1445
1472
|
details.update(
|
|
1446
1473
|
{
|
|
@@ -13,7 +13,7 @@ from urllib.request import Request
|
|
|
13
13
|
|
|
14
14
|
from .. import __version__ as DEEPSCIENTIST_VERSION
|
|
15
15
|
from ..network import urlopen_with_proxy as urlopen
|
|
16
|
-
from ..shared import ensure_dir, read_json, write_json
|
|
16
|
+
from ..shared import ensure_dir, read_json, utc_now, write_json
|
|
17
17
|
|
|
18
18
|
DEFAULT_WEIXIN_BASE_URL = "https://ilinkai.weixin.qq.com"
|
|
19
19
|
DEFAULT_WEIXIN_CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c"
|
|
@@ -611,6 +611,22 @@ def save_weixin_context_tokens(root: Path, items: dict[str, dict[str, Any]]) ->
|
|
|
611
611
|
write_json(weixin_context_tokens_path(root), {"tokens": items})
|
|
612
612
|
|
|
613
613
|
|
|
614
|
+
def _weixin_replay_cursor(value: Any) -> int:
|
|
615
|
+
try:
|
|
616
|
+
return max(0, int(value or 0))
|
|
617
|
+
except (TypeError, ValueError):
|
|
618
|
+
return 0
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def get_weixin_context_entry(root: Path, user_id: str) -> dict[str, Any]:
|
|
622
|
+
normalized_user_id = str(user_id or "").strip()
|
|
623
|
+
if not normalized_user_id:
|
|
624
|
+
return {}
|
|
625
|
+
items = load_weixin_context_tokens(root)
|
|
626
|
+
current = items.get(normalized_user_id)
|
|
627
|
+
return dict(current) if isinstance(current, dict) else {}
|
|
628
|
+
|
|
629
|
+
|
|
614
630
|
def remember_weixin_context_token(
|
|
615
631
|
root: Path,
|
|
616
632
|
*,
|
|
@@ -635,6 +651,16 @@ def remember_weixin_context_token(
|
|
|
635
651
|
"conversation_id": str(conversation_id or current.get("conversation_id") or "").strip() or None,
|
|
636
652
|
"message_id": str(message_id or current.get("message_id") or "").strip() or None,
|
|
637
653
|
"updated_at": str(updated_at or current.get("updated_at") or "").strip() or None,
|
|
654
|
+
"stale_context": False,
|
|
655
|
+
"stale_since": None,
|
|
656
|
+
"last_ret_minus_2_at": None,
|
|
657
|
+
"last_outbound_error": None,
|
|
658
|
+
"last_outbound_kind": None,
|
|
659
|
+
"queued_replay_cursor": _weixin_replay_cursor(current.get("queued_replay_cursor")),
|
|
660
|
+
"last_replay_at": str(current.get("last_replay_at") or "").strip() or None,
|
|
661
|
+
"last_replay_trigger_message_id": str(current.get("last_replay_trigger_message_id") or "").strip() or None,
|
|
662
|
+
"last_replayed_count": _weixin_replay_cursor(current.get("last_replayed_count")) or None,
|
|
663
|
+
"last_replay_dropped_count": _weixin_replay_cursor(current.get("last_replay_dropped_count")) or None,
|
|
638
664
|
}
|
|
639
665
|
save_weixin_context_tokens(root, items)
|
|
640
666
|
|
|
@@ -648,6 +674,101 @@ def get_weixin_context_token(root: Path, user_id: str) -> str | None:
|
|
|
648
674
|
return token or None
|
|
649
675
|
|
|
650
676
|
|
|
677
|
+
def get_weixin_replay_cursor(root: Path, user_id: str) -> int:
|
|
678
|
+
entry = get_weixin_context_entry(root, user_id)
|
|
679
|
+
return _weixin_replay_cursor(entry.get("queued_replay_cursor"))
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def update_weixin_replay_cursor(
|
|
683
|
+
root: Path,
|
|
684
|
+
*,
|
|
685
|
+
user_id: str,
|
|
686
|
+
queued_replay_cursor: int,
|
|
687
|
+
last_replay_at: str | None = None,
|
|
688
|
+
last_replay_trigger_message_id: str | None = None,
|
|
689
|
+
last_replayed_count: int | None = None,
|
|
690
|
+
last_replay_dropped_count: int | None = None,
|
|
691
|
+
) -> dict[str, Any]:
|
|
692
|
+
normalized_user_id = str(user_id or "").strip()
|
|
693
|
+
if not normalized_user_id:
|
|
694
|
+
return {}
|
|
695
|
+
items = load_weixin_context_tokens(root)
|
|
696
|
+
current = dict(items.get(normalized_user_id) or {})
|
|
697
|
+
current.update(
|
|
698
|
+
{
|
|
699
|
+
"user_id": normalized_user_id,
|
|
700
|
+
"queued_replay_cursor": _weixin_replay_cursor(queued_replay_cursor),
|
|
701
|
+
"last_replay_at": str(last_replay_at or utc_now()).strip() or utc_now(),
|
|
702
|
+
"last_replay_trigger_message_id": str(last_replay_trigger_message_id or "").strip() or None,
|
|
703
|
+
"last_replayed_count": _weixin_replay_cursor(last_replayed_count) if last_replayed_count is not None else None,
|
|
704
|
+
"last_replay_dropped_count": _weixin_replay_cursor(last_replay_dropped_count)
|
|
705
|
+
if last_replay_dropped_count is not None
|
|
706
|
+
else None,
|
|
707
|
+
}
|
|
708
|
+
)
|
|
709
|
+
items[normalized_user_id] = current
|
|
710
|
+
save_weixin_context_tokens(root, items)
|
|
711
|
+
return current
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def mark_weixin_context_stale(
|
|
715
|
+
root: Path,
|
|
716
|
+
*,
|
|
717
|
+
user_id: str,
|
|
718
|
+
error: str,
|
|
719
|
+
kind: str | None = None,
|
|
720
|
+
updated_at: str | None = None,
|
|
721
|
+
) -> dict[str, Any]:
|
|
722
|
+
normalized_user_id = str(user_id or "").strip()
|
|
723
|
+
if not normalized_user_id:
|
|
724
|
+
return {}
|
|
725
|
+
timestamp = str(updated_at or utc_now()).strip() or utc_now()
|
|
726
|
+
items = load_weixin_context_tokens(root)
|
|
727
|
+
current = dict(items.get(normalized_user_id) or {})
|
|
728
|
+
current.update(
|
|
729
|
+
{
|
|
730
|
+
"user_id": normalized_user_id,
|
|
731
|
+
"stale_context": True,
|
|
732
|
+
"stale_since": str(current.get("stale_since") or "").strip() or timestamp,
|
|
733
|
+
"last_ret_minus_2_at": timestamp,
|
|
734
|
+
"last_outbound_error": str(error or "").strip() or None,
|
|
735
|
+
"last_outbound_kind": str(kind or "").strip() or None,
|
|
736
|
+
}
|
|
737
|
+
)
|
|
738
|
+
items[normalized_user_id] = current
|
|
739
|
+
save_weixin_context_tokens(root, items)
|
|
740
|
+
return current
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def clear_weixin_context_send_state(
|
|
744
|
+
root: Path,
|
|
745
|
+
*,
|
|
746
|
+
user_id: str,
|
|
747
|
+
kind: str | None = None,
|
|
748
|
+
updated_at: str | None = None,
|
|
749
|
+
) -> dict[str, Any]:
|
|
750
|
+
normalized_user_id = str(user_id or "").strip()
|
|
751
|
+
if not normalized_user_id:
|
|
752
|
+
return {}
|
|
753
|
+
timestamp = str(updated_at or utc_now()).strip() or utc_now()
|
|
754
|
+
items = load_weixin_context_tokens(root)
|
|
755
|
+
current = dict(items.get(normalized_user_id) or {})
|
|
756
|
+
current.update(
|
|
757
|
+
{
|
|
758
|
+
"user_id": normalized_user_id,
|
|
759
|
+
"stale_context": False,
|
|
760
|
+
"stale_since": None,
|
|
761
|
+
"last_ret_minus_2_at": None,
|
|
762
|
+
"last_outbound_error": None,
|
|
763
|
+
"last_outbound_kind": str(kind or "").strip() or None,
|
|
764
|
+
"last_success_at": timestamp,
|
|
765
|
+
}
|
|
766
|
+
)
|
|
767
|
+
items[normalized_user_id] = current
|
|
768
|
+
save_weixin_context_tokens(root, items)
|
|
769
|
+
return current
|
|
770
|
+
|
|
771
|
+
|
|
651
772
|
def weixin_sync_state_path(root: Path) -> Path:
|
|
652
773
|
return ensure_dir(root) / "sync_state.json"
|
|
653
774
|
|
|
@@ -11,12 +11,17 @@ from urllib.parse import parse_qs, unquote
|
|
|
11
11
|
from ...acp import OptionalACPBridge, build_session_descriptor, build_session_update, get_acp_bridge_status
|
|
12
12
|
from ...bash_exec.service import DEFAULT_TERMINAL_SESSION_ID
|
|
13
13
|
from ... import __version__ as DEEPSCIENTIST_VERSION
|
|
14
|
-
from ...gitops import commit_detail, compare_refs, diff_file_between_refs, diff_file_for_commit, export_git_graph,
|
|
14
|
+
from ...gitops import commit_detail, compare_refs, diff_file_between_refs, diff_file_for_commit, export_git_graph, log_ref_history
|
|
15
15
|
from ...memory import MemoryService
|
|
16
16
|
from ...quest import QuestService
|
|
17
17
|
from ...shared import generate_id, read_json, read_text, resolve_within, run_command, sha256_text, utc_now
|
|
18
18
|
from ...runners import RunRequest
|
|
19
19
|
|
|
20
|
+
_COPILOT_LEAD_MESSAGE = (
|
|
21
|
+
"我是 DeepScientist,任何事情都可以找我帮忙。"
|
|
22
|
+
"你可以让我读论文、改代码、看实验、整理思路,或者直接开始执行一个任务。"
|
|
23
|
+
)
|
|
24
|
+
|
|
20
25
|
|
|
21
26
|
class ApiHandlers:
|
|
22
27
|
def __init__(self, app: "DaemonApp") -> None:
|
|
@@ -73,6 +78,8 @@ class ApiHandlers:
|
|
|
73
78
|
runtime_payload = {
|
|
74
79
|
"surface": "quest",
|
|
75
80
|
"version": DEEPSCIENTIST_VERSION,
|
|
81
|
+
"homePath": str(self.app.home),
|
|
82
|
+
"auth": self.app.browser_auth_runtime_payload(),
|
|
76
83
|
"supports": {
|
|
77
84
|
"productApis": False,
|
|
78
85
|
"socketIo": False,
|
|
@@ -158,9 +165,87 @@ npm --prefix src/ui run build</pre>
|
|
|
158
165
|
"daemon_id": self.app.daemon_id,
|
|
159
166
|
"managed_by": self.app.daemon_managed_by,
|
|
160
167
|
"pid": os.getpid(),
|
|
168
|
+
"auth_enabled": self.app.browser_auth_enabled,
|
|
161
169
|
"sessions": self.app.sessions.snapshot(),
|
|
162
170
|
}
|
|
163
171
|
|
|
172
|
+
def auth_login(self, body: dict | None = None) -> tuple[int, dict, str] | tuple[int, dict]:
|
|
173
|
+
if not self.app.browser_auth_enabled:
|
|
174
|
+
payload = {
|
|
175
|
+
"ok": True,
|
|
176
|
+
"authenticated": True,
|
|
177
|
+
"auth_enabled": False,
|
|
178
|
+
}
|
|
179
|
+
return 200, {"Content-Type": "application/json; charset=utf-8"}, json.dumps(payload, ensure_ascii=False)
|
|
180
|
+
|
|
181
|
+
candidate = str(((body or {}) if isinstance(body, dict) else {}).get("token") or "").strip()
|
|
182
|
+
if not candidate:
|
|
183
|
+
return 400, {
|
|
184
|
+
"ok": False,
|
|
185
|
+
"message": "Token is required.",
|
|
186
|
+
"auth_required": True,
|
|
187
|
+
"auth_enabled": True,
|
|
188
|
+
}
|
|
189
|
+
if not self.app.browser_auth_matches(candidate):
|
|
190
|
+
return 401, {
|
|
191
|
+
"ok": False,
|
|
192
|
+
"message": "Invalid token.",
|
|
193
|
+
"auth_required": True,
|
|
194
|
+
"auth_enabled": True,
|
|
195
|
+
}
|
|
196
|
+
payload = {
|
|
197
|
+
"ok": True,
|
|
198
|
+
"authenticated": True,
|
|
199
|
+
"auth_enabled": True,
|
|
200
|
+
"token_masked": self.app.masked_browser_auth_token(),
|
|
201
|
+
}
|
|
202
|
+
return (
|
|
203
|
+
200,
|
|
204
|
+
{
|
|
205
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
206
|
+
"Cache-Control": "no-store, max-age=0, must-revalidate",
|
|
207
|
+
"Set-Cookie": self.app._browser_auth_cookie_header(candidate),
|
|
208
|
+
},
|
|
209
|
+
json.dumps(payload, ensure_ascii=False),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def auth_token(self) -> dict:
|
|
213
|
+
return {
|
|
214
|
+
"ok": True,
|
|
215
|
+
"auth_enabled": self.app.browser_auth_enabled,
|
|
216
|
+
"token": self.app.browser_auth_token,
|
|
217
|
+
"token_masked": self.app.masked_browser_auth_token(),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
def auth_rotate(self, body: dict | None = None) -> tuple[int, dict, str] | tuple[int, dict]:
|
|
221
|
+
if not self.app.browser_auth_enabled:
|
|
222
|
+
payload = {
|
|
223
|
+
"ok": True,
|
|
224
|
+
"auth_enabled": False,
|
|
225
|
+
"rotated": False,
|
|
226
|
+
"token": None,
|
|
227
|
+
"token_masked": None,
|
|
228
|
+
}
|
|
229
|
+
return 200, {"Content-Type": "application/json; charset=utf-8"}, json.dumps(payload, ensure_ascii=False)
|
|
230
|
+
|
|
231
|
+
rotated = self.app.rotate_browser_auth_token()
|
|
232
|
+
payload = {
|
|
233
|
+
"ok": True,
|
|
234
|
+
"auth_enabled": True,
|
|
235
|
+
"rotated": True,
|
|
236
|
+
"token": rotated,
|
|
237
|
+
"token_masked": self.app.masked_browser_auth_token(),
|
|
238
|
+
}
|
|
239
|
+
return (
|
|
240
|
+
200,
|
|
241
|
+
{
|
|
242
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
243
|
+
"Cache-Control": "no-store, max-age=0, must-revalidate",
|
|
244
|
+
"Set-Cookie": self.app._browser_auth_cookie_header(rotated),
|
|
245
|
+
},
|
|
246
|
+
json.dumps(payload, ensure_ascii=False),
|
|
247
|
+
)
|
|
248
|
+
|
|
164
249
|
def system_update(self) -> dict:
|
|
165
250
|
return self.app.system_update_status()
|
|
166
251
|
|
|
@@ -324,6 +409,33 @@ npm --prefix src/ui run build</pre>
|
|
|
324
409
|
return 400, {"ok": False, "message": str(exc)}
|
|
325
410
|
except RuntimeError as exc:
|
|
326
411
|
return 409, {"ok": False, "message": str(exc)}
|
|
412
|
+
workspace_mode = (
|
|
413
|
+
str(startup_contract.get("workspace_mode") or "").strip().lower()
|
|
414
|
+
if isinstance(startup_contract, dict)
|
|
415
|
+
else ""
|
|
416
|
+
)
|
|
417
|
+
if workspace_mode in {"copilot", "autonomous"}:
|
|
418
|
+
quest_root = self.app.quest_service._quest_root(snapshot["quest_id"])
|
|
419
|
+
self.app.quest_service.update_research_state(quest_root, workspace_mode=workspace_mode)
|
|
420
|
+
if workspace_mode == "copilot":
|
|
421
|
+
self.app.quest_service.append_message(
|
|
422
|
+
snapshot["quest_id"],
|
|
423
|
+
"assistant",
|
|
424
|
+
_COPILOT_LEAD_MESSAGE,
|
|
425
|
+
source="deepscientist",
|
|
426
|
+
)
|
|
427
|
+
self.app.quest_service.update_runtime_state(
|
|
428
|
+
quest_root=quest_root,
|
|
429
|
+
status="idle",
|
|
430
|
+
display_status="idle",
|
|
431
|
+
)
|
|
432
|
+
self.app.quest_service.set_continuation_state(
|
|
433
|
+
quest_root,
|
|
434
|
+
policy="wait_for_user_or_resume",
|
|
435
|
+
anchor="decision",
|
|
436
|
+
reason="copilot_mode",
|
|
437
|
+
)
|
|
438
|
+
snapshot = self.app.quest_service.snapshot(snapshot["quest_id"])
|
|
327
439
|
payload: dict[str, object] = {"ok": True, "snapshot": snapshot}
|
|
328
440
|
if auto_start:
|
|
329
441
|
startup = self.app.submit_user_message(
|
|
@@ -474,6 +586,12 @@ npm --prefix src/ui run build</pre>
|
|
|
474
586
|
|
|
475
587
|
def quest_session(self, quest_id: str) -> dict:
|
|
476
588
|
snapshot = self.app.quest_service.snapshot_fast(quest_id)
|
|
589
|
+
for kind in ("details", "canvas", "git_canvas"):
|
|
590
|
+
try:
|
|
591
|
+
self.app.quest_service.prime_projection(quest_id, kind)
|
|
592
|
+
except Exception:
|
|
593
|
+
continue
|
|
594
|
+
self.app.schedule_latest_quest_terminal_prewarm(quest_id)
|
|
477
595
|
return {
|
|
478
596
|
"ok": True,
|
|
479
597
|
"quest_id": quest_id,
|
|
@@ -491,7 +609,7 @@ npm --prefix src/ui run build</pre>
|
|
|
491
609
|
tail = tail_raw in {"1", "true", "yes", "on"}
|
|
492
610
|
format_name = ((query.get("format") or ["both"])[0] or "both").lower()
|
|
493
611
|
session_id = ((query.get("session_id") or [f"quest:{quest_id}"])[0] or f"quest:{quest_id}")
|
|
494
|
-
payload = self.
|
|
612
|
+
payload = self.app.quest_service.events(
|
|
495
613
|
quest_id,
|
|
496
614
|
after=after,
|
|
497
615
|
before=before,
|
|
@@ -768,7 +886,20 @@ npm --prefix src/ui run build</pre>
|
|
|
768
886
|
return self.app.control_quest(quest_id, action=action, source=source)
|
|
769
887
|
|
|
770
888
|
def workflow(self, quest_id: str) -> dict:
|
|
771
|
-
|
|
889
|
+
payload = self.app.quest_service.workflow(quest_id)
|
|
890
|
+
projection_state = str(((payload or {}).get("projection_status") or {}).get("state") or "").strip().lower()
|
|
891
|
+
if projection_state and projection_state != "ready":
|
|
892
|
+
if isinstance(payload, dict):
|
|
893
|
+
payload["optimization_frontier"] = None
|
|
894
|
+
return payload
|
|
895
|
+
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
896
|
+
try:
|
|
897
|
+
frontier = self.app.artifact_service.get_optimization_frontier(quest_root)
|
|
898
|
+
except Exception:
|
|
899
|
+
frontier = {"ok": False}
|
|
900
|
+
if isinstance(payload, dict):
|
|
901
|
+
payload["optimization_frontier"] = frontier.get("optimization_frontier") if isinstance(frontier, dict) else None
|
|
902
|
+
return payload
|
|
772
903
|
|
|
773
904
|
def quest_layout(self, quest_id: str) -> dict:
|
|
774
905
|
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
@@ -816,15 +947,21 @@ npm --prefix src/ui run build</pre>
|
|
|
816
947
|
def metrics_timeline(self, quest_id: str) -> dict:
|
|
817
948
|
return self.app.quest_service.metrics_timeline(quest_id)
|
|
818
949
|
|
|
950
|
+
def baseline_compare(self, quest_id: str) -> dict:
|
|
951
|
+
return self.app.quest_service.baseline_compare(quest_id)
|
|
952
|
+
|
|
819
953
|
def git_branches(self, quest_id: str) -> dict:
|
|
820
954
|
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
821
|
-
payload =
|
|
955
|
+
payload = self.app.quest_service.git_branch_canvas(quest_id)
|
|
822
956
|
research_state = self.app.quest_service.read_research_state(quest_root)
|
|
823
957
|
active_workspace_branch = str(research_state.get("current_workspace_branch") or "").strip() or None
|
|
824
958
|
research_head_branch = str(research_state.get("research_head_branch") or "").strip() or None
|
|
825
959
|
payload["active_workspace_ref"] = active_workspace_branch
|
|
826
960
|
payload["research_head_ref"] = research_head_branch
|
|
827
961
|
payload["workspace_mode"] = str(research_state.get("workspace_mode") or "quest").strip() or "quest"
|
|
962
|
+
projection_state = str(((payload or {}).get("projection_status") or {}).get("state") or "").strip().lower()
|
|
963
|
+
if projection_state and projection_state != "ready" and not (payload.get("nodes") or []):
|
|
964
|
+
return payload
|
|
828
965
|
quest_data = self.app.quest_service.read_quest_yaml(quest_root)
|
|
829
966
|
active_anchor = str(quest_data.get("active_anchor") or "").strip().lower()
|
|
830
967
|
active_analysis_campaign_id = str(research_state.get("active_analysis_campaign_id") or "").strip() or None
|
|
@@ -837,11 +974,40 @@ npm --prefix src/ui run build</pre>
|
|
|
837
974
|
branch_summary = self.app.artifact_service.list_research_branches(quest_root)
|
|
838
975
|
except Exception:
|
|
839
976
|
branch_summary = {"branches": []}
|
|
977
|
+
try:
|
|
978
|
+
optimization_frontier = self.app.artifact_service.get_optimization_frontier(quest_root)
|
|
979
|
+
except Exception:
|
|
980
|
+
optimization_frontier = {"ok": False}
|
|
840
981
|
branch_summary_by_name = {
|
|
841
982
|
str(item.get("branch_name") or "").strip(): item
|
|
842
983
|
for item in (branch_summary.get("branches") or [])
|
|
843
984
|
if str(item.get("branch_name") or "").strip()
|
|
844
985
|
}
|
|
986
|
+
frontier_payload = (
|
|
987
|
+
dict(optimization_frontier.get("optimization_frontier") or {})
|
|
988
|
+
if isinstance(optimization_frontier, dict)
|
|
989
|
+
and isinstance(optimization_frontier.get("optimization_frontier"), dict)
|
|
990
|
+
else {}
|
|
991
|
+
)
|
|
992
|
+
best_branch_name = str(((frontier_payload.get("best_branch") or {}) if isinstance(frontier_payload.get("best_branch"), dict) else {}).get("branch_name") or "").strip() or None
|
|
993
|
+
stagnant_branch_names = {
|
|
994
|
+
str(item.get("branch_name") or "").strip()
|
|
995
|
+
for item in (frontier_payload.get("stagnant_branches") or [])
|
|
996
|
+
if isinstance(item, dict) and str(item.get("branch_name") or "").strip()
|
|
997
|
+
}
|
|
998
|
+
fusion_candidate_names = {
|
|
999
|
+
str(item.get("branch_name") or "").strip()
|
|
1000
|
+
for item in (frontier_payload.get("fusion_candidates") or [])
|
|
1001
|
+
if isinstance(item, dict) and str(item.get("branch_name") or "").strip()
|
|
1002
|
+
}
|
|
1003
|
+
candidate_count_by_branch: dict[str, int] = {}
|
|
1004
|
+
for item in frontier_payload.get("implementation_candidates") or []:
|
|
1005
|
+
if not isinstance(item, dict):
|
|
1006
|
+
continue
|
|
1007
|
+
branch_name = str(item.get("branch") or "").strip()
|
|
1008
|
+
if not branch_name:
|
|
1009
|
+
continue
|
|
1010
|
+
candidate_count_by_branch[branch_name] = candidate_count_by_branch.get(branch_name, 0) + 1
|
|
845
1011
|
active_campaign = {}
|
|
846
1012
|
if active_analysis_campaign_id:
|
|
847
1013
|
try:
|
|
@@ -856,6 +1022,11 @@ npm --prefix src/ui run build</pre>
|
|
|
856
1022
|
if isinstance(active_campaign, dict)
|
|
857
1023
|
else None
|
|
858
1024
|
)
|
|
1025
|
+
campaign_paper_line_branch = (
|
|
1026
|
+
str(active_campaign.get("paper_line_branch") or "").strip() or None
|
|
1027
|
+
if isinstance(active_campaign, dict)
|
|
1028
|
+
else None
|
|
1029
|
+
)
|
|
859
1030
|
campaign_slices = [
|
|
860
1031
|
dict(item)
|
|
861
1032
|
for item in ((active_campaign or {}).get("slices") or [])
|
|
@@ -903,6 +1074,14 @@ npm --prefix src/ui run build</pre>
|
|
|
903
1074
|
workflow_state["status_reason"] = "Analysis slice pending."
|
|
904
1075
|
return workflow_state
|
|
905
1076
|
if branch_kind == "paper":
|
|
1077
|
+
if campaign_paper_line_branch and ref == campaign_paper_line_branch and next_pending_slice_id is not None:
|
|
1078
|
+
workflow_state["analysis_state"] = "active"
|
|
1079
|
+
workflow_state["writing_state"] = "blocked_by_analysis"
|
|
1080
|
+
workflow_state["status_reason"] = (
|
|
1081
|
+
f"Analysis {campaign_completed_slices}/{campaign_total_slices} done"
|
|
1082
|
+
+ (f" · next: {next_pending_slice_id}" if next_pending_slice_id else "")
|
|
1083
|
+
)
|
|
1084
|
+
return workflow_state
|
|
906
1085
|
if ref == current_workspace_branch and workspace_mode == "paper":
|
|
907
1086
|
workflow_state["writing_state"] = "completed" if active_anchor == "finalize" else "active"
|
|
908
1087
|
workflow_state["status_reason"] = (
|
|
@@ -912,7 +1091,7 @@ npm --prefix src/ui run build</pre>
|
|
|
912
1091
|
workflow_state["writing_state"] = "ready"
|
|
913
1092
|
workflow_state["status_reason"] = "Writing workspace prepared."
|
|
914
1093
|
return workflow_state
|
|
915
|
-
if campaign_parent_branch and ref == campaign_parent_branch:
|
|
1094
|
+
if campaign_parent_branch and not campaign_paper_line_branch and ref == campaign_parent_branch:
|
|
916
1095
|
workflow_state["analysis_state"] = "completed" if next_pending_slice_id is None else "active"
|
|
917
1096
|
if has_main_result:
|
|
918
1097
|
workflow_state["writing_state"] = "ready" if next_pending_slice_id is None else "blocked_by_analysis"
|
|
@@ -955,6 +1134,20 @@ npm --prefix src/ui run build</pre>
|
|
|
955
1134
|
node["latest_main_experiment"] = summary.get("latest_main_experiment")
|
|
956
1135
|
node["experiment_count"] = summary.get("experiment_count")
|
|
957
1136
|
node["has_main_result"] = summary.get("has_main_result")
|
|
1137
|
+
node["optimization_mode"] = frontier_payload.get("mode")
|
|
1138
|
+
node["optimization_best"] = ref == best_branch_name
|
|
1139
|
+
node["optimization_stagnant"] = ref in stagnant_branch_names
|
|
1140
|
+
node["optimization_fusion_candidate"] = ref in fusion_candidate_names
|
|
1141
|
+
node["optimization_candidate_count"] = candidate_count_by_branch.get(ref, 0)
|
|
1142
|
+
return payload
|
|
1143
|
+
|
|
1144
|
+
def git_canvas(self, quest_id: str) -> dict:
|
|
1145
|
+
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
1146
|
+
payload = self.app.quest_service.git_commit_canvas(quest_id)
|
|
1147
|
+
research_state = self.app.quest_service.read_research_state(quest_root)
|
|
1148
|
+
active_workspace_branch = str(research_state.get("current_workspace_branch") or "").strip() or None
|
|
1149
|
+
payload["active_workspace_ref"] = active_workspace_branch
|
|
1150
|
+
payload["workspace_mode"] = str(research_state.get("workspace_mode") or "copilot").strip() or "copilot"
|
|
958
1151
|
return payload
|
|
959
1152
|
|
|
960
1153
|
def git_log(self, quest_id: str, path: str) -> dict:
|
|
@@ -1319,10 +1512,7 @@ npm --prefix src/ui run build</pre>
|
|
|
1319
1512
|
mime_type = mimetypes.guess_type(file_path.name)[0] or "application/octet-stream"
|
|
1320
1513
|
content = quest_service._read_git_bytes(quest_root, revision, relative)
|
|
1321
1514
|
return 200, self._asset_headers(mime_type), content
|
|
1322
|
-
path, _writable, _scope, _source_kind = quest_service.
|
|
1323
|
-
quest_service._quest_root(quest_id),
|
|
1324
|
-
document_id,
|
|
1325
|
-
)
|
|
1515
|
+
path, _writable, _scope, _source_kind = quest_service.resolve_document(quest_id, document_id)
|
|
1326
1516
|
if not path.exists() or not path.is_file():
|
|
1327
1517
|
return 404, {"Content-Type": "text/plain; charset=utf-8"}, b"Not Found"
|
|
1328
1518
|
mime_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
|
|
@@ -9,6 +9,9 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
9
9
|
("GET", re.compile(r"^/metis/agent/api/health$"), "lingzhu_health"),
|
|
10
10
|
("POST", re.compile(r"^/metis/agent/api/sse$"), "lingzhu_sse"),
|
|
11
11
|
("GET", re.compile(r"^/(?P<spa_path>(?!api(?:/|$)|metis(?:/|$)|ui(?:/|$)|assets(?:/|$)).+)$"), "spa_root"),
|
|
12
|
+
("POST", re.compile(r"^/api/auth/login$"), "auth_login"),
|
|
13
|
+
("GET", re.compile(r"^/api/auth/token$"), "auth_token"),
|
|
14
|
+
("POST", re.compile(r"^/api/auth/rotate$"), "auth_rotate"),
|
|
12
15
|
("GET", re.compile(r"^/api/health$"), "health"),
|
|
13
16
|
("GET", re.compile(r"^/api/system/update$"), "system_update"),
|
|
14
17
|
("POST", re.compile(r"^/api/system/update$"), "system_update_action"),
|
|
@@ -61,7 +64,9 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
61
64
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/graph$"), "graph"),
|
|
62
65
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/graph/(?P<kind>svg|png|json)$"), "graph_asset"),
|
|
63
66
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/metrics/timeline$"), "metrics_timeline"),
|
|
67
|
+
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/baselines/compare$"), "baseline_compare"),
|
|
64
68
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/branches$"), "git_branches"),
|
|
69
|
+
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/canvas$"), "git_canvas"),
|
|
65
70
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/log$"), "git_log"),
|
|
66
71
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/compare$"), "git_compare"),
|
|
67
72
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/commit$"), "git_commit"),
|