@openma/deepseek-harness-acp 0.4.22 → 0.4.23

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 CHANGED
@@ -29,14 +29,14 @@ config options, slash commands, skills, and MCP servers. Credentials never
29
29
  touch your editor config — it reuses the key you saved in the dsh Web UI, or
30
30
  `dsh-acp login` saves one to the same store.
31
31
 
32
- ## Two entry points, one embeddable plugin
32
+ ## One package, standalone or plugin
33
33
 
34
34
  | | **A · dsh profile plugin (recommended)** | **B · Standalone server** |
35
35
  |---|---|---|
36
36
  | Best for | Normal installation and upgrades | Connecting an ACP client without managing a dsh profile |
37
37
  | Install | `dsh plugin --profile acp add @openma/deepseek-harness-acp@latest` | `npm i -g @openma/deepseek-harness-acp` |
38
38
  | Zed runs | `dsh --profile acp` | `dsh-acp` |
39
- | Harness | The dsh that owns the profile | Your installed dsh — or the npm-installed peer when none exists |
39
+ | Harness | The dsh that owns the profile | Your installed dsh, then the package's locked private runtime |
40
40
  | Composition | dsh-base + this bundle + your profile's own patches | dsh-base + this bundle (profile machinery booted in-process) |
41
41
 
42
42
  Both shapes share `$DSH_HOME`: the same credential store, settings, presets,
@@ -95,10 +95,11 @@ dsh-acp login # interactive; or save the key in the dsh Web UI
95
95
  ```
96
96
 
97
97
  The standalone binary finds DeepSeek Harness via `--dsh-path` / `DSH_PATH`,
98
- its own tree, `./node_modules`, `dsh` on PATH, or `npm root -g`. It ships an
99
- npm-installed harness peer as the **last** candidate, so it still prefers the
100
- dsh you installed. When a real `$DSH_HOME/profiles/acp` exists, that profile
101
- owns the composition.
98
+ `./node_modules`, `dsh` on PATH, or `npm root -g`, then falls back to the exact
99
+ runtime archived inside this package. DSH is only a wildcard optional peer for
100
+ Host integration, never an npm-installed dependency: plugin mode uses the
101
+ Host's tree, while standalone maps the same imports to the private runtime.
102
+ When a real `$DSH_HOME/profiles/acp` exists, that profile owns the composition.
102
103
 
103
104
  ## Plugin and extension model
104
105
 
@@ -216,7 +217,7 @@ another provider.
216
217
  - **Live model catalog** — providers × models from the running composition (third-party providers added in the Web UI appear immediately), plus reasoning-effort selection that follows your product default.
217
218
  - **Slash commands** — adapter built-ins (`/status`, `/model`) plus the harness command registry (`/compact`, `/goal`, `/permission`, `/plan`, …) executed without a model turn, plus **skills** (`/skill-name` — the harness's own invocation gesture). Login and logout are ACP methods, not chat commands.
218
219
  - **Plans & usage** — `todo_write` snapshots as ACP plans; token accounting as `usage_update` and per-turn usage.
219
- - **Sessions** — `session/load` with full history replay, `session/list`, silent restore when a client prompts an old session after an agent restart, titles as `session_info_update`.
220
+ - **Sessions** — `session/load` with full history replay, `session/list`, silent restore when a client prompts an old session after an agent restart, titles as `session_info_update`. Multi-root sessions are not advertised: non-empty `additionalDirectories` on `session/new` or `session/load` return `Invalid params` until [dsh supports multiple workspace roots](https://github.com/deepseek-ai/deepseek-harness/discussions/2474).
220
221
  - **MCP servers** — per-session `mcpServers` mount `@deepseek-ai/dsh-mcp-client` instances (stdio + streamable HTTP); tools join as `mcp__<server>__<tool>`; a failing server never takes the session down.
221
222
  - **Real cancellation** — `session/cancel` interrupts the live turn through the harness agent.
222
223
 
@@ -257,9 +258,10 @@ ACP client (Zed, …)
257
258
  │ ACP JSON-RPC over stdio
258
259
  ▼
259
260
  dsh-acp
261
+ ├─ src/bin.ts selects one DSH tree before Host imports evaluate
260
262
  ├─ src/profile-boot.ts boots the harness's own profile machinery
261
263
  │ (dsh-base + this bundle + $DSH_HOME layers)
262
- ├─ src/harness.ts host discovery (DSH_PATH → cwd → PATH → npm -g → npm peer)
264
+ ├─ src/harness.ts host discovery (DSH_PATH → cwd → PATH → npm -g → bundled runtime)
263
265
  └─ src/bridge/ the ACP bridge (a cordis plugin)
264
266
  ├─ index.ts sessions, prompts, cancel, modes, options,
265
267
  │ commands, credentials, MCP mounts
@@ -267,8 +269,8 @@ dsh-acp
267
269
  ├─ history.ts stored-log replay for session/load (pure)
268
270
  └─ prompt.ts ACP prompt blocks → harness content blocks (pure)
269
271
  ▼
270
- your @deepseek-ai/dsh installation (agent spine, llm, persistence, sandbox,
271
- tools, presets, skills, compaction, …)
272
+ external or package-bundled dsh (agent spine, llm, persistence, sandbox,
273
+ tools, presets, skills, compaction, …)
272
274
  ```
273
275
 
274
276
  When embedded by another surface, only the transport edge changes:
