@researai/deepscientist 1.5.15 → 1.5.17
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 +385 -104
- package/bin/ds.js +1241 -110
- package/docs/en/00_QUICK_START.md +100 -19
- package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/en/05_TUI_GUIDE.md +6 -0
- package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
- package/docs/en/09_DOCTOR.md +25 -8
- package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
- package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
- package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
- package/docs/en/91_DEVELOPMENT.md +237 -0
- package/docs/en/README.md +24 -2
- package/docs/zh/00_QUICK_START.md +89 -19
- package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
- package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
- package/docs/zh/05_TUI_GUIDE.md +6 -0
- package/docs/zh/09_DOCTOR.md +26 -9
- package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
- package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
- package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
- package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
- package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
- package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
- package/docs/zh/README.md +24 -2
- package/install.sh +46 -4
- package/package.json +2 -1
- package/pyproject.toml +1 -1
- package/src/deepscientist/__init__.py +1 -1
- package/src/deepscientist/acp/envelope.py +6 -0
- package/src/deepscientist/artifact/service.py +647 -22
- package/src/deepscientist/bash_exec/service.py +234 -9
- package/src/deepscientist/bridges/connectors.py +8 -2
- package/src/deepscientist/cli.py +115 -19
- package/src/deepscientist/codex_cli_compat.py +367 -22
- package/src/deepscientist/config/models.py +2 -1
- package/src/deepscientist/config/service.py +183 -13
- package/src/deepscientist/daemon/api/handlers.py +255 -31
- package/src/deepscientist/daemon/api/router.py +9 -0
- package/src/deepscientist/daemon/app.py +1146 -105
- package/src/deepscientist/diagnostics/__init__.py +6 -0
- package/src/deepscientist/diagnostics/runner_failures.py +130 -0
- package/src/deepscientist/doctor.py +207 -3
- package/src/deepscientist/gitops/__init__.py +10 -1
- package/src/deepscientist/gitops/diff.py +129 -0
- package/src/deepscientist/gitops/service.py +4 -1
- package/src/deepscientist/mcp/server.py +39 -0
- package/src/deepscientist/prompts/builder.py +275 -34
- package/src/deepscientist/quest/layout.py +15 -2
- package/src/deepscientist/quest/service.py +707 -55
- package/src/deepscientist/quest/stage_views.py +6 -1
- package/src/deepscientist/runners/codex.py +143 -43
- package/src/deepscientist/shared.py +19 -0
- package/src/deepscientist/skills/__init__.py +2 -2
- package/src/deepscientist/skills/installer.py +196 -5
- package/src/deepscientist/skills/registry.py +66 -0
- package/src/prompts/connectors/qq.md +18 -8
- package/src/prompts/connectors/weixin.md +16 -6
- package/src/prompts/contracts/shared_interaction.md +14 -2
- package/src/prompts/system.md +23 -5
- package/src/prompts/system_copilot.md +56 -0
- package/src/skills/analysis-campaign/SKILL.md +1 -0
- package/src/skills/baseline/SKILL.md +8 -0
- package/src/skills/decision/SKILL.md +8 -0
- package/src/skills/experiment/SKILL.md +8 -0
- package/src/skills/figure-polish/SKILL.md +1 -0
- package/src/skills/finalize/SKILL.md +1 -0
- package/src/skills/idea/SKILL.md +1 -0
- package/src/skills/intake-audit/SKILL.md +8 -0
- package/src/skills/mentor/SKILL.md +217 -0
- package/src/skills/mentor/references/correction-rules.md +210 -0
- package/src/skills/mentor/references/knowledge-profile.md +91 -0
- package/src/skills/mentor/references/persona-profile.md +138 -0
- package/src/skills/mentor/references/taste-profile.md +128 -0
- package/src/skills/mentor/references/thought-style-profile.md +138 -0
- package/src/skills/mentor/references/work-profile.md +289 -0
- package/src/skills/mentor/references/workflow-profile.md +240 -0
- package/src/skills/optimize/SKILL.md +1 -0
- package/src/skills/rebuttal/SKILL.md +1 -0
- package/src/skills/review/SKILL.md +1 -0
- package/src/skills/scout/SKILL.md +8 -0
- package/src/skills/write/SKILL.md +1 -0
- package/src/tui/dist/app/AppContainer.js +19 -11
- package/src/tui/dist/index.js +4 -1
- package/src/tui/dist/lib/api.js +33 -3
- package/src/tui/package.json +1 -1
- package/src/ui/dist/assets/AiManusChatView-Bv-Z8YpU.js +204 -0
- package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
- package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
- package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
- package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
- package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
- package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
- package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
- package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
- package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
- package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
- package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
- package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
- package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
- package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
- package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -0
- package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
- package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
- package/src/ui/dist/assets/NotebookEditor-DB9N_T9q.js +361 -0
- package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
- package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
- package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
- package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
- package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
- package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
- package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
- package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
- package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
- package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
- package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
- package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
- package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
- package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
- package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
- package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
- package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
- package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
- package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
- package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
- package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
- package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
- package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
- package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
- package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
- package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.js +6 -0
- package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
- package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
- package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
- package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
- package/src/ui/dist/assets/popover-CLc0pPP8.js +1 -0
- package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
- package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
- package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
- package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
- package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
- package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
- package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
- package/src/ui/dist/assets/zoom-out-E_gaeAxL.js +11 -0
- package/src/ui/dist/index.html +5 -2
- package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
- package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
- package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
- package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
- package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
- package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
- package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
- package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
- package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
- package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
- package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
- package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
- package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
- package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
- package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
- package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
- package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
- package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
- package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
- package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
- package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
- package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
- package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
- package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
- package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
- package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
- package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
- package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
- package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
- package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
- package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
- package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
- package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
- package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
- package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
- package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
- package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
- package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
- package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
- package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
- package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
- package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
- package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
- package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
- package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
- package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
- package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
- package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
- package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
- package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
- package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
- package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
- package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
- package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
|
@@ -6,30 +6,21 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
from ..connector_runtime import normalize_conversation_id, parse_conversation_id
|
|
8
8
|
from ..config import ConfigManager
|
|
9
|
+
from ..home import repo_root
|
|
9
10
|
from ..memory import MemoryService
|
|
10
11
|
from ..memory.frontmatter import load_markdown_document
|
|
11
12
|
from ..quest import QuestService
|
|
12
13
|
from ..registries import BaselineRegistry
|
|
13
14
|
from ..shared import read_json, read_text, read_yaml
|
|
15
|
+
from ..skills import SkillInstaller, companion_skill_ids, stage_skill_ids
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"write",
|
|
23
|
-
"finalize",
|
|
24
|
-
"decision",
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
COMPANION_SKILLS = (
|
|
28
|
-
"figure-polish",
|
|
29
|
-
"intake-audit",
|
|
30
|
-
"review",
|
|
31
|
-
"rebuttal",
|
|
32
|
-
)
|
|
17
|
+
# Backward-compatible snapshots for modules or tests that still import these names directly.
|
|
18
|
+
# Runtime routing should call `current_standard_skills(...)` / `current_companion_skills(...)`.
|
|
19
|
+
STANDARD_SKILLS = stage_skill_ids(repo_root())
|
|
20
|
+
|
|
21
|
+
_AUTO_CONTINUE_MONITOR_INTERVAL_SECONDS = 240
|
|
22
|
+
|
|
23
|
+
COMPANION_SKILLS = companion_skill_ids(repo_root())
|
|
33
24
|
|
|
34
25
|
STAGE_MEMORY_PLAN = {
|
|
35
26
|
"scout": {
|
|
@@ -71,6 +62,14 @@ STAGE_MEMORY_PLAN = {
|
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
|
|
65
|
+
def current_standard_skills(repo_root_path: Path | None = None) -> tuple[str, ...]:
|
|
66
|
+
return stage_skill_ids(repo_root_path or repo_root())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def current_companion_skills(repo_root_path: Path | None = None) -> tuple[str, ...]:
|
|
70
|
+
return companion_skill_ids(repo_root_path or repo_root())
|
|
71
|
+
|
|
72
|
+
|
|
74
73
|
def classify_turn_intent(user_message: str) -> str:
|
|
75
74
|
text = str(user_message or "").strip()
|
|
76
75
|
if not text:
|
|
@@ -104,13 +103,15 @@ def classify_turn_intent(user_message: str) -> str:
|
|
|
104
103
|
|
|
105
104
|
|
|
106
105
|
class PromptBuilder:
|
|
107
|
-
def __init__(self, repo_root: Path, home: Path) -> None:
|
|
106
|
+
def __init__(self, repo_root: Path, home: Path, *, prompt_version_selection: str | None = None) -> None:
|
|
108
107
|
self.repo_root = repo_root
|
|
109
108
|
self.home = home
|
|
110
109
|
self.quest_service = QuestService(home)
|
|
111
110
|
self.memory_service = MemoryService(home)
|
|
112
111
|
self.baseline_registry = BaselineRegistry(home)
|
|
113
112
|
self.config_manager = ConfigManager(home)
|
|
113
|
+
self.skill_installer = SkillInstaller(repo_root, home)
|
|
114
|
+
self.prompt_version_selection = str(prompt_version_selection or "").strip() or None
|
|
114
115
|
|
|
115
116
|
def build(
|
|
116
117
|
self,
|
|
@@ -128,9 +129,14 @@ class PromptBuilder:
|
|
|
128
129
|
runtime_config = self.config_manager.load_named("config")
|
|
129
130
|
connectors_config = self.config_manager.load_named_normalized("connectors")
|
|
130
131
|
quest_root = Path(snapshot["quest_root"])
|
|
132
|
+
self.skill_installer.sync_quest_prompts(quest_root)
|
|
131
133
|
active_anchor = str(snapshot.get("active_anchor") or skill_id)
|
|
132
134
|
default_locale = str(runtime_config.get("default_locale") or "en-US")
|
|
133
|
-
|
|
135
|
+
workspace_mode = self._workspace_mode(snapshot)
|
|
136
|
+
system_block = self._prompt_fragment(
|
|
137
|
+
"system_copilot.md" if workspace_mode == "copilot" else "system.md",
|
|
138
|
+
quest_root=quest_root,
|
|
139
|
+
)
|
|
134
140
|
shared_interaction_block = self._prompt_fragment(
|
|
135
141
|
Path("contracts") / "shared_interaction.md",
|
|
136
142
|
quest_root=quest_root,
|
|
@@ -159,7 +165,7 @@ class PromptBuilder:
|
|
|
159
165
|
f"conversation_id: quest:{quest_id}",
|
|
160
166
|
f"default_locale: {default_locale}",
|
|
161
167
|
"built_in_mcp_namespaces: memory, artifact, bash_exec",
|
|
162
|
-
"mcp_namespace_note: any shell-like command execution must use bash_exec
|
|
168
|
+
"mcp_namespace_note: **any shell-like command execution must use `bash_exec(...)`, including curl/python/bash/node/git/npm/uv and similar CLI tools; do not use native `shell_command` / `command_execution`.**",
|
|
163
169
|
"",
|
|
164
170
|
"Canonical stage skills root:",
|
|
165
171
|
str((self.repo_root / "src" / "skills").resolve()),
|
|
@@ -229,6 +235,14 @@ class PromptBuilder:
|
|
|
229
235
|
"## Recovery Resume Packet",
|
|
230
236
|
self._recovery_resume_block(snapshot=snapshot, turn_reason=turn_reason),
|
|
231
237
|
"",
|
|
238
|
+
"## Resume Context Spine",
|
|
239
|
+
self._resume_context_spine_block(
|
|
240
|
+
quest_id=quest_id,
|
|
241
|
+
quest_root=quest_root,
|
|
242
|
+
snapshot=snapshot,
|
|
243
|
+
turn_reason=turn_reason,
|
|
244
|
+
),
|
|
245
|
+
"",
|
|
232
246
|
"## Interaction Style",
|
|
233
247
|
self._interaction_style_block(default_locale=default_locale, user_message=user_message, snapshot=snapshot),
|
|
234
248
|
"",
|
|
@@ -486,8 +500,14 @@ class PromptBuilder:
|
|
|
486
500
|
]
|
|
487
501
|
)
|
|
488
502
|
if str(turn_reason or "").strip() == "auto_continue":
|
|
489
|
-
lines.
|
|
490
|
-
|
|
503
|
+
lines.extend(
|
|
504
|
+
[
|
|
505
|
+
"- auto_continue_rule: this turn has no new user message; continue from the active requirements, durable artifacts, current quest state, and resume context spine instead of replaying the previous user message",
|
|
506
|
+
f"- auto_continue_interval_rule: when a real long-running external task is already active, background-progress auto-continue becomes a low-frequency monitoring pass, about every {_AUTO_CONTINUE_MONITOR_INTERVAL_SECONDS} seconds rather than sub-minute polling",
|
|
507
|
+
"- auto_continue_fast_prepare_rule: in autonomous mode before a real external long-running task exists, auto-continue may advance quickly, around 0.2 seconds between turns, so the agent can keep preparing or launching the real work without idling",
|
|
508
|
+
"- autonomous_prepare_rule: in autonomous mode, if no real long-running external task is active yet, use the next turns to keep preparing, launching, or durably deciding the next real unit of work instead of parking idly",
|
|
509
|
+
"- copilot_park_rule: in copilot mode, once the current requested unit is complete, it is normal to park and wait for the next user message or `/resume` instead of continuing autonomously",
|
|
510
|
+
]
|
|
491
511
|
)
|
|
492
512
|
else:
|
|
493
513
|
lines.append(
|
|
@@ -614,6 +634,76 @@ class PromptBuilder:
|
|
|
614
634
|
lines.append(f"- remaining_attachment_count: {len(attachments) - 6}")
|
|
615
635
|
return "\n".join(lines)
|
|
616
636
|
|
|
637
|
+
def _resume_context_spine_block(self, *, quest_id: str, quest_root: Path, snapshot: dict, turn_reason: str) -> str:
|
|
638
|
+
if str(turn_reason or "").strip() != "auto_continue":
|
|
639
|
+
return "- none"
|
|
640
|
+
lines = [
|
|
641
|
+
"- resume_spine_rule: on auto_continue turns, first continue from the latest durable user requirement, the latest assistant checkpoint, the latest run summary, and recent memory cues instead of reconstructing intent from scratch",
|
|
642
|
+
]
|
|
643
|
+
bash_running_count = int(((snapshot.get("counts") or {}).get("bash_running_count")) or 0)
|
|
644
|
+
latest_bash_session = (
|
|
645
|
+
dict((snapshot.get("summary") or {}).get("latest_bash_session") or {})
|
|
646
|
+
if isinstance((snapshot.get("summary") or {}).get("latest_bash_session"), dict)
|
|
647
|
+
else {}
|
|
648
|
+
)
|
|
649
|
+
lines.append(f"- active_bash_exec_run_count: {bash_running_count}")
|
|
650
|
+
if latest_bash_session:
|
|
651
|
+
command_preview = " ".join(str(latest_bash_session.get("command") or "").split())
|
|
652
|
+
if len(command_preview) > 180:
|
|
653
|
+
command_preview = command_preview[:177].rstrip() + "..."
|
|
654
|
+
lines.append(
|
|
655
|
+
f"- latest_bash_exec_session: bash_id={str(latest_bash_session.get('bash_id') or 'none')} | "
|
|
656
|
+
f"status={str(latest_bash_session.get('status') or 'unknown')} | "
|
|
657
|
+
f"command={command_preview or 'none'}"
|
|
658
|
+
)
|
|
659
|
+
latest_user = self._latest_user_message(quest_id)
|
|
660
|
+
if latest_user is not None:
|
|
661
|
+
preview = " ".join(str(latest_user.get("content") or "").split())
|
|
662
|
+
if len(preview) > 320:
|
|
663
|
+
preview = preview[:317].rstrip() + "..."
|
|
664
|
+
lines.append(
|
|
665
|
+
f"- latest_user_message: {str(latest_user.get('created_at') or 'unknown')} | "
|
|
666
|
+
f"source={str(latest_user.get('source') or 'unknown')} | "
|
|
667
|
+
f"reply_to={str(latest_user.get('reply_to_interaction_id') or 'none')} | "
|
|
668
|
+
f"preview={preview or 'none'}"
|
|
669
|
+
)
|
|
670
|
+
latest_assistant = self._latest_assistant_message(quest_id)
|
|
671
|
+
if latest_assistant is not None:
|
|
672
|
+
preview = " ".join(str(latest_assistant.get("content") or "").split())
|
|
673
|
+
if len(preview) > 360:
|
|
674
|
+
preview = preview[:357].rstrip() + "..."
|
|
675
|
+
lines.append(
|
|
676
|
+
f"- latest_assistant_checkpoint: {str(latest_assistant.get('created_at') or 'unknown')} | "
|
|
677
|
+
f"skill={str(latest_assistant.get('skill_id') or 'none')} | "
|
|
678
|
+
f"run_id={str(latest_assistant.get('run_id') or 'none')} | "
|
|
679
|
+
f"preview={preview or 'none'}"
|
|
680
|
+
)
|
|
681
|
+
latest_run = self._latest_run_result(quest_root)
|
|
682
|
+
if latest_run is not None:
|
|
683
|
+
preview = " ".join(str(latest_run.get("preview") or "").split())
|
|
684
|
+
if len(preview) > 360:
|
|
685
|
+
preview = preview[:357].rstrip() + "..."
|
|
686
|
+
lines.append(
|
|
687
|
+
f"- latest_run_result: {str(latest_run.get('completed_at') or 'unknown')} | "
|
|
688
|
+
f"run_id={str(latest_run.get('run_id') or 'none')} | "
|
|
689
|
+
f"exit_code={latest_run.get('exit_code') if latest_run.get('exit_code') is not None else 'none'} | "
|
|
690
|
+
f"preview={preview or 'none'}"
|
|
691
|
+
)
|
|
692
|
+
recent_memory = self.memory_service.list_recent(scope="quest", quest_root=quest_root, limit=3)
|
|
693
|
+
if recent_memory:
|
|
694
|
+
lines.append("- recent_memory_cues:")
|
|
695
|
+
for item in recent_memory:
|
|
696
|
+
title = str(item.get("title") or "memory").strip() or "memory"
|
|
697
|
+
card_type = str(item.get("type") or "memory").strip() or "memory"
|
|
698
|
+
excerpt = " ".join(str(item.get("excerpt") or "").split())
|
|
699
|
+
if len(excerpt) > 200:
|
|
700
|
+
excerpt = excerpt[:197].rstrip() + "..."
|
|
701
|
+
lines.append(f" - [{card_type}] {title}: {excerpt or 'no excerpt'}")
|
|
702
|
+
else:
|
|
703
|
+
lines.append("- recent_memory_cues: none")
|
|
704
|
+
lines.append("- resume_spine_conflict_rule: if these spine items conflict with newer durable files or artifacts, trust the newer durable state and update the summary rather than replaying the older plan verbatim")
|
|
705
|
+
return "\n".join(lines)
|
|
706
|
+
|
|
617
707
|
def _retry_recovery_block(self, retry_context: dict | None) -> str:
|
|
618
708
|
if not isinstance(retry_context, dict) or not retry_context:
|
|
619
709
|
return "- none"
|
|
@@ -726,6 +816,19 @@ class PromptBuilder:
|
|
|
726
816
|
def _prompt_path(self, relative_path: str | Path, *, quest_root: Path | None = None) -> Path:
|
|
727
817
|
normalized = Path(relative_path)
|
|
728
818
|
if quest_root is not None:
|
|
819
|
+
selected_version = str(self.prompt_version_selection or "").strip()
|
|
820
|
+
if selected_version and selected_version not in {"latest", "current", "active"}:
|
|
821
|
+
selected_root = self.skill_installer.resolve_prompt_version_root(quest_root, selected_version)
|
|
822
|
+
if selected_root is None:
|
|
823
|
+
raise FileNotFoundError(
|
|
824
|
+
f"Prompt version `{selected_version}` is unavailable for quest `{quest_root.name}`."
|
|
825
|
+
)
|
|
826
|
+
selected_path = selected_root / normalized
|
|
827
|
+
if not selected_path.exists():
|
|
828
|
+
raise FileNotFoundError(
|
|
829
|
+
f"Prompt version `{selected_version}` does not include `{normalized.as_posix()}` for quest `{quest_root.name}`."
|
|
830
|
+
)
|
|
831
|
+
return selected_path
|
|
729
832
|
quest_path = quest_root / ".codex" / "prompts" / normalized
|
|
730
833
|
if quest_path.exists():
|
|
731
834
|
return quest_path
|
|
@@ -737,16 +840,45 @@ class PromptBuilder:
|
|
|
737
840
|
return item
|
|
738
841
|
return None
|
|
739
842
|
|
|
843
|
+
def _latest_assistant_message(self, quest_id: str) -> dict | None:
|
|
844
|
+
for item in reversed(self.quest_service.history(quest_id, limit=120)):
|
|
845
|
+
if str(item.get("role") or "") == "assistant":
|
|
846
|
+
return item
|
|
847
|
+
return None
|
|
848
|
+
|
|
849
|
+
@staticmethod
|
|
850
|
+
def _latest_run_result(quest_root: Path) -> dict[str, object] | None:
|
|
851
|
+
runs_root = quest_root / ".ds" / "runs"
|
|
852
|
+
if not runs_root.exists():
|
|
853
|
+
return None
|
|
854
|
+
candidates = [path for path in runs_root.glob("*/result.json") if path.is_file()]
|
|
855
|
+
if not candidates:
|
|
856
|
+
return None
|
|
857
|
+
latest = max(candidates, key=lambda path: path.stat().st_mtime)
|
|
858
|
+
payload = read_json(latest, {})
|
|
859
|
+
if not isinstance(payload, dict):
|
|
860
|
+
return None
|
|
861
|
+
preview = (
|
|
862
|
+
str(payload.get("output_text") or "").strip()
|
|
863
|
+
or str(payload.get("stderr_text") or "").strip()
|
|
864
|
+
)
|
|
865
|
+
return {
|
|
866
|
+
"run_id": latest.parent.name,
|
|
867
|
+
"completed_at": str(payload.get("completed_at") or "").strip() or None,
|
|
868
|
+
"exit_code": payload.get("exit_code"),
|
|
869
|
+
"preview": preview,
|
|
870
|
+
}
|
|
871
|
+
|
|
740
872
|
def _skill_paths_block(self) -> str:
|
|
741
873
|
lines = []
|
|
742
|
-
for skill_id in
|
|
874
|
+
for skill_id in current_standard_skills(self.repo_root):
|
|
743
875
|
primary = (self.repo_root / "src" / "skills" / skill_id / "SKILL.md").resolve()
|
|
744
876
|
lines.append(f"- {skill_id}: primary={primary}")
|
|
745
877
|
return "\n".join(lines)
|
|
746
878
|
|
|
747
879
|
def _companion_skill_paths_block(self) -> str:
|
|
748
880
|
lines = []
|
|
749
|
-
for skill_id in
|
|
881
|
+
for skill_id in current_companion_skills(self.repo_root):
|
|
750
882
|
primary = (self.repo_root / "src" / "skills" / skill_id / "SKILL.md").resolve()
|
|
751
883
|
lines.append(f"- {skill_id}: primary={primary}")
|
|
752
884
|
return "\n".join(lines)
|
|
@@ -760,6 +892,18 @@ class PromptBuilder:
|
|
|
760
892
|
return value
|
|
761
893
|
return True
|
|
762
894
|
|
|
895
|
+
@staticmethod
|
|
896
|
+
def _workspace_mode(snapshot: dict) -> str:
|
|
897
|
+
value = str(snapshot.get("workspace_mode") or "").strip().lower()
|
|
898
|
+
if value in {"copilot", "autonomous"}:
|
|
899
|
+
return value
|
|
900
|
+
startup_contract = snapshot.get("startup_contract")
|
|
901
|
+
if isinstance(startup_contract, dict):
|
|
902
|
+
value = str(startup_contract.get("workspace_mode") or "").strip().lower()
|
|
903
|
+
if value in {"copilot", "autonomous"}:
|
|
904
|
+
return value
|
|
905
|
+
return "autonomous"
|
|
906
|
+
|
|
763
907
|
@staticmethod
|
|
764
908
|
def _decision_policy(snapshot: dict) -> str:
|
|
765
909
|
startup_contract = snapshot.get("startup_contract")
|
|
@@ -824,6 +968,18 @@ class PromptBuilder:
|
|
|
824
968
|
return "none"
|
|
825
969
|
|
|
826
970
|
def _research_delivery_policy_block(self, snapshot: dict) -> str:
|
|
971
|
+
if self._workspace_mode(snapshot) == "copilot":
|
|
972
|
+
return "\n".join(
|
|
973
|
+
[
|
|
974
|
+
"- workspace_mode: copilot",
|
|
975
|
+
"- delivery_goal: complete the user-requested unit of work instead of forcing the full research graph by default.",
|
|
976
|
+
"- task_scope_rule: arbitrary research tasks such as reading, coding, debugging, experiment design, run inspection, analysis, writing, and planning can all be handled directly in this mode.",
|
|
977
|
+
"- autonomy_boundary: only expand into longer autonomous continuation when the user explicitly asks for end-to-end or unattended progress.",
|
|
978
|
+
"- routing_rule: open only the skills actually needed for the current request.",
|
|
979
|
+
"- durability_rule: keep important plan, evidence, decisions, and outputs durable in quest files or artifacts so later turns can resume cleanly.",
|
|
980
|
+
"- completion_rule: after the requested unit is complete, summarize what changed and stop instead of auto-continuing.",
|
|
981
|
+
]
|
|
982
|
+
)
|
|
827
983
|
need_research_paper = self._need_research_paper(snapshot)
|
|
828
984
|
launch_mode = self._launch_mode(snapshot)
|
|
829
985
|
standard_profile = self._standard_profile(snapshot)
|
|
@@ -1029,6 +1185,41 @@ class PromptBuilder:
|
|
|
1029
1185
|
def _interaction_style_block(self, *, default_locale: str, user_message: str, snapshot: dict) -> str:
|
|
1030
1186
|
normalized_locale = str(default_locale or "").lower()
|
|
1031
1187
|
chinese_turn = normalized_locale.startswith("zh") or bool(re.search(r"[\u4e00-\u9fff]", user_message))
|
|
1188
|
+
if self._workspace_mode(snapshot) == "copilot":
|
|
1189
|
+
lines = [
|
|
1190
|
+
f"- configured_default_locale: {default_locale}",
|
|
1191
|
+
f"- current_turn_language_bias: {'zh' if chinese_turn else 'en'}",
|
|
1192
|
+
"- collaboration_mode: user-directed copilot",
|
|
1193
|
+
"- freeform_task_rule: if the user asks for a concrete research task, solve that task directly before introducing stage-routing language.",
|
|
1194
|
+
"- requested_skill_hint_rule: in copilot mode, treat `requested_skill` as a lightweight routing hint, not as an instruction to default into `decision` for ordinary direct tasks.",
|
|
1195
|
+
"- turn_self_routing_rule: before substantial work, classify the current turn as `direct_answer`, `direct_action`, `stage_continue`, or `route_decision`.",
|
|
1196
|
+
"- direct_answer_rule: if the user mainly wants an answer or clarification, answer with the narrowest sufficient context and avoid reading large stage state unless needed.",
|
|
1197
|
+
"- direct_action_rule: if the user mainly wants one concrete task, execute the smallest useful unit first and do not expand into background research continuation in the same turn unless the user asked for it.",
|
|
1198
|
+
"- stage_continue_rule: if the user mainly wants the quest to keep moving, continue from the active durable stage state after acknowledging the request.",
|
|
1199
|
+
"- route_decision_rule: switch into `decision`-style reasoning only when safe continuation depends on a real route, scope, cost, branch, or scientific-direction judgment.",
|
|
1200
|
+
"- decision_skill_escalation_rule: if a turn upgrades into `route_decision`, explicitly read the `decision` skill before substantial route-changing work.",
|
|
1201
|
+
"- response_pattern: say what changed -> say what it means -> say what happens next",
|
|
1202
|
+
"- mailbox_protocol: artifact.interact(include_recent_inbound_messages=True) remains the queued human-message mailbox and should be checked whenever human continuity matters.",
|
|
1203
|
+
"- planning_rule: before non-trivial execution, make the immediate plan explicit and keep the first step small.",
|
|
1204
|
+
"- tool_rule: use memory for durable recall, artifact for quest state and git-aware research operations, and bash_exec for terminal execution.",
|
|
1205
|
+
"- copilot_sop_rule: classify the request first, choose the narrowest correct tool path, execute the smallest useful unit, persist the important result, then answer plainly.",
|
|
1206
|
+
"- shell_tool_mandate: **for any shell, CLI, Python, bash, node, git, npm, uv, or environment command execution, use `bash_exec(...)`; do not use native `shell_command` or Codex `command_execution`.**",
|
|
1207
|
+
"- git_tool_mandate: for git work inside the current quest repository or worktree, prefer `artifact.git(...)` before raw shell git commands.",
|
|
1208
|
+
"- git_test_rule: if the user wants a generic git smoke test rather than a quest-repo mutation, use `bash_exec(...)` in an isolated scratch repository.",
|
|
1209
|
+
"- decision_entry_rule: use `decision` only for real route, scope, cost, branch, or scientific-direction judgments; do not default to it for ordinary repo, code, environment, or execution tasks.",
|
|
1210
|
+
"- micro_task_stop_rule: after finishing a `direct_answer` or `direct_action` turn, report the result plainly and wait instead of auto-continuing.",
|
|
1211
|
+
"- stop_rule: once the current requested unit is done, send a concise update and wait for the next message or `/resume`.",
|
|
1212
|
+
"- escalation_rule: if a route change materially affects cost, scope, or scientific direction, ask before proceeding.",
|
|
1213
|
+
]
|
|
1214
|
+
if chinese_turn:
|
|
1215
|
+
lines.append(
|
|
1216
|
+
"- tone_hint: 使用自然、礼貌、专业、带一点活泼感的中文;像靠谱又主动汇报进展的研究搭子,不要冷冰冰或官话腔;对真实好消息可自然用“都搞定啦”“有结果了”这种轻微庆祝开头,但下一句要立刻说清具体结果。"
|
|
1217
|
+
)
|
|
1218
|
+
else:
|
|
1219
|
+
lines.append(
|
|
1220
|
+
"- tone_hint: use concise, natural, warm English, lead with the conclusion, and avoid sounding cold, bureaucratic, or log-like."
|
|
1221
|
+
)
|
|
1222
|
+
return "\n".join(lines)
|
|
1032
1223
|
bound_conversations = snapshot.get("bound_conversations") or []
|
|
1033
1224
|
need_research_paper = self._need_research_paper(snapshot)
|
|
1034
1225
|
decision_policy = self._decision_policy(snapshot)
|
|
@@ -1044,9 +1235,16 @@ class PromptBuilder:
|
|
|
1044
1235
|
f"- standard_profile: {standard_profile if launch_mode == 'standard' else 'n/a'}",
|
|
1045
1236
|
f"- custom_profile: {custom_profile if launch_mode == 'custom' else 'n/a'}",
|
|
1046
1237
|
"- collaboration_mode: long-horizon, continuity-first, artifact-aware",
|
|
1238
|
+
"- user_turn_self_routing_rule: on a fresh user message, first classify the turn as `direct_answer`, `direct_action`, `stage_continue`, or `route_decision` before reading additional skills or large quest context.",
|
|
1239
|
+
"- direct_answer_rule: if the user mainly wants an answer or clarification, answer with the narrowest sufficient context and avoid reading large stage state unless needed.",
|
|
1240
|
+
"- direct_action_rule: if the user mainly wants one concrete task, execute the smallest useful unit first and do not silently expand into broader autonomous continuation in the same turn unless the user asked for it.",
|
|
1241
|
+
"- stage_continue_rule: if the user is clearly asking to continue quest progress, resume from the active durable stage state.",
|
|
1242
|
+
"- route_decision_rule: open `decision`-style reasoning only when safe continuation genuinely depends on a real route, scope, cost, branch, or scientific-direction judgment.",
|
|
1243
|
+
"- decision_skill_escalation_rule: if a fresh user-message turn upgrades into `route_decision`, explicitly read the `decision` skill before substantial route-changing work.",
|
|
1047
1244
|
"- response_pattern: say what changed -> say what it means -> say what happens next",
|
|
1048
1245
|
"- 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",
|
|
1049
1246
|
"- shared_interaction_contract_precedence: use the shared interaction contract as the default user-facing cadence; the rules below add runtime-specific execution behavior instead of restating the same chat cadence",
|
|
1247
|
+
"- shell_tool_mandate: **native `shell_command` / `command_execution` is forbidden; all shell-like execution must use `bash_exec(...)`.**",
|
|
1050
1248
|
"- 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",
|
|
1051
1249
|
"- 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",
|
|
1052
1250
|
"- 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",
|
|
@@ -1055,6 +1253,10 @@ class PromptBuilder:
|
|
|
1055
1253
|
"- long_run_reporting_protocol: inspect real logs/status after each meaningful await cycle and at least once every 30 minutes at worst, but only send a user-visible update when there is a human-meaningful delta, blocker, recovery, route change, or the visibility bound would otherwise be exceeded",
|
|
1056
1254
|
"- intervention_threshold_protocol: do not kill or restart a run merely because a short watch window passed without final completion; intervene only on explicit failure, clear invalidity, process exit, or no meaningful delta across a sufficiently long observation window",
|
|
1057
1255
|
"- timeout_protocol: before using bash_exec(mode='await', ...), estimate whether the command can finish within the selected wait window; if runtime is uncertain or likely longer, use bash_exec(mode='detach', ...) and monitor instead of guessing a fake deadline",
|
|
1256
|
+
f"- auto_continue_monitoring_protocol: if the runtime schedules background-progress auto_continue turns while a real external task is already active, treat them as low-frequency monitoring passes roughly every {_AUTO_CONTINUE_MONITOR_INTERVAL_SECONDS} seconds rather than as a fast polling loop",
|
|
1257
|
+
"- auto_continue_prepare_protocol: in autonomous mode before a real long-running external task exists, rapid auto-continue passes around 0.2 seconds apart are acceptable only for active preparation, launch, or durable route closure work; they are not a substitute for starting the real task",
|
|
1258
|
+
"- long_run_ownership_protocol: real long-running execution should stay alive in detached bash_exec sessions or the runtime process it launched; do not rely on repeated model turns to simulate continuous execution",
|
|
1259
|
+
"- auto_continue_resume_protocol: on auto_continue turns, read the resume context spine first and continue from the latest durable user requirement, latest assistant checkpoint, latest run summary, recent memory cues, and current bash_exec state before changing route",
|
|
1058
1260
|
"- blocking_protocol: use reply_mode='blocking' only for true unresolved user decisions; ordinary progress updates should stay threaded and non-blocking",
|
|
1059
1261
|
"- credential_blocking_protocol: if continuation requires user-supplied external credentials or secrets such as an API key, GitHub key/token, or Hugging Face key/token, emit one structured blocking decision request that asks the user to provide the credential or choose an alternative route; do not invent placeholders or silently skip the blocked step",
|
|
1060
1262
|
"- credential_wait_protocol: if that credential request remains unanswered, keep the quest waiting rather than self-resolving; if you are resumed without new credentials and no other work is possible, a long low-frequency park such as `bash_exec(command='sleep 3600', mode='await', timeout_seconds=3700)` is acceptable to avoid busy-looping",
|
|
@@ -1066,6 +1268,7 @@ class PromptBuilder:
|
|
|
1066
1268
|
"- example_and_numbers_protocol: when it materially improves understanding, include one short example or 1 to 3 key numbers or comparisons instead of relying only on vague adjectives such as better, slower, or more stable.",
|
|
1067
1269
|
"- omission_protocol: for ordinary user-facing updates, omit file paths, file names, 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",
|
|
1068
1270
|
"- 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",
|
|
1271
|
+
"- micro_task_stop_rule: after a fresh user-message turn that was only `direct_answer` or `direct_action`, finish that unit and do not silently turn the same turn into a broader autonomous stage pass unless the user asked for it.",
|
|
1069
1272
|
"- watchdog_payload_protocol: if a tool result includes `watchdog_notes`, `progress_watchdog_note`, `visibility_watchdog_note`, or `state_change_watchdog_note`, treat that as an action item to inspect state and decide whether a fresh user-visible update is actually needed; do not emit duplicate progress by reflex",
|
|
1070
1273
|
"- 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",
|
|
1071
1274
|
"- stage_contract_protocol: stage-specific plan/checklist rules, milestone rules, literature rules, and writing rules belong in the requested skill; do not expect this runtime block to restate them",
|
|
@@ -1107,14 +1310,14 @@ class PromptBuilder:
|
|
|
1107
1310
|
if chinese_turn:
|
|
1108
1311
|
lines.extend(
|
|
1109
1312
|
[
|
|
1110
|
-
"- tone_hint:
|
|
1313
|
+
"- tone_hint: 使用自然、礼貌、专业、带一点活泼感的中文;必要时可自然称呼用户为“老师”,但不要每句重复;像靠谱又主动汇报进展的研究搭子,避免冷冰冰、官话化、机械模板腔;对真实好消息可自然用“都搞定啦”“有结果了”这种轻微庆祝开头,但下一句要立刻说清结果。",
|
|
1111
1314
|
"- connector_reply_hint: 在聊天面里优先简明说明当前状态、下一步动作、预计回传内容。",
|
|
1112
1315
|
]
|
|
1113
1316
|
)
|
|
1114
1317
|
else:
|
|
1115
1318
|
lines.extend(
|
|
1116
1319
|
[
|
|
1117
|
-
"- tone_hint: use a polite, professional,
|
|
1320
|
+
"- tone_hint: use a polite, professional, warm English tone; avoid sounding cold, bureaucratic, or like a monitoring log.",
|
|
1118
1321
|
"- connector_reply_hint: keep chat replies concise but operational, with explicit next steps and evidence targets.",
|
|
1119
1322
|
]
|
|
1120
1323
|
)
|
|
@@ -1212,13 +1415,51 @@ class PromptBuilder:
|
|
|
1212
1415
|
plan = STAGE_MEMORY_PLAN.get(stage, STAGE_MEMORY_PLAN["decision"])
|
|
1213
1416
|
quest_kinds = ", ".join(plan.get("quest", ())) or "none"
|
|
1214
1417
|
global_kinds = ", ".join(plan.get("global", ())) or "none"
|
|
1215
|
-
|
|
1216
|
-
[
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
)
|
|
1418
|
+
lines = [
|
|
1419
|
+
f"- stage_memory_rule: for `{stage}`, prefer quest memory kinds [{quest_kinds}] and global memory kinds [{global_kinds}] when memory lookup is needed.",
|
|
1420
|
+
"- memory_lookup_tool: call memory.list_recent(...) to recover context after pause/restart and memory.search(...) before repeating prior work.",
|
|
1421
|
+
"- memory_injection_rule: keep the injected memory compact, but do not drop all continuity on auto_continue turns; reuse a few recent durable cues directly when they materially anchor the next action.",
|
|
1422
|
+
]
|
|
1423
|
+
selected: list[dict] = []
|
|
1424
|
+
seen_paths: set[str] = set()
|
|
1425
|
+
for kind in plan.get("quest", ())[:2]:
|
|
1426
|
+
for card in self.memory_service.list_recent(scope="quest", quest_root=quest_root, limit=2, kind=kind)[:1]:
|
|
1427
|
+
self._append_priority_memory(
|
|
1428
|
+
selected,
|
|
1429
|
+
seen_paths,
|
|
1430
|
+
card=card,
|
|
1431
|
+
scope="quest",
|
|
1432
|
+
quest_root=quest_root,
|
|
1433
|
+
reason=f"recent quest memory for stage `{stage}`",
|
|
1434
|
+
)
|
|
1435
|
+
for kind in plan.get("global", ())[:2]:
|
|
1436
|
+
for card in self.memory_service.list_recent(scope="global", limit=2, kind=kind)[:1]:
|
|
1437
|
+
self._append_priority_memory(
|
|
1438
|
+
selected,
|
|
1439
|
+
seen_paths,
|
|
1440
|
+
card=card,
|
|
1441
|
+
scope="global",
|
|
1442
|
+
quest_root=quest_root,
|
|
1443
|
+
reason=f"recent global memory for stage `{stage}`",
|
|
1444
|
+
)
|
|
1445
|
+
for query in self._memory_queries(user_message)[:2]:
|
|
1446
|
+
for scope in ("quest", "global"):
|
|
1447
|
+
for card in self.memory_service.search(
|
|
1448
|
+
query,
|
|
1449
|
+
scope=scope if scope == "global" else "quest",
|
|
1450
|
+
quest_root=quest_root if scope == "quest" else None,
|
|
1451
|
+
limit=1,
|
|
1452
|
+
):
|
|
1453
|
+
self._append_priority_memory(
|
|
1454
|
+
selected,
|
|
1455
|
+
seen_paths,
|
|
1456
|
+
card=card,
|
|
1457
|
+
scope=scope,
|
|
1458
|
+
quest_root=quest_root,
|
|
1459
|
+
reason=f"matched current-turn query `{query}`",
|
|
1460
|
+
)
|
|
1461
|
+
lines.extend(["- selected_memory:", self._format_priority_memory(selected)])
|
|
1462
|
+
return "\n".join(lines)
|
|
1222
1463
|
|
|
1223
1464
|
def _append_priority_memory(
|
|
1224
1465
|
self,
|
|
@@ -52,11 +52,17 @@ def initial_quest_yaml(
|
|
|
52
52
|
startup_contract: dict | None = None,
|
|
53
53
|
) -> dict:
|
|
54
54
|
timestamp = utc_now()
|
|
55
|
+
workspace_mode = (
|
|
56
|
+
str((startup_contract or {}).get("workspace_mode") or "").strip().lower()
|
|
57
|
+
if isinstance(startup_contract, dict)
|
|
58
|
+
else ""
|
|
59
|
+
)
|
|
60
|
+
initial_status_value = "idle" if workspace_mode == "copilot" else "active"
|
|
55
61
|
return {
|
|
56
62
|
"quest_id": quest_id,
|
|
57
63
|
"title": title or goal,
|
|
58
64
|
"quest_root": str(quest_root.resolve()),
|
|
59
|
-
"status":
|
|
65
|
+
"status": initial_status_value,
|
|
60
66
|
"active_anchor": "baseline",
|
|
61
67
|
"baseline_gate": "pending",
|
|
62
68
|
"confirmed_baseline_ref": None,
|
|
@@ -100,7 +106,14 @@ def initial_plan() -> str:
|
|
|
100
106
|
)
|
|
101
107
|
|
|
102
108
|
|
|
103
|
-
def initial_status() -> str:
|
|
109
|
+
def initial_status(startup_contract: dict | None = None) -> str:
|
|
110
|
+
workspace_mode = (
|
|
111
|
+
str((startup_contract or {}).get("workspace_mode") or "").strip().lower()
|
|
112
|
+
if isinstance(startup_contract, dict)
|
|
113
|
+
else ""
|
|
114
|
+
)
|
|
115
|
+
if workspace_mode == "copilot":
|
|
116
|
+
return "# Status\n\nReady for your first instruction.\n"
|
|
104
117
|
return "# Status\n\nQuest created. Waiting for baseline setup or reuse.\n"
|
|
105
118
|
|
|
106
119
|
|