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.
- package/LICENSE +2 -3
- package/README.md +135 -84
- package/README.zh.md +185 -0
- package/cordis.patch.yml +97 -27
- package/dist/host-contract-CUPSpeNk.d.mts +22 -0
- package/dist/index.d.mts +49 -238
- package/dist/index.mjs +250 -123
- package/dist/models.d.mts +44 -0
- package/dist/models.mjs +201 -0
- package/dist/paths-BT77khwm.mjs +48 -0
- package/dist/pstack/LICENSE +21 -0
- package/dist/pstack/README.md +291 -0
- package/dist/pstack/agents/poteto-agent.md +9 -0
- package/{assets → dist/pstack}/docs/guide/01-setup.md +13 -11
- package/{assets → dist/pstack}/docs/guide/02-poteto-mode.md +4 -4
- package/{assets → dist/pstack}/docs/guide/04-design.md +4 -4
- package/{assets → dist/pstack}/docs/guide/05-build-and-clean.md +2 -2
- package/{assets → dist/pstack}/docs/guide/06-verify-and-ship.md +13 -3
- package/{assets → dist/pstack}/docs/guide/07-overnight.md +15 -13
- package/{assets → dist/pstack}/docs/guide/09-make-it-yours.md +6 -2
- package/{assets → dist/pstack}/docs/guide/10-recipes-and-pitfalls.md +7 -5
- package/{assets → dist/pstack}/docs/guide/README.md +1 -1
- package/dist/pstack/docs/harness/dsh.md +98 -0
- package/{assets → dist/pstack}/skills/architect/SKILL.md +12 -12
- package/dist/pstack/skills/arena/SKILL.md +50 -0
- package/{assets → dist/pstack}/skills/automate-me/SKILL.md +14 -14
- package/{assets → dist/pstack}/skills/benchmark-checklist/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/blast-radius/SKILL.md +4 -6
- package/{assets → dist/pstack}/skills/correct/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/create-formal-verification-skill/SKILL.md +4 -2
- package/{assets → dist/pstack}/skills/create-verification-skill/SKILL.md +5 -5
- package/dist/pstack/skills/figure-it-out/SKILL.md +54 -0
- package/dist/pstack/skills/how/SKILL.md +38 -0
- package/{assets → dist/pstack}/skills/in-your-own-words/SKILL.md +1 -1
- package/dist/pstack/skills/interrogate/SKILL.md +33 -0
- package/{assets → dist/pstack}/skills/ketchup/SKILL.md +3 -5
- package/dist/pstack/skills/ketchup-video/SKILL.md +29 -0
- package/{assets → dist/pstack}/skills/maintain-formal-verification-skill/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/maintain-verification-skill/SKILL.md +3 -3
- package/dist/pstack/skills/make-bot-ui/SKILL.md +117 -0
- package/dist/pstack/skills/no-comments/SKILL.md +26 -0
- package/{assets → dist/pstack}/skills/poteto-help/SKILL.md +34 -31
- package/{assets → dist/pstack}/skills/poteto-help/references/prompting.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-help/references/recipes.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/SKILL.md +20 -19
- package/dist/pstack/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/babysit.md +4 -4
- package/dist/pstack/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/eval.md +3 -3
- package/dist/pstack/skills/poteto-mode/playbooks/feature.md +21 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/hillclimb.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/investigation.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/multi-phase-plan.md +23 -23
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
- package/dist/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/pause-safely.md +2 -2
- package/dist/pstack/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/prototype.md +4 -4
- package/dist/pstack/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/dist/pstack/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/dist/pstack/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/shipping.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
- package/dist/pstack/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/dist/pstack/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/check-plan.mjs +6 -8
- package/dist/pstack/skills/poteto-mode/scripts/check-plan.test.mjs +128 -0
- package/{assets/skills/poteto-mode/scripts/check-plan.test.mjs → dist/pstack/skills/poteto-mode/scripts/expect.mjs} +12 -101
- package/dist/pstack/skills/poteto-mode/scripts/support.mjs +93 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +123 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +217 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +119 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +626 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +521 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +657 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +523 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +106 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +9 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +5 -0
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/worktree-audit.mjs +15 -22
- package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +134 -0
- package/{assets → dist/pstack}/skills/principle-attack-the-premise/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-boundary-discipline/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-encode-lessons-in-structure/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-exhaust-the-design-space/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-experience-first/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-explain-the-number/SKILL.md +3 -2
- package/{assets → dist/pstack}/skills/principle-fix-root-causes/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-laziness-protocol/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-make-operations-idempotent/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-minimize-reader-load/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-model-the-domain/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-never-block-on-the-human/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-outcome-oriented-execution/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-prove-it-works/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/principle-redesign-from-first-principles/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-separate-before-serializing-shared-state/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-sequence-verifiable-units/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-subtract-before-you-add/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-test-behavior-not-implementation/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-type-system-discipline/SKILL.md +1 -1
- package/dist/pstack/skills/recall/SKILL.md +37 -0
- package/dist/pstack/skills/reflect/SKILL.md +71 -0
- package/{assets → dist/pstack}/skills/reflect/references/divergent-reviewer.md +4 -4
- package/{assets → dist/pstack}/skills/reflect/references/judgment-reviewer.md +2 -2
- package/{assets → dist/pstack}/skills/reflect/references/synthesizer.md +5 -5
- package/{assets → dist/pstack}/skills/reflect/references/tooling-reviewer.md +2 -2
- package/dist/pstack/skills/setup-pstack/SKILL.md +80 -0
- package/{assets → dist/pstack}/skills/sharpen/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/show-me-your-work/SKILL.md +8 -8
- package/{assets → dist/pstack}/skills/show-me-your-work/scripts/log.mjs +1 -3
- package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +56 -0
- package/dist/pstack/skills/statechart/SKILL.md +82 -0
- package/dist/pstack/skills/statechart/references/render.md +93 -0
- package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +31 -0
- package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +186 -0
- package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +62 -0
- package/dist/pstack/skills/statechart/scripts/backends/report.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +67 -0
- package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +94 -0
- package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +48 -0
- package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +101 -0
- package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +39 -0
- package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +180 -0
- package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +40 -0
- package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +99 -0
- package/dist/pstack/skills/statechart/scripts/ir.mjs +277 -0
- package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +92 -0
- package/dist/pstack/skills/statechart/scripts/passes/label.mjs +210 -0
- package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +47 -0
- package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +135 -0
- package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +82 -0
- package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/pipeline.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/run.mjs +440 -0
- package/dist/pstack/skills/statechart/scripts/statechart.mjs +155 -0
- package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +198 -0
- package/dist/pstack/skills/statechart/scripts/worker.mjs +31 -0
- package/dist/pstack/skills/swarm/SKILL.md +43 -0
- package/{assets → dist/pstack}/skills/tdd/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/teach/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/technical-writing/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/typescript-best-practices/SKILL.md +3 -6
- package/{assets → dist/pstack}/skills/why/SKILL.md +8 -8
- package/dist/script.d.mts +9 -111
- package/dist/script.mjs +32 -329
- package/dist/skill.d.mts +38 -0
- package/dist/skill.mjs +183 -0
- package/dist/skills/authored/decision-audit/SKILL.md +99 -0
- package/dist/skills/authored/decision-audit/SKILL.zh.md +99 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.md +17 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +17 -0
- package/dist/skills/authored/decision-write/SKILL.md +131 -0
- package/dist/skills/authored/decision-write/SKILL.zh.md +131 -0
- package/dist/skills/authored/decision-write/references/land.md +48 -0
- package/dist/skills/authored/decision-write/references/land.zh.md +48 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +58 -0
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +58 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +11 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +11 -0
- package/dist/skills/vendored/retro/SKILL.md +44 -0
- package/dist/skills/vendored/retro/agents/openai.yaml +5 -0
- package/dist/skills/vendored/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/dist/skills/vendored/writing-for-agents/SKILL.md +81 -0
- package/dist/skills/vendored/writing-for-agents/agents/openai.yaml +3 -0
- package/dist/subagent.d.mts +56 -193
- package/dist/subagent.mjs +443 -741
- package/locale/en.json +3 -2
- package/locale/zh.json +3 -2
- package/package.json +63 -66
- package/assets/agents/poteto-agent.md +0 -8
- package/assets/docs/harness/dsh.md +0 -60
- package/assets/docs/pstack-models.example.md +0 -54
- package/assets/skills/arena/SKILL.md +0 -50
- package/assets/skills/figure-it-out/SKILL.md +0 -54
- package/assets/skills/how/SKILL.md +0 -38
- package/assets/skills/interrogate/SKILL.md +0 -71
- package/assets/skills/interrogate/references/code-quality-review.md +0 -47
- package/assets/skills/interrogate/references/lead-judgment.md +0 -39
- package/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
- package/assets/skills/interrogate/references/rubric.md +0 -77
- package/assets/skills/no-comments/SKILL.md +0 -26
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -15
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
- package/assets/skills/poteto-mode/playbooks/feature.md +0 -21
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
- package/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -20
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +0 -4282
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +0 -2042
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +0 -5101
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +0 -1177
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +0 -1441
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +0 -4601
- package/assets/skills/recall/SKILL.md +0 -37
- package/assets/skills/reflect/SKILL.md +0 -69
- package/assets/skills/setup-pstack/SKILL.md +0 -67
- package/assets/skills/swarm/SKILL.md +0 -43
- package/dist/frontmatter-BQX3ekwP.mjs +0 -229
- package/dist/frontmatter-BQX3ekwP.mjs.map +0 -1
- package/dist/history.d.mts +0 -429
- package/dist/history.mjs +0 -1129
- package/dist/history.mjs.map +0 -1
- package/dist/host-contract-o47JH_r8.d.mts +0 -527
- package/dist/index.mjs.map +0 -1
- package/dist/paths-D_JlVmBk.mjs +0 -42
- package/dist/paths-D_JlVmBk.mjs.map +0 -1
- package/dist/script.mjs.map +0 -1
- package/dist/subagent.mjs.map +0 -1
- /package/{static/icon.png → assets/logo.png} +0 -0
- /package/{assets → dist/pstack}/agents/comment-sicko.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/03-understand.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/08-principles.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/design.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/overnight.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/recipes.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/router.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/understanding.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/verification.jpg +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/design-red-flags.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/rationale-template.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/runner-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/bro/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/README.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/search.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explainer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explorer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/references/bugbot-triage.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-build-the-lever/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-foundational-thinking/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-guard-the-context-window/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/show-me-your-work/references/decision-log-template.tsv +0 -0
- /package/{assets → dist/pstack}/skills/typescript-best-practices/references/patterns.md +0 -0
- /package/{assets → dist/pstack}/skills/unslop/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/epistemics.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/investigator-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/source-playbook.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/code-archaeology.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/databricks.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/datadog.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/incident-postmortem.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/linear.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/notion.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/sentry.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/slack.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/synthesizer-prompt.md +0 -0
package/dist/subagent.mjs
CHANGED
|
@@ -1,162 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
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
|
|
217
|
-
|
|
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
|
-
|
|
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: {
|
|
244
|
-
type: "
|
|
245
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
122
|
+
* File name the role configuration lives under inside `$DSH_HOME`.
|
|
326
123
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
//#region src/models.ts
|
|
223
|
+
// src/internal/role-config.ts
|
|
397
224
|
/**
|
|
398
|
-
* The pstack role
|
|
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
|
-
*
|
|
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
|
|
404
|
-
*
|
|
405
|
-
*
|
|
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
|
-
|
|
423
|
-
|
|
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
|
|
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
|
|
261
|
+
return parseRoleConfiguration(text, path);
|
|
442
262
|
}
|
|
443
263
|
/** Parse role-file text. Pure: no file system access. */
|
|
444
|
-
function
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
501
|
-
|
|
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
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
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
|
|
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
|
-
|
|
626
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
657
|
-
*
|
|
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,
|
|
668
|
-
*
|
|
669
|
-
*
|
|
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
|
-
*
|
|
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
|
|
836
|
-
*
|
|
837
|
-
*
|
|
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
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
-
/**
|
|
873
|
-
function
|
|
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 =
|
|
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
|
-
|
|
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
|
|
931
|
-
if (
|
|
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 !==
|
|
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 !==
|
|
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 (
|
|
651
|
+
if (!isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer");
|
|
945
652
|
const roleIndex = record["roleIndex"];
|
|
946
|
-
if (roleIndex !==
|
|
947
|
-
|
|
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 !==
|
|
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
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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 =
|
|
975
|
-
if (roleFile.status === "unreadable") throw new DelegationError(`the pstack role file at ${rolePath} exists but cannot be read (${roleFile.reason}).
|
|
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 !==
|
|
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
|
|
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
|
-
|
|
999
|
-
|
|
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:
|
|
727
|
+
routeLabel: routeLabel(route, args.reasoningEffort ?? void 0),
|
|
1015
728
|
...effort === void 0 ? {} : { effort },
|
|
1016
729
|
...agentOptions === void 0 ? {} : { agentOptions },
|
|
1017
|
-
...
|
|
1018
|
-
...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 !==
|
|
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
|
|
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 ===
|
|
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
|
|
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
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
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)}
|
|
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(
|
|
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)}.
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
815
|
+
isDirectory = false;
|
|
1102
816
|
}
|
|
1103
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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(", ")}
|
|
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
|
|
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
|
|
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
|
|
1197
|
-
"the absolute path of the poteto-mode instructions; `comment-sicko`
|
|
1198
|
-
"
|
|
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
|
|
1202
|
-
"
|
|
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
|
|
1205
|
-
"
|
|
1206
|
-
"
|
|
1207
|
-
"
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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.
|
|
1364
|
-
return await delegate(parent, parseDelegationArgs(rawArgs
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
-
|
|
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 };
|