@researai/deepscientist 1.5.6 → 1.5.8
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 +32 -0
- package/bin/ds.js +274 -18
- package/docs/en/07_MEMORY_AND_MCP.md +40 -3
- package/docs/en/99_ACKNOWLEDGEMENTS.md +1 -0
- package/docs/zh/07_MEMORY_AND_MCP.md +40 -3
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +1 -0
- package/install.sh +34 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +1 -0
- package/src/deepscientist/artifact/metrics.py +813 -80
- package/src/deepscientist/artifact/schemas.py +1 -0
- package/src/deepscientist/artifact/service.py +1101 -99
- package/src/deepscientist/bash_exec/monitor.py +1 -1
- package/src/deepscientist/bash_exec/service.py +17 -9
- package/src/deepscientist/channels/qq.py +17 -0
- package/src/deepscientist/channels/relay.py +16 -0
- package/src/deepscientist/cli.py +1 -1
- package/src/deepscientist/config/models.py +12 -6
- package/src/deepscientist/config/service.py +75 -2
- package/src/deepscientist/connector_profiles.py +34 -6
- package/src/deepscientist/daemon/api/handlers.py +290 -15
- package/src/deepscientist/daemon/api/router.py +2 -0
- package/src/deepscientist/daemon/app.py +521 -23
- package/src/deepscientist/gitops/diff.py +6 -10
- package/src/deepscientist/mcp/server.py +188 -39
- package/src/deepscientist/prompts/builder.py +71 -22
- package/src/deepscientist/qq_profiles.py +19 -9
- package/src/deepscientist/quest/layout.py +1 -0
- package/src/deepscientist/quest/service.py +83 -34
- package/src/deepscientist/quest/stage_views.py +74 -29
- package/src/deepscientist/runners/codex.py +32 -14
- package/src/deepscientist/runners/runtime_overrides.py +46 -0
- package/src/deepscientist/skills/installer.py +7 -0
- package/src/prompts/connectors/qq.md +1 -1
- package/src/prompts/contracts/shared_interaction.md +14 -0
- package/src/prompts/system.md +134 -30
- package/src/skills/analysis-campaign/SKILL.md +34 -8
- package/src/skills/analysis-campaign/references/campaign-checklist-template.md +41 -0
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +68 -0
- package/src/skills/baseline/SKILL.md +145 -32
- package/src/skills/baseline/references/baseline-checklist-template.md +57 -0
- package/src/skills/baseline/references/baseline-plan-template.md +105 -0
- package/src/skills/decision/SKILL.md +12 -8
- package/src/skills/experiment/SKILL.md +51 -9
- package/src/skills/experiment/references/main-experiment-checklist-template.md +52 -0
- package/src/skills/experiment/references/main-experiment-plan-template.md +79 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +3 -8
- package/src/skills/idea/SKILL.md +2 -8
- package/src/skills/intake-audit/SKILL.md +2 -8
- package/src/skills/rebuttal/SKILL.md +2 -8
- package/src/skills/review/SKILL.md +2 -8
- package/src/skills/scout/SKILL.md +2 -8
- package/src/skills/write/SKILL.md +52 -16
- package/src/skills/write/templates/DEEPSCIENTIST_NOTES.md +21 -0
- package/src/skills/write/templates/README.md +408 -0
- package/src/skills/write/templates/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/write/templates/aaai2026/README.md +534 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bib +111 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bst +1493 -0
- package/src/skills/write/templates/aaai2026/aaai2026.sty +315 -0
- package/src/skills/write/templates/acl/README.md +50 -0
- package/src/skills/write/templates/acl/acl.sty +312 -0
- package/src/skills/write/templates/acl/acl_latex.tex +377 -0
- package/src/skills/write/templates/acl/acl_lualatex.tex +101 -0
- package/src/skills/write/templates/acl/acl_natbib.bst +1940 -0
- package/src/skills/write/templates/acl/anthology.bib.txt +26 -0
- package/src/skills/write/templates/acl/custom.bib +70 -0
- package/src/skills/write/templates/acl/formatting.md +326 -0
- package/src/skills/write/templates/asplos2027/main.tex +459 -0
- package/src/skills/write/templates/asplos2027/references.bib +135 -0
- package/src/skills/write/templates/colm2025/README.md +3 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bib +11 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bst +1440 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.sty +218 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.tex +305 -0
- package/src/skills/write/templates/colm2025/fancyhdr.sty +485 -0
- package/src/skills/write/templates/colm2025/math_commands.tex +508 -0
- package/src/skills/write/templates/colm2025/natbib.sty +1246 -0
- package/src/skills/write/templates/iclr2026/fancyhdr.sty +485 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/src/skills/write/templates/iclr2026/math_commands.tex +508 -0
- package/src/skills/write/templates/iclr2026/natbib.sty +1246 -0
- package/src/skills/write/templates/icml2026/algorithm.sty +79 -0
- package/src/skills/write/templates/icml2026/algorithmic.sty +201 -0
- package/src/skills/write/templates/icml2026/example_paper.bib +75 -0
- package/src/skills/write/templates/icml2026/example_paper.tex +662 -0
- package/src/skills/write/templates/icml2026/fancyhdr.sty +864 -0
- package/src/skills/write/templates/icml2026/icml2026.bst +1443 -0
- package/src/skills/write/templates/icml2026/icml2026.sty +767 -0
- package/src/skills/write/templates/neurips2025/Makefile +36 -0
- package/src/skills/write/templates/neurips2025/extra_pkgs.tex +53 -0
- package/src/skills/write/templates/neurips2025/main.tex +38 -0
- package/src/skills/write/templates/neurips2025/neurips.sty +382 -0
- package/src/skills/write/templates/nsdi2027/main.tex +426 -0
- package/src/skills/write/templates/nsdi2027/references.bib +151 -0
- package/src/skills/write/templates/nsdi2027/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/osdi2026/main.tex +429 -0
- package/src/skills/write/templates/osdi2026/references.bib +150 -0
- package/src/skills/write/templates/osdi2026/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/sosp2026/main.tex +532 -0
- package/src/skills/write/templates/sosp2026/references.bib +148 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BGLArZRn.js → AiManusChatView-m2FNtwbn.js} +110 -14
- package/src/ui/dist/assets/{AnalysisPlugin-BgDGSigG.js → AnalysisPlugin-BMTF8EGL.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-B65HD7L4.js → AutoFigurePlugin-DxPdMUNb.js} +5 -5
- package/src/ui/dist/assets/{CliPlugin-CUqgsFHC.js → CliPlugin-BEOWgxCI.js} +9 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-CF5EdvaS.js → CodeEditorPlugin-BCXvjqmb.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-DEeU063D.js → CodeViewerPlugin-DaJcy3nD.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-Df-FuDlZ.js → DocViewerPlugin-ByfeIq4K.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-RAnNaRxM.js → GitDiffViewerPlugin-Cksf3VZ-.js} +830 -86
- package/src/ui/dist/assets/{ImageViewerPlugin-DXJ0ZJGg.js → ImageViewerPlugin-CFz-OsTS.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BlO-sKsj.js → LabCopilotPanel-CJ1cJzoX.js} +10 -10
- package/src/ui/dist/assets/{LabPlugin-BajPZW5v.js → LabPlugin-BF3dVJwa.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-F1OEol8D.js → LatexPlugin-DDkwZ6Sj.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-MhUupqwT.js → MarkdownViewerPlugin-HAuvurcT.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DxhIEsv0.js → MarketplacePlugin-BtoTYy2C.js} +3 -3
- package/src/ui/dist/assets/{index-B-2scqCJ.js → NotebookEditor-CSJYx7b-.js} +12 -155
- package/src/ui/dist/assets/{NotebookEditor-q7TkhewC.js → NotebookEditor-DQgRezm_.js} +1 -1
- package/src/ui/dist/assets/{PdfLoader-B8ZOTKFc.js → PdfLoader-DPa_-fv6.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-xFPvzvWh.js → PdfMarkdownPlugin-BZpXOEjm.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-EjEcsIB8.js → PdfViewerPlugin-BT8a6wGR.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-ixY-1lgW.js → SearchPlugin-D_blveZi.js} +1 -1
- package/src/ui/dist/assets/{Stepper-gYFK2Pgz.js → Stepper-DH2k75Vo.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-Cym6pv_n.js → TextViewerPlugin-Btx0M3hX.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-BPmIHcmK.js → VNCViewer-DImJO4rO.js} +9 -9
- package/src/ui/dist/assets/{bibtex-Btv6Wi7f.js → bibtex-B-Hqu0Sg.js} +1 -1
- package/src/ui/dist/assets/{code-BlG7g85c.js → code-BUfXGJSl.js} +1 -1
- package/src/ui/dist/assets/{file-content-DBT5OfTZ.js → file-content-VqamwI3X.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-BWXYzqHk.js → file-diff-panel-C_wOoS7a.js} +1 -1
- package/src/ui/dist/assets/{file-socket-wDlx6byM.js → file-socket-D2bTuMVP.js} +1 -1
- package/src/ui/dist/assets/{file-utils-Ba3nJmH0.js → file-utils--zJCPN1i.js} +1 -1
- package/src/ui/dist/assets/{image-BwtCyguk.js → image-BZkGJ4mM.js} +1 -1
- package/src/ui/dist/assets/{index-CfRpE209.js → index-CxkvSeKw.js} +2 -2
- package/src/ui/dist/assets/{index-DcqvKzeJ.js → index-D9QIGcmc.js} +1 -1
- package/src/ui/dist/assets/{index-DpMZw8aM.css → index-DXZ1daiJ.css} +163 -34
- package/src/ui/dist/assets/index-DdRW6RMJ.js +159 -0
- package/src/ui/dist/assets/{index-Bz5AaWL7.js → index-DjggJovS.js} +2948 -1565
- package/src/ui/dist/assets/{message-square-BnlyWVH0.js → message-square-FUIPIhU2.js} +1 -1
- package/src/ui/dist/assets/{monaco-CXe0pAVe.js → monaco-DHMc7kKM.js} +1 -1
- package/src/ui/dist/assets/{popover-BCHmVhHj.js → popover-B85oCgCS.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Brk6kaOD.js → project-sync-DOMCcPac.js} +1 -1
- package/src/ui/dist/assets/{sigma-D72eSUep.js → sigma-BO2rQrl3.js} +1 -1
- package/src/ui/dist/assets/{tooltip-BMWd0dqX.js → tooltip-B1OspAkx.js} +1 -1
- package/src/ui/dist/assets/{trash-BIt_eWIS.js → trash-BsVEH_dV.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-N1hkTRrR.js → useCliAccess-b8L6JuZm.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-DPRPv6rv.js → useFileDiffOverlay-BY7uA9hV.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-E5-UheyP.js → wrap-text-BwyVuUIK.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-D4TR-ZZ_.js → zoom-out-RDpLugQP.js} +1 -1
- package/src/ui/dist/index.html +5 -2
- /package/src/ui/dist/assets/{index-CccQYZjX.css → NotebookEditor-CccQYZjX.css} +0 -0
|
@@ -14,7 +14,7 @@ from ... import __version__ as DEEPSCIENTIST_VERSION
|
|
|
14
14
|
from ...gitops import commit_detail, compare_refs, diff_file_between_refs, diff_file_for_commit, export_git_graph, list_branch_canvas, log_ref_history
|
|
15
15
|
from ...memory import MemoryService
|
|
16
16
|
from ...quest import QuestService
|
|
17
|
-
from ...shared import generate_id, read_text, resolve_within, sha256_text, utc_now
|
|
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
20
|
|
|
@@ -171,9 +171,9 @@ npm --prefix src/ui run build</pre>
|
|
|
171
171
|
|
|
172
172
|
def cli_health(self) -> dict:
|
|
173
173
|
online_channels = [
|
|
174
|
-
|
|
175
|
-
for
|
|
176
|
-
if
|
|
174
|
+
snapshot
|
|
175
|
+
for snapshot in self.app.list_connector_statuses()
|
|
176
|
+
if snapshot.get("enabled") is not False
|
|
177
177
|
]
|
|
178
178
|
return {
|
|
179
179
|
"status": "ok",
|
|
@@ -220,6 +220,9 @@ npm --prefix src/ui run build</pre>
|
|
|
220
220
|
def qq_inbound(self, body: dict) -> dict:
|
|
221
221
|
return self.app.handle_qq_inbound(body)
|
|
222
222
|
|
|
223
|
+
def connector_profile_delete(self, connector: str, profile_id: str) -> dict | tuple[int, dict]:
|
|
224
|
+
return self.app.delete_connector_profile(connector, profile_id)
|
|
225
|
+
|
|
223
226
|
def connector_inbound(self, connector: str, body: dict) -> dict:
|
|
224
227
|
return self.app.handle_connector_inbound(connector, body)
|
|
225
228
|
|
|
@@ -245,12 +248,15 @@ npm --prefix src/ui run build</pre>
|
|
|
245
248
|
else []
|
|
246
249
|
)
|
|
247
250
|
force_connector_rebind_raw = body.get("force_connector_rebind")
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
if force_connector_rebind_raw is None:
|
|
252
|
+
force_connector_rebind = True
|
|
253
|
+
else:
|
|
254
|
+
force_connector_rebind = bool(force_connector_rebind_raw) and str(force_connector_rebind_raw).strip().lower() not in {
|
|
255
|
+
"0",
|
|
256
|
+
"false",
|
|
257
|
+
"no",
|
|
258
|
+
"off",
|
|
259
|
+
}
|
|
254
260
|
requested_baseline_ref = body.get("requested_baseline_ref")
|
|
255
261
|
startup_contract = body.get("startup_contract")
|
|
256
262
|
auto_start = body.get("auto_start") is True
|
|
@@ -642,8 +648,6 @@ npm --prefix src/ui run build</pre>
|
|
|
642
648
|
session = self.app.bash_exec_service.get_session(quest_root, session_id)
|
|
643
649
|
except FileNotFoundError:
|
|
644
650
|
return 404, {"ok": False, "message": f"Unknown terminal session `{session_id}`."}
|
|
645
|
-
if str(session.get("kind") or "").lower() != "terminal":
|
|
646
|
-
return 400, {"ok": False, "message": "not_terminal_session"}
|
|
647
651
|
if str(session.get("status") or "").lower() in {"completed", "failed", "terminated"}:
|
|
648
652
|
return 409, {"ok": False, "message": "terminal_session_inactive", "session": session}
|
|
649
653
|
try:
|
|
@@ -744,6 +748,12 @@ npm --prefix src/ui run build</pre>
|
|
|
744
748
|
def git_branches(self, quest_id: str) -> dict:
|
|
745
749
|
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
746
750
|
payload = list_branch_canvas(quest_root, quest_id=quest_id)
|
|
751
|
+
research_state = self.app.quest_service.read_research_state(quest_root)
|
|
752
|
+
active_workspace_branch = str(research_state.get("current_workspace_branch") or "").strip() or None
|
|
753
|
+
research_head_branch = str(research_state.get("research_head_branch") or "").strip() or None
|
|
754
|
+
payload["active_workspace_ref"] = active_workspace_branch
|
|
755
|
+
payload["research_head_ref"] = research_head_branch
|
|
756
|
+
payload["workspace_mode"] = str(research_state.get("workspace_mode") or "quest").strip() or "quest"
|
|
747
757
|
try:
|
|
748
758
|
branch_summary = self.app.artifact_service.list_research_branches(quest_root)
|
|
749
759
|
except Exception:
|
|
@@ -758,6 +768,8 @@ npm --prefix src/ui run build</pre>
|
|
|
758
768
|
if not ref:
|
|
759
769
|
continue
|
|
760
770
|
summary = branch_summary_by_name.get(ref)
|
|
771
|
+
node["active_workspace"] = ref == active_workspace_branch
|
|
772
|
+
node["research_head"] = ref == research_head_branch
|
|
761
773
|
if not isinstance(summary, dict):
|
|
762
774
|
continue
|
|
763
775
|
node["branch_no"] = summary.get("branch_no")
|
|
@@ -815,6 +827,126 @@ npm --prefix src/ui run build</pre>
|
|
|
815
827
|
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
816
828
|
return diff_file_between_refs(quest_root, base=base, head=head, path=file_path)
|
|
817
829
|
|
|
830
|
+
def file_change_diff(self, quest_id: str, path: str) -> dict:
|
|
831
|
+
query = self.parse_query(path)
|
|
832
|
+
run_id = ((query.get("run_id") or [""])[0] or "").strip()
|
|
833
|
+
event_id = ((query.get("event_id") or [""])[0] or "").strip() or None
|
|
834
|
+
raw_path = ((query.get("path") or [""])[0] or "").strip()
|
|
835
|
+
if not run_id or not raw_path:
|
|
836
|
+
return self._file_change_diff_unavailable(
|
|
837
|
+
raw_path=raw_path,
|
|
838
|
+
run_id=run_id or None,
|
|
839
|
+
event_id=event_id,
|
|
840
|
+
message="`run_id` and `path` are required.",
|
|
841
|
+
ok=False,
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
845
|
+
run_artifact = read_json(quest_root / ".ds" / "runs" / run_id / "artifact.json", default={})
|
|
846
|
+
if not isinstance(run_artifact, dict):
|
|
847
|
+
return self._file_change_diff_unavailable(
|
|
848
|
+
raw_path=raw_path,
|
|
849
|
+
run_id=run_id,
|
|
850
|
+
event_id=event_id,
|
|
851
|
+
message="Historical patch unavailable. Run artifact metadata is missing.",
|
|
852
|
+
ok=False,
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
record = run_artifact.get("record") if isinstance(run_artifact.get("record"), dict) else {}
|
|
856
|
+
checkpoint = run_artifact.get("checkpoint") if isinstance(run_artifact.get("checkpoint"), dict) else {}
|
|
857
|
+
base = str(record.get("head_commit") or "").strip()
|
|
858
|
+
head = str(checkpoint.get("head") or "").strip()
|
|
859
|
+
branch = str(record.get("branch") or "").strip() or None
|
|
860
|
+
workspace_root = self._file_change_workspace_root(run_artifact, record)
|
|
861
|
+
relative_path = None
|
|
862
|
+
display_path = None
|
|
863
|
+
|
|
864
|
+
if not base or not head:
|
|
865
|
+
relative_path = self._relative_file_change_path(None, raw_path, workspace_root)
|
|
866
|
+
display_path = self._display_file_change_path(quest_root, raw_path, relative_path=relative_path)
|
|
867
|
+
return self._file_change_diff_unavailable(
|
|
868
|
+
raw_path=raw_path,
|
|
869
|
+
run_id=run_id,
|
|
870
|
+
event_id=event_id,
|
|
871
|
+
base=base,
|
|
872
|
+
head=head,
|
|
873
|
+
branch=branch,
|
|
874
|
+
relative_path=relative_path,
|
|
875
|
+
display_path=display_path,
|
|
876
|
+
message="Historical patch unavailable. Run artifact metadata is missing the recorded commit range.",
|
|
877
|
+
ok=True,
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
repo_root = self._resolve_git_repo_root_for_file_change(raw_path, workspace_root)
|
|
881
|
+
relative_path = self._relative_file_change_path(repo_root, raw_path, workspace_root)
|
|
882
|
+
display_path = self._display_file_change_path(quest_root, raw_path, relative_path=relative_path)
|
|
883
|
+
|
|
884
|
+
if repo_root is None or not relative_path:
|
|
885
|
+
return self._file_change_diff_unavailable(
|
|
886
|
+
raw_path=raw_path,
|
|
887
|
+
run_id=run_id,
|
|
888
|
+
event_id=event_id,
|
|
889
|
+
base=base,
|
|
890
|
+
head=head,
|
|
891
|
+
branch=branch,
|
|
892
|
+
relative_path=relative_path,
|
|
893
|
+
display_path=display_path,
|
|
894
|
+
message="Historical patch unavailable. DeepScientist could not map this file to a git worktree.",
|
|
895
|
+
ok=True,
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
try:
|
|
899
|
+
diff = diff_file_between_refs(repo_root, base=base, head=head, path=relative_path)
|
|
900
|
+
except Exception:
|
|
901
|
+
return self._file_change_diff_unavailable(
|
|
902
|
+
raw_path=raw_path,
|
|
903
|
+
run_id=run_id,
|
|
904
|
+
event_id=event_id,
|
|
905
|
+
base=base,
|
|
906
|
+
head=head,
|
|
907
|
+
branch=branch,
|
|
908
|
+
relative_path=relative_path,
|
|
909
|
+
display_path=display_path,
|
|
910
|
+
message=(
|
|
911
|
+
"Historical patch unavailable. The saved run checkpoint does not match the git repository "
|
|
912
|
+
"that currently owns this file."
|
|
913
|
+
),
|
|
914
|
+
ok=True,
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
if (
|
|
918
|
+
not diff.get("binary")
|
|
919
|
+
and not diff.get("lines")
|
|
920
|
+
and int(diff.get("added") or 0) == 0
|
|
921
|
+
and int(diff.get("removed") or 0) == 0
|
|
922
|
+
):
|
|
923
|
+
return self._file_change_diff_unavailable(
|
|
924
|
+
raw_path=raw_path,
|
|
925
|
+
run_id=run_id,
|
|
926
|
+
event_id=event_id,
|
|
927
|
+
base=base,
|
|
928
|
+
head=head,
|
|
929
|
+
branch=branch,
|
|
930
|
+
relative_path=relative_path,
|
|
931
|
+
display_path=display_path,
|
|
932
|
+
message=(
|
|
933
|
+
"Historical patch unavailable. This event only preserved file-level metadata or the edit "
|
|
934
|
+
"did not land in the run's final checkpoint."
|
|
935
|
+
),
|
|
936
|
+
ok=True,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
return {
|
|
940
|
+
**diff,
|
|
941
|
+
"available": True,
|
|
942
|
+
"source": "run_range",
|
|
943
|
+
"display_path": display_path or relative_path,
|
|
944
|
+
"run_id": run_id,
|
|
945
|
+
"event_id": event_id,
|
|
946
|
+
"branch": branch,
|
|
947
|
+
"message": None,
|
|
948
|
+
}
|
|
949
|
+
|
|
818
950
|
def git_commit_file(self, quest_id: str, path: str) -> dict:
|
|
819
951
|
query = self.parse_query(path)
|
|
820
952
|
sha = ((query.get("sha") or [""])[0] or "").strip()
|
|
@@ -1188,7 +1320,7 @@ npm --prefix src/ui run build</pre>
|
|
|
1188
1320
|
def run_create(self, quest_id: str, body: dict) -> dict:
|
|
1189
1321
|
quest_root = self.app.quest_service._quest_root(quest_id)
|
|
1190
1322
|
config = self.app.config_manager.load_named("config")
|
|
1191
|
-
runners = self.app.config_manager.
|
|
1323
|
+
runners = self.app.config_manager.load_runners_config()
|
|
1192
1324
|
snapshot = self.app.quest_service.snapshot(quest_id)
|
|
1193
1325
|
runner_name = str(body.get("runner") or snapshot.get("runner") or config.get("default_runner", "codex")).strip().lower()
|
|
1194
1326
|
runner_cfg = runners.get(runner_name, {})
|
|
@@ -1204,6 +1336,16 @@ npm --prefix src/ui run build</pre>
|
|
|
1204
1336
|
"ok": False,
|
|
1205
1337
|
"message": str(exc),
|
|
1206
1338
|
}
|
|
1339
|
+
raw_reasoning_effort = (
|
|
1340
|
+
body.get("model_reasoning_effort")
|
|
1341
|
+
if "model_reasoning_effort" in body
|
|
1342
|
+
else runner_cfg.get("model_reasoning_effort")
|
|
1343
|
+
)
|
|
1344
|
+
reasoning_effort = (
|
|
1345
|
+
str(raw_reasoning_effort).strip()
|
|
1346
|
+
if raw_reasoning_effort is not None and str(raw_reasoning_effort).strip()
|
|
1347
|
+
else ("xhigh" if raw_reasoning_effort is None else None)
|
|
1348
|
+
)
|
|
1207
1349
|
request = RunRequest(
|
|
1208
1350
|
quest_id=quest_id,
|
|
1209
1351
|
quest_root=quest_root,
|
|
@@ -1215,8 +1357,7 @@ npm --prefix src/ui run build</pre>
|
|
|
1215
1357
|
approval_policy=runner_cfg.get("approval_policy", "on-request"),
|
|
1216
1358
|
sandbox_mode=runner_cfg.get("sandbox_mode", "workspace-write"),
|
|
1217
1359
|
turn_reason=body.get("turn_reason") or "user_message",
|
|
1218
|
-
reasoning_effort=
|
|
1219
|
-
or runner_cfg.get("model_reasoning_effort", "xhigh"),
|
|
1360
|
+
reasoning_effort=reasoning_effort,
|
|
1220
1361
|
)
|
|
1221
1362
|
result = runner.run(request)
|
|
1222
1363
|
if result.output_text:
|
|
@@ -1352,8 +1493,12 @@ npm --prefix src/ui run build</pre>
|
|
|
1352
1493
|
result = self.app.config_manager.save_named_payload(name, body["structured"])
|
|
1353
1494
|
else:
|
|
1354
1495
|
result = self.app.config_manager.save_named_text(name, body.get("content", ""))
|
|
1496
|
+
if result.get("ok") and name == "config":
|
|
1497
|
+
result["runtime_reload"] = self.app.reload_runtime_config()
|
|
1355
1498
|
if result.get("ok") and name == "connectors":
|
|
1356
1499
|
result["runtime_reload"] = self.app.reload_connectors_config()
|
|
1500
|
+
if result.get("ok") and name == "runners":
|
|
1501
|
+
result["runtime_reload"] = self.app.reload_runners_config()
|
|
1357
1502
|
return result
|
|
1358
1503
|
|
|
1359
1504
|
def config_validate(self, body: dict | None = None) -> dict:
|
|
@@ -1406,6 +1551,136 @@ npm --prefix src/ui run build</pre>
|
|
|
1406
1551
|
return {}
|
|
1407
1552
|
return json.loads(raw.decode("utf-8"))
|
|
1408
1553
|
|
|
1554
|
+
@staticmethod
|
|
1555
|
+
def _file_change_workspace_root(run_artifact: dict, record: dict) -> Path | None:
|
|
1556
|
+
workspace_root = str(run_artifact.get("workspace_root") or record.get("workspace_root") or "").strip()
|
|
1557
|
+
if not workspace_root:
|
|
1558
|
+
return None
|
|
1559
|
+
return Path(workspace_root).expanduser()
|
|
1560
|
+
|
|
1561
|
+
@staticmethod
|
|
1562
|
+
def _file_change_path_candidates(raw_path: str, workspace_root: Path | None) -> list[Path]:
|
|
1563
|
+
text = str(raw_path or "").strip()
|
|
1564
|
+
if not text:
|
|
1565
|
+
return []
|
|
1566
|
+
raw_candidate = Path(text).expanduser()
|
|
1567
|
+
candidates: list[Path] = []
|
|
1568
|
+
if raw_candidate.is_absolute():
|
|
1569
|
+
candidates.append(raw_candidate)
|
|
1570
|
+
elif workspace_root is not None:
|
|
1571
|
+
candidates.append(workspace_root / raw_candidate)
|
|
1572
|
+
else:
|
|
1573
|
+
candidates.append(raw_candidate)
|
|
1574
|
+
if workspace_root is not None:
|
|
1575
|
+
candidates.append(workspace_root)
|
|
1576
|
+
|
|
1577
|
+
resolved: list[Path] = []
|
|
1578
|
+
seen: set[str] = set()
|
|
1579
|
+
for candidate in candidates:
|
|
1580
|
+
for variant in (candidate, candidate.resolve(strict=False)):
|
|
1581
|
+
key = str(variant)
|
|
1582
|
+
if key in seen:
|
|
1583
|
+
continue
|
|
1584
|
+
seen.add(key)
|
|
1585
|
+
resolved.append(variant)
|
|
1586
|
+
return resolved
|
|
1587
|
+
|
|
1588
|
+
@staticmethod
|
|
1589
|
+
def _git_probe_root(candidate: Path) -> Path:
|
|
1590
|
+
if candidate.exists():
|
|
1591
|
+
return candidate if candidate.is_dir() else candidate.parent
|
|
1592
|
+
for parent in candidate.parents:
|
|
1593
|
+
if parent.exists():
|
|
1594
|
+
return parent
|
|
1595
|
+
return candidate.parent
|
|
1596
|
+
|
|
1597
|
+
def _resolve_git_repo_root_for_file_change(self, raw_path: str, workspace_root: Path | None) -> Path | None:
|
|
1598
|
+
for candidate in self._file_change_path_candidates(raw_path, workspace_root):
|
|
1599
|
+
probe_root = self._git_probe_root(candidate)
|
|
1600
|
+
if not str(probe_root).strip():
|
|
1601
|
+
continue
|
|
1602
|
+
result = run_command(["git", "rev-parse", "--show-toplevel"], cwd=probe_root, check=False)
|
|
1603
|
+
if result.returncode != 0:
|
|
1604
|
+
continue
|
|
1605
|
+
top_level = result.stdout.strip()
|
|
1606
|
+
if top_level:
|
|
1607
|
+
return Path(top_level).resolve()
|
|
1608
|
+
return None
|
|
1609
|
+
|
|
1610
|
+
def _relative_file_change_path(self, repo_root: Path | None, raw_path: str, workspace_root: Path | None) -> str | None:
|
|
1611
|
+
if repo_root is None:
|
|
1612
|
+
if Path(str(raw_path or "").strip()).is_absolute():
|
|
1613
|
+
return None
|
|
1614
|
+
normalized = str(raw_path or "").strip().replace("\\", "/").lstrip("/")
|
|
1615
|
+
return normalized or None
|
|
1616
|
+
|
|
1617
|
+
repo_root_resolved = repo_root.resolve()
|
|
1618
|
+
for candidate in self._file_change_path_candidates(raw_path, workspace_root):
|
|
1619
|
+
try:
|
|
1620
|
+
return candidate.relative_to(repo_root_resolved).as_posix()
|
|
1621
|
+
except ValueError:
|
|
1622
|
+
continue
|
|
1623
|
+
if Path(str(raw_path or "").strip()).is_absolute():
|
|
1624
|
+
return None
|
|
1625
|
+
normalized = str(raw_path or "").strip().replace("\\", "/").lstrip("/")
|
|
1626
|
+
return normalized or None
|
|
1627
|
+
|
|
1628
|
+
@staticmethod
|
|
1629
|
+
def _display_file_change_path(quest_root: Path, raw_path: str, *, relative_path: str | None = None) -> str:
|
|
1630
|
+
quest_root_resolved = quest_root.resolve()
|
|
1631
|
+
for candidate in [Path(str(raw_path or "").strip()).expanduser()]:
|
|
1632
|
+
for variant in (candidate, candidate.resolve(strict=False)):
|
|
1633
|
+
try:
|
|
1634
|
+
relative = variant.relative_to(quest_root_resolved)
|
|
1635
|
+
except ValueError:
|
|
1636
|
+
continue
|
|
1637
|
+
parts = relative.parts
|
|
1638
|
+
if len(parts) >= 3 and parts[0] == ".ds" and parts[1] == "worktrees":
|
|
1639
|
+
branch_root = parts[2]
|
|
1640
|
+
remainder = Path(*parts[3:]).as_posix() if len(parts) > 3 else ""
|
|
1641
|
+
return f"{branch_root}/{remainder}" if remainder else branch_root
|
|
1642
|
+
return relative.as_posix()
|
|
1643
|
+
if relative_path:
|
|
1644
|
+
return relative_path
|
|
1645
|
+
text = str(raw_path or "").strip()
|
|
1646
|
+
return Path(text).name or text
|
|
1647
|
+
|
|
1648
|
+
@staticmethod
|
|
1649
|
+
def _file_change_diff_unavailable(
|
|
1650
|
+
*,
|
|
1651
|
+
raw_path: str,
|
|
1652
|
+
run_id: str | None,
|
|
1653
|
+
event_id: str | None,
|
|
1654
|
+
message: str,
|
|
1655
|
+
ok: bool,
|
|
1656
|
+
base: str = "",
|
|
1657
|
+
head: str = "",
|
|
1658
|
+
branch: str | None = None,
|
|
1659
|
+
relative_path: str | None = None,
|
|
1660
|
+
display_path: str | None = None,
|
|
1661
|
+
) -> dict:
|
|
1662
|
+
normalized_path = relative_path or str(raw_path or "").strip()
|
|
1663
|
+
return {
|
|
1664
|
+
"ok": ok,
|
|
1665
|
+
"available": False,
|
|
1666
|
+
"source": "unavailable",
|
|
1667
|
+
"run_id": run_id,
|
|
1668
|
+
"event_id": event_id,
|
|
1669
|
+
"branch": branch,
|
|
1670
|
+
"display_path": display_path or normalized_path,
|
|
1671
|
+
"base": base,
|
|
1672
|
+
"head": head,
|
|
1673
|
+
"path": normalized_path,
|
|
1674
|
+
"old_path": None,
|
|
1675
|
+
"status": "modified",
|
|
1676
|
+
"binary": False,
|
|
1677
|
+
"added": 0,
|
|
1678
|
+
"removed": 0,
|
|
1679
|
+
"lines": [],
|
|
1680
|
+
"truncated": False,
|
|
1681
|
+
"message": message,
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1409
1684
|
@staticmethod
|
|
1410
1685
|
def _markdown_title(path: Path) -> str:
|
|
1411
1686
|
content = read_text(path)
|
|
@@ -17,6 +17,7 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
17
17
|
("GET", re.compile(r"^/api/connectors/availability$"), "connectors_availability"),
|
|
18
18
|
("GET", re.compile(r"^/api/connectors/qq/bindings$"), "qq_bindings"),
|
|
19
19
|
("POST", re.compile(r"^/api/connectors/qq/inbound$"), "qq_inbound"),
|
|
20
|
+
("DELETE", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/profiles/(?P<profile_id>[^/]+)$"), "connector_profile_delete"),
|
|
20
21
|
("GET", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/bindings$"), "connector_bindings"),
|
|
21
22
|
("POST", re.compile(r"^/api/connectors/(?P<connector>[^/]+)/inbound$"), "connector_inbound"),
|
|
22
23
|
("GET", re.compile(r"^/api/baselines$"), "baselines"),
|
|
@@ -59,6 +60,7 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
59
60
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/commit$"), "git_commit"),
|
|
60
61
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/diff-file$"), "git_diff_file"),
|
|
61
62
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/commit-file$"), "git_commit_file"),
|
|
63
|
+
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/operations/file-change-diff$"), "file_change_diff"),
|
|
62
64
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/runs$"), "runs"),
|
|
63
65
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/memory$"), "quest_memory"),
|
|
64
66
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/documents$"), "documents"),
|