dsh-pstack 0.2.0-beta.1 → 0.2.0-beta.3

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 (261) hide show
  1. package/LICENSE +2 -3
  2. package/README.md +135 -84
  3. package/README.zh.md +185 -0
  4. package/cordis.patch.yml +97 -27
  5. package/dist/host-contract-CUPSpeNk.d.mts +22 -0
  6. package/dist/index.d.mts +49 -238
  7. package/dist/index.mjs +250 -123
  8. package/dist/models.d.mts +44 -0
  9. package/dist/models.mjs +201 -0
  10. package/dist/paths-BT77khwm.mjs +48 -0
  11. package/dist/pstack/LICENSE +21 -0
  12. package/dist/pstack/README.md +291 -0
  13. package/dist/pstack/agents/poteto-agent.md +9 -0
  14. package/{assets → dist/pstack}/docs/guide/01-setup.md +13 -11
  15. package/{assets → dist/pstack}/docs/guide/02-poteto-mode.md +4 -4
  16. package/{assets → dist/pstack}/docs/guide/04-design.md +4 -4
  17. package/{assets → dist/pstack}/docs/guide/05-build-and-clean.md +2 -2
  18. package/{assets → dist/pstack}/docs/guide/06-verify-and-ship.md +13 -3
  19. package/{assets → dist/pstack}/docs/guide/07-overnight.md +15 -13
  20. package/{assets → dist/pstack}/docs/guide/09-make-it-yours.md +6 -2
  21. package/{assets → dist/pstack}/docs/guide/10-recipes-and-pitfalls.md +7 -5
  22. package/{assets → dist/pstack}/docs/guide/README.md +1 -1
  23. package/dist/pstack/docs/harness/dsh.md +98 -0
  24. package/{assets → dist/pstack}/skills/architect/SKILL.md +12 -12
  25. package/dist/pstack/skills/arena/SKILL.md +50 -0
  26. package/{assets → dist/pstack}/skills/automate-me/SKILL.md +14 -14
  27. package/{assets → dist/pstack}/skills/benchmark-checklist/SKILL.md +1 -1
  28. package/{assets → dist/pstack}/skills/blast-radius/SKILL.md +4 -6
  29. package/{assets → dist/pstack}/skills/correct/SKILL.md +1 -1
  30. package/{assets → dist/pstack}/skills/create-formal-verification-skill/SKILL.md +4 -2
  31. package/{assets → dist/pstack}/skills/create-verification-skill/SKILL.md +5 -5
  32. package/dist/pstack/skills/figure-it-out/SKILL.md +54 -0
  33. package/dist/pstack/skills/how/SKILL.md +38 -0
  34. package/{assets → dist/pstack}/skills/in-your-own-words/SKILL.md +1 -1
  35. package/dist/pstack/skills/interrogate/SKILL.md +33 -0
  36. package/{assets → dist/pstack}/skills/ketchup/SKILL.md +3 -5
  37. package/dist/pstack/skills/ketchup-video/SKILL.md +29 -0
  38. package/{assets → dist/pstack}/skills/maintain-formal-verification-skill/SKILL.md +1 -1
  39. package/{assets → dist/pstack}/skills/maintain-verification-skill/SKILL.md +3 -3
  40. package/dist/pstack/skills/make-bot-ui/SKILL.md +117 -0
  41. package/dist/pstack/skills/no-comments/SKILL.md +26 -0
  42. package/{assets → dist/pstack}/skills/poteto-help/SKILL.md +34 -31
  43. package/{assets → dist/pstack}/skills/poteto-help/references/prompting.md +1 -1
  44. package/{assets → dist/pstack}/skills/poteto-help/references/recipes.md +3 -3
  45. package/{assets → dist/pstack}/skills/poteto-mode/SKILL.md +20 -19
  46. package/dist/pstack/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
  47. package/dist/pstack/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
  48. package/dist/pstack/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
  49. package/dist/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
  50. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/babysit.md +4 -4
  51. package/dist/pstack/skills/poteto-mode/playbooks/bug-fix.md +15 -0
  52. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/eval.md +3 -3
  53. package/dist/pstack/skills/poteto-mode/playbooks/feature.md +21 -0
  54. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/hillclimb.md +3 -3
  55. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/investigation.md +2 -2
  56. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/multi-phase-plan.md +23 -23
  57. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
  58. package/dist/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
  59. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/pause-safely.md +2 -2
  60. package/dist/pstack/skills/poteto-mode/playbooks/perf-issue.md +25 -0
  61. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/prototype.md +4 -4
  62. package/dist/pstack/skills/poteto-mode/playbooks/refactoring.md +16 -0
  63. package/dist/pstack/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
  64. package/dist/pstack/skills/poteto-mode/playbooks/session-pickup.md +11 -0
  65. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/shipping.md +2 -2
  66. package/{assets → dist/pstack}/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
  67. package/dist/pstack/skills/poteto-mode/playbooks/visual-parity.md +11 -0
  68. package/dist/pstack/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
  69. package/{assets → dist/pstack}/skills/poteto-mode/scripts/check-plan.mjs +6 -8
  70. package/dist/pstack/skills/poteto-mode/scripts/check-plan.test.mjs +128 -0
  71. package/{assets/skills/poteto-mode/scripts/check-plan.test.mjs → dist/pstack/skills/poteto-mode/scripts/expect.mjs} +12 -101
  72. package/dist/pstack/skills/poteto-mode/scripts/support.mjs +93 -0
  73. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +123 -0
  74. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +217 -0
  75. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +119 -0
  76. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +626 -0
  77. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +521 -0
  78. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +657 -0
  79. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +523 -0
  80. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +106 -0
  81. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +9 -0
  82. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +5 -0
  83. package/{assets → dist/pstack}/skills/poteto-mode/scripts/worktree-audit.mjs +15 -22
  84. package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +134 -0
  85. package/{assets → dist/pstack}/skills/principle-attack-the-premise/SKILL.md +1 -1
  86. package/{assets → dist/pstack}/skills/principle-boundary-discipline/SKILL.md +1 -1
  87. package/{assets → dist/pstack}/skills/principle-encode-lessons-in-structure/SKILL.md +1 -1
  88. package/{assets → dist/pstack}/skills/principle-exhaust-the-design-space/SKILL.md +1 -1
  89. package/{assets → dist/pstack}/skills/principle-experience-first/SKILL.md +1 -1
  90. package/{assets → dist/pstack}/skills/principle-explain-the-number/SKILL.md +3 -2
  91. package/{assets → dist/pstack}/skills/principle-fix-root-causes/SKILL.md +1 -1
  92. package/{assets → dist/pstack}/skills/principle-laziness-protocol/SKILL.md +1 -1
  93. package/{assets → dist/pstack}/skills/principle-make-operations-idempotent/SKILL.md +1 -1
  94. package/{assets → dist/pstack}/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -1
  95. package/{assets → dist/pstack}/skills/principle-minimize-reader-load/SKILL.md +1 -1
  96. package/{assets → dist/pstack}/skills/principle-model-the-domain/SKILL.md +1 -1
  97. package/{assets → dist/pstack}/skills/principle-never-block-on-the-human/SKILL.md +1 -1
  98. package/{assets → dist/pstack}/skills/principle-outcome-oriented-execution/SKILL.md +1 -1
  99. package/{assets → dist/pstack}/skills/principle-prove-it-works/SKILL.md +2 -2
  100. package/{assets → dist/pstack}/skills/principle-redesign-from-first-principles/SKILL.md +1 -1
  101. package/{assets → dist/pstack}/skills/principle-separate-before-serializing-shared-state/SKILL.md +1 -1
  102. package/{assets → dist/pstack}/skills/principle-sequence-verifiable-units/SKILL.md +1 -1
  103. package/{assets → dist/pstack}/skills/principle-subtract-before-you-add/SKILL.md +1 -1
  104. package/{assets → dist/pstack}/skills/principle-test-behavior-not-implementation/SKILL.md +1 -1
  105. package/{assets → dist/pstack}/skills/principle-type-system-discipline/SKILL.md +1 -1
  106. package/dist/pstack/skills/recall/SKILL.md +37 -0
  107. package/dist/pstack/skills/reflect/SKILL.md +71 -0
  108. package/{assets → dist/pstack}/skills/reflect/references/divergent-reviewer.md +4 -4
  109. package/{assets → dist/pstack}/skills/reflect/references/judgment-reviewer.md +2 -2
  110. package/{assets → dist/pstack}/skills/reflect/references/synthesizer.md +5 -5
  111. package/{assets → dist/pstack}/skills/reflect/references/tooling-reviewer.md +2 -2
  112. package/dist/pstack/skills/setup-pstack/SKILL.md +80 -0
  113. package/{assets → dist/pstack}/skills/sharpen/SKILL.md +1 -1
  114. package/{assets → dist/pstack}/skills/show-me-your-work/SKILL.md +8 -8
  115. package/{assets → dist/pstack}/skills/show-me-your-work/scripts/log.mjs +1 -3
  116. package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +56 -0
  117. package/dist/pstack/skills/statechart/SKILL.md +82 -0
  118. package/dist/pstack/skills/statechart/references/render.md +93 -0
  119. package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +31 -0
  120. package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +186 -0
  121. package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +62 -0
  122. package/dist/pstack/skills/statechart/scripts/backends/report.mjs +84 -0
  123. package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +268 -0
  124. package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +67 -0
  125. package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +94 -0
  126. package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +48 -0
  127. package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +101 -0
  128. package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +39 -0
  129. package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +180 -0
  130. package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +40 -0
  131. package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +99 -0
  132. package/dist/pstack/skills/statechart/scripts/ir.mjs +277 -0
  133. package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +92 -0
  134. package/dist/pstack/skills/statechart/scripts/passes/label.mjs +210 -0
  135. package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +84 -0
  136. package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +47 -0
  137. package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +72 -0
  138. package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +135 -0
  139. package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +82 -0
  140. package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +268 -0
  141. package/dist/pstack/skills/statechart/scripts/pipeline.mjs +72 -0
  142. package/dist/pstack/skills/statechart/scripts/run.mjs +440 -0
  143. package/dist/pstack/skills/statechart/scripts/statechart.mjs +155 -0
  144. package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +198 -0
  145. package/dist/pstack/skills/statechart/scripts/worker.mjs +31 -0
  146. package/dist/pstack/skills/swarm/SKILL.md +43 -0
  147. package/{assets → dist/pstack}/skills/tdd/SKILL.md +1 -1
  148. package/{assets → dist/pstack}/skills/teach/SKILL.md +2 -2
  149. package/{assets → dist/pstack}/skills/technical-writing/SKILL.md +1 -1
  150. package/{assets → dist/pstack}/skills/typescript-best-practices/SKILL.md +3 -6
  151. package/{assets → dist/pstack}/skills/why/SKILL.md +8 -8
  152. package/dist/script.d.mts +9 -111
  153. package/dist/script.mjs +32 -329
  154. package/dist/skill.d.mts +38 -0
  155. package/dist/skill.mjs +183 -0
  156. package/dist/skills/authored/decision-audit/SKILL.md +99 -0
  157. package/dist/skills/authored/decision-audit/SKILL.zh.md +99 -0
  158. package/dist/skills/authored/decision-audit/references/recover-commit.md +17 -0
  159. package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +17 -0
  160. package/dist/skills/authored/decision-write/SKILL.md +131 -0
  161. package/dist/skills/authored/decision-write/SKILL.zh.md +131 -0
  162. package/dist/skills/authored/decision-write/references/land.md +48 -0
  163. package/dist/skills/authored/decision-write/references/land.zh.md +48 -0
  164. package/dist/skills/authored/setup-project-docs/SKILL.md +58 -0
  165. package/dist/skills/authored/setup-project-docs/SKILL.zh.md +58 -0
  166. package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +11 -0
  167. package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +11 -0
  168. package/dist/skills/vendored/retro/SKILL.md +44 -0
  169. package/dist/skills/vendored/retro/agents/openai.yaml +5 -0
  170. package/dist/skills/vendored/writing-for-agents/SKILL-MECHANICS.md +22 -0
  171. package/dist/skills/vendored/writing-for-agents/SKILL.md +81 -0
  172. package/dist/skills/vendored/writing-for-agents/agents/openai.yaml +3 -0
  173. package/dist/subagent.d.mts +56 -193
  174. package/dist/subagent.mjs +443 -741
  175. package/locale/en.json +3 -2
  176. package/locale/zh.json +3 -2
  177. package/package.json +63 -66
  178. package/assets/agents/poteto-agent.md +0 -8
  179. package/assets/docs/harness/dsh.md +0 -60
  180. package/assets/docs/pstack-models.example.md +0 -54
  181. package/assets/skills/arena/SKILL.md +0 -50
  182. package/assets/skills/figure-it-out/SKILL.md +0 -54
  183. package/assets/skills/how/SKILL.md +0 -38
  184. package/assets/skills/interrogate/SKILL.md +0 -71
  185. package/assets/skills/interrogate/references/code-quality-review.md +0 -47
  186. package/assets/skills/interrogate/references/lead-judgment.md +0 -39
  187. package/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
  188. package/assets/skills/interrogate/references/rubric.md +0 -77
  189. package/assets/skills/no-comments/SKILL.md +0 -26
  190. package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
  191. package/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -15
  192. package/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
  193. package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
  194. package/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
  195. package/assets/skills/poteto-mode/playbooks/feature.md +0 -21
  196. package/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
  197. package/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
  198. package/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
  199. package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
  200. package/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
  201. package/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
  202. package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -20
  203. package/assets/skills/poteto-mode/scripts/orch/orch.mjs +0 -4282
  204. package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +0 -2042
  205. package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +0 -5101
  206. package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +0 -1177
  207. package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +0 -1441
  208. package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +0 -4601
  209. package/assets/skills/recall/SKILL.md +0 -37
  210. package/assets/skills/reflect/SKILL.md +0 -69
  211. package/assets/skills/setup-pstack/SKILL.md +0 -67
  212. package/assets/skills/swarm/SKILL.md +0 -43
  213. package/dist/frontmatter-BQX3ekwP.mjs +0 -229
  214. package/dist/frontmatter-BQX3ekwP.mjs.map +0 -1
  215. package/dist/history.d.mts +0 -429
  216. package/dist/history.mjs +0 -1129
  217. package/dist/history.mjs.map +0 -1
  218. package/dist/host-contract-o47JH_r8.d.mts +0 -527
  219. package/dist/index.mjs.map +0 -1
  220. package/dist/paths-D_JlVmBk.mjs +0 -42
  221. package/dist/paths-D_JlVmBk.mjs.map +0 -1
  222. package/dist/script.mjs.map +0 -1
  223. package/dist/subagent.mjs.map +0 -1
  224. /package/{static/icon.png → assets/logo.png} +0 -0
  225. /package/{assets → dist/pstack}/agents/comment-sicko.md +0 -0
  226. /package/{assets → dist/pstack}/docs/guide/03-understand.md +0 -0
  227. /package/{assets → dist/pstack}/docs/guide/08-principles.md +0 -0
  228. /package/{assets → dist/pstack}/docs/guide/images/design.jpg +0 -0
  229. /package/{assets → dist/pstack}/docs/guide/images/overnight.jpg +0 -0
  230. /package/{assets → dist/pstack}/docs/guide/images/recipes.jpg +0 -0
  231. /package/{assets → dist/pstack}/docs/guide/images/router.jpg +0 -0
  232. /package/{assets → dist/pstack}/docs/guide/images/understanding.jpg +0 -0
  233. /package/{assets → dist/pstack}/docs/guide/images/verification.jpg +0 -0
  234. /package/{assets → dist/pstack}/skills/architect/references/design-red-flags.md +0 -0
  235. /package/{assets → dist/pstack}/skills/architect/references/rationale-template.md +0 -0
  236. /package/{assets → dist/pstack}/skills/architect/references/runner-prompt.md +0 -0
  237. /package/{assets → dist/pstack}/skills/bro/SKILL.md +0 -0
  238. /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/README.md +0 -0
  239. /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -0
  240. /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/search.md +0 -0
  241. /package/{assets → dist/pstack}/skills/how/references/explainer-prompt.md +0 -0
  242. /package/{assets → dist/pstack}/skills/how/references/explorer-prompt.md +0 -0
  243. /package/{assets → dist/pstack}/skills/poteto-mode/references/bugbot-triage.md +0 -0
  244. /package/{assets → dist/pstack}/skills/principle-build-the-lever/SKILL.md +0 -0
  245. /package/{assets → dist/pstack}/skills/principle-foundational-thinking/SKILL.md +0 -0
  246. /package/{assets → dist/pstack}/skills/principle-guard-the-context-window/SKILL.md +0 -0
  247. /package/{assets → dist/pstack}/skills/show-me-your-work/references/decision-log-template.tsv +0 -0
  248. /package/{assets → dist/pstack}/skills/typescript-best-practices/references/patterns.md +0 -0
  249. /package/{assets → dist/pstack}/skills/unslop/SKILL.md +0 -0
  250. /package/{assets → dist/pstack}/skills/why/references/epistemics.md +0 -0
  251. /package/{assets → dist/pstack}/skills/why/references/investigator-prompt.md +0 -0
  252. /package/{assets → dist/pstack}/skills/why/references/source-playbook.md +0 -0
  253. /package/{assets → dist/pstack}/skills/why/references/sources/code-archaeology.md +0 -0
  254. /package/{assets → dist/pstack}/skills/why/references/sources/databricks.md +0 -0
  255. /package/{assets → dist/pstack}/skills/why/references/sources/datadog.md +0 -0
  256. /package/{assets → dist/pstack}/skills/why/references/sources/incident-postmortem.md +0 -0
  257. /package/{assets → dist/pstack}/skills/why/references/sources/linear.md +0 -0
  258. /package/{assets → dist/pstack}/skills/why/references/sources/notion.md +0 -0
  259. /package/{assets → dist/pstack}/skills/why/references/sources/sentry.md +0 -0
  260. /package/{assets → dist/pstack}/skills/why/references/sources/slack.md +0 -0
  261. /package/{assets → dist/pstack}/skills/why/references/synthesizer-prompt.md +0 -0
