dsh-pstack 0.2.0-beta.1 → 0.2.0
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 +148 -84
- package/README.zh.md +198 -0
- package/assets/pstack-config-entry.png +0 -0
- package/cordis.patch.yml +105 -27
- package/dist/client.mjs +2341 -0
- package/dist/client.mjs.map +1 -0
- 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/remote.d.mts +27 -0
- package/dist/remote.mjs +234 -0
- package/dist/role-config-NNn5r1JX.mjs +415 -0
- package/dist/role-manifest-C3fUzjLI.d.mts +19 -0
- package/dist/role-wire-DZwTn2wF.d.mts +55 -0
- package/dist/roles.d.mts +18 -0
- package/dist/roles.mjs +323 -0
- 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 +103 -0
- package/dist/skills/authored/decision-audit/SKILL.zh.md +103 -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 +133 -0
- package/dist/skills/authored/decision-write/SKILL.zh.md +133 -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/decision-write/references/scope.md +16 -0
- package/dist/skills/authored/decision-write/references/scope.zh.md +16 -0
- package/dist/skills/authored/docs-drift/SKILL.md +120 -0
- package/dist/skills/authored/docs-drift/SKILL.zh.md +120 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +63 -0
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +63 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +13 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +13 -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 +54 -206
- package/dist/subagent.mjs +258 -963
- package/locale/en.json +3 -2
- package/locale/zh.json +3 -2
- package/package.json +93 -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 { join } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
//#region scripts/role-manifest.ts
|
|
7
|
-
/** File name the role policy lives under inside `$DSH_HOME`. */
|
|
8
|
-
const PSTACK_MODELS_FILENAME = "pstack-models.md";
|
|
9
|
-
/** Values that resolve to "send no `agentOptions` and inherit the parent route". */
|
|
10
|
-
const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
|
|
11
|
-
const ROLE_MANIFEST = [
|
|
12
|
-
{
|
|
13
|
-
key: "feature, refactoring",
|
|
14
|
-
shape: "single",
|
|
15
|
-
autoPanel: false,
|
|
16
|
-
consumers: ["poteto-mode/playbooks/feature.md", "poteto-mode/playbooks/refactoring.md"],
|
|
17
|
-
summary: "One route for both code playbooks. The comma joins two playbook names into a single key."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
key: "bug-fix",
|
|
21
|
-
shape: "single",
|
|
22
|
-
autoPanel: false,
|
|
23
|
-
consumers: ["poteto-mode/playbooks/bug-fix.md"],
|
|
24
|
-
summary: "Implementation route for the bug-fix playbook."
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
key: "perf-issue",
|
|
28
|
-
shape: "single",
|
|
29
|
-
autoPanel: false,
|
|
30
|
-
consumers: ["poteto-mode/playbooks/perf-issue.md"],
|
|
31
|
-
summary: "Implementation route for the perf-issue playbook."
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
key: "hillclimb",
|
|
35
|
-
shape: "single",
|
|
36
|
-
autoPanel: false,
|
|
37
|
-
consumers: ["poteto-mode/playbooks/hillclimb.md"],
|
|
38
|
-
summary: "Implementation route for the hillclimb playbook."
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
key: "judgment and prose",
|
|
42
|
-
shape: "single",
|
|
43
|
-
autoPanel: false,
|
|
44
|
-
consumers: [
|
|
45
|
-
"poteto-mode/SKILL.md",
|
|
46
|
-
"unslop",
|
|
47
|
-
"technical-writing"
|
|
48
|
-
],
|
|
49
|
-
summary: "Route for prose work and vague-instruction judgment calls."
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
key: "hardest tasks",
|
|
53
|
-
shape: "single",
|
|
54
|
-
autoPanel: false,
|
|
55
|
-
consumers: ["poteto-mode/SKILL.md"],
|
|
56
|
-
summary: "Route for the strongest judgment model on cross-cutting or subtle work."
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
key: "how explorer",
|
|
60
|
-
shape: "single",
|
|
61
|
-
autoPanel: false,
|
|
62
|
-
consumers: ["how/SKILL.md", "how/references/explorer-prompt.md"],
|
|
63
|
-
summary: "Route for each how explorer child."
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
key: "how explainer",
|
|
67
|
-
shape: "single",
|
|
68
|
-
autoPanel: false,
|
|
69
|
-
consumers: ["how/SKILL.md", "how/references/explainer-prompt.md"],
|
|
70
|
-
summary: "Route for the single how explainer child."
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
key: "why investigators",
|
|
74
|
-
shape: "single",
|
|
75
|
-
autoPanel: false,
|
|
76
|
-
consumers: ["why/SKILL.md", "why/references/investigator-prompt.md"],
|
|
77
|
-
summary: "Route for each why investigator child."
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
key: "why synthesizer",
|
|
81
|
-
shape: "single",
|
|
82
|
-
autoPanel: false,
|
|
83
|
-
consumers: ["why/SKILL.md", "why/references/synthesizer-prompt.md"],
|
|
84
|
-
summary: "Route for the why synthesis child."
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
key: "reflect tooling",
|
|
88
|
-
shape: "single",
|
|
89
|
-
autoPanel: false,
|
|
90
|
-
consumers: ["reflect/SKILL.md", "reflect/references/tooling-reviewer.md"],
|
|
91
|
-
summary: "Route for the reflect tooling reviewer."
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
key: "reflect judgment, divergent, synthesizer",
|
|
95
|
-
shape: "single",
|
|
96
|
-
autoPanel: false,
|
|
97
|
-
consumers: [
|
|
98
|
-
"reflect/SKILL.md",
|
|
99
|
-
"reflect/references/judgment-reviewer.md",
|
|
100
|
-
"reflect/references/divergent-reviewer.md",
|
|
101
|
-
"reflect/references/synthesizer.md"
|
|
102
|
-
],
|
|
103
|
-
summary: "One shared route for the judgment reviewer, the divergent reviewer, and the synthesizer. The commas belong to the key."
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
key: "arena runners",
|
|
107
|
-
shape: "list",
|
|
108
|
-
autoPanel: true,
|
|
109
|
-
consumers: ["arena/SKILL.md"],
|
|
110
|
-
summary: "Panel: one arena child per entry, all started together."
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
key: "arena cross-judge pool",
|
|
114
|
-
shape: "list",
|
|
115
|
-
autoPanel: false,
|
|
116
|
-
consumers: ["arena/SKILL.md"],
|
|
117
|
-
summary: "A pool arena picks one entry from, preferring a model family unlike the parent's. Not fanned out automatically."
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
key: "swarm workers",
|
|
121
|
-
shape: "list",
|
|
122
|
-
autoPanel: false,
|
|
123
|
-
consumers: ["swarm/SKILL.md", "poteto-mode/playbooks/multi-phase-plan.md"],
|
|
124
|
-
summary: "One default route for every worker. A race gives each arm another entry through `roleIndex`, and the caller still starts one worker per call."
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
key: "architect runners",
|
|
128
|
-
shape: "list",
|
|
129
|
-
autoPanel: true,
|
|
130
|
-
consumers: ["architect/SKILL.md"],
|
|
131
|
-
summary: "Panel: one architect child per entry, all started together."
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
key: "interrogate reviewers",
|
|
135
|
-
shape: "list",
|
|
136
|
-
autoPanel: true,
|
|
137
|
-
consumers: ["interrogate/SKILL.md"],
|
|
138
|
-
summary: "Panel: one interrogate reviewer per entry, all started together."
|
|
139
|
-
}
|
|
140
|
-
];
|
|
141
|
-
ROLE_MANIFEST.map((role) => role.key);
|
|
142
|
-
ROLE_MANIFEST.filter((role) => role.autoPanel).map((role) => role.key);
|
|
143
|
-
/** Exact manifest lookup. Callers normalize the key first. */
|
|
144
|
-
function findRoleSpec(key) {
|
|
145
|
-
return ROLE_MANIFEST.find((role) => role.key === key);
|
|
146
|
-
}
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/effort.ts
|
|
1
|
+
import { a as POTETO_MODE_SKILL_PATH } from "./paths-BT77khwm.mjs";
|
|
2
|
+
import { a as resolveDshHome, i as requireRole, o as selectRoleRoutes, r as readRoleConfiguration, t as modelsFilePath } from "./role-config-NNn5r1JX.mjs";
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { existsSync, statSync } from "node:fs";
|
|
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.");
|
|
@@ -187,7 +30,7 @@ async function validateEffortFor(llm, route, effort, parent, signal) {
|
|
|
187
30
|
try {
|
|
188
31
|
efforts = await advertisedEfforts(llm, target, signal);
|
|
189
32
|
} catch (error) {
|
|
190
|
-
throw new Error(`route ${target.provider}/${target.model} could not be resolved for reasoningEffort: ${describeError$
|
|
33
|
+
throw new Error(`route ${target.provider}/${target.model} could not be resolved for reasoningEffort: ${describeError$1(error)}`, { cause: error });
|
|
191
34
|
}
|
|
192
35
|
if (efforts.length === 0) throw new Error(`${target.provider}/${target.model} advertises no reasoning efforts, so it cannot take reasoningEffort "${effort}". Omit the argument and let the adapter use its own default.`);
|
|
193
36
|
if (!efforts.includes(effort)) throw new Error(`${target.provider}/${target.model} does not support reasoning effort "${effort}". It supports: ${efforts.join(", ")}.`);
|
|
@@ -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,32 +76,27 @@ 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 {
|
|
263
84
|
efforts = await advertisedEfforts(llm, target, exec.signal);
|
|
264
85
|
} catch (error) {
|
|
265
|
-
throw new Error(`${target.provider}/${target.model} could not be resolved for reasoning effort: ${describeError$
|
|
86
|
+
throw new Error(`${target.provider}/${target.model} could not be resolved for reasoning effort: ${describeError$1(error)}`, { cause: error });
|
|
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 };
|
|
@@ -295,407 +110,45 @@ function agentOptionsFor(route, effortOverride) {
|
|
|
295
110
|
function effectiveEffort(route, effortOverride) {
|
|
296
111
|
return effortOverride ?? (route.kind === "route" ? route.effort : void 0);
|
|
297
112
|
}
|
|
298
|
-
function describeError$2(error) {
|
|
299
|
-
if (error instanceof Error) {
|
|
300
|
-
const code = error.code;
|
|
301
|
-
return typeof code === "string" ? `${error.message} (${code})` : error.message;
|
|
302
|
-
}
|
|
303
|
-
return String(error);
|
|
304
|
-
}
|
|
305
|
-
//#endregion
|
|
306
|
-
//#region src/roles.ts
|
|
307
|
-
/**
|
|
308
|
-
* Role lookup over the single manifest in `scripts/role-manifest.ts`.
|
|
309
|
-
*
|
|
310
|
-
* A role name is matched exactly after normalization. A near miss is reported as
|
|
311
|
-
* a hint and never guessed, because silently running a role on the wrong route is
|
|
312
|
-
* worse than refusing the delegation.
|
|
313
|
-
*
|
|
314
|
-
* Commas belong to some keys (`feature, refactoring`), so nothing here splits a
|
|
315
|
-
* key on a comma. Only a role file *value* is a comma-separated list.
|
|
316
|
-
*/
|
|
317
|
-
/** Raised when a role name does not match the manifest. */
|
|
318
|
-
var RoleNameError = class extends Error {
|
|
319
|
-
constructor(message) {
|
|
320
|
-
super(message);
|
|
321
|
-
this.name = "RoleNameError";
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
/**
|
|
325
|
-
* Normalize a role name for lookup.
|
|
326
|
-
*
|
|
327
|
-
* Case, surrounding whitespace, runs of whitespace, spacing around a comma, and a
|
|
328
|
-
* trailing colon are formatting, not naming. Nothing else is normalized: a
|
|
329
|
-
* different word is a different role.
|
|
330
|
-
*/
|
|
331
|
-
function normalizeRoleName(raw) {
|
|
332
|
-
return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
|
|
333
|
-
}
|
|
334
|
-
/** Look a role up by its exact normalized key. */
|
|
335
|
-
function lookupRole(raw) {
|
|
336
|
-
return findRoleSpec(normalizeRoleName(raw));
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Near-miss hints for a role name that did not match.
|
|
340
|
-
*
|
|
341
|
-
* Only used to build a diagnostic. A suggestion is never turned into a selection.
|
|
342
|
-
*/
|
|
343
|
-
function suggestRoles(raw, limit = 3) {
|
|
344
|
-
const target = normalizeRoleName(raw);
|
|
345
|
-
if (target === "") return [];
|
|
346
|
-
return ROLE_MANIFEST.map((role) => {
|
|
347
|
-
const shared = sharedTokenCount(target, role.key);
|
|
348
|
-
const distance = editDistance(target, role.key);
|
|
349
|
-
const contains = role.key.includes(target) || target.includes(role.key);
|
|
350
|
-
const score = (contains ? 1e3 : 0) + shared * 100 - distance + Math.min(role.key.length, target.length);
|
|
351
|
-
return {
|
|
352
|
-
key: role.key,
|
|
353
|
-
score,
|
|
354
|
-
distance,
|
|
355
|
-
shared,
|
|
356
|
-
contains
|
|
357
|
-
};
|
|
358
|
-
}).filter((candidate) => candidate.contains || candidate.shared > 0 || candidate.distance <= 4).sort((left, right) => right.score - left.score).slice(0, limit).map((candidate) => candidate.key);
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Resolve a role name, or throw a diagnostic that names the near misses.
|
|
362
|
-
*/
|
|
363
|
-
function requireRole(raw) {
|
|
364
|
-
const spec = lookupRole(raw);
|
|
365
|
-
if (spec !== void 0) return spec;
|
|
366
|
-
const suggestions = suggestRoles(raw);
|
|
367
|
-
const hint = suggestions.length === 0 ? "No role key is close to it." : `Closest role keys: ${suggestions.map((key) => `"${key}"`).join(", ")}.`;
|
|
368
|
-
throw new RoleNameError(`unknown pstack role ${JSON.stringify(raw)}. ${hint} Role names come from the role manifest, not from free text: pass the exact key, or omit \`role\` to inherit the parent route.`);
|
|
369
|
-
}
|
|
370
|
-
function sharedTokenCount(left, right) {
|
|
371
|
-
const rightTokens = new Set(tokenize(right));
|
|
372
|
-
return [...new Set(tokenize(left))].filter((token) => rightTokens.has(token)).length;
|
|
373
|
-
}
|
|
374
|
-
function tokenize(value) {
|
|
375
|
-
return value.split(/[^a-z0-9]+/).filter((token) => token !== "");
|
|
376
|
-
}
|
|
377
|
-
/** Levenshtein distance, iterative with one row of state. */
|
|
378
|
-
function editDistance(left, right) {
|
|
379
|
-
if (left === right) return 0;
|
|
380
|
-
if (left === "") return right.length;
|
|
381
|
-
if (right === "") return left.length;
|
|
382
|
-
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
383
|
-
for (let row = 1; row <= left.length; row += 1) {
|
|
384
|
-
const current = [row];
|
|
385
|
-
for (let column = 1; column <= right.length; column += 1) {
|
|
386
|
-
const substitution = previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1);
|
|
387
|
-
const deletion = previous[column] + 1;
|
|
388
|
-
const insertion = current[column - 1] + 1;
|
|
389
|
-
current.push(Math.min(substitution, deletion, insertion));
|
|
390
|
-
}
|
|
391
|
-
previous = current;
|
|
392
|
-
}
|
|
393
|
-
return previous[right.length];
|
|
394
|
-
}
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/models.ts
|
|
397
|
-
/**
|
|
398
|
-
* The pstack role file: `$DSH_HOME/pstack-models.md`.
|
|
399
|
-
*
|
|
400
|
-
* `pstack_subagent` reads this file again on every delegation, so a change reaches
|
|
401
|
-
* the next child and never a child that already started.
|
|
402
|
-
*
|
|
403
|
-
* Three states are kept apart on purpose, because they need different fixes:
|
|
404
|
-
*
|
|
405
|
-
* - the file does not exist: no policy is configured, so every role inherits the
|
|
406
|
-
* parent route;
|
|
407
|
-
* - the file exists but cannot be read: a broken installation or permission
|
|
408
|
-
* problem, reported as an error and never treated as "no configuration";
|
|
409
|
-
* - the file loads but a line is wrong: a typo in a key, an unknown retired role,
|
|
410
|
-
* or a malformed value is reported with the line number and the near misses.
|
|
411
|
-
*/
|
|
412
|
-
/** Absolute path of the role file for one DSH home. */
|
|
413
|
-
function modelsFilePath(dshHome) {
|
|
414
|
-
return join(dshHome, PSTACK_MODELS_FILENAME);
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Resolve `$DSH_HOME` the way the Host does: the environment when set, else
|
|
418
|
-
* `~/.dsh`.
|
|
419
|
-
*/
|
|
420
|
-
function resolveDshHome(env = process.env, home = homedir()) {
|
|
421
|
-
const configured = env["DSH_HOME"];
|
|
422
|
-
if (configured !== void 0 && configured.trim() !== "") return configured;
|
|
423
|
-
return join(home, ".dsh");
|
|
424
|
-
}
|
|
425
|
-
/** Read the role file. Never throws for a missing or unreadable file. */
|
|
426
|
-
function readRoleFile(path) {
|
|
427
|
-
let text;
|
|
428
|
-
try {
|
|
429
|
-
text = readFileSync(path, "utf8");
|
|
430
|
-
} catch (error) {
|
|
431
|
-
if (isMissingFile(error)) return {
|
|
432
|
-
status: "missing",
|
|
433
|
-
path
|
|
434
|
-
};
|
|
435
|
-
return {
|
|
436
|
-
status: "unreadable",
|
|
437
|
-
path,
|
|
438
|
-
reason: describeError$1(error)
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
return parseRoleFile(text, path);
|
|
442
|
-
}
|
|
443
|
-
/** Parse role-file text. Pure: no file system access. */
|
|
444
|
-
function parseRoleFile(text, path) {
|
|
445
|
-
const issues = [];
|
|
446
|
-
const roles = /* @__PURE__ */ new Map();
|
|
447
|
-
const lines = stripFrontmatter(text).split("\n");
|
|
448
|
-
let budget;
|
|
449
|
-
lines.forEach((rawLine, index) => {
|
|
450
|
-
const line = index + 1;
|
|
451
|
-
const trimmed = rawLine.trim();
|
|
452
|
-
if (trimmed === "") return;
|
|
453
|
-
if (trimmed.startsWith("#")) {
|
|
454
|
-
const match = /^#\s*budget:\s*(.+)$/i.exec(trimmed);
|
|
455
|
-
if (match !== null) budget = match[1].trim();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
const separator = trimmed.indexOf(":");
|
|
459
|
-
if (separator === -1) {
|
|
460
|
-
issues.push({
|
|
461
|
-
line,
|
|
462
|
-
message: `expected \`<role>: <value>\`, found ${JSON.stringify(trimmed)}`
|
|
463
|
-
});
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
const rawKey = trimmed.slice(0, separator).trim();
|
|
467
|
-
const rawValue = trimmed.slice(separator + 1).trim();
|
|
468
|
-
const key = normalizeRoleName(rawKey);
|
|
469
|
-
if (key === "") {
|
|
470
|
-
issues.push({
|
|
471
|
-
line,
|
|
472
|
-
message: "the role key is empty"
|
|
473
|
-
});
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
const spec = findSpec(key);
|
|
477
|
-
if (spec === void 0) {
|
|
478
|
-
issues.push({
|
|
479
|
-
line,
|
|
480
|
-
message: unknownRoleMessage(rawKey, key)
|
|
481
|
-
});
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
const parsed = parseRouteValue(rawValue);
|
|
485
|
-
for (const issue of parsed.issues) issues.push({
|
|
486
|
-
line,
|
|
487
|
-
message: `${rawKey}: ${issue}`
|
|
488
|
-
});
|
|
489
|
-
if (parsed.routes.length === 0) return;
|
|
490
|
-
const previous = roles.get(spec.key);
|
|
491
|
-
if (previous !== void 0) issues.push({
|
|
492
|
-
line,
|
|
493
|
-
message: `role "${spec.key}" is already configured on line ${previous.line}; line ${line} wins`
|
|
494
|
-
});
|
|
495
|
-
roles.set(spec.key, {
|
|
496
|
-
line,
|
|
497
|
-
rawKey,
|
|
498
|
-
key: spec.key,
|
|
499
|
-
spec,
|
|
500
|
-
rawValue,
|
|
501
|
-
routes: parsed.routes
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
return budget === void 0 ? {
|
|
505
|
-
status: "loaded",
|
|
506
|
-
path,
|
|
507
|
-
roles,
|
|
508
|
-
issues
|
|
509
|
-
} : {
|
|
510
|
-
status: "loaded",
|
|
511
|
-
path,
|
|
512
|
-
budget,
|
|
513
|
-
roles,
|
|
514
|
-
issues
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
/** Parse a role value: one route, or a comma-separated list of routes. */
|
|
518
|
-
function parseRouteValue(raw) {
|
|
519
|
-
const routes = [];
|
|
520
|
-
const issues = [];
|
|
521
|
-
const segments = raw.split(",");
|
|
522
|
-
segments.forEach((segment, index) => {
|
|
523
|
-
const trimmed = segment.trim();
|
|
524
|
-
if (trimmed === "") {
|
|
525
|
-
if (index !== segments.length - 1) issues.push(`entry ${index + 1} of the list is empty`);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const parsed = parseRoute(trimmed);
|
|
529
|
-
if (parsed.route === void 0) {
|
|
530
|
-
issues.push(parsed.issue);
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
routes.push(parsed.route);
|
|
534
|
-
});
|
|
535
|
-
if (routes.length === 0 && issues.length === 0) issues.push("the value is empty; use `inherit-parent`, `auto`, or `<provider>/<model> [effort]`");
|
|
536
|
-
return {
|
|
537
|
-
routes,
|
|
538
|
-
issues
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
/** Parse one entry of a role value. */
|
|
542
|
-
function parseRoute(raw) {
|
|
543
|
-
const value = raw.trim();
|
|
544
|
-
if (value === "") return { issue: "the value is empty" };
|
|
545
|
-
if (INHERIT_PARENT_ALIASES.includes(value.toLowerCase())) return { route: { kind: "inherit" } };
|
|
546
|
-
const match = /^([^\s/,[]+)\/([^\s/,[\]]+(?:\/[^\s/,[\]]+)*)(?:\s+\[([^[\]]*)\])?$/.exec(value);
|
|
547
|
-
if (match === null) return { issue: `${JSON.stringify(value)} is not a route; use \`inherit-parent\`, \`auto\`, or \`<provider>/<model> [effort]\`` };
|
|
548
|
-
const provider = match[1];
|
|
549
|
-
const model = match[2];
|
|
550
|
-
const effort = match[3];
|
|
551
|
-
if (effort === void 0) return { route: {
|
|
552
|
-
kind: "route",
|
|
553
|
-
provider,
|
|
554
|
-
model
|
|
555
|
-
} };
|
|
556
|
-
const trimmedEffort = effort.trim();
|
|
557
|
-
if (trimmedEffort === "") return { issue: `${JSON.stringify(value)} has empty brackets; write \`<provider>/<model> [effort]\` or drop the brackets` };
|
|
558
|
-
if (/\s/.test(trimmedEffort)) return { issue: `${JSON.stringify(value)} has a multi-word effort; an effort is one adapter-owned token` };
|
|
559
|
-
return { route: {
|
|
560
|
-
kind: "route",
|
|
561
|
-
provider,
|
|
562
|
-
model,
|
|
563
|
-
effort: trimmedEffort
|
|
564
|
-
} };
|
|
565
|
-
}
|
|
566
|
-
/** Raised when a role's configured list cannot be selected from. */
|
|
567
|
-
var RouteSelectionError = class extends Error {
|
|
568
|
-
constructor(message) {
|
|
569
|
-
super(message);
|
|
570
|
-
this.name = "RouteSelectionError";
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
/**
|
|
574
|
-
* Expand one role into the routes this call starts.
|
|
575
|
-
*
|
|
576
|
-
* The three auto-panel roles start one child per entry. Another list role selects
|
|
577
|
-
* one entry, the first by default, and `roleIndex` chooses which. A single role
|
|
578
|
-
* holds exactly one route and rejects `roleIndex`.
|
|
579
|
-
*/
|
|
580
|
-
function selectRoleRoutes(spec, entry, roleIndex) {
|
|
581
|
-
if (spec.shape === "single" && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" holds a single route, so \`roleIndex\` does not apply; drop \`roleIndex\` or use a list-valued role (${listRoleKeys().join(", ")})`);
|
|
582
|
-
if (spec.autoPanel && roleIndex !== void 0) throw new RouteSelectionError(`role "${spec.key}" is an automatic panel: it starts one child per entry, so \`roleIndex\` does not apply; configure the entries you want in ${PSTACK_MODELS_FILENAME}`);
|
|
583
|
-
if (entry === void 0) return {
|
|
584
|
-
role: spec,
|
|
585
|
-
mode: spec.autoPanel ? "panel" : "single",
|
|
586
|
-
configured: false,
|
|
587
|
-
routes: [{ kind: "inherit" }],
|
|
588
|
-
total: 1,
|
|
589
|
-
issues: []
|
|
590
|
-
};
|
|
591
|
-
if (spec.autoPanel) return {
|
|
592
|
-
role: spec,
|
|
593
|
-
mode: "panel",
|
|
594
|
-
configured: true,
|
|
595
|
-
routes: entry.routes,
|
|
596
|
-
total: entry.routes.length,
|
|
597
|
-
issues: []
|
|
598
|
-
};
|
|
599
|
-
if (spec.shape === "list") {
|
|
600
|
-
const index = roleIndex ?? 0;
|
|
601
|
-
if (index >= entry.routes.length) throw new RouteSelectionError(`role "${spec.key}" has ${entry.routes.length} entr${entry.routes.length === 1 ? "y" : "ies"}, so \`roleIndex\` ${index} is out of range; valid indexes are ${entry.routes.map((_route, position) => position).join(", ")}`);
|
|
602
|
-
return {
|
|
603
|
-
role: spec,
|
|
604
|
-
mode: "selected",
|
|
605
|
-
configured: true,
|
|
606
|
-
routes: [entry.routes[index]],
|
|
607
|
-
total: entry.routes.length,
|
|
608
|
-
roleIndex: index,
|
|
609
|
-
issues: []
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
return {
|
|
613
|
-
role: spec,
|
|
614
|
-
mode: "single",
|
|
615
|
-
configured: true,
|
|
616
|
-
routes: [entry.routes[0]],
|
|
617
|
-
total: entry.routes.length,
|
|
618
|
-
issues: []
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
/** Keys of the list-valued roles, including the automatic panels. */
|
|
622
|
-
function listRoleKeys() {
|
|
623
|
-
return ROLE_MANIFEST.filter((role) => role.shape === "list").map((role) => role.key);
|
|
624
|
-
}
|
|
625
|
-
function findSpec(key) {
|
|
626
|
-
return ROLE_MANIFEST.find((role) => role.key === key);
|
|
627
|
-
}
|
|
628
|
-
function unknownRoleMessage(rawKey, key) {
|
|
629
|
-
const suggestions = suggestRoles(key);
|
|
630
|
-
const hint = suggestions.length === 0 ? "no role key is close to it" : `closest role keys: ${suggestions.map((candidate) => `"${candidate}"`).join(", ")}`;
|
|
631
|
-
return `unknown role ${JSON.stringify(rawKey)} (${hint}). A line for a retired or misspelled role would otherwise be ignored, so it is reported instead.`;
|
|
632
|
-
}
|
|
633
|
-
function stripFrontmatter(text) {
|
|
634
|
-
const normalized = text.replaceAll("\r\n", "\n");
|
|
635
|
-
if (!normalized.startsWith("---\n")) return normalized;
|
|
636
|
-
const end = normalized.indexOf("\n---", 3);
|
|
637
|
-
if (end === -1) return normalized;
|
|
638
|
-
const after = normalized.indexOf("\n", end + 1);
|
|
639
|
-
return after === -1 ? "" : normalized.slice(after + 1);
|
|
640
|
-
}
|
|
641
|
-
function isMissingFile(error) {
|
|
642
|
-
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
643
|
-
}
|
|
644
113
|
function describeError$1(error) {
|
|
645
114
|
if (error instanceof Error) {
|
|
646
115
|
const code = error.code;
|
|
647
|
-
return typeof code === "string" ? `${
|
|
116
|
+
return typeof code === "string" ? `${error.message} (${code})` : error.message;
|
|
648
117
|
}
|
|
649
118
|
return String(error);
|
|
650
119
|
}
|
|
651
|
-
|
|
652
|
-
//#region src/subagent.ts
|
|
120
|
+
// src/subagent.ts
|
|
653
121
|
/**
|
|
654
122
|
* `pstack_subagent`: pstack's delegation entry point.
|
|
655
123
|
*
|
|
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.
|
|
124
|
+
* One call plans one or more fresh children, validates every route and effort before the
|
|
125
|
+
* first child starts, then starts them through `ctx.subagents.startActivation` with
|
|
126
|
+
* parent delivery. Results arrive as completion notices; this module never polls.
|
|
663
127
|
*/
|
|
664
128
|
/** Cordis row name, used by Loader diagnostics. */
|
|
665
129
|
const name = "pstack-subagent";
|
|
666
130
|
/**
|
|
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.
|
|
131
|
+
* Delegation needs the tool registry, the subagent registry, the LLM adapters, and the
|
|
132
|
+
* session directory. `agents` is not here: it is optional and read per call, so a
|
|
133
|
+
* profile without it still delegates and the child simply keeps its starting effort.
|
|
676
134
|
*/
|
|
677
135
|
const inject = [
|
|
678
136
|
"tools",
|
|
679
137
|
"subagents",
|
|
680
|
-
"llm"
|
|
138
|
+
"llm",
|
|
139
|
+
"workingDirectory"
|
|
681
140
|
];
|
|
682
141
|
/** Model-facing tool name. */
|
|
683
142
|
const TOOL_NAME = "pstack_subagent";
|
|
684
143
|
/** Backend used when the row does not name one. */
|
|
685
144
|
const DEFAULT_SUBAGENT_PROVIDER = "spawn";
|
|
686
|
-
/** Agent type used when the call does not name one. */
|
|
687
|
-
const DEFAULT_AGENT_KIND = "poteto-agent";
|
|
688
145
|
/** The child kinds this tool knows how to brief. */
|
|
689
146
|
const AGENT_KINDS = [
|
|
690
147
|
"poteto-agent",
|
|
691
148
|
"comment-sicko",
|
|
692
149
|
"general"
|
|
693
150
|
];
|
|
694
|
-
const DEFAULT_CONFIG = {
|
|
695
|
-
provider: DEFAULT_SUBAGENT_PROVIDER,
|
|
696
|
-
allowedModels: [],
|
|
697
|
-
allowedAgentPresets: []
|
|
698
|
-
};
|
|
151
|
+
const DEFAULT_CONFIG = { provider: DEFAULT_SUBAGENT_PROVIDER };
|
|
699
152
|
/** Raised for a configuration or argument problem, before any child starts. */
|
|
700
153
|
var DelegationError = class extends Error {
|
|
701
154
|
constructor(message) {
|
|
@@ -712,13 +165,6 @@ var PartialDelegationError = class extends DelegationError {
|
|
|
712
165
|
this.startedChildren = startedChildren;
|
|
713
166
|
}
|
|
714
167
|
};
|
|
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
168
|
function isNonEmptyString(value) {
|
|
723
169
|
return typeof value === "string" && value.trim() !== "";
|
|
724
170
|
}
|
|
@@ -726,139 +172,38 @@ function isNonNegativeSafeInteger(value) {
|
|
|
726
172
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
727
173
|
}
|
|
728
174
|
/**
|
|
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.
|
|
175
|
+
* Validate the row configuration without a Schemastery dependency.
|
|
834
176
|
*
|
|
835
|
-
* A
|
|
836
|
-
*
|
|
837
|
-
*
|
|
177
|
+
* A typo must fail at activation rather than at the first delegation, and an omitted
|
|
178
|
+
* `config` must be valid. Cordis passes the raw mapping because this row exports no
|
|
179
|
+
* `Config` object; callers that object as `config` are still checked here.
|
|
838
180
|
*/
|
|
839
|
-
function
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
181
|
+
function normalizeConfig(value) {
|
|
182
|
+
if (value === void 0 || value === null) return DEFAULT_CONFIG;
|
|
183
|
+
if (typeof value !== "object" || Array.isArray(value)) throw new Error("pstack-subagent: config must be a mapping");
|
|
184
|
+
const record = value;
|
|
185
|
+
for (const key of Object.keys(record)) if (key !== "provider" && key !== "maxDepth") throw new Error(`pstack-subagent: unknown config field "${key}"`);
|
|
186
|
+
const provider = record["provider"];
|
|
187
|
+
if (provider !== void 0 && !isNonEmptyString(provider)) throw new Error("pstack-subagent: provider must be a non-empty string");
|
|
188
|
+
const maxDepth = record["maxDepth"];
|
|
189
|
+
if (maxDepth !== void 0 && !isNonNegativeSafeInteger(maxDepth)) throw new Error("pstack-subagent: maxDepth must be a non-negative safe integer");
|
|
190
|
+
return {
|
|
191
|
+
provider: isNonEmptyString(provider) ? provider : DEFAULT_SUBAGENT_PROVIDER,
|
|
192
|
+
...isNonNegativeSafeInteger(maxDepth) ? { maxDepth } : {}
|
|
193
|
+
};
|
|
852
194
|
}
|
|
853
|
-
/**
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
*/
|
|
195
|
+
/** The preset id behind each pstack agent kind. `general` joins the parent preset. */
|
|
196
|
+
const AGENT_PRESETS = {
|
|
197
|
+
"poteto-agent": "poteto-agent",
|
|
198
|
+
"comment-sicko": "comment-sicko"
|
|
199
|
+
};
|
|
200
|
+
/** The preset a child of this kind must mount, or undefined to join the parent preset. */
|
|
201
|
+
function agentPresetFor(agent) {
|
|
202
|
+
return agent === "general" ? void 0 : AGENT_PRESETS[agent];
|
|
203
|
+
}
|
|
204
|
+
/** Compose the self-contained prompt one child receives. */
|
|
859
205
|
function composeChildPrompt(input) {
|
|
860
206
|
const sections = [];
|
|
861
|
-
if (input.preamble !== void 0) sections.push(input.preamble);
|
|
862
207
|
const injected = input.injectedPaths ?? [];
|
|
863
208
|
if (injected.length > 0) sections.push([
|
|
864
209
|
"Absolute paths injected by `pstack_subagent`. Read them directly; do not search a plugin directory.",
|
|
@@ -869,141 +214,102 @@ function composeChildPrompt(input) {
|
|
|
869
214
|
sections.push(`Task:\n\n${input.task}`);
|
|
870
215
|
return sections.join("\n\n---\n\n");
|
|
871
216
|
}
|
|
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) {
|
|
217
|
+
/** Validate every required model-facing argument. Null declines an optional setting. */
|
|
218
|
+
function parseDelegationArgs(raw) {
|
|
912
219
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DelegationError("arguments must be an object");
|
|
913
220
|
const record = raw;
|
|
914
|
-
const known =
|
|
221
|
+
const known = [
|
|
915
222
|
"description",
|
|
916
223
|
"prompt",
|
|
917
224
|
"agent",
|
|
918
225
|
"role",
|
|
919
|
-
"cwd",
|
|
920
|
-
...config.allowedAgentPresets.length > 0 ? ["agentPreset"] : [],
|
|
921
|
-
"maxDepth",
|
|
922
226
|
"roleIndex",
|
|
923
|
-
"reasoningEffort"
|
|
924
|
-
|
|
925
|
-
|
|
227
|
+
"reasoningEffort",
|
|
228
|
+
"cwd",
|
|
229
|
+
"maxDepth"
|
|
230
|
+
];
|
|
231
|
+
for (const key of known) if (!Object.hasOwn(record, key)) throw new DelegationError(`missing required argument "${key}"; every argument must be passed explicitly`);
|
|
232
|
+
for (const key of Object.keys(record)) if (!known.includes(key)) throw new DelegationError(`unknown argument "${key}"; accepted arguments are ${known.join(", ")}`);
|
|
926
233
|
const description = record["description"];
|
|
927
234
|
if (!isNonEmptyString(description)) throw new DelegationError("description must be a non-empty string: it becomes the child label");
|
|
928
235
|
const prompt = record["prompt"];
|
|
929
236
|
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.`);
|
|
237
|
+
const agent = record["agent"];
|
|
238
|
+
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
239
|
const role = record["role"];
|
|
935
|
-
if (role !==
|
|
240
|
+
if (role !== null && !isNonEmptyString(role)) throw new DelegationError("role must be a non-empty string or null");
|
|
936
241
|
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
|
-
}
|
|
242
|
+
if (cwd !== null && !isNonEmptyString(cwd)) throw new DelegationError("cwd must be a non-empty string or null");
|
|
943
243
|
const maxDepth = record["maxDepth"];
|
|
944
|
-
if (
|
|
244
|
+
if (!isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer");
|
|
945
245
|
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
|
-
}
|
|
246
|
+
if (roleIndex !== null && !isNonNegativeSafeInteger(roleIndex)) throw new DelegationError("roleIndex must be a non-negative safe integer or null");
|
|
247
|
+
if (roleIndex !== null && role === null) throw new DelegationError("roleIndex requires a non-null role");
|
|
950
248
|
const requestedEffort = record["reasoningEffort"];
|
|
951
|
-
if (requestedEffort !==
|
|
249
|
+
if (requestedEffort !== null && !isNonEmptyString(requestedEffort)) throw new DelegationError("reasoningEffort must be a non-empty string or null");
|
|
952
250
|
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
251
|
return {
|
|
954
252
|
description,
|
|
955
253
|
prompt,
|
|
956
254
|
agent,
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
...requestedEffort === void 0 ? {} : { reasoningEffort: requestedEffort.trim() }
|
|
255
|
+
role,
|
|
256
|
+
cwd,
|
|
257
|
+
maxDepth,
|
|
258
|
+
roleIndex,
|
|
259
|
+
reasoningEffort: requestedEffort?.trim() ?? null
|
|
963
260
|
};
|
|
964
261
|
}
|
|
965
|
-
/**
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
262
|
+
/** Route text as reported to the model. */
|
|
263
|
+
function routeLabel(route, effortOverride) {
|
|
264
|
+
const effort = effectiveEffort(route, effortOverride);
|
|
265
|
+
const base = route.kind === "inherit" ? "inherit-parent" : `${route.provider}/${route.model}`;
|
|
266
|
+
return effort === void 0 ? base : `${base} [${effort}]`;
|
|
267
|
+
}
|
|
268
|
+
/** Take the smallest of the requested depth, this row's cap, and the Host setting. */
|
|
269
|
+
function resolveDelegationDepth(configured, requested, hostLimit) {
|
|
270
|
+
if (requested !== void 0 && !isNonNegativeSafeInteger(requested)) throw new DelegationError("maxDepth must be a non-negative safe integer");
|
|
271
|
+
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.`);
|
|
272
|
+
const candidates = [
|
|
273
|
+
configured,
|
|
274
|
+
requested,
|
|
275
|
+
hostLimit
|
|
276
|
+
].filter((value) => value !== void 0);
|
|
277
|
+
return candidates.length === 0 ? void 0 : Math.min(...candidates);
|
|
278
|
+
}
|
|
279
|
+
/** Resolve the role file, routes, prompt, and depth into one plan. Pure of service calls. */
|
|
971
280
|
function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
|
|
972
|
-
const preamble = loadAgentPreamble(args.agent, runtime.assetsRoot);
|
|
973
281
|
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}).
|
|
282
|
+
const roleFile = readRoleConfiguration(rolePath);
|
|
283
|
+
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
284
|
const diagnostics = [];
|
|
977
285
|
if (roleFile.status === "loaded") for (const issue of roleFile.issues) diagnostics.push(`${rolePath}${issue.line === void 0 ? "" : `:${issue.line}`}: ${issue.message}`);
|
|
978
286
|
let selection;
|
|
979
287
|
let roleSpec;
|
|
980
|
-
if (args.role !==
|
|
288
|
+
if (args.role !== null) {
|
|
981
289
|
roleSpec = requireRole(args.role);
|
|
982
290
|
const entry = roleFile.status === "loaded" ? roleFile.roles.get(roleSpec.key) : void 0;
|
|
983
|
-
selection = selectRoleRoutes(roleSpec, entry, args.roleIndex);
|
|
291
|
+
selection = selectRoleRoutes(roleSpec, entry, args.roleIndex ?? void 0);
|
|
984
292
|
}
|
|
985
293
|
const routes = selection?.routes ?? [{ kind: "inherit" }];
|
|
986
294
|
const roleName = roleSpec?.key ?? "(none)";
|
|
987
295
|
if (selection !== void 0) {
|
|
988
296
|
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
297
|
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
|
|
298
|
+
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
299
|
}
|
|
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
300
|
const prompt = composeChildPrompt({
|
|
998
|
-
|
|
999
|
-
|
|
301
|
+
injectedPaths: args.agent === "poteto-agent" ? [{
|
|
302
|
+
label: "`poteto-mode` SKILL.md",
|
|
303
|
+
path: POTETO_MODE_SKILL_PATH
|
|
304
|
+
}] : [],
|
|
1000
305
|
task: args.prompt
|
|
1001
306
|
});
|
|
307
|
+
const agentPreset = agentPresetFor(args.agent);
|
|
1002
308
|
const depth = resolveDelegationDepth(config.maxDepth, args.maxDepth, void 0);
|
|
1003
309
|
const total = routes.length;
|
|
1004
310
|
const children = routes.map((route, index) => {
|
|
1005
|
-
const agentOptions = agentOptionsFor(route, args.reasoningEffort);
|
|
1006
|
-
const effort = effectiveEffort(route, args.reasoningEffort);
|
|
311
|
+
const agentOptions = agentOptionsFor(route, args.reasoningEffort ?? void 0);
|
|
312
|
+
const effort = effectiveEffort(route, args.reasoningEffort ?? void 0);
|
|
1007
313
|
return {
|
|
1008
314
|
index,
|
|
1009
315
|
total,
|
|
@@ -1011,17 +317,17 @@ function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
|
|
|
1011
317
|
agent: args.agent,
|
|
1012
318
|
role: roleName,
|
|
1013
319
|
route,
|
|
1014
|
-
routeLabel:
|
|
320
|
+
routeLabel: routeLabel(route, args.reasoningEffort ?? void 0),
|
|
1015
321
|
...effort === void 0 ? {} : { effort },
|
|
1016
322
|
...agentOptions === void 0 ? {} : { agentOptions },
|
|
1017
|
-
...
|
|
1018
|
-
...args.cwd ===
|
|
323
|
+
...agentPreset === void 0 ? {} : { agentPreset },
|
|
324
|
+
...args.cwd === null ? {} : { cwd: args.cwd },
|
|
1019
325
|
...depth === void 0 ? {} : { maxDepth: depth }
|
|
1020
326
|
};
|
|
1021
327
|
});
|
|
1022
|
-
if (args.reasoningEffort !==
|
|
328
|
+
if (args.reasoningEffort !== null) {
|
|
1023
329
|
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
|
|
330
|
+
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
331
|
}
|
|
1026
332
|
addPanelDiagnostics(children, roleName, selection, rolePath, diagnostics);
|
|
1027
333
|
return {
|
|
@@ -1032,18 +338,11 @@ function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
|
|
|
1032
338
|
mode: selection?.mode ?? "single",
|
|
1033
339
|
...selection?.roleIndex === void 0 ? {} : { roleIndex: selection.roleIndex },
|
|
1034
340
|
roleTotal: selection?.total ?? 1,
|
|
1035
|
-
...args.reasoningEffort ===
|
|
341
|
+
...args.reasoningEffort === null ? {} : { reasoningEffort: args.reasoningEffort },
|
|
1036
342
|
children,
|
|
1037
343
|
diagnostics
|
|
1038
344
|
};
|
|
1039
345
|
}
|
|
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
346
|
function addPanelDiagnostics(children, roleName, selection, rolePath, diagnostics) {
|
|
1048
347
|
if (selection === void 0 || selection.mode !== "panel") return;
|
|
1049
348
|
if (children.length === 1) {
|
|
@@ -1052,20 +351,21 @@ function addPanelDiagnostics(children, roleName, selection, rolePath, diagnostic
|
|
|
1052
351
|
}
|
|
1053
352
|
const distinct = new Set(children.map((child) => child.routeLabel));
|
|
1054
353
|
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
|
|
354
|
+
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.`);
|
|
355
|
+
}
|
|
356
|
+
/** Check the backend can enforce what this call asks for. */
|
|
357
|
+
function assertProviderCapabilities(capabilities, providerName, asks) {
|
|
358
|
+
const missing = [];
|
|
359
|
+
if (asks.agentOptions && !capabilities.agentOptions) missing.push("agentOptions");
|
|
360
|
+
if (asks.agentPreset && !capabilities.agentPreset) missing.push("agentPreset");
|
|
361
|
+
if (asks.maxDepth && !capabilities.depthLimit) missing.push("maxDepth");
|
|
362
|
+
if (missing.length === 0) return;
|
|
363
|
+
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
364
|
}
|
|
1057
365
|
/**
|
|
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.
|
|
366
|
+
* Validate every route and effort before anything starts. An inherit-parent entry with
|
|
367
|
+
* no effort carries no agentOptions and is skipped; an override on an inherit route is
|
|
368
|
+
* validated against the parent's live route.
|
|
1069
369
|
*/
|
|
1070
370
|
async function preflightRoutes(llm, parent, children, roleName, effortOverride, signal) {
|
|
1071
371
|
for (const child of children) {
|
|
@@ -1075,54 +375,58 @@ async function preflightRoutes(llm, parent, children, roleName, effortOverride,
|
|
|
1075
375
|
try {
|
|
1076
376
|
await validateEffortFor(llm, void 0, effort, parent, signal);
|
|
1077
377
|
} catch (error) {
|
|
1078
|
-
throw new DelegationError(`role ${roleName} inherits the parent route, and reasoningEffort "${effort}" failed preflight there: ${describeError(error)}
|
|
378
|
+
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
379
|
}
|
|
1080
380
|
continue;
|
|
1081
381
|
}
|
|
1082
382
|
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
383
|
try {
|
|
1089
|
-
await llm.resolveCallConfig(
|
|
384
|
+
await llm.resolveCallConfig({
|
|
385
|
+
provider: route.provider,
|
|
386
|
+
model: route.model,
|
|
387
|
+
...child.effort === void 0 ? {} : { reasoningEffort: child.effort }
|
|
388
|
+
}, signal);
|
|
1090
389
|
} catch (error) {
|
|
1091
|
-
throw new DelegationError(`role ${roleName} resolves to ${child.routeLabel}, which failed route preflight: ${describeError(error)}.
|
|
390
|
+
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
391
|
}
|
|
1093
392
|
}
|
|
1094
393
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Resolve and validate one child's `cwd`.
|
|
396
|
+
*
|
|
397
|
+
* A child may run inside the session directory or inside an explicitly separate Git
|
|
398
|
+
* worktree. Anything else is refused: this tool does not create isolation, so it will
|
|
399
|
+
* not promise one.
|
|
400
|
+
*/
|
|
401
|
+
function resolveChildCwd(requested, sessionRoot) {
|
|
402
|
+
if (requested === void 0) return void 0;
|
|
403
|
+
const resolved = isAbsolute(requested) ? resolve(requested) : resolve(sessionRoot, requested);
|
|
404
|
+
let isDirectory = false;
|
|
405
|
+
try {
|
|
406
|
+
isDirectory = statSync(resolved).isDirectory();
|
|
1100
407
|
} catch {
|
|
1101
|
-
|
|
408
|
+
isDirectory = false;
|
|
1102
409
|
}
|
|
1103
|
-
|
|
410
|
+
if (!isDirectory) throw new DelegationError(`cwd ${resolved} does not exist or is not a directory`);
|
|
411
|
+
if (isWithin(sessionRoot, resolved) || isGitWorktree(resolved)) return resolved;
|
|
412
|
+
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
413
|
}
|
|
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.`);
|
|
414
|
+
function isWithin(root, target) {
|
|
415
|
+
const rel = relative(root, target);
|
|
416
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
417
|
+
}
|
|
418
|
+
function isGitWorktree(directory) {
|
|
419
|
+
return existsSync(join(directory, ".git"));
|
|
1113
420
|
}
|
|
1114
421
|
/**
|
|
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.
|
|
422
|
+
* Start the planned children. Everything rejectable has already been rejected; a
|
|
423
|
+
* failure at entry N keeps children 1..N-1 running and reports their ids.
|
|
1120
424
|
*/
|
|
1121
425
|
async function startPlannedChildren(parent, plan, runtime, config, signal) {
|
|
1122
426
|
const provider = runtime.subagents.getProvider(plan.provider);
|
|
1123
427
|
if (provider === void 0) {
|
|
1124
428
|
const available = runtime.subagents.list();
|
|
1125
|
-
throw new DelegationError(`subagent provider "${plan.provider}" is not registered; registered providers: ${available.length === 0 ? "(none)" : available.join(", ")}
|
|
429
|
+
throw new DelegationError(`subagent provider "${plan.provider}" is not registered; registered providers: ${available.length === 0 ? "(none)" : available.join(", ")}.`);
|
|
1126
430
|
}
|
|
1127
431
|
const hostLimit = runtime.subagents.resolveMaxDepth(config.maxDepth);
|
|
1128
432
|
const effectiveDepth = resolveDelegationDepth(config.maxDepth, plan.children[0]?.maxDepth, hostLimit);
|
|
@@ -1131,9 +435,11 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
|
|
|
1131
435
|
agentPreset: plan.children.some((child) => child.agentPreset !== void 0),
|
|
1132
436
|
maxDepth: effectiveDepth !== void 0
|
|
1133
437
|
});
|
|
438
|
+
const sessionRoot = await runtime.workingDirectory.ensure(parent, signal);
|
|
1134
439
|
const children = plan.children.map((child) => ({
|
|
1135
440
|
...child,
|
|
1136
|
-
...effectiveDepth === void 0 ? {} : { maxDepth: effectiveDepth }
|
|
441
|
+
...effectiveDepth === void 0 ? {} : { maxDepth: effectiveDepth },
|
|
442
|
+
cwd: resolveChildCwd(child.cwd, sessionRoot)
|
|
1137
443
|
}));
|
|
1138
444
|
await preflightRoutes(runtime.llm, parent, children, plan.role, plan.reasoningEffort, signal);
|
|
1139
445
|
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 +457,8 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
|
|
|
1151
457
|
text: plan.prompt
|
|
1152
458
|
}],
|
|
1153
459
|
parent,
|
|
1154
|
-
...child.cwd === void 0 ? {} : { cwd: child.cwd },
|
|
1155
460
|
...child.agentPreset === void 0 ? {} : { agentPreset: child.agentPreset },
|
|
461
|
+
...child.cwd === void 0 ? {} : { cwd: child.cwd },
|
|
1156
462
|
...child.agentOptions === void 0 ? {} : { agentOptions: child.agentOptions },
|
|
1157
463
|
...child.maxDepth === void 0 ? {} : { maxDepth: child.maxDepth }
|
|
1158
464
|
},
|
|
@@ -1161,7 +467,7 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
|
|
|
1161
467
|
});
|
|
1162
468
|
const target = runtime.agents === void 0 ? void 0 : runtime.agents.get(activation.childId);
|
|
1163
469
|
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
|
|
470
|
+
else installEffortControl(target, runtime.llm);
|
|
1165
471
|
started.push({
|
|
1166
472
|
childId: activation.childId,
|
|
1167
473
|
label: child.label,
|
|
@@ -1173,7 +479,7 @@ async function startPlannedChildren(parent, plan, runtime, config, signal) {
|
|
|
1173
479
|
});
|
|
1174
480
|
} catch (error) {
|
|
1175
481
|
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
|
|
482
|
+
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
483
|
}
|
|
1178
484
|
}
|
|
1179
485
|
return {
|
|
@@ -1189,82 +495,79 @@ async function delegate(parent, args, runtime, config = DEFAULT_CONFIG, signal =
|
|
|
1189
495
|
const TOOL_DESCRIPTION = [
|
|
1190
496
|
"Delegate self-contained work to one or more fresh child agents.",
|
|
1191
497
|
"",
|
|
498
|
+
"Every argument is required. Pass null for role, roleIndex, reasoningEffort, or cwd when that",
|
|
499
|
+
"setting is not used. Choose agent explicitly. maxDepth is a non-negative integer. No argument",
|
|
500
|
+
"has an implicit default.",
|
|
501
|
+
"",
|
|
1192
502
|
"Returns the id of every child it started plus the route each one resolved. The children run",
|
|
1193
503
|
"in the background: their results arrive later as completion notices in this conversation.",
|
|
1194
504
|
"Never wait or poll for them, and keep working on something useful meanwhile.",
|
|
1195
505
|
"",
|
|
1196
|
-
"`agent` picks
|
|
1197
|
-
"the absolute path of the poteto-mode instructions; `comment-sicko`
|
|
1198
|
-
"
|
|
506
|
+
"`agent` picks a DSH agent preset: `poteto-agent` mounts the pstack implementation delegate",
|
|
507
|
+
"and receives the absolute path of the poteto-mode instructions; `comment-sicko` mounts the",
|
|
508
|
+
"read-only comment reviewer; `general` joins the parent preset with no pstack persona.",
|
|
1199
509
|
"",
|
|
1200
510
|
"`role` selects a configured pstack role and therefore the child's model route and reasoning",
|
|
1201
|
-
"effort. The role names
|
|
1202
|
-
"
|
|
1203
|
-
"",
|
|
1204
|
-
"`reasoningEffort` raises or lowers the effort for this delegation alone. The role line is a",
|
|
1205
|
-
"starting baseline, not a verdict: start from it, raise it for hard work and lower it for",
|
|
1206
|
-
"routine work. The value must be one the target model advertises, and this package does not",
|
|
1207
|
-
"assume a ladder, so read the effort list the role's model reports rather than guessing. An",
|
|
1208
|
-
"unsupported value fails the call before any child starts, including every entry of a panel.",
|
|
511
|
+
"effort. The role names come from the pstack role manifest, not this schema. Null inherits the",
|
|
512
|
+
"parent route. Automatic panel roles start one child per configured entry.",
|
|
1209
513
|
"",
|
|
1210
|
-
"
|
|
1211
|
-
"
|
|
514
|
+
"`reasoningEffort` raises or lowers the effort for this delegation alone, using a token the",
|
|
515
|
+
"target model advertises. An unsupported value fails the call before any child starts,",
|
|
516
|
+
"including every entry of a panel. Null keeps the role line's starting effort. A running child",
|
|
517
|
+
"can change its own effort with `set_reasoning_effort`, which applies from its next step.",
|
|
1212
518
|
"",
|
|
1213
519
|
"Every prompt is a fresh child that does not see this conversation, so include everything it",
|
|
1214
520
|
"needs. A child's own summary is not evidence: review its diff or artifacts yourself."
|
|
1215
521
|
].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
|
-
};
|
|
522
|
+
function buildParameters() {
|
|
1263
523
|
return {
|
|
1264
524
|
type: "object",
|
|
1265
525
|
additionalProperties: false,
|
|
1266
|
-
properties
|
|
1267
|
-
|
|
526
|
+
properties: {
|
|
527
|
+
description: {
|
|
528
|
+
type: "string",
|
|
529
|
+
description: "A short 3-5 word label for the child, shown in list_agents and its notices."
|
|
530
|
+
},
|
|
531
|
+
prompt: {
|
|
532
|
+
type: "string",
|
|
533
|
+
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."
|
|
534
|
+
},
|
|
535
|
+
agent: {
|
|
536
|
+
type: "string",
|
|
537
|
+
enum: [...AGENT_KINDS],
|
|
538
|
+
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."
|
|
539
|
+
},
|
|
540
|
+
role: {
|
|
541
|
+
type: ["string", "null"],
|
|
542
|
+
description: "Required pstack role key, matched exactly. It selects the child's model route and effort. Null inherits the parent route."
|
|
543
|
+
},
|
|
544
|
+
cwd: {
|
|
545
|
+
type: ["string", "null"],
|
|
546
|
+
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."
|
|
547
|
+
},
|
|
548
|
+
maxDepth: {
|
|
549
|
+
type: "integer",
|
|
550
|
+
description: "Required non-negative delegation-depth limit. It can only lower this row's configured cap."
|
|
551
|
+
},
|
|
552
|
+
roleIndex: {
|
|
553
|
+
type: ["integer", "null"],
|
|
554
|
+
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."
|
|
555
|
+
},
|
|
556
|
+
reasoningEffort: {
|
|
557
|
+
type: ["string", "null"],
|
|
558
|
+
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."
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
required: [
|
|
562
|
+
"description",
|
|
563
|
+
"prompt",
|
|
564
|
+
"agent",
|
|
565
|
+
"role",
|
|
566
|
+
"roleIndex",
|
|
567
|
+
"reasoningEffort",
|
|
568
|
+
"cwd",
|
|
569
|
+
"maxDepth"
|
|
570
|
+
]
|
|
1268
571
|
};
|
|
1269
572
|
}
|
|
1270
573
|
const OUTPUT_SCHEMA = {
|
|
@@ -1305,7 +608,7 @@ const OUTPUT_SCHEMA = {
|
|
|
1305
608
|
},
|
|
1306
609
|
effort: {
|
|
1307
610
|
type: "string",
|
|
1308
|
-
description: "The reasoning effort this child runs at, when one applies.
|
|
611
|
+
description: "The reasoning effort this child runs at, when one applies."
|
|
1309
612
|
},
|
|
1310
613
|
maxDepth: {
|
|
1311
614
|
type: "integer",
|
|
@@ -1323,7 +626,7 @@ const OUTPUT_SCHEMA = {
|
|
|
1323
626
|
},
|
|
1324
627
|
diagnostics: {
|
|
1325
628
|
type: "array",
|
|
1326
|
-
description: "Configuration notes, including a panel whose entries share one route.
|
|
629
|
+
description: "Configuration notes, including a panel whose entries share one route.",
|
|
1327
630
|
items: { type: "string" }
|
|
1328
631
|
}
|
|
1329
632
|
},
|
|
@@ -1339,7 +642,7 @@ function renderDelegationResult(result) {
|
|
|
1339
642
|
result.kind === "activation" ? `started 1 child: ${result.children[0]?.childId ?? "(none)"}` : `started ${result.children.length} children:`,
|
|
1340
643
|
...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
644
|
"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
|
|
645
|
+
"effort from its next step with `set_reasoning_effort`."
|
|
1343
646
|
];
|
|
1344
647
|
if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.map((diagnostic) => `- ${diagnostic}`));
|
|
1345
648
|
return lines.join("\n");
|
|
@@ -1349,7 +652,7 @@ function createPstackSubagentTool(runtime, config = DEFAULT_CONFIG) {
|
|
|
1349
652
|
return {
|
|
1350
653
|
name: TOOL_NAME,
|
|
1351
654
|
description: TOOL_DESCRIPTION,
|
|
1352
|
-
parameters: buildParameters(
|
|
655
|
+
parameters: buildParameters(),
|
|
1353
656
|
output: {
|
|
1354
657
|
schema: OUTPUT_SCHEMA,
|
|
1355
658
|
render: (_args, value) => [{
|
|
@@ -1360,8 +663,8 @@ function createPstackSubagentTool(runtime, config = DEFAULT_CONFIG) {
|
|
|
1360
663
|
isConcurrencySafe: () => true,
|
|
1361
664
|
async execute(rawArgs, exec) {
|
|
1362
665
|
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
|
|
666
|
+
if (parent === void 0) throw new DelegationError("pstack_subagent needs a calling Agent, and exec.agent was undefined.");
|
|
667
|
+
return await delegate(parent, parseDelegationArgs(rawArgs), runtime, config, exec.signal);
|
|
1365
668
|
}
|
|
1366
669
|
};
|
|
1367
670
|
}
|
|
@@ -1370,23 +673,18 @@ function hostRuntime(ctx, getAgentRegistry = () => void 0) {
|
|
|
1370
673
|
return {
|
|
1371
674
|
subagents: ctx.subagents,
|
|
1372
675
|
llm: ctx.llm,
|
|
676
|
+
workingDirectory: ctx.workingDirectory,
|
|
1373
677
|
get agents() {
|
|
1374
678
|
return getAgentRegistry();
|
|
1375
679
|
},
|
|
1376
|
-
dshHome: resolveDshHome()
|
|
1377
|
-
assetsRoot: ASSETS_ROOT
|
|
680
|
+
dshHome: resolveDshHome()
|
|
1378
681
|
};
|
|
1379
682
|
}
|
|
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);
|
|
683
|
+
/** Register `pstack_subagent` for this row. */
|
|
684
|
+
function apply(ctx, config) {
|
|
685
|
+
const resolved = normalizeConfig(config);
|
|
686
|
+
const runtime = hostRuntime(ctx, () => ctx.get?.("agents"));
|
|
687
|
+
ctx.tools.register(createPstackSubagentTool(runtime, resolved));
|
|
1390
688
|
}
|
|
1391
689
|
function describeError(error) {
|
|
1392
690
|
if (error instanceof Error) {
|
|
@@ -1396,7 +694,4 @@ function describeError(error) {
|
|
|
1396
694
|
}
|
|
1397
695
|
return String(error);
|
|
1398
696
|
}
|
|
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
|
|
697
|
+
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 };
|