@researai/deepscientist 1.5.8 → 1.5.11

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.
Files changed (148) hide show
  1. package/LICENSE +186 -21
  2. package/README.md +108 -95
  3. package/assets/branding/connector-qq.png +0 -0
  4. package/assets/branding/connector-rokid.png +0 -0
  5. package/assets/branding/connector-weixin.png +0 -0
  6. package/assets/branding/projects.png +0 -0
  7. package/bin/ds.js +172 -13
  8. package/docs/assets/branding/projects.png +0 -0
  9. package/docs/en/00_QUICK_START.md +308 -70
  10. package/docs/en/01_SETTINGS_REFERENCE.md +3 -0
  11. package/docs/en/02_START_RESEARCH_GUIDE.md +112 -0
  12. package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
  13. package/docs/en/09_DOCTOR.md +41 -5
  14. package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
  15. package/docs/en/11_LICENSE_AND_RISK.md +256 -0
  16. package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +427 -0
  17. package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
  18. package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
  19. package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
  20. package/docs/en/README.md +79 -0
  21. package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
  22. package/docs/images/weixin/weixin-plugin-entry.png +0 -0
  23. package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
  24. package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
  25. package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
  26. package/docs/images/weixin/weixin-settings-bind.svg +57 -0
  27. package/docs/zh/00_QUICK_START.md +315 -74
  28. package/docs/zh/01_SETTINGS_REFERENCE.md +3 -0
  29. package/docs/zh/02_START_RESEARCH_GUIDE.md +112 -0
  30. package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
  31. package/docs/zh/09_DOCTOR.md +41 -5
  32. package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
  33. package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
  34. package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +423 -0
  35. package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
  36. package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
  37. package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
  38. package/docs/zh/README.md +126 -0
  39. package/install.sh +0 -34
  40. package/package.json +3 -3
  41. package/pyproject.toml +2 -2
  42. package/src/deepscientist/__init__.py +1 -1
  43. package/src/deepscientist/annotations.py +343 -0
  44. package/src/deepscientist/artifact/arxiv.py +484 -37
  45. package/src/deepscientist/artifact/metrics.py +1 -3
  46. package/src/deepscientist/artifact/service.py +1347 -111
  47. package/src/deepscientist/arxiv_library.py +275 -0
  48. package/src/deepscientist/bash_exec/service.py +9 -0
  49. package/src/deepscientist/bridges/builtins.py +2 -0
  50. package/src/deepscientist/bridges/connectors.py +447 -0
  51. package/src/deepscientist/channels/__init__.py +2 -0
  52. package/src/deepscientist/channels/builtins.py +3 -1
  53. package/src/deepscientist/channels/qq.py +1 -1
  54. package/src/deepscientist/channels/qq_gateway.py +1 -1
  55. package/src/deepscientist/channels/relay.py +7 -1
  56. package/src/deepscientist/channels/weixin.py +59 -0
  57. package/src/deepscientist/channels/weixin_ilink.py +317 -0
  58. package/src/deepscientist/config/models.py +22 -2
  59. package/src/deepscientist/config/service.py +431 -60
  60. package/src/deepscientist/connector/__init__.py +4 -0
  61. package/src/deepscientist/connector/connector_profiles.py +481 -0
  62. package/src/deepscientist/connector/lingzhu_support.py +668 -0
  63. package/src/deepscientist/connector/qq_profiles.py +206 -0
  64. package/src/deepscientist/connector/weixin_support.py +663 -0
  65. package/src/deepscientist/connector_profiles.py +1 -374
  66. package/src/deepscientist/connector_runtime.py +2 -0
  67. package/src/deepscientist/daemon/api/handlers.py +295 -5
  68. package/src/deepscientist/daemon/api/router.py +16 -1
  69. package/src/deepscientist/daemon/app.py +1130 -61
  70. package/src/deepscientist/doctor.py +5 -2
  71. package/src/deepscientist/gitops/diff.py +120 -29
  72. package/src/deepscientist/lingzhu_support.py +1 -182
  73. package/src/deepscientist/mcp/server.py +14 -5
  74. package/src/deepscientist/prompts/builder.py +29 -1
  75. package/src/deepscientist/qq_profiles.py +1 -196
  76. package/src/deepscientist/quest/node_traces.py +152 -2
  77. package/src/deepscientist/quest/service.py +169 -43
  78. package/src/deepscientist/quest/stage_views.py +172 -9
  79. package/src/deepscientist/registries/baseline.py +56 -4
  80. package/src/deepscientist/runners/codex.py +55 -3
  81. package/src/deepscientist/weixin_support.py +1 -0
  82. package/src/prompts/connectors/lingzhu.md +3 -1
  83. package/src/prompts/connectors/weixin.md +230 -0
  84. package/src/prompts/system.md +9 -0
  85. package/src/skills/idea/SKILL.md +16 -0
  86. package/src/skills/idea/references/literature-survey-template.md +24 -0
  87. package/src/skills/idea/references/related-work-playbook.md +4 -0
  88. package/src/skills/idea/references/selection-gate.md +9 -0
  89. package/src/skills/write/SKILL.md +1 -1
  90. package/src/tui/package.json +1 -1
  91. package/src/ui/dist/assets/{AiManusChatView-m2FNtwbn.js → AiManusChatView-D0mTXG4-.js} +156 -48
  92. package/src/ui/dist/assets/{AnalysisPlugin-BMTF8EGL.js → AnalysisPlugin-Db0cTXxm.js} +1 -1
  93. package/src/ui/dist/assets/{CliPlugin-BEOWgxCI.js → CliPlugin-DrV8je02.js} +164 -9
  94. package/src/ui/dist/assets/{CodeEditorPlugin-BCXvjqmb.js → CodeEditorPlugin-QXMSCH71.js} +8 -8
  95. package/src/ui/dist/assets/{CodeViewerPlugin-DaJcy3nD.js → CodeViewerPlugin-7hhtWj_E.js} +5 -5
  96. package/src/ui/dist/assets/{DocViewerPlugin-ByfeIq4K.js → DocViewerPlugin-BWMSnRJe.js} +3 -3
  97. package/src/ui/dist/assets/{GitDiffViewerPlugin-Cksf3VZ-.js → GitDiffViewerPlugin-7J9h9Vy_.js} +20 -21
  98. package/src/ui/dist/assets/{ImageViewerPlugin-CFz-OsTS.js → ImageViewerPlugin-CHJl_0lr.js} +5 -5
  99. package/src/ui/dist/assets/{LabCopilotPanel-CJ1cJzoX.js → LabCopilotPanel-1qSow1es.js} +11 -11
  100. package/src/ui/dist/assets/{LabPlugin-BF3dVJwa.js → LabPlugin-eQpPPCEp.js} +2 -1
  101. package/src/ui/dist/assets/{LatexPlugin-DDkwZ6Sj.js → LatexPlugin-BwRfi89Z.js} +7 -7
  102. package/src/ui/dist/assets/{MarkdownViewerPlugin-HAuvurcT.js → MarkdownViewerPlugin-836PVQWV.js} +4 -4
  103. package/src/ui/dist/assets/{MarketplacePlugin-BtoTYy2C.js → MarketplacePlugin-C2y_556i.js} +3 -3
  104. package/src/ui/dist/assets/{NotebookEditor-CSJYx7b-.js → NotebookEditor-BRzJbGsn.js} +12 -12
  105. package/src/ui/dist/assets/{NotebookEditor-DQgRezm_.js → NotebookEditor-DIX7Mlzu.js} +1 -1
  106. package/src/ui/dist/assets/{PdfLoader-DPa_-fv6.js → PdfLoader-DzRaTAlq.js} +14 -7
  107. package/src/ui/dist/assets/{PdfMarkdownPlugin-BZpXOEjm.js → PdfMarkdownPlugin-DZUfIUnp.js} +73 -6
  108. package/src/ui/dist/assets/{PdfViewerPlugin-BT8a6wGR.js → PdfViewerPlugin-BwtICzue.js} +103 -34
  109. package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
  110. package/src/ui/dist/assets/{SearchPlugin-D_blveZi.js → SearchPlugin-DHeIAMsx.js} +1 -1
  111. package/src/ui/dist/assets/{TextViewerPlugin-Btx0M3hX.js → TextViewerPlugin-C3tCmFox.js} +5 -4
  112. package/src/ui/dist/assets/{VNCViewer-DImJO4rO.js → VNCViewer-CQsKVm3t.js} +10 -10
  113. package/src/ui/dist/assets/bot-BEA2vWuK.js +21 -0
  114. package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
  115. package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
  116. package/src/ui/dist/assets/{code-BUfXGJSl.js → code-XfbSR8K2.js} +1 -1
  117. package/src/ui/dist/assets/{file-content-VqamwI3X.js → file-content-BjxNaIfy.js} +1 -1
  118. package/src/ui/dist/assets/{file-diff-panel-C_wOoS7a.js → file-diff-panel-D_lLVQk0.js} +1 -1
  119. package/src/ui/dist/assets/{file-socket-D2bTuMVP.js → file-socket-D9x_5vlY.js} +1 -1
  120. package/src/ui/dist/assets/{image-BZkGJ4mM.js → image-BhWT33W1.js} +1 -1
  121. package/src/ui/dist/assets/{index-DdRW6RMJ.js → index--c4iXtuy.js} +12 -12
  122. package/src/ui/dist/assets/{index-CxkvSeKw.js → index-BDxipwrC.js} +2 -2
  123. package/src/ui/dist/assets/{index-DjggJovS.js → index-DZTZ8mWP.js} +14934 -9613
  124. package/src/ui/dist/assets/{index-DXZ1daiJ.css → index-Dqj-Mjb4.css} +2 -13
  125. package/src/ui/dist/assets/index-PJbSbPTy.js +25 -0
  126. package/src/ui/dist/assets/{monaco-DHMc7kKM.js → monaco-K8izTGgo.js} +1 -1
  127. package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DfBors6y.js} +16 -1
  128. package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
  129. package/src/ui/dist/assets/{popover-B85oCgCS.js → popover-yFK1J4fL.js} +1 -1
  130. package/src/ui/dist/assets/{project-sync-DOMCcPac.js → project-sync-PENr2zcz.js} +1 -74
  131. package/src/ui/dist/assets/select-CAbJDfYv.js +1690 -0
  132. package/src/ui/dist/assets/{sigma-BO2rQrl3.js → sigma-DEuYJqTl.js} +1 -1
  133. package/src/ui/dist/assets/{index-D9QIGcmc.js → square-check-big-omoSUmcd.js} +2 -13
  134. package/src/ui/dist/assets/{trash-BsVEH_dV.js → trash--F119N47.js} +1 -1
  135. package/src/ui/dist/assets/{useCliAccess-b8L6JuZm.js → useCliAccess-D31UR23I.js} +1 -1
  136. package/src/ui/dist/assets/{useFileDiffOverlay-BY7uA9hV.js → useFileDiffOverlay-BH6KcMzq.js} +1 -1
  137. package/src/ui/dist/assets/{wrap-text-BwyVuUIK.js → wrap-text-CZ613PM5.js} +1 -1
  138. package/src/ui/dist/assets/{zoom-out-RDpLugQP.js → zoom-out-BgDLAv3z.js} +1 -1
  139. package/src/ui/dist/index.html +2 -2
  140. package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
  141. package/src/ui/dist/assets/AutoFigurePlugin-DxPdMUNb.js +0 -8149
  142. package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
  143. package/src/ui/dist/assets/Stepper-DH2k75Vo.js +0 -158
  144. package/src/ui/dist/assets/bibtex-B-Hqu0Sg.js +0 -189
  145. package/src/ui/dist/assets/file-utils--zJCPN1i.js +0 -109
  146. package/src/ui/dist/assets/message-square-FUIPIhU2.js +0 -16
  147. package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
  148. package/src/ui/dist/assets/tooltip-B1OspAkx.js +0 -108
