@selesai/code 0.5.29 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/dist/core/system-prompt.d.ts.map +1 -1
  4. package/dist/core/system-prompt.js +18 -0
  5. package/dist/core/system-prompt.js.map +1 -1
  6. package/dist/core/system-prompt.test.d.ts +2 -0
  7. package/dist/core/system-prompt.test.d.ts.map +1 -0
  8. package/dist/core/system-prompt.test.js +89 -0
  9. package/dist/core/system-prompt.test.js.map +1 -0
  10. package/dist/defaults/models.json +13 -45
  11. package/dist/defaults/settings.json +1 -2
  12. package/dist/extensions/copy-turn.test.ts +131 -0
  13. package/dist/extensions/copy-turn.ts +6 -1
  14. package/dist/extensions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  15. package/dist/extensions/package.json +0 -1
  16. package/dist/extensions/pi-subagents/CHANGELOG.md +3 -0
  17. package/dist/extensions/pi-subagents/README.md +27 -32
  18. package/dist/extensions/pi-subagents/agents/architect.md +4 -4
  19. package/dist/extensions/pi-subagents/agents/builder.md +5 -4
  20. package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
  21. package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
  22. package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
  23. package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
  24. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -0
  25. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +10 -9
  26. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +12 -11
  27. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +10 -11
  28. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +56 -9
  29. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
  30. package/dist/extensions/pi-subagents/src/api/preflight.ts +1 -1
  31. package/dist/extensions/pi-subagents/src/extension/index.ts +5 -1
  32. package/dist/extensions/pi-subagents/src/extension/schemas.ts +2 -2
  33. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +24 -7
  34. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +23 -5
  35. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +27 -1
  36. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +64 -6
  37. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +16 -1
  38. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +72 -18
  39. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +19 -5
  40. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +127 -31
  41. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +4 -6
  42. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
  43. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -0
  44. package/dist/extensions/pi-subagents/src/shared/types.ts +41 -2
  45. package/dist/extensions/pi-subagents/src/shared/utils.ts +29 -1
  46. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +5 -1
  47. package/dist/extensions/pi-subagents/src/tui/render.ts +28 -6
  48. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +111 -6
  49. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +74 -43
  50. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +36 -21
  51. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +5 -3
  52. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -8
  53. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +14 -7
  54. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +81 -5
  55. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +49 -10
  56. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
  57. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  58. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -6
  59. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
  60. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
  61. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +34 -0
  62. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +24 -0
  63. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +6 -1
  64. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +29 -0
  65. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +2 -0
  66. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +12 -0
  67. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
  68. package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
  69. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +23 -1
  70. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +60 -9
  71. package/dist/skills/ponytail/SKILL.md +1 -3
  72. package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
  73. package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
  74. package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
  75. package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
  76. package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
  77. package/package.json +2 -2
  78. package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
  79. package/dist/extensions/caveman/index.js +0 -118
  80. package/dist/extensions/caveman/package.json +0 -8
  81. package/dist/extensions/caveman/test/extension.test.js +0 -203
  82. package/dist/extensions/caveman/test/helpers.test.js +0 -58
  83. package/dist/skills/caveman/SKILL.md +0 -50
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@selesai/code` will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.6.1] - 2026-08-05
8
+
9
+ ### Added
10
+ - **Delegation contract and dynamic agent catalog.** `subagent({ action: "list" })` now exposes the runtime-resolved catalog as human text — executable/restricted status, source, aliases, role, normalized context, declared tools, and description — and as versioned machine metadata (`details.catalog` with `version`, `agents`, and `chains`). Delegation targets are selected from live discovery, not static prompt text.
11
+ - **Task-aware advisory routing.** An optional `task` on `action: "list"` appends a text-only recommendation for one canonical agent (implementation or read-only) based on the task intent, declared/inferred roles, and write-tool safety, or recovery guidance when intent is unknown or no safe candidate exists. The advice never launches, schedules, or persists work; execution remains an explicit separate call with the recommended canonical agent name.
12
+ - **Reference-first delegated results.** Normal completion now returns saved-output references plus status/lifecycle information instead of child prose. Full output is inspected through the saved output path, async status/transcript, or `resume`. Explicit `outputMode: "inline"` restores legacy full-output delivery, `output: false` disables durable output persistence with a bounded fallback, and per-child debug artifacts remain opt-in via `artifacts: true`.
13
+ - **Always-visible delegation guidance.** Default and custom system prompts now carry the active-tool delegation contract: inspect the delegation catalog before selecting, keep tiny targeted reads and simple answers local, delegate broad local investigation, external research, and mutation/implementation work, and keep the parent as decision-maker and normally the sole writer.
14
+
15
+ ### Changed
16
+ - The parent `subagent` tool description now requires catalog-first, executable-only agent selection and no longer hardcodes bundled agent names in management examples; the routing contract is embedded in mandatory safety guidance so custom descriptions cannot remove it.
17
+ - Bundled agents now declare accurate descriptions and `acceptanceRole` (`writer` for `builder`, `read-only` for the advisory roles) so catalog output communicates routing capability without granting tools.
18
+
19
+ ### Fixed
20
+ - Corrected impossible and stale bundled-agent contracts: the architect no longer relies on unavailable question/subagent tools, the builder's `contact_supervisor` use is conditional on bridge-injected instructions, and the researcher's grep.app tools are declared as MCP tools.
21
+ - Aligned bundled-agent documentation with runtime: exactly six builtin roles with accurate fresh/fork context defaults (only `architect` and `recapper` fork; `builder`, `commentator`, `explorer`, and `researcher` are fresh) and `.selesai` discovery paths instead of stale `.pi` references.
22
+
23
+ ### Not included
24
+ - Phase 4 of the subagent-delegation program (optional enforcement of delegation policy) is intentionally omitted from 0.6.1. Routing remains advisory, and launch-time preflight remains the enforcement point.
package/README.md CHANGED
@@ -124,7 +124,7 @@ Selesai also bundles small extensions that solve common coding-agent failures:
124
124
 
125
125
  Skills are shipped with Selesai and loaded at boot. They include:
126
126
 
127
- - `ponytail` and `caveman` for minimal and compressed communication modes
127
+ - `ponytail` for minimal implementation mode and review helpers
128
128
  - `grill-me` for requirement and design stress-testing
129
129
  - `planger` and `implanger` for decomposed planning and execution
130
130
  - `handoff` and `handoff-text` for session continuity
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,oCAAoC;IACpC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CA6I3E","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatAgentsForPrompt, type AgentPersona } from \"./agents.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Pre-loaded subagent personas. */\n\tagents?: AgentPersona[];\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t} = options;\n\tconst promptCwd = cwd.replace(/\\\\/g, \"/\");\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\tconst agents = options.agents ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\t\tif (customPromptHasRead && agents.length > 0) {\n\t\t\tprompt += formatAgentsForPrompt(agents);\n\t\t}\n\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside SelesaiCode fork of Pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\tif (hasRead && agents.length > 0) {\n\t\tprompt += formatAgentsForPrompt(agents);\n\t}\n\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAUhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,oCAAoC;IACpC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CA0J3E","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatAgentsForPrompt, type AgentPersona } from \"./agents.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\n/**\n * Generic delegation-routing threshold for main sessions. The text is\n * self-conditioning (\"when a delegation/subagent tool is available\"), so\n * sessions without such a tool are unaffected.\n */\nconst DELEGATION_ROUTING_GUIDELINE =\n\t\"When a delegation/subagent tool is available: keep tiny targeted reads and simple answers local; send broad local investigation, external research, and mutation/implementation work to a capable delegated agent, inspecting the delegation catalog/list before selecting; the parent remains the decision-maker and normally the sole writer.\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Pre-loaded subagent personas. */\n\tagents?: AgentPersona[];\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t} = options;\n\tconst promptCwd = cwd.replace(/\\\\/g, \"/\");\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\tconst agents = options.agents ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\t\tif (customPromptHasRead && agents.length > 0) {\n\t\t\tprompt += formatAgentsForPrompt(agents);\n\t\t}\n\n\t\t// Render active-tool guidelines (e.g., mandatory delegation contracts) in\n\t\t// custom-prompt sessions too, without requiring the read tool. The caller's\n\t\t// custom prompt text stays unchanged; a clearly delimited section is appended\n\t\t// only when non-empty guidelines exist.\n\t\tconst activeGuidelines = (promptGuidelines ?? [])\n\t\t\t.map((guideline) => guideline.trim())\n\t\t\t.filter((guideline) => guideline.length > 0)\n\t\t\t.filter((guideline, index, all) => all.indexOf(guideline) === index);\n\t\tif (activeGuidelines.length > 0) {\n\t\t\tprompt += `\\n\\nActive tool guidelines:\\n${activeGuidelines.map((guideline) => `- ${guideline}`).join(\"\\n\")}`;\n\t\t}\n\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(DELEGATION_ROUTING_GUIDELINE);\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside SelesaiCode fork of Pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\tif (hasRead && agents.length > 0) {\n\t\tprompt += formatAgentsForPrompt(agents);\n\t}\n\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
@@ -4,6 +4,12 @@
4
4
  import { getDocsPath, getExamplesPath, getReadmePath } from "../config.js";
5
5
  import { formatAgentsForPrompt } from "./agents.js";
6
6
  import { formatSkillsForPrompt } from "./skills.js";
7
+ /**
8
+ * Generic delegation-routing threshold for main sessions. The text is
9
+ * self-conditioning ("when a delegation/subagent tool is available"), so
10
+ * sessions without such a tool are unaffected.
11
+ */
12
+ const DELEGATION_ROUTING_GUIDELINE = "When a delegation/subagent tool is available: keep tiny targeted reads and simple answers local; send broad local investigation, external research, and mutation/implementation work to a capable delegated agent, inspecting the delegation catalog/list before selecting; the parent remains the decision-maker and normally the sole writer.";
7
13
  /** Build the system prompt with tools, guidelines, and context */
8
14
  export function buildSystemPrompt(options) {
9
15
  const { customPrompt, selectedTools, toolSnippets, promptGuidelines, appendSystemPrompt, cwd, contextFiles: providedContextFiles, skills: providedSkills, } = options;
@@ -34,6 +40,17 @@ export function buildSystemPrompt(options) {
34
40
  if (customPromptHasRead && agents.length > 0) {
35
41
  prompt += formatAgentsForPrompt(agents);
36
42
  }
43
+ // Render active-tool guidelines (e.g., mandatory delegation contracts) in
44
+ // custom-prompt sessions too, without requiring the read tool. The caller's
45
+ // custom prompt text stays unchanged; a clearly delimited section is appended
46
+ // only when non-empty guidelines exist.
47
+ const activeGuidelines = (promptGuidelines ?? [])
48
+ .map((guideline) => guideline.trim())
49
+ .filter((guideline) => guideline.length > 0)
50
+ .filter((guideline, index, all) => all.indexOf(guideline) === index);
51
+ if (activeGuidelines.length > 0) {
52
+ prompt += `\n\nActive tool guidelines:\n${activeGuidelines.map((guideline) => `- ${guideline}`).join("\n")}`;
53
+ }
37
54
  prompt += `\nCurrent working directory: ${promptCwd}`;
38
55
  return prompt;
39
56
  }
@@ -72,6 +89,7 @@ export function buildSystemPrompt(options) {
72
89
  }
73
90
  }
74
91
  // Always include these
92
+ addGuideline(DELEGATION_ROUTING_GUIDELINE);
75
93
  addGuideline("Be concise in your responses");
76
94
  addGuideline("Show file paths clearly when working with files");
77
95
  const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatAgentsForPrompt, type AgentPersona } from \"./agents.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Pre-loaded subagent personas. */\n\tagents?: AgentPersona[];\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t} = options;\n\tconst promptCwd = cwd.replace(/\\\\/g, \"/\");\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\tconst agents = options.agents ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\t\tif (customPromptHasRead && agents.length > 0) {\n\t\t\tprompt += formatAgentsForPrompt(agents);\n\t\t}\n\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside SelesaiCode fork of Pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\tif (hasRead && agents.length > 0) {\n\t\tprompt += formatAgentsForPrompt(agents);\n\t}\n\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatAgentsForPrompt, type AgentPersona } from \"./agents.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\n/**\n * Generic delegation-routing threshold for main sessions. The text is\n * self-conditioning (\"when a delegation/subagent tool is available\"), so\n * sessions without such a tool are unaffected.\n */\nconst DELEGATION_ROUTING_GUIDELINE =\n\t\"When a delegation/subagent tool is available: keep tiny targeted reads and simple answers local; send broad local investigation, external research, and mutation/implementation work to a capable delegated agent, inspecting the delegation catalog/list before selecting; the parent remains the decision-maker and normally the sole writer.\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** Pre-loaded subagent personas. */\n\tagents?: AgentPersona[];\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t} = options;\n\tconst promptCwd = cwd.replace(/\\\\/g, \"/\");\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\tconst agents = options.agents ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\t\tif (customPromptHasRead && agents.length > 0) {\n\t\t\tprompt += formatAgentsForPrompt(agents);\n\t\t}\n\n\t\t// Render active-tool guidelines (e.g., mandatory delegation contracts) in\n\t\t// custom-prompt sessions too, without requiring the read tool. The caller's\n\t\t// custom prompt text stays unchanged; a clearly delimited section is appended\n\t\t// only when non-empty guidelines exist.\n\t\tconst activeGuidelines = (promptGuidelines ?? [])\n\t\t\t.map((guideline) => guideline.trim())\n\t\t\t.filter((guideline) => guideline.length > 0)\n\t\t\t.filter((guideline, index, all) => all.indexOf(guideline) === index);\n\t\tif (activeGuidelines.length > 0) {\n\t\t\tprompt += `\\n\\nActive tool guidelines:\\n${activeGuidelines.map((guideline) => `- ${guideline}`).join(\"\\n\")}`;\n\t\t}\n\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(DELEGATION_ROUTING_GUIDELINE);\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant operating inside SelesaiCode fork of Pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\tif (hasRead && agents.length > 0) {\n\t\tprompt += formatAgentsForPrompt(agents);\n\t}\n\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=system-prompt.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.test.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { buildSystemPrompt } from \"./system-prompt.ts\";\nimport type { AgentPersona } from \"./agents.ts\";\nimport type { Skill } from \"./skills.ts\";\n\ndescribe(\"buildSystemPrompt\", () => {\n\tit(\"includes the generic delegation-routing threshold in the default prompt\", () => {\n\t\tconst prompt = buildSystemPrompt({ cwd: \"/tmp/project\" });\n\n\t\texpect(prompt).toMatch(/When a delegation\\/subagent tool is available/i);\n\t\texpect(prompt).toMatch(/keep tiny targeted reads and simple answers local/i);\n\t\texpect(prompt).toMatch(/broad local investigation, external research, and mutation\\/implementation work/i);\n\t\texpect(prompt).toMatch(/capable delegated agent/i);\n\t\texpect(prompt).toMatch(/inspecting the delegation catalog\\/list before selecting/i);\n\t\texpect(prompt).toMatch(/parent remains the decision-maker and normally the sole writer/i);\n\t\texpect(prompt).toMatch(/Guidelines:/);\n\t\texpect(prompt).toContain(\"Current working directory: /tmp/project\");\n\t});\n\n\tit(\"keeps the default tool snippets, skills, and agents XML read-gated\", () => {\n\t\tconst skill: Skill = {\n\t\t\tname: \"test-skill\",\n\t\t\tdescription: \"Test skill description\",\n\t\t\tfilePath: \"/tmp/skills/test-skill/SKILL.md\",\n\t\t\tbaseDir: \"/tmp/skills/test-skill\",\n\t\t\tsourceInfo: { type: \"file\", path: \"/tmp/skills/test-skill/SKILL.md\" } as never,\n\t\t\tdisableModelInvocation: false,\n\t\t};\n\t\tconst agent: AgentPersona = {\n\t\t\tname: \"test-agent\",\n\t\t\tdescription: \"Test agent description\",\n\t\t\tfilePath: \"/tmp/agents/test-agent.md\",\n\t\t\tbaseDir: \"/tmp/agents\",\n\t\t\tsourceInfo: { type: \"file\", path: \"/tmp/agents/test-agent.md\" } as never,\n\t\t\tfrontmatter: {} as never,\n\t\t};\n\n\t\tconst withoutRead = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tselectedTools: [\"bash\"],\n\t\t\tskills: [skill],\n\t\t\tagents: [agent],\n\t\t});\n\t\texpect(withoutRead).not.toContain(\"<available_skills>\");\n\t\texpect(withoutRead).not.toContain(\"<available_agents>\");\n\t\texpect(withoutRead).toContain(\"Current working directory: /tmp/project\");\n\n\t\tconst withRead = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tselectedTools: [\"read\", \"bash\"],\n\t\t\tskills: [skill],\n\t\t\tagents: [agent],\n\t\t});\n\t\texpect(withRead).toContain(\"<available_skills>\");\n\t\texpect(withRead).toContain(\"<available_agents>\");\n\t\texpect(withRead).toContain(\"test-skill\");\n\t\texpect(withRead).toContain(\"test-agent\");\n\t});\n\n\tit(\"appends active promptGuidelines to a custom system prompt unchanged\", () => {\n\t\tconst prompt = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tcustomPrompt: \"You are my custom assistant.\",\n\t\t\tpromptGuidelines: ['Before executing, call { action: \"list\" }.'],\n\t\t});\n\n\t\texpect(prompt).toContain(\"You are my custom assistant.\");\n\t\texpect(prompt).toContain(\"Active tool guidelines:\");\n\t\texpect(prompt).toContain('- Before executing, call { action: \"list\" }.');\n\t\texpect(prompt).toContain(\"Current working directory: /tmp/project\");\n\t});\n\n\tit(\"deduplicates and trims active promptGuidelines in custom prompts\", () => {\n\t\tconst prompt = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tcustomPrompt: \"Custom.\",\n\t\t\tpromptGuidelines: [\" Same guideline \", \"Same guideline\", \"\", \" \"],\n\t\t});\n\n\t\texpect(prompt).toContain(\"Active tool guidelines:\");\n\t\texpect(prompt.match(/- Same guideline/g)).toHaveLength(1);\n\t});\n\n\tit(\"does not add an empty guideline section when no promptGuidelines are supplied\", () => {\n\t\tconst prompt = buildSystemPrompt({ cwd: \"/tmp/project\", customPrompt: \"Custom text.\" });\n\n\t\texpect(prompt).toContain(\"Custom text.\");\n\t\texpect(prompt).not.toContain(\"Active tool guidelines:\");\n\t\texpect(prompt).toContain(\"Current working directory: /tmp/project\");\n\t});\n\n\tit(\"renders active promptGuidelines in custom prompts without requiring read\", () => {\n\t\tconst prompt = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tcustomPrompt: \"Custom.\",\n\t\t\tselectedTools: [\"bash\"],\n\t\t\tpromptGuidelines: [\"Mandatory active-tool guidance.\"],\n\t\t});\n\n\t\texpect(prompt).toContain(\"- Mandatory active-tool guidance.\");\n\t\texpect(prompt).not.toContain(\"<available_agents>\");\n\t});\n});\n"]}
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildSystemPrompt } from "./system-prompt.js";
3
+ describe("buildSystemPrompt", () => {
4
+ it("includes the generic delegation-routing threshold in the default prompt", () => {
5
+ const prompt = buildSystemPrompt({ cwd: "/tmp/project" });
6
+ expect(prompt).toMatch(/When a delegation\/subagent tool is available/i);
7
+ expect(prompt).toMatch(/keep tiny targeted reads and simple answers local/i);
8
+ expect(prompt).toMatch(/broad local investigation, external research, and mutation\/implementation work/i);
9
+ expect(prompt).toMatch(/capable delegated agent/i);
10
+ expect(prompt).toMatch(/inspecting the delegation catalog\/list before selecting/i);
11
+ expect(prompt).toMatch(/parent remains the decision-maker and normally the sole writer/i);
12
+ expect(prompt).toMatch(/Guidelines:/);
13
+ expect(prompt).toContain("Current working directory: /tmp/project");
14
+ });
15
+ it("keeps the default tool snippets, skills, and agents XML read-gated", () => {
16
+ const skill = {
17
+ name: "test-skill",
18
+ description: "Test skill description",
19
+ filePath: "/tmp/skills/test-skill/SKILL.md",
20
+ baseDir: "/tmp/skills/test-skill",
21
+ sourceInfo: { type: "file", path: "/tmp/skills/test-skill/SKILL.md" },
22
+ disableModelInvocation: false,
23
+ };
24
+ const agent = {
25
+ name: "test-agent",
26
+ description: "Test agent description",
27
+ filePath: "/tmp/agents/test-agent.md",
28
+ baseDir: "/tmp/agents",
29
+ sourceInfo: { type: "file", path: "/tmp/agents/test-agent.md" },
30
+ frontmatter: {},
31
+ };
32
+ const withoutRead = buildSystemPrompt({
33
+ cwd: "/tmp/project",
34
+ selectedTools: ["bash"],
35
+ skills: [skill],
36
+ agents: [agent],
37
+ });
38
+ expect(withoutRead).not.toContain("<available_skills>");
39
+ expect(withoutRead).not.toContain("<available_agents>");
40
+ expect(withoutRead).toContain("Current working directory: /tmp/project");
41
+ const withRead = buildSystemPrompt({
42
+ cwd: "/tmp/project",
43
+ selectedTools: ["read", "bash"],
44
+ skills: [skill],
45
+ agents: [agent],
46
+ });
47
+ expect(withRead).toContain("<available_skills>");
48
+ expect(withRead).toContain("<available_agents>");
49
+ expect(withRead).toContain("test-skill");
50
+ expect(withRead).toContain("test-agent");
51
+ });
52
+ it("appends active promptGuidelines to a custom system prompt unchanged", () => {
53
+ const prompt = buildSystemPrompt({
54
+ cwd: "/tmp/project",
55
+ customPrompt: "You are my custom assistant.",
56
+ promptGuidelines: ['Before executing, call { action: "list" }.'],
57
+ });
58
+ expect(prompt).toContain("You are my custom assistant.");
59
+ expect(prompt).toContain("Active tool guidelines:");
60
+ expect(prompt).toContain('- Before executing, call { action: "list" }.');
61
+ expect(prompt).toContain("Current working directory: /tmp/project");
62
+ });
63
+ it("deduplicates and trims active promptGuidelines in custom prompts", () => {
64
+ const prompt = buildSystemPrompt({
65
+ cwd: "/tmp/project",
66
+ customPrompt: "Custom.",
67
+ promptGuidelines: [" Same guideline ", "Same guideline", "", " "],
68
+ });
69
+ expect(prompt).toContain("Active tool guidelines:");
70
+ expect(prompt.match(/- Same guideline/g)).toHaveLength(1);
71
+ });
72
+ it("does not add an empty guideline section when no promptGuidelines are supplied", () => {
73
+ const prompt = buildSystemPrompt({ cwd: "/tmp/project", customPrompt: "Custom text." });
74
+ expect(prompt).toContain("Custom text.");
75
+ expect(prompt).not.toContain("Active tool guidelines:");
76
+ expect(prompt).toContain("Current working directory: /tmp/project");
77
+ });
78
+ it("renders active promptGuidelines in custom prompts without requiring read", () => {
79
+ const prompt = buildSystemPrompt({
80
+ cwd: "/tmp/project",
81
+ customPrompt: "Custom.",
82
+ selectedTools: ["bash"],
83
+ promptGuidelines: ["Mandatory active-tool guidance."],
84
+ });
85
+ expect(prompt).toContain("- Mandatory active-tool guidance.");
86
+ expect(prompt).not.toContain("<available_agents>");
87
+ });
88
+ });
89
+ //# sourceMappingURL=system-prompt.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.test.js","sourceRoot":"","sources":["../../src/core/system-prompt.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { buildSystemPrompt } from \"./system-prompt.ts\";\nimport type { AgentPersona } from \"./agents.ts\";\nimport type { Skill } from \"./skills.ts\";\n\ndescribe(\"buildSystemPrompt\", () => {\n\tit(\"includes the generic delegation-routing threshold in the default prompt\", () => {\n\t\tconst prompt = buildSystemPrompt({ cwd: \"/tmp/project\" });\n\n\t\texpect(prompt).toMatch(/When a delegation\\/subagent tool is available/i);\n\t\texpect(prompt).toMatch(/keep tiny targeted reads and simple answers local/i);\n\t\texpect(prompt).toMatch(/broad local investigation, external research, and mutation\\/implementation work/i);\n\t\texpect(prompt).toMatch(/capable delegated agent/i);\n\t\texpect(prompt).toMatch(/inspecting the delegation catalog\\/list before selecting/i);\n\t\texpect(prompt).toMatch(/parent remains the decision-maker and normally the sole writer/i);\n\t\texpect(prompt).toMatch(/Guidelines:/);\n\t\texpect(prompt).toContain(\"Current working directory: /tmp/project\");\n\t});\n\n\tit(\"keeps the default tool snippets, skills, and agents XML read-gated\", () => {\n\t\tconst skill: Skill = {\n\t\t\tname: \"test-skill\",\n\t\t\tdescription: \"Test skill description\",\n\t\t\tfilePath: \"/tmp/skills/test-skill/SKILL.md\",\n\t\t\tbaseDir: \"/tmp/skills/test-skill\",\n\t\t\tsourceInfo: { type: \"file\", path: \"/tmp/skills/test-skill/SKILL.md\" } as never,\n\t\t\tdisableModelInvocation: false,\n\t\t};\n\t\tconst agent: AgentPersona = {\n\t\t\tname: \"test-agent\",\n\t\t\tdescription: \"Test agent description\",\n\t\t\tfilePath: \"/tmp/agents/test-agent.md\",\n\t\t\tbaseDir: \"/tmp/agents\",\n\t\t\tsourceInfo: { type: \"file\", path: \"/tmp/agents/test-agent.md\" } as never,\n\t\t\tfrontmatter: {} as never,\n\t\t};\n\n\t\tconst withoutRead = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tselectedTools: [\"bash\"],\n\t\t\tskills: [skill],\n\t\t\tagents: [agent],\n\t\t});\n\t\texpect(withoutRead).not.toContain(\"<available_skills>\");\n\t\texpect(withoutRead).not.toContain(\"<available_agents>\");\n\t\texpect(withoutRead).toContain(\"Current working directory: /tmp/project\");\n\n\t\tconst withRead = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tselectedTools: [\"read\", \"bash\"],\n\t\t\tskills: [skill],\n\t\t\tagents: [agent],\n\t\t});\n\t\texpect(withRead).toContain(\"<available_skills>\");\n\t\texpect(withRead).toContain(\"<available_agents>\");\n\t\texpect(withRead).toContain(\"test-skill\");\n\t\texpect(withRead).toContain(\"test-agent\");\n\t});\n\n\tit(\"appends active promptGuidelines to a custom system prompt unchanged\", () => {\n\t\tconst prompt = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tcustomPrompt: \"You are my custom assistant.\",\n\t\t\tpromptGuidelines: ['Before executing, call { action: \"list\" }.'],\n\t\t});\n\n\t\texpect(prompt).toContain(\"You are my custom assistant.\");\n\t\texpect(prompt).toContain(\"Active tool guidelines:\");\n\t\texpect(prompt).toContain('- Before executing, call { action: \"list\" }.');\n\t\texpect(prompt).toContain(\"Current working directory: /tmp/project\");\n\t});\n\n\tit(\"deduplicates and trims active promptGuidelines in custom prompts\", () => {\n\t\tconst prompt = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tcustomPrompt: \"Custom.\",\n\t\t\tpromptGuidelines: [\" Same guideline \", \"Same guideline\", \"\", \" \"],\n\t\t});\n\n\t\texpect(prompt).toContain(\"Active tool guidelines:\");\n\t\texpect(prompt.match(/- Same guideline/g)).toHaveLength(1);\n\t});\n\n\tit(\"does not add an empty guideline section when no promptGuidelines are supplied\", () => {\n\t\tconst prompt = buildSystemPrompt({ cwd: \"/tmp/project\", customPrompt: \"Custom text.\" });\n\n\t\texpect(prompt).toContain(\"Custom text.\");\n\t\texpect(prompt).not.toContain(\"Active tool guidelines:\");\n\t\texpect(prompt).toContain(\"Current working directory: /tmp/project\");\n\t});\n\n\tit(\"renders active promptGuidelines in custom prompts without requiring read\", () => {\n\t\tconst prompt = buildSystemPrompt({\n\t\t\tcwd: \"/tmp/project\",\n\t\t\tcustomPrompt: \"Custom.\",\n\t\t\tselectedTools: [\"bash\"],\n\t\t\tpromptGuidelines: [\"Mandatory active-tool guidance.\"],\n\t\t});\n\n\t\texpect(prompt).toContain(\"- Mandatory active-tool guidance.\");\n\t\texpect(prompt).not.toContain(\"<available_agents>\");\n\t});\n});\n"]}
@@ -17,9 +17,9 @@
17
17
  "input": ["text"],
