dsh-pstack 0.2.0-beta.3 → 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 +35 -16
- package/README.zh.md +36 -17
- package/assets/pstack-config-entry.png +0 -0
- package/cordis.patch.yml +12 -0
- package/dist/client.mjs +2448 -0
- package/dist/client.mjs.map +1 -0
- 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 +27 -0
- package/dist/remote.mjs +235 -0
- package/dist/role-catalog.d.mts +35 -0
- package/dist/role-catalog.mjs +122 -0
- package/dist/role-config-a4JpVdc-.mjs +379 -0
- package/dist/role-manifest-79cnFfDM.mjs +112 -0
- package/dist/role-manifest-8OERX9-s.d.mts +90 -0
- package/dist/role-wire-CAJsVA6e.d.mts +54 -0
- package/dist/roles.d.mts +18 -0
- package/dist/roles.mjs +293 -0
- package/dist/script.mjs +1 -1
- 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 +27 -20
- package/dist/subagent.mjs +71 -448
- package/package.json +34 -3
package/dist/remote.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { a as isRoleKey } from "./role-manifest-79cnFfDM.mjs";
|
|
2
|
+
// src/remote.ts
|
|
3
|
+
/** npm package that owns these methods. */
|
|
4
|
+
const REMOTE_PACKAGE = "dsh-pstack";
|
|
5
|
+
/** Cordis service key and wire namespace of the Host role service. */
|
|
6
|
+
const ROLES_SERVICE = "pstackRoles";
|
|
7
|
+
function jsonCodec(typeSymbol, parse, decode) {
|
|
8
|
+
return {
|
|
9
|
+
mode: "strict",
|
|
10
|
+
typeSymbol,
|
|
11
|
+
create: () => ({ parse }),
|
|
12
|
+
...decode === void 0 ? {} : { decode }
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function parameter(wire, typeSymbol, parse) {
|
|
16
|
+
return {
|
|
17
|
+
name: wire,
|
|
18
|
+
wire,
|
|
19
|
+
source: "json",
|
|
20
|
+
codec: jsonCodec(typeSymbol, parse)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parseRecord(value, subject) {
|
|
24
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${subject} must be an object`);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function parseRoute(value, subject) {
|
|
28
|
+
const record = parseRecord(value, subject);
|
|
29
|
+
if (record["kind"] === "inherit") {
|
|
30
|
+
const alias = record["alias"] ?? "inherit-parent";
|
|
31
|
+
if (alias !== "inherit-parent" && alias !== "auto") throw new TypeError(`${subject}.alias must be "inherit-parent" or "auto"`);
|
|
32
|
+
return {
|
|
33
|
+
kind: "inherit",
|
|
34
|
+
alias
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (record["kind"] !== "route") throw new TypeError(`${subject}.kind is unknown`);
|
|
38
|
+
const provider = record["provider"];
|
|
39
|
+
const model = record["model"];
|
|
40
|
+
const effort = record["effort"];
|
|
41
|
+
if (typeof provider !== "string" || provider.trim() === "") throw new TypeError(`${subject}.provider must be a non-empty string`);
|
|
42
|
+
if (typeof model !== "string" || model.trim() === "") throw new TypeError(`${subject}.model must be a non-empty string`);
|
|
43
|
+
if (effort !== void 0 && (typeof effort !== "string" || effort.trim() === "")) throw new TypeError(`${subject}.effort must be a non-empty string when present`);
|
|
44
|
+
return {
|
|
45
|
+
kind: "route",
|
|
46
|
+
provider,
|
|
47
|
+
model,
|
|
48
|
+
...typeof effort === "string" ? { effort } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Parse one save request from the wire. */
|
|
52
|
+
function parseSaveRoleRequest(value) {
|
|
53
|
+
const record = parseRecord(value, "the save request");
|
|
54
|
+
if (typeof record["key"] !== "string") throw new TypeError("the save request must name a role");
|
|
55
|
+
const routes = record["routes"];
|
|
56
|
+
if (!Array.isArray(routes)) throw new TypeError("the save request must carry a route list");
|
|
57
|
+
return {
|
|
58
|
+
key: record["key"],
|
|
59
|
+
routes: routes.map((route, index) => parseRoute(route, `routes[${index}]`))
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function parseRoleFileIssue(value) {
|
|
63
|
+
const record = parseRecord(value, "a role file issue");
|
|
64
|
+
if (typeof record["message"] !== "string") throw new TypeError("a role file issue needs a message");
|
|
65
|
+
const line = record["line"];
|
|
66
|
+
if (line !== void 0 && typeof line !== "number") throw new TypeError("a role file issue line must be a number");
|
|
67
|
+
return {
|
|
68
|
+
...typeof line === "number" ? { line } : {},
|
|
69
|
+
message: record["message"]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function parseRoleView(value, index) {
|
|
73
|
+
const record = parseRecord(value, `roles[${index}]`);
|
|
74
|
+
if (typeof record["key"] !== "string" || !isRoleKey(record["key"])) throw new TypeError(`roles[${index}].key is not in the role manifest`);
|
|
75
|
+
if (record["shape"] !== "single" && record["shape"] !== "list") throw new TypeError(`roles[${index}].shape is unknown`);
|
|
76
|
+
if (typeof record["configured"] !== "boolean") throw new TypeError(`roles[${index}].configured must be a boolean`);
|
|
77
|
+
const routes = record["routes"];
|
|
78
|
+
const issues = record["issues"];
|
|
79
|
+
if (!Array.isArray(routes) || !Array.isArray(issues)) throw new TypeError(`roles[${index}] must carry routes and issues arrays`);
|
|
80
|
+
const line = record["line"];
|
|
81
|
+
return {
|
|
82
|
+
key: record["key"],
|
|
83
|
+
shape: record["shape"],
|
|
84
|
+
configured: record["configured"],
|
|
85
|
+
...typeof line === "number" ? { line } : {},
|
|
86
|
+
routes: routes.map((route, routeIndex) => parseRoute(route, `roles[${index}].routes[${routeIndex}]`)),
|
|
87
|
+
issues: issues.map((issue) => String(issue))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Parse a role-file snapshot from the wire. */
|
|
91
|
+
function parseRoleFileSnapshot(value) {
|
|
92
|
+
const record = parseRecord(value, "the role file snapshot");
|
|
93
|
+
const status = record["status"];
|
|
94
|
+
if (status !== "missing" && status !== "loaded" && status !== "unreadable") throw new TypeError("the role file snapshot status is unknown");
|
|
95
|
+
if (typeof record["path"] !== "string") throw new TypeError("the role file snapshot path must be a string");
|
|
96
|
+
if (typeof record["canSave"] !== "boolean") throw new TypeError("the role file snapshot canSave must be a boolean");
|
|
97
|
+
const roles = record["roles"];
|
|
98
|
+
const issues = record["issues"];
|
|
99
|
+
if (!Array.isArray(roles) || !Array.isArray(issues)) throw new TypeError("the role file snapshot must carry roles and issues arrays");
|
|
100
|
+
const reason = record["reason"];
|
|
101
|
+
const budget = record["budget"];
|
|
102
|
+
return {
|
|
103
|
+
path: record["path"],
|
|
104
|
+
status,
|
|
105
|
+
...typeof reason === "string" ? { reason } : {},
|
|
106
|
+
...typeof budget === "string" ? { budget } : {},
|
|
107
|
+
roles: roles.map((role, index) => parseRoleView(role, index)),
|
|
108
|
+
issues: issues.map(parseRoleFileIssue),
|
|
109
|
+
canSave: record["canSave"]
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Parse a save result from the wire. */
|
|
113
|
+
function parseSaveRoleResult(value) {
|
|
114
|
+
const record = parseRecord(value, "the save result");
|
|
115
|
+
if (record["ok"] === true) return {
|
|
116
|
+
ok: true,
|
|
117
|
+
snapshot: parseRoleFileSnapshot(record["snapshot"])
|
|
118
|
+
};
|
|
119
|
+
if (record["ok"] === false && typeof record["reason"] === "string") return {
|
|
120
|
+
ok: false,
|
|
121
|
+
reason: record["reason"]
|
|
122
|
+
};
|
|
123
|
+
throw new TypeError("the save result must be ok with a snapshot or refused with a reason");
|
|
124
|
+
}
|
|
125
|
+
/** Parse the live catalogue the page offers. */
|
|
126
|
+
function parseCatalogue(value) {
|
|
127
|
+
const record = parseRecord(value, "the catalogue");
|
|
128
|
+
const providers = record["providers"];
|
|
129
|
+
if (!Array.isArray(providers)) throw new TypeError("the catalogue must carry providers");
|
|
130
|
+
if (typeof record["truncated"] !== "boolean") throw new TypeError("the catalogue truncated flag must be a boolean");
|
|
131
|
+
return {
|
|
132
|
+
providers: providers.map((provider, index) => {
|
|
133
|
+
const entry = parseRecord(provider, `providers[${index}]`);
|
|
134
|
+
if (typeof entry["provider"] !== "string" || typeof entry["name"] !== "string") throw new TypeError(`providers[${index}] needs provider and name strings`);
|
|
135
|
+
const models = entry["models"];
|
|
136
|
+
if (!Array.isArray(models)) throw new TypeError(`providers[${index}].models must be an array`);
|
|
137
|
+
return {
|
|
138
|
+
provider: entry["provider"],
|
|
139
|
+
name: entry["name"],
|
|
140
|
+
...typeof entry["error"] === "string" ? { error: entry["error"] } : {},
|
|
141
|
+
models: models.map((model, modelIndex) => {
|
|
142
|
+
const item = parseRecord(model, `providers[${index}].models[${modelIndex}]`);
|
|
143
|
+
if (typeof item["route"] !== "string" || typeof item["id"] !== "string" || typeof item["name"] !== "string") throw new TypeError(`providers[${index}].models[${modelIndex}] needs route, id, and name strings`);
|
|
144
|
+
const efforts = item["efforts"];
|
|
145
|
+
if (!Array.isArray(efforts) || efforts.some((effort) => typeof effort !== "string")) throw new TypeError(`providers[${index}].models[${modelIndex}].efforts must be strings`);
|
|
146
|
+
return {
|
|
147
|
+
route: item["route"],
|
|
148
|
+
id: item["id"],
|
|
149
|
+
name: item["name"],
|
|
150
|
+
...typeof item["description"] === "string" ? { description: item["description"] } : {},
|
|
151
|
+
...typeof item["contextWindow"] === "number" ? { contextWindow: item["contextWindow"] } : {},
|
|
152
|
+
...typeof item["maxTokens"] === "number" ? { maxTokens: item["maxTokens"] } : {},
|
|
153
|
+
efforts,
|
|
154
|
+
...typeof item["defaultEffort"] === "string" ? { defaultEffort: item["defaultEffort"] } : {},
|
|
155
|
+
...typeof item["error"] === "string" ? { error: item["error"] } : {}
|
|
156
|
+
};
|
|
157
|
+
})
|
|
158
|
+
};
|
|
159
|
+
}),
|
|
160
|
+
truncated: record["truncated"]
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function directMethod(options) {
|
|
164
|
+
const descriptor = {
|
|
165
|
+
id: `${REMOTE_PACKAGE}#${ROLES_SERVICE}/${options.method}`,
|
|
166
|
+
service: ROLES_SERVICE,
|
|
167
|
+
namespace: ROLES_SERVICE,
|
|
168
|
+
method: options.method,
|
|
169
|
+
invocation: { kind: "direct" },
|
|
170
|
+
parameters: options.parameters,
|
|
171
|
+
result: options.result,
|
|
172
|
+
...options.cancellation === true ? { cancellation: { parameter: "signal" } } : {}
|
|
173
|
+
};
|
|
174
|
+
const input = { create() {
|
|
175
|
+
return { parse(value) {
|
|
176
|
+
const record = parseRecord(value, "the method input");
|
|
177
|
+
const parsed = {};
|
|
178
|
+
for (const item of options.parameters) if (record[item.wire] !== void 0) {
|
|
179
|
+
const codec = item.codec;
|
|
180
|
+
parsed[item.wire] = codec.create().parse(record[item.wire]);
|
|
181
|
+
}
|
|
182
|
+
return parsed;
|
|
183
|
+
} };
|
|
184
|
+
} };
|
|
185
|
+
return {
|
|
186
|
+
...descriptor,
|
|
187
|
+
input: () => input,
|
|
188
|
+
invoke(remote, named, signal) {
|
|
189
|
+
const namespace = Reflect.get(remote, ROLES_SERVICE);
|
|
190
|
+
const values = options.parameters.map((item) => named[item.wire]);
|
|
191
|
+
if (options.cancellation === true) values.push(signal);
|
|
192
|
+
return namespace[options.method](...values);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const statusMethod = directMethod({
|
|
197
|
+
method: "status",
|
|
198
|
+
parameters: [],
|
|
199
|
+
result: jsonCodec("PstackRoleFileSnapshot", parseRoleFileSnapshot, parseRoleFileSnapshot),
|
|
200
|
+
invoke: (remote) => remote.pstackRoles.status()
|
|
201
|
+
});
|
|
202
|
+
const catalogueMethod = directMethod({
|
|
203
|
+
method: "catalogue",
|
|
204
|
+
parameters: [],
|
|
205
|
+
result: jsonCodec("PstackRoleCatalogue", parseCatalogue, parseCatalogue),
|
|
206
|
+
cancellation: true,
|
|
207
|
+
invoke: (remote, _input, signal) => remote.pstackRoles.catalogue(signal)
|
|
208
|
+
});
|
|
209
|
+
const saveMethod = directMethod({
|
|
210
|
+
method: "save",
|
|
211
|
+
parameters: [parameter("request", "PstackSaveRoleRequest", (value) => {
|
|
212
|
+
parseSaveRoleRequest(value);
|
|
213
|
+
return value;
|
|
214
|
+
})],
|
|
215
|
+
result: jsonCodec("PstackSaveRoleResult", parseSaveRoleResult, parseSaveRoleResult),
|
|
216
|
+
cancellation: true,
|
|
217
|
+
invoke: (remote, request, signal) => remote.pstackRoles.save(request, signal)
|
|
218
|
+
});
|
|
219
|
+
/** Mountable contribution the Client passes to `ctx.remote.$mount()`. */
|
|
220
|
+
const pstackRolesRemote = {
|
|
221
|
+
package: REMOTE_PACKAGE,
|
|
222
|
+
descriptors: [
|
|
223
|
+
statusMethod,
|
|
224
|
+
catalogueMethod,
|
|
225
|
+
saveMethod
|
|
226
|
+
],
|
|
227
|
+
methods: {
|
|
228
|
+
[`${ROLES_SERVICE}/status`]: statusMethod,
|
|
229
|
+
[`${ROLES_SERVICE}/catalogue`]: catalogueMethod,
|
|
230
|
+
[`${ROLES_SERVICE}/save`]: saveMethod
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
/** The same descriptors registered by the Host as its strict definitions. */
|
|
234
|
+
const pstackRolesContribution = pstackRolesRemote;
|
|
235
|
+
export { REMOTE_PACKAGE, ROLES_SERVICE, parseCatalogue, parseRoleFileSnapshot, parseSaveRoleRequest, parseSaveRoleResult, pstackRolesContribution, pstackRolesRemote };
|
|
@@ -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 };
|