dsh-pstack 0.2.0 → 0.3.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.
@@ -1,108 +1,97 @@
1
+ import { i as findRoleSpec, n as PSTACK_MODELS_FILENAME, o as normalizeRoleName, r as ROLE_MANIFEST, t as INHERIT_PARENT_ALIASES } from "./role-manifest-79cnFfDM.mjs";
1
2
  import { join, resolve } from "node:path";
2
3
  import { readFileSync } from "node:fs";
3
4
  import { homedir } from "node:os";
4
- // src/internal/role-manifest.ts
5
- /**
6
- * File name the role configuration lives under inside `$DSH_HOME`.
7
- *
8
- * It keeps the upstream name `pstack-models.mdc`, so a reader moving from the Cursor docs
9
- * recognizes it, but stores it directly under `$DSH_HOME`; DSH does not load `.mdc` files
10
- * on its own, and `pstack_subagent` is the only reader.
11
- */
12
- const PSTACK_MODELS_FILENAME = "pstack-models.mdc";
13
- /** Values that resolve to "send no `agentOptions` and inherit the parent route". */
14
- const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
15
- /** The 17 roles, in manifest order. */
16
- const ROLE_MANIFEST = [
17
- {
18
- key: "feature, refactoring",
19
- shape: "single",
20
- autoPanel: false
21
- },
22
- {
23
- key: "bug-fix",
24
- shape: "single",
25
- autoPanel: false
26
- },
27
- {
28
- key: "perf-issue",
29
- shape: "single",
30
- autoPanel: false
31
- },
32
- {
33
- key: "hillclimb",
34
- shape: "single",
35
- autoPanel: false
36
- },
37
- {
38
- key: "judgment and prose",
39
- shape: "single",
40
- autoPanel: false
41
- },
42
- {
43
- key: "hardest tasks",
44
- shape: "single",
45
- autoPanel: false
46
- },
47
- {
48
- key: "how explorer",
49
- shape: "single",
50
- autoPanel: false
51
- },
52
- {
53
- key: "how explainer",
54
- shape: "single",
55
- autoPanel: false
56
- },
57
- {
58
- key: "why investigators",
59
- shape: "single",
60
- autoPanel: false
61
- },
62
- {
63
- key: "why synthesizer",
64
- shape: "single",
65
- autoPanel: false
66
- },
67
- {
68
- key: "reflect tooling",
69
- shape: "single",
70
- autoPanel: false
71
- },
72
- {
73
- key: "reflect judgment, divergent, synthesizer",
74
- shape: "single",
75
- autoPanel: false
76
- },
77
- {
78
- key: "arena runners",
79
- shape: "list",
80
- autoPanel: true
81
- },
82
- {
83
- key: "arena cross-judge pool",
84
- shape: "list",
85
- autoPanel: false
86
- },
87
- {
88
- key: "swarm workers",
89
- shape: "list",
90
- autoPanel: false
91
- },
92
- {
93
- key: "architect runners",
94
- shape: "list",
95
- autoPanel: true
96
- },
97
- {
98
- key: "interrogate reviewers",
99
- shape: "list",
100
- autoPanel: true
5
+ // src/internal/role-grammar.ts
6
+ function frontmatterBodyStart(lines) {
7
+ if (lines[0]?.trim() !== "---") return 0;
8
+ for (let index = 1; index < lines.length; index += 1) if (lines[index]?.trim() === "---") return index + 1;
9
+ return 0;
10
+ }
11
+ /** One line grammar for role parsing and byte-preserving updates. */
12
+ function scanRoleDocument(text) {
13
+ const lines = text.split("\n");
14
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
15
+ const bodyStart = frontmatterBodyStart(lines);
16
+ const scanned = [];
17
+ let offset = 0;
18
+ for (let index = 0; index < lines.length; index += 1) {
19
+ const rawLine = lines[index];
20
+ const line = index + 1 - bodyStart;
21
+ const contentEnd = offset + rawLine.length - (rawLine.endsWith("\r") ? 1 : 0);
22
+ if (index < bodyStart) {
23
+ scanned.push({
24
+ kind: "blank",
25
+ line
26
+ });
27
+ offset += rawLine.length + 1;
28
+ continue;
29
+ }
30
+ const trimmed = rawLine.trim();
31
+ if (trimmed === "") scanned.push({
32
+ kind: "blank",
33
+ line
34
+ });
35
+ else if (trimmed.startsWith("#")) {
36
+ const match = /^#\s*budget:\s*(.+)$/i.exec(trimmed);
37
+ scanned.push({
38
+ kind: "comment",
39
+ line,
40
+ ...match === null ? {} : { budget: match[1].trim() }
41
+ });
42
+ } else {
43
+ const separator = trimmed.indexOf(":");
44
+ if (separator === -1) scanned.push({
45
+ kind: "malformed",
46
+ line,
47
+ rawLine: trimmed
48
+ });
49
+ else {
50
+ const rawKey = trimmed.slice(0, separator).trim();
51
+ const key = normalizeRoleName(rawKey);
52
+ const spec = findRoleSpec(key);
53
+ if (key === "") scanned.push({
54
+ kind: "malformed",
55
+ line,
56
+ rawLine: ""
57
+ });
58
+ else if (spec === void 0) scanned.push({
59
+ kind: "unknown-role",
60
+ line,
61
+ rawKey
62
+ });
63
+ else {
64
+ const valueText = rawLine.slice(0, contentEnd - offset);
65
+ const sourceSeparator = valueText.indexOf(":");
66
+ const afterSeparator = valueText.slice(sourceSeparator + 1);
67
+ const rawValue = afterSeparator.trim();
68
+ const valueLeading = rawValue === "" ? 0 : afterSeparator.length - afterSeparator.trimStart().length;
69
+ const valueTrailing = rawValue === "" ? 0 : afterSeparator.length - afterSeparator.trimEnd().length;
70
+ const valueStart = offset + sourceSeparator + 1 + valueLeading;
71
+ const valueEnd = contentEnd - valueTrailing;
72
+ scanned.push({
73
+ kind: "role",
74
+ line,
75
+ rawKey,
76
+ key,
77
+ rawValue,
78
+ spec,
79
+ valueStart,
80
+ valueEnd
81
+ });
82
+ }
83
+ }
84
+ }
85
+ offset += rawLine.length + 1;
101
86
  }
102
- ];
103
- /** Exact manifest lookup. Callers normalize the key first. */
104
- function findRoleSpec(key) {
105
- return ROLE_MANIFEST.find((role) => role.key === key);
87
+ return { lines: scanned };
88
+ }
89
+ /** Replace only the winning role's value, retaining every other source byte. */
90
+ function updateRoleDocument(text, spec, value) {
91
+ const winning = scanRoleDocument(text).lines.findLast((line) => line.kind === "role" && line.key === spec.key);
92
+ if (winning?.kind === "role") return `${text.slice(0, winning.valueStart)}${value}${text.slice(winning.valueEnd)}`;
93
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
94
+ return `${text}${text === "" || text.endsWith("\n") ? "" : eol}${spec.key}: ${value}${eol}`;
106
95
  }
107
96
  // src/internal/role-config.ts
108
97
  /**
@@ -149,49 +138,36 @@ function parseRoleConfiguration(text, path) {
149
138
  const issues = [];
150
139
  const roles = /* @__PURE__ */ new Map();
151
140
  let budget;
152
- stripFrontmatter(text).split("\n").forEach((rawLine, index) => {
153
- const line = index + 1;
154
- const trimmed = rawLine.trim();
155
- if (trimmed === "") return;
156
- if (trimmed.startsWith("#")) {
157
- const match = /^#\s*budget:\s*(.+)$/i.exec(trimmed);
158
- if (match !== null) budget = match[1].trim();
159
- return;
160
- }
161
- const separator = trimmed.indexOf(":");
162
- if (separator === -1) {
163
- issues.push({
164
- line,
165
- message: `expected \`<role>: <value>\`, found ${JSON.stringify(trimmed)}`
166
- });
167
- return;
141
+ for (const scanned of scanRoleDocument(text).lines) {
142
+ const { line } = scanned;
143
+ if (scanned.kind === "blank") continue;
144
+ if (scanned.kind === "comment") {
145
+ if (scanned.budget !== void 0) budget = scanned.budget;
146
+ continue;
168
147
  }
169
- const rawKey = trimmed.slice(0, separator).trim();
170
- const rawValue = trimmed.slice(separator + 1).trim();
171
- const key = normalizeRoleName(rawKey);
172
- if (key === "") {
148
+ if (scanned.kind === "malformed") {
173
149
  issues.push({
174
150
  line,
175
- message: "the role key is empty"
151
+ message: scanned.rawLine === "" ? "the role key is empty" : `expected \`<role>: <value>\`, found ${JSON.stringify(scanned.rawLine)}`
176
152
  });
177
- return;
153
+ continue;
178
154
  }
179
- const spec = findRoleSpec(key);
180
- if (spec === void 0) {
155
+ if (scanned.kind === "unknown-role") {
181
156
  issues.push({
182
157
  line,
183
- message: unknownRoleMessage(rawKey, key)
158
+ message: unknownRoleMessage(scanned.rawKey, normalizeRoleName(scanned.rawKey))
184
159
  });
185
- return;
160
+ continue;
186
161
  }
187
- const parsed = parseRouteValue(rawValue);
162
+ const { rawKey, spec } = scanned;
163
+ const parsed = parseRouteValue(scanned.rawValue);
188
164
  const entryIssues = [...parsed.issues];
189
165
  if (spec.shape === "single" && parsed.routes.length > 1) entryIssues.push("a single-route role cannot hold a comma-separated list");
190
166
  for (const issue of entryIssues) issues.push({
191
167
  line,
192
168
  message: `${rawKey}: ${issue}`
193
169
  });
194
- if (parsed.routes.length === 0) return;
170
+ if (parsed.routes.length === 0) continue;
195
171
  const previous = roles.get(spec.key);
196
172
  if (previous !== void 0) issues.push({
197
173
  line,
@@ -205,7 +181,7 @@ function parseRoleConfiguration(text, path) {
205
181
  routes: parsed.routes,
206
182
  issues: entryIssues
207
183
  });
208
- });
184
+ }
209
185
  return budget === void 0 ? {
210
186
  status: "loaded",
211
187
  path,
@@ -335,10 +311,6 @@ var RoleNameError = class extends Error {
335
311
  this.name = "RoleNameError";
336
312
  }
337
313
  };
338
- /** Normalize a role name for lookup. Case, spacing, spacing around a comma, and a trailing colon are formatting. */
339
- function normalizeRoleName(raw) {
340
- return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
341
- }
342
314
  /** Resolve a role name, or throw a diagnostic that names the near misses. */
343
315
  function requireRole(raw) {
344
316
  const spec = findRoleSpec(normalizeRoleName(raw));
@@ -394,14 +366,6 @@ function editDistance(left, right) {
394
366
  }
395
367
  return previous[right.length];
396
368
  }
397
- function stripFrontmatter(text) {
398
- const normalized = text.replaceAll("\r\n", "\n");
399
- if (!normalized.startsWith("---\n")) return normalized;
400
- const end = normalized.indexOf("\n---", 3);
401
- if (end === -1) return normalized;
402
- const after = normalized.indexOf("\n", end + 1);
403
- return after === -1 ? "" : normalized.slice(after + 1);
404
- }
405
369
  function isMissingFile(error) {
406
370
  return typeof error === "object" && error !== null && error.code === "ENOENT";
407
371
  }
@@ -412,4 +376,4 @@ function describeError(error) {
412
376
  }
413
377
  return String(error);
414
378
  }
415
- export { resolveDshHome as a, ROLE_MANIFEST as c, requireRole as i, findRoleSpec as l, normalizeRoleName as n, selectRoleRoutes as o, readRoleConfiguration as r, INHERIT_PARENT_ALIASES as s, modelsFilePath as t };
379
+ export { selectRoleRoutes as a, resolveDshHome as i, readRoleConfiguration as n, updateRoleDocument as o, requireRole as r, modelsFilePath as t };
@@ -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. */