@matamt-dbg/dsh-studio 0.2.2 → 0.2.7
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 +281 -174
- package/index.js +193 -60
- package/package.json +1 -1
- package/policy.js +135 -12
- package/roles.js +25 -3
package/index.js
CHANGED
|
@@ -62,6 +62,7 @@ var ROLE_TOOL = "subagent_role";
|
|
|
62
62
|
var LEGACY_ROLE_TOOL = "studio_role";
|
|
63
63
|
var GENERIC_DELEGATION_TOOLS = ["subagent", "subagent_fork", "list_subagent_models"];
|
|
64
64
|
var SUBAGENT_PLUGIN = "@deepseek-ai/dsh-tool-subagent";
|
|
65
|
+
var isDelegationPlugin = (name) => name === SUBAGENT_PLUGIN || name === ROLE_PLUGIN;
|
|
65
66
|
var WITHDRAWN_TOOLS = ["task_run"];
|
|
66
67
|
function delegationTools(tools, hasRoles = false) {
|
|
67
68
|
const delegates = hasRoles || tools.some((name) => name === ROLE_TOOL || name === LEGACY_ROLE_TOOL || GENERIC_DELEGATION_TOOLS.includes(name));
|
|
@@ -69,7 +70,7 @@ function delegationTools(tools, hasRoles = false) {
|
|
|
69
70
|
return delegates ? [...rest, ROLE_TOOL] : rest;
|
|
70
71
|
}
|
|
71
72
|
function withoutDelegationPlugins(rows) {
|
|
72
|
-
return rows.filter((row) => row.name
|
|
73
|
+
return rows.filter((row) => !isDelegationPlugin(row.name)).map((row) => row.group && Array.isArray(row.config) ? { ...row, config: withoutDelegationPlugins(row.config) } : row);
|
|
73
74
|
}
|
|
74
75
|
function renameLegacyRoleKey(entries) {
|
|
75
76
|
if (!entries || !Object.hasOwn(entries, LEGACY_ROLE_TOOL)) return entries;
|
|
@@ -91,7 +92,31 @@ function migratePolicyConfig(config) {
|
|
|
91
92
|
// src/optimized-preset.ts
|
|
92
93
|
var OPTIMIZED_PRESET_ID = "studio-optimized";
|
|
93
94
|
var BUILTIN_TOOL_PRESETS = ["standard", "ptc", "minimal", "cordis"];
|
|
94
|
-
var
|
|
95
|
+
var OPTIMIZED_TOOLS = [
|
|
96
|
+
"read_batch",
|
|
97
|
+
"read",
|
|
98
|
+
"write",
|
|
99
|
+
"edit",
|
|
100
|
+
"glob",
|
|
101
|
+
"grep",
|
|
102
|
+
"job_output",
|
|
103
|
+
"job_list",
|
|
104
|
+
"job_kill",
|
|
105
|
+
"skill",
|
|
106
|
+
"ask_user_question",
|
|
107
|
+
"web_search",
|
|
108
|
+
"web_fetch",
|
|
109
|
+
"present",
|
|
110
|
+
"bash",
|
|
111
|
+
"read_image",
|
|
112
|
+
"exit_plan_mode",
|
|
113
|
+
"send_message",
|
|
114
|
+
"interrupt_agent",
|
|
115
|
+
"list_agents",
|
|
116
|
+
"workflow",
|
|
117
|
+
"subagent_role"
|
|
118
|
+
];
|
|
119
|
+
var ALLOWED_TOOLS = /* @__PURE__ */ new Set([...OPTIMIZED_TOOLS, ...GENERIC_DELEGATION_TOOLS, LEGACY_ROLE_TOOL]);
|
|
95
120
|
var OPTIMIZED_PROMPT = `Use glob to discover paths, grep to search contents, and read to inspect files before editing. Prefer targeted edits.
|
|
96
121
|
Batch independent read-only inspections with read_batch. Start independent tool calls together; keep calls that depend on earlier results sequential. Do not guess unknown arguments.
|
|
97
122
|
Wait for required work to finish before validating it. Use background execution only for independent work or intentionally long-lived services. Do not duplicate running work or busy-poll it.
|
|
@@ -99,7 +124,7 @@ Track every job you start. Before reporting completion, collect all relevant res
|
|
|
99
124
|
Delegate self-contained independent work when useful and collect the result before relying on it.
|
|
100
125
|
Keep changes within the requested scope and preserve user work. Use the smallest sufficient checks for the requested outcome and risk, then stop when the work is complete.`;
|
|
101
126
|
function optimizedTemplate(tools) {
|
|
102
|
-
const selected = delegationTools(tools.filter((name) =>
|
|
127
|
+
const selected = delegationTools(tools.filter((name) => ALLOWED_TOOLS.has(name)));
|
|
103
128
|
const request = {
|
|
104
129
|
preset: "studio-master",
|
|
105
130
|
providers: ["@deepseek-ai/dsh-persona"],
|
|
@@ -378,7 +403,8 @@ var toolPromptSections = {
|
|
|
378
403
|
web_search: ["tool:web_search"],
|
|
379
404
|
get_goal: ["tool:goal"],
|
|
380
405
|
create_goal: ["tool:goal"],
|
|
381
|
-
update_goal: ["tool:goal"]
|
|
406
|
+
update_goal: ["tool:goal"],
|
|
407
|
+
workflow: ["tool:workflow"]
|
|
382
408
|
};
|
|
383
409
|
|
|
384
410
|
// src/preset-catalog.ts
|
|
@@ -491,6 +517,8 @@ function parsePreviewRequest(value) {
|
|
|
491
517
|
if (row.providerConfigs !== void 0 && (!record4(row.providerConfigs) || Object.values(row.providerConfigs).some((v) => !record4(v)))) throw new Error("Invalid provider configs");
|
|
492
518
|
if (row.configOverrides !== void 0) validateConfigOverrides(row.configOverrides);
|
|
493
519
|
if (row.execution !== void 0) validateExecutionDefaults(row.execution);
|
|
520
|
+
const filter = row.subagentToolFilter;
|
|
521
|
+
if (filter !== void 0 && (!record4(filter) || !strings(filter.deny) || filter.deny.some((name) => !name))) throw new Error("Invalid subagent tool filter");
|
|
494
522
|
return {
|
|
495
523
|
...row.configOverrides !== void 0 ? { configOverrides: row.configOverrides } : {},
|
|
496
524
|
...row.execution !== void 0 ? { execution: row.execution } : {},
|
|
@@ -502,7 +530,8 @@ function parsePreviewRequest(value) {
|
|
|
502
530
|
...row.persona !== void 0 ? { persona: row.persona } : {},
|
|
503
531
|
...row.sections !== void 0 ? { sections: row.sections } : {},
|
|
504
532
|
...row.toolOverrides !== void 0 ? { toolOverrides: row.toolOverrides } : {},
|
|
505
|
-
...row.providerConfigs !== void 0 ? { providerConfigs: row.providerConfigs } : {}
|
|
533
|
+
...row.providerConfigs !== void 0 ? { providerConfigs: row.providerConfigs } : {},
|
|
534
|
+
...filter !== void 0 && filter.deny.length ? { subagentToolFilter: { deny: [...new Set(filter.deny)] } } : {}
|
|
506
535
|
};
|
|
507
536
|
}
|
|
508
537
|
function promptProviders(rows) {
|
|
@@ -537,12 +566,26 @@ function parseComposition(content) {
|
|
|
537
566
|
function findProviders(rows) {
|
|
538
567
|
return rows.flatMap((row) => row.disabled === true ? [] : row.group && Array.isArray(row.config) ? findProviders(row.config) : promptProviderModules.includes(row.name) ? [row] : []);
|
|
539
568
|
}
|
|
569
|
+
function normalizeRetention(config, overrides) {
|
|
570
|
+
if (overrides?.retainTokens !== void 0 && overrides.retainRatio !== void 0) {
|
|
571
|
+
throw new Error("Compaction retainRatio and retainTokens are mutually exclusive");
|
|
572
|
+
}
|
|
573
|
+
if (overrides?.retainTokens !== void 0) delete config.retainRatio;
|
|
574
|
+
if (overrides?.retainRatio !== void 0) delete config.retainTokens;
|
|
575
|
+
}
|
|
540
576
|
async function compositionPlan(ctx, input) {
|
|
541
577
|
const request = parsePreviewRequest(input);
|
|
542
|
-
const base = request.preset
|
|
578
|
+
const base = request.preset;
|
|
543
579
|
if (request.configOverrides && base !== MASTER_PRESET_ID) throw new Error("Config overrides require Studio Master");
|
|
544
|
-
const
|
|
545
|
-
|
|
580
|
+
const source = applyConfigOverrides(parseComposition((await ctx.agentPresets.readDocument(base)).content), request.configOverrides ?? {});
|
|
581
|
+
if (request.configOverrides && Object.keys(request.configOverrides).length) {
|
|
582
|
+
for (const { id, row } of configEntries(source)) {
|
|
583
|
+
if (row.name === "@deepseek-ai/dsh-compaction-basic") {
|
|
584
|
+
normalizeRetention(row.config, request.configOverrides[id]);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
const available = new Map(findProviders(source).map((row) => [row.name, row]));
|
|
546
589
|
const presets = await ctx.agentPresets.list();
|
|
547
590
|
for (const preset of presets) {
|
|
548
591
|
if (base === MASTER_PRESET_ID || !["minimal", "standard", "ptc", "cordis"].includes(preset.id)) continue;
|
|
@@ -572,33 +615,21 @@ async function compositionPlan(ctx, input) {
|
|
|
572
615
|
if (request.persona !== void 0) config.prefix = request.persona;
|
|
573
616
|
}
|
|
574
617
|
if (name === "@deepseek-ai/dsh-compaction-basic") {
|
|
575
|
-
|
|
576
|
-
if (overrides?.retainRatio !== void 0 && overrides.retainTokens === void 0) delete config.retainTokens;
|
|
618
|
+
normalizeRetention(config, overrides);
|
|
577
619
|
}
|
|
578
620
|
return { ...row, id: `studio-provider-${index}`, config };
|
|
579
621
|
});
|
|
580
622
|
if (request.persona !== void 0 && !request.providers.includes("@deepseek-ai/dsh-persona")) throw new Error("Select persona before editing its prefix");
|
|
581
623
|
for (const name of Object.keys(request.providerConfigs ?? {})) if (!request.providers.includes(name)) throw new Error(`Unselected provider override: ${name}`);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
lease = await ctx.agentPresets.acquireScope(id);
|
|
591
|
-
const names2 = new Set(ctx.tools.schemas(lease.key).map((tool) => tool.name));
|
|
592
|
-
if (missing.every((name) => names2.has(name))) {
|
|
593
|
-
source = id === base ? minimal : parseComposition((await ctx.agentPresets.readDocument(id)).content);
|
|
594
|
-
break;
|
|
595
|
-
}
|
|
596
|
-
} catch {
|
|
597
|
-
} finally {
|
|
598
|
-
await lease?.[Symbol.asyncDispose]();
|
|
599
|
-
}
|
|
624
|
+
if (request.tools.length || base === MASTER_PRESET_ID) {
|
|
625
|
+
const lease = await ctx.agentPresets.acquireScope(base);
|
|
626
|
+
try {
|
|
627
|
+
const names2 = new Set(ctx.tools.schemas(lease.key).map((tool) => tool.name));
|
|
628
|
+
const missing = request.tools.filter((name) => !names2.has(name));
|
|
629
|
+
if (missing.length) throw new Error(`Requested preset ${base} does not supply tools: ${missing.join(", ")}. Tools from other source scopes cannot be substituted safely.`);
|
|
630
|
+
} finally {
|
|
631
|
+
await lease[Symbol.asyncDispose]();
|
|
600
632
|
}
|
|
601
|
-
if (!source) throw new Error(`No compatible installed tool composition supplies: ${missing.join(", ")}. Tools from incompatible source scopes cannot be combined safely.`);
|
|
602
633
|
plugins.push(...selectPromptProviders(source, []));
|
|
603
634
|
}
|
|
604
635
|
const compaction = plugins.filter((row) => ["@deepseek-ai/dsh-compaction-basic", "@deepseek-ai/dsh-compaction-tool-result-pruner"].includes(row.name));
|
|
@@ -608,7 +639,7 @@ async function compositionPlan(ctx, input) {
|
|
|
608
639
|
}
|
|
609
640
|
|
|
610
641
|
// src/host/preset-policy.ts
|
|
611
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
642
|
+
import { scopeChainOf, scopeOf } from "@deepseek-ai/dsh-scope";
|
|
612
643
|
var POLICY_SECTION_ORDERS = Object.freeze({
|
|
613
644
|
"tool:jobs": "TOOL_JOBS",
|
|
614
645
|
"tool:bash": "TOOL_BASH",
|
|
@@ -620,7 +651,8 @@ var POLICY_SECTION_ORDERS = Object.freeze({
|
|
|
620
651
|
"tool:grep": "TOOL_GREP",
|
|
621
652
|
"tool:web_fetch": "TOOL_WEB_FETCH",
|
|
622
653
|
"tool:web_search": "TOOL_WEB_SEARCH",
|
|
623
|
-
"tool:goal": "TOOL_GOAL"
|
|
654
|
+
"tool:goal": "TOOL_GOAL",
|
|
655
|
+
"tool:workflow": "TOOL_WORKFLOW"
|
|
624
656
|
});
|
|
625
657
|
var own2 = (object3, key) => Object.prototype.hasOwnProperty.call(object3, key);
|
|
626
658
|
var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -638,6 +670,11 @@ function validatePolicy(config) {
|
|
|
638
670
|
for (const [name, text] of Object.entries(config.sections)) {
|
|
639
671
|
if (!own2(POLICY_SECTION_ORDERS, name) || typeof text !== "string") throw new Error(`Unsupported policy section: ${name}`);
|
|
640
672
|
}
|
|
673
|
+
if (config.childToolFilter !== void 0) {
|
|
674
|
+
const deny = record3(config.childToolFilter) ? config.childToolFilter.deny : void 0;
|
|
675
|
+
if (!Array.isArray(deny) || deny.some((name) => typeof name !== "string" || !name)) throw new Error("Invalid subagent tool filter");
|
|
676
|
+
for (const name of deny) if (!config.tools.includes(name)) throw new Error(`Subagent tool filter requires selected tool: ${name}`);
|
|
677
|
+
}
|
|
641
678
|
}
|
|
642
679
|
function patchParameter(parameters, path, description) {
|
|
643
680
|
const segments = path.split(".");
|
|
@@ -651,11 +688,20 @@ function patchParameter(parameters, path, description) {
|
|
|
651
688
|
function installPolicy(ctx, scopeKey, config) {
|
|
652
689
|
validatePolicy(config);
|
|
653
690
|
if (scopeOf(ctx) !== scopeKey) throw new Error("Studio policy requires its owning scoped context");
|
|
691
|
+
if (config.tools.includes("run_code") || ctx.tools.get("run_code", scopeKey) !== void 0) {
|
|
692
|
+
throw new Error("Studio policy does not support PTC presentation or the reserved run_code tool; configure this preset with native tool presentation");
|
|
693
|
+
}
|
|
654
694
|
const missing = config.tools.filter((name) => ctx.tools.get(name, scopeKey) === void 0);
|
|
655
|
-
if (missing.length)
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
695
|
+
if (missing.length) throw new Error(`Studio policy selected tools unavailable: ${missing.join(", ")}`);
|
|
696
|
+
const restoreProbe = ctx.tools.restrict({ allow: [] });
|
|
697
|
+
try {
|
|
698
|
+
const local = ctx.tools.schemas(scopeKey);
|
|
699
|
+
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`);
|
|
700
|
+
} finally {
|
|
701
|
+
restoreProbe();
|
|
702
|
+
}
|
|
703
|
+
const selected = new Set(config.tools);
|
|
704
|
+
const definitions = config.tools.map((name) => {
|
|
659
705
|
const definition = ctx.tools.get(name, scopeKey);
|
|
660
706
|
const override = config.toolOverrides[name];
|
|
661
707
|
const defaults = config.execution?.defaults[name];
|
|
@@ -689,9 +735,7 @@ function installPolicy(ctx, scopeKey, config) {
|
|
|
689
735
|
for (const release of disposers.splice(0).reverse()) release();
|
|
690
736
|
};
|
|
691
737
|
try {
|
|
692
|
-
disposers.push(ctx.tools.restrict({ allow:
|
|
693
|
-
const excess = ctx.tools.schemas(scopeKey).filter((tool) => !selected.has(tool.name));
|
|
694
|
-
if (excess.length) throw new Error(`Unselected scoped tools must be removed from preset plugins: ${excess.map((tool) => tool.name).join(", ")}`);
|
|
738
|
+
disposers.push(ctx.tools.restrict({ allow: [...selected] }));
|
|
695
739
|
disposers.push(ctx.tools.guard((execution) => selected.has(execution.name) ? void 0 : "Tool excluded by Studio preset policy"));
|
|
696
740
|
for (const definition of definitions) disposers.push(ctx.tools.register(definition));
|
|
697
741
|
for (const [name, text] of Object.entries(config.sections)) disposers.push(ctx.systemPrompt.section({ name, order: ctx.systemPrompt.getSectionOrder(POLICY_SECTION_ORDERS[name]), text, interpolate: false }));
|
|
@@ -758,6 +802,7 @@ function executionCapabilities(tools) {
|
|
|
758
802
|
}
|
|
759
803
|
|
|
760
804
|
// src/host/preset-store.ts
|
|
805
|
+
import { createScope as createScope2 } from "@deepseek-ai/dsh-scope";
|
|
761
806
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
762
807
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, rename as rename2 } from "node:fs/promises";
|
|
763
808
|
import { dirname as dirname2, join } from "node:path";
|
|
@@ -787,11 +832,27 @@ function parseRoles(input) {
|
|
|
787
832
|
if (!/^[a-zA-Z0-9_-]+$/.test(reasoning)) throw new Error("Role reasoning must be inherit or an effort identifier");
|
|
788
833
|
if (spawn !== "fresh" && spawn !== "fork") throw new Error("Role spawn must be fresh or fork");
|
|
789
834
|
seen.add(role);
|
|
790
|
-
|
|
835
|
+
const filter = value.toolFilter;
|
|
836
|
+
if (filter !== void 0 && (!object2(filter) || !Array.isArray(filter.deny) || filter.deny.some((name2) => typeof name2 !== "string" || !name2) || filter.mode !== void 0 && filter.mode !== "extend" && filter.mode !== "replace")) throw new Error("Invalid role tool filter");
|
|
837
|
+
return {
|
|
838
|
+
name,
|
|
839
|
+
role,
|
|
840
|
+
model,
|
|
841
|
+
reasoning,
|
|
842
|
+
spawn,
|
|
843
|
+
persona,
|
|
844
|
+
...filter !== void 0 ? { toolFilter: { mode: filter.mode ?? "replace", deny: [...new Set(filter.deny)] } } : {},
|
|
845
|
+
..."overrides" in value ? { overrides: structuredClone(value.overrides) } : {}
|
|
846
|
+
};
|
|
791
847
|
});
|
|
792
848
|
}
|
|
793
849
|
function executableRoles(roles) {
|
|
794
|
-
return roles.map(({ name, role, model, reasoning, spawn, persona }) => ({ name, role, model, reasoning, spawn, persona }));
|
|
850
|
+
return roles.map(({ name, role, model, reasoning, spawn, persona, toolFilter }) => ({ name, role, model, reasoning, spawn, persona, ...toolFilter ? { toolFilter } : {} }));
|
|
851
|
+
}
|
|
852
|
+
function roleToolFilter(role, presetDefault = []) {
|
|
853
|
+
const own3 = role?.toolFilter;
|
|
854
|
+
if (!own3) return [...presetDefault];
|
|
855
|
+
return own3.mode === "replace" ? [...own3.deny] : [.../* @__PURE__ */ new Set([...presetDefault, ...own3.deny])];
|
|
795
856
|
}
|
|
796
857
|
|
|
797
858
|
// src/host/role-validation.ts
|
|
@@ -821,7 +882,7 @@ async function validateRoleModel(llm, role, signal) {
|
|
|
821
882
|
|
|
822
883
|
// src/host/preset-store.ts
|
|
823
884
|
var owned = (id) => id === OPTIMIZED_PRESET_ID || /^studio-user-[a-f0-9-]{36}$/.test(id);
|
|
824
|
-
var
|
|
885
|
+
var retiredLegacyWarning = "Work convenience settings are saved but not applied yet. Legacy role overrides are stored only; roles apply only their supported settings.";
|
|
825
886
|
function migrateLegacyNames(row) {
|
|
826
887
|
const request = row.request;
|
|
827
888
|
if (request.toolOverrides) {
|
|
@@ -864,6 +925,7 @@ var PresetStore = class {
|
|
|
864
925
|
if (!owned(row.id)) throw new Error("Invalid Studio preset ownership");
|
|
865
926
|
parsePreviewRequest(row.request);
|
|
866
927
|
migrateLegacyNames(row);
|
|
928
|
+
if (row.applyWarning === retiredLegacyWarning) delete row.applyWarning;
|
|
867
929
|
}
|
|
868
930
|
} catch (error) {
|
|
869
931
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -879,10 +941,32 @@ var PresetStore = class {
|
|
|
879
941
|
})();
|
|
880
942
|
}
|
|
881
943
|
async mount(id, definition) {
|
|
882
|
-
const
|
|
944
|
+
const plugins = parseComposition(definition.plugins);
|
|
945
|
+
const dispose = await this.ctx.agentPresets.register({ id, name: definition.name, description: definition.description, plugins });
|
|
883
946
|
try {
|
|
884
947
|
const preset = await this.ctx.agentPresets.resolve(id);
|
|
885
948
|
if (preset.broken) throw new Error(preset.broken);
|
|
949
|
+
const policies = (rows) => rows.flatMap((row) => row.disabled === true ? [] : row.group && Array.isArray(row.config) ? policies(row.config) : row.name === POLICY_PLUGIN ? [row.config] : []);
|
|
950
|
+
const configs = policies(plugins);
|
|
951
|
+
if (configs.length) {
|
|
952
|
+
const lease = await this.ctx.agentPresets.acquireScope(id);
|
|
953
|
+
try {
|
|
954
|
+
const key = {};
|
|
955
|
+
const child = createScope2(this.ctx, key, { parent: lease.key });
|
|
956
|
+
const releases = [];
|
|
957
|
+
try {
|
|
958
|
+
for (const config of configs) releases.push(installPolicy(child.ctx, key, config));
|
|
959
|
+
} finally {
|
|
960
|
+
try {
|
|
961
|
+
for (const release of releases.reverse()) release();
|
|
962
|
+
} finally {
|
|
963
|
+
await child.dispose();
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
} finally {
|
|
967
|
+
await lease[Symbol.asyncDispose]();
|
|
968
|
+
}
|
|
969
|
+
}
|
|
886
970
|
this.disposers.set(id, dispose);
|
|
887
971
|
} catch (error) {
|
|
888
972
|
await dispose();
|
|
@@ -909,27 +993,72 @@ var PresetStore = class {
|
|
|
909
993
|
const { applied, ...value } = row;
|
|
910
994
|
return structuredClone({ ...value, ...row.id === OPTIMIZED_PRESET_ID ? { template: true, nonDeletable: true } : {} });
|
|
911
995
|
}
|
|
912
|
-
/**
|
|
996
|
+
/** Compose the Optimized applied snapshot: the template request over the current base composition. */
|
|
997
|
+
optimizedApplied(request, plugins) {
|
|
998
|
+
const delegates = request.tools.includes(ROLE_TOOL);
|
|
999
|
+
const base = applyConfigOverrides(plugins, request.configOverrides ?? {});
|
|
1000
|
+
return dump([...delegates ? [...withoutDelegationPlugins(base), { id: "studio-roles", name: ROLE_PLUGIN, config: { roles: [] } }] : base, { id: "studio-optimized-policy", name: POLICY_PLUGIN, config: { tools: request.tools, toolOverrides: request.toolOverrides, sections: {}, prompt: request.prompt, execution: request.execution } }], { schema: entryListSchema2, noRefs: true });
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Seed the built-in on first run, then keep it aligned with the shipped template. Optimized is
|
|
1004
|
+
* locked against editing, so its stored request carries no user content — only the applied or
|
|
1005
|
+
* unapplied choice is the user's, and that survives. Without the alignment a profile plugin that
|
|
1006
|
+
* registers a new tool would enlarge the preset forever after its first seed.
|
|
1007
|
+
*/
|
|
913
1008
|
ensureOptimized(tools, plugins) {
|
|
914
1009
|
return this.serial(async () => {
|
|
915
|
-
const existing = this.rows.find((row2) => row2.id === OPTIMIZED_PRESET_ID);
|
|
916
|
-
if (existing) return this.public(existing);
|
|
917
1010
|
const template = optimizedTemplate(tools);
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
const base = applyConfigOverrides(plugins, request.configOverrides ?? {});
|
|
921
|
-
const applied = { name: template.name, description: template.description, plugins: dump([...delegates ? [...withoutDelegationPlugins(base), { id: "studio-roles", name: ROLE_PLUGIN, config: { roles: [] } }] : base, { id: "studio-optimized-policy", name: POLICY_PLUGIN, config: { tools: request.tools, toolOverrides: request.toolOverrides, sections: {}, prompt: request.prompt, execution: request.execution } }], { schema: entryListSchema2, noRefs: true }) };
|
|
1011
|
+
const existing = this.rows.find((row) => row.id === OPTIMIZED_PRESET_ID);
|
|
1012
|
+
if (existing && JSON.stringify(existing.request) === JSON.stringify(template.request)) return this.public(existing);
|
|
922
1013
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
923
|
-
const
|
|
924
|
-
|
|
1014
|
+
const buildApplied = () => ({ name: template.name, description: template.description, plugins: this.optimizedApplied(template.request, plugins) });
|
|
1015
|
+
if (!existing) {
|
|
1016
|
+
const applied = buildApplied();
|
|
1017
|
+
const row = { id: template.id, name: template.name, description: template.description, request: template.request, conveniences: {}, agents: [], updatedAt: now, appliedAt: now, status: "applied", applied, ...template.applyWarning ? { applyWarning: template.applyWarning } : {} };
|
|
1018
|
+
await this.mount(row.id, applied);
|
|
1019
|
+
try {
|
|
1020
|
+
await this.persist([...this.rows, row]);
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
await this.disposers.get(row.id)?.();
|
|
1023
|
+
this.disposers.delete(row.id);
|
|
1024
|
+
throw error;
|
|
1025
|
+
}
|
|
1026
|
+
return this.public(row);
|
|
1027
|
+
}
|
|
1028
|
+
const refresh = { ...existing, name: template.name, description: template.description, request: template.request, updatedAt: now };
|
|
1029
|
+
if (template.applyWarning) refresh.applyWarning = template.applyWarning;
|
|
1030
|
+
else delete refresh.applyWarning;
|
|
1031
|
+
if (existing.applied) {
|
|
1032
|
+
const applied = buildApplied();
|
|
1033
|
+
await this.disposers.get(existing.id)?.();
|
|
1034
|
+
this.disposers.delete(existing.id);
|
|
1035
|
+
try {
|
|
1036
|
+
await this.mount(existing.id, applied);
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
try {
|
|
1039
|
+
await this.mount(existing.id, existing.applied);
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
1044
|
+
refresh.applied = applied;
|
|
1045
|
+
refresh.appliedAt = now;
|
|
1046
|
+
refresh.status = "applied";
|
|
1047
|
+
}
|
|
925
1048
|
try {
|
|
926
|
-
await this.persist(
|
|
1049
|
+
await this.persist(this.rows.map((row) => row.id === existing.id ? refresh : row));
|
|
927
1050
|
} catch (error) {
|
|
928
|
-
await this.disposers.get(
|
|
929
|
-
this.disposers.delete(
|
|
1051
|
+
await this.disposers.get(existing.id)?.();
|
|
1052
|
+
this.disposers.delete(existing.id);
|
|
1053
|
+
if (existing.applied) {
|
|
1054
|
+
try {
|
|
1055
|
+
await this.mount(existing.id, existing.applied);
|
|
1056
|
+
} catch {
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
930
1059
|
throw error;
|
|
931
1060
|
}
|
|
932
|
-
return this.public(
|
|
1061
|
+
return this.public(refresh);
|
|
933
1062
|
});
|
|
934
1063
|
}
|
|
935
1064
|
list() {
|
|
@@ -974,21 +1103,25 @@ var PresetStore = class {
|
|
|
974
1103
|
for (const role of roles) await validateRoleModel(this.ctx.get("llm"), role);
|
|
975
1104
|
const delegates = tools.includes(ROLE_TOOL);
|
|
976
1105
|
const keep = (entries) => Object.fromEntries(Object.entries(entries).filter(([name]) => tools.includes(name)));
|
|
977
|
-
const
|
|
1106
|
+
const presetHidden = old.request.subagentToolFilter?.deny ?? [];
|
|
1107
|
+
const lists = [presetHidden, ...roles.map((role) => roleToolFilter(role, presetHidden))];
|
|
1108
|
+
const unknownHidden = [...new Set(lists.flat().filter((name) => !tools.includes(name) || name === ROLE_TOOL))];
|
|
1109
|
+
if (delegates && unknownHidden.length) throw new Error(`Subagent Tool Filter may only list tools selected in this preset: ${unknownHidden.join(", ")}`);
|
|
1110
|
+
const hideable = delegates ? [...new Set(lists.flat())] : [];
|
|
1111
|
+
const childToolFilter = hideable.length ? { deny: hideable } : void 0;
|
|
1112
|
+
const rolePlugins = delegates ? [{ id: "studio-roles", name: ROLE_PLUGIN, config: { roles, ...presetHidden.length ? { toolFilter: { deny: [...presetHidden] } } : {} } }] : [];
|
|
978
1113
|
const execution = request.execution && {
|
|
979
1114
|
...request.execution,
|
|
980
1115
|
defaults: keep(request.execution.defaults),
|
|
981
1116
|
...request.execution.batch && !tools.includes(request.execution.batch.tool) ? { batch: void 0 } : {}
|
|
982
1117
|
};
|
|
983
|
-
const applied = { name: old.name, description: old.description, plugins: dump([...withoutDelegationPlugins(plugins), ...rolePlugins, { id: "studio-policy", name: POLICY_PLUGIN, config: { tools: [...new Set(tools)], toolOverrides: keep(request.toolOverrides ?? {}), sections: request.sections ?? {}, prompt: request.prompt, ...execution ? { execution } : {} } }], { schema: entryListSchema2, noRefs: true }) };
|
|
1118
|
+
const applied = { name: old.name, description: old.description, plugins: dump([...withoutDelegationPlugins(plugins), ...rolePlugins, { id: "studio-policy", name: POLICY_PLUGIN, config: { tools: [...new Set(tools)], toolOverrides: keep(request.toolOverrides ?? {}), sections: request.sections ?? {}, prompt: request.prompt, ...execution ? { execution } : {}, ...childToolFilter ? { childToolFilter } : {} } }], { schema: entryListSchema2, noRefs: true }) };
|
|
984
1119
|
await this.disposers.get(id)?.();
|
|
985
1120
|
this.disposers.delete(id);
|
|
986
1121
|
try {
|
|
987
1122
|
await this.mount(id, applied);
|
|
988
|
-
const hasLegacySettings = old.conveniences != null && Object.keys(Object(old.conveniences)).length > 0 || Array.isArray(old.agents) && old.agents.some((role) => role && Object.hasOwn(role, "overrides"));
|
|
989
1123
|
const row = { ...old, applied, appliedAt: (/* @__PURE__ */ new Date()).toISOString(), status: "applied" };
|
|
990
|
-
|
|
991
|
-
else delete row.applyWarning;
|
|
1124
|
+
delete row.applyWarning;
|
|
992
1125
|
await this.persist(this.rows.map((item) => item.id === id ? row : item));
|
|
993
1126
|
return this.public(row);
|
|
994
1127
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matamt-dbg/dsh-studio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "DSH Studio: build, edit and apply DeepSeek Harness agent presets — tools, prompts, Configs and role-based subagents — from the web GUI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
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,
|