@@ -22,26 +22,49 @@ class BaselineRegistry:
22
22
  self.reconcile_confirmed_quests()
23
23
  entry_files = sorted(self.entries_root.glob("*.yaml"))
24
24
  if entry_files:
25
- return sorted((self._load_entry_file(path) for path in entry_files), key=self._entry_sort_key)
25
+ return sorted(
26
+ (
27
+ entry
28
+ for path in entry_files
29
+ for entry in [self._load_entry_file(path)]
30
+ if not self._is_deleted_entry(entry)
31
+ ),
32
+ key=self._entry_sort_key,
33
+ )
26
34
 
27
35
  latest_by_id: dict[str, dict] = {}
28
36
  for item in self._history_entries():
29
37
  baseline_id = str(item.get("baseline_id") or item.get("entry_id") or "").strip()
30
38
  if baseline_id:
31
39
  latest_by_id[baseline_id] = item
32
- return sorted(latest_by_id.values(), key=self._entry_sort_key)
40
+ return sorted(
41
+ (item for item in latest_by_id.values() if not self._is_deleted_entry(item)),
42
+ key=self._entry_sort_key,
43
+ )
33
44
 
34
- def get(self, baseline_id: str) -> dict | None:
45
+ def get(self, baseline_id: str, *, include_deleted: bool = False) -> dict | None:
35
46
  normalized_id = self._normalize_identifier(baseline_id, field_name="Baseline id")