package/dist/bin.js ADDED
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin.ts
4
+ import { registerHooks } from "node:module";
5
+
6
+ // src/harness.ts
7
+ import { execFileSync } from "node:child_process";
8
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
9
+ import { delimiter, dirname as dirname2, isAbsolute, join as join2, resolve, sep } from "node:path";
10
+ import { pathToFileURL } from "node:url";
11
+
12
+ // src/log.ts
13
+ var PREFIX = "[dsh-acp]";
14
+ function logError(message) {
15
+ process.stderr.write(`${PREFIX} error: ${message}
16
+ `);
17
+ }
18
+
19
+ // src/runtime.ts
20
+ import { createHash } from "node:crypto";
21
+ import {
22
+ existsSync,
23
+ mkdirSync,
24
+ mkdtempSync,
25
+ readFileSync,
26
+ renameSync,
27
+ rmSync,
28
+ writeFileSync
29
+ } from "node:fs";
30
+ import { homedir } from "node:os";
31
+ import { dirname, join } from "node:path";
32
+ import { fileURLToPath } from "node:url";
33
+ import * as tar from "tar";
34
+ function cacheRoot() {
35
+ const explicit = process.env["DSH_ACP_CACHE_DIR"];
36
+ if (explicit !== void 0 && explicit.length > 0) return explicit;
37
+ if (process.platform === "win32") {
38
+ const local = process.env["LOCALAPPDATA"];
39
+ if (local !== void 0 && local.length > 0) return join(local, "dsh-acp");
40
+ }
41
+ const xdg = process.env["XDG_CACHE_HOME"];
42
+ return join(xdg !== void 0 && xdg.length > 0 ? xdg : join(homedir(), ".cache"), "dsh-acp");
43
+ }
44
+ function readMetadata(packageRoot) {
45
+ try {
46
+ const parsed = JSON.parse(readFileSync(join(packageRoot, "vendor/runtime.json"), "utf8"));
47
+ if (typeof parsed.archive !== "string" || typeof parsed.dsh !== "string" || typeof parsed.sha256 !== "string") return void 0;
48
+ return parsed;
49
+ } catch {
50
+ return void 0;
51
+ }
52
+ }
53
+ function resolveVendoredRuntime() {
54
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
55
+ const metadata = readMetadata(packageRoot);
56
+ if (metadata === void 0) return void 0;
57
+ const archive = join(packageRoot, "vendor", metadata.archive);
58
+ if (!existsSync(archive)) return void 0;
59
+ const target = join(cacheRoot(), `${metadata.dsh}-${metadata.sha256.slice(0, 12)}`);
60
+ const marker = join(target, ".dsh-acp-runtime");
61
+ if (existsSync(marker)) return target;
62
+ mkdirSync(dirname(target), { recursive: true });
63
+ const actual = createHash("sha256").update(readFileSync(archive)).digest("hex");
64
+ if (actual !== metadata.sha256) throw new Error(`bundled DSH runtime integrity check failed: ${archive}`);
65
+ if (existsSync(target)) rmSync(target, { recursive: true, force: true });
66
+ const temporary = mkdtempSync(join(dirname(target), ".extract-"));
67
+ try {
68
+ tar.x({ cwd: temporary, file: archive, sync: true, strict: true });
69
+ writeFileSync(join(temporary, ".dsh-acp-runtime"), `${metadata.dsh}
70
+ `);
71
+ try {
72
+ renameSync(temporary, target);
73
+ } catch (error) {
74
+ if (!existsSync(marker)) throw error;
75
+ }
76
+ } finally {
77
+ if (existsSync(temporary)) rmSync(temporary, { recursive: true, force: true });
78
+ }
79
+ return target;
80
+ }
81
+
82
+ // src/harness.ts
83
+ var HarnessNotFoundError = class extends Error {
84
+ constructor(probed) {
85
+ super(
86
+ [
87
+ "could not find a DeepSeek Harness installation.",
88
+ "",
89
+ "The bundled DeepSeek Harness runtime is missing or invalid.",
90
+ "Reinstall @openma/deepseek-harness-acp, or point it at an existing installation:",
91
+ " DSH_PATH=/path/to/dsh dsh-acp (dsh binary, package dir, or npm root)",
92
+ "",
93
+ probed.length > 0 ? `Probed:
94
+ ${probed.map((p) => ` ${p}`).join("\n")}` : ""
95
+ ].join("\n").trimEnd()
96
+ );
97
+ }
98
+ };
99
+ var ANCHOR = "@deepseek-ai/dsh-agent";
100
+ var PROFILE_LAUNCHER = "@deepseek-ai/dsh";
101
+ function packageDirFrom(base, name) {
102
+ let dir = resolve(base);
103
+ for (; ; ) {
104
+ const candidate = join2(dir, "node_modules", ...name.split("/"));
105
+ if (existsSync2(join2(candidate, "package.json"))) return candidate;
106
+ const direct = join2(dir, ...name.split("/"));
107
+ if (dir.endsWith(`${sep}node_modules`) && existsSync2(join2(direct, "package.json"))) return direct;
108
+ const parent = dirname2(dir);
109
+ if (parent === dir) return void 0;
110
+ dir = parent;
111
+ }
112
+ }
113
+ function versionOf(packageDir) {
114
+ try {
115
+ const parsed = JSON.parse(readFileSync2(join2(packageDir, "package.json"), "utf8"));
116
+ return typeof parsed.version === "string" ? parsed.version : void 0;
117
+ } catch {
118
+ return void 0;
119
+ }
120
+ }
121
+ function hostAt(base, source) {
122
+ const anchor = packageDirFrom(base, ANCHOR);
123
+ const launcher = packageDirFrom(base, PROFILE_LAUNCHER);
124
+ if (anchor === void 0 || launcher === void 0) return void 0;
125
+ return { base: resolve(base), version: versionOf(anchor), source };
126
+ }
127
+ function hostFromExplicit(explicit) {
128
+ const path = resolve(explicit);
129
+ const bases = [];
130
+ let stat;
131
+ try {
132
+ stat = realpathSync(path) !== void 0 && existsSync2(join2(path, ".")) ? "dir" : "file";
133
+ } catch {
134
+ stat = void 0;
135
+ }
136
+ if (existsSync2(path)) {
137
+ try {
138
+ const real = realpathSync(path);
139
+ stat = real === path && existsSync2(join2(path, "package.json")) ? "dir" : stat;
140
+ bases.push(dirname2(real));
141
+ } catch {
142
+ bases.push(dirname2(path));
143
+ }
144
+ bases.push(path, join2(path, "node_modules"), join2(path, "lib", "node_modules"));
145
+ }
146
+ for (const base of bases) {
147
+ const host = hostAt(base, `--dsh-path/DSH_PATH (${explicit})`);
148
+ if (host !== void 0) return host;
149
+ }
150
+ return void 0;
151
+ }
152
+ function hostFromPathLookup() {
153
+ const pathEnv = process.env["PATH"] ?? "";
154
+ for (const dir of pathEnv.split(delimiter)) {
155
+ if (dir.length === 0) continue;
156
+ const candidate = join2(dir, "dsh");
157
+ if (!existsSync2(candidate)) continue;
158
+ try {
159
+ const real = realpathSync(candidate);
160
+ const host = hostAt(dirname2(real), `dsh on PATH (${candidate})`);
161
+ if (host !== void 0) return host;
162
+ } catch {
163
+ continue;
164
+ }
165
+ }
166
+ return void 0;
167
+ }
168
+ function hostFromGlobalNpm() {
169
+ try {
170
+ const root = execFileSync("npm", ["root", "-g"], { encoding: "utf8", timeout: 1e4 }).trim();
171
+ if (root.length > 0 && isAbsolute(root)) return hostAt(root, `npm root -g (${root})`);
172
+ } catch {
173
+ }
174
+ return void 0;
175
+ }
176
+ function resolveHost(explicit) {
177
+ const probed = [];
178
+ if (explicit !== void 0) {
179
+ const host = hostFromExplicit(explicit);
180
+ if (host !== void 0) return host;
181
+ probed.push(`--dsh-path/DSH_PATH: ${explicit}`);
182
+ throw new HarnessNotFoundError(probed);
183
+ }
184
+ const local = hostAt(process.cwd(), `invoking directory (${process.cwd()})`);
185
+ if (local !== void 0) return local;
186
+ probed.push(`invoking directory: ${process.cwd()}`);
187
+ const onPath = hostFromPathLookup();
188
+ if (onPath !== void 0) return onPath;
189
+ probed.push("dsh on PATH");
190
+ const globalNpm = hostFromGlobalNpm();
191
+ if (globalNpm !== void 0) return globalNpm;
192
+ probed.push("npm root -g");
193
+ const vendored = resolveVendoredRuntime();
194
+ if (vendored !== void 0) {
195
+ const host = hostAt(vendored, "bundled standalone runtime");
196
+ if (host !== void 0) return host;
197
+ }
198
+ probed.push("bundled standalone runtime");
199
+ throw new HarnessNotFoundError(probed);
200
+ }
201
+ function entryFile(packageDir, subpath) {
202
+ const manifest = JSON.parse(readFileSync2(join2(packageDir, "package.json"), "utf8"));
203
+ const key = subpath === void 0 ? "." : `./${subpath}`;
204
+ const exportsMap = manifest.exports;
205
+ let target;
206
+ if (typeof exportsMap === "string") {
207
+ if (key === ".") target = exportsMap;
208
+ } else if (exportsMap !== null && typeof exportsMap === "object") {
209
+ const entry = exportsMap[key];
210
+ target = entry;
211
+ }
212
+ if (target !== null && typeof target === "object") {
213
+ const conditions = target;
214
+ target = conditions["import"] ?? conditions["default"] ?? conditions["node"];
215
+ if (target !== null && typeof target === "object") {
216
+ const nested = target;
217
+ target = nested["default"] ?? nested["import"];
218
+ }
219
+ }
220
+ if (typeof target !== "string" && key === "." && typeof manifest.main === "string") {
221
+ target = manifest.main;
222
+ }
223
+ if (typeof target !== "string") {
224
+ throw new Error(`cannot resolve entry ${key} of ${packageDir}`);
225
+ }
226
+ return join2(packageDir, target);
227
+ }
228
+ function hostModuleUrl(host, specifier) {
229
+ const [scope, packageName, ...rest] = specifier.split("/");
230
+ if (scope === void 0 || packageName === void 0) throw new Error(`invalid specifier: ${specifier}`);
231
+ const name = `${scope}/${packageName}`;
232
+ const packageDir = packageDirFrom(host.base, name);
233
+ if (packageDir === void 0) {
234
+ throw new Error(
235
+ `the DeepSeek Harness installation at ${host.base} (${host.source}) does not provide ${name}`
236
+ );
237
+ }
238
+ if (rest.length === 1 && rest[0] === "package.json") {
239
+ return pathToFileURL(join2(packageDir, "package.json")).href;
240
+ }
241
+ return pathToFileURL(entryFile(packageDir, rest.length > 0 ? rest.join("/") : void 0)).href;
242
+ }
243
+
244
+ // src/settings.ts
245
+ var HELP_TEXT = `Usage: dsh-acp [options]
246
+ dsh-acp login [--provider <route>] [api-key]
247
+ Save a provider API key (interactive when omitted)
248
+ dsh-acp update Self-update via npm
249
+
250
+ Agent Client Protocol (ACP) stdio server for DeepSeek Harness. Speak ACP on
251
+ stdin/stdout from an ACP client such as Zed; diagnostics go to stderr.
252
+
253
+ Options:
254
+ --dsh-path <path> DeepSeek Harness installation: the dsh binary, its
255
+ package dir, or any dir carrying node_modules/@deepseek-ai
256
+ (DSH_PATH; auto-detected when unset \u2014 own tree, ./node_modules,
257
+ dsh on PATH, npm root -g)
258
+ --bundle <package-dir> Add a dsh bundle layer to the agent tree (repeatable)
259
+ --provider <route> Provider route (DSH_PROVIDER, default deepseek-official)
260
+ --model <id> Default model (DSH_MODEL, default deepseek-v4-flash)
261
+ --models <a,b,...> Selectable models for the session "Model" option
262
+ (DSH_ACP_MODELS, default deepseek-v4-flash,deepseek-v4-pro)
263
+ --max-tokens <n> Per-request output-token cap (DSH_MAX_TOKENS)
264
+ --permission-mode <mode> read-only | workspace-write | danger-full-access
265
+ (DSH_PERMISSION_MODE; otherwise use the Host default)
266
+ --session-root <dir> JSONL session store (DSH_SESSION_ROOT, default ~/.dsh-acp/sessions)
267
+ --persona <text> System-prompt persona (DSH_SYSTEM_PROMPT)
268
+ --reasoning-effort <level> off | high | max (DSH_REASONING_EFFORT, default high)
269
+ --no-thinking Disable model thinking output
270
+ --bash-timeout <ms> Foreground bash timeout (DSH_BASH_TIMEOUT_MS, default 60000)
271
+ --version Print version and exit
272
+ --help Show this help
273
+
274
+ Environment:
275
+ DSH_PATH DeepSeek Harness installation (see --dsh-path)
276
+ DEEPSEEK_API_KEY DeepSeek (or compatible) API credential
277
+ DEEPSEEK_BASE_URL OpenAI-compatible endpoint override
278
+ DSH_ACP_DEBUG Verbose stderr diagnostics
279
+ `;
280
+
281
+ // src/version.ts
282
+ import { createRequire } from "node:module";
283
+ var VERSION = createRequire(import.meta.url)("../package.json").version;
284
+
285
+ // src/bin.ts
286
+ var NAME = "@openma/deepseek-harness-acp";
287
+ function explicitDshPath(argv) {
288
+ const index = argv.indexOf("--dsh-path");
289
+ if (index >= 0) return argv[index + 1];
290
+ const env = process.env["DSH_PATH"];
291
+ return env !== void 0 && env.length > 0 ? env : void 0;
292
+ }
293
+ async function bootstrap() {
294
+ const argv = process.argv.slice(2);
295
+ if (argv.includes("--version")) {
296
+ console.log(`${NAME} ${VERSION}`);
297
+ return;
298
+ }
299
+ if (argv.includes("--help")) {
300
+ console.log(HELP_TEXT.trimEnd());
301
+ return;
302
+ }
303
+ let host;
304
+ try {
305
+ host = resolveHost(explicitDshPath(argv));
306
+ } catch (error) {
307
+ if (error instanceof HarnessNotFoundError) {
308
+ logError(error.message);
309
+ process.exitCode = 1;
310
+ return;
311
+ }
312
+ throw error;
313
+ }
314
+ registerHooks({
315
+ resolve(specifier, context, nextResolve) {
316
+ if (specifier === "@deepseek-ai/cordis" || specifier.startsWith("@deepseek-ai/")) {
317
+ return { shortCircuit: true, url: hostModuleUrl(host, specifier) };
318
+ }
319
+ return nextResolve(specifier, context);
320
+ }
321
+ });
322
+ const cli = import.meta.url.endsWith(".ts") ? "./index.ts" : "./index.js";
323
+ await import(cli);
324
+ }
325
+ bootstrap().catch((error) => {
326
+ logError(error instanceof Error ? error.stack ?? error.message : String(error));
327
+ process.exit(1);
328
+ });
package/dist/bridge.js CHANGED
@@ -54,6 +54,7 @@ __export(bridge_exports, {
54
54
  answerFromElicitation: () => answerFromElicitation,
55
55
  apply: () => apply3,
56
56
  askUserQuestionsOverAcp: () => askUserQuestionsOverAcp,
57
+ createElicitation: () => createElicitation,
57
58
  inject: () => inject3,
58
59
  installAcpUserQuestionProvider: () => installAcpUserQuestionProvider,
59
60
  name: () => name3,
@@ -1593,6 +1594,7 @@ function apply(ctx, config) {
1593
1594
  var user_questions_plugin_exports = {};
1594
1595
  __export(user_questions_plugin_exports, {
1595
1596
  apply: () => apply2,
1597
+ createElicitation: () => createElicitation,
1596
1598
  inject: () => inject2,
1597
1599
  name: () => name2
1598
1600
  });
@@ -1725,6 +1727,9 @@ async function askUserQuestionsOverAcp(request, sessionId, create) {
1725
1727
  }
1726
1728
  return answer;
1727
1729
  }
1730
+ function createElicitation(connection, request) {
1731
+ return connection.createElicitation(request);
1732
+ }
1728
1733
  var ROUTERS_KEY = /* @__PURE__ */ Symbol.for("@openma/deepseek-harness-acp/user-question-routers");
1729
1734
  var routers = globalThis[ROUTERS_KEY] ??= /* @__PURE__ */ new WeakMap();
1730
1735
  function installAcpUserQuestionProvider(service, options) {
@@ -2878,6 +2883,7 @@ async function apply3(ctx, config = {}) {
2878
2883
  await requireCredential(config.provider);
2879
2884
  mark("credential");
2880
2885
  validateCwd(params.cwd);
2886
+ validateAdditionalDirectories(params.additionalDirectories);
2881
2887
  syncMcpServers(params.mcpServers, params.cwd);
2882
2888
  mark("request setup");
2883
2889
  const sessionId = SessionId2(randomUUID());
@@ -2922,6 +2928,7 @@ async function apply3(ctx, config = {}) {
2922
2928
  assertOpen();
2923
2929
  await requireCredential(config.provider);
2924
2930
  validateCwd(params.cwd);
2931
+ validateAdditionalDirectories(params.additionalDirectories);
2925
2932
  syncMcpServers(params.mcpServers, params.cwd);
2926
2933
  requirePersistence();
2927
2934
  const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
@@ -3165,6 +3172,11 @@ async function apply3(ctx, config = {}) {
3165
3172
  function validateCwd(cwd) {
3166
3173
  if (!isAbsolute(cwd)) throw invalidParams(`cwd must be an absolute path: ${cwd}`);
3167
3174
  }
3175
+ function validateAdditionalDirectories(additionalDirectories) {
3176
+ if (additionalDirectories !== void 0 && additionalDirectories.length > 0) {
3177
+ throw invalidParams("additionalDirectories is not supported");
3178
+ }
3179
+ }
3168
3180
  const mcpMounts = /* @__PURE__ */ new Map();
3169
3181
  let warnedNoMcpClient = false;
3170
3182
  const sanitizeServerName = (raw2) => {
@@ -3262,7 +3274,7 @@ async function apply3(ctx, config = {}) {
3262
3274
  const record = ownedRecord(request.agent);
3263
3275
  return record === void 0 ? void 0 : String(record.agent.session.id);
3264
3276
  },
3265
- create: (request) => conn.unstable_createElicitation(request)
3277
+ create: (request) => createElicitation(conn, request)
3266
3278
  });
3267
3279
  let quiescing;
3268
3280
  const quiesce = () => {
package/dist/index.js CHANGED
@@ -5,9 +5,8 @@ import { spawnSync } from "node:child_process";
5
5
 
6
6
  // src/harness.ts
7
7
  import { execFileSync } from "node:child_process";
8
- import { existsSync, readFileSync, realpathSync } from "node:fs";
9
- import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
10
- import { pathToFileURL, fileURLToPath } from "node:url";
8
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync } from "node:fs";
9
+ import { delimiter, dirname as dirname2, isAbsolute, join as join2, resolve, sep } from "node:path";
11
10
 
12
11
  // src/log.ts
13
12
  var PREFIX = "[dsh-acp]";
@@ -24,6 +23,69 @@ function logInfo(message) {
24
23
  `);
25
24
  }
26
25
 
26
+ // src/runtime.ts
27
+ import { createHash } from "node:crypto";
28
+ import {
29
+ existsSync,
30
+ mkdirSync,
31
+ mkdtempSync,
32
+ readFileSync,
33
+ renameSync,
34
+ rmSync,
35
+ writeFileSync
36
+ } from "node:fs";
37
+ import { homedir } from "node:os";
38
+ import { dirname, join } from "node:path";
39
+ import { fileURLToPath } from "node:url";
40
+ import * as tar from "tar";
41
+ function cacheRoot() {
42
+ const explicit = process.env["DSH_ACP_CACHE_DIR"];
43
+ if (explicit !== void 0 && explicit.length > 0) return explicit;
44
+ if (process.platform === "win32") {
45
+ const local = process.env["LOCALAPPDATA"];
46
+ if (local !== void 0 && local.length > 0) return join(local, "dsh-acp");
47
+ }
48
+ const xdg = process.env["XDG_CACHE_HOME"];
49
+ return join(xdg !== void 0 && xdg.length > 0 ? xdg : join(homedir(), ".cache"), "dsh-acp");
50
+ }
51
+ function readMetadata(packageRoot) {
52
+ try {
53
+ const parsed = JSON.parse(readFileSync(join(packageRoot, "vendor/runtime.json"), "utf8"));
54
+ if (typeof parsed.archive !== "string" || typeof parsed.dsh !== "string" || typeof parsed.sha256 !== "string") return void 0;
55
+ return parsed;
56
+ } catch {
57
+ return void 0;
58
+ }
59
+ }
60
+ function resolveVendoredRuntime() {
61
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
62
+ const metadata = readMetadata(packageRoot);
63
+ if (metadata === void 0) return void 0;
64
+ const archive = join(packageRoot, "vendor", metadata.archive);
65
+ if (!existsSync(archive)) return void 0;
66
+ const target = join(cacheRoot(), `${metadata.dsh}-${metadata.sha256.slice(0, 12)}`);
67
+ const marker = join(target, ".dsh-acp-runtime");
68
+ if (existsSync(marker)) return target;
69
+ mkdirSync(dirname(target), { recursive: true });
70
+ const actual = createHash("sha256").update(readFileSync(archive)).digest("hex");
71
+ if (actual !== metadata.sha256) throw new Error(`bundled DSH runtime integrity check failed: ${archive}`);
72
+ if (existsSync(target)) rmSync(target, { recursive: true, force: true });
73
+ const temporary = mkdtempSync(join(dirname(target), ".extract-"));
74
+ try {
75
+ tar.x({ cwd: temporary, file: archive, sync: true, strict: true });
76
+ writeFileSync(join(temporary, ".dsh-acp-runtime"), `${metadata.dsh}
77
+ `);
78
+ try {
79
+ renameSync(temporary, target);
80
+ } catch (error) {
81
+ if (!existsSync(marker)) throw error;
82
+ }
83
+ } finally {
84
+ if (existsSync(temporary)) rmSync(temporary, { recursive: true, force: true });
85
+ }
86
+ return target;
87
+ }
88
+
27
89
  // src/harness.ts
28
90
  var HarnessNotFoundError = class extends Error {
29
91
  constructor(probed) {
@@ -31,10 +93,8 @@ var HarnessNotFoundError = class extends Error {
31
93
  [
32
94
  "could not find a DeepSeek Harness installation.",
33
95
  "",
34
- "Install it first:",
35
- " npm install -g @deepseek-ai/dsh",
36
- "",
37
- "or point dsh-acp at an existing installation:",
96
+ "The bundled DeepSeek Harness runtime is missing or invalid.",
97
+ "Reinstall @openma/deepseek-harness-acp, or point it at an existing installation:",
38
98
  " DSH_PATH=/path/to/dsh dsh-acp (dsh binary, package dir, or npm root)",
39
99
  "",
40
100
  probed.length > 0 ? `Probed:
@@ -48,18 +108,18 @@ var PROFILE_LAUNCHER = "@deepseek-ai/dsh";
48
108
  function packageDirFrom(base, name) {
49
109
  let dir = resolve(base);
50
110
  for (; ; ) {
51
- const candidate = join(dir, "node_modules", ...name.split("/"));
52
- if (existsSync(join(candidate, "package.json"))) return candidate;
53
- const direct = join(dir, ...name.split("/"));
54
- if (dir.endsWith(`${sep}node_modules`) && existsSync(join(direct, "package.json"))) return direct;
55
- const parent = dirname(dir);
111
+ const candidate = join2(dir, "node_modules", ...name.split("/"));
112
+ if (existsSync2(join2(candidate, "package.json"))) return candidate;
113
+ const direct = join2(dir, ...name.split("/"));
114
+ if (dir.endsWith(`${sep}node_modules`) && existsSync2(join2(direct, "package.json"))) return direct;
115
+ const parent = dirname2(dir);
56
116
  if (parent === dir) return void 0;
57
117
  dir = parent;
58
118
  }
59
119
  }
60
120
  function versionOf(packageDir) {
61
121
  try {
62
- const parsed = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
122
+ const parsed = JSON.parse(readFileSync2(join2(packageDir, "package.json"), "utf8"));
63
123
  return typeof parsed.version === "string" ? parsed.version : void 0;
64
124
  } catch {
65
125
  return void 0;
@@ -76,19 +136,19 @@ function hostFromExplicit(explicit) {
76
136
  const bases = [];
77
137
  let stat;
78
138
  try {
79
- stat = realpathSync(path) !== void 0 && existsSync(join(path, ".")) ? "dir" : "file";
139
+ stat = realpathSync(path) !== void 0 && existsSync2(join2(path, ".")) ? "dir" : "file";
80
140
  } catch {
81
141
  stat = void 0;
82
142
  }
83
- if (existsSync(path)) {
143
+ if (existsSync2(path)) {
84
144
  try {
85
145
  const real = realpathSync(path);
86
- stat = real === path && existsSync(join(path, "package.json")) ? "dir" : stat;
87
- bases.push(dirname(real));
146
+ stat = real === path && existsSync2(join2(path, "package.json")) ? "dir" : stat;
147
+ bases.push(dirname2(real));
88
148
  } catch {
89
- bases.push(dirname(path));
149
+ bases.push(dirname2(path));
90
150
  }
91
- bases.push(path, join(path, "node_modules"), join(path, "lib", "node_modules"));
151
+ bases.push(path, join2(path, "node_modules"), join2(path, "lib", "node_modules"));
92
152
  }
93
153
  for (const base of bases) {
94
154
  const host = hostAt(base, `--dsh-path/DSH_PATH (${explicit})`);
@@ -100,11 +160,11 @@ function hostFromPathLookup() {
100
160
  const pathEnv = process.env["PATH"] ?? "";
101
161
  for (const dir of pathEnv.split(delimiter)) {
102
162
  if (dir.length === 0) continue;
103
- const candidate = join(dir, "dsh");
104
- if (!existsSync(candidate)) continue;
163
+ const candidate = join2(dir, "dsh");
164
+ if (!existsSync2(candidate)) continue;
105
165
  try {
106
166
  const real = realpathSync(candidate);
107
- const host = hostAt(dirname(real), `dsh on PATH (${candidate})`);
167
+ const host = hostAt(dirname2(real), `dsh on PATH (${candidate})`);
108
168
  if (host !== void 0) return host;
109
169
  } catch {
110
170
  continue;
@@ -137,20 +197,23 @@ function resolveHost(explicit) {
137
197
  const globalNpm = hostFromGlobalNpm();
138
198
  if (globalNpm !== void 0) return globalNpm;
139
199
  probed.push("npm root -g");
140
- const own = hostAt(dirname(fileURLToPath(import.meta.url)), "dsh-acp package tree (npm peer)");
141
- if (own !== void 0) return own;
142
- probed.push("dsh-acp package tree (npm peer)");
200
+ const vendored = resolveVendoredRuntime();
201
+ if (vendored !== void 0) {
202
+ const host = hostAt(vendored, "bundled standalone runtime");
203
+ if (host !== void 0) return host;
204
+ }
205
+ probed.push("bundled standalone runtime");
143
206
  throw new HarnessNotFoundError(probed);
144
207
  }
145
208
 
146
209
  // src/profile-boot.ts
147
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
210
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
148
211
  import { createRequire } from "node:module";
149
- import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
150
- import { pathToFileURL as pathToFileURL2 } from "node:url";
212
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve as resolve2, sep as sep2 } from "node:path";
213
+ import { pathToFileURL } from "node:url";
151
214
  var BIN = "dsh-acp";
152
215
  function ownRoot() {
153
- return dirname2(createRequire(import.meta.url).resolve("../package.json"));
216
+ return dirname3(createRequire(import.meta.url).resolve("../package.json"));
154
217
  }
155
218
  var OWN_BRIDGE_SPECIFIER = "@openma/deepseek-harness-acp/bridge";
156
219
  var OWN_SERVER_SPECIFIER = "@openma/deepseek-harness-acp/server";
@@ -171,19 +234,19 @@ function rewriteOwnRows(row, entries) {
171
234
  }
172
235
  }
173
236
  function bundlePatchPath(packageJsonPath) {
174
- const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
237
+ const pkg = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
175
238
  const declared = pkg.dsh?.bundle?.patch;
176
239
  if (declared === void 0) {
177
240
  throw new Error(`${BIN}: bundle ${pkg.name ?? packageJsonPath} declares no dsh.bundle in its package.json`);
178
241
  }
179
- return join2(dirname2(packageJsonPath), declared);
242
+ return join3(dirname3(packageJsonPath), declared);
180
243
  }
181
244
  function resolveBundlePackageJson(specifier) {
182
245
  const candidate = resolve2(specifier);
183
- if (existsSync2(candidate)) {
184
- return candidate.endsWith("package.json") ? candidate : join2(candidate, "package.json");
246
+ if (existsSync3(candidate)) {
247
+ return candidate.endsWith("package.json") ? candidate : join3(candidate, "package.json");
185
248
  }
186
- const req = createRequire(join2(process.cwd(), "noop.js"));
249
+ const req = createRequire(join3(process.cwd(), "noop.js"));
187
250
  try {
188
251
  return req.resolve(`${specifier}/package.json`);
189
252
  } catch (cause) {
@@ -198,8 +261,8 @@ function rewriteBundleRows(row, packageJsonPath, manifest) {
198
261
  row["name"] = req.resolve(name);
199
262
  } catch (cause) {
200
263
  if (name === manifest.name && manifest.main !== void 0) {
201
- const entry = resolve2(dirname2(packageJsonPath), manifest.main);
202
- if (existsSync2(entry)) row["name"] = entry;
264
+ const entry = resolve2(dirname3(packageJsonPath), manifest.main);
265
+ if (existsSync3(entry)) row["name"] = entry;
203
266
  else throw new Error(`${BIN}: bundle entry does not exist: ${entry}`, { cause });
204
267
  } else {
205
268
  throw new Error(
@@ -220,45 +283,45 @@ function rewriteBundleRows(row, packageJsonPath, manifest) {
220
283
  }
221
284
  function loadBundleLayer(appBoot, specifier) {
222
285
  const packageJsonPath = resolveBundlePackageJson(specifier);
223
- const manifest = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
286
+ const manifest = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
224
287
  const patches = appBoot.loadOverlayPatches(BIN, bundlePatchPath(packageJsonPath));
225
288
  for (const row of patches) rewriteBundleRows(row, packageJsonPath, manifest);
226
289
  return patches;
227
290
  }
228
291
  async function bootAcpProfile(host, overrides) {
229
- const req = createRequire(join2(host.base, "noop.js"));
292
+ const req = createRequire(join3(host.base, "noop.js"));
230
293
  const dshPkgPath = req.resolve("@deepseek-ai/dsh/package.json");
231
- const dshDir = dirname2(dshPkgPath);
232
- const appBoot = await import(pathToFileURL2(req.resolve("@deepseek-ai/dsh-app-boot")).href);
233
- const homePaths = await import(pathToFileURL2(req.resolve("@deepseek-ai/dsh-home-paths")).href);
294
+ const dshDir = dirname3(dshPkgPath);
295
+ const appBoot = await import(pathToFileURL(req.resolve("@deepseek-ai/dsh-app-boot")).href);
296
+ const homePaths = await import(pathToFileURL(req.resolve("@deepseek-ai/dsh-home-paths")).href);
234
297
  const home = homePaths.resolveDshHome();
235
298
  let bundlePatches;
236
299
  let profilePatches = [];
237
300
  let profilePatchPath;
238
- let rootConfigPath = join2(ownRoot(), "profile-root.cordis.yml");
239
- let bareModuleBaseUrl = pathToFileURL2(dshDir + sep2).href;
240
- const distBridge = join2(ownRoot(), "dist", "bridge.js");
241
- const bridgeEntry = import.meta.url.endsWith(".ts") || !existsSync2(distBridge) ? join2(ownRoot(), "src", "bundle.ts") : distBridge;
242
- const distServer = join2(ownRoot(), "dist", "server.js");
243
- const serverEntry = import.meta.url.endsWith(".ts") || !existsSync2(distServer) ? join2(ownRoot(), "src", "server.ts") : distServer;
244
- const distPlugin = join2(ownRoot(), "dist", "plugin.js");
245
- const pluginEntry = import.meta.url.endsWith(".ts") || !existsSync2(distPlugin) ? join2(ownRoot(), "src", "plugin.ts") : distPlugin;
246
- const distStdio = join2(ownRoot(), "dist", "stdio.js");
247
- const stdioEntry = import.meta.url.endsWith(".ts") || !existsSync2(distStdio) ? join2(ownRoot(), "src", "stdio.ts") : distStdio;
248
- const realProfileDir = join2(home, "profiles", "acp");
249
- if (existsSync2(join2(realProfileDir, "package.json"))) {
301
+ let rootConfigPath = join3(ownRoot(), "profile-root.cordis.yml");
302
+ let bareModuleBaseUrl = pathToFileURL(dshDir + sep2).href;
303
+ const distBridge = join3(ownRoot(), "dist", "bridge.js");
304
+ const bridgeEntry = import.meta.url.endsWith(".ts") || !existsSync3(distBridge) ? join3(ownRoot(), "src", "bundle.ts") : distBridge;
305
+ const distServer = join3(ownRoot(), "dist", "server.js");
306
+ const serverEntry = import.meta.url.endsWith(".ts") || !existsSync3(distServer) ? join3(ownRoot(), "src", "server.ts") : distServer;
307
+ const distPlugin = join3(ownRoot(), "dist", "plugin.js");
308
+ const pluginEntry = import.meta.url.endsWith(".ts") || !existsSync3(distPlugin) ? join3(ownRoot(), "src", "plugin.ts") : distPlugin;
309
+ const distStdio = join3(ownRoot(), "dist", "stdio.js");
310
+ const stdioEntry = import.meta.url.endsWith(".ts") || !existsSync3(distStdio) ? join3(ownRoot(), "src", "stdio.ts") : distStdio;
311
+ const realProfileDir = join3(home, "profiles", "acp");
312
+ if (existsSync3(join3(realProfileDir, "package.json"))) {
250
313
  appBoot.healProfilesModuleFallback(dshPkgPath);
251
314
  const profile = appBoot.loadProfile(BIN, "acp", dshPkgPath);
252
315
  bundlePatches = profile.layers.flatMap((layer) => layer.patches);
253
316
  profilePatches = profile.patches;
254
- profilePatchPath = join2(profile.dir, "cordis.patch.yml");
255
- const realRoot = join2(profile.dir, "cordis.yml");
256
- if (existsSync2(realRoot)) rootConfigPath = realRoot;
317
+ profilePatchPath = join3(profile.dir, "cordis.patch.yml");
318
+ const realRoot = join3(profile.dir, "cordis.yml");
319
+ if (existsSync3(realRoot)) rootConfigPath = realRoot;
257
320
  bareModuleBaseUrl = void 0;
258
321
  logDebug(`profile: real (${profile.dir})`);
259
322
  } else {
260
323
  const basePatch = bundlePatchPath(req.resolve("@deepseek-ai/dsh-base/package.json"));
261
- const ownPatch = bundlePatchPath(join2(ownRoot(), "package.json"));
324
+ const ownPatch = bundlePatchPath(join3(ownRoot(), "package.json"));
262
325
  bundlePatches = [
263
326
  ...appBoot.loadOverlayPatches(BIN, basePatch),
264
327
  ...appBoot.loadOverlayPatches(BIN, ownPatch)
@@ -277,15 +340,15 @@ async function bootAcpProfile(host, overrides) {
277
340
  stdio: stdioEntry
278
341
  });
279
342
  }
280
- const homePatchPath = join2(home, "cordis.patch.yml");
343
+ const homePatchPath = join3(home, "cordis.patch.yml");
281
344
  const homePatches = appBoot.loadOptionalPatches(BIN, homePatchPath) ?? [];
282
345
  const rows = /* @__PURE__ */ new Map();
283
346
  for (const row of appBoot.composeEntries([bundlePatches, profilePatches, homePatches])) {
284
347
  if (typeof row.id === "string") rows.set(row.id, row);
285
348
  }
286
349
  const overlays = [];
287
- const shippedPresets = join2(dshDir, "config", "agent-presets") + sep2;
288
- if (rows.has("agent-presets") && existsSync2(shippedPresets)) {
350
+ const shippedPresets = join3(dshDir, "config", "agent-presets") + sep2;
351
+ if (rows.has("agent-presets") && existsSync3(shippedPresets)) {
289
352
  overlays.push({
290
353
  id: "agent-presets",
291
354
  config: {
@@ -315,7 +378,7 @@ async function bootAcpProfile(host, overrides) {
315
378
  }
316
379
  const allPatches = [...bundlePatches, ...profilePatches, ...homePatches, ...overlays];
317
380
  const launchEnvironment = appBoot.loadLayeredEnv("dsh");
318
- const launchEnvKey = (await import(pathToFileURL2(req.resolve("@deepseek-ai/dsh-launch-environment")).href)).DSH_LAUNCH_ENVIRONMENT_KEY;
381
+ const launchEnvKey = (await import(pathToFileURL(req.resolve("@deepseek-ai/dsh-launch-environment")).href)).DSH_LAUNCH_ENVIRONMENT_KEY;
319
382
  const ctx = await appBoot.boot(
320
383
  BIN,
321
384
  rootConfigPath,
@@ -447,8 +510,8 @@ The dsh Web UI and every dsh surface share this credential.
447
510
  }
448
511
 
449
512
  // src/settings.ts
450
- import { homedir } from "node:os";
451
- import { join as join3 } from "node:path";
513
+ import { homedir as homedir2 } from "node:os";
514
+ import { join as join4 } from "node:path";
452
515
  var DEFAULT_PERSONA = `You are a coding assistant powered by the {{model}} model, working inside an editor via the Agent Client Protocol. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox \u2014 a \`[sandbox: file access denied \u2026]\` result is policy, not a command bug.
453
516
 
454
517
  Verify your work by running the code or tests. Keep answers brief and factual.`;
@@ -552,7 +615,7 @@ function resolveSettings(argv) {
552
615
  models,
553
616
  maxTokens,
554
617
  permissionMode,
555
- sessionRoot: stringFlag(parsed, "session-root") ?? envString("DSH_SESSION_ROOT") ?? join3(homedir(), ".dsh-acp", "sessions"),
618
+ sessionRoot: stringFlag(parsed, "session-root") ?? envString("DSH_SESSION_ROOT") ?? join4(homedir2(), ".dsh-acp", "sessions"),
556
619
  persona: stringFlag(parsed, "persona") ?? envString("DSH_SYSTEM_PROMPT") ?? DEFAULT_PERSONA,
557
620
  thinking: !parsed.flags.has("no-thinking"),
558
621
  reasoningEffort: reasoningRaw,
package/dist/plugin.js CHANGED
@@ -56,6 +56,7 @@ __export(bridge_exports, {
56
56
  answerFromElicitation: () => answerFromElicitation,
57
57
  apply: () => apply3,
58
58
  askUserQuestionsOverAcp: () => askUserQuestionsOverAcp,
59
+ createElicitation: () => createElicitation,
59
60
  inject: () => inject3,
60
61
  installAcpUserQuestionProvider: () => installAcpUserQuestionProvider,
61
62
  name: () => name3,
@@ -1595,6 +1596,7 @@ function apply(ctx, config) {
1595
1596
  var user_questions_plugin_exports = {};
1596
1597
  __export(user_questions_plugin_exports, {
1597
1598
  apply: () => apply2,
1599
+ createElicitation: () => createElicitation,
1598
1600
  inject: () => inject2,
1599
1601
  name: () => name2
1600
1602
  });
@@ -1727,6 +1729,9 @@ async function askUserQuestionsOverAcp(request, sessionId, create) {
1727
1729
  }
1728
1730
  return answer;
1729
1731
  }
1732
+ function createElicitation(connection, request) {
1733
+ return connection.createElicitation(request);
1734
+ }
1730
1735
  var ROUTERS_KEY = /* @__PURE__ */ Symbol.for("@openma/deepseek-harness-acp/user-question-routers");
1731
1736
  var routers = globalThis[ROUTERS_KEY] ??= /* @__PURE__ */ new WeakMap();
1732
1737
  function installAcpUserQuestionProvider(service, options) {
@@ -2880,6 +2885,7 @@ async function apply3(ctx, config = {}) {
2880
2885
  await requireCredential(config.provider);
2881
2886
  mark("credential");
2882
2887
  validateCwd(params.cwd);
2888
+ validateAdditionalDirectories(params.additionalDirectories);
2883
2889
  syncMcpServers(params.mcpServers, params.cwd);
2884
2890
  mark("request setup");
2885
2891
  const sessionId = SessionId2(randomUUID());
@@ -2924,6 +2930,7 @@ async function apply3(ctx, config = {}) {
2924
2930
  assertOpen();
2925
2931
  await requireCredential(config.provider);
2926
2932
  validateCwd(params.cwd);
2933
+ validateAdditionalDirectories(params.additionalDirectories);
2927
2934
  syncMcpServers(params.mcpServers, params.cwd);
2928
2935
  requirePersistence();
2929
2936
  const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
@@ -3167,6 +3174,11 @@ async function apply3(ctx, config = {}) {
3167
3174
  function validateCwd(cwd) {
3168
3175
  if (!isAbsolute(cwd)) throw invalidParams(`cwd must be an absolute path: ${cwd}`);
3169
3176
  }
3177
+ function validateAdditionalDirectories(additionalDirectories) {
3178
+ if (additionalDirectories !== void 0 && additionalDirectories.length > 0) {
3179
+ throw invalidParams("additionalDirectories is not supported");
3180
+ }
3181
+ }
3170
3182
  const mcpMounts = /* @__PURE__ */ new Map();
3171
3183
  let warnedNoMcpClient = false;
3172
3184
  const sanitizeServerName = (raw2) => {
@@ -3264,7 +3276,7 @@ async function apply3(ctx, config = {}) {
3264
3276
  const record = ownedRecord(request.agent);
3265
3277
  return record === void 0 ? void 0 : String(record.agent.session.id);
3266
3278
  },
3267
- create: (request) => conn.unstable_createElicitation(request)
3279
+ create: (request) => createElicitation(conn, request)
3268
3280
  });
3269
3281
  let quiescing;
3270
3282
  const quiesce = () => {
package/dist/server.js CHANGED
@@ -50,6 +50,7 @@ __export(bridge_exports, {
50
50
  answerFromElicitation: () => answerFromElicitation,
51
51
  apply: () => apply3,
52
52
  askUserQuestionsOverAcp: () => askUserQuestionsOverAcp,
53
+ createElicitation: () => createElicitation,
53
54
  inject: () => inject3,
54
55
  installAcpUserQuestionProvider: () => installAcpUserQuestionProvider,
55
56
  name: () => name3,
@@ -1589,6 +1590,7 @@ function apply(ctx, config) {
1589
1590
  var user_questions_plugin_exports = {};
1590
1591
  __export(user_questions_plugin_exports, {
1591
1592
  apply: () => apply2,
1593
+ createElicitation: () => createElicitation,
1592
1594
  inject: () => inject2,
1593
1595
  name: () => name2
1594
1596
  });
@@ -1721,6 +1723,9 @@ async function askUserQuestionsOverAcp(request, sessionId, create) {
1721
1723
  }
1722
1724
  return answer;
1723
1725
  }
1726
+ function createElicitation(connection, request) {
1727
+ return connection.createElicitation(request);
1728
+ }
1724
1729
  var ROUTERS_KEY = /* @__PURE__ */ Symbol.for("@openma/deepseek-harness-acp/user-question-routers");
1725
1730
  var routers = globalThis[ROUTERS_KEY] ??= /* @__PURE__ */ new WeakMap();
1726
1731
  function installAcpUserQuestionProvider(service, options) {
@@ -2874,6 +2879,7 @@ async function apply3(ctx, config = {}) {
2874
2879
  await requireCredential(config.provider);
2875
2880
  mark("credential");
2876
2881
  validateCwd(params.cwd);
2882
+ validateAdditionalDirectories(params.additionalDirectories);
2877
2883
  syncMcpServers(params.mcpServers, params.cwd);
2878
2884
  mark("request setup");
2879
2885
  const sessionId = SessionId2(randomUUID());
@@ -2918,6 +2924,7 @@ async function apply3(ctx, config = {}) {
2918
2924
  assertOpen();
2919
2925
  await requireCredential(config.provider);
2920
2926
  validateCwd(params.cwd);
2927
+ validateAdditionalDirectories(params.additionalDirectories);
2921
2928
  syncMcpServers(params.mcpServers, params.cwd);
2922
2929
  requirePersistence();
2923
2930
  const record = await restoreSession(params.sessionId, { replay: true, cwd: params.cwd });
@@ -3161,6 +3168,11 @@ async function apply3(ctx, config = {}) {
3161
3168
  function validateCwd(cwd) {
3162
3169
  if (!isAbsolute(cwd)) throw invalidParams(`cwd must be an absolute path: ${cwd}`);
3163
3170
  }
3171
+ function validateAdditionalDirectories(additionalDirectories) {
3172
+ if (additionalDirectories !== void 0 && additionalDirectories.length > 0) {
3173
+ throw invalidParams("additionalDirectories is not supported");
3174
+ }
3175
+ }
3164
3176
  const mcpMounts = /* @__PURE__ */ new Map();
3165
3177
  let warnedNoMcpClient = false;
3166
3178
  const sanitizeServerName = (raw2) => {
@@ -3258,7 +3270,7 @@ async function apply3(ctx, config = {}) {
3258
3270
  const record = ownedRecord(request.agent);
3259
3271
  return record === void 0 ? void 0 : String(record.agent.session.id);
3260
3272
  },
3261
- create: (request) => conn.unstable_createElicitation(request)
3273
+ create: (request) => createElicitation(conn, request)
3262
3274
  });
3263
3275
  let quiescing;
3264
3276
  const quiesce = () => {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.22",
6
+ "version": "0.4.23",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/openma-ai/deepseek-harness-acp.git"
@@ -15,9 +15,10 @@
15
15
  "description": "Agent Client Protocol (ACP) adapter for DeepSeek Harness — use DeepSeek Harness from ACP clients such as Zed.",
16
16
  "main": "dist/index.js",
17
17
  "bin": {
18
- "dsh-acp": "dist/index.js"
18
+ "dsh-acp": "dist/bin.js"
19
19
  },
20
20
  "files": [
21
+ "dist/bin.js",
21
22
  "dist/index.js",
22
23
  "dist/bridge.js",
23
24
  "dist/server.js",
@@ -28,18 +29,21 @@
28
29
  "LICENSE",
29
30
  "package.json",
30
31
  "profile-root.cordis.yml",
32
+ "vendor/*.json",
33
+ "vendor/*.tgz",
31
34
  "assets/icon.svg"
32
35
  ],
33
36
  "scripts": {
34
- "prepublishOnly": "npm run build",
37
+ "prepack": "npm run build && npm run runtime:pack",
35
38
  "build": "node build.mjs",
39
+ "runtime:pack": "node scripts/pack-runtime.mjs",
36
40
  "start": "node --import tsx src/index.ts",
37
41
  "pretest": "npm run build",
38
42
  "test": "vitest run",
39
43
  "test:host": "vitest run test/e2e.test.ts -t \"serves session controls through the host tree\"",
40
44
  "test:watch": "vitest",
41
45
  "typecheck": "tsc --noEmit",
42
- "pack:local": "npm run build && npm pack"
46
+ "pack:local": "npm pack"
43
47
  },
44
48
  "license": "Apache-2.0",
45
49
  "type": "module",
@@ -49,62 +53,62 @@
49
53
  "devDependencies": {
50
54
  "@deepseek-ai/cordis": "^4.0.1",
51
55
  "@deepseek-ai/cordis-plugin-timer": "^1.1.3",
52
- "@deepseek-ai/dsh": "^0.1.1-rc.1",
53
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.1",
54
- "@deepseek-ai/dsh-agent-instructions": "^0.1.1-rc.1",
55
- "@deepseek-ai/dsh-agent-loop": "^0.1.1-rc.1",
56
- "@deepseek-ai/dsh-agent-spine-demo": "^0.1.1-rc.1",
57
- "@deepseek-ai/dsh-anonymous-user-id": "^0.1.1-rc.1",
58
- "@deepseek-ai/dsh-attachment": "^0.1.1-rc.1",
59
- "@deepseek-ai/dsh-bash-sandbox": "^0.1.1-rc.1",
60
- "@deepseek-ai/dsh-brand": "^0.1.1-rc.1",
61
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.1",
62
- "@deepseek-ai/dsh-compaction": "^0.1.1-rc.1",
63
- "@deepseek-ai/dsh-compaction-basic": "^0.1.1-rc.1",
64
- "@deepseek-ai/dsh-compaction-tool-result-pruner": "^0.1.1-rc.1",
65
- "@deepseek-ai/dsh-credentials": "^0.1.1-rc.1",
66
- "@deepseek-ai/dsh-credentials-local": "^0.1.1-rc.1",
67
- "@deepseek-ai/dsh-fs": "^0.1.1-rc.1",
68
- "@deepseek-ai/dsh-fs-local": "^0.1.1-rc.1",
69
- "@deepseek-ai/dsh-fs-observation-policy": "^0.1.1-rc.1",
70
- "@deepseek-ai/dsh-fs-sandbox": "^0.1.1-rc.1",
71
- "@deepseek-ai/dsh-goal": "^0.1.1-rc.1",
72
- "@deepseek-ai/dsh-goal-round-driver": "^0.1.1-rc.1",
73
- "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.1",
74
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
75
- "@deepseek-ai/dsh-jobs-local": "^0.1.1-rc.1",
76
- "@deepseek-ai/dsh-launch-environment": "^0.1.1-rc.1",
77
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
78
- "@deepseek-ai/dsh-llm-deepseek": "^0.1.1-rc.1",
79
- "@deepseek-ai/dsh-llm-retry": "^0.1.1-rc.1",
80
- "@deepseek-ai/dsh-sandbox": "^0.1.1-rc.1",
81
- "@deepseek-ai/dsh-sandbox-local": "^0.1.1-rc.1",
82
- "@deepseek-ai/dsh-sandbox-policy": "^0.1.1-rc.1",
83
- "@deepseek-ai/dsh-scope": "^0.1.1-rc.1",
84
- "@deepseek-ai/dsh-session": "^0.1.1-rc.1",
85
- "@deepseek-ai/dsh-session-checkpoint-policy": "^0.1.1-rc.1",
86
- "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.1",
87
- "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.1-rc.1",
88
- "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.1",
89
- "@deepseek-ai/dsh-session-title": "^0.1.1-rc.1",
90
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.1",
91
- "@deepseek-ai/dsh-shell": "^0.1.1-rc.1",
92
- "@deepseek-ai/dsh-shell-env": "^0.1.1-rc.1",
93
- "@deepseek-ai/dsh-skill": "^0.1.1-rc.1",
94
- "@deepseek-ai/dsh-skill-filesystem": "^0.1.1-rc.1",
95
- "@deepseek-ai/dsh-subprocess": "^0.1.1-rc.1",
96
- "@deepseek-ai/dsh-subprocess-local": "^0.1.1-rc.1",
97
- "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
98
- "@deepseek-ai/dsh-timeout": "^0.1.1-rc.1",
99
- "@deepseek-ai/dsh-token-meter": "^0.1.1-rc.1",
100
- "@deepseek-ai/dsh-tool-bash": "^0.1.1-rc.1",
101
- "@deepseek-ai/dsh-tool-fs": "^0.1.1-rc.1",
102
- "@deepseek-ai/dsh-tool-goal": "^0.1.1-rc.1",
103
- "@deepseek-ai/dsh-tool-jobs": "^0.1.1-rc.1",
104
- "@deepseek-ai/dsh-tool-skill": "^0.1.1-rc.1",
105
- "@deepseek-ai/dsh-tool-todo": "^0.1.1-rc.1",
106
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
107
- "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.1",
56
+ "@deepseek-ai/dsh": "0.1.1-rc.2",
57
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
58
+ "@deepseek-ai/dsh-agent-instructions": "0.1.1-rc.2",
59
+ "@deepseek-ai/dsh-agent-loop": "0.1.1-rc.2",
60
+ "@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
61
+ "@deepseek-ai/dsh-anonymous-user-id": "0.1.1-rc.2",
62
+ "@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
63
+ "@deepseek-ai/dsh-bash-sandbox": "0.1.1-rc.2",
64
+ "@deepseek-ai/dsh-brand": "0.1.1-rc.2",
65
+ "@deepseek-ai/dsh-commands": "0.1.1-rc.2",
66
+ "@deepseek-ai/dsh-compaction": "0.1.1-rc.2",
67
+ "@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
68
+ "@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.1-rc.2",
69
+ "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
70
+ "@deepseek-ai/dsh-credentials-local": "0.1.1-rc.2",
71
+ "@deepseek-ai/dsh-fs": "0.1.1-rc.2",
72
+ "@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
73
+ "@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
74
+ "@deepseek-ai/dsh-fs-sandbox": "0.1.1-rc.2",
75
+ "@deepseek-ai/dsh-goal": "0.1.1-rc.2",
76
+ "@deepseek-ai/dsh-goal-round-driver": "0.1.1-rc.2",
77
+ "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
78
+ "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
79
+ "@deepseek-ai/dsh-jobs-local": "0.1.1-rc.2",
80
+ "@deepseek-ai/dsh-launch-environment": "0.1.1-rc.2",
81
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
82
+ "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
83
+ "@deepseek-ai/dsh-llm-retry": "0.1.1-rc.2",
84
+ "@deepseek-ai/dsh-sandbox": "0.1.1-rc.2",
85
+ "@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
86
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
87
+ "@deepseek-ai/dsh-scope": "0.1.1-rc.2",
88
+ "@deepseek-ai/dsh-session": "0.1.1-rc.2",
89
+ "@deepseek-ai/dsh-session-checkpoint-policy": "0.1.1-rc.2",
90
+ "@deepseek-ai/dsh-session-persistence": "0.1.1-rc.2",
91
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
92
+ "@deepseek-ai/dsh-session-projection": "0.1.1-rc.2",
93
+ "@deepseek-ai/dsh-session-title": "0.1.1-rc.2",
94
+ "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
95
+ "@deepseek-ai/dsh-shell": "0.1.1-rc.2",
96
+ "@deepseek-ai/dsh-shell-env": "0.1.1-rc.2",
97
+ "@deepseek-ai/dsh-skill": "0.1.1-rc.2",
98
+ "@deepseek-ai/dsh-skill-filesystem": "0.1.1-rc.2",
99
+ "@deepseek-ai/dsh-subprocess": "0.1.1-rc.2",
100
+ "@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
101
+ "@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2",
102
+ "@deepseek-ai/dsh-timeout": "0.1.1-rc.2",
103
+ "@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
104
+ "@deepseek-ai/dsh-tool-bash": "0.1.1-rc.2",
105
+ "@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
106
+ "@deepseek-ai/dsh-tool-goal": "0.1.1-rc.2",
107
+ "@deepseek-ai/dsh-tool-jobs": "0.1.1-rc.2",
108
+ "@deepseek-ai/dsh-tool-skill": "0.1.1-rc.2",
109
+ "@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2",
110
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
111
+ "@deepseek-ai/dsh-user-approval": "0.1.1-rc.2",
108
112
  "@types/node": "^26.1.0",
109
113
  "esbuild": "^0.28.1",
110
114
  "pnpm": "11.22.0",
@@ -114,11 +118,17 @@
114
118
  "yaml": "^2.9.0"
115
119
  },
116
120
  "dependencies": {
117
- "@agentclientprotocol/sdk": "^1.3.0",
118
- "zod": "^4.4.3"
121
+ "@agentclientprotocol/sdk": "1.4.0",
122
+ "tar": "7.4.3",
123
+ "zod": "4.4.3"
119
124
  },
120
125
  "peerDependencies": {
121
- "@deepseek-ai/dsh": "0.1.1-rc.2"
126
+ "@deepseek-ai/dsh": "*"
127
+ },
128
+ "peerDependenciesMeta": {
129
+ "@deepseek-ai/dsh": {
130
+ "optional": true
131
+ }
122
132
  },
123
133
  "exports": {
124
134
  ".": "./dist/index.js",
@@ -133,5 +143,12 @@
133
143
  "bundle": {
134
144
  "patch": "./cordis.patch.yml"
135
145
  }
146
+ },
147
+ "dshAcp": {
148
+ "standaloneDsh": "0.1.1-rc.2",
149
+ "hostCompatibility": {
150
+ "cordisProtocol": 0,
151
+ "policy": "capability-negotiated"
152
+ }
136
153
  }
137
154
  }
Binary file
@@ -0,0 +1,5 @@
1
+ {
2
+ "archive": "dsh-runtime.tgz",
3
+ "dsh": "0.1.1-rc.2",
4
+ "sha256": "531e1458e52bdf56c4a6257062258c6e1bf697c81b09c8f5c48864758f372dff"
5
+ }