package/dist/subagent.mjs CHANGED
@@ -1,162 +1,13 @@
1
- import { i as POTETO_MODE_SKILL_PATH, o as agentPreamblePath, t as ASSETS_ROOT } from "./paths-D_JlVmBk.mjs";
2
- import { c as parseFrontmatter } from "./frontmatter-BQX3ekwP.mjs";
3
- import { join } from "node:path";
4
- import { readFileSync } from "node:fs";
1
+ import { a as POTETO_MODE_SKILL_PATH } from "./paths-BT77khwm.mjs";
2
+ import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { existsSync, readFileSync, statSync } from "node:fs";
5
4
  import { homedir } from "node:os";
6
- //#region scripts/role-manifest.ts
7
- /** File name the role policy lives under inside `$DSH_HOME`. */
8
- const PSTACK_MODELS_FILENAME = "pstack-models.md";
9
- /** Values that resolve to "send no `agentOptions` and inherit the parent route". */
10
- const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
11
- const ROLE_MANIFEST = [
12
- {
13
- key: "feature, refactoring",
14
- shape: "single",
15
- autoPanel: false,
16
- consumers: ["poteto-mode/playbooks/feature.md", "poteto-mode/playbooks/refactoring.md"],
17
- summary: "One route for both code playbooks. The comma joins two playbook names into a single key."
18
- },
19
- {
20
- key: "bug-fix",
21
- shape: "single",
22
- autoPanel: false,
23
- consumers: ["poteto-mode/playbooks/bug-fix.md"],
24
- summary: "Implementation route for the bug-fix playbook."
25
- },
26
- {
27
- key: "perf-issue",
28
- shape: "single",
29
- autoPanel: false,
30
- consumers: ["poteto-mode/playbooks/perf-issue.md"],
31
- summary: "Implementation route for the perf-issue playbook."
32
- },
33
- {
34
- key: "hillclimb",
35
- shape: "single",
36
- autoPanel: false,
37
- consumers: ["poteto-mode/playbooks/hillclimb.md"],
38
- summary: "Implementation route for the hillclimb playbook."
39
- },
40
- {
41
- key: "judgment and prose",
42
- shape: "single",
43
- autoPanel: false,
44
- consumers: [
45
- "poteto-mode/SKILL.md",
46
- "unslop",
47
- "technical-writing"
48
- ],
49
- summary: "Route for prose work and vague-instruction judgment calls."
50
- },
51
- {
52
- key: "hardest tasks",
53
- shape: "single",
54
- autoPanel: false,
55
- consumers: ["poteto-mode/SKILL.md"],
56
- summary: "Route for the strongest judgment model on cross-cutting or subtle work."
57
- },
58
- {
59
- key: "how explorer",
60
- shape: "single",
61
- autoPanel: false,
62
- consumers: ["how/SKILL.md", "how/references/explorer-prompt.md"],
63
- summary: "Route for each how explorer child."
64
- },
65
- {
66
- key: "how explainer",
67
- shape: "single",
68
- autoPanel: false,
69
- consumers: ["how/SKILL.md", "how/references/explainer-prompt.md"],
70
- summary: "Route for the single how explainer child."
71
- },
72
- {
73
- key: "why investigators",
74
- shape: "single",
75
- autoPanel: false,
76
- consumers: ["why/SKILL.md", "why/references/investigator-prompt.md"],
77
- summary: "Route for each why investigator child."
78
- },
79
- {
80
- key: "why synthesizer",
81
- shape: "single",
82
- autoPanel: false,
83
- consumers: ["why/SKILL.md", "why/references/synthesizer-prompt.md"],
84
- summary: "Route for the why synthesis child."
85
- },
86
- {
87
- key: "reflect tooling",
88
- shape: "single",
89
- autoPanel: false,
90
- consumers: ["reflect/SKILL.md", "reflect/references/tooling-reviewer.md"],
91
- summary: "Route for the reflect tooling reviewer."
92
- },
93
- {
94
- key: "reflect judgment, divergent, synthesizer",
95
- shape: "single",
96
- autoPanel: false,
97
- consumers: [
98
- "reflect/SKILL.md",
99
- "reflect/references/judgment-reviewer.md",
100
- "reflect/references/divergent-reviewer.md",
101
- "reflect/references/synthesizer.md"
102
- ],
103
- summary: "One shared route for the judgment reviewer, the divergent reviewer, and the synthesizer. The commas belong to the key."
104
- },
105
- {
106
- key: "arena runners",
107
- shape: "list",
108
- autoPanel: true,
109
- consumers: ["arena/SKILL.md"],
110
- summary: "Panel: one arena child per entry, all started together."
111
- },
112
- {
113
- key: "arena cross-judge pool",
114
- shape: "list",
115
- autoPanel: false,
116
- consumers: ["arena/SKILL.md"],
117
- summary: "A pool arena picks one entry from, preferring a model family unlike the parent's. Not fanned out automatically."
118
- },
119
- {
120
- key: "swarm workers",
121
- shape: "list",
122
- autoPanel: false,
123
- consumers: ["swarm/SKILL.md", "poteto-mode/playbooks/multi-phase-plan.md"],
124
- summary: "One default route for every worker. A race gives each arm another entry through `roleIndex`, and the caller still starts one worker per call."
125
- },
126
- {
127
- key: "architect runners",
128
- shape: "list",
129
- autoPanel: true,
130
- consumers: ["architect/SKILL.md"],
131
- summary: "Panel: one architect child per entry, all started together."
132
- },
133
- {
134
- key: "interrogate reviewers",
135
- shape: "list",
136
- autoPanel: true,
137
- consumers: ["interrogate/SKILL.md"],
138
- summary: "Panel: one interrogate reviewer per entry, all started together."
139
- }
140
- ];
141
- ROLE_MANIFEST.map((role) => role.key);
142
- ROLE_MANIFEST.filter((role) => role.autoPanel).map((role) => role.key);
143
- /** Exact manifest lookup. Callers normalize the key first. */
144
- function findRoleSpec(key) {
145
- return ROLE_MANIFEST.find((role) => role.key === key);
146
- }
147
- //#endregion
148
- //#region src/effort.ts
5
+ // src/internal/effort.ts
149
6
  /** Model-facing name of the child's own control. */
150
7
  const SET_EFFORT_TOOL = "set_reasoning_effort";
151
8
  /**
152
- * Resolve the route a child's effort will actually apply to.
153
- *
154
- * The session's latest request header is the live route after a model switch, and
155
- * `Agent.options` is the creation-time fallback. DSH's own delegation code resolves
156
- * `inherit-parent` the same way, so reading `options` alone would validate a
157
- * different model than the child really inherits.
158
- *
159
- * An explicit route wins over both, because it is what the delegation will request.
9
+ * Resolve the route a child's effort will actually apply to: an explicit route wins,
10
+ * then the session's live request header, then the creation-time options.
160
11
  */
161
12
  function resolveEffortTarget(agent, route) {
162
13
  const explicit = route?.kind === "route" ? route : void 0;
@@ -168,18 +19,10 @@ function resolveEffortTarget(agent, route) {
168
19
  model
169
20
  } : void 0;
170
21
  }
171
- /** The efforts one route advertises, in the adapter's own order. */
172
22
  async function advertisedEfforts(llm, target, signal) {
173
23
  return (await llm.resolveModelInfo(target.provider, target.model, signal)).reasoning?.efforts.map((entry) => entry.id) ?? [];
174
24
  }
