@researai/deepscientist 1.5.15 → 1.5.17
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 +385 -104
- package/bin/ds.js +1241 -110
- package/docs/en/00_QUICK_START.md +100 -19
- package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -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 +25 -8
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
- 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/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +24 -2
- package/docs/zh/00_QUICK_START.md +89 -19
- package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +26 -9
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
- 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/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +24 -2
- package/install.sh +46 -4
- package/package.json +2 -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/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/bridges/connectors.py +8 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +367 -22
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +183 -13
- package/src/deepscientist/daemon/api/handlers.py +255 -31
- package/src/deepscientist/daemon/api/router.py +9 -0
- package/src/deepscientist/daemon/app.py +1146 -105
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +130 -0
- package/src/deepscientist/doctor.py +207 -3
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +275 -34
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +707 -55
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +143 -43
- package/src/deepscientist/shared.py +19 -0
- 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 +14 -2
- package/src/prompts/system.md +23 -5
- package/src/prompts/system_copilot.md +56 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- 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 +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -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-Bv-Z8YpU.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
- package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
- package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
- package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -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-DB9N_T9q.js +361 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
- package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
- package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
- package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
- package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
- package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
- package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
- package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
- package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
- package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
- package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.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-CLc0pPP8.js +1 -0
- package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
- package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
- package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
- package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
- package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
- package/src/ui/dist/assets/zoom-out-E_gaeAxL.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
|
@@ -17,6 +17,11 @@ 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(
|
|
@@ -411,7 +523,10 @@ npm --prefix src/ui run build</pre>
|
|
|
411
523
|
}
|
|
412
524
|
|
|
413
525
|
def quest(self, quest_id: str) -> dict:
|
|
414
|
-
|
|
526
|
+
try:
|
|
527
|
+
return self.app.quest_service.snapshot(quest_id)
|
|
528
|
+
except FileNotFoundError:
|
|
529
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
415
530
|
|
|
416
531
|
def quest_delete(self, quest_id: str, body: dict | None = None) -> dict | tuple[int, dict]:
|
|
417
532
|
source = "web"
|
|
@@ -424,6 +539,7 @@ npm --prefix src/ui run build</pre>
|
|
|
424
539
|
"title": body.get("title") if "title" in body else None,
|
|
425
540
|
"active_anchor": body.get("active_anchor") if "active_anchor" in body else None,
|
|
426
541
|
"default_runner": body.get("default_runner") if "default_runner" in body else None,
|
|
542
|
+
"workspace_mode": body.get("workspace_mode") if "workspace_mode" in body else None,
|
|
427
543
|
}
|
|
428
544
|
if all(value is None for value in updates.values()):
|
|
429
545
|
return {
|
|
@@ -473,12 +589,16 @@ npm --prefix src/ui run build</pre>
|
|
|
473
589
|
}
|
|
474
590
|
|
|
475
591
|
def quest_session(self, quest_id: str) -> dict:
|
|
476
|
-
|
|
477
|
-
|
|
592
|
+
try:
|
|
593
|
+
snapshot = self.app.quest_service.snapshot_fast(quest_id)
|
|
594
|
+
except FileNotFoundError:
|
|
595
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
596
|
+
for kind in ("details", "canvas", "git_canvas"):
|
|
478
597
|
try:
|
|
479
598
|
self.app.quest_service.prime_projection(quest_id, kind)
|
|
480
599
|
except Exception:
|
|
481
600
|
continue
|
|
601
|
+
self.app.schedule_latest_quest_terminal_prewarm(quest_id)
|
|
482
602
|
return {
|
|
483
603
|
"ok": True,
|
|
484
604
|
"quest_id": quest_id,
|
|
@@ -496,7 +616,7 @@ npm --prefix src/ui run build</pre>
|
|
|
496
616
|
tail = tail_raw in {"1", "true", "yes", "on"}
|
|
497
617
|
format_name = ((query.get("format") or ["both"])[0] or "both").lower()
|
|
498
618
|
session_id = ((query.get("session_id") or [f"quest:{quest_id}"])[0] or f"quest:{quest_id}")
|
|
499
|
-
payload = self.
|
|
619
|
+
payload = self.app.quest_service.events(
|
|
500
620
|
quest_id,
|
|
501
621
|
after=after,
|
|
502
622
|
before=before,
|
|
@@ -1028,6 +1148,15 @@ npm --prefix src/ui run build</pre>
|
|
|
1028
1148
|
node["optimization_candidate_count"] = candidate_count_by_branch.get(ref, 0)
|
|
1029
1149
|
return payload
|
|
1030
1150
|
|
|
1151
|
+
def git_canvas(self, quest_id: str) -> dict:
|
|
1152
|
+
quest_root = self._fresh_quest_service()._quest_root(quest_id)
|
|
1153
|
+
payload = self.app.quest_service.git_commit_canvas(quest_id)
|
|
1154
|
+
research_state = self.app.quest_service.read_research_state(quest_root)
|
|
1155
|
+
active_workspace_branch = str(research_state.get("current_workspace_branch") or "").strip() or None
|
|
1156
|
+
payload["active_workspace_ref"] = active_workspace_branch
|
|
1157
|
+
payload["workspace_mode"] = str(research_state.get("workspace_mode") or "copilot").strip() or "copilot"
|
|
1158
|
+
return payload
|
|
1159
|
+
|
|
1031
1160
|
def git_log(self, quest_id: str, path: str) -> dict:
|
|
1032
1161
|
query = self.parse_query(path)
|
|
1033
1162
|
ref = ((query.get("ref") or [""])[0] or "").strip()
|
|
@@ -1352,19 +1481,25 @@ npm --prefix src/ui run build</pre>
|
|
|
1352
1481
|
)
|
|
1353
1482
|
|
|
1354
1483
|
def documents(self, quest_id: str) -> list[dict]:
|
|
1355
|
-
|
|
1484
|
+
try:
|
|
1485
|
+
return self.app.quest_service.list_documents(quest_id)
|
|
1486
|
+
except FileNotFoundError:
|
|
1487
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
1356
1488
|
|
|
1357
1489
|
def explorer(self, quest_id: str, path: str) -> dict:
|
|
1358
1490
|
query = self.parse_query(path)
|
|
1359
1491
|
revision = ((query.get("revision") or [""])[0] or "").strip() or None
|
|
1360
1492
|
mode = ((query.get("mode") or [""])[0] or "").strip() or None
|
|
1361
1493
|
profile = ((query.get("profile") or [""])[0] or "").strip() or None
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1494
|
+
try:
|
|
1495
|
+
return self.app.quest_service.explorer(
|
|
1496
|
+
quest_id,
|
|
1497
|
+
revision=revision,
|
|
1498
|
+
mode=mode,
|
|
1499
|
+
profile=profile,
|
|
1500
|
+
)
|
|
1501
|
+
except FileNotFoundError:
|
|
1502
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
1368
1503
|
|
|
1369
1504
|
def quest_search(self, quest_id: str, path: str) -> dict:
|
|
1370
1505
|
query = self.parse_query(path)
|
|
@@ -1373,7 +1508,90 @@ npm --prefix src/ui run build</pre>
|
|
|
1373
1508
|
limit = int(((query.get("limit") or ["50"])[0] or "50").strip())
|
|
1374
1509
|
except ValueError:
|
|
1375
1510
|
limit = 50
|
|
1376
|
-
|
|
1511
|
+
try:
|
|
1512
|
+
return self.app.quest_service.search_files(quest_id, term=term, limit=limit)
|
|
1513
|
+
except FileNotFoundError:
|
|
1514
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
1515
|
+
|
|
1516
|
+
def quest_file_create_folder(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
1517
|
+
try:
|
|
1518
|
+
return self._fresh_quest_service().create_workspace_folder(
|
|
1519
|
+
quest_id,
|
|
1520
|
+
name=body.get("name"),
|
|
1521
|
+
parent_path=body.get("parent_path"),
|
|
1522
|
+
)
|
|
1523
|
+
except FileNotFoundError as exc:
|
|
1524
|
+
return 404, {"ok": False, "message": str(exc)}
|
|
1525
|
+
except FileExistsError as exc:
|
|
1526
|
+
return 409, {"ok": False, "message": str(exc)}
|
|
1527
|
+
except ValueError as exc:
|
|
1528
|
+
return 400, {"ok": False, "message": str(exc)}
|
|
1529
|
+
|
|
1530
|
+
def quest_file_upload(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
1531
|
+
file_name = str(body.get("file_name") or "").strip()
|
|
1532
|
+
content_base64 = str(body.get("content_base64") or "").strip()
|
|
1533
|
+
mime_type = str(body.get("mime_type") or "").strip() or None
|
|
1534
|
+
if not file_name:
|
|
1535
|
+
return 400, {"ok": False, "message": "`file_name` is required."}
|
|
1536
|
+
if not content_base64:
|
|
1537
|
+
return 400, {"ok": False, "message": "`content_base64` is required."}
|
|
1538
|
+
try:
|
|
1539
|
+
content = base64.b64decode(content_base64, validate=True)
|
|
1540
|
+
except (ValueError, TypeError):
|
|
1541
|
+
return 400, {"ok": False, "message": "Invalid `content_base64` payload."}
|
|
1542
|
+
try:
|
|
1543
|
+
return self._fresh_quest_service().upload_workspace_file(
|
|
1544
|
+
quest_id,
|
|
1545
|
+
file_name=file_name,
|
|
1546
|
+
content=content,
|
|
1547
|
+
mime_type=mime_type,
|
|
1548
|
+
parent_path=body.get("parent_path"),
|
|
1549
|
+
)
|
|
1550
|
+
except FileNotFoundError as exc:
|
|
1551
|
+
return 404, {"ok": False, "message": str(exc)}
|
|
1552
|
+
except FileExistsError as exc:
|
|
1553
|
+
return 409, {"ok": False, "message": str(exc)}
|
|
1554
|
+
except ValueError as exc:
|
|
1555
|
+
return 400, {"ok": False, "message": str(exc)}
|
|
1556
|
+
|
|
1557
|
+
def quest_file_rename(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
1558
|
+
try:
|
|
1559
|
+
return self._fresh_quest_service().rename_workspace_entry(
|
|
1560
|
+
quest_id,
|
|
1561
|
+
path=body.get("path"),
|
|
1562
|
+
new_name=body.get("new_name"),
|
|
1563
|
+
)
|
|
1564
|
+
except FileNotFoundError as exc:
|
|
1565
|
+
return 404, {"ok": False, "message": str(exc)}
|
|
1566
|
+
except FileExistsError as exc:
|
|
1567
|
+
return 409, {"ok": False, "message": str(exc)}
|
|
1568
|
+
except ValueError as exc:
|
|
1569
|
+
return 400, {"ok": False, "message": str(exc)}
|
|
1570
|
+
|
|
1571
|
+
def quest_file_move(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
1572
|
+
try:
|
|
1573
|
+
return self._fresh_quest_service().move_workspace_entries(
|
|
1574
|
+
quest_id,
|
|
1575
|
+
paths=body.get("paths"),
|
|
1576
|
+
target_parent_path=body.get("target_parent_path"),
|
|
1577
|
+
)
|
|
1578
|
+
except FileNotFoundError as exc:
|
|
1579
|
+
return 404, {"ok": False, "message": str(exc)}
|
|
1580
|
+
except FileExistsError as exc:
|
|
1581
|
+
return 409, {"ok": False, "message": str(exc)}
|
|
1582
|
+
except ValueError as exc:
|
|
1583
|
+
return 400, {"ok": False, "message": str(exc)}
|
|
1584
|
+
|
|
1585
|
+
def quest_file_delete(self, quest_id: str, body: dict) -> dict | tuple[int, dict]:
|
|
1586
|
+
try:
|
|
1587
|
+
return self._fresh_quest_service().delete_workspace_entries(
|
|
1588
|
+
quest_id,
|
|
1589
|
+
paths=body.get("paths"),
|
|
1590
|
+
)
|
|
1591
|
+
except FileNotFoundError as exc:
|
|
1592
|
+
return 404, {"ok": False, "message": str(exc)}
|
|
1593
|
+
except ValueError as exc:
|
|
1594
|
+
return 400, {"ok": False, "message": str(exc)}
|
|
1377
1595
|
|
|
1378
1596
|
def document_asset(self, quest_id: str, path: str) -> tuple[int, dict, bytes]:
|
|
1379
1597
|
quest_service = self._fresh_quest_service()
|
|
@@ -1390,17 +1608,17 @@ npm --prefix src/ui run build</pre>
|
|
|
1390
1608
|
mime_type = mimetypes.guess_type(file_path.name)[0] or "application/octet-stream"
|
|
1391
1609
|
content = quest_service._read_git_bytes(quest_root, revision, relative)
|
|
1392
1610
|
return 200, self._asset_headers(mime_type), content
|
|
1393
|
-
path, _writable, _scope, _source_kind = quest_service.
|
|
1394
|
-
quest_service._quest_root(quest_id),
|
|
1395
|
-
document_id,
|
|
1396
|
-
)
|
|
1611
|
+
path, _writable, _scope, _source_kind = quest_service.resolve_document(quest_id, document_id)
|
|
1397
1612
|
if not path.exists() or not path.is_file():
|
|
1398
1613
|
return 404, {"Content-Type": "text/plain; charset=utf-8"}, b"Not Found"
|
|
1399
1614
|
mime_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
|
|
1400
1615
|
return 200, self._asset_headers(mime_type), path.read_bytes()
|
|
1401
1616
|
|
|
1402
1617
|
def document_open(self, quest_id: str, body: dict) -> dict:
|
|
1403
|
-
|
|
1618
|
+
try:
|
|
1619
|
+
return self._fresh_quest_service().open_document(quest_id, body["document_id"])
|
|
1620
|
+
except FileNotFoundError:
|
|
1621
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
1404
1622
|
|
|
1405
1623
|
def document_asset_upload(self, quest_id: str, body: dict) -> dict:
|
|
1406
1624
|
document_id = str(body.get("document_id") or "").strip()
|
|
@@ -1418,22 +1636,28 @@ npm --prefix src/ui run build</pre>
|
|
|
1418
1636
|
content = base64.b64decode(content_base64, validate=True)
|
|
1419
1637
|
except (ValueError, TypeError):
|
|
1420
1638
|
return {"ok": False, "message": "Invalid `content_base64` payload."}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1639
|
+
try:
|
|
1640
|
+
return self.app.quest_service.save_document_asset(
|
|
1641
|
+
quest_id,
|
|
1642
|
+
document_id,
|
|
1643
|
+
file_name=file_name,
|
|
1644
|
+
mime_type=mime_type or None,
|
|
1645
|
+
content=content,
|
|
1646
|
+
kind=kind,
|
|
1647
|
+
)
|
|
1648
|
+
except FileNotFoundError:
|
|
1649
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
1429
1650
|
|
|
1430
1651
|
def document_save(self, quest_id: str, document_id: str, body: dict) -> dict:
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1652
|
+
try:
|
|
1653
|
+
return self.app.quest_service.save_document(
|
|
1654
|
+
quest_id,
|
|
1655
|
+
document_id,
|
|
1656
|
+
body["content"],
|
|
1657
|
+
previous_revision=body.get("revision"),
|
|
1658
|
+
)
|
|
1659
|
+
except FileNotFoundError:
|
|
1660
|
+
return 404, {"ok": False, "message": f"Unknown quest `{quest_id}`."}
|
|
1437
1661
|
|
|
1438
1662
|
def latex_init(self, project_id: str, body: dict) -> dict:
|
|
1439
1663
|
return self.app.latex_service.init_project(
|
|
@@ -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"),
|
|
@@ -63,6 +66,7 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
63
66
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/metrics/timeline$"), "metrics_timeline"),
|
|
64
67
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/baselines/compare$"), "baseline_compare"),
|
|
65
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"),
|
|
66
70
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/log$"), "git_log"),
|
|
67
71
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/compare$"), "git_compare"),
|
|
68
72
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/git/commit$"), "git_commit"),
|
|
@@ -74,6 +78,11 @@ ROUTES: list[tuple[str, re.Pattern[str], str]] = [
|
|
|
74
78
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/documents$"), "documents"),
|
|
75
79
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/explorer$"), "explorer"),
|
|
76
80
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/search$"), "quest_search"),
|
|
81
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/files/folder$"), "quest_file_create_folder"),
|
|
82
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/files/upload$"), "quest_file_upload"),
|
|
83
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/files/rename$"), "quest_file_rename"),
|
|
84
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/files/move$"), "quest_file_move"),
|
|
85
|
+
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/files/delete$"), "quest_file_delete"),
|
|
77
86
|
("GET", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/documents/asset$"), "document_asset"),
|
|
78
87
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/documents/open$"), "document_open"),
|
|
79
88
|
("POST", re.compile(r"^/api/quests/(?P<quest_id>[^/]+)/documents/assets$"), "document_asset_upload"),
|