dsh-pstack 0.1.0 → 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +92 -117
- package/assets/agents/comment-sicko.md +1 -1
- package/assets/agents/poteto-agent.md +2 -2
- package/assets/docs/guide/01-setup.md +9 -9
- package/assets/docs/guide/02-poteto-mode.md +4 -4
- package/assets/docs/guide/05-build-and-clean.md +3 -3
- package/assets/docs/guide/06-verify-and-ship.md +2 -2
- package/assets/docs/guide/07-overnight.md +10 -12
- package/assets/docs/guide/09-make-it-yours.md +2 -6
- package/assets/docs/guide/10-recipes-and-pitfalls.md +3 -3
- package/assets/docs/guide/README.md +1 -1
- package/assets/docs/guide/images/design.jpg +0 -0
- package/assets/docs/guide/images/overnight.jpg +0 -0
- package/assets/docs/guide/images/recipes.jpg +0 -0
- package/assets/docs/guide/images/router.jpg +0 -0
- package/assets/docs/guide/images/understanding.jpg +0 -0
- package/assets/docs/guide/images/verification.jpg +0 -0
- package/assets/docs/harness/dsh.md +60 -65
- package/assets/docs/pstack-models.example.md +40 -9
- package/assets/skills/architect/SKILL.md +12 -12
- package/assets/skills/arena/SKILL.md +11 -11
- package/assets/skills/automate-me/SKILL.md +15 -14
- package/assets/skills/benchmark-checklist/SKILL.md +0 -2
- package/assets/skills/blast-radius/SKILL.md +5 -5
- package/assets/skills/bro/SKILL.md +0 -2
- package/assets/skills/correct/SKILL.md +0 -2
- package/assets/skills/create-formal-verification-skill/SKILL.md +2 -2
- package/assets/skills/create-verification-skill/SKILL.md +2 -2
- package/assets/skills/figure-it-out/SKILL.md +13 -13
- package/assets/skills/how/SKILL.md +6 -6
- package/assets/skills/how/references/explainer-prompt.md +1 -1
- package/assets/skills/in-your-own-words/SKILL.md +0 -2
- package/assets/skills/interrogate/SKILL.md +9 -7
- package/assets/skills/interrogate/references/rubric.md +1 -1
- package/assets/skills/ketchup/SKILL.md +4 -4
- package/assets/skills/maintain-formal-verification-skill/SKILL.md +2 -2
- package/assets/skills/maintain-verification-skill/SKILL.md +3 -3
- package/assets/skills/no-comments/SKILL.md +7 -7
- package/assets/skills/poteto-help/SKILL.md +32 -31
- package/assets/skills/poteto-help/references/prompting.md +1 -1
- package/assets/skills/poteto-help/references/recipes.md +1 -1
- package/assets/skills/poteto-mode/SKILL.md +17 -19
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +3 -3
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +6 -4
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +6 -6
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +2 -2
- package/assets/skills/poteto-mode/playbooks/babysit.md +3 -3
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +7 -7
- package/assets/skills/poteto-mode/playbooks/eval.md +3 -3
- package/assets/skills/poteto-mode/playbooks/feature.md +8 -8
- package/assets/skills/poteto-mode/playbooks/hillclimb.md +3 -3
- package/assets/skills/poteto-mode/playbooks/investigation.md +2 -2
- package/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +21 -21
- package/assets/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +13 -13
- package/assets/skills/poteto-mode/playbooks/pause-safely.md +2 -2
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +6 -6
- package/assets/skills/poteto-mode/playbooks/prototype.md +4 -4
- package/assets/skills/poteto-mode/playbooks/refactoring.md +9 -9
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +3 -3
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +2 -2
- package/assets/skills/poteto-mode/playbooks/shipping.md +2 -2
- package/assets/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +5 -5
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -6
- package/assets/skills/poteto-mode/scripts/check-plan.mjs +85 -70
- package/assets/skills/poteto-mode/scripts/check-plan.test.mjs +343 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +4282 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +2042 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +5101 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +1177 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +1441 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +4601 -0
- package/assets/skills/poteto-mode/scripts/worktree-audit.mjs +675 -0
- package/assets/skills/principle-attack-the-premise/SKILL.md +0 -2
- package/assets/skills/principle-boundary-discipline/SKILL.md +0 -2
- package/assets/skills/principle-build-the-lever/SKILL.md +0 -3
- package/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -2
- package/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -2
- package/assets/skills/principle-experience-first/SKILL.md +0 -2
- package/assets/skills/principle-explain-the-number/SKILL.md +0 -2
- package/assets/skills/principle-fix-root-causes/SKILL.md +0 -2
- package/assets/skills/principle-foundational-thinking/SKILL.md +0 -2
- package/assets/skills/principle-guard-the-context-window/SKILL.md +0 -2
- package/assets/skills/principle-laziness-protocol/SKILL.md +0 -2
- package/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -2
- package/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -2
- package/assets/skills/principle-minimize-reader-load/SKILL.md +0 -2
- package/assets/skills/principle-model-the-domain/SKILL.md +0 -2
- package/assets/skills/principle-never-block-on-the-human/SKILL.md +0 -2
- package/assets/skills/principle-outcome-oriented-execution/SKILL.md +0 -2
- package/assets/skills/principle-prove-it-works/SKILL.md +2 -2
- package/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -2
- package/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -2
- package/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -2
- package/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -2
- package/assets/skills/principle-test-behavior-not-implementation/SKILL.md +0 -2
- package/assets/skills/principle-type-system-discipline/SKILL.md +0 -2
- package/assets/skills/recall/SKILL.md +9 -9
- package/assets/skills/reflect/SKILL.md +20 -21
- package/assets/skills/reflect/references/divergent-reviewer.md +4 -4
- package/assets/skills/reflect/references/judgment-reviewer.md +2 -2
- package/assets/skills/reflect/references/synthesizer.md +6 -6
- package/assets/skills/reflect/references/tooling-reviewer.md +2 -2
- package/assets/skills/setup-pstack/SKILL.md +28 -37
- package/assets/skills/sharpen/SKILL.md +0 -2
- package/assets/skills/show-me-your-work/SKILL.md +9 -9
- package/assets/skills/show-me-your-work/scripts/log.mjs +108 -0
- package/assets/skills/swarm/SKILL.md +8 -6
- package/assets/skills/tdd/SKILL.md +0 -2
- package/assets/skills/teach/SKILL.md +2 -2
- package/assets/skills/technical-writing/SKILL.md +2 -2
- package/assets/skills/typescript-best-practices/SKILL.md +6 -4
- package/assets/skills/unslop/SKILL.md +0 -2
- package/assets/skills/why/SKILL.md +9 -9
- package/cordis.patch.yml +33 -14
- package/dist/frontmatter-BQX3ekwP.mjs +229 -0
- package/dist/frontmatter-BQX3ekwP.mjs.map +1 -0
- package/dist/history.d.mts +429 -0
- package/dist/history.mjs +1129 -0
- package/dist/history.mjs.map +1 -0
- package/dist/host-contract-o47JH_r8.d.mts +527 -0
- package/dist/index.d.mts +273 -11
- package/dist/index.mjs +248 -148
- package/dist/index.mjs.map +1 -1
- package/dist/paths-D_JlVmBk.mjs +42 -0
- package/dist/paths-D_JlVmBk.mjs.map +1 -0
- package/dist/script.d.mts +193 -0
- package/dist/script.mjs +782 -0
- package/dist/script.mjs.map +1 -0
- package/dist/subagent.d.mts +306 -100
- package/dist/subagent.mjs +1309 -343
- package/dist/subagent.mjs.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +55 -41
- package/static/icon.png +0 -0
- package/assets/skills/poteto-mode/scripts/bootstrap.ts +0 -62
- package/assets/skills/poteto-mode/scripts/bun.lock +0 -67
- package/assets/skills/poteto-mode/scripts/check-plan.test.ts +0 -96
- package/assets/skills/poteto-mode/scripts/orch/orch.test.ts +0 -761
- package/assets/skills/poteto-mode/scripts/orch/orch.ts +0 -590
- package/assets/skills/poteto-mode/scripts/orch/store.ts +0 -1677
- package/assets/skills/poteto-mode/scripts/package.json +0 -16
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.ts +0 -224
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.ts +0 -223
- package/assets/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +0 -129
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.ts +0 -436
- package/assets/skills/poteto-mode/scripts/watch-pr/github.ts +0 -718
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.ts +0 -420
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.ts +0 -836
- package/assets/skills/poteto-mode/scripts/watch-pr/render.ts +0 -169
- package/assets/skills/poteto-mode/scripts/watch-pr/tsconfig.json +0 -13
- package/assets/skills/poteto-mode/scripts/watch-pr/types.compile.ts +0 -93
- package/assets/skills/poteto-mode/scripts/watch-pr/types.ts +0 -413
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr +0 -6
- package/assets/skills/poteto-mode/scripts/worktree-audit.sh +0 -127
- package/assets/skills/show-me-your-work/scripts/log.sh +0 -42
- package/dist/LICENSE +0 -22
- package/dist/README.md +0 -159
- package/dist/assets/agents/comment-sicko.md +0 -32
- package/dist/assets/agents/poteto-agent.md +0 -8
- package/dist/assets/docs/guide/01-setup.md +0 -64
- package/dist/assets/docs/guide/02-poteto-mode.md +0 -139
- package/dist/assets/docs/guide/03-understand.md +0 -79
- package/dist/assets/docs/guide/04-design.md +0 -133
- package/dist/assets/docs/guide/05-build-and-clean.md +0 -83
- package/dist/assets/docs/guide/06-verify-and-ship.md +0 -152
- package/dist/assets/docs/guide/07-overnight.md +0 -122
- package/dist/assets/docs/guide/08-principles.md +0 -72
- package/dist/assets/docs/guide/09-make-it-yours.md +0 -100
- package/dist/assets/docs/guide/10-recipes-and-pitfalls.md +0 -180
- package/dist/assets/docs/guide/README.md +0 -38
- package/dist/assets/docs/harness/dsh.md +0 -65
- package/dist/assets/docs/pstack-models.example.md +0 -23
- package/dist/assets/skills/architect/SKILL.md +0 -81
- package/dist/assets/skills/architect/references/design-red-flags.md +0 -57
- package/dist/assets/skills/architect/references/rationale-template.md +0 -35
- package/dist/assets/skills/architect/references/runner-prompt.md +0 -20
- package/dist/assets/skills/arena/SKILL.md +0 -50
- package/dist/assets/skills/automate-me/SKILL.md +0 -105
- package/dist/assets/skills/benchmark-checklist/SKILL.md +0 -41
- package/dist/assets/skills/blast-radius/SKILL.md +0 -52
- package/dist/assets/skills/bro/SKILL.md +0 -9
- package/dist/assets/skills/correct/SKILL.md +0 -34
- package/dist/assets/skills/create-formal-verification-skill/SKILL.md +0 -43
- package/dist/assets/skills/create-verification-skill/SKILL.md +0 -46
- package/dist/assets/skills/create-verification-skill/references/feature-map-example/README.md +0 -47
- package/dist/assets/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -39
- package/dist/assets/skills/create-verification-skill/references/feature-map-example/search.md +0 -45
- package/dist/assets/skills/figure-it-out/SKILL.md +0 -54
- package/dist/assets/skills/how/SKILL.md +0 -38
- package/dist/assets/skills/how/references/explainer-prompt.md +0 -55
- package/dist/assets/skills/how/references/explorer-prompt.md +0 -35
- package/dist/assets/skills/in-your-own-words/SKILL.md +0 -9
- package/dist/assets/skills/interrogate/SKILL.md +0 -69
- package/dist/assets/skills/interrogate/references/code-quality-review.md +0 -47
- package/dist/assets/skills/interrogate/references/lead-judgment.md +0 -39
- package/dist/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
- package/dist/assets/skills/interrogate/references/rubric.md +0 -77
- package/dist/assets/skills/ketchup/SKILL.md +0 -45
- package/dist/assets/skills/maintain-formal-verification-skill/SKILL.md +0 -60
- package/dist/assets/skills/maintain-verification-skill/SKILL.md +0 -41
- package/dist/assets/skills/no-comments/SKILL.md +0 -26
- package/dist/assets/skills/poteto-help/SKILL.md +0 -161
- package/dist/assets/skills/poteto-help/references/prompting.md +0 -51
- package/dist/assets/skills/poteto-help/references/recipes.md +0 -51
- package/dist/assets/skills/poteto-mode/SKILL.md +0 -153
- package/dist/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
- package/dist/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -13
- package/dist/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
- package/dist/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
- package/dist/assets/skills/poteto-mode/playbooks/babysit.md +0 -23
- package/dist/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
- package/dist/assets/skills/poteto-mode/playbooks/eval.md +0 -25
- package/dist/assets/skills/poteto-mode/playbooks/feature.md +0 -21
- package/dist/assets/skills/poteto-mode/playbooks/hillclimb.md +0 -21
- package/dist/assets/skills/poteto-mode/playbooks/investigation.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +0 -155
- package/dist/assets/skills/poteto-mode/playbooks/opening-a-pr.md +0 -34
- package/dist/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
- package/dist/assets/skills/poteto-mode/playbooks/pause-safely.md +0 -10
- package/dist/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
- package/dist/assets/skills/poteto-mode/playbooks/prototype.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
- package/dist/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
- package/dist/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
- package/dist/assets/skills/poteto-mode/playbooks/shipping.md +0 -12
- package/dist/assets/skills/poteto-mode/playbooks/trace-forensics.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
- package/dist/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -12
- package/dist/assets/skills/poteto-mode/references/bugbot-triage.md +0 -142
- package/dist/assets/skills/poteto-mode/scripts/bootstrap.ts +0 -62
- package/dist/assets/skills/poteto-mode/scripts/bun.lock +0 -67
- package/dist/assets/skills/poteto-mode/scripts/check-plan.mjs +0 -205
- package/dist/assets/skills/poteto-mode/scripts/check-plan.test.ts +0 -96
- package/dist/assets/skills/poteto-mode/scripts/orch/orch.test.ts +0 -761
- package/dist/assets/skills/poteto-mode/scripts/orch/orch.ts +0 -590
- package/dist/assets/skills/poteto-mode/scripts/orch/store.ts +0 -1677
- package/dist/assets/skills/poteto-mode/scripts/package.json +0 -16
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/cli.test.ts +0 -224
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/cli.ts +0 -223
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +0 -129
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/github.test.ts +0 -436
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/github.ts +0 -718
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/policy.test.ts +0 -420
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/policy.ts +0 -836
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/render.ts +0 -169
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/tsconfig.json +0 -13
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/types.compile.ts +0 -93
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/types.ts +0 -413
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/watch-pr +0 -6
- package/dist/assets/skills/poteto-mode/scripts/worktree-audit.sh +0 -127
- package/dist/assets/skills/principle-attack-the-premise/SKILL.md +0 -23
- package/dist/assets/skills/principle-boundary-discipline/SKILL.md +0 -36
- package/dist/assets/skills/principle-build-the-lever/SKILL.md +0 -26
- package/dist/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -13
- package/dist/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -11
- package/dist/assets/skills/principle-experience-first/SKILL.md +0 -21
- package/dist/assets/skills/principle-explain-the-number/SKILL.md +0 -19
- package/dist/assets/skills/principle-fix-root-causes/SKILL.md +0 -23
- package/dist/assets/skills/principle-foundational-thinking/SKILL.md +0 -23
- package/dist/assets/skills/principle-guard-the-context-window/SKILL.md +0 -14
- package/dist/assets/skills/principle-laziness-protocol/SKILL.md +0 -12
- package/dist/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -24
- package/dist/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -16
- package/dist/assets/skills/principle-minimize-reader-load/SKILL.md +0 -20
- package/dist/assets/skills/principle-model-the-domain/SKILL.md +0 -26
- package/dist/assets/skills/principle-never-block-on-the-human/SKILL.md +0 -17
- package/dist/assets/skills/principle-outcome-oriented-execution/SKILL.md +0 -23
- package/dist/assets/skills/principle-prove-it-works/SKILL.md +0 -24
- package/dist/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -18
- package/dist/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -18
- package/dist/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -19
- package/dist/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -20
- package/dist/assets/skills/principle-test-behavior-not-implementation/SKILL.md +0 -22
- package/dist/assets/skills/principle-type-system-discipline/SKILL.md +0 -15
- package/dist/assets/skills/recall/SKILL.md +0 -37
- package/dist/assets/skills/reflect/SKILL.md +0 -70
- package/dist/assets/skills/reflect/references/divergent-reviewer.md +0 -38
- package/dist/assets/skills/reflect/references/judgment-reviewer.md +0 -37
- package/dist/assets/skills/reflect/references/synthesizer.md +0 -56
- package/dist/assets/skills/reflect/references/tooling-reviewer.md +0 -48
- package/dist/assets/skills/setup-pstack/SKILL.md +0 -76
- package/dist/assets/skills/sharpen/SKILL.md +0 -18
- package/dist/assets/skills/show-me-your-work/SKILL.md +0 -84
- package/dist/assets/skills/show-me-your-work/references/decision-log-template.tsv +0 -1
- package/dist/assets/skills/show-me-your-work/scripts/log.sh +0 -42
- package/dist/assets/skills/swarm/SKILL.md +0 -41
- package/dist/assets/skills/tdd/SKILL.md +0 -44
- package/dist/assets/skills/teach/SKILL.md +0 -23
- package/dist/assets/skills/technical-writing/SKILL.md +0 -108
- package/dist/assets/skills/typescript-best-practices/SKILL.md +0 -32
- package/dist/assets/skills/typescript-best-practices/references/patterns.md +0 -328
- package/dist/assets/skills/unslop/SKILL.md +0 -64
- package/dist/assets/skills/why/SKILL.md +0 -100
- package/dist/assets/skills/why/references/epistemics.md +0 -71
- package/dist/assets/skills/why/references/investigator-prompt.md +0 -84
- package/dist/assets/skills/why/references/source-playbook.md +0 -17
- package/dist/assets/skills/why/references/sources/code-archaeology.md +0 -51
- package/dist/assets/skills/why/references/sources/databricks.md +0 -70
- package/dist/assets/skills/why/references/sources/datadog.md +0 -97
- package/dist/assets/skills/why/references/sources/incident-postmortem.md +0 -5
- package/dist/assets/skills/why/references/sources/linear.md +0 -48
- package/dist/assets/skills/why/references/sources/notion.md +0 -55
- package/dist/assets/skills/why/references/sources/sentry.md +0 -66
- package/dist/assets/skills/why/references/sources/slack.md +0 -17
- package/dist/assets/skills/why/references/synthesizer-prompt.md +0 -129
- package/dist/cordis.patch.yml +0 -20
- package/dist/paths-Bbai4CaT.mjs +0 -16
- package/dist/paths-Bbai4CaT.mjs.map +0 -1
package/dist/subagent.mjs
CHANGED
|
@@ -1,436 +1,1402 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
|
-
import { homedir } from "node:os";
|
|
1
|
+
import { i as POTETO_MODE_SKILL_PATH, o as agentPreamblePath, t as ASSETS_ROOT } from "./paths-D_JlVmBk.mjs";
|
|
2
|
+
import { c as parseFrontmatter } from "./frontmatter-BQX3ekwP.mjs";
|
|
6
3
|
import { join } from "node:path";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
//#region scripts/role-manifest.ts
|
|
7
|
+
/** File name the role policy lives under inside `$DSH_HOME`. */
|
|
8
|
+
const PSTACK_MODELS_FILENAME = "pstack-models.md";
|
|
9
|
+
/** Values that resolve to "send no `agentOptions` and inherit the parent route". */
|
|
10
|
+
const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
|
|
11
|
+
const ROLE_MANIFEST = [
|
|
12
|
+
{
|
|
13
|
+
key: "feature, refactoring",
|
|
14
|
+
shape: "single",
|
|
15
|
+
autoPanel: false,
|
|
16
|
+
consumers: ["poteto-mode/playbooks/feature.md", "poteto-mode/playbooks/refactoring.md"],
|
|
17
|
+
summary: "One route for both code playbooks. The comma joins two playbook names into a single key."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: "bug-fix",
|
|
21
|
+
shape: "single",
|
|
22
|
+
autoPanel: false,
|
|
23
|
+
consumers: ["poteto-mode/playbooks/bug-fix.md"],
|
|
24
|
+
summary: "Implementation route for the bug-fix playbook."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "perf-issue",
|
|
28
|
+
shape: "single",
|
|
29
|
+
autoPanel: false,
|
|
30
|
+
consumers: ["poteto-mode/playbooks/perf-issue.md"],
|
|
31
|
+
summary: "Implementation route for the perf-issue playbook."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "hillclimb",
|
|
35
|
+
shape: "single",
|
|
36
|
+
autoPanel: false,
|
|
37
|
+
consumers: ["poteto-mode/playbooks/hillclimb.md"],
|
|
38
|
+
summary: "Implementation route for the hillclimb playbook."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "judgment and prose",
|
|
42
|
+
shape: "single",
|
|
43
|
+
autoPanel: false,
|
|
44
|
+
consumers: [
|
|
45
|
+
"poteto-mode/SKILL.md",
|
|
46
|
+
"unslop",
|
|
47
|
+
"technical-writing"
|
|
48
|
+
],
|
|
49
|
+
summary: "Route for prose work and vague-instruction judgment calls."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "hardest tasks",
|
|
53
|
+
shape: "single",
|
|
54
|
+
autoPanel: false,
|
|
55
|
+
consumers: ["poteto-mode/SKILL.md"],
|
|
56
|
+
summary: "Route for the strongest judgment model on cross-cutting or subtle work."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "how explorer",
|
|
60
|
+
shape: "single",
|
|
61
|
+
autoPanel: false,
|
|
62
|
+
consumers: ["how/SKILL.md", "how/references/explorer-prompt.md"],
|
|
63
|
+
summary: "Route for each how explorer child."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "how explainer",
|
|
67
|
+
shape: "single",
|
|
68
|
+
autoPanel: false,
|
|
69
|
+
consumers: ["how/SKILL.md", "how/references/explainer-prompt.md"],
|
|
70
|
+
summary: "Route for the single how explainer child."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: "why investigators",
|
|
74
|
+
shape: "single",
|
|
75
|
+
autoPanel: false,
|
|
76
|
+
consumers: ["why/SKILL.md", "why/references/investigator-prompt.md"],
|
|
77
|
+
summary: "Route for each why investigator child."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "why synthesizer",
|
|
81
|
+
shape: "single",
|
|
82
|
+
autoPanel: false,
|
|
83
|
+
consumers: ["why/SKILL.md", "why/references/synthesizer-prompt.md"],
|
|
84
|
+
summary: "Route for the why synthesis child."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: "reflect tooling",
|
|
88
|
+
shape: "single",
|
|
89
|
+
autoPanel: false,
|
|
90
|
+
consumers: ["reflect/SKILL.md", "reflect/references/tooling-reviewer.md"],
|
|
91
|
+
summary: "Route for the reflect tooling reviewer."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "reflect judgment, divergent, synthesizer",
|
|
95
|
+
shape: "single",
|
|
96
|
+
autoPanel: false,
|
|
97
|
+
consumers: [
|
|
98
|
+
"reflect/SKILL.md",
|
|
99
|
+
"reflect/references/judgment-reviewer.md",
|
|
100
|
+
"reflect/references/divergent-reviewer.md",
|
|
101
|
+
"reflect/references/synthesizer.md"
|
|
102
|
+
],
|
|
103
|
+
summary: "One shared route for the judgment reviewer, the divergent reviewer, and the synthesizer. The commas belong to the key."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: "arena runners",
|
|
107
|
+
shape: "list",
|
|
108
|
+
autoPanel: true,
|
|
109
|
+
consumers: ["arena/SKILL.md"],
|
|
110
|
+
summary: "Panel: one arena child per entry, all started together."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
key: "arena cross-judge pool",
|
|
114
|
+
shape: "list",
|
|
115
|
+
autoPanel: false,
|
|
116
|
+
consumers: ["arena/SKILL.md"],
|
|
117
|
+
summary: "A pool arena picks one entry from, preferring a model family unlike the parent's. Not fanned out automatically."
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: "swarm workers",
|
|
121
|
+
shape: "list",
|
|
122
|
+
autoPanel: false,
|
|
123
|
+
consumers: ["swarm/SKILL.md", "poteto-mode/playbooks/multi-phase-plan.md"],
|
|
124
|
+
summary: "One default route for every worker. A race gives each arm another entry through `roleIndex`, and the caller still starts one worker per call."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
key: "architect runners",
|
|
128
|
+
shape: "list",
|
|
129
|
+
autoPanel: true,
|
|
130
|
+
consumers: ["architect/SKILL.md"],
|
|
131
|
+
summary: "Panel: one architect child per entry, all started together."
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "interrogate reviewers",
|
|
135
|
+
shape: "list",
|
|
136
|
+
autoPanel: true,
|
|
137
|
+
consumers: ["interrogate/SKILL.md"],
|
|
138
|
+
summary: "Panel: one interrogate reviewer per entry, all started together."
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
ROLE_MANIFEST.map((role) => role.key);
|
|
142
|
+
ROLE_MANIFEST.filter((role) => role.autoPanel).map((role) => role.key);
|
|
143
|
+
/** Exact manifest lookup. Callers normalize the key first. */
|
|
144
|
+
function findRoleSpec(key) {
|
|
145
|
+
return ROLE_MANIFEST.find((role) => role.key === key);
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/effort.ts
|
|
149
|
+
/** Model-facing name of the child's own control. */
|
|
150
|
+
const SET_EFFORT_TOOL = "set_reasoning_effort";
|
|
13
151
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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.
|
|
17
160
|
*/
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
161
|
+
function resolveEffortTarget(agent, route) {
|
|
162
|
+
const explicit = route?.kind === "route" ? route : void 0;
|
|
163
|
+
const header = agent?.session.requestHeader()?.config;
|
|
164
|
+
const provider = explicit?.provider ?? header?.provider ?? agent?.options.provider;
|
|
165
|
+
const model = explicit?.model ?? header?.model ?? agent?.options.model;
|
|
166
|
+
return provider !== void 0 && model !== void 0 ? {
|
|
167
|
+
provider,
|
|
168
|
+
model
|
|
169
|
+
} : void 0;
|
|
170
|
+
}
|
|
171
|
+
/** The efforts one route advertises, in the adapter's own order. */
|
|
172
|
+
async function advertisedEfforts(llm, target, signal) {
|
|
173
|
+
return (await llm.resolveModelInfo(target.provider, target.model, signal)).reasoning?.efforts.map((entry) => entry.id) ?? [];
|
|
174
|
+
}
|
|
27
175
|
/**
|
|
28
|
-
*
|
|
176
|
+
* Validate one effort against the route a child will actually request.
|
|
29
177
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* work, and neither can shadow the other: the full key is a different string.
|
|
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.
|
|
35
182
|
*/
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const issues = [];
|
|
45
|
-
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
46
|
-
for (const [index, rawLine] of lines.entries()) {
|
|
47
|
-
const line = rawLine.trim();
|
|
48
|
-
if (!line || line.startsWith("#") || line.startsWith("//")) continue;
|
|
49
|
-
const lineNumber = index + 1;
|
|
50
|
-
const separator = line.indexOf(":");
|
|
51
|
-
if (separator === -1) {
|
|
52
|
-
issues.push({
|
|
53
|
-
line: lineNumber,
|
|
54
|
-
message: `expected "role: value", got ${JSON.stringify(line)}`
|
|
55
|
-
});
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const roles = splitRoles(line.slice(0, separator));
|
|
59
|
-
if (roles.length === 0) {
|
|
60
|
-
issues.push({
|
|
61
|
-
line: lineNumber,
|
|
62
|
-
message: "no role name before the colon"
|
|
63
|
-
});
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const values = [];
|
|
67
|
-
for (const entry of line.slice(separator + 1).split(",")) {
|
|
68
|
-
const parsed = parseValue(entry.trim(), lineNumber, issues);
|
|
69
|
-
if (parsed !== void 0) values.push(parsed);
|
|
70
|
-
}
|
|
71
|
-
if (values.length === 0) {
|
|
72
|
-
issues.push({
|
|
73
|
-
line: lineNumber,
|
|
74
|
-
message: `no usable value for ${roles.join(", ")}`
|
|
75
|
-
});
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
for (const role of roles) table.set(role, values);
|
|
183
|
+
async function validateEffortFor(llm, route, effort, parent, signal) {
|
|
184
|
+
const target = resolveEffortTarget(parent, route);
|
|
185
|
+
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.");
|
|
186
|
+
let efforts;
|
|
187
|
+
try {
|
|
188
|
+
efforts = await advertisedEfforts(llm, target, signal);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw new Error(`route ${target.provider}/${target.model} could not be resolved for reasoningEffort: ${describeError$2(error)}`, { cause: error });
|
|
79
191
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
192
|
+
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
|
+
if (!efforts.includes(effort)) throw new Error(`${target.provider}/${target.model} does not support reasoning effort "${effort}". It supports: ${efforts.join(", ")}.`);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Install the effort control on one live child.
|
|
197
|
+
*
|
|
198
|
+
* Registration is child-scoped, so the waterfall and the tool dispose with the
|
|
199
|
+
* child: a sibling delegation never sees them, and a later child cannot inherit a
|
|
200
|
+
* stale value.
|
|
201
|
+
*
|
|
202
|
+
* The waterfall re-states the starting effort on every request and applies a
|
|
203
|
+
* change from the next request onward. It never rewrites a request already in
|
|
204
|
+
* flight, because `next()` has already been called and its result is the request
|
|
205
|
+
* the machine built.
|
|
206
|
+
*
|
|
207
|
+
* A missing route is not fatal here. The child still runs on whatever the adapter
|
|
208
|
+
* chose; the tool reports the missing route when the child tries to change effort.
|
|
209
|
+
*
|
|
210
|
+
* @param child - the live child agent, already started.
|
|
211
|
+
* @param llm - the adapter host used to validate a requested change.
|
|
212
|
+
* @param initialEffort - the effort the delegation started the child at, if any.
|
|
213
|
+
* @returns a disposer that unwinds this contribution early, or does nothing when
|
|
214
|
+
* the child context is already gone.
|
|
215
|
+
*/
|
|
216
|
+
function installEffortControl(child, llm, initialEffort) {
|
|
217
|
+
let current = initialEffort;
|
|
218
|
+
const disposeListener = child.ctx.on("agent/request", async (_payload, next) => {
|
|
219
|
+
const config = await next();
|
|
220
|
+
return current === void 0 ? config : {
|
|
221
|
+
...config,
|
|
222
|
+
reasoningEffort: current
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
const disposeTool = child.ctx.tools.register(defineSetEffortTool(child, llm, (effort) => {
|
|
226
|
+
current = effort;
|
|
227
|
+
}));
|
|
228
|
+
return () => {
|
|
229
|
+
disposeTool();
|
|
230
|
+
disposeListener();
|
|
83
231
|
};
|
|
84
232
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (INHERIT.has(value.toLowerCase())) return "inherit-parent";
|
|
88
|
-
const [route, ...rest] = value.split(/\s+/).filter(Boolean);
|
|
89
|
-
if (!route) return void 0;
|
|
90
|
-
const slash = route.indexOf("/");
|
|
91
|
-
if (slash <= 0 || slash === route.length - 1) {
|
|
92
|
-
issues.push({
|
|
93
|
-
line,
|
|
94
|
-
message: `"${route}" is not <provider>/<model>, so this role inherits the parent route`
|
|
95
|
-
});
|
|
96
|
-
return "inherit-parent";
|
|
97
|
-
}
|
|
98
|
-
let reasoningEffort;
|
|
99
|
-
for (const token of rest) {
|
|
100
|
-
if (EFFORTS.has(token.toLowerCase())) {
|
|
101
|
-
reasoningEffort = token.toLowerCase();
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
issues.push({
|
|
105
|
-
line,
|
|
106
|
-
message: `unknown effort "${token}"; expected one of ${REASONING_EFFORTS.join(", ")}`
|
|
107
|
-
});
|
|
108
|
-
}
|
|
233
|
+
/** The child's own view of its effort, for the tool body. */
|
|
234
|
+
function defineSetEffortTool(child, llm, apply) {
|
|
109
235
|
return {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
236
|
+
name: SET_EFFORT_TOOL,
|
|
237
|
+
description: [
|
|
238
|
+
"Change your reasoning effort for future model requests.",
|
|
239
|
+
"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."
|
|
242
|
+
].join(" "),
|
|
243
|
+
parameters: { effort: {
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "The reasoning effort to use from your next step onward. Use one of the values this model advertises; the call rejects an unknown one and lists the real values."
|
|
246
|
+
} },
|
|
247
|
+
output: {
|
|
248
|
+
schema: { type: "string" },
|
|
249
|
+
render: (_args, value) => [{
|
|
250
|
+
type: "text",
|
|
251
|
+
text: String(value)
|
|
252
|
+
}]
|
|
253
|
+
},
|
|
254
|
+
isConcurrencySafe: () => false,
|
|
255
|
+
async execute(rawArgs, exec) {
|
|
256
|
+
const args = rawArgs;
|
|
257
|
+
const value = typeof args.effort === "string" ? args.effort.trim() : "";
|
|
258
|
+
if (value === "") throw new Error("set_reasoning_effort needs a non-empty `effort`. Pass the exact token this model advertises; an empty value would silently re-enable the adapter default.");
|
|
259
|
+
const target = resolveEffortTarget(exec.agent ?? child);
|
|
260
|
+
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
|
+
let efforts;
|
|
262
|
+
try {
|
|
263
|
+
efforts = await advertisedEfforts(llm, target, exec.signal);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
throw new Error(`${target.provider}/${target.model} could not be resolved for reasoning effort: ${describeError$2(error)}`, { cause: error });
|
|
266
|
+
}
|
|
267
|
+
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
|
+
if (!efforts.includes(value)) throw new Error(`${target.provider}/${target.model} does not support reasoning effort "${value}", so nothing changed. It supports: ${efforts.join(", ")}.`);
|
|
269
|
+
apply(value);
|
|
270
|
+
return `reasoning effort set to "${value}". It applies from your next step; the request already in flight is unaffected.`;
|
|
271
|
+
}
|
|
113
272
|
};
|
|
114
273
|
}
|
|
115
274
|
/**
|
|
116
|
-
*
|
|
275
|
+
* The `agentOptions` one (route, effort override) pair contributes.
|
|
117
276
|
*
|
|
118
|
-
* The
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* `
|
|
122
|
-
*
|
|
123
|
-
*
|
|
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.
|
|
124
284
|
*/
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!key) return void 0;
|
|
129
|
-
const values = table.get(key);
|
|
130
|
-
if (!values) return void 0;
|
|
285
|
+
function agentOptionsFor(route, effortOverride) {
|
|
286
|
+
const effort = effortOverride ?? (route.kind === "route" ? route.effort : void 0);
|
|
287
|
+
if (route.kind === "inherit") return effort === void 0 ? void 0 : { reasoningEffort: effort };
|
|
131
288
|
return {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
289
|
+
provider: route.provider,
|
|
290
|
+
model: route.model,
|
|
291
|
+
...effort === void 0 ? {} : { reasoningEffort: effort }
|
|
135
292
|
};
|
|
136
293
|
}
|
|
294
|
+
/** The effort a plan entry will actually run at, override included. */
|
|
295
|
+
function effectiveEffort(route, effortOverride) {
|
|
296
|
+
return effortOverride ?? (route.kind === "route" ? route.effort : void 0);
|
|
297
|
+
}
|
|
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
|
|
137
307
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
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.
|
|
141
316
|
*/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (candidate === key) continue;
|
|
148
|
-
if (candidate.includes(key) || key.includes(candidate)) {
|
|
149
|
-
hits.push(candidate);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
for (const part of candidate.split(",").map((item) => item.trim())) {
|
|
153
|
-
if (part === key) continue;
|
|
154
|
-
if (part.includes(key) || key.includes(part)) {
|
|
155
|
-
hits.push(candidate);
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
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";
|
|
159
322
|
}
|
|
160
|
-
|
|
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));
|
|
161
337
|
}
|
|
162
338
|
/**
|
|
163
|
-
*
|
|
339
|
+
* Near-miss hints for a role name that did not match.
|
|
164
340
|
*
|
|
165
|
-
*
|
|
166
|
-
* children than the list has entries still gets a deterministic route. A
|
|
167
|
-
* missing role or an `inherit-parent` entry both return `undefined`, which
|
|
168
|
-
* tells the caller to omit `agentOptions` and inherit.
|
|
341
|
+
* Only used to build a diagnostic. A suggestion is never turned into a selection.
|
|
169
342
|
*/
|
|
170
|
-
function
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
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);
|
|
175
359
|
}
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region src/models.ts
|
|
178
360
|
/**
|
|
179
|
-
*
|
|
361
|
+
* Resolve a role name, or throw a diagnostic that names the near misses.
|
|
180
362
|
*/
|
|
181
|
-
function
|
|
182
|
-
|
|
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 !== "");
|
|
183
376
|
}
|
|
184
|
-
|
|
185
|
-
|
|
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];
|
|
186
394
|
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region src/models.ts
|
|
187
397
|
/**
|
|
188
|
-
*
|
|
398
|
+
* The pstack role file: `$DSH_HOME/pstack-models.md`.
|
|
189
399
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
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`.
|
|
194
419
|
*/
|
|
195
|
-
|
|
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;
|
|
196
428
|
try {
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
table,
|
|
200
|
-
issues,
|
|
201
|
-
unreadable: false
|
|
202
|
-
};
|
|
429
|
+
text = readFileSync(path, "utf8");
|
|
203
430
|
} catch (error) {
|
|
204
|
-
if (error
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
unreadable: false
|
|
431
|
+
if (isMissingFile(error)) return {
|
|
432
|
+
status: "missing",
|
|
433
|
+
path
|
|
208
434
|
};
|
|
209
435
|
return {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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: []
|
|
216
610
|
};
|
|
217
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
|
+
function describeError$1(error) {
|
|
645
|
+
if (error instanceof Error) {
|
|
646
|
+
const code = error.code;
|
|
647
|
+
return typeof code === "string" ? `${code}: ${error.message}` : error.message;
|
|
648
|
+
}
|
|
649
|
+
return String(error);
|
|
218
650
|
}
|
|
219
651
|
//#endregion
|
|
220
652
|
//#region src/subagent.ts
|
|
221
653
|
/**
|
|
222
|
-
* `pstack_subagent`: pstack's delegation
|
|
654
|
+
* `pstack_subagent`: pstack's delegation entry point.
|
|
223
655
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
656
|
+
* One call plans one or more fresh children, validates everything before the first
|
|
657
|
+
* start, then starts them through `ctx.subagents.startActivation` with parent
|
|
658
|
+
* delivery. Results arrive as completion notices; this module never polls.
|
|
659
|
+
*
|
|
660
|
+
* Validation order follows the design's delegation flow, and every check that can
|
|
661
|
+
* reject a call happens before the first child starts, except a start failure in
|
|
662
|
+
* the middle of a panel, which keeps the children already running and names them.
|
|
229
663
|
*/
|
|
664
|
+
/** Cordis row name, used by Loader diagnostics. */
|
|
230
665
|
const name = "pstack-subagent";
|
|
231
|
-
|
|
666
|
+
/**
|
|
667
|
+
* Delegation needs the tool registry, the subagent registry, and the LLM adapters.
|
|
668
|
+
*
|
|
669
|
+
* `agents` is deliberately **not** here. It is used only to reach a child this tool
|
|
670
|
+
* has just started, and a composition without it still delegates: the children keep
|
|
671
|
+
* the effort their request carried and cannot change it afterwards. Declaring it as
|
|
672
|
+
* a hard dependency would make the whole row inert in such a profile, so the tool
|
|
673
|
+
* would not exist at all — the failure `pstack-history` already paid for once with
|
|
674
|
+
* `sessionQuery`. It is read per call through `ctx.get("agents")` instead, which is
|
|
675
|
+
* why a controller mounted after this row is still seen.
|
|
676
|
+
*/
|
|
677
|
+
const inject = [
|
|
678
|
+
"tools",
|
|
679
|
+
"subagents",
|
|
680
|
+
"llm"
|
|
681
|
+
];
|
|
682
|
+
/** Model-facing tool name. */
|
|
683
|
+
const TOOL_NAME = "pstack_subagent";
|
|
684
|
+
/** Backend used when the row does not name one. */
|
|
685
|
+
const DEFAULT_SUBAGENT_PROVIDER = "spawn";
|
|
686
|
+
/** Agent type used when the call does not name one. */
|
|
687
|
+
const DEFAULT_AGENT_KIND = "poteto-agent";
|
|
688
|
+
/** The child kinds this tool knows how to brief. */
|
|
232
689
|
const AGENT_KINDS = [
|
|
233
690
|
"poteto-agent",
|
|
234
691
|
"comment-sicko",
|
|
235
692
|
"general"
|
|
236
693
|
];
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
694
|
+
const DEFAULT_CONFIG = {
|
|
695
|
+
provider: DEFAULT_SUBAGENT_PROVIDER,
|
|
696
|
+
allowedModels: [],
|
|
697
|
+
allowedAgentPresets: []
|
|
698
|
+
};
|
|
699
|
+
/** Raised for a configuration or argument problem, before any child starts. */
|
|
700
|
+
var DelegationError = class extends Error {
|
|
701
|
+
constructor(message) {
|
|
702
|
+
super(message);
|
|
703
|
+
this.name = "DelegationError";
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
/** Raised when a child starts but a later entry in the same call fails. */
|
|
707
|
+
var PartialDelegationError = class extends DelegationError {
|
|
708
|
+
startedChildren;
|
|
709
|
+
constructor(message, startedChildren) {
|
|
710
|
+
super(message);
|
|
711
|
+
this.name = "PartialDelegationError";
|
|
712
|
+
this.startedChildren = startedChildren;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
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
|
+
function isNonEmptyString(value) {
|
|
723
|
+
return typeof value === "string" && value.trim() !== "";
|
|
724
|
+
}
|
|
725
|
+
function isNonNegativeSafeInteger(value) {
|
|
726
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Minimal Standard Schema for this row, so an omitted `config` is valid and a
|
|
730
|
+
* typo fails at load instead of at the first delegation.
|
|
731
|
+
*/
|
|
732
|
+
const Config = { "~standard": {
|
|
733
|
+
version: 1,
|
|
734
|
+
vendor: "dsh-pstack",
|
|
735
|
+
validate(value) {
|
|
736
|
+
if (value === void 0 || value === null) return { value: DEFAULT_CONFIG };
|
|
737
|
+
if (typeof value !== "object" || Array.isArray(value)) return { issues: [{ message: "config must be a mapping" }] };
|
|
738
|
+
const record = value;
|
|
739
|
+
const issues = [];
|
|
740
|
+
const allowedKeys = /* @__PURE__ */ new Set([
|
|
741
|
+
"provider",
|
|
742
|
+
"maxDepth",
|
|
743
|
+
"allowedModels",
|
|
744
|
+
"allowedAgentPresets"
|
|
745
|
+
]);
|
|
746
|
+
for (const key of Object.keys(record)) if (!allowedKeys.has(key)) issues.push({
|
|
747
|
+
message: `unknown field "${key}"`,
|
|
748
|
+
path: [key]
|
|
749
|
+
});
|
|
750
|
+
const provider = record["provider"];
|
|
751
|
+
if (provider !== void 0 && !isNonEmptyString(provider)) issues.push({
|
|
752
|
+
message: "provider must be a non-empty string",
|
|
753
|
+
path: ["provider"]
|
|
754
|
+
});
|
|
755
|
+
const maxDepth = record["maxDepth"];
|
|
756
|
+
if (maxDepth !== void 0 && !isNonNegativeSafeInteger(maxDepth)) issues.push({
|
|
757
|
+
message: "maxDepth must be a non-negative safe integer",
|
|
758
|
+
path: ["maxDepth"]
|
|
759
|
+
});
|
|
760
|
+
const allowedModels = record["allowedModels"];
|
|
761
|
+
const models = [];
|
|
762
|
+
if (allowedModels !== void 0) {
|
|
763
|
+
if (!Array.isArray(allowedModels)) issues.push({
|
|
764
|
+
message: "allowedModels must be an array",
|
|
765
|
+
path: ["allowedModels"]
|
|
766
|
+
});
|
|
767
|
+
else allowedModels.forEach((entry, index) => {
|
|
768
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
769
|
+
issues.push({
|
|
770
|
+
message: "each allowedModels entry must be a { provider, model } object",
|
|
771
|
+
path: ["allowedModels", index]
|
|
772
|
+
});
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const route = entry;
|
|
776
|
+
const routeProvider = route["provider"];
|
|
777
|
+
const routeModel = route["model"];
|
|
778
|
+
if (!isNonEmptyString(routeProvider) || !isNonEmptyString(routeModel)) {
|
|
779
|
+
issues.push({
|
|
780
|
+
message: "each allowedModels entry needs a non-empty provider and model",
|
|
781
|
+
path: ["allowedModels", index]
|
|
782
|
+
});
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (models.some((known) => known.provider === routeProvider && known.model === routeModel)) {
|
|
786
|
+
issues.push({
|
|
787
|
+
message: `duplicate allowedModels route "${routeProvider}/${routeModel}"`,
|
|
788
|
+
path: ["allowedModels", index]
|
|
789
|
+
});
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
models.push({
|
|
793
|
+
provider: routeProvider,
|
|
794
|
+
model: routeModel
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
const allowedPresets = record["allowedAgentPresets"];
|
|
799
|
+
const presets = [];
|
|
800
|
+
if (allowedPresets !== void 0) {
|
|
801
|
+
if (!Array.isArray(allowedPresets)) issues.push({
|
|
802
|
+
message: "allowedAgentPresets must be an array",
|
|
803
|
+
path: ["allowedAgentPresets"]
|
|
804
|
+
});
|
|
805
|
+
else allowedPresets.forEach((entry, index) => {
|
|
806
|
+
if (!isNonEmptyString(entry)) {
|
|
807
|
+
issues.push({
|
|
808
|
+
message: "each allowedAgentPresets entry must be a non-empty string",
|
|
809
|
+
path: ["allowedAgentPresets", index]
|
|
810
|
+
});
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (presets.includes(entry)) {
|
|
814
|
+
issues.push({
|
|
815
|
+
message: `duplicate allowedAgentPresets entry "${entry}"`,
|
|
816
|
+
path: ["allowedAgentPresets", index]
|
|
817
|
+
});
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
presets.push(entry);
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
if (issues.length > 0) return { issues };
|
|
824
|
+
return { value: {
|
|
825
|
+
provider: isNonEmptyString(provider) ? provider : DEFAULT_SUBAGENT_PROVIDER,
|
|
826
|
+
...isNonNegativeSafeInteger(maxDepth) ? { maxDepth } : {},
|
|
827
|
+
allowedModels: models,
|
|
828
|
+
allowedAgentPresets: presets
|
|
829
|
+
} };
|
|
830
|
+
}
|
|
831
|
+
} };
|
|
832
|
+
/**
|
|
833
|
+
* Read one agent preamble from the package assets by absolute path.
|
|
834
|
+
*
|
|
835
|
+
* A missing or empty preamble blocks that agent type. It never degrades into a
|
|
836
|
+
* plain child, because a `comment-sicko` or `poteto-agent` child without its
|
|
837
|
+
* instructions is a different agent wearing the name.
|
|
838
|
+
*/
|
|
839
|
+
function loadAgentPreamble(agent, assetsRoot) {
|
|
840
|
+
if (agentPreamblePath(agent) === void 0) return void 0;
|
|
841
|
+
const absolute = join(assetsRoot, "agents", `${agent}.md`);
|
|
842
|
+
let text;
|
|
248
843
|
try {
|
|
249
|
-
|
|
250
|
-
} catch {
|
|
251
|
-
|
|
844
|
+
text = readFileSync(absolute, "utf8");
|
|
845
|
+
} catch (error) {
|
|
846
|
+
const code = error.code;
|
|
847
|
+
throw new PreambleError(`the ${agent} preamble cannot be read at ${absolute} (${typeof code === "string" ? code : String(error)}). This agent type does not start without its preamble; reinstall dsh-pstack so the packaged assets are present, or pass agent: "general" for a plain child that gets no preamble.`);
|
|
252
848
|
}
|
|
849
|
+
const body = parseFrontmatter(text).body.trim();
|
|
850
|
+
if (body === "") throw new PreambleError(`the ${agent} preamble at ${absolute} is empty. This agent type does not start without its instructions; repair the packaged asset or pass agent: "general".`);
|
|
851
|
+
return body;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Compose the self-contained prompt one child receives.
|
|
855
|
+
*
|
|
856
|
+
* The preambles used to locate things themselves; the Host injects the absolute
|
|
857
|
+
* paths instead, so a child never scans a plugin directory or guesses a version.
|
|
858
|
+
*/
|
|
859
|
+
function composeChildPrompt(input) {
|
|
860
|
+
const sections = [];
|
|
861
|
+
if (input.preamble !== void 0) sections.push(input.preamble);
|
|
862
|
+
const injected = input.injectedPaths ?? [];
|
|
863
|
+
if (injected.length > 0) sections.push([
|
|
864
|
+
"Absolute paths injected by `pstack_subagent`. Read them directly; do not search a plugin directory.",
|
|
865
|
+
"",
|
|
866
|
+
...injected.map((entry) => `- ${entry.label}: \`${entry.path}\``)
|
|
867
|
+
].join("\n"));
|
|
868
|
+
if (sections.length === 0) return input.task;
|
|
869
|
+
sections.push(`Task:\n\n${input.task}`);
|
|
870
|
+
return sections.join("\n\n---\n\n");
|
|
871
|
+
}
|
|
872
|
+
/** The text form of one route, as reported back to the model. */
|
|
873
|
+
function routeLabel(route) {
|
|
874
|
+
if (route.kind === "inherit") return "inherit-parent";
|
|
875
|
+
return route.effort === void 0 ? `${route.provider}/${route.model}` : `${route.provider}/${route.model} [${route.effort}]`;
|
|
253
876
|
}
|
|
254
877
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
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.
|
|
258
894
|
*/
|
|
259
|
-
function
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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);
|
|
268
904
|
}
|
|
269
905
|
/**
|
|
270
|
-
*
|
|
271
|
-
* instead of swallowing it.
|
|
906
|
+
* Validate the model-facing arguments.
|
|
272
907
|
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* on the parent route. The tool reply says so, so the parent can fix the file or
|
|
276
|
-
* report the miss rather than believing the route applied.
|
|
908
|
+
* The tool schema is advisory at the model boundary, so every constraint is
|
|
909
|
+
* re-checked here and reported with the reason.
|
|
277
910
|
*/
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
911
|
+
function parseDelegationArgs(raw, config = DEFAULT_CONFIG) {
|
|
912
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DelegationError("arguments must be an object");
|
|
913
|
+
const record = raw;
|
|
914
|
+
const known = /* @__PURE__ */ new Set([
|
|
915
|
+
"description",
|
|
916
|
+
"prompt",
|
|
917
|
+
"agent",
|
|
918
|
+
"role",
|
|
919
|
+
"cwd",
|
|
920
|
+
...config.allowedAgentPresets.length > 0 ? ["agentPreset"] : [],
|
|
921
|
+
"maxDepth",
|
|
922
|
+
"roleIndex",
|
|
923
|
+
"reasoningEffort"
|
|
924
|
+
]);
|
|
925
|
+
for (const key of Object.keys(record)) if (!known.has(key)) throw new DelegationError(`unknown argument "${key}": ${key === "agentPreset" ? "agentPreset is only available when this row configures allowedAgentPresets" : `accepted arguments are ${[...known].join(", ")}`}`);
|
|
926
|
+
const description = record["description"];
|
|
927
|
+
if (!isNonEmptyString(description)) throw new DelegationError("description must be a non-empty string: it becomes the child label");
|
|
928
|
+
const prompt = record["prompt"];
|
|
929
|
+
if (!isNonEmptyString(prompt)) throw new DelegationError("prompt must be a non-empty string");
|
|
930
|
+
const agentRaw = record["agent"];
|
|
931
|
+
if (agentRaw !== void 0 && typeof agentRaw !== "string") throw new DelegationError(`agent must be a string: ${AGENT_KINDS.join(", ")}`);
|
|
932
|
+
const agent = agentRaw ?? "poteto-agent";
|
|
933
|
+
if (!AGENT_KINDS.includes(agent)) throw new DelegationError(`unknown agent "${agent}". Accepted kinds are ${AGENT_KINDS.join(", ")}; a pstack agent type is not a DSH agent preset, so \`agent\` never accepts a preset id.`);
|
|
934
|
+
const role = record["role"];
|
|
935
|
+
if (role !== void 0 && !isNonEmptyString(role)) throw new DelegationError("role must be a non-empty string when present");
|
|
936
|
+
const cwd = record["cwd"];
|
|
937
|
+
if (cwd !== void 0 && !isNonEmptyString(cwd)) throw new DelegationError("cwd must be a non-empty string when present");
|
|
938
|
+
const agentPreset = record["agentPreset"];
|
|
939
|
+
if (agentPreset !== void 0) {
|
|
940
|
+
if (config.allowedAgentPresets.length === 0) throw new DelegationError("agentPreset is not available: this pstack-subagent row configures no allowedAgentPresets");
|
|
941
|
+
if (!isNonEmptyString(agentPreset) || !config.allowedAgentPresets.includes(agentPreset)) throw new DelegationError(`agentPreset ${JSON.stringify(agentPreset)} is not in this row's allowlist (${config.allowedAgentPresets.join(", ")})`);
|
|
942
|
+
}
|
|
943
|
+
const maxDepth = record["maxDepth"];
|
|
944
|
+
if (maxDepth !== void 0 && !isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer");
|
|
945
|
+
const roleIndex = record["roleIndex"];
|
|
946
|
+
if (roleIndex !== void 0) {
|
|
947
|
+
if (!isNonNegativeSafeInteger(roleIndex)) throw new DelegationError("roleIndex must be a non-negative safe integer");
|
|
948
|
+
if (role === void 0) throw new DelegationError("roleIndex requires role: it selects one entry of a list-valued role, and without a role there is no list");
|
|
949
|
+
}
|
|
950
|
+
const requestedEffort = record["reasoningEffort"];
|
|
951
|
+
if (requestedEffort !== void 0 && !isNonEmptyString(requestedEffort)) throw new DelegationError("reasoningEffort must be a non-empty string when present. It is one adapter-owned token, validated against the model this call will actually request; an empty value would silently re-enable the adapter default.");
|
|
952
|
+
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
|
+
return {
|
|
954
|
+
description,
|
|
955
|
+
prompt,
|
|
956
|
+
agent,
|
|
957
|
+
...role === void 0 ? {} : { role },
|
|
958
|
+
...cwd === void 0 ? {} : { cwd },
|
|
959
|
+
...agentPreset === void 0 ? {} : { agentPreset },
|
|
960
|
+
...maxDepth === void 0 ? {} : { maxDepth },
|
|
961
|
+
...roleIndex === void 0 ? {} : { roleIndex },
|
|
962
|
+
...requestedEffort === void 0 ? {} : { reasoningEffort: requestedEffort.trim() }
|
|
282
963
|
};
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Resolve the role file, the routes, the prompt, and the depth into one plan.
|
|
967
|
+
*
|
|
968
|
+
* Pure with respect to the Host: it reads the role file and the packaged preamble,
|
|
969
|
+
* and it makes no service call.
|
|
970
|
+
*/
|
|
971
|
+
function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
|
|
972
|
+
const preamble = loadAgentPreamble(args.agent, runtime.assetsRoot);
|
|
973
|
+
const rolePath = modelsFilePath(runtime.dshHome);
|
|
974
|
+
const roleFile = readRoleFile(rolePath);
|
|
975
|
+
if (roleFile.status === "unreadable") throw new DelegationError(`the pstack role file at ${rolePath} exists but cannot be read (${roleFile.reason}). This is a broken configuration, not "no configuration": fix its permissions or delete the file, which makes every role inherit the parent route.`);
|
|
976
|
+
const diagnostics = [];
|
|
977
|
+
if (roleFile.status === "loaded") for (const issue of roleFile.issues) diagnostics.push(`${rolePath}${issue.line === void 0 ? "" : `:${issue.line}`}: ${issue.message}`);
|
|
978
|
+
let selection;
|
|
979
|
+
let roleSpec;
|
|
980
|
+
if (args.role !== void 0) {
|
|
981
|
+
roleSpec = requireRole(args.role);
|
|
982
|
+
const entry = roleFile.status === "loaded" ? roleFile.roles.get(roleSpec.key) : void 0;
|
|
983
|
+
selection = selectRoleRoutes(roleSpec, entry, args.roleIndex);
|
|
984
|
+
}
|
|
985
|
+
const routes = selection?.routes ?? [{ kind: "inherit" }];
|
|
986
|
+
const roleName = roleSpec?.key ?? "(none)";
|
|
987
|
+
if (selection !== void 0) {
|
|
988
|
+
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
|
+
if (selection.mode === "selected") diagnostics.push(`role "${selection.role.key}" holds ${selection.total} entries and is not an automatic panel; entry ${(selection.roleIndex ?? 0) + 1} was selected. Pass \`roleIndex\` to choose another.`);
|
|
990
|
+
if (selection.role.key === "swarm workers") diagnostics.push("role \"swarm workers\" is a list, not a panel: its first entry is the default route for every worker, and `roleIndex` gives one race arm another entry. Each worker still needs its own pstack_subagent call, because this tool cannot compare separate calls.");
|
|
991
|
+
}
|
|
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
|
+
const prompt = composeChildPrompt({
|
|
998
|
+
...preamble === void 0 ? {} : { preamble },
|
|
999
|
+
injectedPaths: injected,
|
|
1000
|
+
task: args.prompt
|
|
1001
|
+
});
|
|
1002
|
+
const depth = resolveDelegationDepth(config.maxDepth, args.maxDepth, void 0);
|
|
1003
|
+
const total = routes.length;
|
|
1004
|
+
const children = routes.map((route, index) => {
|
|
1005
|
+
const agentOptions = agentOptionsFor(route, args.reasoningEffort);
|
|
1006
|
+
const effort = effectiveEffort(route, args.reasoningEffort);
|
|
287
1007
|
return {
|
|
288
|
-
|
|
289
|
-
|
|
1008
|
+
index,
|
|
1009
|
+
total,
|
|
1010
|
+
label: total > 1 ? `${args.description} [${roleName} ${index + 1}/${total}]` : args.description,
|
|
1011
|
+
agent: args.agent,
|
|
1012
|
+
role: roleName,
|
|
1013
|
+
route,
|
|
1014
|
+
routeLabel: plannedRouteLabel(route, args.reasoningEffort),
|
|
1015
|
+
...effort === void 0 ? {} : { effort },
|
|
1016
|
+
...agentOptions === void 0 ? {} : { agentOptions },
|
|
1017
|
+
...args.agentPreset === void 0 ? {} : { agentPreset: args.agentPreset },
|
|
1018
|
+
...args.cwd === void 0 ? {} : { cwd: args.cwd },
|
|
1019
|
+
...depth === void 0 ? {} : { maxDepth: depth }
|
|
290
1020
|
};
|
|
1021
|
+
});
|
|
1022
|
+
if (args.reasoningEffort !== void 0) {
|
|
1023
|
+
const overwritten = routes.filter((route) => route.kind === "route" && route.effort !== void 0 && route.effort !== args.reasoningEffort);
|
|
1024
|
+
if (overwritten.length > 0) diagnostics.push(`reasoningEffort "${args.reasoningEffort}" overrides the effort this call's role line names (${overwritten.map((route) => routeLabel(route)).join(", ")}). The role line is a starting baseline, so this is allowed; pass no reasoningEffort to keep it.`);
|
|
291
1025
|
}
|
|
292
|
-
|
|
293
|
-
const lines = [];
|
|
294
|
-
if (unreadable) lines.push(`pstack: ${modelConfigPath()} could not be read; the child inherits the parent route.`);
|
|
295
|
-
else if (table.size === 0) lines.push(`pstack: no role table at ${modelConfigPath()}; the child inherits the parent route. Run /setup-pstack to configure roles.`);
|
|
296
|
-
else {
|
|
297
|
-
lines.push(`pstack: role "${role}" is not in ${modelConfigPath()}; the child inherits the parent route.`);
|
|
298
|
-
if (near.length > 0) lines.push(`pstack: did you mean ${near.map((n) => `"${n}"`).join(" or ")}?`);
|
|
299
|
-
}
|
|
1026
|
+
addPanelDiagnostics(children, roleName, selection, rolePath, diagnostics);
|
|
300
1027
|
return {
|
|
301
|
-
|
|
302
|
-
|
|
1028
|
+
agent: args.agent,
|
|
1029
|
+
prompt,
|
|
1030
|
+
provider: config.provider,
|
|
1031
|
+
role: roleName,
|
|
1032
|
+
mode: selection?.mode ?? "single",
|
|
1033
|
+
...selection?.roleIndex === void 0 ? {} : { roleIndex: selection.roleIndex },
|
|
1034
|
+
roleTotal: selection?.total ?? 1,
|
|
1035
|
+
...args.reasoningEffort === void 0 ? {} : { reasoningEffort: args.reasoningEffort },
|
|
1036
|
+
children,
|
|
1037
|
+
diagnostics
|
|
303
1038
|
};
|
|
304
1039
|
}
|
|
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
|
+
function addPanelDiagnostics(children, roleName, selection, rolePath, diagnostics) {
|
|
1048
|
+
if (selection === void 0 || selection.mode !== "panel") return;
|
|
1049
|
+
if (children.length === 1) {
|
|
1050
|
+
diagnostics.push(`panel "${roleName}" has one configured entry, so this call started one child and claims no model diversity. Add entries to ${rolePath} for a real panel.`);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const distinct = new Set(children.map((child) => child.routeLabel));
|
|
1054
|
+
if (distinct.size > 1) return;
|
|
1055
|
+
diagnostics.push(`all ${children.length} entries of panel "${roleName}" resolve to the same route (${[...distinct][0]}), so these children are not a cross-model review. Configure distinct routes in ${rolePath} for a real panel.`);
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Validate every route, and every requested effort, before anything starts.
|
|
1059
|
+
*
|
|
1060
|
+
* `resolveCallConfig` rejects an unsupported effort and any route the owning
|
|
1061
|
+
* adapter does not serve. An inherit-parent entry has no provider/model of its own,
|
|
1062
|
+
* so its effort is validated against the parent's live route instead. This does not
|
|
1063
|
+
* prove credentials, quota, or a successful model call; only a real child request
|
|
1064
|
+
* does.
|
|
1065
|
+
*
|
|
1066
|
+
* Every entry is checked before the first child starts. A panel whose second entry
|
|
1067
|
+
* cannot take the effort therefore starts none of them, which is the only outcome
|
|
1068
|
+
* that leaves no half-applied override behind.
|
|
1069
|
+
*/
|
|
1070
|
+
async function preflightRoutes(llm, parent, children, roleName, effortOverride, signal) {
|
|
1071
|
+
for (const child of children) {
|
|
1072
|
+
if (child.agentOptions === void 0) continue;
|
|
1073
|
+
if (child.route.kind === "inherit") {
|
|
1074
|
+
const effort = effortOverride;
|
|
1075
|
+
try {
|
|
1076
|
+
await validateEffortFor(llm, void 0, effort, parent, signal);
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
throw new DelegationError(`role ${roleName} inherits the parent route, and reasoningEffort "${effort}" failed preflight there: ${describeError(error)} Omit \`reasoningEffort\`, or name a role whose line holds an exact route.`);
|
|
1079
|
+
}
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
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
|
+
try {
|
|
1089
|
+
await llm.resolveCallConfig(callConfig, signal);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
throw new DelegationError(`role ${roleName} resolves to ${child.routeLabel}, which failed route preflight: ${describeError(error)}. ${await effortHint(llm, route, child.effort, signal)}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
async function effortHint(llm, route, effort, signal) {
|
|
1096
|
+
if (effort !== void 0) try {
|
|
1097
|
+
const efforts = (await llm.resolveModelInfo(route.provider, route.model, signal)).reasoning?.efforts.map((entry) => entry.id) ?? [];
|
|
1098
|
+
if (efforts.length > 0) return `Model ${route.provider}/${route.model} supports these efforts: ${efforts.join(", ")}.`;
|
|
1099
|
+
return `Model ${route.provider}/${route.model} advertises no reasoning efforts.`;
|
|
1100
|
+
} catch {
|
|
1101
|
+
return `Fix the route or the effort in ${PSTACK_MODELS_FILENAME}; the configured provider/model pair may not be served by this deployment.`;
|
|
1102
|
+
}
|
|
1103
|
+
return `Check the route in ${PSTACK_MODELS_FILENAME} against the providers this deployment registers.`;
|
|
1104
|
+
}
|
|
1105
|
+
/** Check the backend can enforce what this call asks for. */
|
|
1106
|
+
function assertProviderCapabilities(capabilities, providerName, asks) {
|
|
1107
|
+
const missing = [];
|
|
1108
|
+
if (asks.agentOptions && !capabilities.agentOptions) missing.push("agentOptions");
|
|
1109
|
+
if (asks.agentPreset && !capabilities.agentPreset) missing.push("agentPreset");
|
|
1110
|
+
if (asks.maxDepth && !capabilities.depthLimit) missing.push("maxDepth");
|
|
1111
|
+
if (missing.length === 0) return;
|
|
1112
|
+
throw new DelegationError(`subagent provider "${providerName}" does not support ${missing.join(", ")}, so this call cannot be honored. Change the route to \`inherit-parent\`, drop the unsupported argument, or configure a backend that supports it; this tool never silently ignores it.`);
|
|
1113
|
+
}
|
|
305
1114
|
/**
|
|
306
|
-
*
|
|
1115
|
+
* Start the planned children.
|
|
307
1116
|
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* live delegation picked the wrong model.
|
|
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.
|
|
312
1120
|
*/
|
|
313
|
-
function
|
|
314
|
-
|
|
1121
|
+
async function startPlannedChildren(parent, plan, runtime, config, signal) {
|
|
1122
|
+
const provider = runtime.subagents.getProvider(plan.provider);
|
|
1123
|
+
if (provider === void 0) {
|
|
1124
|
+
const available = runtime.subagents.list();
|
|
1125
|
+
throw new DelegationError(`subagent provider "${plan.provider}" is not registered; registered providers: ${available.length === 0 ? "(none)" : available.join(", ")}. This pstack-subagent row delegates to "${plan.provider}"; compose that backend or change the row's provider.`);
|
|
1126
|
+
}
|
|
1127
|
+
const hostLimit = runtime.subagents.resolveMaxDepth(config.maxDepth);
|
|
1128
|
+
const effectiveDepth = resolveDelegationDepth(config.maxDepth, plan.children[0]?.maxDepth, hostLimit);
|
|
1129
|
+
assertProviderCapabilities(provider.capabilities, plan.provider, {
|
|
1130
|
+
agentOptions: plan.children.some((child) => child.agentOptions !== void 0),
|
|
1131
|
+
agentPreset: plan.children.some((child) => child.agentPreset !== void 0),
|
|
1132
|
+
maxDepth: effectiveDepth !== void 0
|
|
1133
|
+
});
|
|
1134
|
+
const children = plan.children.map((child) => ({
|
|
1135
|
+
...child,
|
|
1136
|
+
...effectiveDepth === void 0 ? {} : { maxDepth: effectiveDepth }
|
|
1137
|
+
}));
|
|
1138
|
+
await preflightRoutes(runtime.llm, parent, children, plan.role, plan.reasoningEffort, signal);
|
|
1139
|
+
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`);
|
|
1140
|
+
const started = [];
|
|
1141
|
+
const diagnostics = [...plan.diagnostics];
|
|
1142
|
+
for (const child of children) {
|
|
1143
|
+
signal.throwIfAborted();
|
|
1144
|
+
try {
|
|
1145
|
+
const activation = await runtime.subagents.startActivation({
|
|
1146
|
+
provider: plan.provider,
|
|
1147
|
+
label: child.label,
|
|
1148
|
+
request: {
|
|
1149
|
+
prompt: [{
|
|
1150
|
+
type: "text",
|
|
1151
|
+
text: plan.prompt
|
|
1152
|
+
}],
|
|
1153
|
+
parent,
|
|
1154
|
+
...child.cwd === void 0 ? {} : { cwd: child.cwd },
|
|
1155
|
+
...child.agentPreset === void 0 ? {} : { agentPreset: child.agentPreset },
|
|
1156
|
+
...child.agentOptions === void 0 ? {} : { agentOptions: child.agentOptions },
|
|
1157
|
+
...child.maxDepth === void 0 ? {} : { maxDepth: child.maxDepth }
|
|
1158
|
+
},
|
|
1159
|
+
signal,
|
|
1160
|
+
delivery: "parent"
|
|
1161
|
+
});
|
|
1162
|
+
const target = runtime.agents === void 0 ? void 0 : runtime.agents.get(activation.childId);
|
|
1163
|
+
if (target === void 0) diagnostics.push(`child ${activation.childId} has no live local Agent handle, so it cannot change its own reasoning effort with \`${SET_EFFORT_TOOL}\`. It runs at ${child.effort === void 0 ? "the adapter default" : `"${child.effort}"`} for its whole life.`);
|
|
1164
|
+
else installEffortControl(target, runtime.llm, child.effort);
|
|
1165
|
+
started.push({
|
|
1166
|
+
childId: activation.childId,
|
|
1167
|
+
label: child.label,
|
|
1168
|
+
agent: child.agent,
|
|
1169
|
+
role: child.role,
|
|
1170
|
+
route: child.routeLabel,
|
|
1171
|
+
...child.effort === void 0 ? {} : { effort: child.effort },
|
|
1172
|
+
...child.maxDepth === void 0 ? {} : { maxDepth: child.maxDepth }
|
|
1173
|
+
});
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
if (started.length === 0) throw new DelegationError(`starting the ${plan.role === "(none)" ? "child" : `"${plan.role}" child`} "${child.label}" (${child.routeLabel}) failed: ${describeError(error)}`);
|
|
1176
|
+
throw new PartialDelegationError(`starting entry ${child.index + 1} of ${child.total} for role "${plan.role}" (${child.label}, ${child.routeLabel}) failed: ${describeError(error)}. The ${started.length} already started: ${started.map((record) => `${record.childId} (${record.label})`).join(", ")}. Those children are still running and are not rolled back; do not start them again, and use list_agents or send_message to reach them.`, started);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
315
1179
|
return {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
1180
|
+
kind: started.length === 1 ? "activation" : "activations",
|
|
1181
|
+
children: started,
|
|
1182
|
+
diagnostics
|
|
319
1183
|
};
|
|
320
1184
|
}
|
|
321
|
-
/**
|
|
322
|
-
function
|
|
323
|
-
return
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
1185
|
+
/** Full delegation: plan, validate, start. */
|
|
1186
|
+
async function delegate(parent, args, runtime, config = DEFAULT_CONFIG, signal = new AbortController().signal) {
|
|
1187
|
+
return startPlannedChildren(parent, planDelegation(args, runtime, config), runtime, config, signal);
|
|
1188
|
+
}
|
|
1189
|
+
const TOOL_DESCRIPTION = [
|
|
1190
|
+
"Delegate self-contained work to one or more fresh child agents.",
|
|
1191
|
+
"",
|
|
1192
|
+
"Returns the id of every child it started plus the route each one resolved. The children run",
|
|
1193
|
+
"in the background: their results arrive later as completion notices in this conversation.",
|
|
1194
|
+
"Never wait or poll for them, and keep working on something useful meanwhile.",
|
|
1195
|
+
"",
|
|
1196
|
+
"`agent` picks the brief: `poteto-agent` (default) injects the poteto subagent preamble and",
|
|
1197
|
+
"the absolute path of the poteto-mode instructions; `comment-sicko` injects the comment",
|
|
1198
|
+
"reviewer persona and must be briefed read-only; `general` is a plain child with no preamble.",
|
|
1199
|
+
"",
|
|
1200
|
+
"`role` selects a configured pstack role and therefore the child's model route and reasoning",
|
|
1201
|
+
"effort. The role names and the meaning of each are in the pstack role manifest, not here.",
|
|
1202
|
+
"Omit `role` to inherit the parent route. Panel roles start one child per configured entry.",
|
|
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.",
|
|
1209
|
+
"",
|
|
1210
|
+
"A running child can also change its own effort with `set_reasoning_effort`, which applies",
|
|
1211
|
+
"from its next step. You do not have to predict the level up front.",
|
|
1212
|
+
"",
|
|
1213
|
+
"Every prompt is a fresh child that does not see this conversation, so include everything it",
|
|
1214
|
+
"needs. A child's own summary is not evidence: review its diff or artifacts yourself."
|
|
1215
|
+
].join("\n");
|
|
1216
|
+
function buildParameters(config) {
|
|
1217
|
+
const properties = {
|
|
1218
|
+
description: {
|
|
1219
|
+
type: "string",
|
|
1220
|
+
description: "A short 3-5 word label for the child, shown in list_agents and its notices."
|
|
1221
|
+
},
|
|
1222
|
+
prompt: {
|
|
1223
|
+
type: "string",
|
|
1224
|
+
description: "The complete, self-contained brief. The child does not see this conversation, so include the task, every prior directive, and the paths or branches it needs."
|
|
1225
|
+
},
|
|
1226
|
+
agent: {
|
|
1227
|
+
type: "string",
|
|
1228
|
+
enum: [...AGENT_KINDS],
|
|
1229
|
+
description: "Child brief type: \"poteto-agent\" (default) for pstack implementation delegates, \"comment-sicko\" for the read-only comment reviewer, \"general\" for a plain child with no preamble."
|
|
1230
|
+
},
|
|
1231
|
+
role: {
|
|
1232
|
+
type: "string",
|
|
1233
|
+
description: "A pstack role key, matched exactly. It selects the child's model route and effort. Omit it to inherit the parent route."
|
|
1234
|
+
},
|
|
1235
|
+
cwd: {
|
|
1236
|
+
type: "string",
|
|
1237
|
+
description: "The child's initial working directory. Relative paths resolve against this session's directory. Omitted inherits it."
|
|
1238
|
+
},
|
|
1239
|
+
maxDepth: {
|
|
1240
|
+
type: "integer",
|
|
1241
|
+
description: "How many further delegation levels the child may use; 0 or greater, and negative values are rejected. It can only lower this row's configured cap, never raise it."
|
|
1242
|
+
},
|
|
1243
|
+
roleIndex: {
|
|
1244
|
+
type: "integer",
|
|
1245
|
+
description: "Selects one entry of a list-valued role that is not an automatic panel; 0 or greater, and negative values are rejected. Requires `role`."
|
|
1246
|
+
},
|
|
1247
|
+
reasoningEffort: {
|
|
1248
|
+
type: "string",
|
|
1249
|
+
description: [
|
|
1250
|
+
"Reasoning effort for every child this call starts, overriding the role line's value for this delegation.",
|
|
1251
|
+
"Start from the role's level, raise it for hard work, lower it for routine work.",
|
|
1252
|
+
"The value must be one the target model advertises: this package assumes no ladder and rejects an unknown token with that model's real effort list.",
|
|
1253
|
+
"It is validated for every panel entry before any child starts, so one incompatible entry fails the whole call instead of half-starting it.",
|
|
1254
|
+
"Omit it to use the role line's effort."
|
|
1255
|
+
].join(" ")
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
if (config.allowedAgentPresets.length > 0) properties["agentPreset"] = {
|
|
1259
|
+
type: "string",
|
|
1260
|
+
enum: [...config.allowedAgentPresets],
|
|
1261
|
+
description: "An independent child preset from this row's allowlist."
|
|
1262
|
+
};
|
|
1263
|
+
return {
|
|
1264
|
+
type: "object",
|
|
1265
|
+
additionalProperties: false,
|
|
1266
|
+
properties,
|
|
1267
|
+
required: ["description", "prompt"]
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
const OUTPUT_SCHEMA = {
|
|
1271
|
+
type: "object",
|
|
1272
|
+
additionalProperties: false,
|
|
1273
|
+
properties: {
|
|
1274
|
+
kind: {
|
|
1275
|
+
type: "string",
|
|
1276
|
+
enum: ["activation", "activations"],
|
|
1277
|
+
description: "How many children this call started."
|
|
1278
|
+
},
|
|
1279
|
+
children: {
|
|
1280
|
+
type: "array",
|
|
1281
|
+
description: "One record per started child, in start order.",
|
|
1282
|
+
items: {
|
|
1283
|
+
type: "object",
|
|
1284
|
+
additionalProperties: false,
|
|
1285
|
+
properties: {
|
|
1286
|
+
childId: {
|
|
1287
|
+
type: "string",
|
|
1288
|
+
description: "The child session id."
|
|
1289
|
+
},
|
|
1290
|
+
label: {
|
|
1291
|
+
type: "string",
|
|
1292
|
+
description: "The label this call gave the child."
|
|
1293
|
+
},
|
|
1294
|
+
agent: {
|
|
1295
|
+
type: "string",
|
|
1296
|
+
description: "The child brief type."
|
|
1297
|
+
},
|
|
1298
|
+
role: {
|
|
1299
|
+
type: "string",
|
|
1300
|
+
description: "The role key, or `(none)`."
|
|
1301
|
+
},
|
|
1302
|
+
route: {
|
|
1303
|
+
type: "string",
|
|
1304
|
+
description: "`inherit-parent` or `<provider>/<model> [effort]`."
|
|
1305
|
+
},
|
|
1306
|
+
effort: {
|
|
1307
|
+
type: "string",
|
|
1308
|
+
description: "The reasoning effort this child runs at, when one applies. Equal to the call's `reasoningEffort` when that was passed."
|
|
1309
|
+
},
|
|
1310
|
+
maxDepth: {
|
|
1311
|
+
type: "integer",
|
|
1312
|
+
description: "The delegation-depth cap the child received."
|
|
1313
|
+
}
|
|
1314
|
+
},
|
|
1315
|
+
required: [
|
|
1316
|
+
"childId",
|
|
1317
|
+
"label",
|
|
1318
|
+
"agent",
|
|
1319
|
+
"role",
|
|
1320
|
+
"route"
|
|
1321
|
+
]
|
|
349
1322
|
}
|
|
350
1323
|
},
|
|
1324
|
+
diagnostics: {
|
|
1325
|
+
type: "array",
|
|
1326
|
+
description: "Configuration notes, including a panel whose entries share one route. Empty when nothing needs reporting.",
|
|
1327
|
+
items: { type: "string" }
|
|
1328
|
+
}
|
|
1329
|
+
},
|
|
1330
|
+
required: [
|
|
1331
|
+
"kind",
|
|
1332
|
+
"children",
|
|
1333
|
+
"diagnostics"
|
|
1334
|
+
]
|
|
1335
|
+
};
|
|
1336
|
+
/** Render one delegation result for the model. */
|
|
1337
|
+
function renderDelegationResult(result) {
|
|
1338
|
+
const lines = [
|
|
1339
|
+
result.kind === "activation" ? `started 1 child: ${result.children[0]?.childId ?? "(none)"}` : `started ${result.children.length} children:`,
|
|
1340
|
+
...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
|
+
"Results arrive as completion notices; do not poll for them. Each child can change its own",
|
|
1342
|
+
"effort from its next step with `set_reasoning_effort`; you do not need to predict it up front."
|
|
1343
|
+
];
|
|
1344
|
+
if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.map((diagnostic) => `- ${diagnostic}`));
|
|
1345
|
+
return lines.join("\n");
|
|
1346
|
+
}
|
|
1347
|
+
/** Build the model-facing tool for one row. */
|
|
1348
|
+
function createPstackSubagentTool(runtime, config = DEFAULT_CONFIG) {
|
|
1349
|
+
return {
|
|
1350
|
+
name: TOOL_NAME,
|
|
1351
|
+
description: TOOL_DESCRIPTION,
|
|
1352
|
+
parameters: buildParameters(config),
|
|
351
1353
|
output: {
|
|
352
|
-
schema:
|
|
1354
|
+
schema: OUTPUT_SCHEMA,
|
|
353
1355
|
render: (_args, value) => [{
|
|
354
1356
|
type: "text",
|
|
355
|
-
text: value
|
|
1357
|
+
text: renderDelegationResult(value)
|
|
356
1358
|
}]
|
|
357
1359
|
},
|
|
358
|
-
|
|
1360
|
+
isConcurrencySafe: () => true,
|
|
1361
|
+
async execute(rawArgs, exec) {
|
|
359
1362
|
const parent = exec.agent;
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
const agentInstructions = await deps.loadAgent(kind);
|
|
363
|
-
const prompt = composePrompt({
|
|
364
|
-
kind,
|
|
365
|
-
description: args.description,
|
|
366
|
-
prompt: args.prompt,
|
|
367
|
-
agentInstructions,
|
|
368
|
-
...deps.potetoModePath ? { potetoModePath: deps.potetoModePath } : {}
|
|
369
|
-
});
|
|
370
|
-
const { route, notes } = await deps.loadRole(args.role);
|
|
371
|
-
const started = `started subagent ${(await deps.start({
|
|
372
|
-
parent,
|
|
373
|
-
label: args.description,
|
|
374
|
-
prompt: [{
|
|
375
|
-
type: "text",
|
|
376
|
-
text: prompt
|
|
377
|
-
}],
|
|
378
|
-
signal: exec.signal,
|
|
379
|
-
route,
|
|
380
|
-
cwd: args.cwd
|
|
381
|
-
})).childId}`;
|
|
382
|
-
return notes.length > 0 ? [started, ...notes].join("\n") : started;
|
|
1363
|
+
if (parent === void 0) throw new DelegationError("pstack_subagent needs a calling Agent, and exec.agent was undefined. This tool runs inside a conversation; it cannot start a child from a context that has no parent Agent.");
|
|
1364
|
+
return await delegate(parent, parseDelegationArgs(rawArgs, config), runtime, config, exec.signal);
|
|
383
1365
|
}
|
|
384
|
-
}
|
|
1366
|
+
};
|
|
385
1367
|
}
|
|
386
|
-
/**
|
|
387
|
-
|
|
388
|
-
*
|
|
389
|
-
* A role is resolved once, at delegation time, into concrete provider options.
|
|
390
|
-
* The child inherits the parent route when the role is absent or resolves to
|
|
391
|
-
* `inherit-parent`, which is why the field is omitted rather than set to a
|
|
392
|
-
* placeholder.
|
|
393
|
-
*/
|
|
394
|
-
function buildActivationRequest(input) {
|
|
395
|
-
const options = routeToAgentOptions(input.route);
|
|
1368
|
+
/** Build the runtime a real Host call reads. */
|
|
1369
|
+
function hostRuntime(ctx, getAgentRegistry = () => void 0) {
|
|
396
1370
|
return {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
1371
|
+
subagents: ctx.subagents,
|
|
1372
|
+
llm: ctx.llm,
|
|
1373
|
+
get agents() {
|
|
1374
|
+
return getAgentRegistry();
|
|
1375
|
+
},
|
|
1376
|
+
dshHome: resolveDshHome(),
|
|
1377
|
+
assetsRoot: ASSETS_ROOT
|
|
401
1378
|
};
|
|
402
1379
|
}
|
|
403
1380
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
1381
|
+
* Register `pstack_subagent` for this row.
|
|
1382
|
+
*
|
|
1383
|
+
* @param ctx - context providing the tool registry, the subagent registry, the LLM
|
|
1384
|
+
* adapters, and optionally `ctx.get` for the agent registry.
|
|
1385
|
+
* @param config - validated row config.
|
|
406
1386
|
*/
|
|
407
|
-
function apply(ctx, config =
|
|
408
|
-
const
|
|
409
|
-
const tool = createPstackTool({
|
|
410
|
-
async start({ parent, label, prompt, signal, route, cwd }) {
|
|
411
|
-
const activation = await ctx.subagents.startActivation({
|
|
412
|
-
provider: providerName,
|
|
413
|
-
label,
|
|
414
|
-
signal,
|
|
415
|
-
delivery: "parent",
|
|
416
|
-
request: buildActivationRequest({
|
|
417
|
-
parent,
|
|
418
|
-
prompt,
|
|
419
|
-
route,
|
|
420
|
-
cwd
|
|
421
|
-
})
|
|
422
|
-
});
|
|
423
|
-
return { childId: String(activation.childId) };
|
|
424
|
-
},
|
|
425
|
-
loadAgent: readAgentInstructions,
|
|
426
|
-
loadRole: resolveRoleArg,
|
|
427
|
-
potetoModePath: POTETO_MODE_PATH,
|
|
428
|
-
...config.defaultAgent ? { defaultAgent: config.defaultAgent } : {},
|
|
429
|
-
...config.toolName ? { toolName: config.toolName } : {}
|
|
430
|
-
});
|
|
1387
|
+
function apply(ctx, config = DEFAULT_CONFIG) {
|
|
1388
|
+
const tool = createPstackSubagentTool(hostRuntime(ctx, () => ctx.get?.("agents")), config);
|
|
431
1389
|
ctx.tools.register(tool);
|
|
432
1390
|
}
|
|
1391
|
+
function describeError(error) {
|
|
1392
|
+
if (error instanceof Error) {
|
|
1393
|
+
const code = error.code;
|
|
1394
|
+
const suffix = typeof code === "string" ? ` (${code})` : "";
|
|
1395
|
+
return `${error.message}${suffix}`;
|
|
1396
|
+
}
|
|
1397
|
+
return String(error);
|
|
1398
|
+
}
|
|
433
1399
|
//#endregion
|
|
434
|
-
export { apply,
|
|
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 };
|
|
435
1401
|
|
|
436
1402
|
//# sourceMappingURL=subagent.mjs.map
|