36
47
  path = self._entry_path(normalized_id)
37
48
  if path.exists():
38
- return self._load_entry_file(path)
49
+ entry = self._load_entry_file(path)
50
+ if self._is_deleted_entry(entry) and not include_deleted:
51
+ return None
52
+ return entry
39
53
  latest_match = None
40
54
  for item in self._history_entries():
41
55
  if item.get("baseline_id") == normalized_id or item.get("entry_id") == normalized_id:
42
56
  latest_match = item
57
+ if self._is_deleted_entry(latest_match) and not include_deleted:
58
+ return None
43
59
  return latest_match
44
60
 
61
+ def is_deleted(self, baseline_id: str) -> bool:
62
+ try:
63
+ entry = self.get(baseline_id, include_deleted=True)
64
+ except ValueError:
65
+ return False
66
+ return self._is_deleted_entry(entry)
67
+
45
68
  def publish(self, entry: dict) -> dict:
46
69
  timestamp = utc_now()
47
70
  baseline_id = self._normalize_identifier(
@@ -201,6 +224,8 @@ class BaselineRegistry:
201
224
  }
202
225
 
203
226
  existing = self._existing_entry(baseline_id)
227
+ if self._is_deleted_entry(existing):
228
+ continue
204
229
  if self._entry_needs_publish(existing, entry):
205
230
  synchronized.append(self.publish(entry))
206
231
  elif existing:
@@ -244,6 +269,27 @@ class BaselineRegistry:
244
269
  write_yaml(attachment_root / "attachment.yaml", attachment)
245
270
  return attachment
246
271
 
272
+ def delete(self, baseline_id: str) -> dict:
273
+ normalized_id = self._normalize_identifier(baseline_id, field_name="Baseline id")
274
+ existing = self.get(normalized_id, include_deleted=True) or {}
275
+ timestamp = utc_now()
276
+ deleted_entry = {
277
+ **existing,
278
+ "registry_kind": "baseline",
279
+ "schema_version": 1,
280
+ "entry_id": normalized_id,
281
+ "baseline_id": normalized_id,
282
+ "status": "deleted",
283
+ "updated_at": timestamp,
284
+ "deleted_at": timestamp,
285
+ "summary": str(existing.get("summary") or "").strip(),
286
+ }
287
+ if not deleted_entry.get("created_at"):
288
+ deleted_entry["created_at"] = timestamp
289
+ write_yaml(self._entry_path(normalized_id), deleted_entry)
290
+ append_jsonl(self.index_path, deleted_entry)
291
+ return deleted_entry
292
+
247
293
  def _history_entries(self) -> list[dict]:
248
294
  return read_jsonl(self.index_path)
249
295
 
@@ -292,6 +338,12 @@ class BaselineRegistry:
292
338
  entry = self._load_entry_file(path)
293
339
  return entry if isinstance(entry, dict) and entry else None
294
340
 
341
+ @staticmethod
342
+ def _is_deleted_entry(entry: dict[str, Any] | None) -> bool:
343
+ if not isinstance(entry, dict):
344
+ return False
345
+ return str(entry.get("status") or "").strip().lower() == "deleted"
346
+
295
347
  @staticmethod
296
348
  def _entry_needs_publish(existing: dict[str, Any] | None, candidate: dict[str, Any]) -> bool:
297
349
  if not existing:
@@ -35,6 +35,17 @@ def _compact_text(value: object, *, limit: int = 1200) -> str:
35
35
  return text[: limit - 1].rstrip() + "…"
36
36
 
37
37
 
38
+ def _structured_text(value: object) -> str:
39
+ if value is None:
40
+ return ""
41
+ if isinstance(value, str):
42
+ return value.strip()
43
+ try:
44
+ return json.dumps(value, ensure_ascii=False, indent=2)
45
+ except TypeError:
46
+ return str(value)
47
+
48
+
38
49
  def _iter_event_texts(event: dict[str, Any]) -> list[str]:
39
50
  texts: list[str] = []
40
51
  for key in ("text", "content", "message"):
@@ -184,7 +195,24 @@ def _tool_name(event: dict[str, Any], item: dict[str, Any]) -> str:
184
195
  return "tool"
185
196
 
186
197
 
198
+ def _is_bash_exec_item(event: dict[str, Any], item: dict[str, Any]) -> bool:
199
+ server = str(item.get("server") or event.get("server") or "").strip()
200
+ tool = str(item.get("tool") or event.get("tool") or "").strip()
201
+ return server == "bash_exec" and tool == "bash_exec"
202
+
203
+
187
204
  def _tool_args(event: dict[str, Any], item: dict[str, Any]) -> str:
