@matamt-dbg/dsh-studio 0.2.2 → 0.2.8
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 +9 -4
- package/client.js +499 -219
- package/index.js +340 -64
- package/package.json +1 -1
- package/policy.js +135 -12
- package/roles.js +25 -3
package/policy.js
CHANGED
|
@@ -39,7 +39,34 @@ function applyExecutionDefaults(args, defaults) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// src/host/preset-policy.ts
|
|
42
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
42
|
+
import { scopeChainOf, scopeOf } from "@deepseek-ai/dsh-scope";
|
|
43
|
+
|
|
44
|
+
// src/tool-prompt-sections.ts
|
|
45
|
+
var toolPromptSections = {
|
|
46
|
+
bash: ["tool:bash"],
|
|
47
|
+
pwsh: ["tool:pwsh"],
|
|
48
|
+
read: ["tool:read"],
|
|
49
|
+
write: ["tool:write"],
|
|
50
|
+
edit: ["tool:edit"],
|
|
51
|
+
glob: ["tool:glob"],
|
|
52
|
+
grep: ["tool:grep"],
|
|
53
|
+
job_list: ["tool:jobs"],
|
|
54
|
+
job_output: ["tool:jobs"],
|
|
55
|
+
job_kill: ["tool:jobs"],
|
|
56
|
+
web_fetch: ["tool:web_fetch"],
|
|
57
|
+
web_search: ["tool:web_search"],
|
|
58
|
+
get_goal: ["tool:goal"],
|
|
59
|
+
create_goal: ["tool:goal"],
|
|
60
|
+
update_goal: ["tool:goal"],
|
|
61
|
+
workflow: ["tool:workflow"]
|
|
62
|
+
};
|
|
63
|
+
function sectionsOnlyFor(hidden, kept) {
|
|
64
|
+
const owned = (names2) => new Set(names2.flatMap((name2) => toolPromptSections[name2] ?? []));
|
|
65
|
+
const still = owned(kept);
|
|
66
|
+
return [...owned(hidden)].filter((section) => !still.has(section));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/host/preset-policy.ts
|
|
43
70
|
var POLICY_SECTION_ORDERS = Object.freeze({
|
|
44
71
|
"tool:jobs": "TOOL_JOBS",
|
|
45
72
|
"tool:bash": "TOOL_BASH",
|
|
@@ -51,7 +78,8 @@ var POLICY_SECTION_ORDERS = Object.freeze({
|
|
|
51
78
|
"tool:grep": "TOOL_GREP",
|
|
52
79
|
"tool:web_fetch": "TOOL_WEB_FETCH",
|
|
53
80
|
"tool:web_search": "TOOL_WEB_SEARCH",
|
|
54
|
-
"tool:goal": "TOOL_GOAL"
|
|
81
|
+
"tool:goal": "TOOL_GOAL",
|
|
82
|
+
"tool:workflow": "TOOL_WORKFLOW"
|
|
55
83
|
});
|
|
56
84
|
var own2 = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
57
85
|
var record2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -69,6 +97,26 @@ function validatePolicy(config) {
|
|
|
69
97
|
for (const [name2, text] of Object.entries(config.sections)) {
|
|
70
98
|
if (!own2(POLICY_SECTION_ORDERS, name2) || typeof text !== "string") throw new Error(`Unsupported policy section: ${name2}`);
|
|
71
99
|
}
|
|
100
|
+
if (config.childToolFilter !== void 0) {
|
|
101
|
+
const deny = record2(config.childToolFilter) ? config.childToolFilter.deny : void 0;
|
|
102
|
+
if (!Array.isArray(deny) || deny.some((name2) => typeof name2 !== "string" || !name2)) throw new Error("Invalid subagent tool filter");
|
|
103
|
+
for (const name2 of deny) if (!config.tools.includes(name2)) throw new Error(`Subagent tool filter requires selected tool: ${name2}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function childPolicy(config, hidden) {
|
|
107
|
+
const deny = new Set(hidden);
|
|
108
|
+
if (!deny.size) return config;
|
|
109
|
+
const keep = (entries) => Object.fromEntries(Object.entries(entries).filter(([name2]) => !deny.has(name2)));
|
|
110
|
+
const { childToolFilter: _filter, execution, ...rest } = config;
|
|
111
|
+
const tools = config.tools.filter((name2) => !deny.has(name2));
|
|
112
|
+
const blank = Object.fromEntries(sectionsOnlyFor([...deny], tools).filter((name2) => own2(POLICY_SECTION_ORDERS, name2)).map((name2) => [name2, ""]));
|
|
113
|
+
return {
|
|
114
|
+
...rest,
|
|
115
|
+
tools,
|
|
116
|
+
toolOverrides: keep(config.toolOverrides),
|
|
117
|
+
sections: { ...config.sections, ...blank },
|
|
118
|
+
...execution ? { execution: (({ batch, ...value }) => ({ ...value, defaults: keep(execution.defaults), ...batch && !deny.has(batch.tool) ? { batch } : {} }))(execution) } : {}
|
|
119
|
+
};
|
|
72
120
|
}
|
|
73
121
|
function patchParameter(parameters, path, description) {
|
|
74
122
|
const segments = path.split(".");
|
|
@@ -82,11 +130,20 @@ function patchParameter(parameters, path, description) {
|
|
|
82
130
|
function installPolicy(ctx, scopeKey, config) {
|
|
83
131
|
validatePolicy(config);
|
|
84
132
|
if (scopeOf(ctx) !== scopeKey) throw new Error("Studio policy requires its owning scoped context");
|
|
133
|
+
if (config.tools.includes("run_code") || ctx.tools.get("run_code", scopeKey) !== void 0) {
|
|
134
|
+
throw new Error("Studio policy does not support PTC presentation or the reserved run_code tool; configure this preset with native tool presentation");
|
|
135
|
+
}
|
|
85
136
|
const missing = config.tools.filter((name2) => ctx.tools.get(name2, scopeKey) === void 0);
|
|
86
|
-
if (missing.length)
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
137
|
+
if (missing.length) throw new Error(`Studio policy selected tools unavailable: ${missing.join(", ")}`);
|
|
138
|
+
const restoreProbe = ctx.tools.restrict({ allow: [] });
|
|
139
|
+
try {
|
|
140
|
+
const local = ctx.tools.schemas(scopeKey);
|
|
141
|
+
if (local.length) throw new Error(`Studio policy does not support own-scope tools: ${local.map((tool) => tool.name).join(", ")}; register providers in an ancestor preset scope. Public API cannot hide late own-scope schemas; dynamic own-scope providers are unsupported`);
|
|
142
|
+
} finally {
|
|
143
|
+
restoreProbe();
|
|
144
|
+
}
|
|
145
|
+
const selected = new Set(config.tools);
|
|
146
|
+
const definitions = config.tools.map((name2) => {
|
|
90
147
|
const definition = ctx.tools.get(name2, scopeKey);
|
|
91
148
|
const override = config.toolOverrides[name2];
|
|
92
149
|
const defaults = config.execution?.defaults[name2];
|
|
@@ -120,9 +177,7 @@ function installPolicy(ctx, scopeKey, config) {
|
|
|
120
177
|
for (const release of disposers.splice(0).reverse()) release();
|
|
121
178
|
};
|
|
122
179
|
try {
|
|
123
|
-
disposers.push(ctx.tools.restrict({ allow:
|
|
124
|
-
const excess = ctx.tools.schemas(scopeKey).filter((tool) => !selected.has(tool.name));
|
|
125
|
-
if (excess.length) throw new Error(`Unselected scoped tools must be removed from preset plugins: ${excess.map((tool) => tool.name).join(", ")}`);
|
|
180
|
+
disposers.push(ctx.tools.restrict({ allow: [...selected] }));
|
|
126
181
|
disposers.push(ctx.tools.guard((execution) => selected.has(execution.name) ? void 0 : "Tool excluded by Studio preset policy"));
|
|
127
182
|
for (const definition of definitions) disposers.push(ctx.tools.register(definition));
|
|
128
183
|
for (const [name2, text] of Object.entries(config.sections)) disposers.push(ctx.systemPrompt.section({ name: name2, order: ctx.systemPrompt.getSectionOrder(POLICY_SECTION_ORDERS[name2]), text, interpolate: false }));
|
|
@@ -137,23 +192,91 @@ var name = "studio-preset-policy";
|
|
|
137
192
|
var inject = ["tools", "systemPrompt"];
|
|
138
193
|
function apply(ctx, config) {
|
|
139
194
|
validatePolicy(config);
|
|
195
|
+
const registry = ctx.get("agents");
|
|
196
|
+
const hideable = config.childToolFilter?.deny ?? [];
|
|
197
|
+
const policyFor = (agent) => {
|
|
198
|
+
if (!hideable.length) return config;
|
|
199
|
+
const provided = (name2) => scopeOf(ctx) === void 0 || ctx.tools.get(name2, scopeOf(ctx)) !== void 0;
|
|
200
|
+
const hidden = hideable.filter((name2) => agent.ctx.tools.get(name2, agent) === void 0 && provided(name2));
|
|
201
|
+
return hidden.length ? childPolicy(config, hidden) : config;
|
|
202
|
+
};
|
|
140
203
|
const installed = /* @__PURE__ */ new Map();
|
|
204
|
+
const installing = /* @__PURE__ */ new Set();
|
|
205
|
+
const install = (agent) => {
|
|
206
|
+
if (installed.has(agent) || installing.has(agent)) return;
|
|
207
|
+
installing.add(agent);
|
|
208
|
+
try {
|
|
209
|
+
installed.set(agent, installPolicy(agent.ctx, agent, policyFor(agent)));
|
|
210
|
+
} finally {
|
|
211
|
+
installing.delete(agent);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
const remove = (agent) => {
|
|
215
|
+
installed.get(agent)?.();
|
|
216
|
+
installed.delete(agent);
|
|
217
|
+
};
|
|
141
218
|
ctx.on("agent/created", ({ agent }) => {
|
|
142
|
-
|
|
219
|
+
install(agent);
|
|
143
220
|
return void 0;
|
|
144
221
|
});
|
|
145
222
|
ctx.on("agent/disposed", ({ agent }) => {
|
|
146
|
-
|
|
147
|
-
|
|
223
|
+
remove(agent);
|
|
224
|
+
});
|
|
225
|
+
const composition = scopeOf(ctx);
|
|
226
|
+
const agents = registry;
|
|
227
|
+
if (composition === void 0 || agents === void 0) {
|
|
228
|
+
ctx.effect(() => () => {
|
|
229
|
+
for (const dispose of installed.values()) dispose();
|
|
230
|
+
installed.clear();
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const belongs = (agent) => scopeChainOf(scopeOf(agent.ctx)).includes(composition);
|
|
235
|
+
const blocked = /* @__PURE__ */ new Map();
|
|
236
|
+
const unblock = (agent) => {
|
|
237
|
+
blocked.get(agent)?.release();
|
|
238
|
+
blocked.delete(agent);
|
|
239
|
+
};
|
|
240
|
+
const block = (agent, error) => {
|
|
241
|
+
const reason = `Studio preset policy could not be installed: ${error instanceof Error ? error.message : String(error)}`;
|
|
242
|
+
const current = blocked.get(agent);
|
|
243
|
+
if (current?.reason === reason) return;
|
|
244
|
+
current?.release();
|
|
245
|
+
blocked.set(agent, { release: agent.ctx.tools.guard(() => reason), reason });
|
|
246
|
+
};
|
|
247
|
+
const reconcile = () => {
|
|
248
|
+
const live = agents.list();
|
|
249
|
+
for (const agent of live) {
|
|
250
|
+
if (!belongs(agent)) {
|
|
251
|
+
remove(agent);
|
|
252
|
+
unblock(agent);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (installed.has(agent)) continue;
|
|
256
|
+
try {
|
|
257
|
+
install(agent);
|
|
258
|
+
unblock(agent);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
block(agent, error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const agent of [...blocked.keys()]) if (!live.includes(agent)) unblock(agent);
|
|
264
|
+
};
|
|
265
|
+
ctx.on("tools/change", reconcile);
|
|
266
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
267
|
+
unblock(agent);
|
|
148
268
|
});
|
|
269
|
+
reconcile();
|
|
149
270
|
ctx.effect(() => () => {
|
|
150
271
|
for (const dispose of installed.values()) dispose();
|
|
151
272
|
installed.clear();
|
|
273
|
+
for (const agent of [...blocked.keys()]) unblock(agent);
|
|
152
274
|
});
|
|
153
275
|
}
|
|
154
276
|
export {
|
|
155
277
|
POLICY_SECTION_ORDERS,
|
|
156
278
|
apply,
|
|
279
|
+
childPolicy,
|
|
157
280
|
inject,
|
|
158
281
|
installPolicy,
|
|
159
282
|
name,
|
package/roles.js
CHANGED
|
@@ -50,11 +50,27 @@ function parseRoles(input) {
|
|
|
50
50
|
if (!/^[a-zA-Z0-9_-]+$/.test(reasoning)) throw new Error("Role reasoning must be inherit or an effort identifier");
|
|
51
51
|
if (spawn !== "fresh" && spawn !== "fork") throw new Error("Role spawn must be fresh or fork");
|
|
52
52
|
seen.add(role);
|
|
53
|
-
|
|
53
|
+
const filter = value.toolFilter;
|
|
54
|
+
if (filter !== void 0 && (!object(filter) || !Array.isArray(filter.deny) || filter.deny.some((name3) => typeof name3 !== "string" || !name3) || filter.mode !== void 0 && filter.mode !== "extend" && filter.mode !== "replace")) throw new Error("Invalid role tool filter");
|
|
55
|
+
return {
|
|
56
|
+
name: name2,
|
|
57
|
+
role,
|
|
58
|
+
model,
|
|
59
|
+
reasoning,
|
|
60
|
+
spawn,
|
|
61
|
+
persona,
|
|
62
|
+
...filter !== void 0 ? { toolFilter: { mode: filter.mode ?? "replace", deny: [...new Set(filter.deny)] } } : {},
|
|
63
|
+
..."overrides" in value ? { overrides: structuredClone(value.overrides) } : {}
|
|
64
|
+
};
|
|
54
65
|
});
|
|
55
66
|
}
|
|
56
67
|
function executableRoles(roles) {
|
|
57
|
-
return roles.map(({ name: name2, role, model, reasoning, spawn, persona }) => ({ name: name2, role, model, reasoning, spawn, persona }));
|
|
68
|
+
return roles.map(({ name: name2, role, model, reasoning, spawn, persona, toolFilter }) => ({ name: name2, role, model, reasoning, spawn, persona, ...toolFilter ? { toolFilter } : {} }));
|
|
69
|
+
}
|
|
70
|
+
function roleToolFilter(role, presetDefault = []) {
|
|
71
|
+
const own = role?.toolFilter;
|
|
72
|
+
if (!own) return [...presetDefault];
|
|
73
|
+
return own.mode === "replace" ? [...own.deny] : [.../* @__PURE__ */ new Set([...presetDefault, ...own.deny])];
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
// src/package-name.ts
|
|
@@ -72,6 +88,9 @@ var inject = ["tools", "subagents", "llm"];
|
|
|
72
88
|
var PARENT_ROLE = { name: "Subagent", role: "subagent", model: "inherit", reasoning: "inherit", spawn: "fresh", persona: "" };
|
|
73
89
|
function apply(ctx, config) {
|
|
74
90
|
const roles = executableRoles(parseRoles(config.roles));
|
|
91
|
+
const deny = config.toolFilter?.deny;
|
|
92
|
+
if (deny !== void 0 && (!Array.isArray(deny) || deny.some((name2) => typeof name2 !== "string" || !name2))) throw new Error("Invalid subagent tool filter");
|
|
93
|
+
const presetDefault = deny ?? [];
|
|
75
94
|
ctx.tools.register(defineTool({
|
|
76
95
|
name: ROLE_TOOL,
|
|
77
96
|
description: roles.length ? "Discover saved specialist roles or delegate a task to one. Calls return a native child agent id; the child reports back through normal subagent delivery." : "Delegate a self-contained task to a subagent that starts with the same model and settings as you. Calls return a native child agent id; the child reports back through normal subagent delivery.",
|
|
@@ -102,6 +121,8 @@ function apply(ctx, config) {
|
|
|
102
121
|
}
|
|
103
122
|
if (role.reasoning !== "inherit") agentOptions.reasoningEffort = ReasoningEffortId2(role.reasoning);
|
|
104
123
|
const maxDepth = ctx.subagents.resolveMaxDepth();
|
|
124
|
+
const hidden = roleToolFilter(roles.length ? role : void 0, presetDefault);
|
|
125
|
+
const toolFilter = hidden.length ? { deny: hidden } : void 0;
|
|
105
126
|
const spec = {
|
|
106
127
|
provider: role.spawn === "fork" ? "fork" : "spawn",
|
|
107
128
|
label: role.name,
|
|
@@ -110,7 +131,8 @@ function apply(ctx, config) {
|
|
|
110
131
|
prompt: [{ type: "text", text: args.task }],
|
|
111
132
|
...Object.keys(agentOptions).length ? { agentOptions } : {},
|
|
112
133
|
...role.persona ? { persona: role.persona } : {},
|
|
113
|
-
...maxDepth === void 0 ? {} : { maxDepth }
|
|
134
|
+
...maxDepth === void 0 ? {} : { maxDepth },
|
|
135
|
+
...toolFilter ? { toolFilter } : {}
|
|
114
136
|
},
|
|
115
137
|
signal: exec.signal
|
|
116
138
|
};
|