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
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as VENDORED_SKILLS_ROOT, i as PORT_ROOT, n as HARNESS_DOC_PATH, o as SKILLS_ROOT, r as PACKAGE_ROOT, s as SKILL_ROOTS, t as AUTHORED_SKILLS_ROOT } from "./paths-
|
|
1
|
+
import { c as VENDORED_SKILLS_ROOT, i as PORT_ROOT, n as HARNESS_DOC_PATH, o as SKILLS_ROOT, r as PACKAGE_ROOT, s as SKILL_ROOTS, t as AUTHORED_SKILLS_ROOT } from "./paths-yji9SNak.mjs";
|
|
2
2
|
import { TOOL_NAME, apply as apply$1, createPstackSkillTool, readSkillResource, renderLoadedSkill } from "./skill.mjs";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
4
|
import { readFileSync, readdirSync } from "node:fs";
|
|
@@ -25,7 +25,6 @@ const SKILLS_ROOT = join(PORT_ROOT, "skills");
|
|
|
25
25
|
const VENDORED_SKILLS_ROOT = join(DIST_ROOT, "skills", "vendored");
|
|
26
26
|
/** Absolute path of the skills authored in this repository. */
|
|
27
27
|
const AUTHORED_SKILLS_ROOT = join(DIST_ROOT, "skills", "authored");
|
|
28
|
-
join(PORT_ROOT, "agents");
|
|
29
28
|
/** Absolute path of the `poteto-mode` instructions injected into a poteto-agent brief. */
|
|
30
29
|
const POTETO_MODE_SKILL_PATH = join(SKILLS_ROOT, "poteto-mode", "SKILL.md");
|
|
31
30
|
/** Absolute path of the shipped harness mapping document. */
|
|
@@ -16,7 +16,7 @@ pstack skills use Cursor names. In DeepSeek Harness, read this file once per ses
|
|
|
16
16
|
|
|
17
17
|
| Cursor | DeepSeek Harness |
|
|
18
18
|
| --- | --- |
|
|
19
|
-
| `subagent_type: generalPurpose` | `agent: "general"`. A plain child that joins the parent agent preset and inherits its tools. Every call must also pass `description`, `prompt`, `role`, `roleIndex`, `reasoningEffort`, `cwd
|
|
19
|
+
| `subagent_type: generalPurpose` | `agent: "general"`. A plain child that joins the parent agent preset and inherits its tools. Every call must also pass `description`, `prompt`, `role`, `roleIndex`, `reasoningEffort`, and `cwd`; only `maxDepth` is optional. Pass `null` for `role`, `roleIndex`, `reasoningEffort`, or `cwd` when the setting is unused; `roleIndex` requires a non-null role, and a non-null `cwd` must be inside the Session directory or a separate Git worktree. `maxDepth` is optional: omit it or pass null for the safe default of 1 (the child exists but cannot start its own children); pass 2 or more, within the `pstack-subagent` row's configured cap (2 by default), to let it delegate further. A `0` is treated as 1, because a child is already depth 1 and `0` can never start one. A reasoning effort is one token, exactly as the target model advertises. Choose `agent` explicitly; never pass a DSH agent-preset id in its place. |
|
|
20
20
|
| `poteto-agent`, `comment-sicko` | `agent: "poteto-agent"`, `agent: "comment-sicko"`. The package declares both as `@deepseek-ai/dsh-agent-preset` rows: `poteto-agent` injects the pstack persona and the absolute `poteto-mode` instruction path; `comment-sicko` mounts the comment reviewer preset. These are not `subagent_type` values. |
|
|
21
21
|
| `readonly: true` | No per-call field. Use `agent: "comment-sicko"` for the comment reviewer: its preset disables `write` and `edit` but keeps `bash`, so treat the no-write rule as a review constraint and check `git status` after it reports. For any other read-only brief, say "do not edit files" and check `git status`. Prose is not a sandbox. |
|
|
22
22
|
| `readonly: false` | `agent: "general"`. The child keeps the parent's tools, including mounted MCP tools. |
|
|
@@ -35,7 +35,7 @@ pstack skills use Cursor names. In DeepSeek Harness, read this file once per ses
|
|
|
35
35
|
| A `grok-*`, `gpt-*`, or other non-Claude slug | Pass `role: null` so the child inherits the parent route, and say so. A role line with `inherit-parent` or `auto` does the same. |
|
|
36
36
|
| `auto` or `inherit-parent` | Pass `role: null`, or write the role value as `inherit-parent` or `auto`. `pstack_subagent` sends no route override and the child inherits the parent route. |
|
|
37
37
|
| The `pstack-models.mdc` rule, `~/.cursor/rules/pstack-models.mdc` | `$DSH_HOME/pstack-models.mdc`. `pstack_subagent` re-reads it on every delegation, so an edit affects the next child and never a running child. A missing file makes every role inherit the parent route; an unreadable file is an error; a malformed line is reported at its line number. Role names come from the pstack role manifest, not free text. |
|
|
38
|
-
| A role value `<model> <effort>`, such as `opus high` | `<provider>/<model> [effort]`, such as `provider/model [high]`. Split at the first `/`; the bracketed effort must be a token the model advertises. A comma-separated value is a list: `arena runners`, `architect runners`, and `interrogate reviewers` are automatic panels that start one child per entry; `arena cross-judge pool` and `swarm workers` select one entry by `roleIndex` (null selects the first), and `swarm workers` is the default route for every worker unless a race assigns one per arm. |
|
|
38
|
+
| A role value `<model> <effort>`, such as `opus high` | `<provider>/<model> [effort]`, such as `provider/model [high]`. Split at the first `/`; the bracketed effort must be a token the model advertises. A comma-separated value is a list: `arena runners`, `architect runners`, and `interrogate reviewers` are automatic panels that start one child per entry; `arena cross-judge pool` and `swarm workers` select one entry by `roleIndex` (null selects the first), and `swarm workers` is the default route for every worker unless a race assigns one per arm. Call `pstack_roles` first when a role may hold a list; it reports the role's `shape`, `autoPanel`, and whether `roleIndex` applies. |
|
|
39
39
|
|
|
40
40
|
## setup-pstack
|
|
41
41
|
|
|
@@ -86,13 +86,13 @@ The following DSH plugins are disabled by default. `OPT-07` is stable; the other
|
|
|
86
86
|
| ID | Plugin packages | Capability when enabled | When not enabled |
|
|
87
87
|
| --- | --- | --- | --- |
|
|
88
88
|
| OPT-01 | `@deepseek-ai/dsh-experimental-worktree` and `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` creates a local branch and checkout for `/arena`, `/swarm`, and `/architect`. `from` reads local refs without fetching. | Use `git worktree add` through `bash`, then pass the resulting path to the child as `cwd`. There is no cloud VM; the bundle does not create one worktree per subagent, and each `create_worktree` call changes the Session directory. Skipped in DeepSeek Harness: Use a Cursor cloud agent dashboard or dedicated cloud machine. DeepSeek Harness provides only local worktree isolation. |
|
|
89
|
-
| OPT-02 | `@deepseek-ai/dsh-experimental-forge-bundle` | PR panels, checks, reviews, ready state, merge, and Session
|
|
90
|
-
| OPT-03 | `@deepseek-ai/dsh-browser-use` and exactly one of `@deepseek-ai/dsh-experimental-browser-use-stagehand-native`, `@deepseek-ai/dsh-experimental-browser-use-playwright-mcp`, or `@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp` | Browser navigation, input, screenshots, and ARIA snapshots. Stagehand also provides act, observe, and extract operations. The providers share one provider slot; screenshots need an image-capable model. | Use project-owned browser scripts through `bash` when available. Otherwise print `Skipped in DeepSeek Harness: Drive the browser UI or capture browser evidence. No browser provider is enabled.` Keep static source inspection and any non-browser verification. |
|
|
91
|
-
| OPT-04 | `@deepseek-ai/dsh-cua-bundle` | Clicking, typing, screenshots,
|
|
92
|
-
| OPT-05 | `@deepseek-ai/dsh-experimental-terminal-bundle` | Six global tools keep a shell or REPL alive across calls for CLI and TUI verification. They are added to every Agent, do not cover a GUI, and record no video; clean up persistent state, and expect switching to invalidate the KV cache. | Run foreground commands or background jobs through `bash`. Preserve input, output, and exit codes in the evidence. |
|
|
89
|
+
| OPT-02 | `@deepseek-ai/dsh-experimental-forge-bundle` | PR panels, checks, reviews, ready state, merge, Session binding, and a new-worktree option when starting a Session for `/babysit`, `/shipping`, and `/autopilot-*`. | Use `gh` through `bash`; the bundle covers GitHub only and needs a logged-in `gh`. It has no stack or queued-stack verdict and no `origin` CLI, so keep the bundled `watch-pr` for those. The optional bundle adds no model-side change-request tool. |
|
|
90
|
+
| OPT-03 | `@deepseek-ai/dsh-browser-use` and exactly one of `@deepseek-ai/dsh-experimental-browser-use-stagehand-native`, `@deepseek-ai/dsh-experimental-browser-use-playwright-mcp`, or `@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp` | Browser navigation, input, screenshots, and ARIA snapshots. Stagehand also provides act, observe, and extract operations. The providers share one provider slot and expose different tool names and selectors; screenshots need an image-capable model. | Use project-owned browser scripts through `bash` when available. Otherwise print `Skipped in DeepSeek Harness: Drive the browser UI or capture browser evidence. No browser provider is enabled.` Keep static source inspection and any non-browser verification. |
|
|
91
|
+
| OPT-04 | `@deepseek-ai/dsh-cua-bundle` | Clicking, typing, screenshots, permission pages, and session-level authorization for macOS desktop applications in a PTC Session. Install it on demand; Windows support is incomplete, and it uses the single computer-use provider slot. | Use a project-owned desktop driver through `bash` when available. Otherwise print `Skipped in DeepSeek Harness: Drive the desktop application or capture desktop evidence. No computer-use provider is enabled.` |
|
|
92
|
+
| OPT-05 | `@deepseek-ai/dsh-experimental-terminal-bundle` | Six global tools keep a shell or REPL alive across calls, support background send, and isolate terminals by owner for CLI and TUI verification. They are added to every Agent, do not cover a GUI, and record no video; clean up persistent state, and expect switching to invalidate the KV cache. | Run foreground commands or background jobs through `bash`. Preserve input, output, and exit codes in the evidence. |
|
|
93
93
|
| OPT-06 | `@deepseek-ai/dsh-experimental-session-search` | `session_search`, `session_event_search`, `session_trace`, `session_event_trace`, and `session_event_read` search and inspect Sessions with exact `cwd` equality. Subagent transcript coverage is not confirmed. | Use a resume note or evidence supplied by the user. Print `Skipped in DeepSeek Harness: Search or read the earlier Session transcript. Session-search tools are not enabled.` Do not infer historical results from the current conversation. |
|
|
94
94
|
| OPT-07 | `@deepseek-ai/dsh-mcp-client` | Configured MCP tools appear as `mcp__<server>__<tool>` for `/why` and `/teach`. No server is configured by default, and DSH does not discover evidence categories. | Inspect local files and use standard search or fetch tools. Mark each missing MCP evidence category as not searched. |
|
|
95
95
|
| OPT-08 | `@deepseek-ai/dsh-experimental-agent-team-profile` | Lead and teammate roles, a shared task board, nine team tools, and a Web roster for `/orchestrate` and `/autopilot-*`. There is no pstack PR frontier, ledger, or stack verdict. | Use `pstack_subagent`, `workflow`, and `todo_write`. Do not enable Agent Teams and `pstack_subagent` in one profile. Agent Teams disables `subagent` and `subagent_fork` and replaces the global child controls. |
|
|
96
|
-
| OPT-09 | `@deepseek-ai/dsh-experimental-ralph-bundle` | A foreground `ralph` loop starts a fresh child each round for `/autonomous-run`. It is not a persistent loop in the same Session and has no event watcher. | Use `create_goal` with `schedule_create` or a background `bash` watcher. This fallback does not subscribe to forge events. |
|
|
96
|
+
| OPT-09 | `@deepseek-ai/dsh-experimental-ralph-bundle` | A foreground `ralph` loop starts a fresh child each round and carries a structured handoff for `/autonomous-run`. It is not a persistent loop in the same Session and has no event watcher. | Use `create_goal` with `schedule_create` or a background `bash` watcher. This fallback does not subscribe to forge events. |
|
|
97
97
|
|
|
98
98
|
Browser-use needs its base package and one provider. Stagehand also needs Chromium and a supported non-DeepSeek model. Computer-use covers only macOS PTC Sessions. These optional tools do not record video; use an external recorder or ffmpeg when a step requires video, and report that dependency separately.
|
package/dist/remote.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 { RemoteResult, TypertRemoteContribution, TypertRemoteInfo } from "@deepseek-ai/dsh-typert-protocol";
|
|
4
4
|
/** npm package that owns these methods. */
|
|
5
5
|
export declare const REMOTE_PACKAGE = "dsh-pstack";
|
package/dist/remote.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as isRoleKey } from "./role-manifest-79cnFfDM.mjs";
|
|
1
2
|
// src/remote.ts
|
|
2
3
|
/** npm package that owns these methods. */
|
|
3
4
|
const REMOTE_PACKAGE = "dsh-pstack";
|
|
@@ -70,7 +71,7 @@ function parseRoleFileIssue(value) {
|
|
|
70
71
|
}
|
|
71
72
|
function parseRoleView(value, index) {
|
|
72
73
|
const record = parseRecord(value, `roles[${index}]`);
|
|
73
|
-
if (typeof record["key"] !== "string") throw new TypeError(`roles[${index}].key
|
|
74
|
+
if (typeof record["key"] !== "string" || !isRoleKey(record["key"])) throw new TypeError(`roles[${index}].key is not in the role manifest`);
|
|
74
75
|
if (record["shape"] !== "single" && record["shape"] !== "list") throw new TypeError(`roles[${index}].shape is unknown`);
|
|
75
76
|
if (typeof record["configured"] !== "boolean") throw new TypeError(`roles[${index}].configured must be a boolean`);
|
|
76
77
|
const routes = record["routes"];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { _ as ToolsHost, g as ToolDefinition } from "./host-contract-CUPSpeNk.mjs";
|
|
2
|
+
import { n as RoleSpec, r as RoleValueShape } from "./role-manifest-8OERX9-s.mjs";
|
|
3
|
+
/** Cordis row name, used by Loader diagnostics. */
|
|
4
|
+
export declare const name = "pstack-role-catalog";
|
|
5
|
+
/** The row only needs the tool registry. */
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
/** Model-facing tool name. */
|
|
8
|
+
export declare const TOOL_NAME = "pstack_roles";
|
|
9
|
+
/** Whether `roleIndex` may be passed for a role. */
|
|
10
|
+
export type RoleIndexApplicability = "not-applicable" | "optional";
|
|
11
|
+
export interface RoleCapability {
|
|
12
|
+
/** The exact key to pass as `role`. */
|
|
13
|
+
readonly role: string;
|
|
14
|
+
/** One route, or a comma-separated list of routes. */
|
|
15
|
+
readonly shape: RoleValueShape;
|
|
16
|
+
/** A list role that starts one child per entry automatically. */
|
|
17
|
+
readonly autoPanel: boolean;
|
|
18
|
+
/** `optional` only for a list role that is not an automatic panel. */
|
|
19
|
+
readonly roleIndex: RoleIndexApplicability;
|
|
20
|
+
/** How this role expands into children. */
|
|
21
|
+
readonly usage: string;
|
|
22
|
+
}
|
|
23
|
+
/** Derive one role's capability from its manifest entry. */
|
|
24
|
+
export declare function roleCapability(spec: RoleSpec): RoleCapability;
|
|
25
|
+
/** Every role's capability, in manifest order. */
|
|
26
|
+
export declare function roleCapabilities(): readonly RoleCapability[];
|
|
27
|
+
/** Render the capability list for the model. */
|
|
28
|
+
export declare function renderRoleCapabilities(roles: readonly RoleCapability[]): string;
|
|
29
|
+
export declare function createPstackRoleCatalogTool(): ToolDefinition;
|
|
30
|
+
/** The slice of the Cordis context this row uses. */
|
|
31
|
+
export interface PstackRoleCatalogHost {
|
|
32
|
+
readonly tools: ToolsHost;
|
|
33
|
+
}
|
|
34
|
+
/** Register `pstack_roles`. */
|
|
35
|
+
export declare function apply(ctx: PstackRoleCatalogHost): void;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { r as ROLE_MANIFEST } from "./role-manifest-79cnFfDM.mjs";
|
|
2
|
+
// src/role-catalog.ts
|
|
3
|
+
/** Cordis row name, used by Loader diagnostics. */
|
|
4
|
+
const name = "pstack-role-catalog";
|
|
5
|
+
/** The row only needs the tool registry. */
|
|
6
|
+
const inject = ["tools"];
|
|
7
|
+
/** Model-facing tool name. */
|
|
8
|
+
const TOOL_NAME = "pstack_roles";
|
|
9
|
+
/** Derive one role's capability from its manifest entry. */
|
|
10
|
+
function roleCapability(spec) {
|
|
11
|
+
if (spec.autoPanel) return {
|
|
12
|
+
role: spec.key,
|
|
13
|
+
shape: spec.shape,
|
|
14
|
+
autoPanel: true,
|
|
15
|
+
roleIndex: "not-applicable",
|
|
16
|
+
usage: "one child per configured entry"
|
|
17
|
+
};
|
|
18
|
+
if (spec.shape === "list") return {
|
|
19
|
+
role: spec.key,
|
|
20
|
+
shape: spec.shape,
|
|
21
|
+
autoPanel: false,
|
|
22
|
+
roleIndex: "optional",
|
|
23
|
+
usage: "one child, choosing one configured entry"
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
role: spec.key,
|
|
27
|
+
shape: spec.shape,
|
|
28
|
+
autoPanel: false,
|
|
29
|
+
roleIndex: "not-applicable",
|
|
30
|
+
usage: "one child on the role's single route"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Every role's capability, in manifest order. */
|
|
34
|
+
function roleCapabilities() {
|
|
35
|
+
return ROLE_MANIFEST.map(roleCapability);
|
|
36
|
+
}
|
|
37
|
+
/** Render the capability list for the model. */
|
|
38
|
+
function renderRoleCapabilities(roles) {
|
|
39
|
+
return [
|
|
40
|
+
"pstack roles. Pass the exact `role` key. `roleIndex` is null unless the role is a list that is not an automatic panel.",
|
|
41
|
+
"",
|
|
42
|
+
...roles.map((role) => `- ${role.role} — shape=${role.shape}${role.autoPanel ? ", autoPanel=true" : ""}; roleIndex=${role.roleIndex}; ${role.usage}`)
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
const OUTPUT_SCHEMA = {
|
|
46
|
+
type: "object",
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
properties: { roles: {
|
|
49
|
+
type: "array",
|
|
50
|
+
description: "One capability record per role, in manifest order.",
|
|
51
|
+
items: {
|
|
52
|
+
type: "object",
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
properties: {
|
|
55
|
+
role: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "The exact key to pass as `role`."
|
|
58
|
+
},
|
|
59
|
+
shape: {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: ["single", "list"],
|
|
62
|
+
description: "One route, or a comma-separated list of routes."
|
|
63
|
+
},
|
|
64
|
+
autoPanel: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
description: "A list role that starts one child per configured entry."
|
|
67
|
+
},
|
|
68
|
+
roleIndex: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: ["not-applicable", "optional"],
|
|
71
|
+
description: "`optional` only for a list role that is not an automatic panel; otherwise pass null."
|
|
72
|
+
},
|
|
73
|
+
usage: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "How this role expands into children."
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
required: [
|
|
79
|
+
"role",
|
|
80
|
+
"shape",
|
|
81
|
+
"autoPanel",
|
|
82
|
+
"roleIndex",
|
|
83
|
+
"usage"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
} },
|
|
87
|
+
required: ["roles"]
|
|
88
|
+
};
|
|
89
|
+
const TOOL_DESCRIPTION = [
|
|
90
|
+
"List every pstack role with its `shape` and `autoPanel`, so a `pstack_subagent` call passes the right `role` and `roleIndex` before it starts.",
|
|
91
|
+
"",
|
|
92
|
+
"Read-only. It reports the role manifest, not the routes in `$DSH_HOME/pstack-models.mdc`; use `pstack_models` for the routes this deployment actually serves.",
|
|
93
|
+
"",
|
|
94
|
+
"`roleIndex` applies only to a list role that is not an automatic panel: `arena cross-judge pool` and `swarm workers`. Pass `null` for every single role and for an automatic panel (`arena runners`, `architect runners`, `interrogate reviewers`), which starts one child per configured entry."
|
|
95
|
+
].join("\n");
|
|
96
|
+
function createPstackRoleCatalogTool() {
|
|
97
|
+
return {
|
|
98
|
+
name: TOOL_NAME,
|
|
99
|
+
description: TOOL_DESCRIPTION,
|
|
100
|
+
parameters: {
|
|
101
|
+
type: "object",
|
|
102
|
+
additionalProperties: false,
|
|
103
|
+
properties: {}
|
|
104
|
+
},
|
|
105
|
+
output: {
|
|
106
|
+
schema: OUTPUT_SCHEMA,
|
|
107
|
+
render: (_args, value) => [{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: renderRoleCapabilities(value.roles)
|
|
110
|
+
}]
|
|
111
|
+
},
|
|
112
|
+
isConcurrencySafe: () => true,
|
|
113
|
+
async execute() {
|
|
114
|
+
return { roles: roleCapabilities() };
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Register `pstack_roles`. */
|
|
119
|
+
function apply(ctx) {
|
|
120
|
+
ctx.tools.register(createPstackRoleCatalogTool());
|
|
121
|
+
}
|
|
122
|
+
export { TOOL_NAME, apply, createPstackRoleCatalogTool, inject, name, renderRoleCapabilities, roleCapabilities, roleCapability };
|
|
@@ -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-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
153
|
-
const line =
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
+
continue;
|
|
178
154
|
}
|
|
179
|
-
|
|
180
|
-
if (spec === void 0) {
|
|
155
|
+
if (scanned.kind === "unknown-role") {
|
|
181
156
|
issues.push({
|
|
182
157
|
line,
|
|
183
|
-
message: unknownRoleMessage(rawKey,
|
|
158
|
+
message: unknownRoleMessage(scanned.rawKey, normalizeRoleName(scanned.rawKey))
|
|
184
159
|
});
|
|
185
|
-
|
|
160
|
+
continue;
|
|
186
161
|
}
|
|
187
|
-
const
|
|
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)
|
|
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 {
|
|
379
|
+
export { selectRoleRoutes as a, resolveDshHome as i, readRoleConfiguration as n, updateRoleDocument as o, requireRole as r, modelsFilePath as t };
|