18
18
  "contextWindow": 1000000,
19
19
  "cost": {
20
- "input": 0.14,
21
- "output": 0.28,
22
- "cacheRead": 0.0028,
20
+ "input": 0.15,
21
+ "output": 0.3,
22
+ "cacheRead": 0.003,
23
23
  "cacheWrite": 0
24
24
  },
25
25
  "compat": {
@@ -29,15 +29,15 @@
29
29
  }
30
30
  },
31
31
  {
32
- "id": "kimi-k2.7-code",
33
- "name": "Kimi K2.7 Code",
32
+ "id": "kimi-k3",
33
+ "name": "Kimi K3",
34
34
  "reasoning": true,
35
35
  "input": ["text", "image"],
36
36
  "contextWindow": 262128,
37
37
  "cost": {
38
- "input": 0.95,
39
- "output": 4,
40
- "cacheRead": 0.2375,
38
+ "input": 3,
39
+ "output": 15,
40
+ "cacheRead": 1.5,
41
41
  "cacheWrite": 0
42
42
  },
43
43
  "compat": {
@@ -46,15 +46,15 @@
46
46
  }
47
47
  },
48
48
  {
49
- "id": "kimi-k2.6",
50
- "name": "Kimi K2.6",
49
+ "id": "kimi-k2.7-code",
50
+ "name": "Kimi K2.7 Code",
51
51
  "reasoning": true,
52
52
  "input": ["text", "image"],
53
53
  "contextWindow": 262128,
54
54
  "cost": {
55
- "input": 0.69,
56
- "output": 3.22,
57
- "cacheRead": 0.1725,
55
+ "input": 0.95,
56
+ "output": 4,
57
+ "cacheRead": 0.2375,
58
58
  "cacheWrite": 0
59
59
  },
60
60
  "compat": {
@@ -96,22 +96,6 @@
96
96
  "supportsReasoningEffort": false
97
97
  }
98
98
  },