175
- /**
176
- * Validate one effort against the route a child will actually request.
177
- *
178
- * Rejects with the target's real effort list rather than a generic message, because
179
- * "max is not supported" leaves the caller guessing while "supports xhigh, high,
180
- * medium" is actionable. A model that advertises no efforts at all is reported
181
- * separately: that is a route incapable of the control, not a wrong token.
182
- */
25
+ /** Validate one effort against the route a child will actually request. */
183
26
  async function validateEffortFor(llm, route, effort, parent, signal) {
184
27
  const target = resolveEffortTarget(parent, route);
185
28
  if (target === void 0) throw new Error("cannot resolve the child provider/model route for reasoningEffort. Pass `role` with an exact route, or run the delegation from a session that has already made a request.");
@@ -195,60 +38,37 @@ async function validateEffortFor(llm, route, effort, parent, signal) {
195
38
  /**
196
39
  * Install the effort control on one live child.
197
40
  *
198
- * Registration is child-scoped, so the waterfall and the tool dispose with the
199
- * child: a sibling delegation never sees them, and a later child cannot inherit a
200
- * stale value.
201
- *
202
- * The waterfall re-states the starting effort on every request and applies a
203
- * change from the next request onward. It never rewrites a request already in
204
- * flight, because `next()` has already been called and its result is the request
205
- * the machine built.
206
- *
207
- * A missing route is not fatal here. The child still runs on whatever the adapter
208
- * chose; the tool reports the missing route when the child tries to change effort.
209
- *
210
- * @param child - the live child agent, already started.
211
- * @param llm - the adapter host used to validate a requested change.
212
- * @param initialEffort - the effort the delegation started the child at, if any.
213
- * @returns a disposer that unwinds this contribution early, or does nothing when
214
- * the child context is already gone.
41
+ * Registration is child-scoped, so the tool disposes with the child. A successful
42
+ * call records the choice through `agent.model.select()`, which DSH applies when
43
+ * it prepares the next step. `agent/request` is deliberately not used: DSH
44
+ * deprecated it for model and effort changes and rejects a request that disagrees
45
+ * with the selection fixed during step preparation.
215
46
  */
216
- function installEffortControl(child, llm, initialEffort) {
217
- let current = initialEffort;
218
- const disposeListener = child.ctx.on("agent/request", async (_payload, next) => {
219
- const config = await next();
220
- return current === void 0 ? config : {
221
- ...config,
222
- reasoningEffort: current
223
- };
224
- });
225
- const disposeTool = child.ctx.tools.register(defineSetEffortTool(child, llm, (effort) => {
226
- current = effort;
227
- }));
228
- return () => {
229
- disposeTool();
230
- disposeListener();
231
- };
47
+ function installEffortControl(child, llm) {
48
+ return child.ctx.tools.register(defineSetEffortTool(child, llm));
232
49
  }
233
- /** The child's own view of its effort, for the tool body. */
234
- function defineSetEffortTool(child, llm, apply) {
50
+ function defineSetEffortTool(child, llm) {
235
51
  return {
236
52
  name: SET_EFFORT_TOOL,
237
53
  description: [
238
54
  "Change your reasoning effort for future model requests.",
239
55
  "Use it when the task turns out harder or more routine than the role line assumed: raise it for subtle design, concurrency, or algorithm work, lower it once the remaining work is mechanical.",
240
- "The change applies from your next step; a request already in flight is unaffected, and it lasts for the rest of this child's life.",
241
- "This tool may not be visible until your second step. The effort you were started with applies to the first step, so a slow first step is expected when that level is high."
56
+ "The change applies from your next step; a request already in flight is unaffected, and it lasts for the rest of this child's life."
242
57
  ].join(" "),
243
- parameters: { effort: {
244
- type: "string",
245
- description: "The reasoning effort to use from your next step onward. Use one of the values this model advertises; the call rejects an unknown one and lists the real values."
246
- } },
58
+ parameters: {
59
+ type: "object",
60
+ additionalProperties: false,
61
+ properties: { effort: {
62
+ type: "string",
63
+ description: "The reasoning effort to use from your next step onward. Use one of the values this model advertises; the call rejects an unknown one and lists the real values."
64
+ } },
65
+ required: ["effort"]
66
+ },
247
67
  output: {
248
68
  schema: { type: "string" },
249
69
  render: (_args, value) => [{
250
70
  type: "text",
251
- text: String(value)
71
+ text: typeof value === "string" ? value : JSON.stringify(value)
252
72
  }]
253
73
  },
254
74
  isConcurrencySafe: () => false,
@@ -256,7 +76,8 @@ function defineSetEffortTool(child, llm, apply) {
256
76
  const args = rawArgs;
257
77
  const value = typeof args.effort === "string" ? args.effort.trim() : "";
258
78
  if (value === "") throw new Error("set_reasoning_effort needs a non-empty `effort`. Pass the exact token this model advertises; an empty value would silently re-enable the adapter default.");
259
- const target = resolveEffortTarget(exec.agent ?? child);
79
+ const agent = exec.agent ?? child;
80
+ const target = resolveEffortTarget(agent);
260
81
  if (target === void 0) throw new Error("this child has no resolved provider/model route, so reasoning effort cannot be validated against the live adapter metadata");
261
82
  let efforts;
262
83
  try {
@@ -266,22 +87,16 @@ function defineSetEffortTool(child, llm, apply) {
266
87
  }
267
88
  if (efforts.length === 0) throw new Error(`${target.provider}/${target.model} advertises no reasoning efforts, so this child has none to change. Your current effort stays as it is.`);
268
89
  if (!efforts.includes(value)) throw new Error(`${target.provider}/${target.model} does not support reasoning effort "${value}", so nothing changed. It supports: ${efforts.join(", ")}.`);
269
- apply(value);
90
+ agent.model.select({
91
+ provider: target.provider,
92
+ model: target.model,
93
+ reasoningEffort: value
94
+ });
270
95
  return `reasoning effort set to "${value}". It applies from your next step; the request already in flight is unaffected.`;
271
96
  }
272
97
  };
273
98
  }
274
- /**
275
- * The `agentOptions` one (route, effort override) pair contributes.
276
- *
277
- * The two independent halves are easy to get wrong in opposite directions:
278
- *
279
- * - an inherit-parent route with an override must still send `reasoningEffort`,
280
- * which means `{ reasoningEffort }` on its own is a real `agentOptions` value and
281
- * the backend still has to support the capability;
282
- * - an override always wins over the role line, because the caller asked for this
283
- * delegation specifically.
284
- */
99
+ /** The `agentOptions` one (route, effort override) pair contributes. */
285
100
  function agentOptionsFor(route, effortOverride) {
286
101
  const effort = effortOverride ?? (route.kind === "route" ? route.effort : void 0);
287
102
  if (route.kind === "inherit") return effort === void 0 ? void 0 : { reasoningEffort: effort };
@@ -302,128 +117,133 @@ function describeError$2(error) {
302
117
  }
303
118
  return String(error);
304
119
  }
305
- //#endregion
306
- //#region src/roles.ts
307
- /**
308
- * Role lookup over the single manifest in `scripts/role-manifest.ts`.
309
- *
310
- * A role name is matched exactly after normalization. A near miss is reported as
311
- * a hint and never guessed, because silently running a role on the wrong route is
312
- * worse than refusing the delegation.
313
- *
314
- * Commas belong to some keys (`feature, refactoring`), so nothing here splits a
315
- * key on a comma. Only a role file *value* is a comma-separated list.
316
- */
317
- /** Raised when a role name does not match the manifest. */
318
- var RoleNameError = class extends Error {
319
- constructor(message) {
320
- super(message);
321
- this.name = "RoleNameError";
322
- }
323
- };
120
+ // src/internal/role-manifest.ts
324
121
  /**
325
- * Normalize a role name for lookup.
122
+ * File name the role configuration lives under inside `$DSH_HOME`.
326
123
  *
327
- * Case, surrounding whitespace, runs of whitespace, spacing around a comma, and a
328
- * trailing colon are formatting, not naming. Nothing else is normalized: a
329
- * different word is a different role.
124
+ * It keeps the upstream name `pstack-models.mdc`, so a reader moving from the Cursor docs
125
+ * recognizes it, but stores it directly under `$DSH_HOME`; DSH does not load `.mdc` files
126
+ * on its own, and `pstack_subagent` is the only reader.
330
127
  */
331
- function normalizeRoleName(raw) {
332
- return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
333
- }
334
- /** Look a role up by its exact normalized key. */
335
- function lookupRole(raw) {
336
- return findRoleSpec(normalizeRoleName(raw));
337
- }
338
- /**
339
- * Near-miss hints for a role name that did not match.
340
- *
341
- * Only used to build a diagnostic. A suggestion is never turned into a selection.
342
- */
343
- function suggestRoles(raw, limit = 3) {
344
- const target = normalizeRoleName(raw);
345
- if (target === "") return [];
346
- return ROLE_MANIFEST.map((role) => {
347
- const shared = sharedTokenCount(target, role.key);
348
- const distance = editDistance(target, role.key);
349
- const contains = role.key.includes(target) || target.includes(role.key);
350
- const score = (contains ? 1e3 : 0) + shared * 100 - distance + Math.min(role.key.length, target.length);
351
- return {
352
- key: role.key,
353
- score,
354
- distance,
355
- shared,
356
- contains
357
- };
358
- }).filter((candidate) => candidate.contains || candidate.shared > 0 || candidate.distance <= 4).sort((left, right) => right.score - left.score).slice(0, limit).map((candidate) => candidate.key);
359
- }
360
- /**
361
- * Resolve a role name, or throw a diagnostic that names the near misses.
362
- */
363
- function requireRole(raw) {
364
- const spec = lookupRole(raw);
365
- if (spec !== void 0) return spec;
366
- const suggestions = suggestRoles(raw);
367
- const hint = suggestions.length === 0 ? "No role key is close to it." : `Closest role keys: ${suggestions.map((key) => `"${key}"`).join(", ")}.`;
368
- throw new RoleNameError(`unknown pstack role ${JSON.stringify(raw)}. ${hint} Role names come from the role manifest, not from free text: pass the exact key, or omit \`role\` to inherit the parent route.`);
369
- }
370
- function sharedTokenCount(left, right) {
371
- const rightTokens = new Set(tokenize(right));
372
- return [...new Set(tokenize(left))].filter((token) => rightTokens.has(token)).length;
373
- }
374
- function tokenize(value) {
375
- return value.split(/[^a-z0-9]+/).filter((token) => token !== "");
376
- }
377
- /** Levenshtein distance, iterative with one row of state. */
378
- function editDistance(left, right) {
379
- if (left === right) return 0;
380
- if (left === "") return right.length;
381
- if (right === "") return left.length;
382
- let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
383
- for (let row = 1; row <= left.length; row += 1) {
384
- const current = [row];
385
- for (let column = 1; column <= right.length; column += 1) {
386
- const substitution = previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1);
387
- const deletion = previous[column] + 1;
388
- const insertion = current[column - 1] + 1;
389
- current.push(Math.min(substitution, deletion, insertion));
390
- }
391
- previous = current;
128
+ const PSTACK_MODELS_FILENAME = "pstack-models.mdc";
129
+ /** Values that resolve to "send no `agentOptions` and inherit the parent route". */
130
+ const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
131
+ /** The 17 roles, in manifest order. */
132
+ const ROLE_MANIFEST = [
133
+ {
134
+ key: "feature, refactoring",
135
+ shape: "single",
136
+ autoPanel: false
137
+ },
138
+ {
139
+ key: "bug-fix",
140
+ shape: "single",
141
+ autoPanel: false
142
+ },
143
+ {
144
+ key: "perf-issue",
145
+ shape: "single",
146
+ autoPanel: false
147
+ },
148
+ {
149
+ key: "hillclimb",
150
+ shape: "single",
151
+ autoPanel: false
152
+ },
153
+ {
154
+ key: "judgment and prose",
155
+ shape: "single",
156
+ autoPanel: false
157
+ },
158
+ {
159
+ key: "hardest tasks",
160
+ shape: "single",
161
+ autoPanel: false
162
+ },
163
+ {
164
+ key: "how explorer",
165
+ shape: "single",
166
+ autoPanel: false
167
+ },
168
+ {
169
+ key: "how explainer",
170
+ shape: "single",
171
+ autoPanel: false
172
+ },
173
+ {
174
+ key: "why investigators",
175
+ shape: "single",
176
+ autoPanel: false
177
+ },
178
+ {
179
+ key: "why synthesizer",
180
+ shape: "single",
181
+ autoPanel: false
182
+ },
183
+ {
184
+ key: "reflect tooling",
185
+ shape: "single",
186
+ autoPanel: false
187
+ },
188
+ {
189
+ key: "reflect judgment, divergent, synthesizer",
190
+ shape: "single",
191
+ autoPanel: false
192
+ },
193
+ {
194
+ key: "arena runners",
195
+ shape: "list",
196
+ autoPanel: true
197
+ },
198
+ {
199
+ key: "arena cross-judge pool",
200
+ shape: "list",
201
+ autoPanel: false
202
+ },
203
+ {
204
+ key: "swarm workers",
205
+ shape: "list",
206
+ autoPanel: false
207
+ },
208
+ {
209
+ key: "architect runners",
210
+ shape: "list",
211
+ autoPanel: true
212
+ },
213
+ {
214
+ key: "interrogate reviewers",
215
+ shape: "list",
216
+ autoPanel: true
392
217
  }
393
- return previous[right.length];
218
+ ];
219
+ /** Exact manifest lookup. Callers normalize the key first. */
220
+ function findRoleSpec(key) {
221
+ return ROLE_MANIFEST.find((role) => role.key === key);
394
222
  }
395
- //#endregion
396
- //#region src/models.ts
223
+ // src/internal/role-config.ts
397
224
  /**
398
- * The pstack role file: `$DSH_HOME/pstack-models.md`.
225
+ * The pstack role configuration: `$DSH_HOME/pstack-models.mdc`.
399
226
  *
400
- * `pstack_subagent` reads this file again on every delegation, so a change reaches
401
- * the next child and never a child that already started.
227
+ * `pstack_subagent` reads this file again on every delegation, so a change reaches the
228
+ * next child and never a child that already started.
402
229
  *
403
- * Three states are kept apart on purpose, because they need different fixes:
404
- *
405
- * - the file does not exist: no policy is configured, so every role inherits the
406
- * parent route;
407
- * - the file exists but cannot be read: a broken installation or permission
408
- * problem, reported as an error and never treated as "no configuration";
409
- * - the file loads but a line is wrong: a typo in a key, an unknown retired role,
410
- * or a malformed value is reported with the line number and the near misses.
230
+ * Three states stay apart because each needs a different fix: the file is missing (no
231
+ * policy, so every role inherits the parent route), unreadable (a broken install, never
232
+ * treated as "no configuration"), or loaded with per-line issues reported by line.
411
233
  */
412
234
  /** Absolute path of the role file for one DSH home. */
413
235
  function modelsFilePath(dshHome) {
414
236
  return join(dshHome, PSTACK_MODELS_FILENAME);
415
237
  }
416
- /**
417
- * Resolve `$DSH_HOME` the way the Host does: the environment when set, else
418
- * `~/.dsh`.
419
- */
238
+ /** Resolve `$DSH_HOME` the way the Host does: the environment when set, else `~/.dsh`. */
420
239
  function resolveDshHome(env = process.env, home = homedir()) {
421
240
  const configured = env["DSH_HOME"];
422
- if (configured !== void 0 && configured.trim() !== "") return configured;
423
- return join(home, ".dsh");
241
+ const path = configured !== void 0 && configured.trim() !== "" ? configured : join(home, ".dsh");
242
+ const expanded = path === "~" ? home : path.startsWith("~/") || path.startsWith("~\\") ? join(home, path.slice(2)) : path;
243
+ return resolve(expanded);
424
244
  }
425
245
  /** Read the role file. Never throws for a missing or unreadable file. */
426
- function readRoleFile(path) {
246
+ function readRoleConfiguration(path) {
427
247
  let text;
428
248
  try {
429
249
  text = readFileSync(path, "utf8");
@@ -438,15 +258,14 @@ function readRoleFile(path) {
438
258
  reason: describeError$1(error)
439
259
  };
440
260
  }
441
- return parseRoleFile(text, path);
261
+ return parseRoleConfiguration(text, path);
442
262
  }
443
263
  /** Parse role-file text. Pure: no file system access. */
444
- function parseRoleFile(text, path) {
264
+ function parseRoleConfiguration(text, path) {
445
265
  const issues = [];
446
266
  const roles = /* @__PURE__ */ new Map();
447
- const lines = stripFrontmatter(text).split("\n");
448
267
  let budget;
449
- lines.forEach((rawLine, index) => {
268
+ stripFrontmatter(text).split("\n").forEach((rawLine, index) => {
450
269
  const line = index + 1;
451
270
  const trimmed = rawLine.trim();
452
271
  if (trimmed === "") return;
@@ -473,7 +292,7 @@ function parseRoleFile(text, path) {
473
292
  });
474
293
  return;
475
294
  }
476
- const spec = findSpec(key);
295
+ const spec = findRoleSpec(key);
477
296
  if (spec === void 0) {
478
297
  issues.push({
479
298
  line,
@@ -482,7 +301,9 @@ function parseRoleFile(text, path) {
482
301
  return;
483
302
  }
484
303
  const parsed = parseRouteValue(rawValue);
485
- for (const issue of parsed.issues) issues.push({
304
+ const entryIssues = [...parsed.issues];
305
+ if (spec.shape === "single" && parsed.routes.length > 1) entryIssues.push("a single-route role cannot hold a comma-separated list");
306
+ for (const issue of entryIssues) issues.push({
486
307
  line,
487
308
  message: `${rawKey}: ${issue}`
488
309
  });
@@ -497,8 +318,8 @@ function parseRoleFile(text, path) {
497
318
  rawKey,
498
319
  key: spec.key,
499
320
  spec,
500
- rawValue,
501
- routes: parsed.routes
321
+ routes: parsed.routes,
322
+ issues: entryIssues
502
323
  });
503
324
  });
504
325
  return budget === void 0 ? {
@@ -573,28 +394,27 @@ var RouteSelectionError = class extends Error {
573
394
  /**
574
395
  * Expand one role into the routes this call starts.
575
396
  *
576
- * The three auto-panel roles start one child per entry. Another list role selects
577
- * one entry, the first by default, and `roleIndex` chooses which. A single role
578
- * holds exactly one route and rejects `roleIndex`.
397
+ * Automatic panels start one child per entry. Another list role selects one entry, the
398
+ * first by default; `roleIndex` chooses which. A single role holds exactly one route and
399
+ * rejects `roleIndex`.
579
400
  */
580
401
  function selectRoleRoutes(spec, entry, roleIndex) {
581
402
  if (spec.shape === "single" && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" holds a single route, so \`roleIndex\` does not apply; drop \`roleIndex\` or use a list-valued role (${listRoleKeys().join(", ")})`);
582
- if (spec.autoPanel && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" is an automatic panel: it starts one child per entry, so \`roleIndex\` does not apply; configure the entries you want in ${PSTACK_MODELS_FILENAME}`);
403
+ if (spec.autoPanel && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" is an automatic panel: it starts one child per entry, so \`roleIndex\` does not apply`);
583
404
  if (entry === void 0) return {
584
405
  role: spec,
585
406
  mode: spec.autoPanel ? "panel" : "single",
586
407
  configured: false,
587
408
  routes: [{ kind: "inherit" }],
588
- total: 1,
589
- issues: []
409
+ total: 1
590
410
  };
411
+ if (entry.issues.length > 0) throw new RouteSelectionError(`role "${spec.key}" has invalid configuration on line ${entry.line}: ${entry.issues.join("; ")}. Fix the role file before delegating.`);
591
412
  if (spec.autoPanel) return {
592
413
  role: spec,
593
414
  mode: "panel",
594
415
  configured: true,
595
416
  routes: entry.routes,
596
- total: entry.routes.length,
597
- issues: []
417
+ total: entry.routes.length
598
418
  };
599
419
  if (spec.shape === "list") {
600
420
  const index = roleIndex ?? 0;
@@ -605,8 +425,7 @@ function selectRoleRoutes(spec, entry, roleIndex) {
605
425
  configured: true,
606
426
  routes: [entry.routes[index]],
607
427
  total: entry.routes.length,
608
- roleIndex: index,
609
- issues: []
428
+ roleIndex: index
610
429
  };
611
430
  }
612
431
  return {
@@ -614,21 +433,78 @@ function selectRoleRoutes(spec, entry, roleIndex) {
614
433
  mode: "single",
615
434
  configured: true,
616
435
  routes: [entry.routes[0]],
617
- total: entry.routes.length,
618
- issues: []
436
+ total: entry.routes.length
619
437
  };
620
438
  }
621
439
  /** Keys of the list-valued roles, including the automatic panels. */
622
440
  function listRoleKeys() {
623
441
  return ROLE_MANIFEST.filter((role) => role.shape === "list").map((role) => role.key);
624
442
  }
625
- function findSpec(key) {
626
- return ROLE_MANIFEST.find((role) => role.key === key);
443
+ /** Raised when a role name does not match the manifest. */
444
+ var RoleNameError = class extends Error {
445
+ constructor(message) {
446
+ super(message);
447
+ this.name = "RoleNameError";
448
+ }
449
+ };
450
+ /** Normalize a role name for lookup. Case, spacing, spacing around a comma, and a trailing colon are formatting. */
451
+ function normalizeRoleName(raw) {
452
+ return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
453
+ }
454
+ /** Resolve a role name, or throw a diagnostic that names the near misses. */
455
+ function requireRole(raw) {
456
+ const spec = findRoleSpec(normalizeRoleName(raw));
457
+ if (spec !== void 0) return spec;
458
+ const suggestions = suggestRoles(raw);
459
+ const hint = suggestions.length === 0 ? "No role key is close to it." : `Closest role keys: ${suggestions.map((key) => `"${key}"`).join(", ")}.`;
460
+ throw new RoleNameError(`unknown pstack role ${JSON.stringify(raw)}. ${hint} Role names come from the role manifest, not from free text: pass the exact key, or omit \`role\` to inherit the parent route.`);
461
+ }
462
+ function suggestRoles(raw, limit = 3) {
463
+ const target = normalizeRoleName(raw);
464
+ if (target === "") return [];
465
+ return ROLE_MANIFEST.map((role) => {
466
+ const shared = sharedTokenCount(target, role.key);
467
+ const distance = editDistance(target, role.key);
468
+ const contains = role.key.includes(target) || target.includes(role.key);
469
+ const score = (contains ? 1e3 : 0) + shared * 100 - distance + Math.min(role.key.length, target.length);
470
+ return {
471
+ key: role.key,
472
+ score,
473
+ distance,
474
+ shared,
475
+ contains
476
+ };
477
+ }).filter((candidate) => candidate.contains || candidate.shared > 0 || candidate.distance <= 4).sort((left, right) => right.score - left.score).slice(0, limit).map((candidate) => candidate.key);
627
478
  }
628
479
  function unknownRoleMessage(rawKey, key) {
629
480
  const suggestions = suggestRoles(key);
630
481
  const hint = suggestions.length === 0 ? "no role key is close to it" : `closest role keys: ${suggestions.map((candidate) => `"${candidate}"`).join(", ")}`;
631
- return `unknown role ${JSON.stringify(rawKey)} (${hint}). A line for a retired or misspelled role would otherwise be ignored, so it is reported instead.`;
482
+ return `unknown role ${JSON.stringify(rawKey)} (${hint}). A line for a retired or misspelled role is reported rather than ignored.`;
483
+ }
484
+ function sharedTokenCount(left, right) {
485
+ const rightTokens = new Set(tokenize(right));
486
+ return [...new Set(tokenize(left))].filter((token) => rightTokens.has(token)).length;
487
+ }
488
+ function tokenize(value) {
489
+ return value.split(/[^a-z0-9]+/).filter((token) => token !== "");
490
+ }
491
+ /** Levenshtein distance, iterative with one row of state. */
492
+ function editDistance(left, right) {
493
+ if (left === right) return 0;
494
+ if (left === "") return right.length;
495
+ if (right === "") return left.length;
496
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
497
+ for (let row = 1; row <= left.length; row += 1) {
498
+ const current = [row];
499
+ for (let column = 1; column <= right.length; column += 1) {
500
+ const substitution = previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1);
501
+ const deletion = previous[column] + 1;
502
+ const insertion = current[column - 1] + 1;
503
+ current.push(Math.min(substitution, deletion, insertion));
504
+ }
505
+ previous = current;
506
+ }
507
+ return previous[right.length];
632
508
  }
633
509
  function stripFrontmatter(text) {
634
510
  const normalized = text.replaceAll("\r\n", "\n");
@@ -648,54 +524,38 @@ function describeError$1(error) {
648
524
  }
649
525
  return String(error);
650
526
  }
651
- //#endregion
652
- //#region src/subagent.ts
527
+ // src/subagent.ts
653
528
  /**
654
529
  * `pstack_subagent`: pstack's delegation entry point.
655
530
  *
656
- * One call plans one or more fresh children, validates everything before the first
657
- * start, then starts them through `ctx.subagents.startActivation` with parent
658
- * delivery. Results arrive as completion notices; this module never polls.
659
- *
660
- * Validation order follows the design's delegation flow, and every check that can
661
- * reject a call happens before the first child starts, except a start failure in
662
- * the middle of a panel, which keeps the children already running and names them.
531
+ * One call plans one or more fresh children, validates every route and effort before the
532
+ * first child starts, then starts them through `ctx.subagents.startActivation` with
533
+ * parent delivery. Results arrive as completion notices; this module never polls.
663
534
  */
664
535
  /** Cordis row name, used by Loader diagnostics. */
665
536
  const name = "pstack-subagent";
666
537
  /**
667
- * Delegation needs the tool registry, the subagent registry, and the LLM adapters.
668
- *
669
- * `agents` is deliberately **not** here. It is used only to reach a child this tool
670
- * has just started, and a composition without it still delegates: the children keep
671
- * the effort their request carried and cannot change it afterwards. Declaring it as
672
- * a hard dependency would make the whole row inert in such a profile, so the tool
673
- * would not exist at all — the failure `pstack-history` already paid for once with
674
- * `sessionQuery`. It is read per call through `ctx.get("agents")` instead, which is
675
- * why a controller mounted after this row is still seen.
538
+ * Delegation needs the tool registry, the subagent registry, the LLM adapters, and the
539
+ * session directory. `agents` is not here: it is optional and read per call, so a
540
+ * profile without it still delegates and the child simply keeps its starting effort.
676
541
  */
677
542
  const inject = [
678
543
  "tools",
679
544
  "subagents",
680
- "llm"
545
+ "llm",
546
+ "workingDirectory"
681
547
  ];
682
548
  /** Model-facing tool name. */
683
549
  const TOOL_NAME = "pstack_subagent";
684
550
  /** Backend used when the row does not name one. */
685
551
  const DEFAULT_SUBAGENT_PROVIDER = "spawn";
686
- /** Agent type used when the call does not name one. */
687
- const DEFAULT_AGENT_KIND = "poteto-agent";
688
552
  /** The child kinds this tool knows how to brief. */
689
553
  const AGENT_KINDS = [
690
554
  "poteto-agent",
691
555
  "comment-sicko",
692
556
  "general"
693
557
  ];
694
- const DEFAULT_CONFIG = {
695
- provider: DEFAULT_SUBAGENT_PROVIDER,
696
- allowedModels: [],
697
- allowedAgentPresets: []
698
- };
558
+ const DEFAULT_CONFIG = { provider: DEFAULT_SUBAGENT_PROVIDER };
699
559
  /** Raised for a configuration or argument problem, before any child starts. */
700
560
  var DelegationError = class extends Error {
701
561
  constructor(message) {
@@ -712,13 +572,6 @@ var PartialDelegationError = class extends DelegationError {
712
572
  this.startedChildren = startedChildren;
713
573
  }
714
574
  };
715
- /** Raised when a required agent preamble is missing or unusable. */
716
- var PreambleError = class extends DelegationError {
717
- constructor(message) {
718
- super(message);
719
- this.name = "PreambleError";
720
- }
721
- };
722
575
  function isNonEmptyString(value) {
723
576
  return typeof value === "string" && value.trim() !== "";
724
577
  }
@@ -726,139 +579,38 @@ function isNonNegativeSafeInteger(value) {
726
579
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
727
580
  }
728
581
  /**
729
- * Minimal Standard Schema for this row, so an omitted `config` is valid and a
730
- * typo fails at load instead of at the first delegation.
731
- */
732
- const Config = { "~standard": {
733
- version: 1,
734
- vendor: "dsh-pstack",
735
- validate(value) {
736
- if (value === void 0 || value === null) return { value: DEFAULT_CONFIG };
737
- if (typeof value !== "object" || Array.isArray(value)) return { issues: [{ message: "config must be a mapping" }] };
738
- const record = value;
739
- const issues = [];
740
- const allowedKeys = /* @__PURE__ */ new Set([
741
- "provider",
742
- "maxDepth",
743
- "allowedModels",
744
- "allowedAgentPresets"
745
- ]);
746
- for (const key of Object.keys(record)) if (!allowedKeys.has(key)) issues.push({
747
- message: `unknown field "${key}"`,
748
- path: [key]
749
- });
750
- const provider = record["provider"];
751
- if (provider !== void 0 && !isNonEmptyString(provider)) issues.push({
752
- message: "provider must be a non-empty string",
753
- path: ["provider"]
754
- });
755
- const maxDepth = record["maxDepth"];
756
- if (maxDepth !== void 0 && !isNonNegativeSafeInteger(maxDepth)) issues.push({
757
- message: "maxDepth must be a non-negative safe integer",
758
- path: ["maxDepth"]
759
- });
760
- const allowedModels = record["allowedModels"];
761
- const models = [];
762
- if (allowedModels !== void 0) {
763
- if (!Array.isArray(allowedModels)) issues.push({
764
- message: "allowedModels must be an array",
765
- path: ["allowedModels"]
766
- });
767
- else allowedModels.forEach((entry, index) => {
768
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
769
- issues.push({
770
- message: "each allowedModels entry must be a { provider, model } object",
771
- path: ["allowedModels", index]
772
- });
773
- return;
774
- }
775
- const route = entry;
776
- const routeProvider = route["provider"];
777
- const routeModel = route["model"];
778
- if (!isNonEmptyString(routeProvider) || !isNonEmptyString(routeModel)) {
779
- issues.push({
780
- message: "each allowedModels entry needs a non-empty provider and model",
781
- path: ["allowedModels", index]
782
- });
783
- return;
784
- }
785
- if (models.some((known) => known.provider === routeProvider && known.model === routeModel)) {
786
- issues.push({
787
- message: `duplicate allowedModels route "${routeProvider}/${routeModel}"`,
788
- path: ["allowedModels", index]
789
- });
790
- return;
791
- }
792
- models.push({
793
- provider: routeProvider,
794
- model: routeModel
795
- });
796
- });
797
- }
798
- const allowedPresets = record["allowedAgentPresets"];
799
- const presets = [];
800
- if (allowedPresets !== void 0) {
801
- if (!Array.isArray(allowedPresets)) issues.push({
802
- message: "allowedAgentPresets must be an array",
803
- path: ["allowedAgentPresets"]
804
- });
805
- else allowedPresets.forEach((entry, index) => {
806
- if (!isNonEmptyString(entry)) {
807
- issues.push({
808
- message: "each allowedAgentPresets entry must be a non-empty string",
809
- path: ["allowedAgentPresets", index]
810
- });
811
- return;
812
- }
813
- if (presets.includes(entry)) {
814
- issues.push({
815
- message: `duplicate allowedAgentPresets entry "${entry}"`,
816
- path: ["allowedAgentPresets", index]
817
- });
818
- return;
819
- }
820
- presets.push(entry);
821
- });
822
- }
823
- if (issues.length > 0) return { issues };
824
- return { value: {
825
- provider: isNonEmptyString(provider) ? provider : DEFAULT_SUBAGENT_PROVIDER,
826
- ...isNonNegativeSafeInteger(maxDepth) ? { maxDepth } : {},
827
- allowedModels: models,
828
- allowedAgentPresets: presets
829
- } };
830
- }
831
- } };
832
- /**
833
- * Read one agent preamble from the package assets by absolute path.
582
+ * Validate the row configuration without a Schemastery dependency.
834
583
  *
835
- * A missing or empty preamble blocks that agent type. It never degrades into a
836
- * plain child, because a `comment-sicko` or `poteto-agent` child without its
837
- * instructions is a different agent wearing the name.
584
+ * A typo must fail at activation rather than at the first delegation, and an omitted
585
+ * `config` must be valid. Cordis passes the raw mapping because this row exports no
586
+ * `Config` object; callers that object as `config` are still checked here.
838
587
  */
839
- function loadAgentPreamble(agent, assetsRoot) {
840
- if (agentPreamblePath(agent) === void 0) return void 0;
841
- const absolute = join(assetsRoot, "agents", `${agent}.md`);
842
- let text;
843
- try {
844
- text = readFileSync(absolute, "utf8");
845
- } catch (error) {
846
- const code = error.code;
847
- throw new PreambleError(`the ${agent} preamble cannot be read at ${absolute} (${typeof code === "string" ? code : String(error)}). This agent type does not start without its preamble; reinstall dsh-pstack so the packaged assets are present, or pass agent: "general" for a plain child that gets no preamble.`);
848
- }
849
- const body = parseFrontmatter(text).body.trim();
850
- if (body === "") throw new PreambleError(`the ${agent} preamble at ${absolute} is empty. This agent type does not start without its instructions; repair the packaged asset or pass agent: "general".`);
851
- return body;
588
+ function normalizeConfig(value) {
589
+ if (value === void 0 || value === null) return DEFAULT_CONFIG;
590
+ if (typeof value !== "object" || Array.isArray(value)) throw new Error("pstack-subagent: config must be a mapping");
591
+ const record = value;
592
+ for (const key of Object.keys(record)) if (key !== "provider" && key !== "maxDepth") throw new Error(`pstack-subagent: unknown config field "${key}"`);
593
+ const provider = record["provider"];
594
+ if (provider !== void 0 && !isNonEmptyString(provider)) throw new Error("pstack-subagent: provider must be a non-empty string");
595
+ const maxDepth = record["maxDepth"];
596
+ if (maxDepth !== void 0 && !isNonNegativeSafeInteger(maxDepth)) throw new Error("pstack-subagent: maxDepth must be a non-negative safe integer");
597
+ return {
598
+ provider: isNonEmptyString(provider) ? provider : DEFAULT_SUBAGENT_PROVIDER,
599
+ ...isNonNegativeSafeInteger(maxDepth) ? { maxDepth } : {}
600
+ };
852
601
  }
853
- /**
854
- * Compose the self-contained prompt one child receives.
855
- *
856
- * The preambles used to locate things themselves; the Host injects the absolute
857
- * paths instead, so a child never scans a plugin directory or guesses a version.
858
- */
602
+ /** The preset id behind each pstack agent kind. `general` joins the parent preset. */
603
+ const AGENT_PRESETS = {
604
+ "poteto-agent": "poteto-agent",
605
+ "comment-sicko": "comment-sicko"
606
+ };
607
+ /** The preset a child of this kind must mount, or undefined to join the parent preset. */
608
+ function agentPresetFor(agent) {
609
+ return agent === "general" ? void 0 : AGENT_PRESETS[agent];
610
+ }
611
+ /** Compose the self-contained prompt one child receives. */
859
612
  function composeChildPrompt(input) {
860
613
  const sections = [];
861
- if (input.preamble !== void 0) sections.push(input.preamble);
862
614
  const injected = input.injectedPaths ?? [];
863
615
  if (injected.length > 0) sections.push([
864
616
  "Absolute paths injected by `pstack_subagent`. Read them directly; do not search a plugin directory.",
@@ -869,141 +621,102 @@ function composeChildPrompt(input) {
869
621
  sections.push(`Task:\n\n${input.task}`);
870
622
  return sections.join("\n\n---\n\n");
871
623
  }
872
- /** The text form of one route, as reported back to the model. */
873
- function routeLabel(route) {
874
- if (route.kind === "inherit") return "inherit-parent";
875
- return route.effort === void 0 ? `${route.provider}/${route.model}` : `${route.provider}/${route.model} [${route.effort}]`;
876
- }
877
- /**
878
- * The route text a caller sees when an effort override wins.
879
- *
880
- * `routeLabel` describes the role file, which stops being the whole truth once the
881
- * caller overrode the effort. Reporting the file's value there would tell the model
882
- * its override had been ignored.
883
- */
884
- function plannedRouteLabel(route, effortOverride) {
885
- const effort = effectiveEffort(route, effortOverride);
886
- const base = route.kind === "inherit" ? "inherit-parent" : `${route.provider}/${route.model}`;
887
- return effort === void 0 ? base : `${base} [${effort}]`;
888
- }
889
- /**
890
- * Take the smallest of the requested depth, this row's cap, and the Host setting.
891
- *
892
- * A request above the row cap is an error rather than a silent clamp, so a caller
893
- * cannot believe it asked for two levels when the row allows one.
894
- */
895
- function resolveDelegationDepth(configured, requested, hostLimit) {
896
- if (requested !== void 0 && !isNonNegativeSafeInteger(requested)) throw new DelegationError("maxDepth must be a non-negative safe integer");
897
- if (configured !== void 0 && requested !== void 0 && requested > configured) throw new DelegationError(`maxDepth ${requested} exceeds this row's configured cap of ${configured}. Lower the request or raise \`pstack-subagent\` config.maxDepth; this tool never forwards a depth above its own row.`);
898
- const candidates = [
899
- configured,
900
- requested,
901
- hostLimit
902
- ].filter((value) => value !== void 0);
903
- return candidates.length === 0 ? void 0 : Math.min(...candidates);
904
- }
905
- /**
906
- * Validate the model-facing arguments.
907
- *
908
- * The tool schema is advisory at the model boundary, so every constraint is
909
- * re-checked here and reported with the reason.
910
- */
911
- function parseDelegationArgs(raw, config = DEFAULT_CONFIG) {
624
+ /** Validate every required model-facing argument. Null declines an optional setting. */
625
+ function parseDelegationArgs(raw) {
912
626
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DelegationError("arguments must be an object");
913
627
  const record = raw;
914
- const known = /* @__PURE__ */ new Set([
628
+ const known = [
915
629
  "description",
916
630
  "prompt",
917
631
  "agent",
918
632
  "role",
919
- "cwd",
920
- ...config.allowedAgentPresets.length > 0 ? ["agentPreset"] : [],
921
- "maxDepth",
922
633
  "roleIndex",
923
- "reasoningEffort"
924
- ]);
925
- for (const key of Object.keys(record)) if (!known.has(key)) throw new DelegationError(`unknown argument "${key}": ${key === "agentPreset" ? "agentPreset is only available when this row configures allowedAgentPresets" : `accepted arguments are ${[...known].join(", ")}`}`);
634
+ "reasoningEffort",
635
+ "cwd",
636
+ "maxDepth"
637
+ ];
638
+ for (const key of known) if (!Object.hasOwn(record, key)) throw new DelegationError(`missing required argument "${key}"; every argument must be passed explicitly`);
639
+ for (const key of Object.keys(record)) if (!known.includes(key)) throw new DelegationError(`unknown argument "${key}"; accepted arguments are ${known.join(", ")}`);
926
640
  const description = record["description"];
927
641
  if (!isNonEmptyString(description)) throw new DelegationError("description must be a non-empty string: it becomes the child label");
928
642
  const prompt = record["prompt"];
929
643
  if (!isNonEmptyString(prompt)) throw new DelegationError("prompt must be a non-empty string");
930
- const agentRaw = record["agent"];
931
- if (agentRaw !== void 0 && typeof agentRaw !== "string") throw new DelegationError(`agent must be a string: ${AGENT_KINDS.join(", ")}`);
932
- const agent = agentRaw ?? "poteto-agent";
933
- if (!AGENT_KINDS.includes(agent)) throw new DelegationError(`unknown agent "${agent}". Accepted kinds are ${AGENT_KINDS.join(", ")}; a pstack agent type is not a DSH agent preset, so \`agent\` never accepts a preset id.`);
644
+ const agent = record["agent"];
645
+ if (typeof agent !== "string" || !AGENT_KINDS.includes(agent)) throw new DelegationError(`agent must be one of ${AGENT_KINDS.join(", ")}; choose explicitly, and never pass a DSH agent preset id.`);
934
646
  const role = record["role"];
935
- if (role !== void 0 && !isNonEmptyString(role)) throw new DelegationError("role must be a non-empty string when present");
647
+ if (role !== null && !isNonEmptyString(role)) throw new DelegationError("role must be a non-empty string or null");
936
648
  const cwd = record["cwd"];
937
- if (cwd !== void 0 && !isNonEmptyString(cwd)) throw new DelegationError("cwd must be a non-empty string when present");
938
- const agentPreset = record["agentPreset"];
939
- if (agentPreset !== void 0) {
940
- if (config.allowedAgentPresets.length === 0) throw new DelegationError("agentPreset is not available: this pstack-subagent row configures no allowedAgentPresets");
941
- if (!isNonEmptyString(agentPreset) || !config.allowedAgentPresets.includes(agentPreset)) throw new DelegationError(`agentPreset ${JSON.stringify(agentPreset)} is not in this row's allowlist (${config.allowedAgentPresets.join(", ")})`);
942
- }
649
+ if (cwd !== null && !isNonEmptyString(cwd)) throw new DelegationError("cwd must be a non-empty string or null");
943
650
  const maxDepth = record["maxDepth"];
944
- if (maxDepth !== void 0 && !isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer");
651
+ if (!isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer");
945
652
  const roleIndex = record["roleIndex"];
946
- if (roleIndex !== void 0) {
947
- if (!isNonNegativeSafeInteger(roleIndex)) throw new DelegationError("roleIndex must be a non-negative safe integer");
948
- if (role === void 0) throw new DelegationError("roleIndex requires role: it selects one entry of a list-valued role, and without a role there is no list");
949
- }
653
+ if (roleIndex !== null && !isNonNegativeSafeInteger(roleIndex)) throw new DelegationError("roleIndex must be a non-negative safe integer or null");
654
+ if (roleIndex !== null && role === null) throw new DelegationError("roleIndex requires a non-null role");
950
655
  const requestedEffort = record["reasoningEffort"];
951
- if (requestedEffort !== void 0 && !isNonEmptyString(requestedEffort)) throw new DelegationError("reasoningEffort must be a non-empty string when present. It is one adapter-owned token, validated against the model this call will actually request; an empty value would silently re-enable the adapter default.");
656
+ if (requestedEffort !== null && !isNonEmptyString(requestedEffort)) throw new DelegationError("reasoningEffort must be a non-empty string or null");
952
657
  if (typeof requestedEffort === "string" && /\s/.test(requestedEffort.trim())) throw new DelegationError(`reasoningEffort ${JSON.stringify(requestedEffort)} contains whitespace. An effort is one token, exactly as the model advertises it.`);
953
658
  return {
954
659
  description,
955
660
  prompt,
956
661
  agent,
957
- ...role === void 0 ? {} : { role },
958
- ...cwd === void 0 ? {} : { cwd },
959
- ...agentPreset === void 0 ? {} : { agentPreset },
960
- ...maxDepth === void 0 ? {} : { maxDepth },
961
- ...roleIndex === void 0 ? {} : { roleIndex },
962
- ...requestedEffort === void 0 ? {} : { reasoningEffort: requestedEffort.trim() }
662
+ role,
663
+ cwd,
664
+ maxDepth,
665
+ roleIndex,
666
+ reasoningEffort: requestedEffort?.trim() ?? null
963
667
  };
964
668
  }
965
- /**
966
- * Resolve the role file, the routes, the prompt, and the depth into one plan.
967
- *
968
- * Pure with respect to the Host: it reads the role file and the packaged preamble,
969
- * and it makes no service call.
970
- */
669
+ /** Route text as reported to the model. */
670
+ function routeLabel(route, effortOverride) {
671
+ const effort = effectiveEffort(route, effortOverride);
672
+ const base = route.kind === "inherit" ? "inherit-parent" : `${route.provider}/${route.model}`;
673
+ return effort === void 0 ? base : `${base} [${effort}]`;
674
+ }
675
+ /** Take the smallest of the requested depth, this row's cap, and the Host setting. */
676
+ function resolveDelegationDepth(configured, requested, hostLimit) {
677
+ if (requested !== void 0 && !isNonNegativeSafeInteger(requested)) throw new DelegationError("maxDepth must be a non-negative safe integer");
678
+ if (configured !== void 0 && requested !== void 0 && requested > configured) throw new DelegationError(`maxDepth ${requested} exceeds this row's configured cap of ${configured}. Lower the request or raise \`pstack-subagent\` config.maxDepth.`);
679
+ const candidates = [
680
+ configured,
681
+ requested,
682
+ hostLimit
683
+ ].filter((value) => value !== void 0);
684
+ return candidates.length === 0 ? void 0 : Math.min(...candidates);
685
+ }
686
+ /** Resolve the role file, routes, prompt, and depth into one plan. Pure of service calls. */
971
687
  function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
972
- const preamble = loadAgentPreamble(args.agent, runtime.assetsRoot);
973
688
  const rolePath = modelsFilePath(runtime.dshHome);
974
- const roleFile = readRoleFile(rolePath);
975
- if (roleFile.status === "unreadable") throw new DelegationError(`the pstack role file at ${rolePath} exists but cannot be read (${roleFile.reason}). This is a broken configuration, not "no configuration": fix its permissions or delete the file, which makes every role inherit the parent route.`);
689
+ const roleFile = readRoleConfiguration(rolePath);
690
+ if (roleFile.status === "unreadable") throw new DelegationError(`the pstack role file at ${rolePath} exists but cannot be read (${roleFile.reason}). Fix its permissions or delete it, which makes every role inherit the parent route.`);
976
691
  const diagnostics = [];
977
692
  if (roleFile.status === "loaded") for (const issue of roleFile.issues) diagnostics.push(`${rolePath}${issue.line === void 0 ? "" : `:${issue.line}`}: ${issue.message}`);
978
693
  let selection;
979
694
  let roleSpec;
980
- if (args.role !== void 0) {
695
+ if (args.role !== null) {
981
696
  roleSpec = requireRole(args.role);
982
697
  const entry = roleFile.status === "loaded" ? roleFile.roles.get(roleSpec.key) : void 0;
983
- selection = selectRoleRoutes(roleSpec, entry, args.roleIndex);
698
+ selection = selectRoleRoutes(roleSpec, entry, args.roleIndex ?? void 0);
984
699
  }
985
700
  const routes = selection?.routes ?? [{ kind: "inherit" }];
986
701
  const roleName = roleSpec?.key ?? "(none)";
987
702
  if (selection !== void 0) {
988
703
  if (!selection.configured) diagnostics.push(`role "${selection.role.key}" has no line in ${rolePath}, so it inherits the parent route. Add a line to route it explicitly.`);
989
704
  if (selection.mode === "selected") diagnostics.push(`role "${selection.role.key}" holds ${selection.total} entries and is not an automatic panel; entry ${(selection.roleIndex ?? 0) + 1} was selected. Pass \`roleIndex\` to choose another.`);
990
- if (selection.role.key === "swarm workers") diagnostics.push("role \"swarm workers\" is a list, not a panel: its first entry is the default route for every worker, and `roleIndex` gives one race arm another entry. Each worker still needs its own pstack_subagent call, because this tool cannot compare separate calls.");
705
+ if (selection.role.key === "swarm workers") diagnostics.push("role \"swarm workers\" is a list, not a panel: its first entry is the default route for every worker, and `roleIndex` gives one race arm another entry. Each worker still needs its own pstack_subagent call.");
991
706
  }
992
- assertAllowedModels(routes, config, roleName);
993
- const injected = args.agent === "poteto-agent" ? [{
994
- label: "`poteto-mode` SKILL.md",
995
- path: POTETO_MODE_SKILL_PATH
996
- }] : [];
997
707
  const prompt = composeChildPrompt({
998
- ...preamble === void 0 ? {} : { preamble },
999
- injectedPaths: injected,
708
+ injectedPaths: args.agent === "poteto-agent" ? [{
709
+ label: "`poteto-mode` SKILL.md",
710
+ path: POTETO_MODE_SKILL_PATH
711
+ }] : [],
1000
712
  task: args.prompt
1001
713
  });
714
+ const agentPreset = agentPresetFor(args.agent);
1002
715
  const depth = resolveDelegationDepth(config.maxDepth, args.maxDepth, void 0);
1003
716
  const total = routes.length;
1004
717
  const children = routes.map((route, index) => {
1005
- const agentOptions = agentOptionsFor(route, args.reasoningEffort);
1006
- const effort = effectiveEffort(route, args.reasoningEffort);
718
+ const agentOptions = agentOptionsFor(route, args.reasoningEffort ?? void 0);
719
+ const effort = effectiveEffort(route, args.reasoningEffort ?? void 0);
1007
720
  return {
1008
721
  index,
1009
722
  total,
@@ -1011,17 +724,17 @@ function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
1011
724
  agent: args.agent,
1012
725
  role: roleName,
1013
726
  route,
1014
- routeLabel: plannedRouteLabel(route, args.reasoningEffort),
727
+ routeLabel: routeLabel(route, args.reasoningEffort ?? void 0),
1015
728
  ...effort === void 0 ? {} : { effort },
1016
729
  ...agentOptions === void 0 ? {} : { agentOptions },
1017
- ...args.agentPreset === void 0 ? {} : { agentPreset: args.agentPreset },
1018
- ...args.cwd === void 0 ? {} : { cwd: args.cwd },
730
+ ...agentPreset === void 0 ? {} : { agentPreset },
731
+ ...args.cwd === null ? {} : { cwd: args.cwd },
1019
732
  ...depth === void 0 ? {} : { maxDepth: depth }
1020
733
  };
1021
734
  });
1022
- if (args.reasoningEffort !== void 0) {
735
+ if (args.reasoningEffort !== null) {
1023
736
  const overwritten = routes.filter((route) => route.kind === "route" && route.effort !== void 0 && route.effort !== args.reasoningEffort);
1024
- if (overwritten.length > 0) diagnostics.push(`reasoningEffort "${args.reasoningEffort}" overrides the effort this call's role line names (${overwritten.map((route) => routeLabel(route)).join(", ")}). The role line is a starting baseline, so this is allowed; pass no reasoningEffort to keep it.`);
737
+ if (overwritten.length > 0) diagnostics.push(`reasoningEffort "${args.reasoningEffort}" overrides the effort this call's role line names (${overwritten.map((route) => routeLabel(route)).join(", ")}). The role line is a starting level; pass no reasoningEffort to keep it.`);
1025
738
  }
1026
739
  addPanelDiagnostics(children, roleName, selection, rolePath, diagnostics);
1027
740
  return {
@@ -1032,18 +745,11 @@ function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
1032
745
  mode: selection?.mode ?? "single",
1033
746
  ...selection?.roleIndex === void 0 ? {} : { roleIndex: selection.roleIndex },
1034
747
  roleTotal: selection?.total ?? 1,
1035
- ...args.reasoningEffort === void 0 ? {} : { reasoningEffort: args.reasoningEffort },
748
+ ...args.reasoningEffort === null ? {} : { reasoningEffort: args.reasoningEffort },
1036
749
  children,
1037
750
  diagnostics
1038
751
  };
1039
752
  }
1040
- function assertAllowedModels(routes, config, roleName) {
1041
- if (config.allowedModels.length === 0) return;
1042
- const unauthorized = routes.filter((route) => route.kind === "route" && !config.allowedModels.some((allowed) => allowed.provider === route.provider && allowed.model === route.model));
1043
- if (unauthorized.length === 0) return;
1044
- const allowed = config.allowedModels.map((route) => `${route.provider}/${route.model}`).join(", ");
1045
- throw new DelegationError(`role ${roleName} resolves to ${unauthorized.map(routeLabel).join(", ")}, which this row's allowedModels does not authorize. Authorized routes: ${allowed}. This is pstack's own row limit and is unrelated to the native session allowedModels policy.`);
1046
- }
1047
753
  function addPanelDiagnostics(children, roleName, selection, rolePath, diagnostics) {
1048
754
  if (selection === void 0 || selection.mode !== "panel") return;
1049
755
  if (children.length === 1) {
@@ -1052,20 +758,21 @@ function addPanelDiagnostics(children, roleName, selection, rolePath, diagnostic
1052
758
  }
1053
759
  const distinct = new Set(children.map((child) => child.routeLabel));
1054
760
  if (distinct.size > 1) return;
1055
- diagnostics.push(`all ${children.length} entries of panel "${roleName}" resolve to the same route (${[...distinct][0]}), so these children are not a cross-model review. Configure distinct routes in ${rolePath} for a real panel.`);
761
+ diagnostics.push(`all ${children.length} entries of panel "${roleName}" resolve to the same route (${[...distinct][0]}), so these children are not a cross-model review.`);
762
+ }
763
+ /** Check the backend can enforce what this call asks for. */
764
+ function assertProviderCapabilities(capabilities, providerName, asks) {
765
+ const missing = [];
766
+ if (asks.agentOptions && !capabilities.agentOptions) missing.push("agentOptions");
767
+ if (asks.agentPreset && !capabilities.agentPreset) missing.push("agentPreset");
768
+ if (asks.maxDepth && !capabilities.depthLimit) missing.push("maxDepth");
769
+ if (missing.length === 0) return;
770
+ throw new DelegationError(`subagent provider "${providerName}" does not support ${missing.join(", ")}. Change the route to \`inherit-parent\`, drop the unsupported argument, or configure a backend that supports it.`);
1056
771
  }
1057
772
  /**
1058
- * Validate every route, and every requested effort, before anything starts.
1059
- *
1060
- * `resolveCallConfig` rejects an unsupported effort and any route the owning
1061
- * adapter does not serve. An inherit-parent entry has no provider/model of its own,
1062
- * so its effort is validated against the parent's live route instead. This does not
1063
- * prove credentials, quota, or a successful model call; only a real child request
1064
- * does.
1065
- *
1066
- * Every entry is checked before the first child starts. A panel whose second entry
1067
- * cannot take the effort therefore starts none of them, which is the only outcome
1068
- * that leaves no half-applied override behind.
773
+ * Validate every route and effort before anything starts. An inherit-parent entry with
774
+ * no effort carries no agentOptions and is skipped; an override on an inherit route is
775
+ * validated against the parent's live route.
1069
776
  */
1070
777
  async function preflightRoutes(llm, parent, children, roleName, effortOverride, signal) {
1071
778
  for (const child of children) {
@@ -1075,54 +782,58 @@ async function preflightRoutes(llm, parent, children, roleName, effortOverride,
1075
782
  try {
1076
783
  await validateEffortFor(llm, void 0, effort, parent, signal);
1077
784
  } catch (error) {
1078
- throw new DelegationError(`role ${roleName} inherits the parent route, and reasoningEffort "${effort}" failed preflight there: ${describeError(error)} Omit \`reasoningEffort\`, or name a role whose line holds an exact route.`);
785
+ throw new DelegationError(`role ${roleName} inherits the parent route, and reasoningEffort "${effort}" failed preflight there: ${describeError(error)} Pass null for reasoningEffort, or name a role whose line holds an exact route.`);
1079
786
  }
1080
787
  continue;
1081
788
  }
1082
789
  const route = child.route;
1083
- const callConfig = {
1084
- provider: route.provider,
1085
- model: route.model,
1086
- ...child.effort === void 0 ? {} : { reasoningEffort: child.effort }
1087
- };
1088
790
  try {
1089
- await llm.resolveCallConfig(callConfig, signal);
791
+ await llm.resolveCallConfig({
792
+ provider: route.provider,
793
+ model: route.model,
794
+ ...child.effort === void 0 ? {} : { reasoningEffort: child.effort }
795
+ }, signal);
1090
796
  } catch (error) {
1091
- throw new DelegationError(`role ${roleName} resolves to ${child.routeLabel}, which failed route preflight: ${describeError(error)}. ${await effortHint(llm, route, child.effort, signal)}`);
797
+ throw new DelegationError(`role ${roleName} resolves to ${child.routeLabel}, which failed route preflight: ${describeError(error)}. Check the route in ${modelsFilePath("$DSH_HOME")} against the providers this deployment serves.`);
1092
798
  }
1093
799
  }
1094
800
  }
1095
- async function effortHint(llm, route, effort, signal) {
1096
- if (effort !== void 0) try {
1097
- const efforts = (await llm.resolveModelInfo(route.provider, route.model, signal)).reasoning?.efforts.map((entry) => entry.id) ?? [];
1098
- if (efforts.length > 0) return `Model ${route.provider}/${route.model} supports these efforts: ${efforts.join(", ")}.`;
1099
- return `Model ${route.provider}/${route.model} advertises no reasoning efforts.`;
801
+ /**
802
+ * Resolve and validate one child's `cwd`.
803
+ *
804
+ * A child may run inside the session directory or inside an explicitly separate Git
805
+ * worktree. Anything else is refused: this tool does not create isolation, so it will
806
+ * not promise one.
807
+ */
808
+ function resolveChildCwd(requested, sessionRoot) {
809
+ if (requested === void 0) return void 0;
810
+ const resolved = isAbsolute(requested) ? resolve(requested) : resolve(sessionRoot, requested);
811
+ let isDirectory = false;
812
+ try {
813
+ isDirectory = statSync(resolved).isDirectory();
1100
814
  } catch {
1101
- return `Fix the route or the effort in ${PSTACK_MODELS_FILENAME}; the configured provider/model pair may not be served by this deployment.`;
815
+ isDirectory = false;
1102
816
  }
1103
- return `Check the route in ${PSTACK_MODELS_FILENAME} against the providers this deployment registers.`;
817
+ if (!isDirectory) throw new DelegationError(`cwd ${resolved} does not exist or is not a directory`);
818
+ if (isWithin(sessionRoot, resolved) || isGitWorktree(resolved)) return resolved;
819
+ throw new DelegationError(`cwd ${resolved} is outside this session's directory (${sessionRoot}) and is not a separate Git worktree. Pass a path inside the session directory, or create a worktree first and pass its checkout.`);
1104
820
  }
1105
- /** Check the backend can enforce what this call asks for. */
1106
- function assertProviderCapabilities(capabilities, providerName, asks) {
1107
- const missing = [];
1108
- if (asks.agentOptions && !capabilities.agentOptions) missing.push("agentOptions");
1109
- if (asks.agentPreset && !capabilities.agentPreset) missing.push("agentPreset");
1110
- if (asks.maxDepth && !capabilities.depthLimit) missing.push("maxDepth");
1111
- if (missing.length === 0) return;
1112
- throw new DelegationError(`subagent provider "${providerName}" does not support ${missing.join(", ")}, so this call cannot be honored. Change the route to \`inherit-parent\`, drop the unsupported argument, or configure a backend that supports it; this tool never silently ignores it.`);
821
+ function isWithin(root, target) {
822
+ const rel = relative(root, target);
823
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
824
+ }
825
+ function isGitWorktree(directory) {
826
+ return existsSync(join(directory, ".git"));
1113
827
  }
1114
828
  /**
1115
- * Start the planned children.
1116
- *
1117
- * Everything that can be rejected has already been rejected. A failure at entry N
1118
- * keeps children 1..N-1 running and reports their ids, because the runtime owns
1119
- * those activations and this tool cannot roll them back.
829
+ * Start the planned children. Everything rejectable has already been rejected; a
830
+ * failure at entry N keeps children 1..N-1 running and reports their ids.
1120
831
  */
1121
832
  async function startPlannedChildren(parent, plan, runtime, config, signal) {
1122
833
  const provider = runtime.subagents.getProvider(plan.provider);
1123
834
  if (provider === void 0) {
1124
835
  const available = runtime.subagents.list();
1125
- throw new DelegationError(`subagent provider "${plan.provider}" is not registered; registered providers: ${available.length === 0 ? "(none)" : available.join(", ")}. This pstack-subagent row delegates to "${plan.provider}"; compose that backend or change the row's provider.`);
836
+ throw new DelegationError(`subagent provider "${plan.provider}" is not registered; registered providers: ${available.length === 0 ? "(none)" : available.join(", ")}.`);
1126
837
  }
1127
838
  const hostLimit = runtime.subagents.resolveMaxDepth(config.maxDepth);
1128
839
  const effectiveDepth = resolveDelegationDepth(config.maxDepth, plan.children[0]?.maxDepth, hostLimit);
@@ -1131,9 +842,11 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
1131
842
  agentPreset: plan.children.some((child) => child.agentPreset !== void 0),
1132
843
  maxDepth: effectiveDepth !== void 0
1133
844
  });
845
+ const sessionRoot = await runtime.workingDirectory.ensure(parent, signal);
1134
846
  const children = plan.children.map((child) => ({
1135
847
  ...child,
1136
- ...effectiveDepth === void 0 ? {} : { maxDepth: effectiveDepth }
848
+ ...effectiveDepth === void 0 ? {} : { maxDepth: effectiveDepth },
849
+ cwd: resolveChildCwd(child.cwd, sessionRoot)
1137
850
  }));
1138
851
  await preflightRoutes(runtime.llm, parent, children, plan.role, plan.reasoningEffort, signal);
1139
852
  if (runtime.subagents.getProvider(plan.provider) !== provider) throw new DelegationError(`subagent provider "${plan.provider}" changed while the child routes were being resolved; retry the delegation`);
@@ -1151,8 +864,8 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
1151
864
  text: plan.prompt
1152
865
  }],
1153
866
  parent,
1154
- ...child.cwd === void 0 ? {} : { cwd: child.cwd },
1155
867
  ...child.agentPreset === void 0 ? {} : { agentPreset: child.agentPreset },
868
+ ...child.cwd === void 0 ? {} : { cwd: child.cwd },
1156
869
  ...child.agentOptions === void 0 ? {} : { agentOptions: child.agentOptions },
1157
870
  ...child.maxDepth === void 0 ? {} : { maxDepth: child.maxDepth }
1158
871
  },
@@ -1161,7 +874,7 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
1161
874
  });
1162
875
  const target = runtime.agents === void 0 ? void 0 : runtime.agents.get(activation.childId);
1163
876
  if (target === void 0) diagnostics.push(`child ${activation.childId} has no live local Agent handle, so it cannot change its own reasoning effort with \`${SET_EFFORT_TOOL}\`. It runs at ${child.effort === void 0 ? "the adapter default" : `"${child.effort}"`} for its whole life.`);
1164
- else installEffortControl(target, runtime.llm, child.effort);
877
+ else installEffortControl(target, runtime.llm);
1165
878
  started.push({
1166
879
  childId: activation.childId,
1167
880
  label: child.label,
@@ -1173,7 +886,7 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
1173
886
  });
1174
887
  } catch (error) {
1175
888
  if (started.length === 0) throw new DelegationError(`starting the ${plan.role === "(none)" ? "child" : `"${plan.role}" child`} "${child.label}" (${child.routeLabel}) failed: ${describeError(error)}`);
1176
- throw new PartialDelegationError(`starting entry ${child.index + 1} of ${child.total} for role "${plan.role}" (${child.label}, ${child.routeLabel}) failed: ${describeError(error)}. The ${started.length} already started: ${started.map((record) => `${record.childId} (${record.label})`).join(", ")}. Those children are still running and are not rolled back; do not start them again, and use list_agents or send_message to reach them.`, started);
889
+ throw new PartialDelegationError(`starting entry ${child.index + 1} of ${child.total} for role "${plan.role}" (${child.label}, ${child.routeLabel}) failed: ${describeError(error)}. The ${started.length} already started: ${started.map((record) => `${record.childId} (${record.label})`).join(", ")}. Those children are still running and are not rolled back.`, started);
1177
890
  }
1178
891
  }
1179
892
  return {
@@ -1189,82 +902,79 @@ async function delegate(parent, args, runtime, config = DEFAULT_CONFIG, signal =
1189
902
  const TOOL_DESCRIPTION = [
1190
903
  "Delegate self-contained work to one or more fresh child agents.",
1191
904
  "",
905
+ "Every argument is required. Pass null for role, roleIndex, reasoningEffort, or cwd when that",
906
+ "setting is not used. Choose agent explicitly. maxDepth is a non-negative integer. No argument",
907
+ "has an implicit default.",
908
+ "",
1192
909
  "Returns the id of every child it started plus the route each one resolved. The children run",
1193
910
  "in the background: their results arrive later as completion notices in this conversation.",
1194
911
  "Never wait or poll for them, and keep working on something useful meanwhile.",
1195
912
  "",
1196
- "`agent` picks the brief: `poteto-agent` (default) injects the poteto subagent preamble and",
1197
- "the absolute path of the poteto-mode instructions; `comment-sicko` injects the comment",
1198
- "reviewer persona and must be briefed read-only; `general` is a plain child with no preamble.",
913
+ "`agent` picks a DSH agent preset: `poteto-agent` mounts the pstack implementation delegate",
914
+ "and receives the absolute path of the poteto-mode instructions; `comment-sicko` mounts the",
915
+ "read-only comment reviewer; `general` joins the parent preset with no pstack persona.",
1199
916
  "",
1200
917
  "`role` selects a configured pstack role and therefore the child's model route and reasoning",
1201
- "effort. The role names and the meaning of each are in the pstack role manifest, not here.",
1202
- "Omit `role` to inherit the parent route. Panel roles start one child per configured entry.",
918
+ "effort. The role names come from the pstack role manifest, not this schema. Null inherits the",
919
+ "parent route. Automatic panel roles start one child per configured entry.",
1203
920
  "",
1204
- "`reasoningEffort` raises or lowers the effort for this delegation alone. The role line is a",
1205
- "starting baseline, not a verdict: start from it, raise it for hard work and lower it for",
1206
- "routine work. The value must be one the target model advertises, and this package does not",
1207
- "assume a ladder, so read the effort list the role's model reports rather than guessing. An",
1208
- "unsupported value fails the call before any child starts, including every entry of a panel.",
1209
- "",
1210
- "A running child can also change its own effort with `set_reasoning_effort`, which applies",
1211
- "from its next step. You do not have to predict the level up front.",
921
+ "`reasoningEffort` raises or lowers the effort for this delegation alone, using a token the",
922
+ "target model advertises. An unsupported value fails the call before any child starts,",
923
+ "including every entry of a panel. Null keeps the role line's starting effort. A running child",
924
+ "can change its own effort with `set_reasoning_effort`, which applies from its next step.",
1212
925
  "",
1213
926
  "Every prompt is a fresh child that does not see this conversation, so include everything it",
1214
927
  "needs. A child's own summary is not evidence: review its diff or artifacts yourself."
1215
928
  ].join("\n");
1216
- function buildParameters(config) {
1217
- const properties = {
1218
- description: {
1219
- type: "string",
1220
- description: "A short 3-5 word label for the child, shown in list_agents and its notices."
1221
- },
1222
- prompt: {
1223
- type: "string",
1224
- description: "The complete, self-contained brief. The child does not see this conversation, so include the task, every prior directive, and the paths or branches it needs."
1225
- },
1226
- agent: {
1227
- type: "string",
1228
- enum: [...AGENT_KINDS],
1229
- description: "Child brief type: \"poteto-agent\" (default) for pstack implementation delegates, \"comment-sicko\" for the read-only comment reviewer, \"general\" for a plain child with no preamble."
1230
- },
1231
- role: {
1232
- type: "string",
1233
- description: "A pstack role key, matched exactly. It selects the child's model route and effort. Omit it to inherit the parent route."
1234
- },
1235
- cwd: {
1236
- type: "string",
1237
- description: "The child's initial working directory. Relative paths resolve against this session's directory. Omitted inherits it."
1238
- },
1239
- maxDepth: {
1240
- type: "integer",
1241
- description: "How many further delegation levels the child may use; 0 or greater, and negative values are rejected. It can only lower this row's configured cap, never raise it."
1242
- },
1243
- roleIndex: {
1244
- type: "integer",
1245
- description: "Selects one entry of a list-valued role that is not an automatic panel; 0 or greater, and negative values are rejected. Requires `role`."
1246
- },
1247
- reasoningEffort: {
1248
- type: "string",
1249
- description: [
1250
- "Reasoning effort for every child this call starts, overriding the role line's value for this delegation.",
1251
- "Start from the role's level, raise it for hard work, lower it for routine work.",
1252
- "The value must be one the target model advertises: this package assumes no ladder and rejects an unknown token with that model's real effort list.",
1253
- "It is validated for every panel entry before any child starts, so one incompatible entry fails the whole call instead of half-starting it.",
1254
- "Omit it to use the role line's effort."
1255
- ].join(" ")
1256
- }
1257
- };
1258
- if (config.allowedAgentPresets.length > 0) properties["agentPreset"] = {
1259
- type: "string",
1260
- enum: [...config.allowedAgentPresets],
1261
- description: "An independent child preset from this row's allowlist."
1262
- };
929
+ function buildParameters() {
1263
930
  return {
1264
931
  type: "object",
1265
932
  additionalProperties: false,
1266
- properties,
1267
- required: ["description", "prompt"]
933
+ properties: {
934
+ description: {
935
+ type: "string",
936
+ description: "A short 3-5 word label for the child, shown in list_agents and its notices."
937
+ },
938
+ prompt: {
939
+ type: "string",
940
+ description: "The complete, self-contained brief. The child does not see this conversation, so include the task, every prior directive, and the paths or branches it needs."
941
+ },
942
+ agent: {
943
+ type: "string",
944
+ enum: [...AGENT_KINDS],
945
+ description: "Required child preset: \"poteto-agent\" for pstack implementation delegates, \"comment-sicko\" for the read-only comment reviewer, or \"general\" to join the parent preset."
946
+ },
947
+ role: {
948
+ type: ["string", "null"],
949
+ description: "Required pstack role key, matched exactly. It selects the child's model route and effort. Null inherits the parent route."
950
+ },
951
+ cwd: {
952
+ type: ["string", "null"],
953
+ description: "Required initial working directory, inside this session's directory or a separate Git worktree. Relative paths resolve against this session's directory. Null inherits the session directory."
954
+ },
955
+ maxDepth: {
956
+ type: "integer",
957
+ description: "Required non-negative delegation-depth limit. It can only lower this row's configured cap."
958
+ },
959
+ roleIndex: {
960
+ type: ["integer", "null"],
961
+ description: "Required entry index for a list-valued role that is not an automatic panel, 0 or greater. Requires a non-null role. Null selects the first entry."
962
+ },
963
+ reasoningEffort: {
964
+ type: ["string", "null"],
965
+ description: "Required reasoning-effort override for every child this call starts. Use one token the target model advertises, or null to keep the role line's starting effort. An unknown token fails the whole call before any child starts."
966
+ }
967
+ },
968
+ required: [
969
+ "description",
970
+ "prompt",
971
+ "agent",
972
+ "role",
973
+ "roleIndex",
974
+ "reasoningEffort",
975
+ "cwd",
976
+ "maxDepth"
977
+ ]
1268
978
  };
1269
979
  }
1270
980
  const OUTPUT_SCHEMA = {
@@ -1305,7 +1015,7 @@ const OUTPUT_SCHEMA = {
1305
1015
  },
1306
1016
  effort: {
1307
1017
  type: "string",
1308
- description: "The reasoning effort this child runs at, when one applies. Equal to the call's `reasoningEffort` when that was passed."
1018
+ description: "The reasoning effort this child runs at, when one applies."
1309
1019
  },
1310
1020
  maxDepth: {
1311
1021
  type: "integer",
@@ -1323,7 +1033,7 @@ const OUTPUT_SCHEMA = {
1323
1033
  },
1324
1034
  diagnostics: {
1325
1035
  type: "array",
1326
- description: "Configuration notes, including a panel whose entries share one route. Empty when nothing needs reporting.",
1036
+ description: "Configuration notes, including a panel whose entries share one route.",
1327
1037
  items: { type: "string" }
1328
1038
  }
1329
1039
  },
@@ -1339,7 +1049,7 @@ function renderDelegationResult(result) {
1339
1049
  result.kind === "activation" ? `started 1 child: ${result.children[0]?.childId ?? "(none)"}` : `started ${result.children.length} children:`,
1340
1050
  ...result.children.map((child) => `- ${child.childId} (${child.label}) agent=${child.agent} role=${child.role} route=${child.route}${child.effort === void 0 ? "" : ` effort=${child.effort}`}${child.maxDepth === void 0 ? "" : ` maxDepth=${child.maxDepth}`}`),
1341
1051
  "Results arrive as completion notices; do not poll for them. Each child can change its own",
1342
- "effort from its next step with `set_reasoning_effort`; you do not need to predict it up front."
1052
+ "effort from its next step with `set_reasoning_effort`."
1343
1053
  ];
1344
1054
  if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.map((diagnostic) => `- ${diagnostic}`));
1345
1055
  return lines.join("\n");
@@ -1349,7 +1059,7 @@ function createPstackSubagentTool(runtime, config = DEFAULT_CONFIG) {
1349
1059
  return {
1350
1060
  name: TOOL_NAME,
1351
1061
  description: TOOL_DESCRIPTION,
1352
- parameters: buildParameters(config),
1062
+ parameters: buildParameters(),
1353
1063
  output: {
1354
1064
  schema: OUTPUT_SCHEMA,
1355
1065
  render: (_args, value) => [{
@@ -1360,8 +1070,8 @@ function createPstackSubagentTool(runtime, config = DEFAULT_CONFIG) {
1360
1070
  isConcurrencySafe: () => true,
1361
1071
  async execute(rawArgs, exec) {
1362
1072
  const parent = exec.agent;
1363
- if (parent === void 0) throw new DelegationError("pstack_subagent needs a calling Agent, and exec.agent was undefined. This tool runs inside a conversation; it cannot start a child from a context that has no parent Agent.");
1364
- return await delegate(parent, parseDelegationArgs(rawArgs, config), runtime, config, exec.signal);
1073
+ if (parent === void 0) throw new DelegationError("pstack_subagent needs a calling Agent, and exec.agent was undefined.");
1074
+ return await delegate(parent, parseDelegationArgs(rawArgs), runtime, config, exec.signal);
1365
1075
  }
1366
1076
  };
1367
1077
  }
@@ -1370,23 +1080,18 @@ function hostRuntime(ctx, getAgentRegistry = () => void 0) {
1370
1080
  return {
1371
1081
  subagents: ctx.subagents,
1372
1082
  llm: ctx.llm,
1083
+ workingDirectory: ctx.workingDirectory,
1373
1084
  get agents() {
1374
1085
  return getAgentRegistry();
1375
1086
  },
1376
- dshHome: resolveDshHome(),
1377
- assetsRoot: ASSETS_ROOT
1087
+ dshHome: resolveDshHome()
1378
1088
  };
1379
1089
  }
1380
- /**
1381
- * Register `pstack_subagent` for this row.
1382
- *
1383
- * @param ctx - context providing the tool registry, the subagent registry, the LLM
1384
- * adapters, and optionally `ctx.get` for the agent registry.
1385
- * @param config - validated row config.
1386
- */
1387
- function apply(ctx, config = DEFAULT_CONFIG) {
1388
- const tool = createPstackSubagentTool(hostRuntime(ctx, () => ctx.get?.("agents")), config);
1389
- ctx.tools.register(tool);
1090
+ /** Register `pstack_subagent` for this row. */
1091
+ function apply(ctx, config) {
1092
+ const resolved = normalizeConfig(config);
1093
+ const runtime = hostRuntime(ctx, () => ctx.get?.("agents"));
1094
+ ctx.tools.register(createPstackSubagentTool(runtime, resolved));
1390
1095
  }
1391
1096
  function describeError(error) {
1392
1097
  if (error instanceof Error) {
@@ -1396,7 +1101,4 @@ function describeError(error) {
1396
1101
  }
1397
1102
  return String(error);
1398
1103
  }
1399
- //#endregion
1400
- export { AGENT_KINDS, Config, DEFAULT_AGENT_KIND, DEFAULT_SUBAGENT_PROVIDER, DelegationError, OUTPUT_SCHEMA, PartialDelegationError, PreambleError, TOOL_NAME, agentOptionsFor, apply, assertProviderCapabilities, composeChildPrompt, createPstackSubagentTool, delegate, hostRuntime, inject, loadAgentPreamble, name, parseDelegationArgs, planDelegation, plannedRouteLabel, renderDelegationResult, resolveDelegationDepth, routeLabel, startPlannedChildren };
1401
-
1402
- //# sourceMappingURL=subagent.mjs.map
1104
+ export { AGENT_KINDS, AGENT_PRESETS, DEFAULT_SUBAGENT_PROVIDER, DelegationError, PartialDelegationError, TOOL_NAME, agentPresetFor, apply, assertProviderCapabilities, composeChildPrompt, createPstackSubagentTool, delegate, hostRuntime, inject, name, normalizeConfig, parseDelegationArgs, planDelegation, renderDelegationResult, resolveChildCwd, resolveDelegationDepth, routeLabel, startPlannedChildren };