dsh-pstack 0.2.0-beta.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/README.zh.md +18 -5
- package/assets/pstack-config-entry.png +0 -0
- package/cordis.patch.yml +8 -0
- package/dist/client.mjs +2341 -0
- package/dist/client.mjs.map +1 -0
- package/dist/remote.d.mts +27 -0
- package/dist/remote.mjs +234 -0
- package/dist/role-config-NNn5r1JX.mjs +415 -0
- package/dist/role-manifest-C3fUzjLI.d.mts +19 -0
- package/dist/role-wire-DZwTn2wF.d.mts +55 -0
- package/dist/roles.d.mts +18 -0
- package/dist/roles.mjs +323 -0
- package/dist/skills/authored/decision-audit/SKILL.md +21 -17
- package/dist/skills/authored/decision-audit/SKILL.zh.md +21 -17
- package/dist/skills/authored/decision-audit/references/recover-commit.md +3 -3
- package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +3 -3
- package/dist/skills/authored/decision-write/SKILL.md +11 -9
- package/dist/skills/authored/decision-write/SKILL.zh.md +11 -9
- package/dist/skills/authored/decision-write/references/land.md +3 -3
- package/dist/skills/authored/decision-write/references/land.zh.md +3 -3
- package/dist/skills/authored/decision-write/references/scope.md +16 -0
- package/dist/skills/authored/decision-write/references/scope.zh.md +16 -0
- package/dist/skills/authored/docs-drift/SKILL.md +120 -0
- package/dist/skills/authored/docs-drift/SKILL.zh.md +120 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +14 -9
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +14 -9
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +3 -1
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +3 -1
- package/dist/subagent.d.mts +3 -18
- package/dist/subagent.mjs +5 -412
- package/package.json +32 -2
package/dist/subagent.d.mts
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
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
|
-
|
|
3
|
-
* The one pstack role manifest.
|
|
4
|
-
*
|
|
5
|
-
* Keys are matched exactly after normalization, and commas belong to some keys
|
|
6
|
-
* (`feature, refactoring`, `reflect judgment, divergent, synthesizer`), so nothing here
|
|
7
|
-
* may split a key on a comma. A second literal role list anywhere is a bug.
|
|
8
|
-
*
|
|
9
|
-
* This module has no Node imports: the bundle imports it directly.
|
|
10
|
-
*/
|
|
11
|
-
/** Whether a role holds one route or a comma-separated list of routes. */
|
|
12
|
-
type RoleValueShape = "single" | "list";
|
|
13
|
-
interface RoleSpec {
|
|
14
|
-
/** Exact key as written in the role file. Commas belong to the key. */
|
|
15
|
-
readonly key: string;
|
|
16
|
-
readonly shape: RoleValueShape;
|
|
17
|
-
/** A list role that starts one child per entry automatically. */
|
|
18
|
-
readonly autoPanel: boolean;
|
|
19
|
-
}
|
|
2
|
+
import { t as RoleSpec } from "./role-manifest-C3fUzjLI.mjs";
|
|
20
3
|
/** One resolved child route. */
|
|
21
4
|
type Route = {
|
|
22
5
|
readonly kind: "inherit";
|
|
6
|
+
/** The spelling the file used; `undefined` for an implicit fallback. */
|
|
7
|
+
readonly alias?: "inherit-parent" | "auto";
|
|
23
8
|
} | {
|
|
24
9
|
readonly kind: "route";
|
|
25
10
|
readonly provider: string;
|
package/dist/subagent.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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";
|
|
2
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
import { existsSync,
|
|
4
|
-
import { homedir } from "node:os";
|
|
4
|
+
import { existsSync, statSync } from "node:fs";
|
|
5
5
|
// src/internal/effort.ts
|
|
6
6
|
/** Model-facing name of the child's own control. */
|
|
7
7
|
const SET_EFFORT_TOOL = "set_reasoning_effort";
|
|
@@ -30,7 +30,7 @@ async function validateEffortFor(llm, route, effort, parent, signal) {
|
|
|
30
30
|
try {
|
|
31
31
|
efforts = await advertisedEfforts(llm, target, signal);
|
|
32
32
|
} catch (error) {
|
|
33
|
-
throw new Error(`route ${target.provider}/${target.model} could not be resolved for reasoningEffort: ${describeError$
|
|
33
|
+
throw new Error(`route ${target.provider}/${target.model} could not be resolved for reasoningEffort: ${describeError$1(error)}`, { cause: error });
|
|
34
34
|
}
|
|
35
35
|
if (efforts.length === 0) throw new Error(`${target.provider}/${target.model} advertises no reasoning efforts, so it cannot take reasoningEffort "${effort}". Omit the argument and let the adapter use its own default.`);
|
|
36
36
|
if (!efforts.includes(effort)) throw new Error(`${target.provider}/${target.model} does not support reasoning effort "${effort}". It supports: ${efforts.join(", ")}.`);
|
|
@@ -83,7 +83,7 @@ function defineSetEffortTool(child, llm) {
|
|
|
83
83
|
try {
|
|
84
84
|
efforts = await advertisedEfforts(llm, target, exec.signal);
|
|
85
85
|
} catch (error) {
|
|
86
|
-
throw new Error(`${target.provider}/${target.model} could not be resolved for reasoning effort: ${describeError$
|
|
86
|
+
throw new Error(`${target.provider}/${target.model} could not be resolved for reasoning effort: ${describeError$1(error)}`, { cause: error });
|
|
87
87
|
}
|
|
88
88
|
if (efforts.length === 0) throw new Error(`${target.provider}/${target.model} advertises no reasoning efforts, so this child has none to change. Your current effort stays as it is.`);
|
|
89
89
|
if (!efforts.includes(value)) throw new Error(`${target.provider}/${target.model} does not support reasoning effort "${value}", so nothing changed. It supports: ${efforts.join(", ")}.`);
|
|
@@ -110,417 +110,10 @@ function agentOptionsFor(route, effortOverride) {
|
|
|
110
110
|
function effectiveEffort(route, effortOverride) {
|
|
111
111
|
return effortOverride ?? (route.kind === "route" ? route.effort : void 0);
|
|
112
112
|
}
|
|
113
|
-
function describeError$2(error) {
|
|
114
|
-
if (error instanceof Error) {
|
|
115
|
-
const code = error.code;
|
|
116
|
-
return typeof code === "string" ? `${error.message} (${code})` : error.message;
|
|
117
|
-
}
|
|
118
|
-
return String(error);
|
|
119
|
-
}
|
|
120
|
-
// src/internal/role-manifest.ts
|
|
121
|
-
/**
|
|
122
|
-
* File name the role configuration lives under inside `$DSH_HOME`.
|
|
123
|
-
*
|
|
124
|
-
* It keeps the upstream name `pstack-models.mdc`, so a reader moving from the Cursor docs
|
|
125
|
-
* recognizes it, but stores it directly under `$DSH_HOME`; DSH does not load `.mdc` files
|
|
126
|
-
* on its own, and `pstack_subagent` is the only reader.
|
|
127
|
-
*/
|
|
128
|
-
const PSTACK_MODELS_FILENAME = "pstack-models.mdc";
|
|
129
|
-
/** Values that resolve to "send no `agentOptions` and inherit the parent route". */
|
|
130
|
-
const INHERIT_PARENT_ALIASES = ["inherit-parent", "auto"];
|
|
131
|
-
/** The 17 roles, in manifest order. */
|
|
132
|
-
const ROLE_MANIFEST = [
|
|
133
|
-
{
|
|
134
|
-
key: "feature, refactoring",
|
|
135
|
-
shape: "single",
|
|
136
|
-
autoPanel: false
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
key: "bug-fix",
|
|
140
|
-
shape: "single",
|
|
141
|
-
autoPanel: false
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
key: "perf-issue",
|
|
145
|
-
shape: "single",
|
|
146
|
-
autoPanel: false
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
key: "hillclimb",
|
|
150
|
-
shape: "single",
|
|
151
|
-
autoPanel: false
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
key: "judgment and prose",
|
|
155
|
-
shape: "single",
|
|
156
|
-
autoPanel: false
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
key: "hardest tasks",
|
|
160
|
-
shape: "single",
|
|
161
|
-
autoPanel: false
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
key: "how explorer",
|
|
165
|
-
shape: "single",
|
|
166
|
-
autoPanel: false
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
key: "how explainer",
|
|
170
|
-
shape: "single",
|
|
171
|
-
autoPanel: false
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
key: "why investigators",
|
|
175
|
-
shape: "single",
|
|
176
|
-
autoPanel: false
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
key: "why synthesizer",
|
|
180
|
-
shape: "single",
|
|
181
|
-
autoPanel: false
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
key: "reflect tooling",
|
|
185
|
-
shape: "single",
|
|
186
|
-
autoPanel: false
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
key: "reflect judgment, divergent, synthesizer",
|
|
190
|
-
shape: "single",
|
|
191
|
-
autoPanel: false
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
key: "arena runners",
|
|
195
|
-
shape: "list",
|
|
196
|
-
autoPanel: true
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
key: "arena cross-judge pool",
|
|
200
|
-
shape: "list",
|
|
201
|
-
autoPanel: false
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
key: "swarm workers",
|
|
205
|
-
shape: "list",
|
|
206
|
-
autoPanel: false
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
key: "architect runners",
|
|
210
|
-
shape: "list",
|
|
211
|
-
autoPanel: true
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
key: "interrogate reviewers",
|
|
215
|
-
shape: "list",
|
|
216
|
-
autoPanel: true
|
|
217
|
-
}
|
|
218
|
-
];
|
|
219
|
-
/** Exact manifest lookup. Callers normalize the key first. */
|
|
220
|
-
function findRoleSpec(key) {
|
|
221
|
-
return ROLE_MANIFEST.find((role) => role.key === key);
|
|
222
|
-
}
|
|
223
|
-
// src/internal/role-config.ts
|
|
224
|
-
/**
|
|
225
|
-
* The pstack role configuration: `$DSH_HOME/pstack-models.mdc`.
|
|
226
|
-
*
|
|
227
|
-
* `pstack_subagent` reads this file again on every delegation, so a change reaches the
|
|
228
|
-
* next child and never a child that already started.
|
|
229
|
-
*
|
|
230
|
-
* Three states stay apart because each needs a different fix: the file is missing (no
|
|
231
|
-
* policy, so every role inherits the parent route), unreadable (a broken install, never
|
|
232
|
-
* treated as "no configuration"), or loaded with per-line issues reported by line.
|
|
233
|
-
*/
|
|
234
|
-
/** Absolute path of the role file for one DSH home. */
|
|
235
|
-
function modelsFilePath(dshHome) {
|
|
236
|
-
return join(dshHome, PSTACK_MODELS_FILENAME);
|
|
237
|
-
}
|
|
238
|
-
/** Resolve `$DSH_HOME` the way the Host does: the environment when set, else `~/.dsh`. */
|
|
239
|
-
function resolveDshHome(env = process.env, home = homedir()) {
|
|
240
|
-
const configured = env["DSH_HOME"];
|
|
241
|
-
const path = configured !== void 0 && configured.trim() !== "" ? configured : join(home, ".dsh");
|
|
242
|
-
const expanded = path === "~" ? home : path.startsWith("~/") || path.startsWith("~\\") ? join(home, path.slice(2)) : path;
|
|
243
|
-
return resolve(expanded);
|
|
244
|
-
}
|
|
245
|
-
/** Read the role file. Never throws for a missing or unreadable file. */
|
|
246
|
-
function readRoleConfiguration(path) {
|
|
247
|
-
let text;
|
|
248
|
-
try {
|
|
249
|
-
text = readFileSync(path, "utf8");
|
|
250
|
-
} catch (error) {
|
|
251
|
-
if (isMissingFile(error)) return {
|
|
252
|
-
status: "missing",
|
|
253
|
-
path
|
|
254
|
-
};
|
|
255
|
-
return {
|
|
256
|
-
status: "unreadable",
|
|
257
|
-
path,
|
|
258
|
-
reason: describeError$1(error)
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
return parseRoleConfiguration(text, path);
|
|
262
|
-
}
|
|
263
|
-
/** Parse role-file text. Pure: no file system access. */
|
|
264
|
-
function parseRoleConfiguration(text, path) {
|
|
265
|
-
const issues = [];
|
|
266
|
-
const roles = /* @__PURE__ */ new Map();
|
|
267
|
-
let budget;
|
|
268
|
-
stripFrontmatter(text).split("\n").forEach((rawLine, index) => {
|
|
269
|
-
const line = index + 1;
|
|
270
|
-
const trimmed = rawLine.trim();
|
|
271
|
-
if (trimmed === "") return;
|
|
272
|
-
if (trimmed.startsWith("#")) {
|
|
273
|
-
const match = /^#\s*budget:\s*(.+)$/i.exec(trimmed);
|
|
274
|
-
if (match !== null) budget = match[1].trim();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const separator = trimmed.indexOf(":");
|
|
278
|
-
if (separator === -1) {
|
|
279
|
-
issues.push({
|
|
280
|
-
line,
|
|
281
|
-
message: `expected \`<role>: <value>\`, found ${JSON.stringify(trimmed)}`
|
|
282
|
-
});
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const rawKey = trimmed.slice(0, separator).trim();
|
|
286
|
-
const rawValue = trimmed.slice(separator + 1).trim();
|
|
287
|
-
const key = normalizeRoleName(rawKey);
|
|
288
|
-
if (key === "") {
|
|
289
|
-
issues.push({
|
|
290
|
-
line,
|
|
291
|
-
message: "the role key is empty"
|
|
292
|
-
});
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const spec = findRoleSpec(key);
|
|
296
|
-
if (spec === void 0) {
|
|
297
|
-
issues.push({
|
|
298
|
-
line,
|
|
299
|
-
message: unknownRoleMessage(rawKey, key)
|
|
300
|
-
});
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const parsed = parseRouteValue(rawValue);
|
|
304
|
-
const entryIssues = [...parsed.issues];
|
|
305
|
-
if (spec.shape === "single" && parsed.routes.length > 1) entryIssues.push("a single-route role cannot hold a comma-separated list");
|
|
306
|
-
for (const issue of entryIssues) issues.push({
|
|
307
|
-
line,
|
|
308
|
-
message: `${rawKey}: ${issue}`
|
|
309
|
-
});
|
|
310
|
-
if (parsed.routes.length === 0) return;
|
|
311
|
-
const previous = roles.get(spec.key);
|
|
312
|
-
if (previous !== void 0) issues.push({
|
|
313
|
-
line,
|
|
314
|
-
message: `role "${spec.key}" is already configured on line ${previous.line}; line ${line} wins`
|
|
315
|
-
});
|
|
316
|
-
roles.set(spec.key, {
|
|
317
|
-
line,
|
|
318
|
-
rawKey,
|
|
319
|
-
key: spec.key,
|
|
320
|
-
spec,
|
|
321
|
-
routes: parsed.routes,
|
|
322
|
-
issues: entryIssues
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
return budget === void 0 ? {
|
|
326
|
-
status: "loaded",
|
|
327
|
-
path,
|
|
328
|
-
roles,
|
|
329
|
-
issues
|
|
330
|
-
} : {
|
|
331
|
-
status: "loaded",
|
|
332
|
-
path,
|
|
333
|
-
budget,
|
|
334
|
-
roles,
|
|
335
|
-
issues
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
/** Parse a role value: one route, or a comma-separated list of routes. */
|
|
339
|
-
function parseRouteValue(raw) {
|
|
340
|
-
const routes = [];
|
|
341
|
-
const issues = [];
|
|
342
|
-
const segments = raw.split(",");
|
|
343
|
-
segments.forEach((segment, index) => {
|
|
344
|
-
const trimmed = segment.trim();
|
|
345
|
-
if (trimmed === "") {
|
|
346
|
-
if (index !== segments.length - 1) issues.push(`entry ${index + 1} of the list is empty`);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const parsed = parseRoute(trimmed);
|
|
350
|
-
if (parsed.route === void 0) {
|
|
351
|
-
issues.push(parsed.issue);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
routes.push(parsed.route);
|
|
355
|
-
});
|
|
356
|
-
if (routes.length === 0 && issues.length === 0) issues.push("the value is empty; use `inherit-parent`, `auto`, or `<provider>/<model> [effort]`");
|
|
357
|
-
return {
|
|
358
|
-
routes,
|
|
359
|
-
issues
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
/** Parse one entry of a role value. */
|
|
363
|
-
function parseRoute(raw) {
|
|
364
|
-
const value = raw.trim();
|
|
365
|
-
if (value === "") return { issue: "the value is empty" };
|
|
366
|
-
if (INHERIT_PARENT_ALIASES.includes(value.toLowerCase())) return { route: { kind: "inherit" } };
|
|
367
|
-
const match = /^([^\s/,[]+)\/([^\s/,[\]]+(?:\/[^\s/,[\]]+)*)(?:\s+\[([^[\]]*)\])?$/.exec(value);
|
|
368
|
-
if (match === null) return { issue: `${JSON.stringify(value)} is not a route; use \`inherit-parent\`, \`auto\`, or \`<provider>/<model> [effort]\`` };
|
|
369
|
-
const provider = match[1];
|
|
370
|
-
const model = match[2];
|
|
371
|
-
const effort = match[3];
|
|
372
|
-
if (effort === void 0) return { route: {
|
|
373
|
-
kind: "route",
|
|
374
|
-
provider,
|
|
375
|
-
model
|
|
376
|
-
} };
|
|
377
|
-
const trimmedEffort = effort.trim();
|
|
378
|
-
if (trimmedEffort === "") return { issue: `${JSON.stringify(value)} has empty brackets; write \`<provider>/<model> [effort]\` or drop the brackets` };
|
|
379
|
-
if (/\s/.test(trimmedEffort)) return { issue: `${JSON.stringify(value)} has a multi-word effort; an effort is one adapter-owned token` };
|
|
380
|
-
return { route: {
|
|
381
|
-
kind: "route",
|
|
382
|
-
provider,
|
|
383
|
-
model,
|
|
384
|
-
effort: trimmedEffort
|
|
385
|
-
} };
|
|
386
|
-
}
|
|
387
|
-
/** Raised when a role's configured list cannot be selected from. */
|
|
388
|
-
var RouteSelectionError = class extends Error {
|
|
389
|
-
constructor(message) {
|
|
390
|
-
super(message);
|
|
391
|
-
this.name = "RouteSelectionError";
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
/**
|
|
395
|
-
* Expand one role into the routes this call starts.
|
|
396
|
-
*
|
|
397
|
-
* Automatic panels start one child per entry. Another list role selects one entry, the
|
|
398
|
-
* first by default; `roleIndex` chooses which. A single role holds exactly one route and
|
|
399
|
-
* rejects `roleIndex`.
|
|
400
|
-
*/
|
|
401
|
-
function selectRoleRoutes(spec, entry, roleIndex) {
|
|
402
|
-
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(", ")})`);
|
|
403
|
-
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`);
|
|
404
|
-
if (entry === void 0) return {
|
|
405
|
-
role: spec,
|
|
406
|
-
mode: spec.autoPanel ? "panel" : "single",
|
|
407
|
-
configured: false,
|
|
408
|
-
routes: [{ kind: "inherit" }],
|
|
409
|
-
total: 1
|
|
410
|
-
};
|
|
411
|
-
if (entry.issues.length > 0) throw new RouteSelectionError(`role "${spec.key}" has invalid configuration on line ${entry.line}: ${entry.issues.join("; ")}. Fix the role file before delegating.`);
|
|
412
|
-
if (spec.autoPanel) return {
|
|
413
|
-
role: spec,
|
|
414
|
-
mode: "panel",
|
|
415
|
-
configured: true,
|
|
416
|
-
routes: entry.routes,
|
|
417
|
-
total: entry.routes.length
|
|
418
|
-
};
|
|
419
|
-
if (spec.shape === "list") {
|
|
420
|
-
const index = roleIndex ?? 0;
|
|
421
|
-
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(", ")}`);
|
|
422
|
-
return {
|
|
423
|
-
role: spec,
|
|
424
|
-
mode: "selected",
|
|
425
|
-
configured: true,
|
|
426
|
-
routes: [entry.routes[index]],
|
|
427
|
-
total: entry.routes.length,
|
|
428
|
-
roleIndex: index
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
return {
|
|
432
|
-
role: spec,
|
|
433
|
-
mode: "single",
|
|
434
|
-
configured: true,
|
|
435
|
-
routes: [entry.routes[0]],
|
|
436
|
-
total: entry.routes.length
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
/** Keys of the list-valued roles, including the automatic panels. */
|
|
440
|
-
function listRoleKeys() {
|
|
441
|
-
return ROLE_MANIFEST.filter((role) => role.shape === "list").map((role) => role.key);
|
|
442
|
-
}
|
|
443
|
-
/** Raised when a role name does not match the manifest. */
|
|
444
|
-
var RoleNameError = class extends Error {
|
|
445
|
-
constructor(message) {
|
|
446
|
-
super(message);
|
|
447
|
-
this.name = "RoleNameError";
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
/** Normalize a role name for lookup. Case, spacing, spacing around a comma, and a trailing colon are formatting. */
|
|
451
|
-
function normalizeRoleName(raw) {
|
|
452
|
-
return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
|
|
453
|
-
}
|
|
454
|
-
/** Resolve a role name, or throw a diagnostic that names the near misses. */
|
|
455
|
-
function requireRole(raw) {
|
|
456
|
-
const spec = findRoleSpec(normalizeRoleName(raw));
|
|
457
|
-
if (spec !== void 0) return spec;
|
|
458
|
-
const suggestions = suggestRoles(raw);
|
|
459
|
-
const hint = suggestions.length === 0 ? "No role key is close to it." : `Closest role keys: ${suggestions.map((key) => `"${key}"`).join(", ")}.`;
|
|
460
|
-
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.`);
|
|
461
|
-
}
|
|
462
|
-
function suggestRoles(raw, limit = 3) {
|
|
463
|
-
const target = normalizeRoleName(raw);
|
|
464
|
-
if (target === "") return [];
|
|
465
|
-
return ROLE_MANIFEST.map((role) => {
|
|
466
|
-
const shared = sharedTokenCount(target, role.key);
|
|
467
|
-
const distance = editDistance(target, role.key);
|
|
468
|
-
const contains = role.key.includes(target) || target.includes(role.key);
|
|
469
|
-
const score = (contains ? 1e3 : 0) + shared * 100 - distance + Math.min(role.key.length, target.length);
|
|
470
|
-
return {
|
|
471
|
-
key: role.key,
|
|
472
|
-
score,
|
|
473
|
-
distance,
|
|
474
|
-
shared,
|
|
475
|
-
contains
|
|
476
|
-
};
|
|
477
|
-
}).filter((candidate) => candidate.contains || candidate.shared > 0 || candidate.distance <= 4).sort((left, right) => right.score - left.score).slice(0, limit).map((candidate) => candidate.key);
|
|
478
|
-
}
|
|
479
|
-
function unknownRoleMessage(rawKey, key) {
|
|
480
|
-
const suggestions = suggestRoles(key);
|
|
481
|
-
const hint = suggestions.length === 0 ? "no role key is close to it" : `closest role keys: ${suggestions.map((candidate) => `"${candidate}"`).join(", ")}`;
|
|
482
|
-
return `unknown role ${JSON.stringify(rawKey)} (${hint}). A line for a retired or misspelled role is reported rather than ignored.`;
|
|
483
|
-
}
|
|
484
|
-
function sharedTokenCount(left, right) {
|
|
485
|
-
const rightTokens = new Set(tokenize(right));
|
|
486
|
-
return [...new Set(tokenize(left))].filter((token) => rightTokens.has(token)).length;
|
|
487
|
-
}
|
|
488
|
-
function tokenize(value) {
|
|
489
|
-
return value.split(/[^a-z0-9]+/).filter((token) => token !== "");
|
|
490
|
-
}
|
|
491
|
-
/** Levenshtein distance, iterative with one row of state. */
|
|
492
|
-
function editDistance(left, right) {
|
|
493
|
-
if (left === right) return 0;
|
|
494
|
-
if (left === "") return right.length;
|
|
495
|
-
if (right === "") return left.length;
|
|
496
|
-
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
497
|
-
for (let row = 1; row <= left.length; row += 1) {
|
|
498
|
-
const current = [row];
|
|
499
|
-
for (let column = 1; column <= right.length; column += 1) {
|
|
500
|
-
const substitution = previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1);
|
|
501
|
-
const deletion = previous[column] + 1;
|
|
502
|
-
const insertion = current[column - 1] + 1;
|
|
503
|
-
current.push(Math.min(substitution, deletion, insertion));
|
|
504
|
-
}
|
|
505
|
-
previous = current;
|
|
506
|
-
}
|
|
507
|
-
return previous[right.length];
|
|
508
|
-
}
|
|
509
|
-
function stripFrontmatter(text) {
|
|
510
|
-
const normalized = text.replaceAll("\r\n", "\n");
|
|
511
|
-
if (!normalized.startsWith("---\n")) return normalized;
|
|
512
|
-
const end = normalized.indexOf("\n---", 3);
|
|
513
|
-
if (end === -1) return normalized;
|
|
514
|
-
const after = normalized.indexOf("\n", end + 1);
|
|
515
|
-
return after === -1 ? "" : normalized.slice(after + 1);
|
|
516
|
-
}
|
|
517
|
-
function isMissingFile(error) {
|
|
518
|
-
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
519
|
-
}
|
|
520
113
|
function describeError$1(error) {
|
|
521
114
|
if (error instanceof Error) {
|
|
522
115
|
const code = error.code;
|
|
523
|
-
return typeof code === "string" ? `${
|
|
116
|
+
return typeof code === "string" ? `${error.message} (${code})` : error.message;
|
|
524
117
|
}
|
|
525
118
|
return String(error);
|
|
526
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pstack",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "pstack for DeepSeek Harness: rigorous agent workflows ported from cursor/plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -37,10 +37,13 @@
|
|
|
37
37
|
"exports": {
|
|
38
38
|
".": "./dist/index.mjs",
|
|
39
39
|
"./models": "./dist/models.mjs",
|
|
40
|
+
"./remote": "./dist/remote.mjs",
|
|
41
|
+
"./roles": "./dist/roles.mjs",
|
|
40
42
|
"./script": "./dist/script.mjs",
|
|
41
43
|
"./skill": "./dist/skill.mjs",
|
|
42
44
|
"./subagent": "./dist/subagent.mjs",
|
|
43
45
|
"./package.json": "./package.json",
|
|
46
|
+
"./client": "./dist/client.mjs",
|
|
44
47
|
"./locale/*.json": "./locale/*.json"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
@@ -52,13 +55,25 @@
|
|
|
52
55
|
"@deepseek-ai/dsh-subagent": "0.2.2-alpha.2",
|
|
53
56
|
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2",
|
|
54
57
|
"@deepseek-ai/dsh-tools": "0.2.2-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2",
|
|
55
59
|
"@deepseek-ai/dsh-working-directory": "0.2.2-alpha.2",
|
|
56
60
|
"commander": "14.0.0"
|
|
57
61
|
},
|
|
58
62
|
"devDependencies": {
|
|
63
|
+
"@deepseek-ai/dsh-api-gateway": "0.2.2-alpha.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-locale": "0.2.2-alpha.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.2-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.2-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.2-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-typert-registry": "0.2.2-alpha.2",
|
|
59
69
|
"@types/node": "^24.1.0",
|
|
70
|
+
"@types/react": "~18.3.1",
|
|
71
|
+
"@types/react-dom": "~18.3.0",
|
|
72
|
+
"agent-browser": "0.41.0",
|
|
60
73
|
"bumpp": "^12.3.0",
|
|
61
74
|
"js-yaml": "^5.5.0",
|
|
75
|
+
"react": "^18.3.1",
|
|
76
|
+
"react-dom": "^18.3.1",
|
|
62
77
|
"skills": "^1.7.4",
|
|
63
78
|
"typescript": "^7.0.2",
|
|
64
79
|
"vite-plus": "1.4.0"
|
|
@@ -80,13 +95,22 @@
|
|
|
80
95
|
}
|
|
81
96
|
},
|
|
82
97
|
"engines": {
|
|
83
|
-
"dsh": ">=0.2.
|
|
98
|
+
"dsh": ">=0.2.1-rc.5",
|
|
84
99
|
"node": ">=24.21.1"
|
|
85
100
|
},
|
|
86
101
|
"dsh": {
|
|
87
102
|
"manifestVersion": 1,
|
|
88
103
|
"bundle": {
|
|
89
104
|
"patch": "./cordis.patch.yml"
|
|
105
|
+
},
|
|
106
|
+
"client": {
|
|
107
|
+
"platform": "web",
|
|
108
|
+
"inject": [
|
|
109
|
+
"@deepseek-ai/dsh-api-gateway",
|
|
110
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
111
|
+
"@deepseek-ai/dsh-client-locale",
|
|
112
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager"
|
|
113
|
+
]
|
|
90
114
|
}
|
|
91
115
|
},
|
|
92
116
|
"skills": [
|
|
@@ -97,6 +121,12 @@
|
|
|
97
121
|
"retro"
|
|
98
122
|
],
|
|
99
123
|
"ref": "59db609eb1e18aaf59b63284d0e52255aff0c63c"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"source": "npm:agent-browser",
|
|
127
|
+
"skills": [
|
|
128
|
+
"agent-browser"
|
|
129
|
+
]
|
|
100
130
|
}
|
|
101
131
|
],
|
|
102
132
|
"scripts": {
|