@deepseek-ai/dsh 0.0.1-rc.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 +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +38 -0
- package/README.zh.md +38 -0
- package/config/agent-presets/code/agent.cordis.yml +254 -0
- package/config/agent-presets/code/preset.yml +3 -0
- package/config/agent-presets/cordis/agent.cordis.yml +254 -0
- package/config/agent-presets/cordis/preset.yml +3 -0
- package/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md +96 -0
- package/config/agent-presets/minimal/agent.cordis.yml +70 -0
- package/config/agent-presets/minimal/preset.yml +3 -0
- package/config/agent-presets/standard/agent.cordis.yml +243 -0
- package/config/agent-presets/standard/preset.yml +3 -0
- package/lib/bin.js +154 -0
- package/lib/dump-config-DlxoPVYx.js +57 -0
- package/lib/plugin-Xxr7y7sa.js +129 -0
- package/lib/profile-boot-DQYCwKII.js +335 -0
- package/lib/profile-boot-T5aOCb6v.js +2 -0
- package/package.json +95 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { t as INSTALL_ANCHOR } from "./profile-boot-DQYCwKII.js";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { DEFAULT_PROFILE_BUNDLES, PROFILE_TEMPLATES, initProfile, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest } from "@deepseek-ai/dsh-app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
//#region lib/types/plugin.js
|
|
7
|
+
/**
|
|
8
|
+
* `dsh plugin --profile <name> <args...>` — profile plugin management as a
|
|
9
|
+
* thin pnpm forwarder: initialize the profile on first use, run
|
|
10
|
+
* `pnpm <args...>` in the profile directory, then reconcile the
|
|
11
|
+
* `dsh.profile.bundles` layer list against the installed state (a dependency
|
|
12
|
+
* resolving to a package that declares `dsh.bundle` joins the layer stack; a
|
|
13
|
+
* removed or bundle-less dependency leaves it). Reconciling by installed
|
|
14
|
+
* state, not by dependency diff, means `update` activates a package that
|
|
15
|
+
* gained its `dsh.bundle` declaration in a newer version.
|
|
16
|
+
* @module @deepseek-ai/dsh/plugin
|
|
17
|
+
*/
|
|
18
|
+
const NAME = "dsh";
|
|
19
|
+
/**
|
|
20
|
+
* Whether a resolved dependency exports a profile patch, i.e. is a bundle.
|
|
21
|
+
* @param packageName - the dependency's package name.
|
|
22
|
+
* @param profileDir - the profile directory (resolution anchor).
|
|
23
|
+
* @returns true when the package manifest declares `dsh.bundle`.
|
|
24
|
+
*/
|
|
25
|
+
function exportsPatch(packageName, profileDir) {
|
|
26
|
+
let dir;
|
|
27
|
+
try {
|
|
28
|
+
dir = resolveBundleDir(NAME, packageName, INSTALL_ANCHOR, profileDir);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return readProfileManifest(NAME, dir).dsh?.bundle?.patch !== void 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reconcile `dsh.profile.bundles` against the installed state: pnpm has
|
|
36
|
+
* already written the real installed names (so a git/path/tarball/alias spec
|
|
37
|
+
* on the command line reconciles by its true package name) and materialized
|
|
38
|
+
* the packages. A dependency that resolves to a `dsh.bundle`-declaring
|
|
39
|
+
* package joins the layer stack (appended in dependency order); a
|
|
40
|
+
* dependency-listed name that no longer does — removed, or the installed
|
|
41
|
+
* version dropped the declaration — leaves it. In-box bundles from the
|
|
42
|
+
* profile template are not dependencies and are never touched. Warns once
|
|
43
|
+
* per newly-added bundle-less dependency (a plain library is fine; the
|
|
44
|
+
* warning is orientation).
|
|
45
|
+
*/
|
|
46
|
+
function reconcilePlugins(before, profileDir) {
|
|
47
|
+
const after = readProfileManifest(NAME, profileDir);
|
|
48
|
+
const beforeDeps = new Set(Object.keys(before.dependencies ?? {}));
|
|
49
|
+
const dependencies = Object.keys(after.dependencies ?? {});
|
|
50
|
+
const plugins = after.dsh?.profile?.bundles ?? [];
|
|
51
|
+
let changed = false;
|
|
52
|
+
for (const packageName of dependencies) {
|
|
53
|
+
const isBundle = exportsPatch(packageName, profileDir);
|
|
54
|
+
if (isBundle && !plugins.includes(packageName)) {
|
|
55
|
+
plugins.push(packageName);
|
|
56
|
+
changed = true;
|
|
57
|
+
} else if (!isBundle && !beforeDeps.has(packageName)) process.stderr.write(`${NAME}: warning: ${packageName} declares no dsh.bundle — installed as a plain dependency, not a profile layer (a later update that gains one activates it automatically)
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
const dependencySet = new Set(dependencies);
|
|
61
|
+
for (const packageName of [...plugins]) {
|
|
62
|
+
const wasDependency = beforeDeps.has(packageName) || dependencySet.has(packageName);
|
|
63
|
+
const stillBundle = dependencySet.has(packageName) && exportsPatch(packageName, profileDir);
|
|
64
|
+
if (wasDependency && !stillBundle) {
|
|
65
|
+
plugins.splice(plugins.indexOf(packageName), 1);
|
|
66
|
+
changed = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!changed) return;
|
|
70
|
+
after.dsh = {
|
|
71
|
+
...after.dsh,
|
|
72
|
+
profile: {
|
|
73
|
+
...after.dsh?.profile,
|
|
74
|
+
bundles: plugins
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
writeProfileManifest(profileDir, after);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Rewrite relative filesystem specs against the user's invoking directory.
|
|
81
|
+
* pnpm runs with cwd = the profile directory, so a bare `.` or `../plugin`
|
|
82
|
+
* (or their `file:`/`link:` forms) would silently resolve inside the profile
|
|
83
|
+
* — `add .` from a plugin checkout would self-link the profile. Absolute
|
|
84
|
+
* specs, registry names, and every other pnpm argument pass through
|
|
85
|
+
* untouched.
|
|
86
|
+
* @param argument - one pnpm argument, verbatim from argv.
|
|
87
|
+
* @param cwd - the directory `dsh` was invoked from.
|
|
88
|
+
* @returns the argument with a relative path spec anchored to `cwd`.
|
|
89
|
+
*/
|
|
90
|
+
function anchorPathSpec(argument, cwd) {
|
|
91
|
+
const match = /^(?<prefix>(?:file|link):)?(?<path>\.{1,2}(?:[/\\].*)?)$/.exec(argument);
|
|
92
|
+
if (match?.groups?.path === void 0) return argument;
|
|
93
|
+
return `${match.groups.prefix ?? ""}${resolve(cwd, match.groups.path)}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run one `dsh plugin` invocation: init if needed, forward to pnpm, reconcile.
|
|
97
|
+
* @param profile - the profile name.
|
|
98
|
+
* @param args - pnpm arguments with relative path specs anchored to the invoking directory.
|
|
99
|
+
* @returns the pnpm exit code.
|
|
100
|
+
*/
|
|
101
|
+
function runPlugin(profile, args) {
|
|
102
|
+
const dir = resolveProfileDir(profile);
|
|
103
|
+
if (!existsSync(join(dir, "package.json"))) {
|
|
104
|
+
initProfile(dir, PROFILE_TEMPLATES[profile] ?? DEFAULT_PROFILE_BUNDLES);
|
|
105
|
+
process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`);
|
|
106
|
+
}
|
|
107
|
+
const before = readProfileManifest(NAME, dir);
|
|
108
|
+
const result = spawnSync("pnpm", args.map((argument) => anchorPathSpec(argument, process.cwd())), {
|
|
109
|
+
cwd: dir,
|
|
110
|
+
stdio: "inherit",
|
|
111
|
+
shell: process.platform === "win32"
|
|
112
|
+
});
|
|
113
|
+
if (result.error !== void 0) {
|
|
114
|
+
if (result.error.code === "ENOENT") {
|
|
115
|
+
process.stderr.write(`${NAME}: pnpm not found on PATH — install pnpm to manage profile plugins\n`);
|
|
116
|
+
return 127;
|
|
117
|
+
}
|
|
118
|
+
throw result.error;
|
|
119
|
+
}
|
|
120
|
+
const exitCode = result.status ?? 1;
|
|
121
|
+
if (exitCode === 0) reconcilePlugins(before, dir);
|
|
122
|
+
else {
|
|
123
|
+
process.stderr.write(`${NAME}: pnpm failed in profile directory ${dir}\n`);
|
|
124
|
+
if (args.some((argument) => /^git\+|^github:|\.git(?:#|$)/.test(argument))) process.stderr.write(`${NAME}: git-hosted plugins build on install via their prepare script, which pnpm blocks until allowed — add the exact key pnpm printed above under allowBuilds in ${join(dir, "pnpm-workspace.yaml")}, then re-run\n`);
|
|
125
|
+
}
|
|
126
|
+
return exitCode;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { runPlugin };
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { PROFILE_PATCH_FILENAME, boot, composeEntries, healProfilesModuleFallback, installFailLoud, loadOptionalPatches, loadOverlayPatches, loadProfile, watchUserPatches } from "@deepseek-ai/dsh-app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-paths";
|
|
6
|
+
import { DSH_ENVIRONMENT_KEY } from "@deepseek-ai/dsh-environment";
|
|
7
|
+
import { provideCmdline } from "@deepseek-ai/dsh-cmdline";
|
|
8
|
+
//#region lib/types/process-shutdown.js
|
|
9
|
+
/** Bounded, escalating process shutdown for the long-lived CLI surfaces. */
|
|
10
|
+
/** Maximum grace allowed for the application tree to dispose before process exit. */
|
|
11
|
+
const PROCESS_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
12
|
+
/**
|
|
13
|
+
* Create one process-exit controller around an application disposer.
|
|
14
|
+
* @param dispose - Whole-application teardown that resolves at quiescence.
|
|
15
|
+
* @param forceExit - Function that exits the process immediately, replaceable by tests.
|
|
16
|
+
* @param complete - Function that records the natural completion code, replaceable by tests.
|
|
17
|
+
* @param timeoutMs - Grace before forced exit, replaceable by tests.
|
|
18
|
+
* @returns A controller whose normal calls coalesce and whose repeated signal call escalates.
|
|
19
|
+
*/
|
|
20
|
+
function createProcessShutdown(dispose, forceExit = (code) => {
|
|
21
|
+
process.exit(code);
|
|
22
|
+
}, complete = (code) => {
|
|
23
|
+
process.exitCode = code;
|
|
24
|
+
}, timeoutMs = PROCESS_SHUTDOWN_TIMEOUT_MS) {
|
|
25
|
+
let pending;
|
|
26
|
+
let timeout;
|
|
27
|
+
let completed = false;
|
|
28
|
+
let forceExited = false;
|
|
29
|
+
const clearExitTimeout = () => {
|
|
30
|
+
/* v8 ignore else -- shutdown() arms the timer before any asynchronous exit path can run. */
|
|
31
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
32
|
+
};
|
|
33
|
+
const forceExitOnce = (code) => {
|
|
34
|
+
if (forceExited) return;
|
|
35
|
+
forceExited = true;
|
|
36
|
+
clearExitTimeout();
|
|
37
|
+
forceExit(code);
|
|
38
|
+
};
|
|
39
|
+
const completeOnce = (code) => {
|
|
40
|
+
if (completed || forceExited) return;
|
|
41
|
+
completed = true;
|
|
42
|
+
clearExitTimeout();
|
|
43
|
+
complete(code);
|
|
44
|
+
};
|
|
45
|
+
const start = (code, forceAfterDispose) => {
|
|
46
|
+
if (pending !== void 0) return pending;
|
|
47
|
+
timeout = setTimeout(() => {
|
|
48
|
+
forceExitOnce(code);
|
|
49
|
+
}, timeoutMs);
|
|
50
|
+
pending = Promise.resolve().then(dispose).then(() => {
|
|
51
|
+
if (forceAfterDispose) forceExitOnce(code);
|
|
52
|
+
else completeOnce(code);
|
|
53
|
+
}, () => {
|
|
54
|
+
forceExitOnce(code);
|
|
55
|
+
});
|
|
56
|
+
return pending;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
shutdown(code) {
|
|
60
|
+
return start(code, false);
|
|
61
|
+
},
|
|
62
|
+
interrupt(code) {
|
|
63
|
+
if (pending !== void 0) {
|
|
64
|
+
forceExitOnce(code);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
start(code, true);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region lib/types/windows-shell.js
|
|
73
|
+
/**
|
|
74
|
+
* The Windows shell platform layer: on win32 hosts the shipped profile
|
|
75
|
+
* compositions swap the POSIX-only bash stack for the sandbox-confined
|
|
76
|
+
* PowerShell stack (`@deepseek-ai/dsh-pwsh-sandbox` +
|
|
77
|
+
* `@deepseek-ai/dsh-tool-pwsh`). The layer is the base bundle's
|
|
78
|
+
* `windows.cordis.patch.yml`, injected by the launcher between the bundle
|
|
79
|
+
* layers and the user layers so a user patch can still override it — the
|
|
80
|
+
* only override channel is composition config, like every other roster
|
|
81
|
+
* decision. POSIX hosts never receive the layer.
|
|
82
|
+
* @module @deepseek-ai/dsh/windows-shell
|
|
83
|
+
*/
|
|
84
|
+
/** The base bundle whose package carries the Windows shell patch. */
|
|
85
|
+
const BASE_BUNDLE = "@deepseek-ai/dsh-base";
|
|
86
|
+
/** The Windows shell patch filename inside the base bundle package. */
|
|
87
|
+
const WINDOWS_SHELL_PATCH_FILENAME = "windows.cordis.patch.yml";
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the Windows shell platform layer for a profile composition.
|
|
90
|
+
* @param platform - the host platform (`process.platform` at call sites).
|
|
91
|
+
* @param layers - the profile's bundle layers, in application order.
|
|
92
|
+
* @param binName - the diagnostic prefix on thrown errors (`dsh`).
|
|
93
|
+
* @returns the pwsh layer on win32, else `undefined`. A custom profile that
|
|
94
|
+
* mounts no base bundle is skipped (it owns its shell stack); a base
|
|
95
|
+
* bundle whose Windows shell patch is missing fails loud in
|
|
96
|
+
* {@link loadOverlayPatches} — the shipped package always carries it, so
|
|
97
|
+
* a miss is a broken installation.
|
|
98
|
+
*/
|
|
99
|
+
function resolveWindowsShellLayer(platform, layers, binName) {
|
|
100
|
+
if (platform !== "win32") return void 0;
|
|
101
|
+
const base = layers.find((layer) => layer.packageName === BASE_BUNDLE);
|
|
102
|
+
if (base === void 0) return void 0;
|
|
103
|
+
const label = join(base.packageDir, WINDOWS_SHELL_PATCH_FILENAME);
|
|
104
|
+
return {
|
|
105
|
+
label,
|
|
106
|
+
patches: loadOverlayPatches(binName, label)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region lib/types/profile-boot.js
|
|
111
|
+
/**
|
|
112
|
+
* Shared profile boot for every `dsh` surface: resolve the profile, stack its
|
|
113
|
+
* patch layers (bundle layers in `dsh.profile.bundles` order, the profile's
|
|
114
|
+
* own `cordis.patch.yml`, `--patch` overlays, the telemetry switch), mount the
|
|
115
|
+
* tree over the profile's empty root config, keep the profile patch layer
|
|
116
|
+
* live, and wire fail-loud plus bounded shutdown.
|
|
117
|
+
*
|
|
118
|
+
* App flags are not the launcher's business: the invocation's inner arguments
|
|
119
|
+
* are provided to the tree through `ctx.cmdlineArgs`, where any injected app
|
|
120
|
+
* plugin may read the same immutable snapshot.
|
|
121
|
+
* @module @deepseek-ai/dsh/profile-boot
|
|
122
|
+
*/
|
|
123
|
+
/** Shipped agent-preset root: beside this app's own config, in both source and built layouts. */
|
|
124
|
+
const SHIPPED_PRESET_ROOT = fileURLToPath(new URL("../config/agent-presets/", import.meta.url));
|
|
125
|
+
/** Harness-home directory holding locally authored agent presets. */
|
|
126
|
+
const USER_PRESET_DIR = ".agent-presets";
|
|
127
|
+
const NAME = "dsh";
|
|
128
|
+
/**
|
|
129
|
+
* The home-level user patch layer (`$DSH_HOME/cordis.patch.yml`), applied
|
|
130
|
+
* over every profile's own layer. Resolved per call, not at module load:
|
|
131
|
+
* `$DSH_HOME` may be set by the test or launcher after import.
|
|
132
|
+
* @returns the absolute patch-file path.
|
|
133
|
+
*/
|
|
134
|
+
function homePatchPath() {
|
|
135
|
+
return join(resolveDshHome(), PROFILE_PATCH_FILENAME);
|
|
136
|
+
}
|
|
137
|
+
/** Absolute path of this dsh installation's package.json (both anchors: src/ and lib/ sit one level under apps/cli). */
|
|
138
|
+
const INSTALL_ANCHOR = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
139
|
+
/** The session-telemetry row id the DSH_TELEMETRY_DISABLED switch targets. */
|
|
140
|
+
const TELEMETRY_ROW_ID = "telemetry-otel";
|
|
141
|
+
/** The one-shot runner row: its presence means this composition exits by itself. */
|
|
142
|
+
const HEADLESS_ROW_ID = "headless-runner";
|
|
143
|
+
/** The empty root entry list every profile tree patches over. */
|
|
144
|
+
const PROFILE_ROOT_CONFIG = `# dsh profile root — an empty entry list. The tree is composed as patches:
|
|
145
|
+
# each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml, then any
|
|
146
|
+
# --patch overlays. Edit cordis.patch.yml, not this file.
|
|
147
|
+
[]
|
|
148
|
+
`;
|
|
149
|
+
/** Root config filename inside a profile directory. */
|
|
150
|
+
const PROFILE_ROOT_FILENAME = "cordis.yml";
|
|
151
|
+
/**
|
|
152
|
+
* Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
|
|
153
|
+
* value (including `'0'`/`'false'`) disables: a privacy switch prefers
|
|
154
|
+
* off-by-mistake over on-by-mistake. A composition without the telemetry row
|
|
155
|
+
* exports nothing, so the switch is then trivially satisfied and no patch is
|
|
156
|
+
* generated — custom profiles need not mount telemetry to run with the
|
|
157
|
+
* switch set.
|
|
158
|
+
* @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
|
|
159
|
+
* @param hasRow - whether the composition carries the telemetry row.
|
|
160
|
+
* @returns the disable patch, or `undefined` when telemetry stays enabled or is not mounted.
|
|
161
|
+
*/
|
|
162
|
+
function resolveTelemetryPatch(disabledEnv, hasRow) {
|
|
163
|
+
if ((disabledEnv ?? "") === "" || !hasRow) return void 0;
|
|
164
|
+
return {
|
|
165
|
+
id: TELEMETRY_ROW_ID,
|
|
166
|
+
disabled: true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Load a resolved profile for `name`: heal the shared module fallback, then
|
|
171
|
+
* (re)write the empty root config. The root is always rewritten: the whole
|
|
172
|
+
* composition is patch layers, and the vendored Loader's tree write-back (a
|
|
173
|
+
* plugin self-disposing persists the current tree) can bake composed rows
|
|
174
|
+
* into this file — which would duplicate every bundle insert on the next
|
|
175
|
+
* boot. The file exists on disk only because the Loader needs a real include
|
|
176
|
+
* root to anchor `baseUrl` at the profile directory (the config dump anchors
|
|
177
|
+
* on the same file, so both compose over the identical base).
|
|
178
|
+
* @param name - the profile name.
|
|
179
|
+
* @param userLayer - `false` skips parsing `cordis.patch.yml` (the default dump).
|
|
180
|
+
* @returns the loaded profile.
|
|
181
|
+
*/
|
|
182
|
+
function prepareProfile(name, userLayer = true) {
|
|
183
|
+
healProfilesModuleFallback(INSTALL_ANCHOR);
|
|
184
|
+
const profile = loadProfile(NAME, name, INSTALL_ANCHOR, void 0, { userLayer });
|
|
185
|
+
writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG);
|
|
186
|
+
return profile;
|
|
187
|
+
}
|
|
188
|
+
/** The full patch stack of one composed profile, in application order. */
|
|
189
|
+
function allPatches(composed) {
|
|
190
|
+
return [
|
|
191
|
+
...composed.bundlePatches,
|
|
192
|
+
...composed.windowsShellPatches,
|
|
193
|
+
...composed.profile.patches,
|
|
194
|
+
...composed.homePatches,
|
|
195
|
+
...composed.overlays
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Load `name` and compose its effective patch stack: bundle layers in
|
|
200
|
+
* `dsh.profile.bundles` order, the win32 shell platform layer (when the host
|
|
201
|
+
* is Windows), the profile's user layer, the home-level user layer
|
|
202
|
+
* (`$DSH_HOME/cordis.patch.yml` — machine-local preferences that apply to
|
|
203
|
+
* every profile, so it outranks the per-profile layer), `--patch` overlays,
|
|
204
|
+
* then the telemetry switch.
|
|
205
|
+
* @param name - the profile name.
|
|
206
|
+
* @param patchFiles - `--patch` overlay paths, in argv order.
|
|
207
|
+
* @returns the profile, its patch layers, and the composed row index.
|
|
208
|
+
*/
|
|
209
|
+
function composeProfile(name, patchFiles) {
|
|
210
|
+
const profile = prepareProfile(name);
|
|
211
|
+
const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? [];
|
|
212
|
+
const overlays = patchFiles.flatMap((file) => loadOverlayPatches(NAME, resolve(file)));
|
|
213
|
+
const bundlePatches = profile.layers.flatMap((layer) => layer.patches);
|
|
214
|
+
const windowsShellPatches = resolveWindowsShellLayer(process.platform, profile.layers, NAME)?.patches ?? [];
|
|
215
|
+
const rows = /* @__PURE__ */ new Map();
|
|
216
|
+
for (const row of composeEntries([
|
|
217
|
+
bundlePatches,
|
|
218
|
+
windowsShellPatches,
|
|
219
|
+
profile.patches,
|
|
220
|
+
homePatches,
|
|
221
|
+
overlays
|
|
222
|
+
])) if (typeof row.id === "string") rows.set(row.id, row);
|
|
223
|
+
const composedOverlays = [...overlays];
|
|
224
|
+
if (rows.has("agent-presets")) composedOverlays.push({
|
|
225
|
+
id: "agent-presets",
|
|
226
|
+
config: {
|
|
227
|
+
...rows.get("agent-presets")?.config ?? {},
|
|
228
|
+
roots: [{
|
|
229
|
+
path: SHIPPED_PRESET_ROOT,
|
|
230
|
+
trust: "system"
|
|
231
|
+
}, {
|
|
232
|
+
path: dshHomePath(USER_PRESET_DIR),
|
|
233
|
+
trust: "user"
|
|
234
|
+
}]
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
const telemetryPatch = resolveTelemetryPatch(process.env.DSH_TELEMETRY_DISABLED, rows.has(TELEMETRY_ROW_ID));
|
|
238
|
+
if (telemetryPatch !== void 0) composedOverlays.push(telemetryPatch);
|
|
239
|
+
return {
|
|
240
|
+
profile,
|
|
241
|
+
bundlePatches,
|
|
242
|
+
windowsShellPatches,
|
|
243
|
+
homePatches,
|
|
244
|
+
overlays: composedOverlays,
|
|
245
|
+
rows
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/** Re-throw setup failures unless this invocation's signal already owns shutdown. */
|
|
249
|
+
function suppressSignalShutdownError(signal, error) {
|
|
250
|
+
if (!signal.aborted) throw error;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Boot one profile invocation end to end and leave process lifetime to the
|
|
254
|
+
* mounted plugins (or to a one-shot runner the composition mounts).
|
|
255
|
+
* @param options - environment snapshot, profile name, overlays, and the booted app's own arguments.
|
|
256
|
+
* @returns the settled root context and the shutdown controller.
|
|
257
|
+
*/
|
|
258
|
+
async function runProfile(options) {
|
|
259
|
+
const composed = composeProfile(options.profile, options.patchFiles);
|
|
260
|
+
const headlessRow = composed.rows.get(HEADLESS_ROW_ID);
|
|
261
|
+
const oneShot = headlessRow !== void 0 && headlessRow.disabled !== true;
|
|
262
|
+
const app = {};
|
|
263
|
+
const shutdown = createProcessShutdown(async () => {
|
|
264
|
+
await app.current?.fiber.dispose();
|
|
265
|
+
});
|
|
266
|
+
const signalShutdown = new AbortController();
|
|
267
|
+
const interrupt = (code) => {
|
|
268
|
+
signalShutdown.abort();
|
|
269
|
+
shutdown.interrupt(code);
|
|
270
|
+
};
|
|
271
|
+
process.on("SIGTERM", () => {
|
|
272
|
+
interrupt(oneShot ? 143 : 0);
|
|
273
|
+
});
|
|
274
|
+
process.on("SIGINT", () => {
|
|
275
|
+
interrupt(130);
|
|
276
|
+
});
|
|
277
|
+
installFailLoud(NAME, process, async () => {
|
|
278
|
+
await app.current?.fiber.dispose();
|
|
279
|
+
});
|
|
280
|
+
const rootConfig = join(composed.profile.dir, PROFILE_ROOT_FILENAME);
|
|
281
|
+
const composeLive = () => structuredClone([
|
|
282
|
+
...composed.bundlePatches,
|
|
283
|
+
...composed.windowsShellPatches,
|
|
284
|
+
...loadOptionalPatches(NAME, composed.profile.patchPath) ?? [],
|
|
285
|
+
...loadOptionalPatches(NAME, homePatchPath()) ?? [],
|
|
286
|
+
...composed.overlays
|
|
287
|
+
]);
|
|
288
|
+
const watchProfilePatch = !oneShot;
|
|
289
|
+
const ctx = await boot(NAME, rootConfig, structuredClone(allPatches(composed)), (hostCtx) => {
|
|
290
|
+
app.current = hostCtx;
|
|
291
|
+
hostCtx.provide(DSH_ENVIRONMENT_KEY, options.environment);
|
|
292
|
+
provideCmdline(hostCtx, {
|
|
293
|
+
args: options.args,
|
|
294
|
+
exit: (code) => void shutdown.shutdown(code)
|
|
295
|
+
});
|
|
296
|
+
if (oneShot) {
|
|
297
|
+
const io = {
|
|
298
|
+
stdout: process.stdout,
|
|
299
|
+
stderr: process.stderr,
|
|
300
|
+
exit: (code) => {
|
|
301
|
+
shutdown.shutdown(code);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
hostCtx.provide("headlessIo", io);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
app.current = ctx;
|
|
308
|
+
if (watchProfilePatch && !signalShutdown.signal.aborted && ctx.fiber.state === 2 && ctx.get("loader") !== void 0) try {
|
|
309
|
+
if (ctx.get("hmr") === void 0) {
|
|
310
|
+
if (ctx.get("timer") === void 0) await ctx.loader.create({ name: "@deepseek-ai/cordis-plugin-timer" });
|
|
311
|
+
await ctx.loader.create({
|
|
312
|
+
name: "@deepseek-ai/cordis-plugin-hmr",
|
|
313
|
+
config: { root: [] }
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
await watchUserPatches(ctx, {
|
|
317
|
+
binName: NAME,
|
|
318
|
+
filename: composed.profile.patchPath,
|
|
319
|
+
compose: composeLive
|
|
320
|
+
});
|
|
321
|
+
await watchUserPatches(ctx, {
|
|
322
|
+
binName: NAME,
|
|
323
|
+
filename: homePatchPath(),
|
|
324
|
+
compose: composeLive
|
|
325
|
+
});
|
|
326
|
+
} catch (error) {
|
|
327
|
+
suppressSignalShutdownError(signalShutdown.signal, error);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
ctx,
|
|
331
|
+
shutdown
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
export { resolveTelemetryPatch as a, prepareProfile as i, PROFILE_ROOT_FILENAME as n, runProfile as o, homePatchPath as r, resolveWindowsShellLayer as s, INSTALL_ANCHOR as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh",
|
|
3
|
+
"description": "dsh CLI: profile boot, plugin management, and the browser UI alias",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "apps/cli"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"bin": {
|
|
15
|
+
"dsh": "lib/bin.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib/*.js",
|
|
19
|
+
"config"
|
|
20
|
+
],
|
|
21
|
+
"license": "BSD-3-Clause",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"commander": "^15.0.0",
|
|
24
|
+
"js-yaml": "^4.2.0",
|
|
25
|
+
"node-addon-require-builtin": "^0.1.4",
|
|
26
|
+
"@deepseek-ai/cordis-plugin-hmr": "^1.0.16-rc.1",
|
|
27
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.5-rc.1",
|
|
28
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
|
|
29
|
+
"@deepseek-ai/dsh-agent-tool-mode": "^0.0.1-rc.1",
|
|
30
|
+
"@deepseek-ai/dsh-app-boot": "^0.0.1-rc.1",
|
|
31
|
+
"@deepseek-ai/cordis-plugin-timer": "^1.1.3-rc.1",
|
|
32
|
+
"@deepseek-ai/dsh-base": "^0.0.1-rc.1",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-agent-preset": "^0.0.1-rc.1",
|
|
34
|
+
"@deepseek-ai/dsh-command-compact": "^0.0.1-rc.1",
|
|
35
|
+
"@deepseek-ai/dsh-command-goal": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-compact-basic": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-compact-tool-result-prune": "^0.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/dsh-goal": "^0.0.1-rc.1",
|
|
39
|
+
"@deepseek-ai/dsh-goal-session": "^0.0.1-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-cmdline": "^0.0.1-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-environment": "^0.0.1-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-headless": "^0.0.1-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-mcp-client": "^0.0.1-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-paths": "^0.0.1-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-persona": "^0.0.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-plan-mode": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-pty": "^0.0.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-pty-local": "^0.0.1-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-pwsh-local": "^0.0.1-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-skill": "^0.0.1-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-skill-local": "^0.0.1-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-tasks-local": "^0.0.1-rc.1",
|
|
53
|
+
"@deepseek-ai/dsh-tmux-context": "^0.0.1-rc.1",
|
|
54
|
+
"@deepseek-ai/dsh-token-meter": "^0.0.1-rc.1",
|
|
55
|
+
"@deepseek-ai/dsh-tool-ask-user": "^0.0.1-rc.1",
|
|
56
|
+
"@deepseek-ai/dsh-tool-bash": "^0.0.1-rc.1",
|
|
57
|
+
"@deepseek-ai/dsh-tool-bash-persistent": "^0.0.1-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-tool-cordis": "^0.0.1-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-tool-fs": "^0.0.1-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-tool-fs-search": "^0.0.1-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-tool-goal": "^0.0.1-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-tool-pwsh": "^0.0.1-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-tool-ralph": "^0.0.1-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-tool-skill": "^0.0.1-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-session-reference": "^0.0.1-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-tool-str-replace-editor": "^0.0.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-pwsh-sandbox": "^0.0.1-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-tool-subagent": "^0.0.1-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-tool-subagent-control": "^0.0.1-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-tool-tasks": "^0.0.1-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-tool-todo": "^0.0.1-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-tool-web": "^0.0.1-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-tool-workflow": "^0.0.1-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-web-app": "^0.0.1-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-workflow-workerthread": "^0.0.1-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-workspace-context": "^0.0.1-rc.1",
|
|
77
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@types/js-yaml": "^4.0.9",
|
|
81
|
+
"execa": "^10.0.0",
|
|
82
|
+
"@deepseek-ai/dsh-frontend-static": "^0.0.1-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-llm-mock-server": "^0.0.1-rc.1",
|
|
87
|
+
"@deepseek-ai/dsh-loader-smoke": "^0.0.1-rc.1",
|
|
88
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
89
|
+
"@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.1",
|
|
90
|
+
"@deepseek-ai/dsh-settings": "^0.0.1-rc.1",
|
|
91
|
+
"@deepseek-ai/dsh-subagent": "^0.0.1-rc.1",
|
|
92
|
+
"@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.1",
|
|
93
|
+
"@deepseek-ai/dsh-tools": "^0.0.1-rc.1"
|
|
94
|
+
}
|
|
95
|
+
}
|