205
+ if _is_bash_exec_item(event, item):
206
+ for value in (
207
+ item.get("arguments"),
208
+ event.get("arguments"),
209
+ item.get("input"),
210
+ event.get("input"),
211
+ ):
212
+ text = _structured_text(value)
213
+ if text:
214
+ return text
215
+ return ""
188
216
  for value in (
189
217
  item.get("command"),
190
218
  item.get("query"),
@@ -204,6 +232,21 @@ def _tool_args(event: dict[str, Any], item: dict[str, Any]) -> str:
204
232
 
205
233
 
206
234
  def _tool_output(event: dict[str, Any], item: dict[str, Any]) -> str:
235
+ if _is_bash_exec_item(event, item):
236
+ for value in (
237
+ item.get("result"),
238
+ item.get("output"),
239
+ item.get("content"),
240
+ event.get("result"),
241
+ event.get("output"),
242
+ event.get("content"),
243
+ item.get("aggregated_output"),
244
+ event.get("aggregated_output"),
245
+ ):
246
+ text = _structured_text(value)
247
+ if text:
248
+ return text
249
+ return ""
207
250
  for value in (
208
251
  item.get("aggregated_output"),
209
252
  item.get("changes"),
@@ -253,10 +296,12 @@ def _mcp_tool_metadata(
253
296
  metadata["workdir"] = arguments.get("workdir")
254
297
  if isinstance(arguments.get("mode"), str):
255
298
  metadata["mode"] = arguments.get("mode")
256
- if isinstance(arguments.get("timeout_seconds"), int):
299
+ if arguments.get("timeout_seconds") is not None:
257
300
  metadata["timeout_seconds"] = arguments.get("timeout_seconds")
258
301
  if "comment" in arguments:
259
302
  metadata["comment"] = arguments.get("comment")
303
+ if server == "bash_exec" and tool == "bash_exec" and isinstance(arguments.get("id"), str):
304
+ metadata["bash_id"] = arguments.get("id")
260
305
  metadata["session_id"] = f"quest:{quest_id}"
261
306
  metadata["agent_id"] = "pi"
262
307
  metadata["agent_instance_id"] = run_id
@@ -266,12 +311,18 @@ def _mcp_tool_metadata(
266
311
  for key in (
267
312
  "bash_id",
268
313
  "status",
314
+ "command",
315
+ "workdir",
316
+ "cwd",
317
+ "kind",
318
+ "comment",
269
319
  "started_at",
270
320
  "finished_at",
271
321
  "exit_code",
272
322
  "stop_reason",
273
323
  "last_progress",
274
324
  "log_path",
325
+ "watchdog_after_seconds",
275
326
  ):
276
327
  if key in result_payload:
277
328
  metadata[key] = result_payload.get(key)
@@ -758,6 +809,7 @@ class CodexRunner:
758
809
  workspace_root = request.worktree_root or request.quest_root
759
810
  resolved_binary = resolve_runner_binary(self.binary, runner_name="codex")
760
811
  resolved_runner_config = runner_config if isinstance(runner_config, dict) else self._load_runner_config()
812
+ normalized_model = str(request.model or "").strip()
761
813
  command = [
762
814
  resolved_binary or self.binary,
763
815
  "--search",
@@ -766,9 +818,9 @@ class CodexRunner:
766
818
  "--cd",
767
819
  str(workspace_root),
768
820
  "--skip-git-repo-check",
769
- "--model",
770
- request.model,
771
821
  ]
822
+ if normalized_model.lower() not in {"", "inherit", "default", "codex-default"}:
823
+ command.extend(["--model", normalized_model])
772
824
  if request.approval_policy:
773
825
  command.extend(["-c", f'approval_policy="{request.approval_policy}"'])
774
826
  reasoning_effort = request.reasoning_effort
@@ -0,0 +1 @@
1
+ from .connector.weixin_support import * # noqa: F401,F403
@@ -11,5 +11,7 @@
11
11
  - lingzhu_safety_rule: request only actions that are clearly justified by the current quest and understandable to the human user
12
12
  - lingzhu_text_rule: even when requesting `surface_actions`, always include a clear text explanation of what is happening and why
13
13
  - lingzhu_reply_style_rule: for Lingzhu-facing user-visible text sent through `artifact.interact(...)`, keep the message clear, concise, respectful, and high-information-density
14
- - lingzhu_reply_length_rule: for each Lingzhu-facing `artifact.interact(...)` message, normally answer in at most 2 to 3 sentences unless the user explicitly asks for more detail
14
+ - lingzhu_reply_length_rule: for each Lingzhu-facing `artifact.interact(...)` message, normally keep the text within about 20 Chinese characters or one very short sentence unless the user explicitly asks for more detail
15
15
  - lingzhu_summary_first_rule: in Lingzhu-facing `artifact.interact(...)` messages, usually give only the synopsis and key facts needed for the user's next decision or understanding; avoid long preambles, repetition, and low-signal detail
16
+ - lingzhu_task_gate_rule: only treat a Lingzhu user utterance as a new quest instruction when the text explicitly starts with `我现在的任务是`; otherwise assume the device is polling for queued progress or buffered replies
17
+ - lingzhu_poll_rule: when Lingzhu is polling rather than giving a new task, return only the buffered progress checkpoints or the latest short status; do not reinterpret the poll text as a fresh instruction
@@ -0,0 +1,230 @@
1
+ # Weixin Connector Contract
2
+
3
+ - connector_contract_id: weixin
4
+ - connector_contract_scope: loaded only when Weixin is the active or bound external connector for this quest
5
+ - connector_contract_goal: use `artifact.interact(...)` as the main durable user-visible thread while respecting the Weixin iLink `context_token` reply model
6
+ - weixin_runtime_ack_rule: the Weixin bridge itself emits the immediate transport-level receipt acknowledgement before the model turn starts
7
+ - weixin_no_duplicate_ack_rule: do not waste your first model response or first `artifact.interact(...)` call on a second bare acknowledgement such as "received", "已收到", or "processing" when the bridge already sent that
8
+ - weixin_reply_style_rule: keep Weixin replies concise, milestone-first, respectful, and easy to scan on a phone
9
+ - weixin_reply_length_rule: for ordinary Weixin progress replies, normally use only 2 to 4 short sentences, or 3 short bullets at most
10
+ - weixin_summary_first_rule: start with the user-facing conclusion, then what it means, then the next action
11
+ - weixin_progress_shape_rule: make the current task, the main difficulty or latest real progress, and the next concrete measure explicit whenever possible
12
+ - weixin_eta_rule: for important long-running phases such as baseline reproduction, main experiments, analysis, or paper packaging, include a rough ETA or next check-in window when you can
13
+ - weixin_tool_call_keepalive_rule: for ordinary active work, prefer one concise Weixin progress update after roughly 10 tool calls when there is already a human-meaningful delta, and do not let work drift beyond roughly 20 tool calls or about 15 minutes without a user-visible checkpoint
14
+ - weixin_internal_detail_rule: omit worker names, retry counters, pending/running/completed counts, low-level file listings, and monitor-window narration unless the user explicitly asked for them or they change the recommended action
15
+ - weixin_translation_rule: translate internal execution and file-management work into user value instead of narrating tool or filesystem churn
16
+ - weixin_preflight_rule: before sending a Weixin-facing progress update, rewrite it if it still reads like a monitor log, execution diary, or file inventory
17
+ - weixin_operator_surface_rule: treat Weixin as an operator surface for concise coordination and milestone delivery, not as a full artifact browser
18
+ - weixin_default_text_rule: plain text is the default and safest Weixin mode
19
+ - weixin_context_token_rule: ordinary downstream replies rely on the runtime-managed `context_token`; do not invent your own reply token fields
20
+ - weixin_media_rule: Weixin supports native image, video, and file delivery through structured attachments; request them through `artifact.interact(..., attachments=[...])` instead of inventing inline tag syntax
21
+ - weixin_media_path_rule: when sending native Weixin media, prefer absolute local paths; remote URLs are allowed only when the bridge can download them safely
22
+ - weixin_media_path_priority_rule: prefer quest-local files under `artifacts/`, `experiments/`, `paper/`, or `userfiles/` over arbitrary external URLs
23
+ - weixin_media_hint_rule: when you need native Weixin media typing, set `connector_delivery={'weixin': {'media_kind': ...}}` on the attachment instead of relying only on filename suffixes
24
+ - weixin_inbound_media_rule: inbound image, video, and file messages can now enter the quest as attachments, including media-only inbound turns
25
+ - weixin_inbound_materialization_rule: inbound media is copied into quest-local `userfiles/weixin/...`; if the user sent media, read those quest-local files before continuing
26
+ - weixin_audio_output_rule: there is no native Weixin voice-message output branch; audio files fall back to ordinary file delivery, not Weixin voice messages
27
+ - weixin_partial_delivery_rule: the runtime now preflights native attachments before send and prefers a single combined Weixin message for text plus media, so do not assume text was already delivered if attachment preparation failed
28
+ - weixin_failure_rule: if `artifact.interact(...)` returns `attachment_issues` or `delivery_results` errors, treat that as a real delivery failure and adapt before assuming the user received the media
29
+ - weixin_first_followup_rule: after a new inbound Weixin message, your first substantive follow-up should either answer directly or give the first meaningful checkpoint and next action, not a second bare acknowledgement
30
+
31
+ ## Weixin Runtime Capabilities
32
+
33
+ - always supported:
34
+ - concise plain-text Weixin replies through `artifact.interact(...)`
35
+ - ordinary threaded continuity through runtime-managed `context_token`
36
+ - automatic downstream reply-to-user behavior when a valid `context_token` has been seen for that user
37
+ - inbound text messages entering the quest as user turns
38
+ - inbound image, video, and file attachments being materialized into quest-local `userfiles/weixin/...`
39
+ - supported when you attach one structured attachment with explicit delivery hints:
40
+ - native Weixin image delivery
41
+ - native Weixin video delivery
42
+ - native Weixin file delivery
43
+ - do not assume:
44
+ - inline connector-specific tags in the message body
45
+ - arbitrary historical quote reconstruction beyond the active `context_token`
46
+ - device-side `surface_actions`
47
+ - native Weixin voice-message output
48
+
49
+ ## Structured Usage Rules
50
+
51
+ - request native Weixin image delivery by attaching one structured attachment with:
52
+ - `connector_delivery={'weixin': {'media_kind': 'image'}}`
53
+ - request native Weixin video delivery by attaching one structured attachment with:
54
+ - `connector_delivery={'weixin': {'media_kind': 'video'}}`
55
+ - request native Weixin file delivery by attaching one structured attachment with:
56
+ - `connector_delivery={'weixin': {'media_kind': 'file'}}`
57
+ - when you want native Weixin media delivery, make sure the attachment exposes at least one usable file reference such as:
58
+ - `path`
59
+ - `source_path`
60
+ - `output_path`
61
+ - `artifact_path`
62
+ - `url`
63
+ - if no native media delivery is needed, omit `connector_delivery`
64
+ - do not attach many files to Weixin by default; choose only the one highest-value image, video, or file for that milestone
65
+ - if native delivery fails, fall back to a concise text update unless the missing media is essential
66
+ - if the user sent media into Weixin, prefer the quest-local copied attachment path over connector cache or remote URL
67
+
68
+ ## Examples
69
+
70
+ ### 0. Bad vs good Weixin progress update
71
+
72
+ Bad:
73
+
74
+ ```text
75
+ 我刚看完新的一轮监控窗,现在还是 12 pending / 3 running / 1 completed。retry 计数已经到第 4 次,workspace 里又多了几个 png 和 json。我接下来继续盯日志和文件变动,之后再看看是不是还要再补一轮。
76
+ ```
77
+
78
+ Why bad:
79
+
80
+ - it forces the user to infer the real conclusion from internal telemetry
81
+ - it exposes retry counters, queue numbers, and file churn that usually do not help a phone-side operator
82
+ - it reads like a monitor log, not a concise collaborator update
83
+
84
+ Good:
85
+
86
+ ```text
87
+ 主实验还在继续推进,当前不需要您额外处理。最新进展是核心结果已经基本稳定,但还有一条对照线比较慢。接下来我会补完这条对照,预计 20 分钟左右给您下一次关键更新。
88
+ ```
89
+
90
+ Why good:
91
+
92
+ - it starts with the conclusion the user actually needs
93
+ - it keeps the meaningful risk but removes low-level runtime chatter
94
+ - it tells the user what happens next and when to expect the next checkpoint
95
+
96
+ ### 1. Plain-text Weixin progress update
97
+
98
+ ```python
99
+ artifact.interact(
100
+ kind="progress",
101
+ message="主实验第一轮已经跑完,当前结果基本稳定。接下来我会继续补关键对照,确认这个提升是不是稳得住。预计下一次关键更新在 20 分钟左右。",
102
+ reply_mode="threaded",
103
+ )
104
+ ```
105
+
106
+ ### 2. Continue the current Weixin thread normally
107
+
108
+ Use the normal `artifact.interact(...)` call. The runtime keeps continuity through the latest `context_token` for that Weixin user.
109
+
110
+ ```python
111
+ artifact.interact(
112
+ kind="progress",
113
+ message="我已经看完您刚才发来的材料,也确认了它和当前 baseline 的关键差异。接下来我会把真正影响路线判断的部分整理出来,再给您一个更完整的结论。",
114
+ reply_mode="threaded",
115
+ )
116
+ ```
117
+
118
+ ### 3. Send one native Weixin image
119
+
120
+ ```python
121
+ artifact.interact(
122
+ kind="milestone",
123
+ message="主实验已经完成。我发一张汇总图给您,方便直接在手机上看。",
124
+ reply_mode="threaded",
125
+ attachments=[
126
+ {
127
+ "kind": "path",
128
+ "path": "/absolute/path/to/main_summary.png",
129
+ "label": "main-summary",
130
+ "content_type": "image/png",
131
+ "connector_delivery": {"weixin": {"media_kind": "image"}},
132
+ }
133
+ ],
134
+ )
135
+ ```
136
+
137
+ ### 4. Send one native Weixin video
138
+
139
+ ```python
140
+ artifact.interact(
141
+ kind="milestone",
142
+ message="我把这段关键演示视频一起发给您。",
143
+ reply_mode="threaded",
144
+ attachments=[
145
+ {
146
+ "kind": "path",
147
+ "path": "/absolute/path/to/demo.mp4",
148
+ "label": "demo-video",
149
+ "content_type": "video/mp4",
150
+ "connector_delivery": {"weixin": {"media_kind": "video"}},
151
+ }
152
+ ],
153
+ )
154
+ ```
155
+
156
+ ### 5. Send one native Weixin file
157
+
158
+ ```python
159
+ artifact.interact(
160
+ kind="milestone",
161
+ message="论文初稿已经整理完成,我把 PDF 一并发给您。",
162
+ reply_mode="threaded",
163
+ attachments=[
164
+ {
165
+ "kind": "path",
166
+ "path": "/absolute/path/to/paper_draft.pdf",
167
+ "label": "paper-draft",
168
+ "content_type": "application/pdf",
169
+ "connector_delivery": {"weixin": {"media_kind": "file"}},
170
+ }
171
+ ],
172
+ )
173
+ ```
174
+
175
+ ### 6. Send a native Weixin image from an artifact-style path field
176
+
177
+ If the attachment is not using `path` but does expose a real quest-local file through `source_path`, `output_path`, or `artifact_path`, the runtime can still use it for native Weixin media delivery.
178
+
179
+ ```python
180
+ artifact.interact(
181
+ kind="milestone",
182
+ message="我把这张结果图直接发给您。",
183
+ reply_mode="threaded",
184
+ attachments=[
185
+ {
186
+ "kind": "runner_result",
187
+ "source_path": "/absolute/path/to/result.png",
188
+ "content_type": "image/png",
189
+ "connector_delivery": {"weixin": {"media_kind": "image"}},
190
+ }
191
+ ],
192
+ )
193
+ ```
194
+
195
+ ### 7. If the user sent Weixin media into the quest
196
+
197
+ - inspect the current turn attachments
198
+ - prefer the copied quest-local file under `userfiles/weixin/...`
199
+ - reason over that local file instead of asking the user to resend unless the attachment is broken
200
+
201
+ ### 8. If delivery fails
202
+
203
+ - inspect `attachment_issues`
204
+ - inspect `delivery_results`
205
+ - if native media failed, send a concise text-only fallback unless the missing media is essential
206
+
207
+ Example fallback shape:
208
+
209
+ ```python
210
+ result = artifact.interact(
211
+ kind="milestone",
212
+ message="我把汇总图发给您。",
213
+ reply_mode="threaded",
214
+ attachments=[
215
+ {
216
+ "kind": "path",
217
+ "path": "/absolute/path/to/main_summary.png",
218
+ "content_type": "image/png",
219
+ "connector_delivery": {"weixin": {"media_kind": "image"}},
220
+ }
221
+ ],
222
+ )
223
+
224
+ if result.get("attachment_issues") or any(not item.get("ok") for item in (result.get("delivery_results") or [])):
225
+ artifact.interact(
226
+ kind="progress",
227
+ message="图片这次没有成功送达。我先继续用文字给您同步结论,稍后再补发可用版本。",
228
+ reply_mode="threaded",
229
+ )
230
+ ```
@@ -978,12 +978,15 @@ Prefer these patterns:
978
978
  - treat the resulting branch as one durable research round or route, not merely a temporary Git container
979
979
  - every accepted durable idea submission should normally create a new user-visible canvas node
980
980
  - before accepting an idea, unless strong durable evidence already narrows the route to one obvious serious option, run one bounded divergent -> convergent ideation pass instead of collapsing onto the first plausible route
981
+ - before writing or submitting the final selected idea, durably map at least 5 and usually 5 to 10 related and usable papers; prioritize direct task-modeling or mechanism-neighbor papers and only backfill with the closest adjacent translatable work when the direct pool is truly smaller
981
982
  - classify the current framing as `problem-first` or `solution-first`
982
983
  - generate a small but genuinely diverse candidate slate before ranking, then shrink it back to a serious frontier that is usually 2 to 3 alternatives and at most 5
983
984
  - if the candidates are all from the same mechanism family, widen once with distinct lenses such as abstraction ladder, tension hunting, analogy transfer, inversion, or adjacent-possible reasoning
984
985
  - require each serious candidate to answer `why now` / `what changed`
985
986
  - before `artifact.submit_idea(...)`, make the winner pass a two-sentence pitch and strongest-objection check
986
987
  - before calling it, first finish a concise but durable idea draft in Markdown that explains the route clearly enough for later implementation and review
988
+ - do not treat the literature floor as optional; if fewer than 5 usable papers are durably mapped, go back to search or record a blocked state instead of forcing the idea through
989
+ - that final idea draft must use one consistent standard citation format and include a `References` or `Bibliography` section for the survey-stage papers that actually shaped the idea
987
990
  - when available, pass that draft through `draft_markdown` so the branch keeps both a compact `idea.md` contract and a richer `draft.md`
988
991
  - `continue_line` means the new idea is a child of the current active branch
989
992
  - `branch_alternative` means the new idea is a sibling-like branch that starts from the current branch's parent foundation
@@ -1042,6 +1045,8 @@ Prefer these patterns:
1042
1045
  - use `artifact.checkpoint(...)` for meaningful code-state milestones
1043
1046
  - use `artifact.render_git_graph(...)` when the quest needs a refreshed Git history view
1044
1047
  - use `artifact.arxiv(paper_id=..., full_text=False)` to read an already identified arXiv paper
1048
+ - `artifact.arxiv(mode='read', paper_id=..., full_text=False)` is the preferred explicit form; it is local-first and will auto-persist the paper into the quest arXiv library when missing
1049
+ - use `artifact.arxiv(mode='list')` when you need to inspect the arXiv papers already saved for the current quest
1045
1050
  - keep paper discovery in web search; switch to `artifact.arxiv(..., full_text=True)` only when the full paper body is actually needed
1046
1051
  - use stage-significant artifact writes for progress, milestone, report, run, and decision updates
1047
1052
  - if the runtime exposes `artifact.interact(...)`, use it for structured progress updates, decision requests, and approval responses
@@ -1626,6 +1631,9 @@ If you choose a non-default foundation, record why.
1626
1631
  At the start of `idea`, if related-work coverage or novelty judgment is not already durable and explicit, also open `scout/SKILL.md` as a companion skill before final selection.
1627
1632
  At the start of a fresh or resumed `idea` pass, search quest/global memory first.
1628
1633
  If coverage is still incomplete or stale, actively use the runner's web/search tool for discovery and `artifact.arxiv(...)` for reading shortlisted arXiv papers before selecting a direction.
1634
+ Treat literature grounding as a hard gate: do not write or submit a final selected idea until the durable survey covers at least 5 and usually 5 to 10 related and usable papers.
1635
+ Those papers should be close enough to the task-modeling problem, failure mode, mechanism, or codebase translation question to justify the selected route with real evidence rather than intuition alone.
1636
+ If the direct neighborhood is genuinely smaller, document that shortage explicitly and use the closest adjacent translatable papers to finish the grounding.
1629
1637
 
1630
1638
  Expected outcomes:
1631
1639
 
@@ -1640,6 +1648,7 @@ Expected outcomes:
1640
1648
  - explicit mechanism and risk
1641
1649
  - cheapest falsification path
1642
1650
  - selected direction or rejection decision
1651
+ - a final idea draft that uses standard-format citations and a `References` or `Bibliography` section for the papers actually used
1643
1652
  - when the pass is substantial, a research-outline style note can be preferable to loose ideation prose; that note should usually cover:
1644
1653
  - executive summary
1645
1654
  - codebase analysis
@@ -103,6 +103,9 @@ Break ties primarily through careful reasoning over:
103
103
  - Do not select an idea before checking whether close prior work already did it.
104
104
  - Do not confuse "I can implement this" with "this is a publishable or useful research direction".
105
105
  - Do not treat a weak literature search as sufficient because the idea sounds elegant.
106
+ - Do not write, promote, or submit a final idea until the durable survey covers at least `5` and usually `5-10` task-modeling-related, mechanism-relevant, or otherwise directly usable papers.
107
+ - Treat that literature floor as a hard gate, not a suggestion.
108
+ If the direct task-modeling neighborhood truly contains fewer than `5` usable papers, record that evidence explicitly and fill the remaining slots with the closest adjacent papers whose mechanism can be translated into the current task and codebase.
106
109
  - Every fresh idea build or idea-refinement pass must begin with:
107
110
  - a memory sweep, and
108
111
  - an external literature sweep.
@@ -206,6 +209,8 @@ Before you choose a direction, perform a broad but bounded literature sweep.
206
209
 
207
210
  The sweep must be grounded in actual retrieval, not recall alone.
208
211
  If durable quest memory already contains a recent and explicit survey, reuse it first and search externally only for the missing buckets, newer papers, or unresolved overlaps.
212
+ For a normal selected-idea decision, the durable sweep must end with at least `5` and usually `5-10` papers that are close enough to the task-modeling problem, failure mode, mechanism, or codebase translation question to inform the actual design.
213
+ This floor exists to prevent thin novelty claims and under-motivated ideas, not to reward quota chasing.
209
214
 
210
215
  When tools allow it, combine:
211
216
 
@@ -240,6 +245,8 @@ For each promising idea, you must be able to answer:
240
245
 
241
246
  The goal is not to cite everything on Earth.
242
247
  The goal is to avoid fake novelty and to identify a direction that has credible research value.
248
+ However, do not stop the sweep early once the first plausible argument appears.
249
+ Keep going until the strongest obvious overlaps are mapped and the `5-10` usable-paper floor is durably satisfied.
243
250
 
244
251
  Recommended search outputs:
245
252
 
@@ -962,9 +969,15 @@ At minimum, preserve:
962
969
  - a `why now` statement
963
970
  - the code-level plan and minimal experiment
964
971
  - the literature relation and evidence pointers
972
+ - inline citations or citation markers tied to the papers actually used in the idea rationale
973
+ - a `References` or `Bibliography` section in a standard citation format
965
974
  - the strongest alternative hypothesis
966
975
  - the strongest likely objection
967
976
 
977
+ The selected idea draft must cite the survey papers that actually shaped the mechanism, motivation, novelty check, or claim boundary.
978
+ Use one consistent standard citation format throughout the draft, such as numbered references or author-year style.
979
+ Do not mention paper titles casually in prose without giving them a proper citation entry.
980
+
968
981
  ## Idea quality rules
969
982
 
970
983
  Good ideas should be:
@@ -1135,6 +1148,7 @@ Preferred artifact choices:
1135
1148
 
1136
1149
  If the idea is selected and becomes the active route, immediately call `artifact.submit_idea(mode='create', lineage_intent='continue_line'|'branch_alternative', ...)`.
1137
1150
  Before that call, first finalize a concise but durable Markdown draft for the chosen route.
1151
+ Do not start writing that final draft until the literature survey has already met the hard minimum of at least `5` and usually `5-10` usable papers.
1138
1152
  That draft should usually cover:
1139
1153
 
1140
1154
  - executive summary
@@ -1148,9 +1162,11 @@ That draft should usually cover:
1148
1162
  - code-level change plan
1149
1163
  - evaluation or falsification plan
1150
1164
  - risks, caveats, and implementation notes
1165
+ - a citation-ready `References` or `Bibliography` section that lists the survey-stage papers actually used by the idea in a standard citation format
1151
1166
 
1152
1167
  Use the draft to think clearly first, then compress the accepted contract into the structured `artifact.submit_idea(...)` fields.
1153
1168
  When the MCP surface supports it, pass the final Markdown draft through `draft_markdown` so the branch records both `idea.md` and `draft.md`.
1169
+ Ensure the final draft carries appropriate citations for the closest prior work, direct inspirations, and any cross-domain papers that materially shaped the selected idea.
1154
1170
  Normal durable idea flow should create a new branch and a new canvas node every time an accepted idea package changes meaningfully, including documentation-only idea-package changes.
1155
1171
  Use `lineage_intent='continue_line'` when the new idea is a child of the current active branch.
1156
1172
  Use `lineage_intent='branch_alternative'` when the new idea should branch from the current branch's parent foundation as a sibling-like alternative.
@@ -11,6 +11,11 @@ The purpose is to make related-work coverage durable, searchable, and reusable s
11
11
  - baseline id or method name
12
12
  - task / dataset / metric contract
13
13
  - current investigation target
14
+ - survey minimum gate status
15
+ - related and usable papers found so far
16
+ - how many are direct task-modeling papers
17
+ - how many are adjacent but translatable papers
18
+ - whether the hard floor of at least `5` and usually `5-10` usable papers has been satisfied
14
19
  - why the survey is being run now
15
20
  - first idea build
16
21
  - idea refinement
@@ -66,9 +71,11 @@ For each paper, include:
66
71
  - year
67
72
  - identifier or arXiv id
68
73
  - URL
74
+ - standard citation string or citation key
69
75
  - short mechanism summary
70
76
  - task / dataset / metric overlap
71
77
  - what it means for the current idea
78
+ - whether it is directly usable for the current idea, only a novelty check, or only an adjacent inspiration
72
79
  - status:
73
80
  - `new_this_pass`
74
81
  - `known_before`
@@ -80,6 +87,7 @@ Recommended columns:
80
87
 
81
88
  - identifier
82
89
  - year
90
+ - standard citation key
83
91
  - mechanism overlap
84
92
  - task overlap
85
93
  - dataset overlap
@@ -129,3 +137,19 @@ Close with:
129
137
  - the rejected ideas and why
130
138
  - what still needs more search before selection
131
139
  - whether the stage is ready for `idea` selection, more `scout`, or a user decision
140
+
141
+ ## 10. Citation-ready shortlist for the selected idea
142
+
143
+ Before the final idea draft is written, extract the papers that materially support the winning idea.
144
+
145
+ For each such paper, include:
146
+
147
+ - standard citation entry in the format you plan to use later
148
+ - what part of the idea it supports:
149
+ - problem motivation
150
+ - closest prior work
151
+ - mechanism inspiration
152
+ - claim boundary
153
+ - whether it must appear inline in the idea draft or only in the references section
154
+
155
+ The final selected idea should not be written or submitted until this shortlist is ready.
@@ -52,6 +52,9 @@ Try to cover these buckets before final selection:
52
52
  - papers focused on the same failure mode
53
53
  - papers with the same task but different mechanism families
54
54
 
55
+ For a normal selected-idea decision, the survey should durably cover at least `5` and usually `5-10` related and usable papers.
56
+ Prefer direct task-modeling papers first; if that pool is truly small, fill the rest with the closest adjacent and translatable work instead of pretending the literature is empty.
57
+
55
58
  If the area is active, recent work matters a lot.
56
59
  If the area is stable, seminal work may matter more than recency.
57
60
 
@@ -132,4 +135,5 @@ The related-work search is good enough to stop when:
132
135
  - the strongest obvious nearby papers are mapped
133
136
  - the closest-prior-work table is complete enough to compare seriously
134
137
  - each top candidate has an explicit novelty or value verdict
138
+ - the usable-paper floor for the selected idea has been satisfied or the shortage is explicitly documented
135
139
  - the remaining uncertainty is recorded rather than hidden