@matamt-dbg/dsh-studio 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +89 -2
- package/batch.js +62 -0
- package/client.js +2111 -0
- package/cordis.patch.yml +3 -0
- package/index.js +1196 -0
- package/package.json +68 -4
- package/policy.js +159 -0
- package/roles.js +129 -0
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matamt-dbg/dsh-studio",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "DSH Studio: build, edit and apply DeepSeek Harness agent presets — tools, prompts, Configs and role-based subagents — from the web GUI.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek-harness",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"dsh-studio",
|
|
9
|
+
"agent-preset"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/matamt-dbg/dsh-studio.git"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/matamt-dbg/dsh-studio#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/matamt-dbg/dsh-studio/issues"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./index.js",
|
|
23
|
+
"./client": "./client.js",
|
|
24
|
+
"./policy": "./policy.js",
|
|
25
|
+
"./roles": "./roles.js",
|
|
26
|
+
"./batch": "./batch.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"index.js",
|
|
30
|
+
"client.js",
|
|
31
|
+
"policy.js",
|
|
32
|
+
"roles.js",
|
|
33
|
+
"batch.js",
|
|
34
|
+
"cordis.patch.yml"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"js-yaml": "^4.3.2"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@deepseek-ai/dsh-app-boot": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
42
|
+
"@deepseek-ai/dsh": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
43
|
+
"@deepseek-ai/dsh-scope": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
45
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-jobs": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2 || 0.2.1-alpha.2",
|
|
48
|
+
"@deepseek-ai/cordis-plugin-include": "*"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"@deepseek-ai/dsh": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"dsh": {
|
|
59
|
+
"bundle": {
|
|
60
|
+
"patch": "./cordis.patch.yml"
|
|
61
|
+
},
|
|
62
|
+
"client": {
|
|
63
|
+
"platform": "web",
|
|
64
|
+
"immediately": true,
|
|
65
|
+
"inject": [
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/policy.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// src/execution-defaults.ts
|
|
2
|
+
function batchLimitFields(tool, parameters) {
|
|
3
|
+
if (tool !== "read_batch" || parameters.type !== "object" || !record(parameters.properties)) return [];
|
|
4
|
+
return Object.entries(parameters.properties).flatMap(([name2, schema]) => record(schema) && schema.type === "array" && Number.isSafeInteger(schema.maxItems) && schema.maxItems > 0 && (schema.minItems === void 0 || Number.isSafeInteger(schema.minItems) && schema.minItems >= 0) ? [name2] : []);
|
|
5
|
+
}
|
|
6
|
+
function validateBatchLimit(parameters, batch) {
|
|
7
|
+
if (!batchLimitFields(batch.tool, parameters).includes(batch.parameter)) throw new Error("Unsupported batch limit");
|
|
8
|
+
const schema = parameters.properties[batch.parameter];
|
|
9
|
+
if (!Number.isSafeInteger(batch.maxItems) || batch.maxItems < 1 || batch.maxItems > schema.maxItems || batch.maxItems < (typeof schema.minItems === "number" ? schema.minItems : 0)) throw new Error("Batch limit must tighten the published bounds");
|
|
10
|
+
}
|
|
11
|
+
var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
13
|
+
var names = /* @__PURE__ */ new Set(["timeoutMs", "timeout_ms"]);
|
|
14
|
+
var annotations = ["description", "title", "default", "examples"];
|
|
15
|
+
var bounds = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
|
|
16
|
+
function executionDefaultFields(parameters) {
|
|
17
|
+
if (parameters.type !== "object" || !record(parameters.properties) || Object.keys(parameters).some((key) => !["type", "properties", "required", "additionalProperties", ...annotations].includes(key)) || parameters.required !== void 0 && (!Array.isArray(parameters.required) || parameters.required.some((key) => typeof key !== "string"))) return [];
|
|
18
|
+
return Object.entries(parameters.properties).flatMap(([name2, schema]) => {
|
|
19
|
+
if (!names.has(name2) || parameters.required?.includes(name2) || !record(schema) || !["number", "integer"].includes(String(schema.type)) || Object.keys(schema).some((key) => !["type", ...annotations, ...bounds, "enum", "const"].includes(key)) || bounds.some((key) => own(schema, key) && (typeof schema[key] !== "number" || !Number.isFinite(schema[key]) || key === "multipleOf" && schema[key] <= 0)) || own(schema, "const") && (typeof schema.const !== "number" || !Number.isFinite(schema.const)) || own(schema, "enum") && (!Array.isArray(schema.enum) || !schema.enum.length || schema.enum.some((value) => typeof value !== "number" || !Number.isFinite(value)))) return [];
|
|
20
|
+
return [name2];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function validateExecutionDefaults(value) {
|
|
24
|
+
if (!record(value) || Object.keys(value).some((key) => !["defaults", "batch"].includes(key)) || !record(value.defaults)) throw new Error("Invalid execution defaults");
|
|
25
|
+
if (value.batch !== void 0 && (!record(value.batch) || Object.keys(value.batch).some((key) => !["tool", "parameter", "maxItems"].includes(key)) || value.batch.tool !== "read_batch" || typeof value.batch.parameter !== "string" || !value.batch.parameter || ["__proto__", "prototype", "constructor"].includes(value.batch.parameter) || !Number.isSafeInteger(value.batch.maxItems) || value.batch.maxItems < 1)) throw new Error("Invalid batch limit");
|
|
26
|
+
for (const [tool, fields] of Object.entries(value.defaults)) {
|
|
27
|
+
if (!tool || ["__proto__", "prototype", "constructor"].includes(tool) || !record(fields)) throw new Error("Invalid execution defaults");
|
|
28
|
+
for (const [field, number] of Object.entries(fields)) if (!names.has(field) || typeof number !== "number" || !Number.isFinite(number) || number <= 0) throw new Error(`Invalid execution default: ${tool}.${field}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function validateExecutionDefault(parameters, field, value) {
|
|
32
|
+
if (!executionDefaultFields(parameters).includes(field)) throw new Error(`Unsupported execution default: ${field}`);
|
|
33
|
+
const schema = parameters.properties[field];
|
|
34
|
+
if (!Number.isFinite(value) || value <= 0 || schema.type === "integer" && !Number.isInteger(value) || typeof schema.minimum === "number" && value < schema.minimum || typeof schema.maximum === "number" && value > schema.maximum || typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum || typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum || typeof schema.multipleOf === "number" && !Number.isInteger(value / schema.multipleOf) || Array.isArray(schema.enum) && !schema.enum.includes(value) || own(schema, "const") && value !== schema.const) throw new Error(`Execution default outside schema: ${field}`);
|
|
35
|
+
}
|
|
36
|
+
function applyExecutionDefaults(args, defaults) {
|
|
37
|
+
if (!record(args)) return args;
|
|
38
|
+
return { ...Object.fromEntries(Object.entries(defaults).filter(([field]) => !own(args, field))), ...args };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/host/preset-policy.ts
|
|
42
|
+
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
43
|
+
var POLICY_SECTION_ORDERS = Object.freeze({
|
|
44
|
+
"tool:jobs": "TOOL_JOBS",
|
|
45
|
+
"tool:bash": "TOOL_BASH",
|
|
46
|
+
"tool:pwsh": "TOOL_PWSH",
|
|
47
|
+
"tool:read": "TOOL_READ",
|
|
48
|
+
"tool:write": "TOOL_WRITE",
|
|
49
|
+
"tool:edit": "TOOL_EDIT",
|
|
50
|
+
"tool:glob": "TOOL_GLOB",
|
|
51
|
+
"tool:grep": "TOOL_GREP",
|
|
52
|
+
"tool:web_fetch": "TOOL_WEB_FETCH",
|
|
53
|
+
"tool:web_search": "TOOL_WEB_SEARCH",
|
|
54
|
+
"tool:goal": "TOOL_GOAL"
|
|
55
|
+
});
|
|
56
|
+
var own2 = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
57
|
+
var record2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
58
|
+
function validatePolicy(config) {
|
|
59
|
+
if (!record2(config) || !Array.isArray(config.tools) || config.tools.some((name2) => typeof name2 !== "string" || !name2) || new Set(config.tools).size !== config.tools.length || !record2(config.toolOverrides) || !record2(config.sections) || typeof config.prompt !== "string") throw new Error("Invalid Studio policy");
|
|
60
|
+
if (config.execution !== void 0) {
|
|
61
|
+
validateExecutionDefaults(config.execution);
|
|
62
|
+
for (const name2 of Object.keys(config.execution.defaults)) if (!config.tools.includes(name2)) throw new Error(`Execution default requires selected tool: ${name2}`);
|
|
63
|
+
if (config.execution.batch && !config.tools.includes(config.execution.batch.tool)) throw new Error("Batch limit requires selected tool");
|
|
64
|
+
}
|
|
65
|
+
for (const [name2, override] of Object.entries(config.toolOverrides)) {
|
|
66
|
+
if (!config.tools.includes(name2)) throw new Error(`Override requires selected tool: ${name2}`);
|
|
67
|
+
if (!record2(override) || Object.keys(override).some((key) => !["description", "parameters"].includes(key)) || override.description !== void 0 && typeof override.description !== "string" || override.parameters !== void 0 && (!record2(override.parameters) || Object.values(override.parameters).some((value) => typeof value !== "string"))) throw new Error(`Invalid override: ${name2}`);
|
|
68
|
+
}
|
|
69
|
+
for (const [name2, text] of Object.entries(config.sections)) {
|
|
70
|
+
if (!own2(POLICY_SECTION_ORDERS, name2) || typeof text !== "string") throw new Error(`Unsupported policy section: ${name2}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function patchParameter(parameters, path, description) {
|
|
74
|
+
const segments = path.split(".");
|
|
75
|
+
let node = parameters;
|
|
76
|
+
for (const segment of segments) {
|
|
77
|
+
if (!segment || ["__proto__", "prototype", "constructor"].includes(segment) || !record2(node.properties) || !own2(node.properties, segment) || !record2(node.properties[segment])) throw new Error(`Unknown parameter path: ${path}`);
|
|
78
|
+
node = node.properties[segment];
|
|
79
|
+
}
|
|
80
|
+
node.description = description;
|
|
81
|
+
}
|
|
82
|
+
function installPolicy(ctx, scopeKey, config) {
|
|
83
|
+
validatePolicy(config);
|
|
84
|
+
if (scopeOf(ctx) !== scopeKey) throw new Error("Studio policy requires its owning scoped context");
|
|
85
|
+
const selected = new Set(config.tools);
|
|
86
|
+
const definitions = config.tools.map((name2) => {
|
|
87
|
+
const definition = ctx.tools.get(name2, scopeKey);
|
|
88
|
+
if (!definition) throw new Error(`Unavailable selected tool: ${name2}`);
|
|
89
|
+
const override = config.toolOverrides[name2];
|
|
90
|
+
const defaults = config.execution?.defaults[name2];
|
|
91
|
+
const batch = config.execution?.batch?.tool === name2 ? config.execution.batch : void 0;
|
|
92
|
+
if (!override && !defaults && !batch) return void 0;
|
|
93
|
+
const parameters = structuredClone(definition.parameters);
|
|
94
|
+
for (const [path, description] of Object.entries(override?.parameters ?? {})) patchParameter(parameters, path, description);
|
|
95
|
+
for (const [field, value] of Object.entries(defaults ?? {})) {
|
|
96
|
+
validateExecutionDefault(parameters, field, value);
|
|
97
|
+
parameters.properties[field].default = value;
|
|
98
|
+
}
|
|
99
|
+
if (batch) {
|
|
100
|
+
validateBatchLimit(parameters, batch);
|
|
101
|
+
parameters.properties[batch.parameter].maxItems = batch.maxItems;
|
|
102
|
+
}
|
|
103
|
+
const stableDefaults = { ...defaults };
|
|
104
|
+
const stableBatch = batch && { ...batch };
|
|
105
|
+
return {
|
|
106
|
+
...definition,
|
|
107
|
+
parameters,
|
|
108
|
+
...override?.description === void 0 ? {} : { description: override.description },
|
|
109
|
+
...defaults || batch ? { execute(args, exec) {
|
|
110
|
+
const effective = applyExecutionDefaults(args, stableDefaults);
|
|
111
|
+
if (stableBatch && record2(effective) && Array.isArray(effective[stableBatch.parameter]) && effective[stableBatch.parameter].length > stableBatch.maxItems) throw new Error(`Studio batch limit exceeded: ${stableBatch.maxItems}`);
|
|
112
|
+
return definition.execute.call(definition, effective, exec);
|
|
113
|
+
} } : {}
|
|
114
|
+
};
|
|
115
|
+
}).filter((definition) => definition !== void 0);
|
|
116
|
+
const disposers = [];
|
|
117
|
+
const dispose = () => {
|
|
118
|
+
for (const release of disposers.splice(0).reverse()) release();
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
disposers.push(ctx.tools.restrict({ allow: config.tools }));
|
|
122
|
+
const excess = ctx.tools.schemas(scopeKey).filter((tool) => !selected.has(tool.name));
|
|
123
|
+
if (excess.length) throw new Error(`Unselected scoped tools must be removed from preset plugins: ${excess.map((tool) => tool.name).join(", ")}`);
|
|
124
|
+
disposers.push(ctx.tools.guard((execution) => selected.has(execution.name) ? void 0 : "Tool excluded by Studio preset policy"));
|
|
125
|
+
for (const definition of definitions) disposers.push(ctx.tools.register(definition));
|
|
126
|
+
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 }));
|
|
127
|
+
disposers.push(ctx.systemPrompt.section({ name: "studio:additional", order: Number.MAX_SAFE_INTEGER, text: config.prompt, interpolate: false }));
|
|
128
|
+
return dispose;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
dispose();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
var name = "studio-preset-policy";
|
|
135
|
+
var inject = ["tools", "systemPrompt"];
|
|
136
|
+
function apply(ctx, config) {
|
|
137
|
+
validatePolicy(config);
|
|
138
|
+
const installed = /* @__PURE__ */ new Map();
|
|
139
|
+
ctx.on("agent/created", ({ agent }) => {
|
|
140
|
+
if (!installed.has(agent)) installed.set(agent, installPolicy(agent.ctx, agent, config));
|
|
141
|
+
return void 0;
|
|
142
|
+
});
|
|
143
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
144
|
+
installed.get(agent)?.();
|
|
145
|
+
installed.delete(agent);
|
|
146
|
+
});
|
|
147
|
+
ctx.effect(() => () => {
|
|
148
|
+
for (const dispose of installed.values()) dispose();
|
|
149
|
+
installed.clear();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
POLICY_SECTION_ORDERS,
|
|
154
|
+
apply,
|
|
155
|
+
inject,
|
|
156
|
+
installPolicy,
|
|
157
|
+
name,
|
|
158
|
+
validatePolicy
|
|
159
|
+
};
|
package/roles.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/host/role-validation.ts
|
|
2
|
+
import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
async function validateRoleModel(llm, role, signal) {
|
|
4
|
+
signal?.throwIfAborted();
|
|
5
|
+
if (role.model === "inherit") return;
|
|
6
|
+
if (!llm) throw new Error("Studio role model validation unavailable: LLM service is not mounted.");
|
|
7
|
+
const slash = role.model.indexOf("/");
|
|
8
|
+
const provider = role.model.slice(0, slash);
|
|
9
|
+
const model = role.model.slice(slash + 1);
|
|
10
|
+
if (!llm.listProviders().some((item) => item.id === provider)) {
|
|
11
|
+
throw new Error(`Studio role "${role.role}": provider "${provider}" is not registered. Select an available model in Studio, save and reapply the preset.`);
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
await llm.resolveCallConfig({
|
|
15
|
+
provider,
|
|
16
|
+
model,
|
|
17
|
+
...role.reasoning === "inherit" ? {} : { reasoningEffort: ReasoningEffortId(role.reasoning) }
|
|
18
|
+
}, signal);
|
|
19
|
+
} catch {
|
|
20
|
+
signal?.throwIfAborted();
|
|
21
|
+
throw new Error(`Studio role "${role.role}": could not validate model "${role.model}" and reasoning settings. Check provider availability and model settings, then retry. No child was started.`);
|
|
22
|
+
}
|
|
23
|
+
signal?.throwIfAborted();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/host/role-tool.ts
|
|
27
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
28
|
+
import { ReasoningEffortId as ReasoningEffortId2 } from "@deepseek-ai/dsh-llm";
|
|
29
|
+
|
|
30
|
+
// src/role-settings.ts
|
|
31
|
+
var object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
32
|
+
function parseRoles(input) {
|
|
33
|
+
if (!Array.isArray(input) || input.length > 50) throw new Error("Studio agents must be an array of at most 50 roles");
|
|
34
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35
|
+
return input.map((value) => {
|
|
36
|
+
if (!object(value)) throw new Error("Invalid Studio role");
|
|
37
|
+
const text = (key, fallback, max) => {
|
|
38
|
+
const v = value[key] ?? fallback;
|
|
39
|
+
if (typeof v !== "string" || v.length > max) throw new Error(`Invalid role ${key}`);
|
|
40
|
+
return key === "persona" ? v : v.trim();
|
|
41
|
+
};
|
|
42
|
+
const name2 = text("name", "", 120);
|
|
43
|
+
const role = text("role", name2, 120);
|
|
44
|
+
const model = text("model", "inherit", 512);
|
|
45
|
+
const reasoning = text("reasoning", "inherit", 120);
|
|
46
|
+
const spawn = text("spawn", "fresh", 10);
|
|
47
|
+
const persona = text("persona", "", 32e3);
|
|
48
|
+
if (!name2 || !role || seen.has(role)) throw new Error("Role names and unique role identifiers are required");
|
|
49
|
+
if (model !== "inherit" && !/^[^\s/]+\/\S+$/.test(model)) throw new Error("Role model must be inherit or provider/model");
|
|
50
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(reasoning)) throw new Error("Role reasoning must be inherit or an effort identifier");
|
|
51
|
+
if (spawn !== "fresh" && spawn !== "fork") throw new Error("Role spawn must be fresh or fork");
|
|
52
|
+
seen.add(role);
|
|
53
|
+
return { name: name2, role, model, reasoning, spawn, persona, ..."overrides" in value ? { overrides: structuredClone(value.overrides) } : {} };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function executableRoles(roles) {
|
|
57
|
+
return roles.map(({ name: name2, role, model, reasoning, spawn, persona }) => ({ name: name2, role, model, reasoning, spawn, persona }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/package-name.ts
|
|
61
|
+
var PACKAGE_NAME = "@matamt-dbg/dsh-studio";
|
|
62
|
+
var POLICY_PLUGIN = `${PACKAGE_NAME}/policy`;
|
|
63
|
+
var ROLE_PLUGIN = `${PACKAGE_NAME}/roles`;
|
|
64
|
+
var BATCH_PLUGIN = `${PACKAGE_NAME}/batch`;
|
|
65
|
+
|
|
66
|
+
// src/delegation.ts
|
|
67
|
+
var ROLE_TOOL = "subagent_role";
|
|
68
|
+
|
|
69
|
+
// src/host/role-tool.ts
|
|
70
|
+
var name = "studio-role";
|
|
71
|
+
var inject = ["tools", "subagents", "llm"];
|
|
72
|
+
var PARENT_ROLE = { name: "Subagent", role: "subagent", model: "inherit", reasoning: "inherit", spawn: "fresh", persona: "" };
|
|
73
|
+
function apply(ctx, config) {
|
|
74
|
+
const roles = executableRoles(parseRoles(config.roles));
|
|
75
|
+
ctx.tools.register(defineTool({
|
|
76
|
+
name: ROLE_TOOL,
|
|
77
|
+
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.",
|
|
78
|
+
parameters: {
|
|
79
|
+
action: { type: "string", enum: ["list", "call"], required: true, description: "list discovers role identifiers and settings; call delegates a task." },
|
|
80
|
+
role: { type: "string", description: roles.length ? "Saved role identifier, required for call." : "Not needed; no specialist roles are defined, so every call starts a child matching the parent." },
|
|
81
|
+
task: { type: "string", description: "Self-contained task for the specialist, required for call. Fork roles also inherit the conversation." }
|
|
82
|
+
},
|
|
83
|
+
output: {
|
|
84
|
+
schema: { type: "object", additionalProperties: false, properties: { result: { type: "string", required: true } } },
|
|
85
|
+
render: (_args, value) => [{ type: "text", text: value.result }]
|
|
86
|
+
},
|
|
87
|
+
isConcurrencySafe: () => true,
|
|
88
|
+
async execute(args, exec) {
|
|
89
|
+
if (args.action === "list") return { result: JSON.stringify({ roles: roles.map(({ persona: _persona, ...role2 }) => role2) }) };
|
|
90
|
+
if (args.action !== "call") throw new Error("Unknown Studio role action");
|
|
91
|
+
const role = roles.length ? roles.find((role2) => role2.role === args.role) : PARENT_ROLE;
|
|
92
|
+
if (!role) throw new Error("Unknown Studio role; use action=list to discover roles");
|
|
93
|
+
if (typeof args.task !== "string" || !args.task.trim()) throw new Error("Role call requires a task");
|
|
94
|
+
if (!exec.agent) throw new Error("Studio role call requires a calling agent");
|
|
95
|
+
exec.signal.throwIfAborted();
|
|
96
|
+
await validateRoleModel(ctx.llm, role, exec.signal);
|
|
97
|
+
const agentOptions = {};
|
|
98
|
+
if (role.model !== "inherit") {
|
|
99
|
+
const slash = role.model.indexOf("/");
|
|
100
|
+
agentOptions.provider = role.model.slice(0, slash);
|
|
101
|
+
agentOptions.model = role.model.slice(slash + 1);
|
|
102
|
+
}
|
|
103
|
+
if (role.reasoning !== "inherit") agentOptions.reasoningEffort = ReasoningEffortId2(role.reasoning);
|
|
104
|
+
const maxDepth = ctx.subagents.resolveMaxDepth();
|
|
105
|
+
const spec = {
|
|
106
|
+
provider: role.spawn === "fork" ? "fork" : "spawn",
|
|
107
|
+
label: role.name,
|
|
108
|
+
request: {
|
|
109
|
+
parent: exec.agent,
|
|
110
|
+
prompt: [{ type: "text", text: args.task }],
|
|
111
|
+
...Object.keys(agentOptions).length ? { agentOptions } : {},
|
|
112
|
+
...role.persona ? { persona: role.persona } : {},
|
|
113
|
+
...maxDepth === void 0 ? {} : { maxDepth }
|
|
114
|
+
},
|
|
115
|
+
signal: exec.signal
|
|
116
|
+
};
|
|
117
|
+
const native = ctx.subagents;
|
|
118
|
+
const started = typeof native.startActivation === "function" ? await native.startActivation({ ...spec, delivery: "parent" }) : native.startContinuable ? await native.startContinuable(spec) : (() => {
|
|
119
|
+
throw new Error("This DSH runtime lacks a native child start API");
|
|
120
|
+
})();
|
|
121
|
+
return { result: JSON.stringify({ kind: "activation", subagentId: started.childId, role: role.role }) };
|
|
122
|
+
}
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
apply,
|
|
127
|
+
inject,
|
|
128
|
+
name
|
|
129
|
+
};
|