99
- {
100
- "id": "qwen3.6-35b-fast",
101
- "name": "Qwen3.6 35B Fast",
102
- "reasoning": false,
103
- "input": ["text", "image"],
104
- "contextWindow": 131056,
105
- "cost": {
106
- "input": 0.29,
107
- "output": 1.15,
108
- "cacheRead": 0.0725,
109
- "cacheWrite": 0
110
- },
111
- "compat": {
112
- "supportsReasoningEffort": false
113
- }
114
- },
115
99
  {
116
100
  "id": "glm-5.2",
117
101
  "name": "GLM-5.2",
@@ -128,22 +112,6 @@
128
112
  "supportsDeveloperRole": false,
129
113
  "supportsReasoningEffort": true
130
114
  }
131
- },
132
- {
133
- "id": "glm-5.2-fast",
134
- "name": "GLM-5.2 Fast",
135
- "reasoning": false,
136
- "input": ["text"],
137
- "contextWindow": 1000000,
138
- "cost": {
139
- "input": 1.45,
140
- "output": 4.5,
141
- "cacheRead": 0.3625,
142
- "cacheWrite": 0
143
- },
144
- "compat": {
145
- "supportsReasoningEffort": true
146
- }
147
115
  }
148
116
  ]
149
117
  }
@@ -25,8 +25,7 @@
25
25
  "lastChangelogVersion": "0.80.2",
