dsh-pstack 0.2.0 → 0.2.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/README.md +19 -13
- package/README.zh.md +19 -13
- package/cordis.patch.yml +4 -0
- package/dist/client.mjs +116 -9
- package/dist/client.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{paths-BT77khwm.mjs → paths-yji9SNak.mjs} +0 -1
- package/dist/pstack/docs/harness/dsh.md +7 -7
- package/dist/remote.d.mts +1 -1
- package/dist/remote.mjs +2 -1
- package/dist/role-catalog.d.mts +35 -0
- package/dist/role-catalog.mjs +122 -0
- package/dist/{role-config-NNn5r1JX.mjs → role-config-a4JpVdc-.mjs} +107 -143
- package/dist/role-manifest-79cnFfDM.mjs +112 -0
- package/dist/role-manifest-8OERX9-s.d.mts +90 -0
- package/dist/{role-wire-DZwTn2wF.d.mts → role-wire-CAJsVA6e.d.mts} +2 -3
- package/dist/roles.d.mts +1 -1
- package/dist/roles.mjs +4 -34
- package/dist/script.mjs +1 -1
- package/dist/subagent.d.mts +25 -3
- package/dist/subagent.mjs +67 -37
- package/package.json +3 -2
- package/dist/role-manifest-C3fUzjLI.d.mts +0 -19
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/internal/role-manifest.ts
|
|
2
|
+
/**
|
|
3
|
+
* File name the role configuration lives under inside `$DSH_HOME`.
|
|
4
|
+
*
|
|
5
|
+
* It keeps the upstream name `pstack-models.mdc`, so a reader moving from the Cursor docs
|
|
6
|
+
* recognizes it, but stores it directly under `$DSH_HOME`; DSH does not load `.mdc` files
|
|
7
|
+
* on its own, and `pstack_subagent` is the only reader.
|
|
8
|
+
*/
|
|
9
|
+
const PSTACK_MODELS_FILENAME = "pstack-models.mdc";
|
|
10
|
+
/** Values that resolve to "send no `agentOptions` and inherit the parent route". */
|
|
11
|
+
const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
|
|
12
|
+
/** The 17 roles, in manifest order. */
|
|
13
|
+
const ROLE_MANIFEST = [
|
|
14
|
+
{
|
|
15
|
+
key: "feature, refactoring",
|
|
16
|
+
shape: "single",
|
|
17
|
+
autoPanel: false
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: "bug-fix",
|
|
21
|
+
shape: "single",
|
|
22
|
+
autoPanel: false
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: "perf-issue",
|
|
26
|
+
shape: "single",
|
|
27
|
+
autoPanel: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: "hillclimb",
|
|
31
|
+
shape: "single",
|
|
32
|
+
autoPanel: false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "judgment and prose",
|
|
36
|
+
shape: "single",
|
|
37
|
+
autoPanel: false
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: "hardest tasks",
|
|
41
|
+
shape: "single",
|
|
42
|
+
autoPanel: false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "how explorer",
|
|
46
|
+
shape: "single",
|
|
47
|
+
autoPanel: false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: "how explainer",
|
|
51
|
+
shape: "single",
|
|
52
|
+
autoPanel: false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "why investigators",
|
|
56
|
+
shape: "single",
|
|
57
|
+
autoPanel: false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "why synthesizer",
|
|
61
|
+
shape: "single",
|
|
62
|
+
autoPanel: false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "reflect tooling",
|
|
66
|
+
shape: "single",
|
|
67
|
+
autoPanel: false
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: "reflect judgment, divergent, synthesizer",
|
|
71
|
+
shape: "single",
|
|
72
|
+
autoPanel: false
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "arena runners",
|
|
76
|
+
shape: "list",
|
|
77
|
+
autoPanel: true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "arena cross-judge pool",
|
|
81
|
+
shape: "list",
|
|
82
|
+
autoPanel: false
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "swarm workers",
|
|
86
|
+
shape: "list",
|
|
87
|
+
autoPanel: false
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: "architect runners",
|
|
91
|
+
shape: "list",
|
|
92
|
+
autoPanel: true
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: "interrogate reviewers",
|
|
96
|
+
shape: "list",
|
|
97
|
+
autoPanel: true
|
|
98
|
+
}
|
|
99
|
+
];
|
|
100
|
+
function isRoleKey(value) {
|
|
101
|
+
return ROLE_MANIFEST.some((role) => role.key === value);
|
|
102
|
+
}
|
|
103
|
+
/** Normalize formatting while keeping commas that belong to a role name. */
|
|
104
|
+
function normalizeRoleName(raw) {
|
|
105
|
+
return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
|
|
106
|
+
}
|
|
107
|
+
/** Exact manifest lookup. Callers normalize the key first. */
|
|
108
|
+
function findRoleSpec(key) {
|
|
109
|
+
if (!isRoleKey(key)) return void 0;
|
|
110
|
+
return ROLE_MANIFEST.find((role) => role.key === key);
|
|
111
|
+
}
|
|
112
|
+
export { isRoleKey as a, findRoleSpec as i, PSTACK_MODELS_FILENAME as n, normalizeRoleName as o, ROLE_MANIFEST as r, INHERIT_PARENT_ALIASES as t };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one pstack role manifest.
|
|
3
|
+
*
|
|
4
|
+
* Keys are matched exactly after normalization, and commas belong to some keys
|
|
5
|
+
* (`feature, refactoring`, `reflect judgment, divergent, synthesizer`), so nothing here
|
|
6
|
+
* may split a key on a comma. A second literal role list anywhere is a bug.
|
|
7
|
+
*
|
|
8
|
+
* This module has no Node imports: the bundle imports it directly.
|
|
9
|
+
*/
|
|
10
|
+
/** Whether a role holds one route or a comma-separated list of routes. */
|
|
11
|
+
type RoleValueShape = "single" | "list";
|
|
12
|
+
interface RoleSpec {
|
|
13
|
+
/** Exact key as written in the role file. Commas belong to the key. */
|
|
14
|
+
readonly key: string;
|
|
15
|
+
readonly shape: RoleValueShape;
|
|
16
|
+
/** A list role that starts one child per entry automatically. */
|
|
17
|
+
readonly autoPanel: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** The 17 roles, in manifest order. */
|
|
20
|
+
declare const ROLE_MANIFEST: readonly [{
|
|
21
|
+
readonly key: "feature, refactoring";
|
|
22
|
+
readonly shape: "single";
|
|
23
|
+
readonly autoPanel: false;
|
|
24
|
+
}, {
|
|
25
|
+
readonly key: "bug-fix";
|
|
26
|
+
readonly shape: "single";
|
|
27
|
+
readonly autoPanel: false;
|
|
28
|
+
}, {
|
|
29
|
+
readonly key: "perf-issue";
|
|
30
|
+
readonly shape: "single";
|
|
31
|
+
readonly autoPanel: false;
|
|
32
|
+
}, {
|
|
33
|
+
readonly key: "hillclimb";
|
|
34
|
+
readonly shape: "single";
|
|
35
|
+
readonly autoPanel: false;
|
|
36
|
+
}, {
|
|
37
|
+
readonly key: "judgment and prose";
|
|
38
|
+
readonly shape: "single";
|
|
39
|
+
readonly autoPanel: false;
|
|
40
|
+
}, {
|
|
41
|
+
readonly key: "hardest tasks";
|
|
42
|
+
readonly shape: "single";
|
|
43
|
+
readonly autoPanel: false;
|
|
44
|
+
}, {
|
|
45
|
+
readonly key: "how explorer";
|
|
46
|
+
readonly shape: "single";
|
|
47
|
+
readonly autoPanel: false;
|
|
48
|
+
}, {
|
|
49
|
+
readonly key: "how explainer";
|
|
50
|
+
readonly shape: "single";
|
|
51
|
+
readonly autoPanel: false;
|
|
52
|
+
}, {
|
|
53
|
+
readonly key: "why investigators";
|
|
54
|
+
readonly shape: "single";
|
|
55
|
+
readonly autoPanel: false;
|
|
56
|
+
}, {
|
|
57
|
+
readonly key: "why synthesizer";
|
|
58
|
+
readonly shape: "single";
|
|
59
|
+
readonly autoPanel: false;
|
|
60
|
+
}, {
|
|
61
|
+
readonly key: "reflect tooling";
|
|
62
|
+
readonly shape: "single";
|
|
63
|
+
readonly autoPanel: false;
|
|
64
|
+
}, {
|
|
65
|
+
readonly key: "reflect judgment, divergent, synthesizer";
|
|
66
|
+
readonly shape: "single";
|
|
67
|
+
readonly autoPanel: false;
|
|
68
|
+
}, {
|
|
69
|
+
readonly key: "arena runners";
|
|
70
|
+
readonly shape: "list";
|
|
71
|
+
readonly autoPanel: true;
|
|
72
|
+
}, {
|
|
73
|
+
readonly key: "arena cross-judge pool";
|
|
74
|
+
readonly shape: "list";
|
|
75
|
+
readonly autoPanel: false;
|
|
76
|
+
}, {
|
|
77
|
+
readonly key: "swarm workers";
|
|
78
|
+
readonly shape: "list";
|
|
79
|
+
readonly autoPanel: false;
|
|
80
|
+
}, {
|
|
81
|
+
readonly key: "architect runners";
|
|
82
|
+
readonly shape: "list";
|
|
83
|
+
readonly autoPanel: true;
|
|
84
|
+
}, {
|
|
85
|
+
readonly key: "interrogate reviewers";
|
|
86
|
+
readonly shape: "list";
|
|
87
|
+
readonly autoPanel: true;
|
|
88
|
+
}];
|
|
89
|
+
type RoleKey = (typeof ROLE_MANIFEST)[number]["key"];
|
|
90
|
+
export { RoleSpec as n, RoleValueShape as r, RoleKey as t };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import { n as RoleValueShape } from "./role-manifest-C3fUzjLI.mjs";
|
|
1
|
+
import { r as RoleValueShape, t as RoleKey } from "./role-manifest-8OERX9-s.mjs";
|
|
3
2
|
/** Which spelling of "inherit the parent route" a role line uses. */
|
|
4
3
|
type InheritAlias = "inherit-parent" | "auto";
|
|
5
4
|
/** One role value as the configuration page sees it. */
|
|
@@ -14,7 +13,7 @@ type RouteView = {
|
|
|
14
13
|
};
|
|
15
14
|
/** One of the 17 roles in manifest order. */
|
|
16
15
|
interface RoleView {
|
|
17
|
-
readonly key:
|
|
16
|
+
readonly key: RoleKey;
|
|
18
17
|
readonly shape: RoleValueShape;
|
|
19
18
|
/** Whether the role file configures this role. */
|
|
20
19
|
readonly configured: boolean;
|
package/dist/roles.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CatalogueResult } from "./models.mjs";
|
|
2
|
-
import { n as SaveRoleRequest, r as SaveRoleResult, t as RoleFileSnapshot } from "./role-wire-
|
|
2
|
+
import { n as SaveRoleRequest, r as SaveRoleResult, t as RoleFileSnapshot } from "./role-wire-CAJsVA6e.mjs";
|
|
3
3
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
4
|
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
5
5
|
/** Read and write the role file for the Plugins configuration page. */
|
package/dist/roles.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { collectCatalogue } from "./models.mjs";
|
|
2
|
+
import { i as findRoleSpec, o as normalizeRoleName, r as ROLE_MANIFEST, t as INHERIT_PARENT_ALIASES } from "./role-manifest-79cnFfDM.mjs";
|
|
2
3
|
import { REMOTE_PACKAGE, ROLES_SERVICE, pstackRolesRemote } from "./remote.mjs";
|
|
3
|
-
import {
|
|
4
|
+
import { i as resolveDshHome, n as readRoleConfiguration, o as updateRoleDocument, t as modelsFilePath } from "./role-config-a4JpVdc-.mjs";
|
|
4
5
|
import { dirname } from "node:path";
|
|
5
6
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { Service } from "@deepseek-ai/cordis";
|
|
@@ -178,40 +179,9 @@ function validateSaveRequest(request, catalogue) {
|
|
|
178
179
|
function renderRoleValue(routes) {
|
|
179
180
|
return routes.map((route) => route.kind === "inherit" ? route.alias ?? "inherit-parent" : route.effort === void 0 ? `${route.provider}/${route.model}` : `${route.provider}/${route.model} [${route.effort}]`).join(", ");
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
-
if (lines[0]?.trim() !== "---") return 0;
|
|
183
|
-
for (let index = 1; index < lines.length; index += 1) if (lines[index]?.trim() === "---") return index + 1;
|
|
184
|
-
return 0;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Replace the winning line for one role, or append one when absent.
|
|
188
|
-
*
|
|
189
|
-
* Every other line is returned verbatim. A role configured twice keeps its
|
|
190
|
-
* earlier line and replaces the last one, which is the line the parser treats
|
|
191
|
-
* as winning; the duplicate remains visible as a per-line issue.
|
|
192
|
-
*/
|
|
182
|
+
/** Update the winning role through the same line grammar used by the parser. */
|
|
193
183
|
function updateRoleText(text, spec, value) {
|
|
194
|
-
|
|
195
|
-
const crlf = text.includes("\r\n");
|
|
196
|
-
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
197
|
-
let match = -1;
|
|
198
|
-
for (let index = bodyStart(lines); index < lines.length; index += 1) {
|
|
199
|
-
const line = lines[index];
|
|
200
|
-
const trimmed = line.endsWith("\r") ? line.slice(0, -1).trim() : line.trim();
|
|
201
|
-
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
202
|
-
const separator = trimmed.indexOf(":");
|
|
203
|
-
if (separator === -1) continue;
|
|
204
|
-
if (normalizeRoleName(trimmed.slice(0, separator)) !== spec.key) continue;
|
|
205
|
-
match = index;
|
|
206
|
-
}
|
|
207
|
-
const rendered = `${spec.key}: ${value}`;
|
|
208
|
-
if (match >= 0) {
|
|
209
|
-
const original = lines[match];
|
|
210
|
-
const indent = /^\s*/.exec(original)?.[0] ?? "";
|
|
211
|
-
const ending = original.endsWith("\r") ? "\r" : "";
|
|
212
|
-
lines[match] = `${indent}${rendered}${ending}`;
|
|
213
|
-
} else lines.push(`${rendered}${crlf ? "\r" : ""}`);
|
|
214
|
-
return `${lines.join("\n")}\n`;
|
|
184
|
+
return updateRoleDocument(text, spec, value);
|
|
215
185
|
}
|
|
216
186
|
function writeAtomically(path, text) {
|
|
217
187
|
mkdirSync(dirname(path), { recursive: true });
|
package/dist/script.mjs
CHANGED
package/dist/subagent.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as ToolsHost, g as ToolDefinition, h as SubagentsHost, m as SubagentCapabilities, n as AgentOptions, r as AgentsHost, s as LlmHost, t as Agent, v as WorkingDirectoryHost } from "./host-contract-CUPSpeNk.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { n as RoleSpec } from "./role-manifest-8OERX9-s.mjs";
|
|
3
3
|
/** One resolved child route. */
|
|
4
4
|
type Route = {
|
|
5
5
|
readonly kind: "inherit";
|
|
@@ -43,6 +43,13 @@ export interface PstackSubagentConfig {
|
|
|
43
43
|
readonly provider: string;
|
|
44
44
|
readonly maxDepth?: number;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Depth a child runs at when a call omits `maxDepth` or passes an unusable value.
|
|
48
|
+
*
|
|
49
|
+
* A child is always at least depth 1 (it is one level below its parent), so this is the
|
|
50
|
+
* smallest value that can start a child while forbidding it from delegating further.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEFAULT_CHILD_MAX_DEPTH = 1;
|
|
46
53
|
/** What a call asked for, after every required argument has been validated. */
|
|
47
54
|
export interface DelegationArgs {
|
|
48
55
|
readonly description: string;
|
|
@@ -50,7 +57,7 @@ export interface DelegationArgs {
|
|
|
50
57
|
readonly agent: AgentKind;
|
|
51
58
|
readonly role: string | null;
|
|
52
59
|
readonly cwd: string | null;
|
|
53
|
-
readonly maxDepth: number;
|
|
60
|
+
readonly maxDepth: number | null;
|
|
54
61
|
readonly roleIndex: number | null;
|
|
55
62
|
readonly reasoningEffort: string | null;
|
|
56
63
|
}
|
|
@@ -107,6 +114,13 @@ export declare function composeChildPrompt(input: {
|
|
|
107
114
|
}[];
|
|
108
115
|
readonly task: string;
|
|
109
116
|
}): string;
|
|
117
|
+
export declare const DELEGATION_ARGUMENT_KEYS: readonly ["description", "prompt", "agent", "role", "roleIndex", "reasoningEffort", "cwd", "maxDepth"];
|
|
118
|
+
/**
|
|
119
|
+
* Keys a call must pass. `maxDepth` is accepted but not required: a missing or unusable
|
|
120
|
+
* value resolves to `DEFAULT_CHILD_MAX_DEPTH` instead of forcing the caller to guess an
|
|
121
|
+
* integer, which is where the reflexive `0` came from.
|
|
122
|
+
*/
|
|
123
|
+
export declare const REQUIRED_DELEGATION_ARGUMENT_KEYS: readonly ["description", "prompt", "agent", "role", "roleIndex", "reasoningEffort", "cwd"];
|
|
110
124
|
/** Validate every required model-facing argument. Null declines an optional setting. */
|
|
111
125
|
export declare function parseDelegationArgs(raw: unknown): DelegationArgs;
|
|
112
126
|
/** One planned child, before any Host call. */
|
|
@@ -140,7 +154,15 @@ export interface DelegationPlan {
|
|
|
140
154
|
/** Route text as reported to the model. */
|
|
141
155
|
export declare function routeLabel(route: Route, effortOverride?: string): string;
|
|
142
156
|
/** Take the smallest of the requested depth, this row's cap, and the Host setting. */
|
|
143
|
-
export declare function resolveDelegationDepth(configured: number | undefined, requested: number | undefined, hostLimit: number | undefined): number | undefined;
|
|
157
|
+
export declare function resolveDelegationDepth(configured: number | undefined, requested: number | null | undefined, hostLimit: number | undefined): number | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Turn the per-call `maxDepth` request into an effective depth request.
|
|
160
|
+
*
|
|
161
|
+
* A child is always at least depth 1, so `0` can never start one: it is reported and
|
|
162
|
+
* replaced with `DEFAULT_CHILD_MAX_DEPTH` rather than failing the whole call. Omitting
|
|
163
|
+
* `maxDepth` (null) also uses the default, so a caller never has to guess an integer.
|
|
164
|
+
*/
|
|
165
|
+
export declare function requestedChildDepth(requested: number | null, diagnostics: string[]): number;
|
|
144
166
|
/** Resolve the role file, routes, prompt, and depth into one plan. Pure of service calls. */
|
|
145
167
|
export declare function planDelegation(args: DelegationArgs, runtime: SubagentRuntime, config?: PstackSubagentConfig): DelegationPlan;
|
|
146
168
|
/** Check the backend can enforce what this call asks for. */
|
package/dist/subagent.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as POTETO_MODE_SKILL_PATH } from "./paths-
|
|
2
|
-
import { a as
|
|
1
|
+
import { a as POTETO_MODE_SKILL_PATH } from "./paths-yji9SNak.mjs";
|
|
2
|
+
import { a as selectRoleRoutes, i as resolveDshHome, n as readRoleConfiguration, r as requireRole, t as modelsFilePath } from "./role-config-a4JpVdc-.mjs";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { existsSync, statSync } from "node:fs";
|
|
5
5
|
// src/internal/effort.ts
|
|
@@ -149,6 +149,13 @@ const AGENT_KINDS = [
|
|
|
149
149
|
"general"
|
|
150
150
|
];
|
|
151
151
|
const DEFAULT_CONFIG = { provider: DEFAULT_SUBAGENT_PROVIDER };
|
|
152
|
+
/**
|
|
153
|
+
* Depth a child runs at when a call omits `maxDepth` or passes an unusable value.
|
|
154
|
+
*
|
|
155
|
+
* A child is always at least depth 1 (it is one level below its parent), so this is the
|
|
156
|
+
* smallest value that can start a child while forbidding it from delegating further.
|
|
157
|
+
*/
|
|
158
|
+
const DEFAULT_CHILD_MAX_DEPTH = 1;
|
|
152
159
|
/** Raised for a configuration or argument problem, before any child starts. */
|
|
153
160
|
var DelegationError = class extends Error {
|
|
154
161
|
constructor(message) {
|
|
@@ -214,22 +221,36 @@ function composeChildPrompt(input) {
|
|
|
214
221
|
sections.push(`Task:\n\n${input.task}`);
|
|
215
222
|
return sections.join("\n\n---\n\n");
|
|
216
223
|
}
|
|
224
|
+
const DELEGATION_ARGUMENT_KEYS = [
|
|
225
|
+
"description",
|
|
226
|
+
"prompt",
|
|
227
|
+
"agent",
|
|
228
|
+
"role",
|
|
229
|
+
"roleIndex",
|
|
230
|
+
"reasoningEffort",
|
|
231
|
+
"cwd",
|
|
232
|
+
"maxDepth"
|
|
233
|
+
];
|
|
234
|
+
/**
|
|
235
|
+
* Keys a call must pass. `maxDepth` is accepted but not required: a missing or unusable
|
|
236
|
+
* value resolves to `DEFAULT_CHILD_MAX_DEPTH` instead of forcing the caller to guess an
|
|
237
|
+
* integer, which is where the reflexive `0` came from.
|
|
238
|
+
*/
|
|
239
|
+
const REQUIRED_DELEGATION_ARGUMENT_KEYS = [
|
|
240
|
+
"description",
|
|
241
|
+
"prompt",
|
|
242
|
+
"agent",
|
|
243
|
+
"role",
|
|
244
|
+
"roleIndex",
|
|
245
|
+
"reasoningEffort",
|
|
246
|
+
"cwd"
|
|
247
|
+
];
|
|
217
248
|
/** Validate every required model-facing argument. Null declines an optional setting. */
|
|
218
249
|
function parseDelegationArgs(raw) {
|
|
219
250
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new DelegationError("arguments must be an object");
|
|
220
251
|
const record = raw;
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
"prompt",
|
|
224
|
-
"agent",
|
|
225
|
-
"role",
|
|
226
|
-
"roleIndex",
|
|
227
|
-
"reasoningEffort",
|
|
228
|
-
"cwd",
|
|
229
|
-
"maxDepth"
|
|
230
|
-
];
|
|
231
|
-
for (const key of known) if (!Object.hasOwn(record, key)) throw new DelegationError(`missing required argument "${key}"; every argument must be passed explicitly`);
|
|
232
|
-
for (const key of Object.keys(record)) if (!known.includes(key)) throw new DelegationError(`unknown argument "${key}"; accepted arguments are ${known.join(", ")}`);
|
|
252
|
+
for (const key of REQUIRED_DELEGATION_ARGUMENT_KEYS) if (!Object.hasOwn(record, key)) throw new DelegationError(`missing required argument "${key}"; every argument except maxDepth must be passed explicitly`);
|
|
253
|
+
for (const key of Object.keys(record)) if (!DELEGATION_ARGUMENT_KEYS.includes(key)) throw new DelegationError(`unknown argument "${key}"; accepted arguments are ${DELEGATION_ARGUMENT_KEYS.join(", ")}`);
|
|
233
254
|
const description = record["description"];
|
|
234
255
|
if (!isNonEmptyString(description)) throw new DelegationError("description must be a non-empty string: it becomes the child label");
|
|
235
256
|
const prompt = record["prompt"];
|
|
@@ -240,8 +261,9 @@ function parseDelegationArgs(raw) {
|
|
|
240
261
|
if (role !== null && !isNonEmptyString(role)) throw new DelegationError("role must be a non-empty string or null");
|
|
241
262
|
const cwd = record["cwd"];
|
|
242
263
|
if (cwd !== null && !isNonEmptyString(cwd)) throw new DelegationError("cwd must be a non-empty string or null");
|
|
243
|
-
const
|
|
244
|
-
|
|
264
|
+
const rawMaxDepth = record["maxDepth"];
|
|
265
|
+
const maxDepth = rawMaxDepth === void 0 ? null : rawMaxDepth;
|
|
266
|
+
if (maxDepth !== null && !isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer, or null/omitted");
|
|
245
267
|
const roleIndex = record["roleIndex"];
|
|
246
268
|
if (roleIndex !== null && !isNonNegativeSafeInteger(roleIndex)) throw new DelegationError("roleIndex must be a non-negative safe integer or null");
|
|
247
269
|
if (roleIndex !== null && role === null) throw new DelegationError("roleIndex requires a non-null role");
|
|
@@ -267,15 +289,30 @@ function routeLabel(route, effortOverride) {
|
|
|
267
289
|
}
|
|
268
290
|
/** Take the smallest of the requested depth, this row's cap, and the Host setting. */
|
|
269
291
|
function resolveDelegationDepth(configured, requested, hostLimit) {
|
|
270
|
-
if (requested !== void 0 && !isNonNegativeSafeInteger(requested)) throw new DelegationError("maxDepth must be a non-negative safe integer");
|
|
271
|
-
if (configured !== void 0 && requested !== void 0 && requested > configured) throw new DelegationError(`maxDepth ${requested} exceeds this row's configured cap of ${configured}. Lower the request or raise \`pstack-subagent\` config.maxDepth.`);
|
|
292
|
+
if (requested !== void 0 && requested !== null && !isNonNegativeSafeInteger(requested)) throw new DelegationError("maxDepth must be a non-negative safe integer or null");
|
|
293
|
+
if (configured !== void 0 && requested !== void 0 && requested !== null && requested > configured) throw new DelegationError(`maxDepth ${requested} exceeds this row's configured cap of ${configured}. Lower the request or raise \`pstack-subagent\` config.maxDepth.`);
|
|
272
294
|
const candidates = [
|
|
273
295
|
configured,
|
|
274
296
|
requested,
|
|
275
297
|
hostLimit
|
|
276
|
-
].filter((value) => value !== void 0);
|
|
298
|
+
].filter((value) => value !== void 0 && value !== null);
|
|
277
299
|
return candidates.length === 0 ? void 0 : Math.min(...candidates);
|
|
278
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Turn the per-call `maxDepth` request into an effective depth request.
|
|
303
|
+
*
|
|
304
|
+
* A child is always at least depth 1, so `0` can never start one: it is reported and
|
|
305
|
+
* replaced with `DEFAULT_CHILD_MAX_DEPTH` rather than failing the whole call. Omitting
|
|
306
|
+
* `maxDepth` (null) also uses the default, so a caller never has to guess an integer.
|
|
307
|
+
*/
|
|
308
|
+
function requestedChildDepth(requested, diagnostics) {
|
|
309
|
+
if (requested === null) return 1;
|
|
310
|
+
if (requested === 0) {
|
|
311
|
+
diagnostics.push(`maxDepth 0 cannot start a child (the child itself is depth 1), so this call used 1. Omit maxDepth for that default, or pass 1 or more.`);
|
|
312
|
+
return 1;
|
|
313
|
+
}
|
|
314
|
+
return requested;
|
|
315
|
+
}
|
|
279
316
|
/** Resolve the role file, routes, prompt, and depth into one plan. Pure of service calls. */
|
|
280
317
|
function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
|
|
281
318
|
const rolePath = modelsFilePath(runtime.dshHome);
|
|
@@ -305,7 +342,7 @@ function planDelegation(args, runtime, config = DEFAULT_CONFIG) {
|
|
|
305
342
|
task: args.prompt
|
|
306
343
|
});
|
|
307
344
|
const agentPreset = agentPresetFor(args.agent);
|
|
308
|
-
const depth = resolveDelegationDepth(config.maxDepth, args.maxDepth, void 0);
|
|
345
|
+
const depth = resolveDelegationDepth(config.maxDepth, requestedChildDepth(args.maxDepth, diagnostics), void 0);
|
|
309
346
|
const total = routes.length;
|
|
310
347
|
const children = routes.map((route, index) => {
|
|
311
348
|
const agentOptions = agentOptionsFor(route, args.reasoningEffort ?? void 0);
|
|
@@ -495,9 +532,11 @@ async function delegate(parent, args, runtime, config = DEFAULT_CONFIG, signal =
|
|
|
495
532
|
const TOOL_DESCRIPTION = [
|
|
496
533
|
"Delegate self-contained work to one or more fresh child agents.",
|
|
497
534
|
"",
|
|
498
|
-
"Every argument is required. Pass null for role, roleIndex, reasoningEffort,
|
|
499
|
-
"setting is not used. Choose agent explicitly. maxDepth is
|
|
500
|
-
"
|
|
535
|
+
"Every argument is required except maxDepth. Pass null for role, roleIndex, reasoningEffort,",
|
|
536
|
+
"or cwd when that setting is not used. Choose agent explicitly. maxDepth is optional: omit it",
|
|
537
|
+
"(or pass null) for the safe default of 1, where the child exists but cannot start its own",
|
|
538
|
+
"children; pass 2 or more, within this row's configured cap, to let it delegate further. A 0 is",
|
|
539
|
+
"treated as 1, because a child is already depth 1 and 0 can never start one.",
|
|
501
540
|
"",
|
|
502
541
|
"Returns the id of every child it started plus the route each one resolved. The children run",
|
|
503
542
|
"in the background: their results arrive later as completion notices in this conversation.",
|
|
@@ -546,28 +585,19 @@ function buildParameters() {
|
|
|
546
585
|
description: "Required initial working directory, inside this session's directory or a separate Git worktree. Relative paths resolve against this session's directory. Null inherits the session directory."
|
|
547
586
|
},
|
|
548
587
|
maxDepth: {
|
|
549
|
-
type: "integer",
|
|
550
|
-
description: "
|
|
588
|
+
type: ["integer", "null"],
|
|
589
|
+
description: "Optional delegation-depth cap for the child. Omit or pass null for the safe default of 1 (the child exists but cannot start its own children). Pass 2 or more, within this row's configured cap, to let it delegate further. 0 is treated as 1: a child is already depth 1, so 0 can never start one."
|
|
551
590
|
},
|
|
552
591
|
roleIndex: {
|
|
553
592
|
type: ["integer", "null"],
|
|
554
|
-
description: "Required entry index for a list-valued role that is not an automatic panel, 0 or greater. Requires a non-null role. Null selects the first entry."
|
|
593
|
+
description: "Required entry index for a list-valued role that is not an automatic panel, 0 or greater. Requires a non-null role. Null selects the first entry. Call `pstack_roles` to see which roles accept an index."
|
|
555
594
|
},
|
|
556
595
|
reasoningEffort: {
|
|
557
596
|
type: ["string", "null"],
|
|
558
597
|
description: "Required reasoning-effort override for every child this call starts. Use one token the target model advertises, or null to keep the role line's starting effort. An unknown token fails the whole call before any child starts."
|
|
559
598
|
}
|
|
560
599
|
},
|
|
561
|
-
required: [
|
|
562
|
-
"description",
|
|
563
|
-
"prompt",
|
|
564
|
-
"agent",
|
|
565
|
-
"role",
|
|
566
|
-
"roleIndex",
|
|
567
|
-
"reasoningEffort",
|
|
568
|
-
"cwd",
|
|
569
|
-
"maxDepth"
|
|
570
|
-
]
|
|
600
|
+
required: [...REQUIRED_DELEGATION_ARGUMENT_KEYS]
|
|
571
601
|
};
|
|
572
602
|
}
|
|
573
603
|
const OUTPUT_SCHEMA = {
|
|
@@ -694,4 +724,4 @@ function describeError(error) {
|
|
|
694
724
|
}
|
|
695
725
|
return String(error);
|
|
696
726
|
}
|
|
697
|
-
export { AGENT_KINDS, AGENT_PRESETS, DEFAULT_SUBAGENT_PROVIDER, DelegationError, PartialDelegationError, TOOL_NAME, agentPresetFor, apply, assertProviderCapabilities, composeChildPrompt, createPstackSubagentTool, delegate, hostRuntime, inject, name, normalizeConfig, parseDelegationArgs, planDelegation, renderDelegationResult, resolveChildCwd, resolveDelegationDepth, routeLabel, startPlannedChildren };
|
|
727
|
+
export { AGENT_KINDS, AGENT_PRESETS, DEFAULT_CHILD_MAX_DEPTH, DEFAULT_SUBAGENT_PROVIDER, DELEGATION_ARGUMENT_KEYS, DelegationError, PartialDelegationError, REQUIRED_DELEGATION_ARGUMENT_KEYS, TOOL_NAME, agentPresetFor, apply, assertProviderCapabilities, composeChildPrompt, createPstackSubagentTool, delegate, hostRuntime, inject, name, normalizeConfig, parseDelegationArgs, planDelegation, renderDelegationResult, requestedChildDepth, resolveChildCwd, resolveDelegationDepth, routeLabel, startPlannedChildren };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pstack",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "pstack for DeepSeek Harness: rigorous agent workflows ported from cursor/plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
".": "./dist/index.mjs",
|
|
39
39
|
"./models": "./dist/models.mjs",
|
|
40
40
|
"./remote": "./dist/remote.mjs",
|
|
41
|
+
"./role-catalog": "./dist/role-catalog.mjs",
|
|
41
42
|
"./roles": "./dist/roles.mjs",
|
|
42
43
|
"./script": "./dist/script.mjs",
|
|
43
44
|
"./skill": "./dist/skill.mjs",
|
|
@@ -130,7 +131,7 @@
|
|
|
130
131
|
}
|
|
131
132
|
],
|
|
132
133
|
"scripts": {
|
|
133
|
-
"prerelease": "vp run -r check && vp run -r test",
|
|
134
|
+
"prerelease": "vp run build && vp run -r check && vp run typecheck && vp run -r test",
|
|
134
135
|
"release": "bumpp"
|
|
135
136
|
}
|
|
136
137
|
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The one pstack role manifest.
|
|
3
|
-
*
|
|
4
|
-
* Keys are matched exactly after normalization, and commas belong to some keys
|
|
5
|
-
* (`feature, refactoring`, `reflect judgment, divergent, synthesizer`), so nothing here
|
|
6
|
-
* may split a key on a comma. A second literal role list anywhere is a bug.
|
|
7
|
-
*
|
|
8
|
-
* This module has no Node imports: the bundle imports it directly.
|
|
9
|
-
*/
|
|
10
|
-
/** Whether a role holds one route or a comma-separated list of routes. */
|
|
11
|
-
type RoleValueShape = "single" | "list";
|
|
12
|
-
interface RoleSpec {
|
|
13
|
-
/** Exact key as written in the role file. Commas belong to the key. */
|
|
14
|
-
readonly key: string;
|
|
15
|
-
readonly shape: RoleValueShape;
|
|
16
|
-
/** A list role that starts one child per entry automatically. */
|
|
17
|
-
readonly autoPanel: boolean;
|
|
18
|
-
}
|
|
19
|
-
export { RoleValueShape as n, RoleSpec as t };
|