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.
@@ -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 "./models.mjs";
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: string;
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-DZwTn2wF.mjs";
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 { a as resolveDshHome, c as ROLE_MANIFEST, l as findRoleSpec, n as normalizeRoleName, r as readRoleConfiguration, s as INHERIT_PARENT_ALIASES, t as modelsFilePath } from "./role-config-NNn5r1JX.mjs";
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
- function bodyStart(lines) {
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
- const lines = text.split("\n");
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
@@ -1,4 +1,4 @@
1
- import { i as PORT_ROOT } from "./paths-BT77khwm.mjs";
1
+ import { i as PORT_ROOT } from "./paths-yji9SNak.mjs";
2
2
  import { isAbsolute, join, resolve } from "node:path";
3
3
  import { spawn } from "node:child_process";
4
4
  // src/internal/script-registry.ts
@@ -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 { t as RoleSpec } from "./role-manifest-C3fUzjLI.mjs";
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-BT77khwm.mjs";
2
- import { a as resolveDshHome, i as requireRole, o as selectRoleRoutes, r as readRoleConfiguration, t as modelsFilePath } from "./role-config-NNn5r1JX.mjs";
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 known = [
222
- "description",
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 maxDepth = record["maxDepth"];
244
- if (!isNonNegativeSafeInteger(maxDepth)) throw new DelegationError("maxDepth must be a non-negative safe integer");
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, or cwd when that",
499
- "setting is not used. Choose agent explicitly. maxDepth is a non-negative integer. No argument",
500
- "has an implicit default.",
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: "Required non-negative delegation-depth limit. It can only lower this row's configured cap."
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.0",
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 };