26
26
  "packages": [
27
27
  "git:github.com/MasuRii/pi-tool-display",
28
- "git:github.com/omaclaren/pi-markdown-preview",
29
- "git:github.com/aliou/pi-guardrails"
28
+ "git:github.com/omaclaren/pi-markdown-preview"
30
29
  ],
31
30
  "quietStartup": true,
32
31
  "retry": {
@@ -0,0 +1,131 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import copyTurnExtension from "./copy-turn.ts";
3
+
4
+ vi.mock("@selesai/code", () => ({
5
+ copyToClipboard: vi.fn(),
6
+ }));
7
+
8
+ import { copyToClipboard } from "@selesai/code";
9
+
10
+ type AnyMessage = any;
11
+ type Handler = (event: any, ctx: any) => any;
12
+ type Command = { description: string; handler: (args: string, ctx: any) => Promise<void> };
13
+
14
+ const MARK_RE = /\n?\n?\s*⧉ copy(?:\s+\w+)?: \/cp ([0-9a-f]{6,12})\s*$/;
15
+
16
+ function hashFromMarkedText(text: string): string {
17
+ const m = text.match(MARK_RE);
18
+ if (!m) throw new Error(`no copy marker found in: ${JSON.stringify(text)}`);
19
+ return m[1]!;
20
+ }
21
+
22
+ function createHarness() {
23
+ const handlers = new Map<string, Handler>();
24
+ const commands = new Map<string, Command>();
25
+ const sent: AnyMessage[] = [];
26
+ const pi = {
27
+ on: vi.fn((event: string, handler: Handler) => {
28
+ handlers.set(event, handler);
29
+ }),
30
+ registerCommand: vi.fn((name: string, opts: Command) => {
31
+ commands.set(name, opts);
32
+ }),
33
+ registerMessageRenderer: vi.fn(),
34
+ sendMessage: vi.fn((msg: AnyMessage) => {
35
+ sent.push(msg);
36
+ }),
37
+ };
38
+ copyTurnExtension(pi as any);
39
+ return { handlers, commands, sent, pi };
40
+ }
41
+
42
+ function branchCtx(messages: AnyMessage[]) {
43
+ return {
44
+ sessionManager: { getBranch: () => messages.map((message) => ({ type: "message", message })) },
45
+ ui: { notify: vi.fn() },
46
+ };
47
+ }
48
+
49
+ describe("copy-turn hash stability", () => {
50
+ beforeEach(() => {
51
+ vi.clearAllMocks();
52
+ });
53
+
54
+ it("recomputes the displayed assistant hash from the persisted message despite metadata changes", async () => {
55
+ const { handlers, commands } = createHarness();
56
+ const messageEnd = handlers.get("message_end")!;
57
+
58
+ // Transient message as seen by message_end (before persistence).
59
+ const transient = {
60
+ role: "assistant",
61
+ content: [{ type: "text", text: "Fix the copy bug now." }],
62
+ stopReason: "done",
63
+ };
64
+ const result = await messageEnd({ type: "message_end", message: transient }, {});
65
+ const markedText = result.message.content[0].text as string;
66
+ const displayedHash = hashFromMarkedText(markedText);
67
+ expect(displayedHash).toMatch(/^[0-9a-f]{6}$/);
68
+
69
+ // Persisted variant: same content plus the copy marker and changed metadata.
70
+ const persisted = {
71
+ ...result.message,
72
+ details: { latencyMs: 42 },
73
+ stopReason: "error",
74
+ timestamp: 1_700_000_000_000,
75
+ sessionId: "s-1",
76
+ msgId: "m-1",
77
+ usage: { inputTokens: 1, outputTokens: 2 },
78
+ };
79
+
80
+ const ctx = branchCtx([persisted]);
81
+ const cmd = commands.get("cp")!;
82
+ await cmd.handler(displayedHash, ctx);
83
+
84
+ expect(copyToClipboard).toHaveBeenCalledTimes(1);
85
+ expect(copyToClipboard).toHaveBeenCalledWith("Fix the copy bug now.");
86
+ expect(ctx.ui.notify).not.toHaveBeenCalledWith(`No message for hash ${displayedHash}`, "warning");
87
+ expect(ctx.ui.notify).toHaveBeenCalledWith(`Copied ${displayedHash} (21 chars)`, "info");
88
+ });
89
+
90
+ it("resolves the displayed user hash from the persisted user message despite metadata changes", async () => {
91
+ const { handlers, commands, sent } = createHarness();
92
+ const messageEnd = handlers.get("message_end")!;
93
+
94
+ await messageEnd({ type: "message_end", message: { role: "user", content: "show me the files" } }, {});
95
+ const copyRow = sent.find((m) => m.customType === "copy-turn");
96
+ expect(copyRow).toBeDefined();
97
+ const displayedHash: string = copyRow.details.hash;
98
+
99
+ // Persisted user message with metadata the transient object did not have.
100
+ const persisted = {
101
+ role: "user",
102
+ content: "show me the files",
103
+ details: { reranked: true },
104
+ timestamp: 42,
105
+ };
106
+
107
+ const ctx = branchCtx([persisted]);
108
+ const cmd = commands.get("cp")!;
109
+ await cmd.handler(displayedHash, ctx);
110
+
111
+ expect(copyToClipboard).toHaveBeenCalledTimes(1);
112
+ expect(copyToClipboard).toHaveBeenCalledWith("show me the files");
113
+ expect(ctx.ui.notify).not.toHaveBeenCalledWith(`No message for hash ${displayedHash}`, "warning");
114
+ });
115
+
116
+ it("strips existing copy markers before hashing", async () => {
117
+ const { handlers } = createHarness();
118
+ const messageEnd = handlers.get("message_end")!;
119
+
120
+ const plain = { role: "assistant", content: [{ type: "text", text: "Same content" }] };
121
+ const alreadyMarked = {
122
+ role: "assistant",
123
+ content: [{ type: "text", text: "Same content\n\n⧉ copy assistant: /cp abc123" }],
124
+ };
125
+
126
+ const r1 = await messageEnd({ type: "message_end", message: plain }, {});
127
+ const r2 = await messageEnd({ type: "message_end", message: alreadyMarked }, {});
128
+
129
+ expect(hashFromMarkedText(r1.message.content[0].text)).toBe(hashFromMarkedText(r2.message.content[0].text));
130
+ });
131
+ });
@@ -39,7 +39,12 @@ function resultOf(message: AnyMessage): string {
39
39
  }
40
40
 
41
41
  function hashFor(m: AnyMessage): string {
42
- return createHash("sha1").update(JSON.stringify(cleanMessage(m))).digest("hex").slice(0, HASH_LEN);
42
+ // Hash the stable copy identity (role + cleaned copy result) only, so the hash
43
+ // shown at display time (computed from the transient message) still matches the
44
+ // one /cp recomputes from the persisted message, whose metadata may have changed.
45
+ const role = m.role ?? "message";
46
+ const text = resultOf(m);
47
+ return createHash("sha1").update(JSON.stringify([role, text])).digest("hex").slice(0, HASH_LEN);
43
48
  }
44
49
 
45
50
  function addMarker(m: AnyMessage): AnyMessage {
@@ -0,0 +1 @@
1
+ {"version":"4.1.9","results":[[":handoff-new.test.ts",{"duration":0,"failed":true}]]}
@@ -5,7 +5,6 @@
5
5
  "description": "Built-in extensions shipped with @selesai/code, loaded via additionalExtensionPaths at boot.",
6
6
  "pi": {
7
7
  "extensions": [
8
- "./caveman/index.js",
9
8
  "./copy-turn.ts",
10
9
  "./context-compaction-reminder.ts",
11
10
  "./pi-intercom/index.ts",
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Changed
6
+ - Made per-child debug artifacts opt-in with `artifacts: true` instead of writing prompt, output, transcript, and metadata copies for every run by default.
7
+
5
8
  ## [0.40.0] - 2026-08-01
6
9
 
7
10
  ### Added