@norman-else/dsh-claude 0.1.6 → 0.1.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/lib/index.mjs CHANGED
@@ -1,15 +1,16 @@
1
- import { a as latestClaudeTasks, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_PROJECTION_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as TASK_TOOL_NAMES } from "./events-DP0Ov_Re.mjs";
2
- import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-YMR2WMjn.mjs";
3
- import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-BKw-SdOm.mjs";
1
+ import { _ as CLAUDE_UPDATE_CHECK_PATH, a as latestClaudeTasks, b as TASK_TOOL_NAMES, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_PROJECTION_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_UPDATE_PATH } from "./events-BVQkPmjg.mjs";
2
+ import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-CQ9dBVyY.mjs";
3
+ import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-DUmmDvBm.mjs";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { CallId, LlmAdapter, ReasoningEffortId, createToolResultMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
6
6
  import { randomUUID } from "node:crypto";
7
- import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
8
- import { join } from "node:path";
9
- import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
7
+ import { chmod, mkdir, opendir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
10
10
  import { query } from "@anthropic-ai/claude-agent-sdk";
11
11
  import { EventEmitter } from "node:events";
12
12
  import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subprocess";
13
+ import { fileURLToPath } from "node:url";
13
14
  //#region src/sidecar.ts
14
15
  const SIDECAR_SCHEMA_VERSION = 1;
15
16
  const MAX_ACTIVITIES = 1e4;
@@ -45,6 +46,7 @@ const ACTIVITY_KINDS = /* @__PURE__ */ new Set([
45
46
  "tool-call",
46
47
  "tool-result",
47
48
  "permission",
49
+ "question",
48
50
  "subagent",
49
51
  "usage",
50
52
  "warning",
@@ -336,8 +338,14 @@ function mapApprovalOutcome(outcome, input, toolUseID) {
336
338
  decisionClassification: "user_reject"
337
339
  };
338
340
  }
339
- function createPermissionBridge(approval, activeContext) {
341
+ function createPermissionBridge(approval, activeContext, userQuestion) {
340
342
  return async (toolName, input, options) => {
343
+ if (toolName === "AskUserQuestion") return userQuestion === void 0 ? {
344
+ behavior: "deny",
345
+ message: "DeepSeek Harness user questions are unavailable; the question was cancelled.",
346
+ toolUseID: options.toolUseID,
347
+ decisionClassification: "user_reject"
348
+ } : userQuestion(input, options);
341
349
  const active = activeContext();
342
350
  if (active === void 0) return {
343
351
  behavior: "deny",
@@ -401,6 +409,120 @@ function createPermissionBridge(approval, activeContext) {
401
409
  };
402
410
  }
403
411
  //#endregion
412
+ //#region src/user-question.ts
413
+ const FAILURE_MESSAGE = "DeepSeek Harness could not collect an answer; the question was cancelled.";
414
+ const INVALID_MESSAGE = "Claude Code sent an invalid user-question request; the question was cancelled.";
415
+ function failureMessage(error) {
416
+ const code = error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
417
+ return typeof code === "string" && /^[A-Z][A-Z0-9_]*$/.test(code) ? `${FAILURE_MESSAGE} (${code})` : FAILURE_MESSAGE;
418
+ }
419
+ function deny(message, toolUseID) {
420
+ return {
421
+ behavior: "deny",
422
+ message,
423
+ toolUseID,
424
+ decisionClassification: "user_reject"
425
+ };
426
+ }
427
+ function optionalText(value) {
428
+ return typeof value === "string" && value.length > 0 ? value : void 0;
429
+ }
430
+ function parseQuestions(input, toolUseID) {
431
+ if (!Array.isArray(input.questions) || input.questions.length === 0 || input.questions.length > 20) return void 0;
432
+ const seen = /* @__PURE__ */ new Set();
433
+ const questions = [];
434
+ for (const [index, value] of input.questions.entries()) {
435
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
436
+ const item = value;
437
+ if (typeof item.question !== "string" || item.question.length === 0 || seen.has(item.question)) return void 0;
438
+ seen.add(item.question);
439
+ if (item.options !== void 0 && !Array.isArray(item.options)) return void 0;
440
+ const options = (item.options ?? []).map((option) => {
441
+ if (option === null || typeof option !== "object" || Array.isArray(option)) return void 0;
442
+ const record = option;
443
+ if (typeof record.label !== "string" || record.label.length === 0) return void 0;
444
+ const description = optionalText(record.description);
445
+ return {
446
+ label: record.label,
447
+ ...description === void 0 ? {} : { description }
448
+ };
449
+ });
450
+ if (options.some((option) => option === void 0)) return void 0;
451
+ const header = optionalText(item.header);
452
+ questions.push({
453
+ id: `${toolUseID}:${index}`,
454
+ question: item.question,
455
+ ...header === void 0 ? {} : { header },
456
+ options,
457
+ multiSelect: item.multiSelect === true
458
+ });
459
+ }
460
+ return questions;
461
+ }
462
+ function answerText(answer, multiSelect) {
463
+ if (answer === void 0) return "";
464
+ const custom = optionalText(answer.custom);
465
+ if (!multiSelect && custom !== void 0) return custom;
466
+ return [...answer.selected, ...custom === void 0 ? [] : [custom]].join(", ");
467
+ }
468
+ function createUserQuestionBridge(userQuestions, activeContext) {
469
+ return async (input, options) => {
470
+ const active = activeContext();
471
+ if (active === void 0) return deny("No active DeepSeek Harness turn owns this Claude Code question.", options.toolUseID);
472
+ const questions = parseQuestions(input, options.toolUseID);
473
+ if (questions === void 0) return deny(INVALID_MESSAGE, options.toolUseID);
474
+ active.markActivity?.();
475
+ try {
476
+ await active.appendActivity({
477
+ kind: "question",
478
+ phase: "started",
479
+ toolUseId: options.toolUseID,
480
+ toolName: "AskUserQuestion",
481
+ title: "Claude asked a question",
482
+ summary: questions.length === 1 ? questions[0].question : `Claude asked ${questions.length} questions`
483
+ });
484
+ const response = await userQuestions.ask({
485
+ questions,
486
+ agent: active.agent,
487
+ signal: options.signal
488
+ });
489
+ const answersById = new Map(response.answers.map((answer) => [answer.id, answer]));
490
+ const answers = Object.fromEntries(questions.map((question) => [question.question, answerText(answersById.get(question.id), question.multiSelect === true)]));
491
+ await active.appendActivity({
492
+ kind: "question",
493
+ phase: "completed",
494
+ toolUseId: options.toolUseID,
495
+ toolName: "AskUserQuestion",
496
+ title: "Claude asked a question",
497
+ summary: "Answered in DeepSeek Harness"
498
+ });
499
+ return {
500
+ behavior: "allow",
501
+ updatedInput: {
502
+ ...input,
503
+ answers
504
+ },
505
+ toolUseID: options.toolUseID,
506
+ decisionClassification: "user_temporary"
507
+ };
508
+ } catch (error) {
509
+ const message = failureMessage(error);
510
+ try {
511
+ await active.appendActivity({
512
+ kind: "question",
513
+ phase: "failed",
514
+ toolUseId: options.toolUseID,
515
+ toolName: "AskUserQuestion",
516
+ title: "Claude asked a question",
517
+ summary: message,
518
+ isError: true
519
+ });
520
+ } catch {}
521
+ return deny(message, options.toolUseID);
522
+ }
523
+ };
524
+ }
525
+ //#endregion
404
526
  //#region src/sdk-messages.ts
405
527
  function record(value) {
406
528
  return value !== null && typeof value === "object" ? value : void 0;
@@ -908,6 +1030,7 @@ var ClaudeSupervisor = class {
908
1030
  #entries = /* @__PURE__ */ new Map();
909
1031
  #runtime;
910
1032
  #approval;
1033
+ #userQuestions;
911
1034
  #config;
912
1035
  #queryFactory;
913
1036
  #runDetached;
@@ -919,6 +1042,7 @@ var ClaudeSupervisor = class {
919
1042
  constructor(dependencies) {
920
1043
  this.#runtime = dependencies.runtime;
921
1044
  this.#approval = dependencies.approval;
1045
+ this.#userQuestions = dependencies.userQuestions;
922
1046
  this.#config = dependencies.config;
923
1047
  this.#queryFactory = dependencies.queryFactory ?? ((params) => query(params));
924
1048
  this.#runDetached = dependencies.runDetached ?? ((operation) => operation());
@@ -1154,7 +1278,7 @@ var ClaudeSupervisor = class {
1154
1278
  taskSnapshotAt: 0,
1155
1279
  taskSnapshotTimer: void 0
1156
1280
  };
1157
- const canUseTool = createPermissionBridge(this.#approval, () => {
1281
+ const activeInteraction = () => {
1158
1282
  const active = entry.active;
1159
1283
  return active === void 0 ? void 0 : {
1160
1284
  agent: active.agent,
@@ -1171,7 +1295,9 @@ var ClaudeSupervisor = class {
1171
1295
  },
1172
1296
  appendActivity: (activity) => this.#appendActivity(active, activity)
1173
1297
  };
1174
- });
1298
+ };
1299
+ const userQuestion = createUserQuestionBridge(this.#userQuestions, activeInteraction);
1300
+ const canUseTool = createPermissionBridge(this.#approval, activeInteraction, userQuestion);
1175
1301
  const options = {
1176
1302
  pathToClaudeCodeExecutable: this.#config.executablePath,
1177
1303
  cwd,
@@ -1939,7 +2065,7 @@ function json(res, status, value) {
1939
2065
  //#region src/doctor-routes.ts
1940
2066
  const CLAUDE_DOCTOR_PROBE_TIMEOUT_MS = 15e3;
1941
2067
  const claudeBridgeDiagnostics = /* @__PURE__ */ new WeakMap();
1942
- function safeMessage(error) {
2068
+ function safeMessage$1(error) {
1943
2069
  return redactText(error instanceof Error ? error.message : String(error), 1e3);
1944
2070
  }
1945
2071
  /** Live command-bridge diagnostics: which agents exist, their presets, and how
@@ -1955,14 +2081,14 @@ function commandDiagnostics(ctx) {
1955
2081
  const preset = ctx.agentPresets.composedPreset(agent.ctx);
1956
2082
  if (preset !== void 0) info.preset = preset;
1957
2083
  } catch (error) {
1958
- info.error = safeMessage(error);
2084
+ info.error = safeMessage$1(error);
1959
2085
  }
1960
2086
  try {
1961
2087
  const list = ctx.commands.list(agent);
1962
2088
  info.commandCount = list.length;
1963
2089
  info.sample = list.slice(0, 10).map((command) => command.name);
1964
2090
  } catch (error) {
1965
- info.error = info.error === void 0 ? safeMessage(error) : `${info.error}; ${safeMessage(error)}`;
2091
+ info.error = info.error === void 0 ? safeMessage$1(error) : `${info.error}; ${safeMessage$1(error)}`;
1966
2092
  }
1967
2093
  const bridge = claudeBridgeDiagnostics.get(agent);
1968
2094
  if (bridge !== void 0) info.bridge = bridge;
@@ -1970,7 +2096,7 @@ function commandDiagnostics(ctx) {
1970
2096
  })
1971
2097
  };
1972
2098
  } catch (error) {
1973
- return { error: safeMessage(error) };
2099
+ return { error: safeMessage$1(error) };
1974
2100
  }
1975
2101
  }
1976
2102
  function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolutionError) {
@@ -1994,7 +2120,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
1994
2120
  version: { status: "not-run" },
1995
2121
  authentication: { status: "not-run" },
1996
2122
  handshake: "not-run",
1997
- message: safeMessage(resolutionError),
2123
+ message: safeMessage$1(resolutionError),
1998
2124
  limits: {
1999
2125
  idleTimeoutMs: config.idleTimeoutMs,
2000
2126
  maxProcesses: config.maxProcesses
@@ -2024,7 +2150,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
2024
2150
  commandBridge: commandDiagnostics(ctx)
2025
2151
  });
2026
2152
  } catch (error) {
2027
- json(res, 500, { error: safeMessage(error) });
2153
+ json(res, 500, { error: safeMessage$1(error) });
2028
2154
  }
2029
2155
  }
2030
2156
  }), "dsh-claude: Doctor route");
@@ -2073,6 +2199,259 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession) {
2073
2199
  }), "dsh-claude: sidecar projection route");
2074
2200
  }
2075
2201
  //#endregion
2202
+ //#region src/update-routes.ts
2203
+ const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
2204
+ const UPDATE_TIMEOUT_MS = 3e4;
2205
+ const CHECK_TIMEOUT_MS = 1e4;
2206
+ const MAX_MANIFEST_BYTES = 262144;
2207
+ const MAX_UPDATE_OUTPUT_BYTES = 32768;
2208
+ const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
2209
+ const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
2210
+ function safeMessage(error) {
2211
+ return redactText(error instanceof Error ? error.message : String(error), 500);
2212
+ }
2213
+ async function readManifest(path) {
2214
+ const text = await readFile(path, "utf8");
2215
+ if (Buffer.byteLength(text) > MAX_MANIFEST_BYTES) throw new Error("package manifest is too large");
2216
+ const value = JSON.parse(text);
2217
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("package manifest is invalid");
2218
+ return value;
2219
+ }
2220
+ function dependencySpec(manifest) {
2221
+ if (typeof manifest.dependencies !== "object" || manifest.dependencies === null || Array.isArray(manifest.dependencies)) return void 0;
2222
+ const value = manifest.dependencies[PLUGIN_PACKAGE_NAME];
2223
+ return typeof value === "string" && value.length <= 2e3 ? value : void 0;
2224
+ }
2225
+ function classifyInstallSpec(spec) {
2226
+ if (/^(?:link|file|workspace):/i.test(spec)) return "link";
2227
+ if (/^(?:git(?:\+[^:]+)?:|github:|https?:|npm:)/i.test(spec) || /\.git(?:#|$)/i.test(spec)) return "unsupported";
2228
+ return /^(?:\^|~|>=?|<=?|=)?\s*v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\s*\|\|\s*(?:\^|~|>=?|<=?|=)?\s*v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)*$/.test(spec.trim()) ? "registry" : "unsupported";
2229
+ }
2230
+ async function samePath(left, right) {
2231
+ try {
2232
+ const [a, b] = await Promise.all([realpath(left), realpath(right)]);
2233
+ return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
2234
+ } catch {
2235
+ return false;
2236
+ }
2237
+ }
2238
+ function linkedTarget(profileDir, spec) {
2239
+ const match = /^(?:link|file):(.*)$/i.exec(spec);
2240
+ return match?.[1] === void 0 ? void 0 : resolve(profileDir, match[1]);
2241
+ }
2242
+ async function discoverInstallation(dshHome, packageDir) {
2243
+ const profilesDir = join(dshHome, "profiles");
2244
+ const matches = [];
2245
+ let profiles;
2246
+ try {
2247
+ profiles = await opendir(profilesDir);
2248
+ } catch (error) {
2249
+ if (error.code === "ENOENT") return void 0;
2250
+ throw error;
2251
+ }
2252
+ for await (const entry of profiles) {
2253
+ if (!entry.isDirectory() || !PROFILE_NAME.test(entry.name)) continue;
2254
+ const profileDir = join(profilesDir, entry.name);
2255
+ let manifest;
2256
+ try {
2257
+ manifest = await readManifest(join(profileDir, "package.json"));
2258
+ } catch {
2259
+ continue;
2260
+ }
2261
+ const spec = dependencySpec(manifest);
2262
+ if (spec === void 0) continue;
2263
+ const source = classifyInstallSpec(spec);
2264
+ if (source === "link" ? linkedTarget(profileDir, spec) !== void 0 && await samePath(linkedTarget(profileDir, spec), packageDir) : await samePath(join(profileDir, "node_modules", ..."@norman-else/dsh-claude".split("/")), packageDir)) matches.push({
2265
+ profile: entry.name,
2266
+ profileDir,
2267
+ source,
2268
+ spec
2269
+ });
2270
+ }
2271
+ return matches.length === 1 ? matches[0] : void 0;
2272
+ }
2273
+ function parseVersion(version) {
2274
+ const match = SEMVER.exec(version);
2275
+ if (match === null) throw new Error("package version is not valid semver");
2276
+ return match;
2277
+ }
2278
+ function compareVersions(left, right) {
2279
+ const a = parseVersion(left);
2280
+ const b = parseVersion(right);
2281
+ for (const index of [
2282
+ 1,
2283
+ 2,
2284
+ 3
2285
+ ]) {
2286
+ const difference = Number(a[index]) - Number(b[index]);
2287
+ if (difference !== 0) return Math.sign(difference);
2288
+ }
2289
+ const aPre = a[4];
2290
+ const bPre = b[4];
2291
+ if (aPre === void 0) return bPre === void 0 ? 0 : 1;
2292
+ if (bPre === void 0) return -1;
2293
+ return aPre.localeCompare(bPre, "en", { numeric: true });
2294
+ }
2295
+ async function registryLatest(signal) {
2296
+ const response = await fetch("https://registry.npmjs.org/%40norman-else%2Fdsh-claude", {
2297
+ headers: { accept: "application/vnd.npm.install-v1+json" },
2298
+ signal
2299
+ });
2300
+ if (!response.ok) throw new Error(`npm registry returned HTTP ${response.status}`);
2301
+ const latest = (await response.json())["dist-tags"]?.latest;
2302
+ if (typeof latest !== "string") throw new Error("npm registry response has no latest version");
2303
+ parseVersion(latest);
2304
+ return latest;
2305
+ }
2306
+ async function resolvePackageContextDir(configured) {
2307
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
2308
+ const candidates = configured === void 0 ? [moduleDir, dirname(moduleDir)] : [configured];
2309
+ for (const packageDir of candidates) try {
2310
+ const manifest = await readManifest(join(packageDir, "package.json"));
2311
+ if (manifest.name === "@norman-else/dsh-claude") return {
2312
+ packageDir,
2313
+ manifest
2314
+ };
2315
+ } catch {}
2316
+ throw new Error("plugin package manifest is invalid");
2317
+ }
2318
+ async function packageContext(deps) {
2319
+ const { packageDir, manifest } = await resolvePackageContextDir(deps.packageDir);
2320
+ if (typeof manifest.version !== "string") throw new Error("plugin package manifest is invalid");
2321
+ parseVersion(manifest.version);
2322
+ const installation = await discoverInstallation(deps.dshHome ?? resolveDshHome(), packageDir);
2323
+ return {
2324
+ version: manifest.version,
2325
+ ...installation === void 0 ? {} : { installation }
2326
+ };
2327
+ }
2328
+ async function checkPluginUpdate(deps = {}) {
2329
+ try {
2330
+ const { version, installation } = await packageContext(deps);
2331
+ if (installation === void 0) return {
2332
+ currentVersion: version,
2333
+ source: "unknown",
2334
+ state: "unavailable",
2335
+ canUpdate: false,
2336
+ restartRequired: false,
2337
+ message: "Active DSH profile could not be identified uniquely"
2338
+ };
2339
+ if (installation.source === "link") return {
2340
+ currentVersion: version,
2341
+ source: "link",
2342
+ state: "linked",
2343
+ canUpdate: false,
2344
+ restartRequired: false,
2345
+ message: "Local development link; updates come from the linked checkout"
2346
+ };
2347
+ if (installation.source !== "registry") return {
2348
+ currentVersion: version,
2349
+ source: installation.source,
2350
+ state: "unsupported",
2351
+ canUpdate: false,
2352
+ restartRequired: false,
2353
+ message: "This installation source cannot be updated from the npm registry"
2354
+ };
2355
+ const latest = await (deps.fetchLatest ?? registryLatest)(AbortSignal.timeout(CHECK_TIMEOUT_MS));
2356
+ const comparison = compareVersions(version, latest);
2357
+ return {
2358
+ currentVersion: version,
2359
+ latestVersion: latest,
2360
+ source: "registry",
2361
+ state: comparison < 0 ? "available" : "current",
2362
+ canUpdate: comparison < 0,
2363
+ restartRequired: comparison < 0
2364
+ };
2365
+ } catch (error) {
2366
+ return {
2367
+ currentVersion: "unknown",
2368
+ source: "unknown",
2369
+ state: "error",
2370
+ canUpdate: false,
2371
+ restartRequired: false,
2372
+ message: safeMessage(error)
2373
+ };
2374
+ }
2375
+ }
2376
+ async function updatePlugin(deps = {}) {
2377
+ const { version, installation } = await packageContext(deps);
2378
+ if (installation === void 0 || installation.source !== "registry") throw new Error("Plugin update is unavailable for this installation");
2379
+ const latest = await (deps.fetchLatest ?? registryLatest)(AbortSignal.timeout(CHECK_TIMEOUT_MS));
2380
+ if (compareVersions(version, latest) >= 0) return {
2381
+ currentVersion: version,
2382
+ latestVersion: latest,
2383
+ source: "registry",
2384
+ state: "current",
2385
+ canUpdate: false,
2386
+ restartRequired: false
2387
+ };
2388
+ const resolveExecutable = deps.resolveExecutable;
2389
+ const spawn = deps.spawn;
2390
+ if (resolveExecutable === void 0 || spawn === void 0) throw new Error("DSH update runtime is unavailable");
2391
+ const signal = AbortSignal.timeout(UPDATE_TIMEOUT_MS);
2392
+ const handle = spawn({
2393
+ argv: [
2394
+ await resolveExecutable("dsh", {}, signal),
2395
+ "plugin",
2396
+ "--profile",
2397
+ installation.profile,
2398
+ "update",
2399
+ PLUGIN_PACKAGE_NAME
2400
+ ],
2401
+ cwd: installation.profileDir,
2402
+ env: {},
2403
+ stdio: {
2404
+ stdin: "ignore",
2405
+ stdout: { maxBytes: MAX_UPDATE_OUTPUT_BYTES },
2406
+ stderr: { maxBytes: MAX_UPDATE_OUTPUT_BYTES }
2407
+ },
2408
+ graceMs: 2e3,
2409
+ signal
2410
+ });
2411
+ const outcome = await handle.done;
2412
+ if (outcome.exitCode !== 0) {
2413
+ const detail = handle.collected.stderr?.readFrom(0).text ?? "";
2414
+ throw new Error(`DSH plugin update failed (${outcome.exitCode ?? outcome.signal ?? "unknown exit"}): ${safeMessage(detail)}`);
2415
+ }
2416
+ return {
2417
+ currentVersion: version,
2418
+ latestVersion: latest,
2419
+ source: "registry",
2420
+ state: "current",
2421
+ canUpdate: false,
2422
+ restartRequired: true,
2423
+ message: "Update installed; restart DSH Desktop to load it"
2424
+ };
2425
+ }
2426
+ function registerClaudeUpdateRoutes(ctx, runtime, deps = {}) {
2427
+ const shared = {
2428
+ ...deps,
2429
+ resolveExecutable: runtime.resolveExecutable.bind(runtime),
2430
+ spawn: runtime.spawn.bind(runtime)
2431
+ };
2432
+ for (const route of [{
2433
+ path: CLAUDE_UPDATE_CHECK_PATH,
2434
+ method: "GET",
2435
+ run: () => checkPluginUpdate(shared)
2436
+ }, {
2437
+ path: CLAUDE_UPDATE_PATH,
2438
+ method: "POST",
2439
+ run: () => updatePlugin(shared)
2440
+ }]) ctx.effect(() => ctx.webServer.register({
2441
+ kind: "exact",
2442
+ path: route.path,
2443
+ handler: async (req, res) => {
2444
+ if (req.method !== route.method) return json(res, 405, { error: "method not allowed" });
2445
+ if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
2446
+ try {
2447
+ json(res, 200, await route.run());
2448
+ } catch (error) {
2449
+ json(res, 500, { error: safeMessage(error) });
2450
+ }
2451
+ }
2452
+ }), `dsh-claude: ${route.method} ${route.path}`);
2453
+ }
2454
+ //#endregion
2076
2455
  //#region src/index.ts
2077
2456
  const name = "llm-claude";
2078
2457
  const inject = [
@@ -2081,7 +2460,8 @@ const inject = [
2081
2460
  "agentPresets",
2082
2461
  "commands",
2083
2462
  "subprocess",
2084
- "approval"
2463
+ "approval",
2464
+ "userQuestions"
2085
2465
  ];
2086
2466
  const Config = z.object({
2087
2467
  executablePath: z.string().default(""),
@@ -2213,6 +2593,7 @@ async function apply(ctx, config) {
2213
2593
  const supervisor = new ClaudeSupervisor({
2214
2594
  runtime: ctx.subprocess,
2215
2595
  approval: ctx.approval,
2596
+ userQuestions: ctx.userQuestions,
2216
2597
  config: supervisorConfig,
2217
2598
  runDetached: (operation) => ctx.agents.withoutInitiator(operation),
2218
2599
  sidecar
@@ -2282,6 +2663,7 @@ async function apply(ctx, config) {
2282
2663
  ctx.effect(() => () => supervisor.dispose(), "dsh-claude: process supervisor");
2283
2664
  ctx.inject(["webServer"], (webCtx) => {
2284
2665
  registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
2666
+ registerClaudeUpdateRoutes(webCtx, webCtx.subprocess);
2285
2667
  registerClaudeProjectionRoute(webCtx, sidecar, (sessionId) => {
2286
2668
  const agent = webCtx.agents.get(sessionId);
2287
2669
  return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";