@researai/deepscientist 1.5.9 → 1.5.12
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 +112 -99
- package/assets/branding/connector-qq.png +0 -0
- package/assets/branding/connector-rokid.png +0 -0
- package/assets/branding/connector-weixin.png +0 -0
- package/assets/branding/projects.png +0 -0
- package/bin/ds.js +519 -63
- package/docs/assets/branding/projects.png +0 -0
- package/docs/en/00_QUICK_START.md +338 -68
- package/docs/en/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/en/02_START_RESEARCH_GUIDE.md +180 -4
- package/docs/en/04_LINGZHU_CONNECTOR_GUIDE.md +62 -179
- package/docs/en/09_DOCTOR.md +66 -5
- package/docs/en/10_WEIXIN_CONNECTOR_GUIDE.md +137 -0
- package/docs/en/11_LICENSE_AND_RISK.md +256 -0
- package/docs/en/12_GUIDED_WORKFLOW_TOUR.md +446 -0
- package/docs/en/13_CORE_ARCHITECTURE_GUIDE.md +297 -0
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +284 -0
- package/docs/en/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/en/README.md +83 -0
- package/docs/images/lingzhu/rokid-agent-platform-create.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.png +0 -0
- package/docs/images/weixin/weixin-plugin-entry.svg +33 -0
- package/docs/images/weixin/weixin-qr-confirm.svg +30 -0
- package/docs/images/weixin/weixin-quest-media-flow.svg +44 -0
- package/docs/images/weixin/weixin-settings-bind.svg +57 -0
- package/docs/zh/00_QUICK_START.md +345 -72
- package/docs/zh/01_SETTINGS_REFERENCE.md +14 -0
- package/docs/zh/02_START_RESEARCH_GUIDE.md +181 -3
- package/docs/zh/04_LINGZHU_CONNECTOR_GUIDE.md +62 -193
- package/docs/zh/09_DOCTOR.md +68 -5
- package/docs/zh/10_WEIXIN_CONNECTOR_GUIDE.md +144 -0
- package/docs/zh/11_LICENSE_AND_RISK.md +256 -0
- package/docs/zh/12_GUIDED_WORKFLOW_TOUR.md +442 -0
- package/docs/zh/13_CORE_ARCHITECTURE_GUIDE.md +296 -0
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +506 -0
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +285 -0
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +4 -1
- package/docs/zh/README.md +129 -0
- package/install.sh +0 -34
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/annotations.py +343 -0
- package/src/deepscientist/artifact/arxiv.py +484 -37
- package/src/deepscientist/artifact/service.py +574 -108
- package/src/deepscientist/arxiv_library.py +275 -0
- package/src/deepscientist/bash_exec/monitor.py +7 -5
- package/src/deepscientist/bash_exec/service.py +93 -21
- package/src/deepscientist/bridges/builtins.py +2 -0
- package/src/deepscientist/bridges/connectors.py +447 -0
- package/src/deepscientist/channels/__init__.py +2 -0
- package/src/deepscientist/channels/builtins.py +3 -1
- package/src/deepscientist/channels/local.py +3 -3
- package/src/deepscientist/channels/qq.py +8 -8
- package/src/deepscientist/channels/qq_gateway.py +1 -1
- package/src/deepscientist/channels/relay.py +14 -8
- package/src/deepscientist/channels/weixin.py +59 -0
- package/src/deepscientist/channels/weixin_ilink.py +388 -0
- package/src/deepscientist/config/models.py +23 -2
- package/src/deepscientist/config/service.py +539 -67
- package/src/deepscientist/connector/__init__.py +4 -0
- package/src/deepscientist/connector/connector_profiles.py +481 -0
- package/src/deepscientist/connector/lingzhu_support.py +668 -0
- package/src/deepscientist/connector/qq_profiles.py +206 -0
- package/src/deepscientist/connector/weixin_support.py +663 -0
- package/src/deepscientist/connector_profiles.py +1 -374
- package/src/deepscientist/connector_runtime.py +2 -0
- package/src/deepscientist/daemon/api/handlers.py +165 -5
- package/src/deepscientist/daemon/api/router.py +13 -1
- package/src/deepscientist/daemon/app.py +1444 -67
- package/src/deepscientist/doctor.py +4 -5
- package/src/deepscientist/gitops/diff.py +120 -29
- package/src/deepscientist/lingzhu_support.py +1 -182
- package/src/deepscientist/mcp/server.py +135 -7
- package/src/deepscientist/prompts/builder.py +128 -11
- package/src/deepscientist/qq_profiles.py +1 -196
- package/src/deepscientist/quest/node_traces.py +23 -0
- package/src/deepscientist/quest/service.py +359 -74
- package/src/deepscientist/quest/stage_views.py +71 -5
- package/src/deepscientist/runners/codex.py +170 -19
- package/src/deepscientist/runners/runtime_overrides.py +6 -0
- package/src/deepscientist/shared.py +33 -14
- package/src/deepscientist/weixin_support.py +1 -0
- package/src/prompts/connectors/lingzhu.md +3 -1
- package/src/prompts/connectors/qq.md +2 -1
- package/src/prompts/connectors/weixin.md +231 -0
- package/src/prompts/contracts/shared_interaction.md +4 -1
- package/src/prompts/system.md +61 -9
- package/src/skills/analysis-campaign/SKILL.md +46 -6
- package/src/skills/analysis-campaign/references/campaign-plan-template.md +21 -8
- package/src/skills/baseline/SKILL.md +1 -1
- package/src/skills/decision/SKILL.md +1 -1
- package/src/skills/experiment/SKILL.md +1 -1
- package/src/skills/finalize/SKILL.md +1 -1
- package/src/skills/idea/SKILL.md +1 -1
- package/src/skills/intake-audit/SKILL.md +1 -1
- package/src/skills/rebuttal/SKILL.md +74 -1
- package/src/skills/rebuttal/references/response-letter-template.md +55 -11
- package/src/skills/review/SKILL.md +118 -1
- package/src/skills/review/references/experiment-todo-template.md +23 -0
- package/src/skills/review/references/review-report-template.md +16 -0
- package/src/skills/review/references/revision-log-template.md +4 -0
- package/src/skills/scout/SKILL.md +1 -1
- package/src/skills/write/SKILL.md +168 -7
- package/src/skills/write/references/paper-experiment-matrix-template.md +131 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BKZ103sn.js → AiManusChatView-CnJcXynW.js} +156 -48
- package/src/ui/dist/assets/{AnalysisPlugin-mTTzGAlK.js → AnalysisPlugin-DeyzPEhV.js} +1 -1
- package/src/ui/dist/assets/{CliPlugin-BH58n3GY.js → CliPlugin-CB1YODQn.js} +164 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-BKGRUH7e.js → CodeEditorPlugin-B-xicq1e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-BMADwFWJ.js → CodeViewerPlugin-DT54ysXa.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ZOnTIHLN.js → DocViewerPlugin-DQtKT-VD.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-CQ7h1Djm.js → GitDiffViewerPlugin-hqHbCfnv.js} +20 -21
- package/src/ui/dist/assets/{ImageViewerPlugin-GVS5MsnC.js → ImageViewerPlugin-OcVo33jV.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BZNv1JML.js → LabCopilotPanel-DdGwhEUV.js} +11 -11
- package/src/ui/dist/assets/{LabPlugin-TWcJsdQA.js → LabPlugin-Ciz1gDaX.js} +2 -1
- package/src/ui/dist/assets/{LatexPlugin-DIjHiR2x.js → LatexPlugin-BhmjNQRC.js} +37 -11
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D3ooGAH0.js → MarkdownViewerPlugin-BzdVH9Bx.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-DfVfE9hN.js → MarketplacePlugin-DmyHspXt.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-DDl0_Mc0.js → NotebookEditor-BMXKrDRk.js} +1 -1
- package/src/ui/dist/assets/{NotebookEditor-s8JhzuX1.js → NotebookEditor-BTVYRGkm.js} +12 -12
- package/src/ui/dist/assets/{PdfLoader-C2Sf6SJM.js → PdfLoader-CvcjJHXv.js} +14 -7
- package/src/ui/dist/assets/{PdfMarkdownPlugin-CXFLoIsa.js → PdfMarkdownPlugin-DW2ej8Vk.js} +73 -6
- package/src/ui/dist/assets/{PdfViewerPlugin-BYTmz2fK.js → PdfViewerPlugin-CmlDxbhU.js} +103 -34
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +3627 -0
- package/src/ui/dist/assets/{SearchPlugin-CjWBI1O9.js → SearchPlugin-DAjQZPSv.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-DdOBU3-S.js → TextViewerPlugin-C-nVAZb_.js} +5 -4
- package/src/ui/dist/assets/{VNCViewer-B8HGgLwQ.js → VNCViewer-D7-dIYon.js} +10 -10
- package/src/ui/dist/assets/bot-C_G4WtNI.js +21 -0
- package/src/ui/dist/assets/branding/logo-rokid.png +0 -0
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +2895 -0
- package/src/ui/dist/assets/{code-BWAY76JP.js → code-Cd7WfiWq.js} +1 -1
- package/src/ui/dist/assets/{file-content-C1NwU5oQ.js → file-content-B57zsL9y.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CywslwB9.js → file-diff-panel-DVoheLFq.js} +1 -1
- package/src/ui/dist/assets/{file-socket-B4kzuOBQ.js → file-socket-B5kXFxZP.js} +1 -1
- package/src/ui/dist/assets/{image-D-NZM-6P.js → image-LLOjkMHF.js} +1 -1
- package/src/ui/dist/assets/{index-DGIYDuTv.css → index-BQG-1s2o.css} +40 -13
- package/src/ui/dist/assets/{index-DHZJ_0TI.js → index-C3r2iGrp.js} +12 -12
- package/src/ui/dist/assets/{index-7Chr1g9c.js → index-CLQauncb.js} +15050 -9561
- package/src/ui/dist/assets/index-Dxa2eYMY.js +25 -0
- package/src/ui/dist/assets/{index-BdM1Gqfr.js → index-hOUOWbW2.js} +2 -2
- package/src/ui/dist/assets/{monaco-Cb2uKKe6.js → monaco-BGGAEii3.js} +1 -1
- package/src/ui/dist/assets/{pdf-effect-queue-DSw_D3RV.js → pdf-effect-queue-DlEr1_y5.js} +16 -1
- package/src/ui/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
- package/src/ui/dist/assets/{popover-Bg72DGgT.js → popover-CWJbJuYY.js} +1 -1
- package/src/ui/dist/assets/{project-sync-Ce_0BglY.js → project-sync-CRJiucYO.js} +18 -77
- package/src/ui/dist/assets/select-CoHB7pvH.js +1690 -0
- package/src/ui/dist/assets/{sigma-DPaACDrh.js → sigma-D5aJWR8J.js} +1 -1
- package/src/ui/dist/assets/{index-CDxNdQdz.js → square-check-big-DUK_mnkS.js} +2 -13
- package/src/ui/dist/assets/{trash-BvTgE5__.js → trash-ChU3SEE3.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-CgPeMOwP.js → useCliAccess-BrJBV3tY.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-xPhz7P5B.js → useFileDiffOverlay-C2OQaVWc.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-C3Un3YQr.js → wrap-text-C7Qqh-om.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-BgxLa0Ri.js → zoom-out-rtX0FKya.js} +1 -1
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/AutoFigurePlugin-BGxN8Umr.css +0 -3056
- package/src/ui/dist/assets/AutoFigurePlugin-C_wWw4AP.js +0 -8149
- package/src/ui/dist/assets/PdfViewerPlugin-BJXtIwj_.css +0 -260
- package/src/ui/dist/assets/Stepper-B0Dd8CxK.js +0 -158
- package/src/ui/dist/assets/bibtex-CKaefIN2.js +0 -189
- package/src/ui/dist/assets/file-utils-H2fjA46S.js +0 -109
- package/src/ui/dist/assets/message-square-BzjLiXir.js +0 -16
- package/src/ui/dist/assets/pdfjs-DU1YE8WO.js +0 -3
- package/src/ui/dist/assets/tooltip-C_mA6R0w.js +0 -108
|
@@ -257,7 +257,7 @@ class PromptBuilder:
|
|
|
257
257
|
f"- bound_external_connector_count: {surface_context['bound_external_connector_count']}",
|
|
258
258
|
"- surface_rule: treat web, TUI, and connector threads as one continuous quest, but adapt the amount of detail to the active surface.",
|
|
259
259
|
"- surface_reply_rule: use artifact.interact(...) for durable user-visible continuity; do not dump raw internal tool chatter into connector replies.",
|
|
260
|
-
"- connector_contract_rule:
|
|
260
|
+
"- connector_contract_rule: choose the active connector surface from the latest inbound external user turn when one exists; otherwise fall back to the bound external connector; keep purely local web/TUI turns on the local surface even if the quest is externally bound.",
|
|
261
261
|
]
|
|
262
262
|
|
|
263
263
|
if connector == "qq":
|
|
@@ -283,6 +283,21 @@ class PromptBuilder:
|
|
|
283
283
|
"- 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.",
|
|
284
284
|
]
|
|
285
285
|
)
|
|
286
|
+
elif connector == "weixin":
|
|
287
|
+
lines.extend(
|
|
288
|
+
[
|
|
289
|
+
"- weixin_surface_rule: Weixin is a concise operator surface, not a full artifact browser.",
|
|
290
|
+
"- weixin_default_mode: keep outbound replies concise, respectful, text-first, and progress-aware.",
|
|
291
|
+
"- weixin_length_rule: for ordinary Weixin progress replies, normally use only 2 to 4 short sentences, or 3 very short bullets at most.",
|
|
292
|
+
"- weixin_summary_first_rule: start with the user-facing conclusion, then the immediate meaning, then the next action.",
|
|
293
|
+
"- weixin_progress_shape_rule: make the current task, the main difficulty or latest real progress, and the next concrete next step explicit whenever possible.",
|
|
294
|
+
"- weixin_eta_rule: for important long-running phases, include a rough ETA or next check-in window when it is helpful and defensible.",
|
|
295
|
+
"- weixin_internal_detail_rule: do not proactively dump file inventories, path lists, retry counters, or monitor-log style telemetry unless the user asked for them or they explain a real risk.",
|
|
296
|
+
"- weixin_context_token_rule: reply continuity is managed by the runtime through `context_token`; do not invent your own reply token scheme.",
|
|
297
|
+
"- weixin_media_rule: when you want native Weixin image, video, or file delivery, request it through artifact.interact(..., attachments=[...]) with `connector_delivery={'weixin': {'media_kind': ...}}` instead of inventing connector-specific inline tag syntax.",
|
|
298
|
+
"- weixin_inbound_media_rule: inbound Weixin image, video, and file messages can arrive as quest-local attachments under `userfiles/weixin/...`; read those files when the user sent media.",
|
|
299
|
+
]
|
|
300
|
+
)
|
|
286
301
|
else:
|
|
287
302
|
lines.append("- connector_media_rule: if the active surface is not QQ, keep using the general artifact interaction discipline for milestone delivery.")
|
|
288
303
|
|
|
@@ -301,12 +316,23 @@ class PromptBuilder:
|
|
|
301
316
|
if str(parsed.get("connector") or "").strip().lower() == "local":
|
|
302
317
|
continue
|
|
303
318
|
bound_external.append(parsed)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
319
|
+
latest_connector = str((latest_user_parsed or {}).get("connector") or "").strip().lower()
|
|
320
|
+
if latest_connector and latest_connector != "local":
|
|
321
|
+
active = latest_user_parsed
|
|
322
|
+
origin = "latest_user_source"
|
|
323
|
+
elif latest_user is not None:
|
|
324
|
+
return {
|
|
325
|
+
"latest_user_source": latest_user_source,
|
|
326
|
+
"active_surface": "local",
|
|
327
|
+
"active_connector": "local",
|
|
328
|
+
"active_chat_type": "local",
|
|
329
|
+
"active_chat_id": "default",
|
|
330
|
+
"active_connector_origin": "latest_user_source_local",
|
|
331
|
+
"bound_external_connector_count": len(bound_external),
|
|
332
|
+
}
|
|
333
|
+
else:
|
|
334
|
+
active = bound_external[0] if bound_external else None
|
|
335
|
+
origin = "bound_external_binding" if active is not None else "none"
|
|
310
336
|
if active is None:
|
|
311
337
|
return {
|
|
312
338
|
"latest_user_source": latest_user_source,
|
|
@@ -672,18 +698,51 @@ class PromptBuilder:
|
|
|
672
698
|
startup_contract = snapshot.get("startup_contract")
|
|
673
699
|
if isinstance(startup_contract, dict):
|
|
674
700
|
value = str(startup_contract.get("custom_profile") or "").strip().lower()
|
|
675
|
-
if value in {"continue_existing_state", "revision_rebuttal", "freeform"}:
|
|
701
|
+
if value in {"continue_existing_state", "review_audit", "revision_rebuttal", "freeform"}:
|
|
676
702
|
return value
|
|
677
703
|
return "freeform"
|
|
678
704
|
|
|
705
|
+
@staticmethod
|
|
706
|
+
def _baseline_execution_policy(snapshot: dict) -> str:
|
|
707
|
+
startup_contract = snapshot.get("startup_contract")
|
|
708
|
+
if isinstance(startup_contract, dict):
|
|
709
|
+
value = str(startup_contract.get("baseline_execution_policy") or "").strip().lower()
|
|
710
|
+
if value in {"auto", "must_reproduce_or_verify", "reuse_existing_only", "skip_unless_blocking"}:
|
|
711
|
+
return value
|
|
712
|
+
return "auto"
|
|
713
|
+
|
|
714
|
+
@staticmethod
|
|
715
|
+
def _review_followup_policy(snapshot: dict) -> str:
|
|
716
|
+
startup_contract = snapshot.get("startup_contract")
|
|
717
|
+
if isinstance(startup_contract, dict):
|
|
718
|
+
value = str(startup_contract.get("review_followup_policy") or "").strip().lower()
|
|
719
|
+
if value in {"audit_only", "auto_execute_followups", "user_gated_followups"}:
|
|
720
|
+
return value
|
|
721
|
+
return "audit_only"
|
|
722
|
+
|
|
723
|
+
@staticmethod
|
|
724
|
+
def _manuscript_edit_mode(snapshot: dict) -> str:
|
|
725
|
+
startup_contract = snapshot.get("startup_contract")
|
|
726
|
+
if isinstance(startup_contract, dict):
|
|
727
|
+
value = str(startup_contract.get("manuscript_edit_mode") or "").strip().lower()
|
|
728
|
+
if value in {"none", "copy_ready_text", "latex_required"}:
|
|
729
|
+
return value
|
|
730
|
+
return "none"
|
|
731
|
+
|
|
679
732
|
def _research_delivery_policy_block(self, snapshot: dict) -> str:
|
|
680
733
|
need_research_paper = self._need_research_paper(snapshot)
|
|
681
734
|
launch_mode = self._launch_mode(snapshot)
|
|
682
735
|
custom_profile = self._custom_profile(snapshot)
|
|
736
|
+
baseline_execution_policy = self._baseline_execution_policy(snapshot)
|
|
737
|
+
review_followup_policy = self._review_followup_policy(snapshot)
|
|
738
|
+
manuscript_edit_mode = self._manuscript_edit_mode(snapshot)
|
|
683
739
|
lines = [
|
|
684
740
|
f"- need_research_paper: {need_research_paper}",
|
|
685
741
|
f"- launch_mode: {launch_mode}",
|
|
686
742
|
f"- custom_profile: {custom_profile if launch_mode == 'custom' else 'n/a'}",
|
|
743
|
+
f"- review_followup_policy: {review_followup_policy if custom_profile == 'review_audit' else 'n/a'}",
|
|
744
|
+
f"- baseline_execution_policy: {baseline_execution_policy if launch_mode == 'custom' else 'n/a'}",
|
|
745
|
+
f"- manuscript_edit_mode: {manuscript_edit_mode if custom_profile in {'review_audit', 'revision_rebuttal'} else 'n/a'}",
|
|
687
746
|
f"- delivery_mode: {'paper_required' if need_research_paper else 'algorithm_first'}",
|
|
688
747
|
"- idea_stage_rule: every accepted idea submission should normally create a new branch/worktree and a new user-visible research node.",
|
|
689
748
|
"- 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.",
|
|
@@ -698,7 +757,7 @@ class PromptBuilder:
|
|
|
698
757
|
lines.extend(
|
|
699
758
|
[
|
|
700
759
|
"- custom_launch_rule: do not force the canonical full-research path when the custom startup contract is narrower.",
|
|
701
|
-
"- custom_context_rule: treat `entry_state_summary`, `review_summary`, and `custom_brief` as active runtime context rather than decorative metadata.",
|
|
760
|
+
"- custom_context_rule: treat `entry_state_summary`, `review_summary`, `review_materials`, and `custom_brief` as active runtime context rather than decorative metadata.",
|
|
702
761
|
]
|
|
703
762
|
)
|
|
704
763
|
if custom_profile == "continue_existing_state":
|
|
@@ -708,6 +767,31 @@ class PromptBuilder:
|
|
|
708
767
|
"- reuse_first_rule: trust-rank and reconcile existing assets before deciding to rerun anything costly.",
|
|
709
768
|
]
|
|
710
769
|
)
|
|
770
|
+
elif custom_profile == "review_audit":
|
|
771
|
+
lines.extend(
|
|
772
|
+
[
|
|
773
|
+
"- review_entry_rule: treat the current draft/paper state as the active contract; open `review` before more writing or finalization.",
|
|
774
|
+
"- review_routing_rule: if that audit finds real evidence gaps, route to `analysis-campaign`, `baseline`, `scout`, or `write` instead of polishing blindly.",
|
|
775
|
+
]
|
|
776
|
+
)
|
|
777
|
+
if review_followup_policy == "auto_execute_followups":
|
|
778
|
+
lines.extend(
|
|
779
|
+
[
|
|
780
|
+
"- review_followup_rule: after the audit artifacts are durable, continue automatically into the required experiments, manuscript deltas, and review-closure work instead of stopping at the audit report.",
|
|
781
|
+
]
|
|
782
|
+
)
|
|
783
|
+
elif review_followup_policy == "user_gated_followups":
|
|
784
|
+
lines.extend(
|
|
785
|
+
[
|
|
786
|
+
"- review_followup_rule: after the audit artifacts are durable, package the next expensive follow-up step into one structured decision instead of continuing silently.",
|
|
787
|
+
]
|
|
788
|
+
)
|
|
789
|
+
else:
|
|
790
|
+
lines.extend(
|
|
791
|
+
[
|
|
792
|
+
"- review_followup_rule: stop after the durable audit artifacts and route recommendation unless the user later asks for execution follow-up.",
|
|
793
|
+
]
|
|
794
|
+
)
|
|
711
795
|
elif custom_profile == "revision_rebuttal":
|
|
712
796
|
lines.extend(
|
|
713
797
|
[
|
|
@@ -721,6 +805,36 @@ class PromptBuilder:
|
|
|
721
805
|
"- freeform_entry_rule: prefer the custom brief over the default stage order and open only the skills actually needed.",
|
|
722
806
|
]
|
|
723
807
|
)
|
|
808
|
+
if baseline_execution_policy == "must_reproduce_or_verify":
|
|
809
|
+
lines.extend(
|
|
810
|
+
[
|
|
811
|
+
"- baseline_execution_rule: before reviewer-linked follow-up work, explicitly verify or recover the rebuttal-critical baseline/comparator instead of assuming the stored evidence is still trustworthy.",
|
|
812
|
+
]
|
|
813
|
+
)
|
|
814
|
+
elif baseline_execution_policy == "reuse_existing_only":
|
|
815
|
+
lines.extend(
|
|
816
|
+
[
|
|
817
|
+
"- baseline_execution_rule: prefer the existing trusted baseline/results and do not rerun them unless you find concrete inconsistency, corruption, or missing-evidence problems.",
|
|
818
|
+
]
|
|
819
|
+
)
|
|
820
|
+
elif baseline_execution_policy == "skip_unless_blocking":
|
|
821
|
+
lines.extend(
|
|
822
|
+
[
|
|
823
|
+
"- baseline_execution_rule: do not spend time on baseline reruns by default; only open `baseline` if a named review/rebuttal issue truly depends on a missing comparator or unusable prior evidence.",
|
|
824
|
+
]
|
|
825
|
+
)
|
|
826
|
+
if manuscript_edit_mode == "latex_required":
|
|
827
|
+
lines.extend(
|
|
828
|
+
[
|
|
829
|
+
"- manuscript_edit_rule: when manuscript revision is needed, treat the provided LaTeX tree or `paper/latex/` as the authoritative writing surface; if LaTeX source is unavailable, produce LaTeX-ready replacement text and make that blocker explicit instead of pretending the manuscript was edited.",
|
|
830
|
+
]
|
|
831
|
+
)
|
|
832
|
+
elif manuscript_edit_mode == "copy_ready_text":
|
|
833
|
+
lines.extend(
|
|
834
|
+
[
|
|
835
|
+
"- manuscript_edit_rule: when manuscript revision is needed, provide section-level copy-ready replacement text and explicit deltas even if no LaTeX source is available.",
|
|
836
|
+
]
|
|
837
|
+
)
|
|
724
838
|
if need_research_paper:
|
|
725
839
|
lines.extend(
|
|
726
840
|
[
|
|
@@ -768,7 +882,10 @@ class PromptBuilder:
|
|
|
768
882
|
"- 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",
|
|
769
883
|
"- 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",
|
|
770
884
|
"- acknowledgment_protocol: after artifact.interact returns any human message, immediately send one substantive artifact.interact(...) follow-up; if the active connector runtime already emitted a transport-level receipt acknowledgement, do not send a redundant receipt-only message; if answerable, answer directly, otherwise state the short plan, nearest checkpoint, and that the current background subtask is paused",
|
|
771
|
-
"- progress_protocol: emit artifact.interact(kind='progress', reply_mode='threaded', ...) at real human-meaningful checkpoints; if no natural checkpoint appears during active user-relevant work, prefer a concise keepalive once work has crossed roughly
|
|
885
|
+
"- progress_protocol: emit artifact.interact(kind='progress', reply_mode='threaded', ...) at real human-meaningful checkpoints; if no natural checkpoint appears during active user-relevant work, prefer a concise keepalive once work has crossed roughly 6 tool calls with a human-meaningful delta, and do not drift beyond roughly 12 tool calls or about 8 minutes without a user-visible update",
|
|
886
|
+
"- stage_kickoff_protocol: after entering any stage or companion skill, send one user-visible artifact.interact progress update within the first 3 tool calls of substantial work",
|
|
887
|
+
"- read_plan_keepalive_protocol: if work is still mostly reading, searching, comparison, or planning, do not wait too long for a 'big result'; send one concise user-visible checkpoint after about 5 consecutive tool calls if the user would otherwise see silence",
|
|
888
|
+
"- subtask_boundary_protocol: send a user-visible update whenever the active subtask changes materially, especially across intake -> audit, audit -> experiment planning, experiment planning -> run launch, run result -> drafting, or drafting -> review/rebuttal",
|
|
772
889
|
"- smoke_then_detach_protocol: for baseline reproduction, main experiments, and analysis experiments, first validate the command path with a bounded smoke test; once the smoke test passes, launch the real long run with bash_exec(mode='detach', ...) and usually leave timeout_seconds unset rather than guessing a fake deadline",
|
|
773
890
|
"- progress_first_monitoring_protocol: when supervising a long-running bash_exec session, judge health by forward progress rather than by whether the final artifact has already appeared within a short window",
|
|
774
891
|
"- delta_monitoring_protocol: compare deltas such as new sample counters, new task counters, new saved files, new last_output_seq values, or changed last_progress payloads; if any of these move forward, treat the run as alive and keep observing",
|
|
@@ -789,7 +906,7 @@ class PromptBuilder:
|
|
|
789
906
|
"- 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",
|
|
790
907
|
"- 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",
|
|
791
908
|
"- compaction_protocol: ordinary artifact.interact progress updates should usually fit in 2 to 4 short sentences and should not read like a monitoring transcript or execution diary",
|
|
792
|
-
"- tool_call_keepalive_protocol: for active multi-step work outside long detached experiment waits, prefer sending one concise artifact.interact progress update after roughly
|
|
909
|
+
"- tool_call_keepalive_protocol: for active multi-step work outside long detached experiment waits, prefer sending one concise artifact.interact progress update after roughly 6 tool calls when there is already a human-meaningful delta, and do not exceed roughly 12 tool calls or about 8 minutes without a user-visible checkpoint",
|
|
793
910
|
"- human_progress_shape_protocol: ordinary progress updates should usually make three things explicit in human language: the current task, the main difficulty or latest real progress, and the concrete next measure you will take",
|
|
794
911
|
"- milestone_graduation_protocol: keep ordinary subtask completions concise; upgrade to a richer milestone report only when a stage-significant deliverable or route-changing checkpoint becomes durably true",
|
|
795
912
|
"- eta_visibility_protocol: for baseline reproduction, main experiments, analysis experiments, and other important long-running phases, progress updates should also make the expected time to the next meaningful result, next milestone, or next user-visible update explicit; use roughly 10 to 30 minutes as the normal update window, and if the ETA is unreliable, say that and give a realistic next check-in window instead",
|
|
@@ -1,196 +1 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from copy import deepcopy
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from .shared import slugify
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
QQ_PROFILE_ID_PREFIX = "qq-profile"
|
|
10
|
-
def default_qq_profile() -> dict[str, Any]:
|
|
11
|
-
return {
|
|
12
|
-
"profile_id": None,
|
|
13
|
-
"enabled": True,
|
|
14
|
-
"app_id": None,
|
|
15
|
-
"app_secret": None,
|
|
16
|
-
"app_secret_env": None,
|
|
17
|
-
"bot_name": "DeepScientist",
|
|
18
|
-
"main_chat_id": None,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _as_text(value: Any) -> str | None:
|
|
23
|
-
text = str(value or "").strip()
|
|
24
|
-
return text or None
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _normalize_secret_pair(payload: dict[str, Any], direct_key: str, env_key: str) -> None:
|
|
28
|
-
direct = _as_text(payload.get(direct_key))
|
|
29
|
-
env_name = _as_text(payload.get(env_key))
|
|
30
|
-
payload[direct_key] = direct
|
|
31
|
-
payload[env_key] = None if direct else env_name
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _profile_id_seed(*, profile_id: Any, app_id: Any, bot_name: Any, index: int) -> str:
|
|
35
|
-
explicit = _as_text(profile_id)
|
|
36
|
-
if explicit:
|
|
37
|
-
return explicit
|
|
38
|
-
app_text = _as_text(app_id)
|
|
39
|
-
if app_text:
|
|
40
|
-
return f"qq-{app_text}"
|
|
41
|
-
bot_text = slugify(str(bot_name or "").strip(), default="")
|
|
42
|
-
if bot_text:
|
|
43
|
-
return f"{QQ_PROFILE_ID_PREFIX}-{bot_text}"
|
|
44
|
-
return f"{QQ_PROFILE_ID_PREFIX}-{index:03d}"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _unique_profile_id(seed: str, *, used: set[str]) -> str:
|
|
48
|
-
base = slugify(seed, default=QQ_PROFILE_ID_PREFIX)
|
|
49
|
-
candidate = base
|
|
50
|
-
suffix = 2
|
|
51
|
-
while candidate in used:
|
|
52
|
-
candidate = f"{base}-{suffix}"
|
|
53
|
-
suffix += 1
|
|
54
|
-
used.add(candidate)
|
|
55
|
-
return candidate
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def list_qq_profiles(config: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
59
|
-
normalized = normalize_qq_connector_config(config)
|
|
60
|
-
profiles = normalized.get("profiles")
|
|
61
|
-
return [dict(item) for item in profiles] if isinstance(profiles, list) else []
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def find_qq_profile(
|
|
65
|
-
config: dict[str, Any] | None,
|
|
66
|
-
*,
|
|
67
|
-
profile_id: str | None = None,
|
|
68
|
-
app_id: str | None = None,
|
|
69
|
-
) -> dict[str, Any] | None:
|
|
70
|
-
normalized_profile_id = _as_text(profile_id)
|
|
71
|
-
normalized_app_id = _as_text(app_id)
|
|
72
|
-
for profile in list_qq_profiles(config):
|
|
73
|
-
if normalized_profile_id and str(profile.get("profile_id") or "").strip() == normalized_profile_id:
|
|
74
|
-
return profile
|
|
75
|
-
if normalized_app_id and str(profile.get("app_id") or "").strip() == normalized_app_id:
|
|
76
|
-
return profile
|
|
77
|
-
return None
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def merge_qq_profile_config(shared_config: dict[str, Any] | None, profile: dict[str, Any]) -> dict[str, Any]:
|
|
81
|
-
normalized = normalize_qq_connector_config(shared_config)
|
|
82
|
-
merged = deepcopy(normalized)
|
|
83
|
-
merged.pop("profiles", None)
|
|
84
|
-
app_secret = _as_text(profile.get("app_secret"))
|
|
85
|
-
app_secret_env = _as_text(profile.get("app_secret_env"))
|
|
86
|
-
merged.update(
|
|
87
|
-
{
|
|
88
|
-
"profile_id": str(profile.get("profile_id") or "").strip() or None,
|
|
89
|
-
"app_id": _as_text(profile.get("app_id")),
|
|
90
|
-
"app_secret": app_secret,
|
|
91
|
-
"app_secret_env": None if app_secret else app_secret_env,
|
|
92
|
-
"bot_name": _as_text(profile.get("bot_name")) or str(normalized.get("bot_name") or "DeepScientist"),
|
|
93
|
-
"main_chat_id": _as_text(profile.get("main_chat_id")),
|
|
94
|
-
"enabled": bool(normalized.get("enabled", False)) and bool(profile.get("enabled", True)),
|
|
95
|
-
"transport": "gateway_direct",
|
|
96
|
-
}
|
|
97
|
-
)
|
|
98
|
-
return merged
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def qq_profile_label(profile: dict[str, Any] | None) -> str:
|
|
102
|
-
if not isinstance(profile, dict):
|
|
103
|
-
return "QQ"
|
|
104
|
-
bot_name = _as_text(profile.get("bot_name"))
|
|
105
|
-
app_id = _as_text(profile.get("app_id"))
|
|
106
|
-
if bot_name and app_id:
|
|
107
|
-
return f"{bot_name} · {app_id}"
|
|
108
|
-
if bot_name:
|
|
109
|
-
return bot_name
|
|
110
|
-
if app_id:
|
|
111
|
-
return f"QQ · {app_id}"
|
|
112
|
-
return "QQ"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def normalize_qq_connector_config(config: dict[str, Any] | None) -> dict[str, Any]:
|
|
116
|
-
payload = deepcopy(config or {})
|
|
117
|
-
shared_defaults = {
|
|
118
|
-
"enabled": False,
|
|
119
|
-
"transport": "gateway_direct",
|
|
120
|
-
"app_id": None,
|
|
121
|
-
"app_secret": None,
|
|
122
|
-
"app_secret_env": None,
|
|
123
|
-
"bot_name": "DeepScientist",
|
|
124
|
-
"command_prefix": "/",
|
|
125
|
-
"main_chat_id": None,
|
|
126
|
-
"require_at_in_groups": True,
|
|
127
|
-
"auto_bind_dm_to_active_quest": True,
|
|
128
|
-
"gateway_restart_on_config_change": True,
|
|
129
|
-
"auto_send_main_experiment_png": True,
|
|
130
|
-
"auto_send_analysis_summary_png": True,
|
|
131
|
-
"auto_send_slice_png": True,
|
|
132
|
-
"auto_send_paper_pdf": True,
|
|
133
|
-
"enable_markdown_send": False,
|
|
134
|
-
"enable_file_upload_experimental": False,
|
|
135
|
-
"profiles": [],
|
|
136
|
-
}
|
|
137
|
-
shared = {**shared_defaults, **payload}
|
|
138
|
-
shared["transport"] = "gateway_direct"
|
|
139
|
-
shared["command_prefix"] = _as_text(shared.get("command_prefix")) or "/"
|
|
140
|
-
shared["bot_name"] = _as_text(shared.get("bot_name")) or "DeepScientist"
|
|
141
|
-
_normalize_secret_pair(shared, "app_secret", "app_secret_env")
|
|
142
|
-
|
|
143
|
-
raw_profiles = payload.get("profiles")
|
|
144
|
-
items = list(raw_profiles) if isinstance(raw_profiles, list) else []
|
|
145
|
-
legacy_profile_seed = {
|
|
146
|
-
"app_id": payload.get("app_id"),
|
|
147
|
-
"app_secret": payload.get("app_secret"),
|
|
148
|
-
"app_secret_env": payload.get("app_secret_env"),
|
|
149
|
-
"bot_name": payload.get("bot_name"),
|
|
150
|
-
"main_chat_id": payload.get("main_chat_id"),
|
|
151
|
-
}
|
|
152
|
-
if not items:
|
|
153
|
-
has_direct_profile_seed = any(_as_text(legacy_profile_seed.get(key)) for key in ("app_id", "app_secret", "main_chat_id"))
|
|
154
|
-
has_env_profile_seed = bool(payload.get("enabled")) and bool(_as_text(legacy_profile_seed.get("app_secret_env")))
|
|
155
|
-
if has_direct_profile_seed or has_env_profile_seed:
|
|
156
|
-
items = [legacy_profile_seed]
|
|
157
|
-
|
|
158
|
-
profiles: list[dict[str, Any]] = []
|
|
159
|
-
used_ids: set[str] = set()
|
|
160
|
-
for index, raw in enumerate(items, start=1):
|
|
161
|
-
if not isinstance(raw, dict):
|
|
162
|
-
continue
|
|
163
|
-
current = {**default_qq_profile(), **raw}
|
|
164
|
-
current["enabled"] = bool(current.get("enabled", True))
|
|
165
|
-
current["app_id"] = _as_text(current.get("app_id"))
|
|
166
|
-
current["app_secret"] = _as_text(current.get("app_secret"))
|
|
167
|
-
current["app_secret_env"] = _as_text(current.get("app_secret_env")) or shared["app_secret_env"]
|
|
168
|
-
_normalize_secret_pair(current, "app_secret", "app_secret_env")
|
|
169
|
-
current["bot_name"] = _as_text(current.get("bot_name")) or shared["bot_name"]
|
|
170
|
-
current["main_chat_id"] = _as_text(current.get("main_chat_id"))
|
|
171
|
-
current["profile_id"] = _unique_profile_id(
|
|
172
|
-
_profile_id_seed(
|
|
173
|
-
profile_id=current.get("profile_id"),
|
|
174
|
-
app_id=current.get("app_id"),
|
|
175
|
-
bot_name=current.get("bot_name"),
|
|
176
|
-
index=index,
|
|
177
|
-
),
|
|
178
|
-
used=used_ids,
|
|
179
|
-
)
|
|
180
|
-
profiles.append(current)
|
|
181
|
-
|
|
182
|
-
shared["profiles"] = profiles
|
|
183
|
-
if len(profiles) == 1:
|
|
184
|
-
mirror = profiles[0]
|
|
185
|
-
shared["app_id"] = mirror.get("app_id")
|
|
186
|
-
shared["app_secret"] = mirror.get("app_secret")
|
|
187
|
-
shared["app_secret_env"] = mirror.get("app_secret_env")
|
|
188
|
-
shared["bot_name"] = mirror.get("bot_name")
|
|
189
|
-
shared["main_chat_id"] = mirror.get("main_chat_id")
|
|
190
|
-
else:
|
|
191
|
-
shared["app_id"] = None
|
|
192
|
-
shared["app_secret"] = None
|
|
193
|
-
shared["app_secret_env"] = None
|
|
194
|
-
shared["main_chat_id"] = None
|
|
195
|
-
|
|
196
|
-
return shared
|
|
1
|
+
from .connector.qq_profiles import * # noqa: F401,F403
|
|
@@ -6,6 +6,8 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from ..shared import ensure_dir, read_json, sha256_text, utc_now, write_json
|
|
8
8
|
|
|
9
|
+
NODE_TRACE_SCHEMA_VERSION = 2
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def _format_state_label(value: str | None) -> str:
|
|
11
13
|
normalized = str(value or "").strip().replace("_", " ").replace("-", " ")
|
|
@@ -36,6 +38,23 @@ def _normalize_branch_name(value: object, *, fallback: str) -> str:
|
|
|
36
38
|
return text or fallback
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
def _infer_stage_from_branch_name(value: object) -> str | None:
|
|
42
|
+
normalized = str(value or "").strip().lower()
|
|
43
|
+
if not normalized:
|
|
44
|
+
return None
|
|
45
|
+
if normalized.startswith("analysis/"):
|
|
46
|
+
return "analysis"
|
|
47
|
+
if normalized.startswith("run/"):
|
|
48
|
+
return "experiment"
|
|
49
|
+
if normalized.startswith("idea/"):
|
|
50
|
+
return "idea"
|
|
51
|
+
if normalized.startswith("paper/") or normalized.startswith("write/"):
|
|
52
|
+
return "writing"
|
|
53
|
+
if normalized.startswith("baseline/"):
|
|
54
|
+
return "baseline"
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
39
58
|
def _infer_stage_from_skill(skill_id: object) -> str | None:
|
|
40
59
|
normalized = str(skill_id or "").strip().lower()
|
|
41
60
|
if not normalized:
|
|
@@ -240,6 +259,7 @@ def _resolve_entry_context(
|
|
|
240
259
|
or _infer_stage_from_artifact((((run_artifact or {}).get("record") or {}) if run_artifact else {}))
|
|
241
260
|
or (run_context or {}).get("stage_key")
|
|
242
261
|
or _infer_stage_from_event_type(raw_event_type)
|
|
262
|
+
or _infer_stage_from_branch_name(branch_name)
|
|
243
263
|
or "general"
|
|
244
264
|
)
|
|
245
265
|
return {
|
|
@@ -440,6 +460,7 @@ class QuestNodeTraceManager:
|
|
|
440
460
|
source_signature = sha256_text(
|
|
441
461
|
json.dumps(
|
|
442
462
|
{
|
|
463
|
+
"schema_version": NODE_TRACE_SCHEMA_VERSION,
|
|
443
464
|
"entries": workflow.get("entries") or [],
|
|
444
465
|
"branch": (snapshot or {}).get("branch"),
|
|
445
466
|
},
|
|
@@ -451,6 +472,7 @@ class QuestNodeTraceManager:
|
|
|
451
472
|
if (
|
|
452
473
|
isinstance(existing, dict)
|
|
453
474
|
and existing.get("quest_id") == quest_id
|
|
475
|
+
and existing.get("schema_version") == NODE_TRACE_SCHEMA_VERSION
|
|
454
476
|
and existing.get("source_signature") == source_signature
|
|
455
477
|
and isinstance(existing.get("items"), list)
|
|
456
478
|
):
|
|
@@ -545,6 +567,7 @@ class QuestNodeTraceManager:
|
|
|
545
567
|
|
|
546
568
|
payload = {
|
|
547
569
|
"quest_id": quest_id,
|
|
570
|
+
"schema_version": NODE_TRACE_SCHEMA_VERSION,
|
|
548
571
|
"generated_at": utc_now(),
|
|
549
572
|
"source_signature": source_signature,
|
|
550
573
|
"items": items,
|