@researai/deepscientist 1.5.7 → 1.5.9
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/LICENSE +186 -21
- package/README.md +8 -4
- package/bin/ds.js +224 -9
- package/docs/en/00_QUICK_START.md +2 -2
- package/docs/en/07_MEMORY_AND_MCP.md +40 -3
- package/docs/en/99_ACKNOWLEDGEMENTS.md +1 -0
- package/docs/zh/00_QUICK_START.md +2 -2
- package/docs/zh/07_MEMORY_AND_MCP.md +40 -3
- package/docs/zh/99_ACKNOWLEDGEMENTS.md +1 -0
- package/install.sh +34 -0
- package/package.json +2 -2
- package/pyproject.toml +2 -2
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +1 -0
- package/src/deepscientist/artifact/metrics.py +814 -83
- package/src/deepscientist/artifact/schemas.py +1 -0
- package/src/deepscientist/artifact/service.py +2001 -229
- package/src/deepscientist/bash_exec/monitor.py +1 -1
- package/src/deepscientist/bash_exec/service.py +17 -9
- package/src/deepscientist/channels/qq.py +17 -0
- package/src/deepscientist/channels/relay.py +16 -0
- package/src/deepscientist/config/models.py +6 -0
- package/src/deepscientist/config/service.py +70 -2
- package/src/deepscientist/daemon/api/handlers.py +414 -14
- package/src/deepscientist/daemon/api/router.py +4 -0
- package/src/deepscientist/daemon/app.py +292 -21
- package/src/deepscientist/gitops/diff.py +6 -10
- package/src/deepscientist/mcp/server.py +191 -40
- package/src/deepscientist/prompts/builder.py +65 -19
- package/src/deepscientist/quest/node_traces.py +129 -2
- package/src/deepscientist/quest/service.py +140 -34
- package/src/deepscientist/quest/stage_views.py +175 -33
- package/src/deepscientist/registries/baseline.py +56 -4
- package/src/deepscientist/runners/codex.py +1 -1
- package/src/prompts/connectors/qq.md +1 -1
- package/src/prompts/contracts/shared_interaction.md +14 -0
- package/src/prompts/system.md +113 -32
- package/src/skills/analysis-campaign/SKILL.md +10 -14
- package/src/skills/baseline/SKILL.md +51 -38
- package/src/skills/baseline/references/baseline-plan-template.md +2 -0
- package/src/skills/decision/SKILL.md +12 -8
- package/src/skills/experiment/SKILL.md +28 -16
- package/src/skills/experiment/references/main-experiment-plan-template.md +2 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +3 -8
- package/src/skills/idea/SKILL.md +18 -8
- package/src/skills/idea/references/literature-survey-template.md +24 -0
- package/src/skills/idea/references/related-work-playbook.md +4 -0
- package/src/skills/idea/references/selection-gate.md +9 -0
- package/src/skills/intake-audit/SKILL.md +2 -8
- package/src/skills/rebuttal/SKILL.md +2 -8
- package/src/skills/review/SKILL.md +2 -8
- package/src/skills/scout/SKILL.md +2 -8
- package/src/skills/write/SKILL.md +53 -17
- package/src/skills/write/templates/DEEPSCIENTIST_NOTES.md +21 -0
- package/src/skills/write/templates/README.md +408 -0
- package/src/skills/write/templates/UPSTREAM_LICENSE.txt +21 -0
- package/src/skills/write/templates/aaai2026/README.md +534 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-supp.tex +144 -0
- package/src/skills/write/templates/aaai2026/aaai2026-unified-template.tex +952 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bib +111 -0
- package/src/skills/write/templates/aaai2026/aaai2026.bst +1493 -0
- package/src/skills/write/templates/aaai2026/aaai2026.sty +315 -0
- package/src/skills/write/templates/acl/README.md +50 -0
- package/src/skills/write/templates/acl/acl.sty +312 -0
- package/src/skills/write/templates/acl/acl_latex.tex +377 -0
- package/src/skills/write/templates/acl/acl_lualatex.tex +101 -0
- package/src/skills/write/templates/acl/acl_natbib.bst +1940 -0
- package/src/skills/write/templates/acl/anthology.bib.txt +26 -0
- package/src/skills/write/templates/acl/custom.bib +70 -0
- package/src/skills/write/templates/acl/formatting.md +326 -0
- package/src/skills/write/templates/asplos2027/main.tex +459 -0
- package/src/skills/write/templates/asplos2027/references.bib +135 -0
- package/src/skills/write/templates/colm2025/README.md +3 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bib +11 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.bst +1440 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.sty +218 -0
- package/src/skills/write/templates/colm2025/colm2025_conference.tex +305 -0
- package/src/skills/write/templates/colm2025/fancyhdr.sty +485 -0
- package/src/skills/write/templates/colm2025/math_commands.tex +508 -0
- package/src/skills/write/templates/colm2025/natbib.sty +1246 -0
- package/src/skills/write/templates/iclr2026/fancyhdr.sty +485 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bib +24 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.bst +1440 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.sty +246 -0
- package/src/skills/write/templates/iclr2026/iclr2026_conference.tex +414 -0
- package/src/skills/write/templates/iclr2026/math_commands.tex +508 -0
- package/src/skills/write/templates/iclr2026/natbib.sty +1246 -0
- package/src/skills/write/templates/icml2026/algorithm.sty +79 -0
- package/src/skills/write/templates/icml2026/algorithmic.sty +201 -0
- package/src/skills/write/templates/icml2026/example_paper.bib +75 -0
- package/src/skills/write/templates/icml2026/example_paper.tex +662 -0
- package/src/skills/write/templates/icml2026/fancyhdr.sty +864 -0
- package/src/skills/write/templates/icml2026/icml2026.bst +1443 -0
- package/src/skills/write/templates/icml2026/icml2026.sty +767 -0
- package/src/skills/write/templates/neurips2025/Makefile +36 -0
- package/src/skills/write/templates/neurips2025/extra_pkgs.tex +53 -0
- package/src/skills/write/templates/neurips2025/main.tex +38 -0
- package/src/skills/write/templates/neurips2025/neurips.sty +382 -0
- package/src/skills/write/templates/nsdi2027/main.tex +426 -0
- package/src/skills/write/templates/nsdi2027/references.bib +151 -0
- package/src/skills/write/templates/nsdi2027/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/osdi2026/main.tex +429 -0
- package/src/skills/write/templates/osdi2026/references.bib +150 -0
- package/src/skills/write/templates/osdi2026/usenix-2020-09.sty +83 -0
- package/src/skills/write/templates/sosp2026/main.tex +532 -0
- package/src/skills/write/templates/sosp2026/references.bib +148 -0
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/{AiManusChatView-BS3V4ZOk.js → AiManusChatView-BKZ103sn.js} +110 -14
- package/src/ui/dist/assets/{AnalysisPlugin-DLPXQsmr.js → AnalysisPlugin-mTTzGAlK.js} +1 -1
- package/src/ui/dist/assets/{AutoFigurePlugin-C-Fr9knQ.js → AutoFigurePlugin-C_wWw4AP.js} +5 -5
- package/src/ui/dist/assets/{CliPlugin-Dd8AHzFg.js → CliPlugin-BH58n3GY.js} +9 -9
- package/src/ui/dist/assets/{CodeEditorPlugin-Dg-RepTl.js → CodeEditorPlugin-BKGRUH7e.js} +8 -8
- package/src/ui/dist/assets/{CodeViewerPlugin-D2J_3nyt.js → CodeViewerPlugin-BMADwFWJ.js} +5 -5
- package/src/ui/dist/assets/{DocViewerPlugin-ChRLLKNb.js → DocViewerPlugin-ZOnTIHLN.js} +3 -3
- package/src/ui/dist/assets/{GitDiffViewerPlugin-DgHfcved.js → GitDiffViewerPlugin-CQ7h1Djm.js} +830 -86
- package/src/ui/dist/assets/{ImageViewerPlugin-C89GZMBy.js → ImageViewerPlugin-GVS5MsnC.js} +5 -5
- package/src/ui/dist/assets/{LabCopilotPanel-BUfIwUcb.js → LabCopilotPanel-BZNv1JML.js} +10 -10
- package/src/ui/dist/assets/{LabPlugin-zvUmQUMq.js → LabPlugin-TWcJsdQA.js} +1 -1
- package/src/ui/dist/assets/{LatexPlugin-C1SSNuWp.js → LatexPlugin-DIjHiR2x.js} +7 -7
- package/src/ui/dist/assets/{MarkdownViewerPlugin-D2Mf5tU5.js → MarkdownViewerPlugin-D3ooGAH0.js} +4 -4
- package/src/ui/dist/assets/{MarketplacePlugin-CF4LgiS2.js → MarketplacePlugin-DfVfE9hN.js} +3 -3
- package/src/ui/dist/assets/{NotebookEditor-BM7Bgwlv.js → NotebookEditor-DDl0_Mc0.js} +1 -1
- package/src/ui/dist/assets/{index-Be0NAmh8.js → NotebookEditor-s8JhzuX1.js} +12 -155
- package/src/ui/dist/assets/{PdfLoader-Bc5qfD-Z.js → PdfLoader-C2Sf6SJM.js} +1 -1
- package/src/ui/dist/assets/{PdfMarkdownPlugin-sh1-IRcp.js → PdfMarkdownPlugin-CXFLoIsa.js} +3 -3
- package/src/ui/dist/assets/{PdfViewerPlugin-C_a7CpWG.js → PdfViewerPlugin-BYTmz2fK.js} +10 -10
- package/src/ui/dist/assets/{SearchPlugin-L4z3HcLf.js → SearchPlugin-CjWBI1O9.js} +1 -1
- package/src/ui/dist/assets/{Stepper-Dk4aQ3fN.js → Stepper-B0Dd8CxK.js} +1 -1
- package/src/ui/dist/assets/{TextViewerPlugin-BsNtlKVo.js → TextViewerPlugin-DdOBU3-S.js} +4 -4
- package/src/ui/dist/assets/{VNCViewer-BpeDcZ5_.js → VNCViewer-B8HGgLwQ.js} +9 -9
- package/src/ui/dist/assets/{bibtex-C4QI-bbj.js → bibtex-CKaefIN2.js} +1 -1
- package/src/ui/dist/assets/{code-DuMINRsg.js → code-BWAY76JP.js} +1 -1
- package/src/ui/dist/assets/{file-content-C3N-432K.js → file-content-C1NwU5oQ.js} +1 -1
- package/src/ui/dist/assets/{file-diff-panel-CffQ4ZMg.js → file-diff-panel-CywslwB9.js} +1 -1
- package/src/ui/dist/assets/{file-socket-CRH59PCO.js → file-socket-B4kzuOBQ.js} +1 -1
- package/src/ui/dist/assets/{file-utils-vYGtW2mI.js → file-utils-H2fjA46S.js} +1 -1
- package/src/ui/dist/assets/{image-DBVGaooo.js → image-D-NZM-6P.js} +1 -1
- package/src/ui/dist/assets/{index-B1P6hQRJ.js → index-7Chr1g9c.js} +3734 -1862
- package/src/ui/dist/assets/{index-DjSFDmgB.js → index-BdM1Gqfr.js} +2 -2
- package/src/ui/dist/assets/{index-BpjYH9Vg.js → index-CDxNdQdz.js} +1 -1
- package/src/ui/dist/assets/{index-Do9N28uB.css → index-DGIYDuTv.css} +163 -34
- package/src/ui/dist/assets/index-DHZJ_0TI.js +159 -0
- package/src/ui/dist/assets/{message-square-BsPDBhiY.js → message-square-BzjLiXir.js} +1 -1
- package/src/ui/dist/assets/{monaco-BTkdPojV.js → monaco-Cb2uKKe6.js} +1 -1
- package/src/ui/dist/assets/{popover-cWjCk-vc.js → popover-Bg72DGgT.js} +1 -1
- package/src/ui/dist/assets/{project-sync-CXn530xb.js → project-sync-Ce_0BglY.js} +1 -1
- package/src/ui/dist/assets/{sigma-04Jr12jg.js → sigma-DPaACDrh.js} +1 -1
- package/src/ui/dist/assets/{tooltip-BdVDl0G5.js → tooltip-C_mA6R0w.js} +1 -1
- package/src/ui/dist/assets/{trash-CB_GlQyC.js → trash-BvTgE5__.js} +1 -1
- package/src/ui/dist/assets/{useCliAccess-BL932NwS.js → useCliAccess-CgPeMOwP.js} +1 -1
- package/src/ui/dist/assets/{useFileDiffOverlay-B2WK7Tvq.js → useFileDiffOverlay-xPhz7P5B.js} +1 -1
- package/src/ui/dist/assets/{wrap-text-YC68g12z.js → wrap-text-C3Un3YQr.js} +1 -1
- package/src/ui/dist/assets/{zoom-out-C0RJvFiJ.js → zoom-out-BgxLa0Ri.js} +1 -1
- package/src/ui/dist/index.html +5 -2
- /package/src/ui/dist/assets/{index-CccQYZjX.css → NotebookEditor-CccQYZjX.css} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from pathlib import Path
|
|
4
|
+
from pathlib import Path, PurePosixPath
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from ..memory.frontmatter import load_markdown_document
|
|
@@ -117,7 +117,7 @@ class QuestStageViewBuilder:
|
|
|
117
117
|
self.snapshot = dict(snapshot or {})
|
|
118
118
|
self.selection = dict(selection or {})
|
|
119
119
|
self.trace = dict(trace or {})
|
|
120
|
-
self.workspace_root =
|
|
120
|
+
self.workspace_root = self._resolve_workspace_root()
|
|
121
121
|
self.branch_name = (
|
|
122
122
|
str(
|
|
123
123
|
self.selection.get("branch_name")
|
|
@@ -135,8 +135,27 @@ class QuestStageViewBuilder:
|
|
|
135
135
|
self.stage_status = str(self.selection.get("status") or self.trace.get("status") or "").strip() or None
|
|
136
136
|
self.artifacts = sorted(list(self.quest_service._collect_artifacts(quest_root)), key=_artifact_sort_key)
|
|
137
137
|
|
|
138
|
+
def _resolve_workspace_root(self) -> Path:
|
|
139
|
+
for raw in (
|
|
140
|
+
self.selection.get("worktree_rel_path"),
|
|
141
|
+
self.trace.get("worktree_rel_path"),
|
|
142
|
+
self.selection.get("worktree_root"),
|
|
143
|
+
self.trace.get("worktree_root"),
|
|
144
|
+
self.snapshot.get("active_workspace_root"),
|
|
145
|
+
):
|
|
146
|
+
text = str(raw or "").strip()
|
|
147
|
+
if not text:
|
|
148
|
+
continue
|
|
149
|
+
candidate = Path(text)
|
|
150
|
+
if not candidate.is_absolute():
|
|
151
|
+
candidate = (self.quest_root / text).resolve()
|
|
152
|
+
if candidate.exists():
|
|
153
|
+
return candidate
|
|
154
|
+
return self.quest_root
|
|
155
|
+
|
|
138
156
|
def build(self) -> dict[str, Any]:
|
|
139
|
-
|
|
157
|
+
selection_type = str(self.selection.get("selection_type") or "").strip()
|
|
158
|
+
if selection_type == "branch_node" and self.stage_key not in {"experiment", "analysis", "paper"}:
|
|
140
159
|
return self._build_branch()
|
|
141
160
|
if self.stage_key == "baseline":
|
|
142
161
|
return self._build_baseline()
|
|
@@ -208,7 +227,7 @@ class QuestStageViewBuilder:
|
|
|
208
227
|
"idea": self._idea_scope_paths(),
|
|
209
228
|
"experiment": self._experiment_scope_paths(None),
|
|
210
229
|
"analysis": self._analysis_scope_paths(None),
|
|
211
|
-
"paper":
|
|
230
|
+
"paper": self._paper_scope_paths(),
|
|
212
231
|
}
|
|
213
232
|
return defaults.get(self.stage_key, [])
|
|
214
233
|
|
|
@@ -314,6 +333,52 @@ class QuestStageViewBuilder:
|
|
|
314
333
|
text = str(body or "").strip()
|
|
315
334
|
return text or None
|
|
316
335
|
|
|
336
|
+
def _recent_trace_actions(self, *, limit: int = 6) -> list[dict[str, Any]]:
|
|
337
|
+
raw_actions = self.trace.get("actions") if isinstance(self.trace, dict) else []
|
|
338
|
+
if not isinstance(raw_actions, list) or not raw_actions:
|
|
339
|
+
return []
|
|
340
|
+
normalized: list[dict[str, Any]] = []
|
|
341
|
+
for item in raw_actions[-limit:]:
|
|
342
|
+
if not isinstance(item, dict):
|
|
343
|
+
continue
|
|
344
|
+
summary = _compact(item.get("summary") or item.get("output") or item.get("args"), limit=1000)
|
|
345
|
+
normalized.append(
|
|
346
|
+
{
|
|
347
|
+
"action_id": item.get("action_id"),
|
|
348
|
+
"title": item.get("title") or item.get("tool_name") or item.get("raw_event_type") or item.get("kind"),
|
|
349
|
+
"summary": summary,
|
|
350
|
+
"status": item.get("status"),
|
|
351
|
+
"created_at": item.get("created_at"),
|
|
352
|
+
"tool_name": item.get("tool_name"),
|
|
353
|
+
"artifact_kind": item.get("artifact_kind"),
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
return normalized
|
|
357
|
+
|
|
358
|
+
def _trace_summary(self) -> str | None:
|
|
359
|
+
if not isinstance(self.trace, dict):
|
|
360
|
+
return None
|
|
361
|
+
return _compact(
|
|
362
|
+
self.trace.get("summary")
|
|
363
|
+
or self.selection.get("summary")
|
|
364
|
+
or self.trace.get("title"),
|
|
365
|
+
limit=600,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def _trace_markdown(self, *, limit: int = 5) -> str | None:
|
|
369
|
+
items = self._recent_trace_actions(limit=limit)
|
|
370
|
+
if not items:
|
|
371
|
+
return None
|
|
372
|
+
lines: list[str] = []
|
|
373
|
+
for item in items:
|
|
374
|
+
title = str(item.get("title") or "Trace").strip() or "Trace"
|
|
375
|
+
summary = str(item.get("summary") or "").strip()
|
|
376
|
+
if summary:
|
|
377
|
+
lines.append(f"- **{title}**: {summary}")
|
|
378
|
+
else:
|
|
379
|
+
lines.append(f"- **{title}**")
|
|
380
|
+
return "\n".join(lines) if lines else None
|
|
381
|
+
|
|
317
382
|
def _file_entry(
|
|
318
383
|
self,
|
|
319
384
|
raw_path: object,
|
|
@@ -356,9 +421,29 @@ class QuestStageViewBuilder:
|
|
|
356
421
|
}
|
|
357
422
|
|
|
358
423
|
def _paper_bundle_manifest(self) -> dict[str, Any]:
|
|
359
|
-
payload = read_json(self.
|
|
424
|
+
payload = read_json(self._paper_root() / "paper_bundle_manifest.json", {})
|
|
360
425
|
return payload if isinstance(payload, dict) else {}
|
|
361
426
|
|
|
427
|
+
def _paper_root(self) -> Path:
|
|
428
|
+
candidates = [self.workspace_root / "paper", self.quest_root / "paper"]
|
|
429
|
+
for candidate in candidates:
|
|
430
|
+
if candidate.exists():
|
|
431
|
+
return candidate
|
|
432
|
+
return candidates[0]
|
|
433
|
+
|
|
434
|
+
def _paper_scope_paths(self) -> list[str]:
|
|
435
|
+
try:
|
|
436
|
+
return [self._paper_root().relative_to(self.quest_root).as_posix()]
|
|
437
|
+
except ValueError:
|
|
438
|
+
return ["paper"]
|
|
439
|
+
|
|
440
|
+
def _open_source_root(self) -> Path:
|
|
441
|
+
candidates = [self.workspace_root / "release" / "open_source", self.quest_root / "release" / "open_source"]
|
|
442
|
+
for candidate in candidates:
|
|
443
|
+
if candidate.exists():
|
|
444
|
+
return candidate
|
|
445
|
+
return candidates[0]
|
|
446
|
+
|
|
362
447
|
def _paper_relative_path(self, raw_path: object) -> str | None:
|
|
363
448
|
resolved = self._path_in_quest(raw_path)
|
|
364
449
|
if resolved is None:
|
|
@@ -370,9 +455,10 @@ class QuestStageViewBuilder:
|
|
|
370
455
|
preferred = self._paper_relative_path(bundle_manifest.get("latex_root_path"))
|
|
371
456
|
if preferred:
|
|
372
457
|
return preferred
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
458
|
+
paper_root = self._paper_root()
|
|
459
|
+
for candidate in (paper_root / "latex", paper_root / "tex"):
|
|
460
|
+
if candidate.exists():
|
|
461
|
+
return candidate.relative_to(self.quest_root).as_posix()
|
|
376
462
|
return None
|
|
377
463
|
|
|
378
464
|
def _paper_main_tex(self, latex_root_rel: str | None) -> str | None:
|
|
@@ -401,7 +487,7 @@ class QuestStageViewBuilder:
|
|
|
401
487
|
guessed = str(PurePosixPath(main_tex_rel).with_suffix(".pdf"))
|
|
402
488
|
if (self.quest_root / guessed).exists():
|
|
403
489
|
candidates.append(guessed)
|
|
404
|
-
for path in sorted(
|
|
490
|
+
for path in sorted(self._paper_root().glob("*.pdf")):
|
|
405
491
|
candidates.append(path.relative_to(self.quest_root).as_posix())
|
|
406
492
|
deduped: list[str] = []
|
|
407
493
|
seen: set[str] = set()
|
|
@@ -689,6 +775,8 @@ class QuestStageViewBuilder:
|
|
|
689
775
|
or self.snapshot.get("active_idea_draft_path")
|
|
690
776
|
or str(Path(worktree_root) / "memory" / "ideas" / idea_id / "draft.md")
|
|
691
777
|
)
|
|
778
|
+
idea_markdown = self._markdown_body_for_path(idea_md_path)
|
|
779
|
+
idea_md_rel_path = self._relative_path_or_raw(idea_md_path)
|
|
692
780
|
draft_md_rel_path = self._relative_path_or_raw(draft_md_path)
|
|
693
781
|
draft_markdown = self._markdown_body_for_path(draft_md_path)
|
|
694
782
|
lineage_intent = str(payload.get("lineage_intent") or details.get("lineage_intent") or "").strip() or None
|
|
@@ -761,9 +849,12 @@ class QuestStageViewBuilder:
|
|
|
761
849
|
"risks": details.get("risks") or [],
|
|
762
850
|
"evidence_paths": details.get("evidence_paths") or [],
|
|
763
851
|
"lineage_intent": lineage_intent,
|
|
852
|
+
"idea_path": idea_md_rel_path,
|
|
853
|
+
"idea_markdown": idea_markdown,
|
|
764
854
|
"draft_path": draft_md_rel_path,
|
|
765
855
|
"draft_markdown": draft_markdown,
|
|
766
856
|
"literature_files": literature_files,
|
|
857
|
+
"decision_reason": payload.get("reason"),
|
|
767
858
|
}
|
|
768
859
|
},
|
|
769
860
|
lineage_intent=lineage_intent,
|
|
@@ -830,6 +921,7 @@ class QuestStageViewBuilder:
|
|
|
830
921
|
if isinstance(latest_experiment_payload.get("paths"), dict)
|
|
831
922
|
else {}
|
|
832
923
|
)
|
|
924
|
+
latest_run_markdown = self._markdown_body_for_path(latest_experiment_paths.get("run_md"))
|
|
833
925
|
latest_result_payload = (
|
|
834
926
|
read_json(Path(str(latest_experiment_paths.get("result_json"))), {})
|
|
835
927
|
if str(latest_experiment_paths.get("result_json") or "").strip()
|
|
@@ -865,6 +957,7 @@ class QuestStageViewBuilder:
|
|
|
865
957
|
if str(analysis_manifest.get("campaign_id") or "").strip()
|
|
866
958
|
else None
|
|
867
959
|
)
|
|
960
|
+
analysis_summary_markdown = self._markdown_body_for_path(analysis_summary_path)
|
|
868
961
|
|
|
869
962
|
branch_items = [
|
|
870
963
|
item
|
|
@@ -948,7 +1041,7 @@ class QuestStageViewBuilder:
|
|
|
948
1041
|
description="Merged analysis summary for this branch.",
|
|
949
1042
|
),
|
|
950
1043
|
self._file_entry(
|
|
951
|
-
"
|
|
1044
|
+
(self._paper_root() / "selected_outline.json") if (self._paper_root() / "selected_outline.json").exists() else None,
|
|
952
1045
|
label="Selected Outline",
|
|
953
1046
|
description="Current selected paper outline.",
|
|
954
1047
|
),
|
|
@@ -969,6 +1062,11 @@ class QuestStageViewBuilder:
|
|
|
969
1062
|
"next_target": next_target,
|
|
970
1063
|
"idea_draft_path": idea_draft_rel_path,
|
|
971
1064
|
"idea_draft_markdown": idea_draft_markdown,
|
|
1065
|
+
"idea_hypothesis": latest_idea_details.get("hypothesis"),
|
|
1066
|
+
"idea_mechanism": latest_idea_details.get("mechanism"),
|
|
1067
|
+
"idea_expected_gain": latest_idea_details.get("expected_gain"),
|
|
1068
|
+
"idea_risks": latest_idea_details.get("risks") or [],
|
|
1069
|
+
"decision_reason": latest_idea_payload.get("reason"),
|
|
972
1070
|
"latest_main_experiment": {
|
|
973
1071
|
"run_id": latest_run_id,
|
|
974
1072
|
"summary": latest_experiment_payload.get("summary"),
|
|
@@ -978,10 +1076,14 @@ class QuestStageViewBuilder:
|
|
|
978
1076
|
"progress_eval": latest_progress_eval,
|
|
979
1077
|
"evaluation_summary": latest_evaluation_summary,
|
|
980
1078
|
"run_md_path": latest_experiment_paths.get("run_md"),
|
|
1079
|
+
"run_markdown": latest_run_markdown,
|
|
981
1080
|
"result_json_path": latest_experiment_paths.get("result_json"),
|
|
1081
|
+
"result_payload": latest_result_payload,
|
|
982
1082
|
}
|
|
983
1083
|
if latest_run_id
|
|
984
1084
|
else None,
|
|
1085
|
+
"analysis_summary_path": self._relative_path_or_raw(analysis_summary_path),
|
|
1086
|
+
"analysis_summary_markdown": analysis_summary_markdown,
|
|
985
1087
|
}
|
|
986
1088
|
},
|
|
987
1089
|
lineage_intent=lineage_intent,
|
|
@@ -1018,8 +1120,17 @@ class QuestStageViewBuilder:
|
|
|
1018
1120
|
baseline_ref = payload.get("baseline_ref") or result_payload.get("baseline_ref") or {}
|
|
1019
1121
|
evaluation_summary = _evaluation_summary(payload.get("evaluation_summary") or result_payload.get("evaluation_summary"))
|
|
1020
1122
|
run_id = str(payload.get("run_id") or "pending").strip() or "pending"
|
|
1123
|
+
run_markdown = self._markdown_body_for_path(paths.get("run_md"))
|
|
1124
|
+
trace_summary = self._trace_summary()
|
|
1125
|
+
trace_markdown = self._trace_markdown()
|
|
1021
1126
|
note = (
|
|
1022
|
-
str(
|
|
1127
|
+
str(
|
|
1128
|
+
payload.get("summary")
|
|
1129
|
+
or result_payload.get("conclusion")
|
|
1130
|
+
or (progress_eval or {}).get("reason")
|
|
1131
|
+
or trace_summary
|
|
1132
|
+
or ""
|
|
1133
|
+
).strip()
|
|
1023
1134
|
or "No durable main experiment result has been recorded yet."
|
|
1024
1135
|
)
|
|
1025
1136
|
title = f"Experiment · {run_id}"
|
|
@@ -1080,7 +1191,13 @@ class QuestStageViewBuilder:
|
|
|
1080
1191
|
"metrics_summary": metrics_summary,
|
|
1081
1192
|
"progress_eval": progress_eval,
|
|
1082
1193
|
"evaluation_summary": evaluation_summary,
|
|
1194
|
+
"run_path": self._relative_path_or_raw(paths.get("run_md")),
|
|
1195
|
+
"run_markdown": run_markdown,
|
|
1196
|
+
"result_json_path": self._relative_path_or_raw(paths.get("result_json")),
|
|
1083
1197
|
"result_payload": result_payload,
|
|
1198
|
+
"trace_summary": trace_summary,
|
|
1199
|
+
"trace_markdown": trace_markdown,
|
|
1200
|
+
"trace_actions": self._recent_trace_actions(),
|
|
1084
1201
|
}
|
|
1085
1202
|
},
|
|
1086
1203
|
)
|
|
@@ -1106,7 +1223,9 @@ class QuestStageViewBuilder:
|
|
|
1106
1223
|
flow_type = str(payload.get("flow_type") or "").strip()
|
|
1107
1224
|
if flow_type not in {"analysis_campaign", "analysis_slice"}:
|
|
1108
1225
|
continue
|
|
1109
|
-
if campaign_id
|
|
1226
|
+
if campaign_id:
|
|
1227
|
+
if str(payload.get("campaign_id") or "").strip() == campaign_id:
|
|
1228
|
+
items.append(item)
|
|
1110
1229
|
continue
|
|
1111
1230
|
if self._branch_matches(payload, allow_parent=True, include_unscoped=False):
|
|
1112
1231
|
items.append(item)
|
|
@@ -1189,13 +1308,23 @@ class QuestStageViewBuilder:
|
|
|
1189
1308
|
"plan_path": item.get("plan_path"),
|
|
1190
1309
|
"result_path": item.get("result_path"),
|
|
1191
1310
|
"mirror_path": item.get("mirror_path"),
|
|
1311
|
+
"plan_markdown": self._markdown_body_for_path(item.get("plan_path")),
|
|
1312
|
+
"result_markdown": self._markdown_body_for_path(item.get("result_path")),
|
|
1313
|
+
"mirror_markdown": self._markdown_body_for_path(item.get("mirror_path")),
|
|
1192
1314
|
}
|
|
1193
1315
|
)
|
|
1194
1316
|
title = f"Analysis · {campaign_id}"
|
|
1317
|
+
trace_summary = self._trace_summary()
|
|
1195
1318
|
note = (
|
|
1196
|
-
str(latest_payload.get("summary") or manifest.get("goal") or "").strip()
|
|
1319
|
+
str(latest_payload.get("summary") or manifest.get("goal") or trace_summary or "").strip()
|
|
1197
1320
|
or "No durable analysis campaign has been created yet."
|
|
1198
1321
|
)
|
|
1322
|
+
summary_path = (
|
|
1323
|
+
self.quest_root / "experiments" / "analysis-results" / campaign_id / "SUMMARY.md"
|
|
1324
|
+
if campaign_id != "pending"
|
|
1325
|
+
else None
|
|
1326
|
+
)
|
|
1327
|
+
summary_markdown = self._markdown_body_for_path(summary_path) if summary_path else None
|
|
1199
1328
|
key_files = self._dedupe_files(
|
|
1200
1329
|
[
|
|
1201
1330
|
self._file_entry(manifest.get("_manifest_path"), label="Campaign Manifest", description="Structured analysis campaign manifest."),
|
|
@@ -1254,7 +1383,14 @@ class QuestStageViewBuilder:
|
|
|
1254
1383
|
"summary": latest_payload.get("summary"),
|
|
1255
1384
|
"manifest_path": manifest.get("_manifest_path"),
|
|
1256
1385
|
"charter_path": manifest.get("charter_path"),
|
|
1386
|
+
"charter_markdown": self._markdown_body_for_path(manifest.get("charter_path")),
|
|
1257
1387
|
"todo_manifest_path": manifest.get("todo_manifest_path"),
|
|
1388
|
+
"todo_manifest_markdown": self._markdown_body_for_path(manifest.get("todo_manifest_path")),
|
|
1389
|
+
"summary_path": self._relative_path_or_raw(summary_path) if summary_path else None,
|
|
1390
|
+
"summary_markdown": summary_markdown,
|
|
1391
|
+
"trace_summary": trace_summary,
|
|
1392
|
+
"trace_markdown": self._trace_markdown(),
|
|
1393
|
+
"trace_actions": self._recent_trace_actions(),
|
|
1258
1394
|
}
|
|
1259
1395
|
},
|
|
1260
1396
|
)
|
|
@@ -1264,23 +1400,25 @@ class QuestStageViewBuilder:
|
|
|
1264
1400
|
latex_root_rel = self._paper_latex_root(bundle_manifest)
|
|
1265
1401
|
main_tex_rel = self._paper_main_tex(latex_root_rel)
|
|
1266
1402
|
pdf_candidates = self._paper_pdf_candidates(bundle_manifest, main_tex_rel=main_tex_rel)
|
|
1267
|
-
|
|
1403
|
+
paper_root = self._paper_root()
|
|
1404
|
+
open_source_root = self._open_source_root()
|
|
1405
|
+
candidates = sorted((paper_root / "outlines" / "candidates").glob("*.json"))
|
|
1268
1406
|
files: list[dict[str, Any] | None] = [
|
|
1269
1407
|
*[
|
|
1270
1408
|
self._file_entry(path, label=f"Outline Candidate · {path.stem}", description="Paper outline candidate JSON.")
|
|
1271
1409
|
for path in candidates
|
|
1272
1410
|
],
|
|
1273
|
-
self._file_entry("
|
|
1274
|
-
self._file_entry("
|
|
1275
|
-
self._file_entry("
|
|
1276
|
-
self._file_entry("
|
|
1277
|
-
self._file_entry("
|
|
1278
|
-
self._file_entry("
|
|
1279
|
-
self._file_entry("
|
|
1280
|
-
self._file_entry("
|
|
1281
|
-
self._file_entry("
|
|
1282
|
-
self._file_entry("
|
|
1283
|
-
self._file_entry("
|
|
1411
|
+
self._file_entry(paper_root / "selected_outline.json", label="Selected Outline", description="Chosen paper outline."),
|
|
1412
|
+
self._file_entry(paper_root / "outline_selection.md", label="Outline Selection Note", description="Outline selection rationale."),
|
|
1413
|
+
self._file_entry(paper_root / "draft.md", label="Draft Markdown", description="Current paper draft."),
|
|
1414
|
+
self._file_entry(paper_root / "writing_plan.md", label="Writing Plan", description="Paper writing plan."),
|
|
1415
|
+
self._file_entry(paper_root / "references.bib", label="References", description="Bibliography file."),
|
|
1416
|
+
self._file_entry(paper_root / "claim_evidence_map.json", label="Claim-Evidence Map", description="Claim to evidence mapping."),
|
|
1417
|
+
self._file_entry(paper_root / "baseline_inventory.json", label="Baseline Inventory", description="Canonical and supplementary baseline inventory for writing."),
|
|
1418
|
+
self._file_entry(paper_root / "build" / "compile_report.json", label="Compile Report", description="Paper build/compile report."),
|
|
1419
|
+
self._file_entry(paper_root / "paper_bundle_manifest.json", label="Bundle Manifest", description="Final paper bundle manifest."),
|
|
1420
|
+
self._file_entry(open_source_root / "manifest.json", label="Open Source Manifest", description="Open-source cleanup and release preparation manifest."),
|
|
1421
|
+
self._file_entry(open_source_root / "cleanup_plan.md", label="Open Source Cleanup Plan", description="Checklist for cleaning the paper branch into a public release."),
|
|
1284
1422
|
self._file_entry(latex_root_rel, label="LaTeX Sources", description="LaTeX source folder.", expected_kind="directory"),
|
|
1285
1423
|
self._file_entry(main_tex_rel, label="Main TeX", description="Primary TeX source file."),
|
|
1286
1424
|
]
|
|
@@ -1290,7 +1428,7 @@ class QuestStageViewBuilder:
|
|
|
1290
1428
|
|
|
1291
1429
|
def _paper_candidates(self) -> list[dict[str, Any]]:
|
|
1292
1430
|
candidates: list[dict[str, Any]] = []
|
|
1293
|
-
for path in sorted((self.
|
|
1431
|
+
for path in sorted((self._paper_root() / "outlines" / "candidates").glob("*.json")):
|
|
1294
1432
|
payload = read_json(path, {})
|
|
1295
1433
|
if not isinstance(payload, dict):
|
|
1296
1434
|
payload = {}
|
|
@@ -1319,7 +1457,8 @@ class QuestStageViewBuilder:
|
|
|
1319
1457
|
)
|
|
1320
1458
|
and self._branch_matches(self._payload(item), allow_parent=True)
|
|
1321
1459
|
]
|
|
1322
|
-
|
|
1460
|
+
paper_root = self._paper_root()
|
|
1461
|
+
selected_outline_path = paper_root / "selected_outline.json"
|
|
1323
1462
|
selected_outline = read_json(selected_outline_path, {}) if selected_outline_path.exists() else {}
|
|
1324
1463
|
if not isinstance(selected_outline, dict):
|
|
1325
1464
|
selected_outline = {}
|
|
@@ -1329,15 +1468,18 @@ class QuestStageViewBuilder:
|
|
|
1329
1468
|
else {}
|
|
1330
1469
|
)
|
|
1331
1470
|
candidates = self._paper_candidates()
|
|
1332
|
-
compile_report = read_json(
|
|
1471
|
+
compile_report = read_json(paper_root / "build" / "compile_report.json", {})
|
|
1333
1472
|
if not isinstance(compile_report, dict):
|
|
1334
1473
|
compile_report = {}
|
|
1335
1474
|
bundle_manifest = self._paper_bundle_manifest()
|
|
1336
1475
|
latex_root_rel = self._paper_latex_root(bundle_manifest)
|
|
1337
1476
|
main_tex_rel = self._paper_main_tex(latex_root_rel)
|
|
1338
|
-
references_bib = read_text(
|
|
1477
|
+
references_bib = read_text(paper_root / "references.bib", "")
|
|
1339
1478
|
references_count = sum(1 for line in references_bib.splitlines() if line.lstrip().startswith("@"))
|
|
1340
1479
|
pdf_paths = self._paper_pdf_candidates(bundle_manifest, main_tex_rel=main_tex_rel)
|
|
1480
|
+
draft_rel = self._paper_relative_path(paper_root / "draft.md") or "paper/draft.md"
|
|
1481
|
+
writing_plan_rel = self._paper_relative_path(paper_root / "writing_plan.md") or "paper/writing_plan.md"
|
|
1482
|
+
claim_map_rel = self._paper_relative_path(paper_root / "claim_evidence_map.json") or "paper/claim_evidence_map.json"
|
|
1341
1483
|
selected_title = str(
|
|
1342
1484
|
detailed.get("title") or selected_outline.get("title") or bundle_manifest.get("title") or "Drafting"
|
|
1343
1485
|
).strip() or "Drafting"
|
|
@@ -1356,7 +1498,7 @@ class QuestStageViewBuilder:
|
|
|
1356
1498
|
overview=[
|
|
1357
1499
|
_field("Selected Outline", selected_title if selected_outline else "Not selected"),
|
|
1358
1500
|
_field("Candidate Count", len(candidates)),
|
|
1359
|
-
_field("Draft Status", "present" if (
|
|
1501
|
+
_field("Draft Status", "present" if (paper_root / "draft.md").exists() else "missing"),
|
|
1360
1502
|
_field("Bundle Status", "present" if bundle_manifest else "missing"),
|
|
1361
1503
|
],
|
|
1362
1504
|
key_facts=[
|
|
@@ -1377,10 +1519,10 @@ class QuestStageViewBuilder:
|
|
|
1377
1519
|
"outline_candidates": candidates,
|
|
1378
1520
|
"selected_outline": selected_outline,
|
|
1379
1521
|
"drafting": {
|
|
1380
|
-
"writing_plan_path":
|
|
1381
|
-
"draft_path":
|
|
1522
|
+
"writing_plan_path": writing_plan_rel,
|
|
1523
|
+
"draft_path": draft_rel,
|
|
1382
1524
|
"references_count": references_count,
|
|
1383
|
-
"claim_evidence_map_path":
|
|
1525
|
+
"claim_evidence_map_path": claim_map_rel,
|
|
1384
1526
|
},
|
|
1385
1527
|
"build": {
|
|
1386
1528
|
"compile_report": compile_report,
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
@@ -771,7 +771,7 @@ class CodexRunner:
|
|
|
771
771
|
]
|
|
772
772
|
if request.approval_policy:
|
|
773
773
|
command.extend(["-c", f'approval_policy="{request.approval_policy}"'])
|
|
774
|
-
reasoning_effort = request.reasoning_effort
|
|
774
|
+
reasoning_effort = request.reasoning_effort
|
|
775
775
|
if reasoning_effort:
|
|
776
776
|
command.extend(["-c", f'model_reasoning_effort="{reasoning_effort}"'])
|
|
777
777
|
tool_timeout_sec = self._positive_timeout_seconds(resolved_runner_config.get("mcp_tool_timeout_sec"))
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- qq_summary_first_rule: start with the conclusion the user cares about, then what it means, then the next action
|
|
11
11
|
- qq_progress_shape_rule: make the current task, the main difficulty or latest real progress, and the next concrete measure explicit whenever possible
|
|
12
12
|
- qq_eta_rule: for baseline reproduction, main experiments, analysis experiments, and other important long-running research phases, include a rough ETA for the next meaningful result or the next update; if uncertain, say that and still give the next check-in window
|
|
13
|
-
- qq_tool_call_keepalive_rule: for ordinary active work,
|
|
13
|
+
- qq_tool_call_keepalive_rule: for ordinary active work, prefer one concise QQ 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
14
|
- qq_internal_detail_rule: omit worker names, heartbeat timestamps, retry counters, pending/running/completed counts, file names, and monitor-window narration unless the user asked for them or the detail changes the recommended action
|
|
15
15
|
- qq_translation_rule: convert internal execution and file-management work into user value, such as saying the baseline record is now organized for easier later comparison instead of listing touched files
|
|
16
16
|
- qq_preflight_rule: before sending a QQ progress update, rewrite it if it still sounds like a monitoring log, execution diary, or file inventory
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Shared Interaction Contract
|
|
2
|
+
|
|
3
|
+
This shared contract is injected once per turn and applies across the stage and companion skills that use `artifact.interact(...)` as the main user-visible continuity channel.
|
|
4
|
+
|
|
5
|
+
## Shared interaction rules
|
|
6
|
+
|
|
7
|
+
- Treat `artifact.interact(...)` as the main long-lived communication thread across TUI, web, and bound connectors.
|
|
8
|
+
- If `artifact.interact(...)` returns queued user requirements, treat them as the highest-priority user instruction bundle before continuing the current stage or companion-skill task.
|
|
9
|
+
- Immediately follow any non-empty mailbox poll with another `artifact.interact(...)` update that confirms receipt; if the request is directly answerable, answer there, otherwise say the current subtask is paused, give a short plan plus nearest report-back point, and handle that request first.
|
|
10
|
+
- Emit `artifact.interact(kind='progress', reply_mode='threaded', ...)` when there is real user-visible progress: a meaningful checkpoint, route-shaping update, or a concise keepalive once active work has crossed roughly 10 tool calls with a human-meaningful delta. Do not let ordinary active work drift beyond roughly 20 tool calls or about 15 minutes without a user-visible update.
|
|
11
|
+
- Keep progress updates chat-like and easy to understand: say what changed, what it means, and what happens next.
|
|
12
|
+
- Default to plain-language summaries. Do not mention file paths, artifact ids, branch/worktree ids, session ids, raw commands, or raw logs unless the user asks or needs them to act.
|
|
13
|
+
- Use `reply_mode='blocking'` only for real user decisions that cannot be resolved from local evidence.
|
|
14
|
+
- For any blocking decision request, provide 1 to 3 concrete options, put the recommended option first, explain each option's actual content plus pros and cons, and wait up to 1 day when feasible. If the blocker is a missing external credential or secret that only the user can provide, keep the quest waiting, ask the user to supply it or choose an alternative, and do not self-resolve; if resumed without that credential and no other work is possible, a long low-frequency wait such as `bash_exec(command='sleep 3600', mode='await', timeout_seconds=3700)` is acceptable. Otherwise choose the best option yourself and notify the user of the chosen option if the timeout expires.
|