@researai/deepscientist 1.5.0 → 1.5.1
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/AGENTS.md +26 -0
- package/README.md +19 -179
- package/assets/connectors/lingzhu/openclaw-bridge/README.md +124 -0
- package/assets/connectors/lingzhu/openclaw-bridge/index.ts +162 -0
- package/assets/connectors/lingzhu/openclaw-bridge/openclaw.plugin.json +145 -0
- package/assets/connectors/lingzhu/openclaw-bridge/package.json +35 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/cli.ts +180 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/config.ts +196 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/debug-log.ts +111 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/events.ts +4 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/http-handler.ts +1133 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/image-cache.ts +75 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/lingzhu-tools.ts +246 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/transform.ts +541 -0
- package/assets/connectors/lingzhu/openclaw-bridge/src/types.ts +131 -0
- package/assets/connectors/lingzhu/openclaw-bridge/tsconfig.json +14 -0
- package/assets/connectors/lingzhu/openclaw.lingzhu.config.template.json +39 -0
- package/bin/ds.js +233 -53
- package/docs/en/00_QUICK_START.md +134 -0
- package/docs/en/01_SETTINGS_REFERENCE.md +1104 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +404 -0
- package/docs/en/03_QQ_CONNECTOR_GUIDE.md +325 -0
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +216 -0
- package/docs/en/05_TUI_GUIDE.md +141 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +679 -0
- package/docs/en/07_MEMORY_AND_MCP.md +253 -0
- package/docs/en/08_FIGURE_STYLE_GUIDE.md +97 -0
- package/docs/en/09_DOCTOR.md +108 -0
- package/docs/en/90_ARCHITECTURE.md +245 -0
- package/docs/en/91_DEVELOPMENT.md +195 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +29 -0
- package/docs/zh/00_QUICK_START.md +134 -0
- package/docs/zh/01_SETTINGS_REFERENCE.md +1137 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +414 -0
- package/docs/zh/03_QQ_CONNECTOR_GUIDE.md +324 -0
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +230 -0
- package/docs/zh/05_TUI_GUIDE.md +128 -0
- package/docs/zh/06_RUNTIME_AND_CANVAS.md +271 -0
- package/docs/zh/07_MEMORY_AND_MCP.md +235 -0
- package/docs/zh/08_FIGURE_STYLE_GUIDE.md +97 -0
- package/docs/zh/09_DOCTOR.md +112 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +29 -0
- package/install.sh +32 -8
- package/package.json +4 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/artifact/guidance.py +9 -2
- package/src/deepscientist/artifact/service.py +482 -22
- package/src/deepscientist/bash_exec/monitor.py +27 -5
- package/src/deepscientist/bash_exec/runtime.py +639 -0
- package/src/deepscientist/bash_exec/service.py +99 -16
- package/src/deepscientist/bridges/base.py +3 -0
- package/src/deepscientist/bridges/connectors.py +292 -13
- package/src/deepscientist/channels/qq.py +19 -2
- package/src/deepscientist/channels/relay.py +1 -0
- package/src/deepscientist/cli.py +32 -25
- package/src/deepscientist/config/models.py +28 -2
- package/src/deepscientist/config/service.py +201 -6
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +50 -5
- package/src/deepscientist/daemon/api/router.py +1 -0
- package/src/deepscientist/daemon/app.py +442 -15
- package/src/deepscientist/doctor.py +444 -0
- package/src/deepscientist/home.py +1 -0
- package/src/deepscientist/latex_runtime.py +17 -4
- package/src/deepscientist/lingzhu_support.py +182 -0
- package/src/deepscientist/mcp/server.py +49 -2
- package/src/deepscientist/prompts/builder.py +181 -58
- package/src/deepscientist/quest/layout.py +1 -0
- package/src/deepscientist/quest/service.py +63 -2
- package/src/deepscientist/quest/stage_views.py +19 -1
- package/src/deepscientist/runtime_tools/__init__.py +16 -0
- package/src/deepscientist/runtime_tools/builtins.py +19 -0
- package/src/deepscientist/runtime_tools/models.py +29 -0
- package/src/deepscientist/runtime_tools/registry.py +40 -0
- package/src/deepscientist/runtime_tools/service.py +59 -0
- package/src/deepscientist/runtime_tools/tinytex.py +25 -0
- package/src/deepscientist/tinytex.py +276 -0
- package/src/prompts/connectors/lingzhu.md +12 -0
- package/src/prompts/connectors/qq.md +121 -0
- package/src/prompts/system.md +177 -33
- package/src/skills/analysis-campaign/SKILL.md +22 -6
- package/src/skills/baseline/SKILL.md +5 -4
- package/src/skills/decision/SKILL.md +4 -3
- package/src/skills/experiment/SKILL.md +5 -4
- package/src/skills/finalize/SKILL.md +5 -4
- package/src/skills/idea/SKILL.md +5 -4
- package/src/skills/intake-audit/SKILL.md +277 -0
- package/src/skills/intake-audit/references/state-audit-template.md +41 -0
- package/src/skills/rebuttal/SKILL.md +407 -0
- package/src/skills/rebuttal/references/action-plan-template.md +63 -0
- package/src/skills/rebuttal/references/evidence-update-template.md +30 -0
- package/src/skills/rebuttal/references/response-letter-template.md +113 -0
- package/src/skills/rebuttal/references/review-matrix-template.md +55 -0
- package/src/skills/review/SKILL.md +293 -0
- package/src/skills/review/references/experiment-todo-template.md +29 -0
- package/src/skills/review/references/review-report-template.md +83 -0
- package/src/skills/review/references/revision-log-template.md +40 -0
- package/src/skills/scout/SKILL.md +5 -4
- package/src/skills/write/SKILL.md +7 -3
- package/src/tui/dist/components/WelcomePanel.js +17 -43
- package/src/tui/dist/components/messages/BashExecOperationMessage.js +3 -2
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-7v-dHngU.js → AiManusChatView-w5lF2Ttt.js} +109 -575
- package/src/ui/dist/assets/{AnalysisPlugin-B_Xmz-KE.js → AnalysisPlugin-DJOED79I.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-Cko-0tm1.js → AutoFigurePlugin-DaG61Y0M.js} +63 -8
- package/src/ui/dist/assets/{CliPlugin-BsU0ht7q.js → CliPlugin-CV4LqUB_.js} +43 -609
- package/src/ui/dist/assets/{CodeEditorPlugin-DcMMP0Rt.js → CodeEditorPlugin-DylfAea4.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BqoQ5QyY.js → CodeViewerPlugin-F7saY0LM.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-D7eHNhU6.js → DocViewerPlugin-COP0c7jf.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DLJN42T5.js → GitDiffViewerPlugin-CAS05pT9.js} +1 -1
- package/src/ui/dist/assets/{ImageViewerPlugin-gJMV7MOu.js → ImageViewerPlugin-Bco1CN_w.js} +5 -6
- package/src/ui/dist/assets/{LabCopilotPanel-B857sfxP.js → LabCopilotPanel-CvMlCD99.js} +12 -15
- package/src/ui/dist/assets/LabPlugin-BYankkE4.js +2676 -0
- package/src/ui/dist/assets/LabPlugin-D9jVIo0A.css +2698 -0
- package/src/ui/dist/assets/{LatexPlugin-DWKEo-Wj.js → LatexPlugin-LDSMR-t-.js} +16 -16
- package/src/ui/dist/assets/{MarkdownViewerPlugin-DBzoEmhv.js → MarkdownViewerPlugin-B7o80jgm.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DoHc-8vo.js → MarketplacePlugin-CM6ZOcpC.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-CKjKH-yS.js → NotebookEditor-Dc61cXmK.js} +3 -3
- package/src/ui/dist/assets/{PdfLoader-zFoL0VPo.js → PdfLoader-DWowuQwx.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-DXPaL9Nt.js → PdfMarkdownPlugin-BsJM1q_a.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-DhK8qCFp.js → PdfViewerPlugin-DB2eEEFQ.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-CdSi6krf.js → SearchPlugin-CraThSvt.js} +1 -1
- package/src/ui/dist/assets/{Stepper-V-WiDQJl.js → Stepper-CgocRTPq.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-hIs1Efiu.js → TextViewerPlugin-B1JGhKtd.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-DG8b0q2X.js → VNCViewer-CclFC7FM.js} +9 -10
- package/src/ui/dist/assets/{bibtex-HDac6fVW.js → bibtex-D3IKsMl7.js} +1 -1
- package/src/ui/dist/assets/{code-BnBeNxBc.js → code-BP37Xx0p.js} +1 -1
- package/src/ui/dist/assets/{file-content-IRQ3jHb8.js → file-content-BAJSu-9r.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-DZoQ9I6r.js → file-diff-panel-DUGeCTuy.js} +1 -1
- package/src/ui/dist/assets/{file-socket-BMCdLc-P.js → file-socket-CXc1Ojf7.js} +1 -1
- package/src/ui/dist/assets/{file-utils-CltILB3w.js → file-utils-2J21jt7M.js} +1 -1
- package/src/ui/dist/assets/{image-Boe6ffhu.js → image-CMMmgvcn.js} +1 -1
- package/src/ui/dist/assets/{index-BlplpvE1.js → index-BaVumsQT.js} +2 -2
- package/src/ui/dist/assets/{index-DZqJ-qAM.js → index-CWgMgpow.js} +60 -2154
- package/src/ui/dist/assets/{index-DO43pFZP.js → index-DmwmJmbW.js} +6372 -8434
- package/src/ui/dist/assets/{index-Bq2bvfkl.css → index-KGt-z-dD.css} +225 -2920
- package/src/ui/dist/assets/{index-2Zf65FZt.js → index-s7aHnNQ4.js} +1 -1
- package/src/ui/dist/assets/{message-square-mUHn_Ssb.js → message-square-CQRfX0Am.js} +1 -1
- package/src/ui/dist/assets/{monaco-fe0arNEU.js → monaco-B4TbdsrF.js} +1 -1
- package/src/ui/dist/assets/{popover-D_7i19qU.js → popover-B8Rokodk.js} +1 -1
- package/src/ui/dist/assets/{project-sync-DyVGrU7H.js → project-sync-D_i96KH4.js} +2 -8
- package/src/ui/dist/assets/{sigma-BzazRyxQ.js → sigma-D12PnzCN.js} +1 -1
- package/src/ui/dist/assets/{tooltip-DN_yjHFH.js → tooltip-B6YrI4aJ.js} +1 -1
- package/src/ui/dist/assets/trash-Bc8jGp0V.js +32 -0
- package/src/ui/dist/assets/{useCliAccess-DV2L2Qxy.js → useCliAccess-mXVCYSZ-.js} +12 -42
- package/src/ui/dist/assets/{useFileDiffOverlay-DyTj-p_V.js → useFileDiffOverlay-Bg6b9H9K.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-ozYHtUwq.js → wrap-text-Drh5GEnL.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BN9MUyCQ.js → zoom-out-CJj9DZLn.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/assets/fonts/Inter-Variable.ttf +0 -0
- package/assets/fonts/NotoSerifSC-Regular-C94HN_ZN.ttf +0 -0
- package/assets/fonts/NunitoSans-Variable.ttf +0 -0
- package/assets/fonts/Satoshi-Medium-ByP-Zb-9.woff2 +0 -0
- package/assets/fonts/SourceSans3-Variable.ttf +0 -0
- package/assets/fonts/ds-fonts.css +0 -83
- package/src/ui/dist/assets/Inter-Variable-VF2RPR_K.ttf +0 -0
- package/src/ui/dist/assets/LabPlugin-bL7rpic8.js +0 -43
- package/src/ui/dist/assets/NotoSerifSC-Regular-C94HN_ZN-C94HN_ZN.ttf +0 -0
- package/src/ui/dist/assets/NunitoSans-Variable-B_ZymHAd.ttf +0 -0
- package/src/ui/dist/assets/Satoshi-Medium-ByP-Zb-9-GkA34YXu.woff2 +0 -0
- package/src/ui/dist/assets/SourceSans3-Variable-CD-WOsSK.ttf +0 -0
- package/src/ui/dist/assets/info-CcsK_htA.js +0 -18
- package/src/ui/dist/assets/user-plus-BusDx-hF.js +0 -79
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import secrets
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DEFAULT_LINGZHU_GATEWAY_PORT = 18789
|
|
10
|
+
DEFAULT_LINGZHU_LOCAL_HOST = "127.0.0.1"
|
|
11
|
+
DEFAULT_LINGZHU_AGENT_ID = "main"
|
|
12
|
+
DEFAULT_LINGZHU_SESSION_NAMESPACE = "lingzhu"
|
|
13
|
+
|
|
14
|
+
_AUTH_AK_SEGMENTS = (8, 4, 4, 4, 12)
|
|
15
|
+
_AUTH_AK_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_lingzhu_auth_ak() -> str:
|
|
19
|
+
parts: list[str] = []
|
|
20
|
+
for segment_length in _AUTH_AK_SEGMENTS:
|
|
21
|
+
parts.append("".join(secrets.choice(_AUTH_AK_CHARS) for _ in range(segment_length)))
|
|
22
|
+
return "-".join(parts)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def lingzhu_local_host(config: dict[str, Any] | None) -> str:
|
|
26
|
+
value = str((config or {}).get("local_host") or DEFAULT_LINGZHU_LOCAL_HOST).strip()
|
|
27
|
+
return value or DEFAULT_LINGZHU_LOCAL_HOST
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def lingzhu_gateway_port(config: dict[str, Any] | None) -> int:
|
|
31
|
+
raw = (config or {}).get("gateway_port")
|
|
32
|
+
try:
|
|
33
|
+
value = int(raw)
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return DEFAULT_LINGZHU_GATEWAY_PORT
|
|
36
|
+
if value < 1 or value > 65535:
|
|
37
|
+
return DEFAULT_LINGZHU_GATEWAY_PORT
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def normalize_public_base_url(value: Any) -> str | None:
|
|
42
|
+
text = str(value or "").strip()
|
|
43
|
+
if not text:
|
|
44
|
+
return None
|
|
45
|
+
parsed = urlparse(text)
|
|
46
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
47
|
+
return None
|
|
48
|
+
return text.rstrip("/")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def lingzhu_local_base_url(config: dict[str, Any] | None) -> str:
|
|
52
|
+
return f"http://{lingzhu_local_host(config)}:{lingzhu_gateway_port(config)}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def lingzhu_public_base_url(config: dict[str, Any] | None) -> str | None:
|
|
56
|
+
return normalize_public_base_url((config or {}).get("public_base_url"))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def lingzhu_health_url(config: dict[str, Any] | None, *, public: bool = False) -> str | None:
|
|
60
|
+
base = lingzhu_public_base_url(config) if public else lingzhu_local_base_url(config)
|
|
61
|
+
if not base:
|
|
62
|
+
return None
|
|
63
|
+
return f"{base}/metis/agent/api/health"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def lingzhu_sse_url(config: dict[str, Any] | None, *, public: bool = False) -> str | None:
|
|
67
|
+
base = lingzhu_public_base_url(config) if public else lingzhu_local_base_url(config)
|
|
68
|
+
if not base:
|
|
69
|
+
return None
|
|
70
|
+
return f"{base}/metis/agent/api/sse"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def lingzhu_agent_id(config: dict[str, Any] | None) -> str:
|
|
74
|
+
value = str((config or {}).get("agent_id") or DEFAULT_LINGZHU_AGENT_ID).strip()
|
|
75
|
+
return value or DEFAULT_LINGZHU_AGENT_ID
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def lingzhu_probe_payload(
|
|
79
|
+
config: dict[str, Any] | None,
|
|
80
|
+
*,
|
|
81
|
+
message_id: str = "ds-lingzhu-probe-001",
|
|
82
|
+
text: str = "你好",
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
return {
|
|
85
|
+
"message_id": message_id,
|
|
86
|
+
"agent_id": lingzhu_agent_id(config),
|
|
87
|
+
"message": [
|
|
88
|
+
{
|
|
89
|
+
"role": "user",
|
|
90
|
+
"type": "text",
|
|
91
|
+
"text": text,
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def lingzhu_generated_openclaw_config(config: dict[str, Any] | None) -> dict[str, Any]:
|
|
98
|
+
resolved = dict(config or {})
|
|
99
|
+
return {
|
|
100
|
+
"gateway": {
|
|
101
|
+
"port": lingzhu_gateway_port(resolved),
|
|
102
|
+
"http": {
|
|
103
|
+
"endpoints": {
|
|
104
|
+
"chatCompletions": {
|
|
105
|
+
"enabled": True,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
"plugins": {
|
|
111
|
+
"entries": {
|
|
112
|
+
"lingzhu": {
|
|
113
|
+
"enabled": bool(resolved.get("enabled", False)),
|
|
114
|
+
"config": {
|
|
115
|
+
"authAk": str(resolved.get("auth_ak") or "").strip(),
|
|
116
|
+
"agentId": lingzhu_agent_id(resolved),
|
|
117
|
+
"includeMetadata": bool(resolved.get("include_metadata", True)),
|
|
118
|
+
"requestTimeoutMs": int(resolved.get("request_timeout_ms") or 60000),
|
|
119
|
+
"systemPrompt": str(resolved.get("system_prompt") or ""),
|
|
120
|
+
"defaultNavigationMode": str(resolved.get("default_navigation_mode") or "0"),
|
|
121
|
+
"enableFollowUp": bool(resolved.get("enable_follow_up", True)),
|
|
122
|
+
"followUpMaxCount": int(resolved.get("follow_up_max_count") or 3),
|
|
123
|
+
"maxImageBytes": int(resolved.get("max_image_bytes") or 5 * 1024 * 1024),
|
|
124
|
+
"sessionMode": str(resolved.get("session_mode") or "per_user"),
|
|
125
|
+
"sessionNamespace": str(
|
|
126
|
+
resolved.get("session_namespace") or DEFAULT_LINGZHU_SESSION_NAMESPACE
|
|
127
|
+
),
|
|
128
|
+
"autoReceiptAck": bool(resolved.get("auto_receipt_ack", True)),
|
|
129
|
+
"visibleProgressHeartbeat": bool(
|
|
130
|
+
resolved.get("visible_progress_heartbeat", True)
|
|
131
|
+
),
|
|
132
|
+
"visibleProgressHeartbeatSec": int(
|
|
133
|
+
resolved.get("visible_progress_heartbeat_sec") or 10
|
|
134
|
+
),
|
|
135
|
+
"debugLogging": bool(resolved.get("debug_logging", False)),
|
|
136
|
+
"debugLogPayloads": bool(resolved.get("debug_log_payloads", False)),
|
|
137
|
+
"debugLogDir": str(resolved.get("debug_log_dir") or ""),
|
|
138
|
+
"enableExperimentalNativeActions": bool(
|
|
139
|
+
resolved.get("enable_experimental_native_actions", False)
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def lingzhu_generated_openclaw_config_text(config: dict[str, Any] | None) -> str:
|
|
149
|
+
return json.dumps(lingzhu_generated_openclaw_config(config), indent=2, ensure_ascii=False)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def lingzhu_generated_curl(config: dict[str, Any] | None, *, text: str = "你好") -> str:
|
|
153
|
+
auth_ak = str((config or {}).get("auth_ak") or "").strip()
|
|
154
|
+
payload = lingzhu_probe_payload(config, text=text)
|
|
155
|
+
endpoint_url = lingzhu_sse_url(config) or ""
|
|
156
|
+
return (
|
|
157
|
+
f"curl -X POST '{endpoint_url}' \\\n"
|
|
158
|
+
f" --header 'Authorization: Bearer {auth_ak}' \\\n"
|
|
159
|
+
" --header 'Content-Type: application/json' \\\n"
|
|
160
|
+
f" --data '{json.dumps(payload, ensure_ascii=False)}'"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def lingzhu_supported_commands(*, experimental_enabled: bool) -> list[str]:
|
|
165
|
+
commands = [
|
|
166
|
+
"take_photo",
|
|
167
|
+
"take_navigation",
|
|
168
|
+
"control_calendar",
|
|
169
|
+
"notify_agent_off",
|
|
170
|
+
]
|
|
171
|
+
if experimental_enabled:
|
|
172
|
+
commands.extend(
|
|
173
|
+
[
|
|
174
|
+
"send_notification",
|
|
175
|
+
"send_toast",
|
|
176
|
+
"speak_tts",
|
|
177
|
+
"start_video_record",
|
|
178
|
+
"stop_video_record",
|
|
179
|
+
"open_custom_view",
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
return commands
|
|
@@ -256,6 +256,29 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
256
256
|
def list_research_branches(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
257
257
|
return service.list_research_branches(context.require_quest_root())
|
|
258
258
|
|
|
259
|
+
@server.tool(
|
|
260
|
+
name="resolve_runtime_refs",
|
|
261
|
+
description=(
|
|
262
|
+
"Resolve the current canonical research ids and refs. "
|
|
263
|
+
"Use this before supplementary work when you need the active idea, latest main run, active campaign, outline, or reply-thread ids without guessing."
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
def resolve_runtime_refs(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
267
|
+
return service.resolve_runtime_refs(context.require_quest_root())
|
|
268
|
+
|
|
269
|
+
@server.tool(
|
|
270
|
+
name="get_analysis_campaign",
|
|
271
|
+
description=(
|
|
272
|
+
"Get one analysis campaign manifest with todo items, slice status, and next pending slice. "
|
|
273
|
+
"Pass campaign_id='active' or omit it to recover the active campaign."
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
def get_analysis_campaign(
|
|
277
|
+
campaign_id: str | None = "active",
|
|
278
|
+
comment: str | dict[str, Any] | None = None,
|
|
279
|
+
) -> dict[str, Any]:
|
|
280
|
+
return service.get_analysis_campaign(context.require_quest_root(), campaign_id=campaign_id)
|
|
281
|
+
|
|
259
282
|
@server.tool(
|
|
260
283
|
name="record_main_experiment",
|
|
261
284
|
description=(
|
|
@@ -312,8 +335,8 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
312
335
|
@server.tool(
|
|
313
336
|
name="create_analysis_campaign",
|
|
314
337
|
description=(
|
|
315
|
-
"Create a structured analysis campaign from the
|
|
316
|
-
"
|
|
338
|
+
"Create a structured analysis campaign from the current workspace/result node. "
|
|
339
|
+
"Use this for one or more extra experiments; each slice receives its own child branch/worktree and explicit requirements."
|
|
317
340
|
),
|
|
318
341
|
)
|
|
319
342
|
def create_analysis_campaign(
|
|
@@ -321,6 +344,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
321
344
|
campaign_goal: str,
|
|
322
345
|
slices: list[dict[str, Any]],
|
|
323
346
|
parent_run_id: str | None = None,
|
|
347
|
+
campaign_origin: dict[str, Any] | None = None,
|
|
324
348
|
selected_outline_ref: str | None = None,
|
|
325
349
|
research_questions: list[str] | None = None,
|
|
326
350
|
experimental_designs: list[str] | None = None,
|
|
@@ -333,6 +357,7 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
333
357
|
campaign_goal=campaign_goal,
|
|
334
358
|
parent_run_id=parent_run_id or context.run_id,
|
|
335
359
|
slices=slices,
|
|
360
|
+
campaign_origin=campaign_origin,
|
|
336
361
|
selected_outline_ref=selected_outline_ref,
|
|
337
362
|
research_questions=research_questions,
|
|
338
363
|
experimental_designs=experimental_designs,
|
|
@@ -371,6 +396,16 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
371
396
|
selected_reason=selected_reason,
|
|
372
397
|
)
|
|
373
398
|
|
|
399
|
+
@server.tool(
|
|
400
|
+
name="list_paper_outlines",
|
|
401
|
+
description=(
|
|
402
|
+
"List candidate/revised paper outlines and the selected outline reference. "
|
|
403
|
+
"Use this before writing-facing analysis campaigns or when you need a valid outline_id."
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
def list_paper_outlines(comment: str | dict[str, Any] | None = None) -> dict[str, Any]:
|
|
407
|
+
return service.list_paper_outlines(context.require_quest_root())
|
|
408
|
+
|
|
374
409
|
@server.tool(
|
|
375
410
|
name="submit_paper_bundle",
|
|
376
411
|
description=(
|
|
@@ -421,6 +456,10 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
421
456
|
evidence_paths: list[str] | None = None,
|
|
422
457
|
metric_rows: list[dict[str, Any]] | None = None,
|
|
423
458
|
deviations: list[str] | None = None,
|
|
459
|
+
claim_impact: str | None = None,
|
|
460
|
+
reviewer_resolution: str | None = None,
|
|
461
|
+
manuscript_update_hint: str | None = None,
|
|
462
|
+
next_recommendation: str | None = None,
|
|
424
463
|
dataset_scope: str = "full",
|
|
425
464
|
subset_approval_ref: str | None = None,
|
|
426
465
|
comment: str | dict[str, Any] | None = None,
|
|
@@ -436,6 +475,10 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
436
475
|
evidence_paths=evidence_paths,
|
|
437
476
|
metric_rows=metric_rows,
|
|
438
477
|
deviations=deviations,
|
|
478
|
+
claim_impact=claim_impact,
|
|
479
|
+
reviewer_resolution=reviewer_resolution,
|
|
480
|
+
manuscript_update_hint=manuscript_update_hint,
|
|
481
|
+
next_recommendation=next_recommendation,
|
|
439
482
|
dataset_scope=dataset_scope,
|
|
440
483
|
subset_approval_ref=subset_approval_ref,
|
|
441
484
|
)
|
|
@@ -547,6 +590,8 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
547
590
|
expects_reply: bool | None = None,
|
|
548
591
|
reply_mode: str | None = None,
|
|
549
592
|
options: list[dict[str, Any]] | None = None,
|
|
593
|
+
surface_actions: list[dict[str, Any]] | None = None,
|
|
594
|
+
connector_hints: dict[str, Any] | None = None,
|
|
550
595
|
allow_free_text: bool = True,
|
|
551
596
|
reply_schema: dict[str, Any] | None = None,
|
|
552
597
|
reply_to_interaction_id: str | None = None,
|
|
@@ -567,6 +612,8 @@ def build_artifact_server(context: McpContext) -> FastMCP:
|
|
|
567
612
|
expects_reply=expects_reply,
|
|
568
613
|
reply_mode=reply_mode,
|
|
569
614
|
options=options,
|
|
615
|
+
surface_actions=surface_actions,
|
|
616
|
+
connector_hints=connector_hints,
|
|
570
617
|
allow_free_text=allow_free_text,
|
|
571
618
|
reply_schema=reply_schema,
|
|
572
619
|
reply_to_interaction_id=reply_to_interaction_id,
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import re
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from ..connector_runtime import parse_conversation_id
|
|
7
|
+
from ..connector_runtime import normalize_conversation_id, parse_conversation_id
|
|
8
8
|
from ..config import ConfigManager
|
|
9
9
|
from ..memory import MemoryService
|
|
10
10
|
from ..memory.frontmatter import load_markdown_document
|
|
@@ -25,6 +25,9 @@ STANDARD_SKILLS = (
|
|
|
25
25
|
|
|
26
26
|
COMPANION_SKILLS = (
|
|
27
27
|
"figure-polish",
|
|
28
|
+
"intake-audit",
|
|
29
|
+
"review",
|
|
30
|
+
"rebuttal",
|
|
28
31
|
)
|
|
29
32
|
|
|
30
33
|
STAGE_MEMORY_PLAN = {
|
|
@@ -89,46 +92,57 @@ class PromptBuilder:
|
|
|
89
92
|
active_anchor = str(snapshot.get("active_anchor") or skill_id)
|
|
90
93
|
default_locale = str(runtime_config.get("default_locale") or "zh-CN")
|
|
91
94
|
system_block = self._prompt_fragment("src/prompts/system.md")
|
|
92
|
-
|
|
95
|
+
connector_contract_block = self._connector_contract_block(quest_id=quest_id, snapshot=snapshot)
|
|
96
|
+
sections = [
|
|
97
|
+
system_block,
|
|
98
|
+
"",
|
|
99
|
+
"## Runtime Context",
|
|
100
|
+
f"ds_home: {self.home.resolve()}",
|
|
101
|
+
f"quest_id: {quest_id}",
|
|
102
|
+
f"quest_root: {quest_root}",
|
|
103
|
+
f"research_head_branch: {snapshot.get('research_head_branch') or 'none'}",
|
|
104
|
+
f"research_head_worktree_root: {snapshot.get('research_head_worktree_root') or 'none'}",
|
|
105
|
+
f"current_workspace_branch: {snapshot.get('current_workspace_branch') or 'none'}",
|
|
106
|
+
f"current_workspace_root: {snapshot.get('current_workspace_root') or 'none'}",
|
|
107
|
+
f"active_idea_id: {snapshot.get('active_idea_id') or 'none'}",
|
|
108
|
+
f"active_analysis_campaign_id: {snapshot.get('active_analysis_campaign_id') or 'none'}",
|
|
109
|
+
f"active_anchor: {active_anchor}",
|
|
110
|
+
f"active_branch: {snapshot.get('branch')}",
|
|
111
|
+
f"requested_skill: {skill_id}",
|
|
112
|
+
f"runner_name: codex",
|
|
113
|
+
f"model: {model}",
|
|
114
|
+
f"conversation_id: quest:{quest_id}",
|
|
115
|
+
f"default_locale: {default_locale}",
|
|
116
|
+
"built_in_mcp_namespaces: memory, artifact, bash_exec",
|
|
117
|
+
"mcp_namespace_note: any shell-like command execution must use bash_exec, including curl/python/bash/node and similar CLI tools; do not use transient shell snippets.",
|
|
118
|
+
"",
|
|
119
|
+
"Canonical stage skills root:",
|
|
120
|
+
str((self.repo_root / "src" / "skills").resolve()),
|
|
121
|
+
"",
|
|
122
|
+
"Standard stage skill paths:",
|
|
123
|
+
self._skill_paths_block(),
|
|
124
|
+
"",
|
|
125
|
+
"Companion skill paths:",
|
|
126
|
+
self._companion_skill_paths_block(),
|
|
127
|
+
"",
|
|
128
|
+
"## Active Communication Surface",
|
|
129
|
+
self._active_communication_surface_block(
|
|
130
|
+
quest_id=quest_id,
|
|
131
|
+
snapshot=snapshot,
|
|
132
|
+
runtime_config=runtime_config,
|
|
133
|
+
connectors_config=connectors_config,
|
|
134
|
+
),
|
|
135
|
+
]
|
|
136
|
+
if connector_contract_block:
|
|
137
|
+
sections.extend(
|
|
138
|
+
[
|
|
139
|
+
"",
|
|
140
|
+
"## Connector Contract",
|
|
141
|
+
connector_contract_block,
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
sections.extend(
|
|
93
145
|
[
|
|
94
|
-
system_block,
|
|
95
|
-
"",
|
|
96
|
-
"## Runtime Context",
|
|
97
|
-
f"ds_home: {self.home.resolve()}",
|
|
98
|
-
f"quest_id: {quest_id}",
|
|
99
|
-
f"quest_root: {quest_root}",
|
|
100
|
-
f"research_head_branch: {snapshot.get('research_head_branch') or 'none'}",
|
|
101
|
-
f"research_head_worktree_root: {snapshot.get('research_head_worktree_root') or 'none'}",
|
|
102
|
-
f"current_workspace_branch: {snapshot.get('current_workspace_branch') or 'none'}",
|
|
103
|
-
f"current_workspace_root: {snapshot.get('current_workspace_root') or 'none'}",
|
|
104
|
-
f"active_idea_id: {snapshot.get('active_idea_id') or 'none'}",
|
|
105
|
-
f"active_analysis_campaign_id: {snapshot.get('active_analysis_campaign_id') or 'none'}",
|
|
106
|
-
f"active_anchor: {active_anchor}",
|
|
107
|
-
f"active_branch: {snapshot.get('branch')}",
|
|
108
|
-
f"requested_skill: {skill_id}",
|
|
109
|
-
f"runner_name: codex",
|
|
110
|
-
f"model: {model}",
|
|
111
|
-
f"conversation_id: quest:{quest_id}",
|
|
112
|
-
f"default_locale: {default_locale}",
|
|
113
|
-
"built_in_mcp_namespaces: memory, artifact, bash_exec",
|
|
114
|
-
"mcp_namespace_note: any shell-like command execution must use bash_exec, including curl/python/bash/node and similar CLI tools; do not use transient shell snippets.",
|
|
115
|
-
"",
|
|
116
|
-
"Canonical stage skills root:",
|
|
117
|
-
str((self.repo_root / "src" / "skills").resolve()),
|
|
118
|
-
"",
|
|
119
|
-
"Standard stage skill paths:",
|
|
120
|
-
self._skill_paths_block(),
|
|
121
|
-
"",
|
|
122
|
-
"Companion skill paths:",
|
|
123
|
-
self._companion_skill_paths_block(),
|
|
124
|
-
"",
|
|
125
|
-
"## Active Communication Surface",
|
|
126
|
-
self._active_communication_surface_block(
|
|
127
|
-
quest_id=quest_id,
|
|
128
|
-
snapshot=snapshot,
|
|
129
|
-
runtime_config=runtime_config,
|
|
130
|
-
connectors_config=connectors_config,
|
|
131
|
-
),
|
|
132
146
|
"",
|
|
133
147
|
"## Turn Driver",
|
|
134
148
|
self._turn_driver_block(turn_reason=turn_reason, user_message=user_message),
|
|
@@ -183,7 +197,8 @@ class PromptBuilder:
|
|
|
183
197
|
"## Current User Message",
|
|
184
198
|
self._current_user_message_block(turn_reason=turn_reason, user_message=user_message),
|
|
185
199
|
]
|
|
186
|
-
)
|
|
200
|
+
)
|
|
201
|
+
return "\n\n".join(sections).strip() + "\n"
|
|
187
202
|
|
|
188
203
|
def _turn_driver_block(self, *, turn_reason: str, user_message: str) -> str:
|
|
189
204
|
normalized_reason = str(turn_reason or "user_message").strip() or "user_message"
|
|
@@ -218,30 +233,25 @@ class PromptBuilder:
|
|
|
218
233
|
runtime_config: dict,
|
|
219
234
|
connectors_config: dict,
|
|
220
235
|
) -> str:
|
|
221
|
-
|
|
222
|
-
source =
|
|
223
|
-
|
|
236
|
+
surface_context = self._surface_context(quest_id=quest_id, snapshot=snapshot)
|
|
237
|
+
source = surface_context["latest_user_source"]
|
|
238
|
+
surface = surface_context["active_surface"]
|
|
239
|
+
connector = surface_context["active_connector"]
|
|
240
|
+
chat_type = surface_context["active_chat_type"]
|
|
241
|
+
chat_id = surface_context["active_chat_id"]
|
|
224
242
|
qq_config = connectors_config.get("qq") if isinstance(connectors_config.get("qq"), dict) else {}
|
|
225
243
|
|
|
226
|
-
if parsed is None:
|
|
227
|
-
surface = "local"
|
|
228
|
-
connector = "local"
|
|
229
|
-
chat_type = "local"
|
|
230
|
-
chat_id = "default"
|
|
231
|
-
else:
|
|
232
|
-
surface = "connector"
|
|
233
|
-
connector = str(parsed.get("connector") or "connector")
|
|
234
|
-
chat_type = str(parsed.get("chat_type") or "direct")
|
|
235
|
-
chat_id = str(parsed.get("chat_id") or "unknown")
|
|
236
|
-
|
|
237
244
|
lines = [
|
|
238
245
|
f"- latest_user_source: {source}",
|
|
239
246
|
f"- active_surface: {surface}",
|
|
240
247
|
f"- active_connector: {connector}",
|
|
241
248
|
f"- active_chat_type: {chat_type}",
|
|
242
249
|
f"- active_chat_id: {chat_id}",
|
|
250
|
+
f"- active_connector_origin: {surface_context['active_connector_origin']}",
|
|
251
|
+
f"- bound_external_connector_count: {surface_context['bound_external_connector_count']}",
|
|
243
252
|
"- surface_rule: treat web, TUI, and connector threads as one continuous quest, but adapt the amount of detail to the active surface.",
|
|
244
253
|
"- surface_reply_rule: use artifact.interact(...) for durable user-visible continuity; do not dump raw internal tool chatter into connector replies.",
|
|
254
|
+
"- connector_contract_rule: load connector-specific prompt fragments only for the active or bound external connector; do not load unused connector contracts.",
|
|
245
255
|
]
|
|
246
256
|
|
|
247
257
|
if connector == "qq":
|
|
@@ -254,10 +264,12 @@ class PromptBuilder:
|
|
|
254
264
|
f"- qq_auto_send_analysis_summary_png: {bool(qq_config.get('auto_send_analysis_summary_png', True))}",
|
|
255
265
|
f"- qq_auto_send_slice_png: {bool(qq_config.get('auto_send_slice_png', False))}",
|
|
256
266
|
f"- qq_auto_send_paper_pdf: {bool(qq_config.get('auto_send_paper_pdf', True))}",
|
|
267
|
+
f"- qq_enable_markdown_send: {bool(qq_config.get('enable_markdown_send', False))}",
|
|
257
268
|
f"- qq_enable_file_upload_experimental: {bool(qq_config.get('enable_file_upload_experimental', False))}",
|
|
258
269
|
"- qq_visual_rule: follow the fixed Morandi palette guide defined in the system prompt and active stage skill; do not assume per-install palette config exists.",
|
|
259
270
|
"- qq_media_rule: auto-send only high-value milestone media such as a main-experiment summary PNG, an aggregated analysis summary PNG, or the final paper PDF when available and configured.",
|
|
260
271
|
"- qq_media_rule_2: do not auto-send every slice image, every debug plot, or draft paper figures unless the user explicitly asked for them.",
|
|
272
|
+
"- qq_structured_delivery_rule: when you want native QQ markdown or native QQ image/file delivery, request it through artifact.interact(connector_hints=..., attachments=[...]) instead of inventing connector-specific inline tag syntax.",
|
|
261
273
|
]
|
|
262
274
|
)
|
|
263
275
|
else:
|
|
@@ -265,6 +277,61 @@ class PromptBuilder:
|
|
|
265
277
|
|
|
266
278
|
return "\n".join(lines)
|
|
267
279
|
|
|
280
|
+
def _surface_context(self, *, quest_id: str, snapshot: dict) -> dict[str, str | int]:
|
|
281
|
+
latest_user = self._latest_user_message(quest_id)
|
|
282
|
+
latest_user_source = str((latest_user or {}).get("source") or "local:default").strip() or "local:default"
|
|
283
|
+
latest_user_parsed = parse_conversation_id(normalize_conversation_id(latest_user_source))
|
|
284
|
+
bound_sources = snapshot.get("bound_conversations") or []
|
|
285
|
+
bound_external: list[dict[str, str]] = []
|
|
286
|
+
for raw in bound_sources:
|
|
287
|
+
parsed = parse_conversation_id(normalize_conversation_id(raw))
|
|
288
|
+
if parsed is None:
|
|
289
|
+
continue
|
|
290
|
+
if str(parsed.get("connector") or "").strip().lower() == "local":
|
|
291
|
+
continue
|
|
292
|
+
bound_external.append(parsed)
|
|
293
|
+
active = bound_external[0] if bound_external else None
|
|
294
|
+
origin = "bound_external_binding" if active is not None else "latest_user_source"
|
|
295
|
+
if active is None and latest_user_parsed is not None:
|
|
296
|
+
latest_connector = str(latest_user_parsed.get("connector") or "").strip().lower()
|
|
297
|
+
if latest_connector and latest_connector != "local":
|
|
298
|
+
active = latest_user_parsed
|
|
299
|
+
if active is None:
|
|
300
|
+
return {
|
|
301
|
+
"latest_user_source": latest_user_source,
|
|
302
|
+
"active_surface": "local",
|
|
303
|
+
"active_connector": "local",
|
|
304
|
+
"active_chat_type": "local",
|
|
305
|
+
"active_chat_id": "default",
|
|
306
|
+
"active_connector_origin": "none",
|
|
307
|
+
"bound_external_connector_count": len(bound_external),
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
"latest_user_source": latest_user_source,
|
|
311
|
+
"active_surface": "connector",
|
|
312
|
+
"active_connector": str(active.get("connector") or "connector"),
|
|
313
|
+
"active_chat_type": str(active.get("chat_type") or "direct"),
|
|
314
|
+
"active_chat_id": str(active.get("chat_id") or "unknown"),
|
|
315
|
+
"active_connector_origin": origin,
|
|
316
|
+
"bound_external_connector_count": len(bound_external),
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
def _active_external_connector_name(self, *, quest_id: str, snapshot: dict) -> str | None:
|
|
320
|
+
surface_context = self._surface_context(quest_id=quest_id, snapshot=snapshot)
|
|
321
|
+
connector = str(surface_context.get("active_connector") or "").strip().lower()
|
|
322
|
+
if not connector or connector == "local":
|
|
323
|
+
return None
|
|
324
|
+
return connector
|
|
325
|
+
|
|
326
|
+
def _connector_contract_block(self, *, quest_id: str, snapshot: dict) -> str:
|
|
327
|
+
connector = self._active_external_connector_name(quest_id=quest_id, snapshot=snapshot)
|
|
328
|
+
if connector is None:
|
|
329
|
+
return ""
|
|
330
|
+
path = self.repo_root / "src" / "prompts" / "connectors" / f"{connector}.md"
|
|
331
|
+
if not path.exists():
|
|
332
|
+
return ""
|
|
333
|
+
return self._markdown_body(path)
|
|
334
|
+
|
|
268
335
|
def _active_user_requirements_block(self, quest_root: Path) -> str:
|
|
269
336
|
path = self.quest_service._active_user_requirements_path(quest_root)
|
|
270
337
|
if not path.exists():
|
|
@@ -563,10 +630,32 @@ class PromptBuilder:
|
|
|
563
630
|
return value
|
|
564
631
|
return "user_gated"
|
|
565
632
|
|
|
633
|
+
@staticmethod
|
|
634
|
+
def _launch_mode(snapshot: dict) -> str:
|
|
635
|
+
startup_contract = snapshot.get("startup_contract")
|
|
636
|
+
if isinstance(startup_contract, dict):
|
|
637
|
+
value = str(startup_contract.get("launch_mode") or "").strip().lower()
|
|
638
|
+
if value in {"standard", "custom"}:
|
|
639
|
+
return value
|
|
640
|
+
return "standard"
|
|
641
|
+
|
|
642
|
+
@staticmethod
|
|
643
|
+
def _custom_profile(snapshot: dict) -> str:
|
|
644
|
+
startup_contract = snapshot.get("startup_contract")
|
|
645
|
+
if isinstance(startup_contract, dict):
|
|
646
|
+
value = str(startup_contract.get("custom_profile") or "").strip().lower()
|
|
647
|
+
if value in {"continue_existing_state", "revision_rebuttal", "freeform"}:
|
|
648
|
+
return value
|
|
649
|
+
return "freeform"
|
|
650
|
+
|
|
566
651
|
def _research_delivery_policy_block(self, snapshot: dict) -> str:
|
|
567
652
|
need_research_paper = self._need_research_paper(snapshot)
|
|
653
|
+
launch_mode = self._launch_mode(snapshot)
|
|
654
|
+
custom_profile = self._custom_profile(snapshot)
|
|
568
655
|
lines = [
|
|
569
656
|
f"- need_research_paper: {need_research_paper}",
|
|
657
|
+
f"- launch_mode: {launch_mode}",
|
|
658
|
+
f"- custom_profile: {custom_profile if launch_mode == 'custom' else 'n/a'}",
|
|
570
659
|
f"- delivery_mode: {'paper_required' if need_research_paper else 'algorithm_first'}",
|
|
571
660
|
"- idea_stage_rule: every accepted idea submission should normally create a new branch/worktree and a new user-visible research node.",
|
|
572
661
|
"- idea_draft_rule: before `artifact.submit_idea(...)`, first finish a concise durable Markdown draft for the chosen route; keep `idea.md` compact and `draft.md` richer.",
|
|
@@ -575,12 +664,40 @@ class PromptBuilder:
|
|
|
575
664
|
"- post_main_result_rule: after every `artifact.record_main_experiment(...)`, first interpret the measured result and only then choose the next route.",
|
|
576
665
|
"- foundation_selection_rule: for a genuinely new idea round, default to the current research head but feel free to choose another durable foundation when it is cleaner or stronger; inspect `artifact.list_research_branches(...)` first when the best foundation is not obvious.",
|
|
577
666
|
]
|
|
667
|
+
if launch_mode == "custom":
|
|
668
|
+
lines.extend(
|
|
669
|
+
[
|
|
670
|
+
"- custom_launch_rule: do not force the canonical full-research path when the custom startup contract is narrower.",
|
|
671
|
+
"- custom_context_rule: treat `entry_state_summary`, `review_summary`, and `custom_brief` as active runtime context rather than decorative metadata.",
|
|
672
|
+
]
|
|
673
|
+
)
|
|
674
|
+
if custom_profile == "continue_existing_state":
|
|
675
|
+
lines.extend(
|
|
676
|
+
[
|
|
677
|
+
"- existing_state_entry_rule: if reusable baselines, runs, drafts, or review assets already exist, open `intake-audit` before restarting baseline discovery or new experiments.",
|
|
678
|
+
"- reuse_first_rule: trust-rank and reconcile existing assets before deciding to rerun anything costly.",
|
|
679
|
+
]
|
|
680
|
+
)
|
|
681
|
+
elif custom_profile == "revision_rebuttal":
|
|
682
|
+
lines.extend(
|
|
683
|
+
[
|
|
684
|
+
"- rebuttal_entry_rule: treat reviewer comments and the current paper state as the active contract; open `rebuttal` before ordinary writing.",
|
|
685
|
+
"- rebuttal_routing_rule: route supplementary reviewer-facing evidence through `analysis-campaign` and manuscript deltas through `write`, but let `rebuttal` orchestrate that mapping.",
|
|
686
|
+
]
|
|
687
|
+
)
|
|
688
|
+
else:
|
|
689
|
+
lines.extend(
|
|
690
|
+
[
|
|
691
|
+
"- freeform_entry_rule: prefer the custom brief over the default stage order and open only the skills actually needed.",
|
|
692
|
+
]
|
|
693
|
+
)
|
|
578
694
|
if need_research_paper:
|
|
579
695
|
lines.extend(
|
|
580
696
|
[
|
|
581
697
|
"- delivery_goal: the quest should normally continue until at least one paper-like deliverable exists.",
|
|
582
698
|
"- main_result_rule: a strong main experiment is evidence, not the endpoint; usually continue into the necessary analysis, writing, or further strengthening work.",
|
|
583
699
|
"- writing_rule: when the evidence becomes strong enough, analysis and paper writing remain in scope by default.",
|
|
700
|
+
"- review_gate_rule: before declaring a substantial paper/draft task done, open `review` for an independent skeptical audit; if that audit finds serious gaps, route to `analysis-campaign`, `baseline`, `scout`, or `write` instead of stopping.",
|
|
584
701
|
"- stop_rule: do not stop with only an improved algorithm or isolated run logs unless the user explicitly narrows scope.",
|
|
585
702
|
]
|
|
586
703
|
)
|
|
@@ -602,21 +719,27 @@ class PromptBuilder:
|
|
|
602
719
|
bound_conversations = snapshot.get("bound_conversations") or []
|
|
603
720
|
need_research_paper = self._need_research_paper(snapshot)
|
|
604
721
|
decision_policy = self._decision_policy(snapshot)
|
|
722
|
+
launch_mode = self._launch_mode(snapshot)
|
|
723
|
+
custom_profile = self._custom_profile(snapshot)
|
|
605
724
|
lines = [
|
|
606
725
|
f"- configured_default_locale: {default_locale}",
|
|
607
726
|
f"- current_turn_language_bias: {'zh' if chinese_turn else 'en'}",
|
|
608
727
|
f"- bound_conversation_count: {len(bound_conversations)}",
|
|
609
728
|
f"- decision_policy: {decision_policy}",
|
|
729
|
+
f"- launch_mode: {launch_mode}",
|
|
730
|
+
f"- custom_profile: {custom_profile if launch_mode == 'custom' else 'n/a'}",
|
|
610
731
|
"- collaboration_mode: long-horizon, continuity-first, artifact-aware",
|
|
611
|
-
"- response_pattern:
|
|
732
|
+
"- response_pattern: say what changed -> say what it means -> say what happens next",
|
|
612
733
|
"- interaction_protocol: first message may be plain conversation; after that, treat artifact.interact threads and mailbox polls as the main continuity spine across TUI, web, and connectors",
|
|
613
734
|
"- mailbox_protocol: artifact.interact(include_recent_inbound_messages=True) is the queued human-message mailbox; when it returns user text, treat that input as higher priority than background subtasks until it has been acknowledged",
|
|
614
735
|
"- acknowledgment_protocol: after artifact.interact returns any human message, immediately call artifact.interact(...) again to confirm receipt; if answerable, answer directly, otherwise state the short plan, nearest checkpoint, and that the current background subtask is paused",
|
|
615
|
-
"- progress_protocol: emit artifact.interact(kind='progress', reply_mode='threaded', ...) at
|
|
736
|
+
"- progress_protocol: emit artifact.interact(kind='progress', reply_mode='threaded', ...) only at real human-meaningful checkpoints, after the first meaningful signal from long-running work, and then only occasional keepalives during truly long work, usually about every 20 to 30 minutes",
|
|
616
737
|
"- long_run_reporting_protocol: for long-running bash_exec monitoring loops, report after each completed sleep/await cycle with real evidence plus the next planned check time and estimated next reply time",
|
|
617
738
|
"- blocking_protocol: use reply_mode='blocking' only for true unresolved user decisions; ordinary progress updates should stay threaded and non-blocking",
|
|
739
|
+
f"- standby_prefix_rule: when you intentionally leave one blocking standby interaction after task completion, prefix it with {'[等待决策]' if chinese_turn else '[Waiting for decision]'} and wait for a new user reply before continuing",
|
|
618
740
|
"- stop_notice_protocol: if work must pause or stop, send a user-visible notice that explains why, confirms preserved context, and states that any new message or `/resume` will continue from the same quest",
|
|
619
|
-
"- respect_protocol: write user-facing updates as
|
|
741
|
+
"- respect_protocol: write user-facing updates as natural, respectful, easy-to-follow chat; do not sound like a formal status report or internal tool log",
|
|
742
|
+
"- omission_protocol: for ordinary user-facing updates, omit file paths, artifact ids, branch/worktree ids, session ids, raw commands, raw logs, and internal tool names unless the user asked for them or needs them to act",
|
|
620
743
|
"- non_research_mode_protocol: if the user message looks like a non-research request, ask for a second confirmation before engaging stage skills or research workflow; after completion, leave one blocking standby interaction instead of repeatedly pinging",
|
|
621
744
|
"- workspace_discipline: read and modify code inside current_workspace_root; treat quest_root as the canonical repo identity and durable runtime root",
|
|
622
745
|
"- binary_safety: do not open or rewrite large binary assets unless truly necessary; prefer summaries, metadata, and targeted inspection first",
|