@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
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
from mcp.types import ToolAnnotations
|
|
9
10
|
|
|
10
11
|
from ..artifact import ArtifactService
|
|
11
12
|
from ..artifact.metrics import MetricContractValidationError
|
|
@@ -54,6 +55,16 @@ ARTIFACT_STATE_CHANGE_WATCHDOG_NOTES = {
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
def _read_only_tool_annotations(*, title: str | None = None) -> ToolAnnotations:
|
|
59
|
+
return ToolAnnotations(
|
|
60
|
+
title=title,
|
|
61
|
+
readOnlyHint=True,
|
|
62
|
+
destructiveHint=False,
|
|
63
|
+
idempotentHint=True,
|
|
64
|
+
openWorldHint=False,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
57
68
|
def _metric_validation_error_payload(exc: MetricContractValidationError) -> dict[str, Any]:
|
|
58
69
|
return exc.as_payload()
|
|
59
70
|
|
|
@@ -62,7 +73,7 @@ def _progress_watchdog_note(tool_call_count: int) -> str:
|
|
|
62
73
|
return (
|
|
63
74
|
"By the way, you have gone "
|
|
64
75
|
f"{tool_call_count} tool calls without notifying the user via artifact.interact(...). "
|
|
65
|
-
"
|
|
76
|
+
"Inspect whether the user-visible state actually changed; only send a progress update if there is a real new checkpoint, blocker, or route change."
|
|
66
77
|
)
|
|
67
78
|
|
|
68
79
|
|
|
@@ -71,7 +82,7 @@ def _visibility_watchdog_note(seconds_since_last_update: int) -> str:
|
|
|
71
82
|
return (
|
|
72
83
|
"By the way, it has been "
|
|
73
84
|
f"{minutes} minutes since the last user-visible artifact.interact(...). "
|
|
74
|
-
"
|
|
85
|
+
"Inspect the current run or task state now. Only send a new user-visible update if the frontier materially changed or the user explicitly needs a fresh checkpoint."
|
|
75
86
|
)
|
|
76
87
|
|
|
77
88
|
|
|
@@ -114,11 +125,16 @@ def _attach_interaction_watchdog(
|
|
|
114
125
|
state_change_note: str | None = None,
|
|
115
126
|
) -> dict[str, Any]:
|
|
116
127
|
enriched = dict(payload)
|
|
117
|
-
|
|
128
|
+
interaction_watchdog = dict(watchdog or {})
|
|
118
129
|
notes = _collect_interaction_watchdog_notes(
|
|
119
130
|
watchdog,
|
|
120
131
|
state_change_note=state_change_note,
|
|
121
132
|
)
|
|
133
|
+
interaction_watchdog["user_update_due"] = bool(
|
|
134
|
+
interaction_watchdog.get("user_update_due")
|
|
135
|
+
or any(str(item.get("kind") or "") == "state_change" for item in notes)
|
|
136
|
+
)
|
|
137
|
+
enriched["interaction_watchdog"] = interaction_watchdog
|
|
122
138
|
if not notes:
|
|
123
139
|
return enriched
|
|
124
140
|
enriched["watchdog_notes"] = notes
|
|
@@ -377,6 +393,7 @@ def build_memory_server(context: McpContext) -> FastMCP:
|
|
|
377
393
|
"Read a memory card by id or path. "
|
|
378
394
|
"Use after list_recent or search surfaced a specific card worth reusing now."
|
|
379
395
|
),
|
|
396
|
+
annotations=_read_only_tool_annotations(title="Read memory card"),
|
|
380
397
|
)
|
|
381
398
|
def read(
|
|
382
399
|
card_id: str | None = None,
|
|
@@ -394,6 +411,7 @@ def build_memory_server(context: McpContext) -> FastMCP:
|
|
|
394
411
|
"Search memory cards by metadata or body text. "
|
|
395
412
|
"Use before broad literature search, retries, route decisions, or repeated debugging."
|
|
396
413
|
),
|
|
414
|
+
annotations=_read_only_tool_annotations(title="Search memory cards"),
|
|
397
415
|
)
|
|
398
416
|
def search(
|
|
399
417
|
query: str,
|
|
@@ -413,6 +431,7 @@ def build_memory_server(context: McpContext) -> FastMCP:
|
|
|
413
431
|
"List the most recently updated memory cards. "
|
|
414
432
|
"Use to recover quest context at turn start, after resume, or after a long pause."
|
|
415
433
|
),
|
|
434
|
+
annotations=_read_only_tool_annotations(title="List recent memory cards"),
|
|
416
435
|
)
|
|
417
436
|
def list_recent(
|
|
418
437
|
scope: str = "quest",
|
|
@@ -507,6 +526,45 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
507
526
|
allow_empty=allow_empty,
|
|
508
527
|
)
|
|
509
528
|
|
|
529
|
+
@server.tool(
|
|
530
|
+
name="git",
|
|
531
|
+
description=(
|
|
532
|
+
"Run git-oriented workspace operations for Copilot mode and general quest maintenance. "
|
|
533
|
+
"Use action=status|commit|branch|checkout|log|show|diff|graph."
|
|
534
|
+
),
|
|
535
|
+
)
|
|
536
|
+
def git(
|
|
537
|
+
action: str,
|
|
538
|
+
message: str | None = None,
|
|
539
|
+
ref: str | None = None,
|
|
540
|
+
base: str | None = None,
|
|
541
|
+
head: str | None = None,
|
|
542
|
+
sha: str | None = None,
|
|
543
|
+
path: str | None = None,
|
|
544
|
+
branch: str | None = None,
|
|
545
|
+
create_from: str | None = None,
|
|
546
|
+
limit: int = 30,
|
|
547
|
+
allow_empty: bool = False,
|
|
548
|
+
checkout_new_branch: bool = False,
|
|
549
|
+
comment: str | dict[str, Any] | None = None,
|
|
550
|
+
) -> dict[str, Any]:
|
|
551
|
+
return service.git_action(
|
|
552
|
+
context.require_quest_root(),
|
|
553
|
+
action=action,
|
|
554
|
+
workspace_root=context.worktree_root,
|
|
555
|
+
message=message,
|
|
556
|
+
ref=ref,
|
|
557
|
+
base=base,
|
|
558
|
+
head=head,
|
|
559
|
+
sha=sha,
|
|
560
|
+
path=path,
|
|
561
|
+
branch=branch,
|
|
562
|
+
create_from=create_from,
|
|
563
|
+
limit=limit,
|
|
564
|
+
allow_empty=allow_empty,
|
|
565
|
+
checkout_new_branch=checkout_new_branch,
|
|
566
|
+
)
|
|
567
|
+
|
|
510
568
|
@server.tool(name="prepare_branch", description="Prepare an idea or run branch and optional worktree.")
|
|
511
569
|
def prepare_branch(
|
|
512
570
|
run_id: str | None = None,
|
|
@@ -557,19 +615,26 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
557
615
|
name="submit_idea",
|
|
558
616
|
description=(
|
|
559
617
|
"Create or revise the active research idea. "
|
|
560
|
-
"Normal research flow should use mode=create together with lineage_intent=continue_line or branch_alternative, so each durable idea submission becomes a new branch/worktree and a new user-visible research node. "
|
|
618
|
+
"Normal research flow should use mode=create together with submission_mode='line' and lineage_intent=continue_line or branch_alternative, so each durable idea submission becomes a new branch/worktree and a new user-visible research node. "
|
|
619
|
+
"submission_mode='candidate' records a candidate idea brief without opening a new branch yet. "
|
|
561
620
|
"mode=revise is maintenance-only for refining the current active idea.md in place. "
|
|
562
621
|
"When foundation_ref is omitted, lineage_intent infers the parent and default foundation from the active research line."
|
|
563
622
|
),
|
|
564
623
|
)
|
|
565
624
|
def submit_idea(
|
|
566
625
|
mode: str = "create",
|
|
626
|
+
submission_mode: str = "line",
|
|
567
627
|
idea_id: str | None = None,
|
|
568
628
|
lineage_intent: str | None = None,
|
|
569
629
|
title: str = "",
|
|
570
630
|
problem: str = "",
|
|
571
631
|
hypothesis: str = "",
|
|
572
632
|
mechanism: str = "",
|
|
633
|
+
method_brief: str = "",
|
|
634
|
+
selection_scores: dict[str, Any] | None = None,
|
|
635
|
+
mechanism_family: str = "",
|
|
636
|
+
change_layer: str = "",
|
|
637
|
+
source_lens: str = "",
|
|
573
638
|
expected_gain: str = "",
|
|
574
639
|
evidence_paths: list[str] | None = None,
|
|
575
640
|
risks: list[str] | None = None,
|
|
@@ -578,17 +643,24 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
578
643
|
foundation_reason: str = "",
|
|
579
644
|
next_target: str = "experiment",
|
|
580
645
|
draft_markdown: str = "",
|
|
646
|
+
source_candidate_id: str | None = None,
|
|
581
647
|
comment: str | dict[str, Any] | None = None,
|
|
582
648
|
) -> dict[str, Any]:
|
|
583
649
|
return service.submit_idea(
|
|
584
650
|
context.require_quest_root(),
|
|
585
651
|
mode=mode,
|
|
652
|
+
submission_mode=submission_mode,
|
|
586
653
|
idea_id=idea_id,
|
|
587
654
|
lineage_intent=lineage_intent,
|
|
588
655
|
title=title,
|
|
589
656
|
problem=problem,
|
|
590
657
|
hypothesis=hypothesis,
|
|
591
658
|
mechanism=mechanism,
|
|
659
|
+
method_brief=method_brief,
|
|
660
|
+
selection_scores=selection_scores,
|
|
661
|
+
mechanism_family=mechanism_family,
|
|
662
|
+
change_layer=change_layer,
|
|
663
|
+
source_lens=source_lens,
|
|
592
664
|
expected_gain=expected_gain,
|
|
593
665
|
evidence_paths=evidence_paths,
|
|
594
666
|
risks=risks,
|
|
@@ -597,6 +669,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
597
669
|
foundation_reason=foundation_reason,
|
|
598
670
|
next_target=next_target,
|
|
599
671
|
draft_markdown=draft_markdown,
|
|
672
|
+
source_candidate_id=source_candidate_id,
|
|
600
673
|
)
|
|
601
674
|
|
|
602
675
|
@server.tool(
|
|
@@ -605,6 +678,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
605
678
|
"List research branches with branch number, active idea, foundation info, and corresponding main-experiment results. "
|
|
606
679
|
"Use before creating the next idea when you need to compare possible foundations."
|
|
607
680
|
),
|
|
681
|
+
annotations=_read_only_tool_annotations(title="List research branches"),
|
|
608
682
|
)
|
|
609
683
|
def list_research_branches(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
610
684
|
return service.list_research_branches(context.require_quest_root())
|
|
@@ -615,16 +689,135 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
615
689
|
"Resolve the current canonical research ids and refs. "
|
|
616
690
|
"Use this before supplementary work when you need the active idea, latest main run, active campaign, outline, or reply-thread ids without guessing."
|
|
617
691
|
),
|
|
692
|
+
annotations=_read_only_tool_annotations(title="Resolve runtime refs"),
|
|
618
693
|
)
|
|
619
694
|
def resolve_runtime_refs(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
620
695
|
return service.resolve_runtime_refs(context.require_quest_root())
|
|
621
696
|
|
|
697
|
+
@server.tool(
|
|
698
|
+
name="get_paper_contract_health",
|
|
699
|
+
description=(
|
|
700
|
+
"Inspect whether the active paper line is actually unblocked for writing or finalize work. "
|
|
701
|
+
"Use detail='summary' for a compact decision surface or detail='full' for exact blocking items."
|
|
702
|
+
),
|
|
703
|
+
annotations=_read_only_tool_annotations(title="Get paper contract health"),
|
|
704
|
+
)
|
|
705
|
+
def get_paper_contract_health(
|
|
706
|
+
detail: str = "summary",
|
|
707
|
+
comment: str | dict[str, Any] | None = None,
|
|
708
|
+
) -> dict[str, Any]:
|
|
709
|
+
return service.get_paper_contract_health(
|
|
710
|
+
context.require_quest_root(),
|
|
711
|
+
detail=detail,
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
@server.tool(
|
|
715
|
+
name="get_quest_state",
|
|
716
|
+
description=(
|
|
717
|
+
"Read the current quest runtime state without mutating anything. "
|
|
718
|
+
"Use detail='summary' for a compact operational view or detail='full' for recent artifacts, runs, and active interactions."
|
|
719
|
+
),
|
|
720
|
+
annotations=_read_only_tool_annotations(title="Get quest state"),
|
|
721
|
+
)
|
|
722
|
+
def get_quest_state(
|
|
723
|
+
detail: str = "summary",
|
|
724
|
+
comment: str | dict[str, Any] | None = None,
|
|
725
|
+
) -> dict[str, Any]:
|
|
726
|
+
return service.get_quest_state(
|
|
727
|
+
context.require_quest_root(),
|
|
728
|
+
detail=detail,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
@server.tool(
|
|
732
|
+
name="get_global_status",
|
|
733
|
+
description=(
|
|
734
|
+
"Read a concise quest-global status summary for direct user questions such as overall progress, paper readiness, or the latest measured result. "
|
|
735
|
+
"Use detail='brief' for a compact answer surface or detail='full' for more structured context."
|
|
736
|
+
),
|
|
737
|
+
annotations=_read_only_tool_annotations(title="Get global status"),
|
|
738
|
+
)
|
|
739
|
+
def get_global_status(
|
|
740
|
+
detail: str = "brief",
|
|
741
|
+
locale: str = "zh",
|
|
742
|
+
comment: str | dict[str, Any] | None = None,
|
|
743
|
+
) -> dict[str, Any]:
|
|
744
|
+
return service.get_global_status(
|
|
745
|
+
context.require_quest_root(),
|
|
746
|
+
detail=detail,
|
|
747
|
+
locale=locale,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
@server.tool(
|
|
751
|
+
name="get_method_scoreboard",
|
|
752
|
+
description=(
|
|
753
|
+
"Read or refresh the quest-level method scoreboard so overall experiment history and the current incumbent line are explicit."
|
|
754
|
+
),
|
|
755
|
+
)
|
|
756
|
+
def get_method_scoreboard(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
757
|
+
return service.refresh_method_scoreboard(context.require_quest_root())
|
|
758
|
+
|
|
759
|
+
@server.tool(
|
|
760
|
+
name="get_optimization_frontier",
|
|
761
|
+
description=(
|
|
762
|
+
"Read a compact optimization-frontier summary for algorithm-first quests. "
|
|
763
|
+
"It summarizes candidate briefs, promoted lines, recent implementation candidates, stagnant branches, fusion opportunities, and the recommended next mode."
|
|
764
|
+
),
|
|
765
|
+
annotations=_read_only_tool_annotations(title="Get optimization frontier"),
|
|
766
|
+
)
|
|
767
|
+
def get_optimization_frontier(
|
|
768
|
+
comment: str | dict[str, Any] | None = None,
|
|
769
|
+
) -> dict[str, Any]:
|
|
770
|
+
return service.get_optimization_frontier(
|
|
771
|
+
context.require_quest_root(),
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
@server.tool(
|
|
775
|
+
name="read_quest_documents",
|
|
776
|
+
description=(
|
|
777
|
+
"Read durable quest documents such as brief, plan, status, summary, and active user requirements. "
|
|
778
|
+
"Use mode='excerpt' for compact recovery or mode='full' when exact document wording matters."
|
|
779
|
+
),
|
|
780
|
+
annotations=_read_only_tool_annotations(title="Read quest documents"),
|
|
781
|
+
)
|
|
782
|
+
def read_quest_documents(
|
|
783
|
+
names: list[str] | None = None,
|
|
784
|
+
mode: str = "excerpt",
|
|
785
|
+
max_lines: int = 12,
|
|
786
|
+
comment: str | dict[str, Any] | None = None,
|
|
787
|
+
) -> dict[str, Any]:
|
|
788
|
+
return service.read_quest_documents(
|
|
789
|
+
context.require_quest_root(),
|
|
790
|
+
names=names,
|
|
791
|
+
mode=mode,
|
|
792
|
+
max_lines=max_lines,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
@server.tool(
|
|
796
|
+
name="get_conversation_context",
|
|
797
|
+
description=(
|
|
798
|
+
"Read a recent window of quest conversation history. "
|
|
799
|
+
"Use this when earlier user/assistant continuity matters and the current prompt intentionally keeps only a compact turn launcher."
|
|
800
|
+
),
|
|
801
|
+
annotations=_read_only_tool_annotations(title="Get conversation context"),
|
|
802
|
+
)
|
|
803
|
+
def get_conversation_context(
|
|
804
|
+
limit: int = 12,
|
|
805
|
+
include_attachments: bool = False,
|
|
806
|
+
comment: str | dict[str, Any] | None = None,
|
|
807
|
+
) -> dict[str, Any]:
|
|
808
|
+
return service.get_conversation_context(
|
|
809
|
+
context.require_quest_root(),
|
|
810
|
+
limit=limit,
|
|
811
|
+
include_attachments=include_attachments,
|
|
812
|
+
)
|
|
813
|
+
|
|
622
814
|
@server.tool(
|
|
623
815
|
name="get_analysis_campaign",
|
|
624
816
|
description=(
|
|
625
817
|
"Get one analysis campaign manifest with todo items, slice status, and next pending slice. "
|
|
626
818
|
"Pass campaign_id='active' or omit it to recover the active campaign."
|
|
627
819
|
),
|
|
820
|
+
annotations=_read_only_tool_annotations(title="Get analysis campaign"),
|
|
628
821
|
)
|
|
629
822
|
def get_analysis_campaign(
|
|
630
823
|
campaign_id: str | None = "active",
|
|
@@ -762,6 +955,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
762
955
|
"List candidate/revised paper outlines and the selected outline reference. "
|
|
763
956
|
"Use this before writing-facing analysis campaigns or when you need a valid outline_id."
|
|
764
957
|
),
|
|
958
|
+
annotations=_read_only_tool_annotations(title="List paper outlines"),
|
|
765
959
|
)
|
|
766
960
|
def list_paper_outlines(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
767
961
|
return service.list_paper_outlines(context.require_quest_root())
|
|
@@ -957,7 +1151,14 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
957
1151
|
def render_git_graph(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
958
1152
|
return service.render_git_graph(context.require_quest_root())
|
|
959
1153
|
|
|
960
|
-
@server.tool(
|
|
1154
|
+
@server.tool(
|
|
1155
|
+
name="interact",
|
|
1156
|
+
description=(
|
|
1157
|
+
"Send a structured user-facing interaction and optionally fetch new inbound messages. "
|
|
1158
|
+
"Use kind='answer' for direct user questions, kind='progress' for long-running checkpoint updates, "
|
|
1159
|
+
"kind='milestone' for material state changes, and kind='decision_request' only for true blocking decisions."
|
|
1160
|
+
),
|
|
1161
|
+
)
|
|
961
1162
|
def interact(
|
|
962
1163
|
kind: str = "progress",
|
|
963
1164
|
message: str = "",
|
|
@@ -977,6 +1178,9 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
977
1178
|
reply_schema: dict[str, Any] | None = None,
|
|
978
1179
|
reply_to_interaction_id: str | None = None,
|
|
979
1180
|
supersede_open_requests: bool = True,
|
|
1181
|
+
dedupe_key: str | None = None,
|
|
1182
|
+
suppress_if_unchanged: bool | None = None,
|
|
1183
|
+
min_interval_seconds: int | None = None,
|
|
980
1184
|
comment: str | dict[str, Any] | None = None,
|
|
981
1185
|
) -> dict[str, Any]:
|
|
982
1186
|
result = service.interact(
|
|
@@ -999,6 +1203,9 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
999
1203
|
reply_schema=reply_schema,
|
|
1000
1204
|
reply_to_interaction_id=reply_to_interaction_id,
|
|
1001
1205
|
supersede_open_requests=supersede_open_requests,
|
|
1206
|
+
dedupe_key=dedupe_key,
|
|
1207
|
+
suppress_if_unchanged=suppress_if_unchanged,
|
|
1208
|
+
min_interval_seconds=min_interval_seconds,
|
|
1002
1209
|
)
|
|
1003
1210
|
result["interaction_watchdog"] = quest_service.artifact_interaction_watchdog_status(context.require_quest_root())
|
|
1004
1211
|
return result
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import signal
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def process_session_popen_kwargs(
|
|
12
|
+
*,
|
|
13
|
+
hide_window: bool = False,
|
|
14
|
+
new_process_group: bool = True,
|
|
15
|
+
) -> dict[str, Any]:
|
|
16
|
+
if os.name == "nt": # pragma: no cover - exercised on Windows
|
|
17
|
+
creationflags = 0
|
|
18
|
+
if new_process_group and hasattr(subprocess, "CREATE_NEW_PROCESS_GROUP"):
|
|
19
|
+
creationflags |= getattr(subprocess, "CREATE_NEW_PROCESS_GROUP")
|
|
20
|
+
if hide_window and hasattr(subprocess, "CREATE_NO_WINDOW"):
|
|
21
|
+
creationflags |= getattr(subprocess, "CREATE_NO_WINDOW")
|
|
22
|
+
payload: dict[str, Any] = {}
|
|
23
|
+
if creationflags:
|
|
24
|
+
payload["creationflags"] = creationflags
|
|
25
|
+
if hide_window and hasattr(subprocess, "STARTUPINFO") and hasattr(subprocess, "STARTF_USESHOWWINDOW"):
|
|
26
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
27
|
+
startupinfo.dwFlags |= getattr(subprocess, "STARTF_USESHOWWINDOW")
|
|
28
|
+
startupinfo.wShowWindow = getattr(subprocess, "SW_HIDE", 0)
|
|
29
|
+
payload["startupinfo"] = startupinfo
|
|
30
|
+
return payload
|
|
31
|
+
if new_process_group:
|
|
32
|
+
return {
|
|
33
|
+
"start_new_session": True,
|
|
34
|
+
}
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_process_alive(pid: object) -> bool:
|
|
39
|
+
if not isinstance(pid, int) or pid <= 0:
|
|
40
|
+
return False
|
|
41
|
+
if os.name != "nt":
|
|
42
|
+
proc_stat_path = Path("/proc") / str(pid) / "stat"
|
|
43
|
+
if proc_stat_path.exists():
|
|
44
|
+
try:
|
|
45
|
+
parts = proc_stat_path.read_text(encoding="utf-8").split()
|
|
46
|
+
except OSError:
|
|
47
|
+
parts = []
|
|
48
|
+
if len(parts) >= 3 and parts[2] == "Z":
|
|
49
|
+
return False
|
|
50
|
+
try:
|
|
51
|
+
os.kill(pid, 0)
|
|
52
|
+
except ProcessLookupError:
|
|
53
|
+
return False
|
|
54
|
+
except PermissionError:
|
|
55
|
+
return True
|
|
56
|
+
except OSError:
|
|
57
|
+
return False
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def terminate_process_ids(
|
|
62
|
+
*,
|
|
63
|
+
process_pid: int | None,
|
|
64
|
+
process_group_id: int | None,
|
|
65
|
+
force: bool = False,
|
|
66
|
+
) -> None:
|
|
67
|
+
if os.name == "nt": # pragma: no cover - exercised on Windows
|
|
68
|
+
if isinstance(process_pid, int) and process_pid > 0:
|
|
69
|
+
taskkill_args = ["taskkill", "/PID", str(process_pid), "/T"]
|
|
70
|
+
if force:
|
|
71
|
+
taskkill_args.append("/F")
|
|
72
|
+
subprocess.run(taskkill_args, check=False, capture_output=True, text=True)
|
|
73
|
+
if not force and is_process_alive(process_pid):
|
|
74
|
+
try:
|
|
75
|
+
os.kill(process_pid, signal.SIGTERM)
|
|
76
|
+
except OSError:
|
|
77
|
+
pass
|
|
78
|
+
return
|
|
79
|
+
if isinstance(process_group_id, int) and process_group_id > 0:
|
|
80
|
+
try:
|
|
81
|
+
os.killpg(process_group_id, signal.SIGKILL if force else signal.SIGTERM)
|
|
82
|
+
except ProcessLookupError:
|
|
83
|
+
return
|
|
84
|
+
return
|
|
85
|
+
if isinstance(process_pid, int) and process_pid > 0:
|
|
86
|
+
try:
|
|
87
|
+
os.kill(process_pid, signal.SIGKILL if force else signal.SIGTERM)
|
|
88
|
+
except ProcessLookupError:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def terminate_subprocess(
|
|
93
|
+
process: subprocess.Popen[Any],
|
|
94
|
+
*,
|
|
95
|
+
process_group_id: int | None = None,
|
|
96
|
+
force: bool = False,
|
|
97
|
+
prefer_ctrl_break: bool = False,
|
|
98
|
+
grace_seconds: float = 5.0,
|
|
99
|
+
) -> None:
|
|
100
|
+
if process.poll() is not None:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if os.name == "nt": # pragma: no cover - exercised on Windows
|
|
104
|
+
if not force and prefer_ctrl_break and hasattr(signal, "CTRL_BREAK_EVENT"):
|
|
105
|
+
try:
|
|
106
|
+
process.send_signal(signal.CTRL_BREAK_EVENT) # type: ignore[attr-defined]
|
|
107
|
+
except (AttributeError, OSError, ValueError):
|
|
108
|
+
try:
|
|
109
|
+
process.terminate()
|
|
110
|
+
except OSError:
|
|
111
|
+
return
|
|
112
|
+
elif not force:
|
|
113
|
+
try:
|
|
114
|
+
process.terminate()
|
|
115
|
+
except OSError:
|
|
116
|
+
return
|
|
117
|
+
else:
|
|
118
|
+
try:
|
|
119
|
+
process.kill()
|
|
120
|
+
except OSError:
|
|
121
|
+
return
|
|
122
|
+
if force:
|
|
123
|
+
return
|
|
124
|
+
deadline = time.monotonic() + max(grace_seconds, 0.1)
|
|
125
|
+
while time.monotonic() < deadline:
|
|
126
|
+
if process.poll() is not None:
|
|
127
|
+
return
|
|
128
|
+
time.sleep(0.05)
|
|
129
|
+
try:
|
|
130
|
+
process.kill()
|
|
131
|
+
except OSError:
|
|
132
|
+
return
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if isinstance(process_group_id, int) and process_group_id > 0:
|
|
136
|
+
try:
|
|
137
|
+
os.killpg(process_group_id, signal.SIGKILL if force else signal.SIGTERM)
|
|
138
|
+
except ProcessLookupError:
|
|
139
|
+
return
|
|
140
|
+
else:
|
|
141
|
+
try:
|
|
142
|
+
process.kill() if force else process.terminate()
|
|
143
|
+
except OSError:
|
|
144
|
+
return
|
|
145
|
+
if force:
|
|
146
|
+
return
|
|
147
|
+
deadline = time.monotonic() + max(grace_seconds, 0.1)
|
|
148
|
+
while time.monotonic() < deadline:
|
|
149
|
+
if process.poll() is not None:
|
|
150
|
+
return
|
|
151
|
+
time.sleep(0.05)
|
|
152
|
+
if isinstance(process_group_id, int) and process_group_id > 0:
|
|
153
|
+
try:
|
|
154
|
+
os.killpg(process_group_id, signal.SIGKILL)
|
|
155
|
+
except ProcessLookupError:
|
|
156
|
+
return
|
|
157
|
+
elif process.poll() is None:
|
|
158
|
+
try:
|
|
159
|
+
process.kill()
|
|
160
|
+
except OSError:
|
|
161
|
+
return
|