@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/index.js
CHANGED
|
@@ -56,12 +56,14 @@ var BATCH_PLUGIN = `${PACKAGE_NAME}/batch`;
|
|
|
56
56
|
function currentPluginName(name) {
|
|
57
57
|
return name === LEGACY_PACKAGE_NAME || name.startsWith(`${LEGACY_PACKAGE_NAME}/`) ? PACKAGE_NAME + name.slice(LEGACY_PACKAGE_NAME.length) : name;
|
|
58
58
|
}
|
|
59
|
+
var STUDIO_VERSION = "0.2.8";
|
|
59
60
|
|
|
60
61
|
// src/delegation.ts
|
|
61
62
|
var ROLE_TOOL = "subagent_role";
|
|
62
63
|
var LEGACY_ROLE_TOOL = "studio_role";
|
|
63
64
|
var GENERIC_DELEGATION_TOOLS = ["subagent", "subagent_fork", "list_subagent_models"];
|
|
64
65
|
var SUBAGENT_PLUGIN = "@deepseek-ai/dsh-tool-subagent";
|
|
66
|
+
var isDelegationPlugin = (name) => name === SUBAGENT_PLUGIN || name === ROLE_PLUGIN;
|
|
65
67
|
var WITHDRAWN_TOOLS = ["task_run"];
|
|
66
68
|
function delegationTools(tools, hasRoles = false) {
|
|
67
69
|
const delegates = hasRoles || tools.some((name) => name === ROLE_TOOL || name === LEGACY_ROLE_TOOL || GENERIC_DELEGATION_TOOLS.includes(name));
|
|
@@ -69,7 +71,7 @@ function delegationTools(tools, hasRoles = false) {
|
|
|
69
71
|
return delegates ? [...rest, ROLE_TOOL] : rest;
|
|
70
72
|
}
|
|
71
73
|
function withoutDelegationPlugins(rows) {
|
|
72
|
-
return rows.filter((row) => row.name
|
|
74
|
+
return rows.filter((row) => !isDelegationPlugin(row.name)).map((row) => row.group && Array.isArray(row.config) ? { ...row, config: withoutDelegationPlugins(row.config) } : row);
|
|
73
75
|
}
|
|
74
76
|
function renameLegacyRoleKey(entries) {
|
|
75
77
|
if (!entries || !Object.hasOwn(entries, LEGACY_ROLE_TOOL)) return entries;
|
|
@@ -91,7 +93,31 @@ function migratePolicyConfig(config) {
|
|
|
91
93
|
// src/optimized-preset.ts
|
|
92
94
|
var OPTIMIZED_PRESET_ID = "studio-optimized";
|
|
93
95
|
var BUILTIN_TOOL_PRESETS = ["standard", "ptc", "minimal", "cordis"];
|
|
94
|
-
var
|
|
96
|
+
var OPTIMIZED_TOOLS = [
|
|
97
|
+
"read_batch",
|
|
98
|
+
"read",
|
|
99
|
+
"write",
|
|
100
|
+
"edit",
|
|
101
|
+
"glob",
|
|
102
|
+
"grep",
|
|
103
|
+
"job_output",
|
|
104
|
+
"job_list",
|
|
105
|
+
"job_kill",
|
|
106
|
+
"skill",
|
|
107
|
+
"ask_user_question",
|
|
108
|
+
"web_search",
|
|
109
|
+
"web_fetch",
|
|
110
|
+
"present",
|
|
111
|
+
"bash",
|
|
112
|
+
"read_image",
|
|
113
|
+
"exit_plan_mode",
|
|
114
|
+
"send_message",
|
|
115
|
+
"interrupt_agent",
|
|
116
|
+
"list_agents",
|
|
117
|
+
"workflow",
|
|
118
|
+
"subagent_role"
|
|
119
|
+
];
|
|
120
|
+
var ALLOWED_TOOLS = /* @__PURE__ */ new Set([...OPTIMIZED_TOOLS, ...GENERIC_DELEGATION_TOOLS, LEGACY_ROLE_TOOL]);
|
|
95
121
|
var OPTIMIZED_PROMPT = `Use glob to discover paths, grep to search contents, and read to inspect files before editing. Prefer targeted edits.
|
|
96
122
|
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
123
|
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 +125,7 @@ Track every job you start. Before reporting completion, collect all relevant res
|
|
|
99
125
|
Delegate self-contained independent work when useful and collect the result before relying on it.
|
|
100
126
|
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
127
|
function optimizedTemplate(tools) {
|
|
102
|
-
const selected = delegationTools(tools.filter((name) =>
|
|
128
|
+
const selected = delegationTools(tools.filter((name) => ALLOWED_TOOLS.has(name)));
|
|
103
129
|
const request = {
|
|
104
130
|
preset: "studio-master",
|
|
105
131
|
providers: ["@deepseek-ai/dsh-persona"],
|
|
@@ -358,7 +384,7 @@ var MasterExtensions = class {
|
|
|
358
384
|
};
|
|
359
385
|
|
|
360
386
|
// src/host/shell.ts
|
|
361
|
-
import { dirname as dirname3, join as
|
|
387
|
+
import { dirname as dirname3, join as join3 } from "node:path";
|
|
362
388
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
363
389
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
364
390
|
|
|
@@ -378,7 +404,8 @@ var toolPromptSections = {
|
|
|
378
404
|
web_search: ["tool:web_search"],
|
|
379
405
|
get_goal: ["tool:goal"],
|
|
380
406
|
create_goal: ["tool:goal"],
|
|
381
|
-
update_goal: ["tool:goal"]
|
|
407
|
+
update_goal: ["tool:goal"],
|
|
408
|
+
workflow: ["tool:workflow"]
|
|
382
409
|
};
|
|
383
410
|
|
|
384
411
|
// src/preset-catalog.ts
|
|
@@ -491,6 +518,8 @@ function parsePreviewRequest(value) {
|
|
|
491
518
|
if (row.providerConfigs !== void 0 && (!record4(row.providerConfigs) || Object.values(row.providerConfigs).some((v) => !record4(v)))) throw new Error("Invalid provider configs");
|
|
492
519
|
if (row.configOverrides !== void 0) validateConfigOverrides(row.configOverrides);
|
|
493
520
|
if (row.execution !== void 0) validateExecutionDefaults(row.execution);
|
|
521
|
+
const filter = row.subagentToolFilter;
|
|
522
|
+
if (filter !== void 0 && (!record4(filter) || !strings(filter.deny) || filter.deny.some((name) => !name))) throw new Error("Invalid subagent tool filter");
|
|
494
523
|
return {
|
|
495
524
|
...row.configOverrides !== void 0 ? { configOverrides: row.configOverrides } : {},
|
|
496
525
|
...row.execution !== void 0 ? { execution: row.execution } : {},
|
|
@@ -502,7 +531,8 @@ function parsePreviewRequest(value) {
|
|
|
502
531
|
...row.persona !== void 0 ? { persona: row.persona } : {},
|
|
503
532
|
...row.sections !== void 0 ? { sections: row.sections } : {},
|
|
504
533
|
...row.toolOverrides !== void 0 ? { toolOverrides: row.toolOverrides } : {},
|
|
505
|
-
...row.providerConfigs !== void 0 ? { providerConfigs: row.providerConfigs } : {}
|
|
534
|
+
...row.providerConfigs !== void 0 ? { providerConfigs: row.providerConfigs } : {},
|
|
535
|
+
...filter !== void 0 && filter.deny.length ? { subagentToolFilter: { deny: [...new Set(filter.deny)] } } : {}
|
|
506
536
|
};
|
|
507
537
|
}
|
|
508
538
|
function promptProviders(rows) {
|
|
@@ -537,12 +567,26 @@ function parseComposition(content) {
|
|
|
537
567
|
function findProviders(rows) {
|
|
538
568
|
return rows.flatMap((row) => row.disabled === true ? [] : row.group && Array.isArray(row.config) ? findProviders(row.config) : promptProviderModules.includes(row.name) ? [row] : []);
|
|
539
569
|
}
|
|
570
|
+
function normalizeRetention(config, overrides) {
|
|
571
|
+
if (overrides?.retainTokens !== void 0 && overrides.retainRatio !== void 0) {
|
|
572
|
+
throw new Error("Compaction retainRatio and retainTokens are mutually exclusive");
|
|
573
|
+
}
|
|
574
|
+
if (overrides?.retainTokens !== void 0) delete config.retainRatio;
|
|
575
|
+
if (overrides?.retainRatio !== void 0) delete config.retainTokens;
|
|
576
|
+
}
|
|
540
577
|
async function compositionPlan(ctx, input) {
|
|
541
578
|
const request = parsePreviewRequest(input);
|
|
542
|
-
const base = request.preset
|
|
579
|
+
const base = request.preset;
|
|
543
580
|
if (request.configOverrides && base !== MASTER_PRESET_ID) throw new Error("Config overrides require Studio Master");
|
|
544
|
-
const
|
|
545
|
-
|
|
581
|
+
const source = applyConfigOverrides(parseComposition((await ctx.agentPresets.readDocument(base)).content), request.configOverrides ?? {});
|
|
582
|
+
if (request.configOverrides && Object.keys(request.configOverrides).length) {
|
|
583
|
+
for (const { id, row } of configEntries(source)) {
|
|
584
|
+
if (row.name === "@deepseek-ai/dsh-compaction-basic") {
|
|
585
|
+
normalizeRetention(row.config, request.configOverrides[id]);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const available = new Map(findProviders(source).map((row) => [row.name, row]));
|
|
546
590
|
const presets = await ctx.agentPresets.list();
|
|
547
591
|
for (const preset of presets) {
|
|
548
592
|
if (base === MASTER_PRESET_ID || !["minimal", "standard", "ptc", "cordis"].includes(preset.id)) continue;
|
|
@@ -572,33 +616,21 @@ async function compositionPlan(ctx, input) {
|
|
|
572
616
|
if (request.persona !== void 0) config.prefix = request.persona;
|
|
573
617
|
}
|
|
574
618
|
if (name === "@deepseek-ai/dsh-compaction-basic") {
|
|
575
|
-
|
|
576
|
-
if (overrides?.retainRatio !== void 0 && overrides.retainTokens === void 0) delete config.retainTokens;
|
|
619
|
+
normalizeRetention(config, overrides);
|
|
577
620
|
}
|
|
578
621
|
return { ...row, id: `studio-provider-${index}`, config };
|
|
579
622
|
});
|
|
580
623
|
if (request.persona !== void 0 && !request.providers.includes("@deepseek-ai/dsh-persona")) throw new Error("Select persona before editing its prefix");
|
|
581
624
|
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
|
-
}
|
|
625
|
+
if (request.tools.length || base === MASTER_PRESET_ID) {
|
|
626
|
+
const lease = await ctx.agentPresets.acquireScope(base);
|
|
627
|
+
try {
|
|
628
|
+
const names2 = new Set(ctx.tools.schemas(lease.key).map((tool) => tool.name));
|
|
629
|
+
const missing = request.tools.filter((name) => !names2.has(name));
|
|
630
|
+
if (missing.length) throw new Error(`Requested preset ${base} does not supply tools: ${missing.join(", ")}. Tools from other source scopes cannot be substituted safely.`);
|
|
631
|
+
} finally {
|
|
632
|
+
await lease[Symbol.asyncDispose]();
|
|
600
633
|
}
|
|
601
|
-
if (!source) throw new Error(`No compatible installed tool composition supplies: ${missing.join(", ")}. Tools from incompatible source scopes cannot be combined safely.`);
|
|
602
634
|
plugins.push(...selectPromptProviders(source, []));
|
|
603
635
|
}
|
|
604
636
|
const compaction = plugins.filter((row) => ["@deepseek-ai/dsh-compaction-basic", "@deepseek-ai/dsh-compaction-tool-result-pruner"].includes(row.name));
|
|
@@ -608,7 +640,7 @@ async function compositionPlan(ctx, input) {
|
|
|
608
640
|
}
|
|
609
641
|
|
|
610
642
|
// src/host/preset-policy.ts
|
|
611
|
-
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
643
|
+
import { scopeChainOf, scopeOf } from "@deepseek-ai/dsh-scope";
|
|
612
644
|
var POLICY_SECTION_ORDERS = Object.freeze({
|
|
613
645
|
"tool:jobs": "TOOL_JOBS",
|
|
614
646
|
"tool:bash": "TOOL_BASH",
|
|
@@ -620,7 +652,8 @@ var POLICY_SECTION_ORDERS = Object.freeze({
|
|
|
620
652
|
"tool:grep": "TOOL_GREP",
|
|
621
653
|
"tool:web_fetch": "TOOL_WEB_FETCH",
|
|
622
654
|
"tool:web_search": "TOOL_WEB_SEARCH",
|
|
623
|
-
"tool:goal": "TOOL_GOAL"
|
|
655
|
+
"tool:goal": "TOOL_GOAL",
|
|
656
|
+
"tool:workflow": "TOOL_WORKFLOW"
|
|
624
657
|
});
|
|
625
658
|
var own2 = (object3, key) => Object.prototype.hasOwnProperty.call(object3, key);
|
|
626
659
|
var record3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -638,6 +671,11 @@ function validatePolicy(config) {
|
|
|
638
671
|
for (const [name, text] of Object.entries(config.sections)) {
|
|
639
672
|
if (!own2(POLICY_SECTION_ORDERS, name) || typeof text !== "string") throw new Error(`Unsupported policy section: ${name}`);
|
|
640
673
|
}
|
|
674
|
+
if (config.childToolFilter !== void 0) {
|
|
675
|
+
const deny = record3(config.childToolFilter) ? config.childToolFilter.deny : void 0;
|
|
676
|
+
if (!Array.isArray(deny) || deny.some((name) => typeof name !== "string" || !name)) throw new Error("Invalid subagent tool filter");
|
|
677
|
+
for (const name of deny) if (!config.tools.includes(name)) throw new Error(`Subagent tool filter requires selected tool: ${name}`);
|
|
678
|
+
}
|
|
641
679
|
}
|
|
642
680
|
function patchParameter(parameters, path, description) {
|
|
643
681
|
const segments = path.split(".");
|
|
@@ -651,11 +689,20 @@ function patchParameter(parameters, path, description) {
|
|
|
651
689
|
function installPolicy(ctx, scopeKey, config) {
|
|
652
690
|
validatePolicy(config);
|
|
653
691
|
if (scopeOf(ctx) !== scopeKey) throw new Error("Studio policy requires its owning scoped context");
|
|
692
|
+
if (config.tools.includes("run_code") || ctx.tools.get("run_code", scopeKey) !== void 0) {
|
|
693
|
+
throw new Error("Studio policy does not support PTC presentation or the reserved run_code tool; configure this preset with native tool presentation");
|
|
694
|
+
}
|
|
654
695
|
const missing = config.tools.filter((name) => ctx.tools.get(name, scopeKey) === void 0);
|
|
655
|
-
if (missing.length)
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
696
|
+
if (missing.length) throw new Error(`Studio policy selected tools unavailable: ${missing.join(", ")}`);
|
|
697
|
+
const restoreProbe = ctx.tools.restrict({ allow: [] });
|
|
698
|
+
try {
|
|
699
|
+
const local = ctx.tools.schemas(scopeKey);
|
|
700
|
+
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`);
|
|
701
|
+
} finally {
|
|
702
|
+
restoreProbe();
|
|
703
|
+
}
|
|
704
|
+
const selected = new Set(config.tools);
|
|
705
|
+
const definitions = config.tools.map((name) => {
|
|
659
706
|
const definition = ctx.tools.get(name, scopeKey);
|
|
660
707
|
const override = config.toolOverrides[name];
|
|
661
708
|
const defaults = config.execution?.defaults[name];
|
|
@@ -689,9 +736,7 @@ function installPolicy(ctx, scopeKey, config) {
|
|
|
689
736
|
for (const release of disposers.splice(0).reverse()) release();
|
|
690
737
|
};
|
|
691
738
|
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(", ")}`);
|
|
739
|
+
disposers.push(ctx.tools.restrict({ allow: [...selected] }));
|
|
695
740
|
disposers.push(ctx.tools.guard((execution) => selected.has(execution.name) ? void 0 : "Tool excluded by Studio preset policy"));
|
|
696
741
|
for (const definition of definitions) disposers.push(ctx.tools.register(definition));
|
|
697
742
|
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 +803,7 @@ function executionCapabilities(tools) {
|
|
|
758
803
|
}
|
|
759
804
|
|
|
760
805
|
// src/host/preset-store.ts
|
|
806
|
+
import { createScope as createScope2 } from "@deepseek-ai/dsh-scope";
|
|
761
807
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
762
808
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, rename as rename2 } from "node:fs/promises";
|
|
763
809
|
import { dirname as dirname2, join } from "node:path";
|
|
@@ -787,11 +833,27 @@ function parseRoles(input) {
|
|
|
787
833
|
if (!/^[a-zA-Z0-9_-]+$/.test(reasoning)) throw new Error("Role reasoning must be inherit or an effort identifier");
|
|
788
834
|
if (spawn !== "fresh" && spawn !== "fork") throw new Error("Role spawn must be fresh or fork");
|
|
789
835
|
seen.add(role);
|
|
790
|
-
|
|
836
|
+
const filter = value.toolFilter;
|
|
837
|
+
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");
|
|
838
|
+
return {
|
|
839
|
+
name,
|
|
840
|
+
role,
|
|
841
|
+
model,
|
|
842
|
+
reasoning,
|
|
843
|
+
spawn,
|
|
844
|
+
persona,
|
|
845
|
+
...filter !== void 0 ? { toolFilter: { mode: filter.mode ?? "replace", deny: [...new Set(filter.deny)] } } : {},
|
|
846
|
+
..."overrides" in value ? { overrides: structuredClone(value.overrides) } : {}
|
|
847
|
+
};
|
|
791
848
|
});
|
|
792
849
|
}
|
|
793
850
|
function executableRoles(roles) {
|
|
794
|
-
return roles.map(({ name, role, model, reasoning, spawn, persona }) => ({ name, role, model, reasoning, spawn, persona }));
|
|
851
|
+
return roles.map(({ name, role, model, reasoning, spawn, persona, toolFilter }) => ({ name, role, model, reasoning, spawn, persona, ...toolFilter ? { toolFilter } : {} }));
|
|
852
|
+
}
|
|
853
|
+
function roleToolFilter(role, presetDefault = []) {
|
|
854
|
+
const own3 = role?.toolFilter;
|
|
855
|
+
if (!own3) return [...presetDefault];
|
|
856
|
+
return own3.mode === "replace" ? [...own3.deny] : [.../* @__PURE__ */ new Set([...presetDefault, ...own3.deny])];
|
|
795
857
|
}
|
|
796
858
|
|
|
797
859
|
// src/host/role-validation.ts
|
|
@@ -821,7 +883,7 @@ async function validateRoleModel(llm, role, signal) {
|
|
|
821
883
|
|
|
822
884
|
// src/host/preset-store.ts
|
|
823
885
|
var owned = (id) => id === OPTIMIZED_PRESET_ID || /^studio-user-[a-f0-9-]{36}$/.test(id);
|
|
824
|
-
var
|
|
886
|
+
var retiredLegacyWarning = "Work convenience settings are saved but not applied yet. Legacy role overrides are stored only; roles apply only their supported settings.";
|
|
825
887
|
function migrateLegacyNames(row) {
|
|
826
888
|
const request = row.request;
|
|
827
889
|
if (request.toolOverrides) {
|
|
@@ -864,6 +926,7 @@ var PresetStore = class {
|
|
|
864
926
|
if (!owned(row.id)) throw new Error("Invalid Studio preset ownership");
|
|
865
927
|
parsePreviewRequest(row.request);
|
|
866
928
|
migrateLegacyNames(row);
|
|
929
|
+
if (row.applyWarning === retiredLegacyWarning) delete row.applyWarning;
|
|
867
930
|
}
|
|
868
931
|
} catch (error) {
|
|
869
932
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -879,10 +942,32 @@ var PresetStore = class {
|
|
|
879
942
|
})();
|
|
880
943
|
}
|
|
881
944
|
async mount(id, definition) {
|
|
882
|
-
const
|
|
945
|
+
const plugins = parseComposition(definition.plugins);
|
|
946
|
+
const dispose = await this.ctx.agentPresets.register({ id, name: definition.name, description: definition.description, plugins });
|
|
883
947
|
try {
|
|
884
948
|
const preset = await this.ctx.agentPresets.resolve(id);
|
|
885
949
|
if (preset.broken) throw new Error(preset.broken);
|
|
950
|
+
const policies = (rows) => rows.flatMap((row) => row.disabled === true ? [] : row.group && Array.isArray(row.config) ? policies(row.config) : row.name === POLICY_PLUGIN ? [row.config] : []);
|
|
951
|
+
const configs = policies(plugins);
|
|
952
|
+
if (configs.length) {
|
|
953
|
+
const lease = await this.ctx.agentPresets.acquireScope(id);
|
|
954
|
+
try {
|
|
955
|
+
const key = {};
|
|
956
|
+
const child = createScope2(this.ctx, key, { parent: lease.key });
|
|
957
|
+
const releases = [];
|
|
958
|
+
try {
|
|
959
|
+
for (const config of configs) releases.push(installPolicy(child.ctx, key, config));
|
|
960
|
+
} finally {
|
|
961
|
+
try {
|
|
962
|
+
for (const release of releases.reverse()) release();
|
|
963
|
+
} finally {
|
|
964
|
+
await child.dispose();
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
} finally {
|
|
968
|
+
await lease[Symbol.asyncDispose]();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
886
971
|
this.disposers.set(id, dispose);
|
|
887
972
|
} catch (error) {
|
|
888
973
|
await dispose();
|
|
@@ -909,27 +994,72 @@ var PresetStore = class {
|
|
|
909
994
|
const { applied, ...value } = row;
|
|
910
995
|
return structuredClone({ ...value, ...row.id === OPTIMIZED_PRESET_ID ? { template: true, nonDeletable: true } : {} });
|
|
911
996
|
}
|
|
912
|
-
/**
|
|
997
|
+
/** Compose the Optimized applied snapshot: the template request over the current base composition. */
|
|
998
|
+
optimizedApplied(request, plugins) {
|
|
999
|
+
const delegates = request.tools.includes(ROLE_TOOL);
|
|
1000
|
+
const base = applyConfigOverrides(plugins, request.configOverrides ?? {});
|
|
1001
|
+
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 });
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Seed the built-in on first run, then keep it aligned with the shipped template. Optimized is
|
|
1005
|
+
* locked against editing, so its stored request carries no user content — only the applied or
|
|
1006
|
+
* unapplied choice is the user's, and that survives. Without the alignment a profile plugin that
|
|
1007
|
+
* registers a new tool would enlarge the preset forever after its first seed.
|
|
1008
|
+
*/
|
|
913
1009
|
ensureOptimized(tools, plugins) {
|
|
914
1010
|
return this.serial(async () => {
|
|
915
|
-
const existing = this.rows.find((row2) => row2.id === OPTIMIZED_PRESET_ID);
|
|
916
|
-
if (existing) return this.public(existing);
|
|
917
1011
|
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 }) };
|
|
1012
|
+
const existing = this.rows.find((row) => row.id === OPTIMIZED_PRESET_ID);
|
|
1013
|
+
if (existing && JSON.stringify(existing.request) === JSON.stringify(template.request)) return this.public(existing);
|
|
922
1014
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
923
|
-
const
|
|
924
|
-
|
|
1015
|
+
const buildApplied = () => ({ name: template.name, description: template.description, plugins: this.optimizedApplied(template.request, plugins) });
|
|
1016
|
+
if (!existing) {
|
|
1017
|
+
const applied = buildApplied();
|
|
1018
|
+
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 } : {} };
|
|
1019
|
+
await this.mount(row.id, applied);
|
|
1020
|
+
try {
|
|
1021
|
+
await this.persist([...this.rows, row]);
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
await this.disposers.get(row.id)?.();
|
|
1024
|
+
this.disposers.delete(row.id);
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
return this.public(row);
|
|
1028
|
+
}
|
|
1029
|
+
const refresh = { ...existing, name: template.name, description: template.description, request: template.request, updatedAt: now };
|
|
1030
|
+
if (template.applyWarning) refresh.applyWarning = template.applyWarning;
|
|
1031
|
+
else delete refresh.applyWarning;
|
|
1032
|
+
if (existing.applied) {
|
|
1033
|
+
const applied = buildApplied();
|
|
1034
|
+
await this.disposers.get(existing.id)?.();
|
|
1035
|
+
this.disposers.delete(existing.id);
|
|
1036
|
+
try {
|
|
1037
|
+
await this.mount(existing.id, applied);
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
try {
|
|
1040
|
+
await this.mount(existing.id, existing.applied);
|
|
1041
|
+
} catch {
|
|
1042
|
+
}
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
refresh.applied = applied;
|
|
1046
|
+
refresh.appliedAt = now;
|
|
1047
|
+
refresh.status = "applied";
|
|
1048
|
+
}
|
|
925
1049
|
try {
|
|
926
|
-
await this.persist(
|
|
1050
|
+
await this.persist(this.rows.map((row) => row.id === existing.id ? refresh : row));
|
|
927
1051
|
} catch (error) {
|
|
928
|
-
await this.disposers.get(
|
|
929
|
-
this.disposers.delete(
|
|
1052
|
+
await this.disposers.get(existing.id)?.();
|
|
1053
|
+
this.disposers.delete(existing.id);
|
|
1054
|
+
if (existing.applied) {
|
|
1055
|
+
try {
|
|
1056
|
+
await this.mount(existing.id, existing.applied);
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
930
1060
|
throw error;
|
|
931
1061
|
}
|
|
932
|
-
return this.public(
|
|
1062
|
+
return this.public(refresh);
|
|
933
1063
|
});
|
|
934
1064
|
}
|
|
935
1065
|
list() {
|
|
@@ -974,21 +1104,25 @@ var PresetStore = class {
|
|
|
974
1104
|
for (const role of roles) await validateRoleModel(this.ctx.get("llm"), role);
|
|
975
1105
|
const delegates = tools.includes(ROLE_TOOL);
|
|
976
1106
|
const keep = (entries) => Object.fromEntries(Object.entries(entries).filter(([name]) => tools.includes(name)));
|
|
977
|
-
const
|
|
1107
|
+
const presetHidden = old.request.subagentToolFilter?.deny ?? [];
|
|
1108
|
+
const lists = [presetHidden, ...roles.map((role) => roleToolFilter(role, presetHidden))];
|
|
1109
|
+
const unknownHidden = [...new Set(lists.flat().filter((name) => !tools.includes(name) || name === ROLE_TOOL))];
|
|
1110
|
+
if (delegates && unknownHidden.length) throw new Error(`Subagent Tool Filter may only list tools selected in this preset: ${unknownHidden.join(", ")}`);
|
|
1111
|
+
const hideable = delegates ? [...new Set(lists.flat())] : [];
|
|
1112
|
+
const childToolFilter = hideable.length ? { deny: hideable } : void 0;
|
|
1113
|
+
const rolePlugins = delegates ? [{ id: "studio-roles", name: ROLE_PLUGIN, config: { roles, ...presetHidden.length ? { toolFilter: { deny: [...presetHidden] } } : {} } }] : [];
|
|
978
1114
|
const execution = request.execution && {
|
|
979
1115
|
...request.execution,
|
|
980
1116
|
defaults: keep(request.execution.defaults),
|
|
981
1117
|
...request.execution.batch && !tools.includes(request.execution.batch.tool) ? { batch: void 0 } : {}
|
|
982
1118
|
};
|
|
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 }) };
|
|
1119
|
+
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
1120
|
await this.disposers.get(id)?.();
|
|
985
1121
|
this.disposers.delete(id);
|
|
986
1122
|
try {
|
|
987
1123
|
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
1124
|
const row = { ...old, applied, appliedAt: (/* @__PURE__ */ new Date()).toISOString(), status: "applied" };
|
|
990
|
-
|
|
991
|
-
else delete row.applyWarning;
|
|
1125
|
+
delete row.applyWarning;
|
|
992
1126
|
await this.persist(this.rows.map((item) => item.id === id ? row : item));
|
|
993
1127
|
return this.public(row);
|
|
994
1128
|
} catch (error) {
|
|
@@ -1037,9 +1171,142 @@ var PresetStore = class {
|
|
|
1037
1171
|
}
|
|
1038
1172
|
};
|
|
1039
1173
|
|
|
1174
|
+
// src/host/studio-update.ts
|
|
1175
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1176
|
+
import { join as join2 } from "node:path";
|
|
1177
|
+
import { evaluatePluginCompatibility, getDshRuntimeVersion } from "@deepseek-ai/dsh-app-boot";
|
|
1178
|
+
|
|
1179
|
+
// src/update-check.ts
|
|
1180
|
+
var STABLE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
1181
|
+
function compareStable(a, b) {
|
|
1182
|
+
const x = STABLE.exec(a);
|
|
1183
|
+
const y = STABLE.exec(b);
|
|
1184
|
+
if (!x || !y) return Number(Boolean(x)) - Number(Boolean(y));
|
|
1185
|
+
for (let i = 1; i <= 3; i++) {
|
|
1186
|
+
const diff = Number(x[i]) - Number(y[i]);
|
|
1187
|
+
if (diff) return Math.sign(diff);
|
|
1188
|
+
}
|
|
1189
|
+
return 0;
|
|
1190
|
+
}
|
|
1191
|
+
function isNewer(candidate, current) {
|
|
1192
|
+
return STABLE.test(candidate) && compareStable(candidate, current.split("-")[0]) > 0;
|
|
1193
|
+
}
|
|
1194
|
+
function pickUpdate(document, current, compatible) {
|
|
1195
|
+
const latest = document["dist-tags"]?.latest;
|
|
1196
|
+
const candidates = Object.entries(document.versions ?? {}).filter(([version, manifest]) => isNewer(version, current) && !manifest.deprecated).sort(([a], [b]) => compareStable(b, a));
|
|
1197
|
+
const available = candidates.find(([, manifest]) => compatible(manifest))?.[0];
|
|
1198
|
+
const incompatibleLatest = latest !== void 0 && isNewer(latest, current) && latest !== available && candidates.some(([version]) => version === latest) ? latest : void 0;
|
|
1199
|
+
return { ...latest ? { latest } : {}, ...available ? { available } : {}, ...incompatibleLatest ? { incompatibleLatest } : {} };
|
|
1200
|
+
}
|
|
1201
|
+
function packumentUrl(registry, name) {
|
|
1202
|
+
return `${registry.replace(/\/+$/, "")}/${name.replace("/", "%2f")}`;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/host/studio-update.ts
|
|
1206
|
+
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
1207
|
+
var CACHE_MS = 10 * 6e4;
|
|
1208
|
+
var StudioUpdater = class {
|
|
1209
|
+
constructor(ctx, fetcher = globalThis.fetch) {
|
|
1210
|
+
this.ctx = ctx;
|
|
1211
|
+
this.fetcher = fetcher;
|
|
1212
|
+
}
|
|
1213
|
+
cached;
|
|
1214
|
+
installing;
|
|
1215
|
+
manager() {
|
|
1216
|
+
return this.ctx.get("pluginManager");
|
|
1217
|
+
}
|
|
1218
|
+
/** Read the current status, reusing a recent registry answer unless `force`. */
|
|
1219
|
+
status(force = false) {
|
|
1220
|
+
if (!force && this.cached && Date.now() - this.cached.at < CACHE_MS) return this.cached.status;
|
|
1221
|
+
const status = this.check();
|
|
1222
|
+
this.cached = { at: Date.now(), status };
|
|
1223
|
+
void status.catch(() => {
|
|
1224
|
+
if (this.cached?.status === status) this.cached = void 0;
|
|
1225
|
+
});
|
|
1226
|
+
return status;
|
|
1227
|
+
}
|
|
1228
|
+
async check() {
|
|
1229
|
+
const result = { current: STUDIO_VERSION };
|
|
1230
|
+
let runtimeVersion;
|
|
1231
|
+
try {
|
|
1232
|
+
runtimeVersion = getDshRuntimeVersion();
|
|
1233
|
+
result.runtimeVersion = runtimeVersion;
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
const { source, installed } = await this.profilePackage();
|
|
1237
|
+
if (source) result.source = source;
|
|
1238
|
+
if (installed && installed !== STUDIO_VERSION) result.installed = installed;
|
|
1239
|
+
const baseline = installed && isNewer(installed, STUDIO_VERSION) ? installed : STUDIO_VERSION;
|
|
1240
|
+
const manager = this.manager();
|
|
1241
|
+
if (!manager) result.blocked = "no-plugin-manager";
|
|
1242
|
+
else if (!source) result.blocked = "not-profile-dependency";
|
|
1243
|
+
try {
|
|
1244
|
+
const registries = manager ? await manager.registries().catch(() => void 0) : void 0;
|
|
1245
|
+
const registry = registries?.registry ?? registries?.resolved ?? DEFAULT_REGISTRY;
|
|
1246
|
+
const response = await this.fetcher(packumentUrl(registry, PACKAGE_NAME), {
|
|
1247
|
+
headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8" },
|
|
1248
|
+
signal: AbortSignal.timeout(15e3)
|
|
1249
|
+
});
|
|
1250
|
+
if (!response.ok) throw new Error(`Registry answered ${response.status}`);
|
|
1251
|
+
const document = await response.json();
|
|
1252
|
+
Object.assign(result, pickUpdate(document, baseline, (manifest) => {
|
|
1253
|
+
try {
|
|
1254
|
+
return evaluatePluginCompatibility({ name: PACKAGE_NAME, version: "0.0.0", ...manifest }, {}, runtimeVersion) === void 0;
|
|
1255
|
+
} catch {
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
}));
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
1261
|
+
}
|
|
1262
|
+
return result;
|
|
1263
|
+
}
|
|
1264
|
+
/** How the profile declares Studio and which version its files hold now. */
|
|
1265
|
+
async profilePackage() {
|
|
1266
|
+
const dir = this.ctx.get("profileContext")?.dir ?? process.env.DSH_PROFILE_DIR;
|
|
1267
|
+
if (!dir) return {};
|
|
1268
|
+
const json2 = async (path) => {
|
|
1269
|
+
try {
|
|
1270
|
+
return JSON.parse(await readFile3(path, "utf8"));
|
|
1271
|
+
} catch {
|
|
1272
|
+
return void 0;
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
const manifest = await json2(join2(dir, "package.json"));
|
|
1276
|
+
const source = manifest?.dependencies?.[PACKAGE_NAME];
|
|
1277
|
+
const installed = (await json2(join2(dir, "node_modules", ...PACKAGE_NAME.split("/"), "package.json")))?.version;
|
|
1278
|
+
return { ...source ? { source } : {}, ...typeof installed === "string" ? { installed } : {} };
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Install one exact version offered by {@link status}. The running Studio keeps its code until DSH restarts.
|
|
1282
|
+
* @param version Exact version; must equal the current offer so arbitrary specs never reach pnpm.
|
|
1283
|
+
*/
|
|
1284
|
+
update(version) {
|
|
1285
|
+
if (this.installing) return this.installing;
|
|
1286
|
+
const run = (async () => {
|
|
1287
|
+
const status = await this.status();
|
|
1288
|
+
if (status.available !== version) throw new Error(`Version ${version} is not the offered update`);
|
|
1289
|
+
if (status.blocked) throw new Error(`Studio cannot update this install (${status.blocked})`);
|
|
1290
|
+
const manager = this.manager();
|
|
1291
|
+
const result = await manager.installBundle(`${PACKAGE_NAME}@${version}`, { enabled: true });
|
|
1292
|
+
const installed = result.version ?? version;
|
|
1293
|
+
if (result.application === "failed" || result.application === "cancelled") {
|
|
1294
|
+
const detail = result.error?.diagnostic ?? result.error?.code ?? result.packageResult?.output?.trim().split("\n").slice(-3).join("\n");
|
|
1295
|
+
return { status: result.application, version: installed, ...detail ? { message: detail } : {} };
|
|
1296
|
+
}
|
|
1297
|
+
this.cached = void 0;
|
|
1298
|
+
return { status: result.application === "applied" ? "applied" : "restart-required", version: installed };
|
|
1299
|
+
})();
|
|
1300
|
+
this.installing = run;
|
|
1301
|
+
return run.finally(() => {
|
|
1302
|
+
this.installing = void 0;
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1040
1307
|
// src/host/shell.ts
|
|
1041
|
-
var _preview_dec, _saveExtensions_dec, _readExtensions_dec, _read_dec, _deletePreset_dec, _roleModels_dec, _unapplyPreset_dec, _applyPreset_dec, _savePreset_dec, _listPresets_dec, _a, _init;
|
|
1042
|
-
var StudioCatalog = class extends (_a = TypertRemoteService, _listPresets_dec = [Remote], _savePreset_dec = [Remote], _applyPreset_dec = [Remote], _unapplyPreset_dec = [Remote], _roleModels_dec = [Remote], _deletePreset_dec = [Remote], _read_dec = [Remote], _readExtensions_dec = [Remote], _saveExtensions_dec = [Remote], _preview_dec = [Remote], _a) {
|
|
1308
|
+
var _preview_dec, _saveExtensions_dec, _readExtensions_dec, _read_dec, _deletePreset_dec, _updateStudio_dec, _updateStatus_dec, _roleModels_dec, _unapplyPreset_dec, _applyPreset_dec, _savePreset_dec, _listPresets_dec, _a, _init;
|
|
1309
|
+
var StudioCatalog = class extends (_a = TypertRemoteService, _listPresets_dec = [Remote], _savePreset_dec = [Remote], _applyPreset_dec = [Remote], _unapplyPreset_dec = [Remote], _roleModels_dec = [Remote], _updateStatus_dec = [Remote], _updateStudio_dec = [Remote], _deletePreset_dec = [Remote], _read_dec = [Remote], _readExtensions_dec = [Remote], _saveExtensions_dec = [Remote], _preview_dec = [Remote], _a) {
|
|
1043
1310
|
constructor(ctx) {
|
|
1044
1311
|
super(ctx, "studioCatalog");
|
|
1045
1312
|
__runInitializers(_init, 5, this);
|
|
@@ -1049,8 +1316,9 @@ var StudioCatalog = class extends (_a = TypertRemoteService, _listPresets_dec =
|
|
|
1049
1316
|
__publicField(this, "stopped", false);
|
|
1050
1317
|
__publicField(this, "extensions");
|
|
1051
1318
|
__publicField(this, "changes", Promise.resolve());
|
|
1319
|
+
__publicField(this, "updater", new StudioUpdater(this.ctx));
|
|
1052
1320
|
const storePath = studioStorePath(process.env, ctx.get("profileContext"));
|
|
1053
|
-
this.extensions = new MasterExtensions(
|
|
1321
|
+
this.extensions = new MasterExtensions(join3(dirname3(storePath), "master-extensions.json"));
|
|
1054
1322
|
this.store = new PresetStore(ctx, storePath);
|
|
1055
1323
|
this.snapshot = new Promise((resolve2) => {
|
|
1056
1324
|
setImmediate(resolve2);
|
|
@@ -1086,6 +1354,12 @@ var StudioCatalog = class extends (_a = TypertRemoteService, _listPresets_dec =
|
|
|
1086
1354
|
if (!service) throw new Error("Model catalog API unavailable");
|
|
1087
1355
|
return service.modelCatalog();
|
|
1088
1356
|
}
|
|
1357
|
+
async updateStatus(force) {
|
|
1358
|
+
return this.updater.status(force === "force");
|
|
1359
|
+
}
|
|
1360
|
+
async updateStudio(version) {
|
|
1361
|
+
return this.updater.update(version);
|
|
1362
|
+
}
|
|
1089
1363
|
async deletePreset(id) {
|
|
1090
1364
|
return this.store.delete(id);
|
|
1091
1365
|
}
|
|
@@ -1186,6 +1460,8 @@ __decorateElement(_init, 1, "savePreset", _savePreset_dec, StudioCatalog);
|
|
|
1186
1460
|
__decorateElement(_init, 1, "applyPreset", _applyPreset_dec, StudioCatalog);
|
|
1187
1461
|
__decorateElement(_init, 1, "unapplyPreset", _unapplyPreset_dec, StudioCatalog);
|
|
1188
1462
|
__decorateElement(_init, 1, "roleModels", _roleModels_dec, StudioCatalog);
|
|
1463
|
+
__decorateElement(_init, 1, "updateStatus", _updateStatus_dec, StudioCatalog);
|
|
1464
|
+
__decorateElement(_init, 1, "updateStudio", _updateStudio_dec, StudioCatalog);
|
|
1189
1465
|
__decorateElement(_init, 1, "deletePreset", _deletePreset_dec, StudioCatalog);
|
|
1190
1466
|
__decorateElement(_init, 1, "read", _read_dec, StudioCatalog);
|
|
1191
1467
|
__decorateElement(_init, 1, "readExtensions", _readExtensions_dec, StudioCatalog);
|
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.8",
|
|
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",
|