@norman-else/dsh-claude 0.1.8 → 0.1.10

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.d.mts CHANGED
@@ -176,7 +176,7 @@ type ClaudeTurnStreamEvent = {
176
176
  type ClaudeThinkingMode = 'off' | 'ultracode' | EffortLevel;
177
177
  interface ClaudeTurnRequest {
178
178
  agent: Agent;
179
- prompt: string;
179
+ prompt: SDKUserMessage['message']['content'];
180
180
  model?: string;
181
181
  thinkingMode?: ClaudeThinkingMode;
182
182
  signal?: AbortSignal;
package/lib/index.mjs CHANGED
@@ -1,12 +1,13 @@
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";
1
+ import { _ as CLAUDE_PROJECTION_PATH, a as latestClaudeTasks, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_GLOBAL_SETTINGS_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_CHECK_PATH, x as TASK_TOOL_NAMES, y as CLAUDE_UPDATE_PATH } from "./events-6xTApIQw.mjs";
2
+ import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-C8Yx_rNY.mjs";
3
+ import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-B0NrA4Hi.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
7
  import { chmod, mkdir, opendir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
8
- import { dirname, join, resolve } from "node:path";
8
+ import { dirname, extname, join, resolve } from "node:path";
9
9
  import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
10
+ import { homedir } from "node:os";
10
11
  import { query } from "@anthropic-ai/claude-agent-sdk";
11
12
  import { EventEmitter } from "node:events";
12
13
  import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subprocess";
@@ -1866,15 +1867,81 @@ const NO_RETRY_POLICY = Object.freeze({
1866
1867
  maxDelayMs: 1e4,
1867
1868
  jitterRatio: .1
1868
1869
  });
1869
- function extractDirectUserText(messages) {
1870
- for (let index = messages.length - 1; index >= 0; index -= 1) {
1871
- const message = messages[index];
1872
- if (message?.role !== "user" || message.source.kind !== "user") continue;
1870
+ function abortIfRequested(signal) {
1871
+ if (signal?.aborted !== true) return;
1872
+ if (signal.reason instanceof Error) throw signal.reason;
1873
+ const error = /* @__PURE__ */ new Error("Claude Code input resolution aborted");
1874
+ error.name = "AbortError";
1875
+ throw error;
1876
+ }
1877
+ function finiteNonNegative(value) {
1878
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1879
+ }
1880
+ function validateImageRef(ref, attachments, imageIndex) {
1881
+ const limits = attachments.imageLimits;
1882
+ if (!limits.mediaTypes.includes(ref.mediaType)) throw new Error(`dsh-claude: image ${imageIndex} has an unsupported media type`);
1883
+ if (!finiteNonNegative(ref.bytes) || ref.bytes > limits.maxImageBytes) throw new Error(`dsh-claude: image ${imageIndex} exceeds the configured byte limit`);
1884
+ const maxDimension = "maxImageDimension" in limits && finiteNonNegative(limits.maxImageDimension) ? limits.maxImageDimension : void 0;
1885
+ if (!finiteNonNegative(ref.width) || !finiteNonNegative(ref.height) || ref.width * ref.height > limits.maxImagePixels || maxDimension !== void 0 && (ref.width > maxDimension || ref.height > maxDimension)) throw new Error(`dsh-claude: image ${imageIndex} exceeds the configured dimension limit`);
1886
+ }
1887
+ function imageBlock(data, mediaType) {
1888
+ return {
1889
+ type: "image",
1890
+ source: {
1891
+ type: "base64",
1892
+ media_type: mediaType,
1893
+ data: Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64")
1894
+ }
1895
+ };
1896
+ }
1897
+ /** Resolve only the newest direct human message; Claude's session owns history. */
1898
+ async function resolveDirectUserPrompt(messages, attachments, signal) {
1899
+ const message = [...messages].reverse().find((candidate) => candidate.role === "user" && candidate.source.kind === "user");
1900
+ if (message === void 0) throw new Error("dsh-claude: no direct human input was present in this model step");
1901
+ const imageRefs = message.content.filter((block) => block.type === "image").map((block) => block.attachment);
1902
+ const limits = attachments.imageLimits;
1903
+ if (imageRefs.length > limits.maxImagesPerMessage) throw new Error("dsh-claude: prompt exceeds the configured image-count limit");
1904
+ let declaredBytes = 0;
1905
+ imageRefs.forEach((ref, index) => {
1906
+ validateImageRef(ref, attachments, index + 1);
1907
+ declaredBytes += ref.bytes;
1908
+ if (!Number.isSafeInteger(declaredBytes) || declaredBytes > limits.maxMessageImageBytes) throw new Error("dsh-claude: prompt exceeds the configured aggregate image-byte limit");
1909
+ });
1910
+ if (imageRefs.length === 0) {
1873
1911
  const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
1874
1912
  if (text.length > 0) return text;
1875
- if (message.content.some((block) => block.type === "image")) throw new Error("dsh-claude: image-only prompts are not supported in v0.1; include a text prompt");
1913
+ throw new Error("dsh-claude: the newest direct human message has no supported content");
1876
1914
  }
1877
- throw new Error("dsh-claude: no direct human text was present in this model step");
1915
+ const content = [];
1916
+ let imageIndex = 0;
1917
+ let verifiedBytes = 0;
1918
+ for (const block of message.content) {
1919
+ abortIfRequested(signal);
1920
+ if (block.type === "text") {
1921
+ content.push({
1922
+ type: "text",
1923
+ text: block.text
1924
+ });
1925
+ continue;
1926
+ }
1927
+ if (block.type !== "image") continue;
1928
+ imageIndex += 1;
1929
+ let stored;
1930
+ try {
1931
+ stored = await attachments.readImage(block.attachment, signal);
1932
+ } catch {
1933
+ abortIfRequested(signal);
1934
+ throw new Error(`dsh-claude: image ${imageIndex} could not be read or verified`);
1935
+ }
1936
+ abortIfRequested(signal);
1937
+ validateImageRef(stored.ref, attachments, imageIndex);
1938
+ if (stored.data.byteLength !== stored.ref.bytes || stored.ref.mediaType !== block.attachment.mediaType) throw new Error(`dsh-claude: image ${imageIndex} failed attachment verification`);
1939
+ verifiedBytes += stored.data.byteLength;
1940
+ if (!Number.isSafeInteger(verifiedBytes) || verifiedBytes > limits.maxMessageImageBytes) throw new Error("dsh-claude: prompt exceeds the configured aggregate image-byte limit");
1941
+ content.push(imageBlock(stored.data, stored.ref.mediaType));
1942
+ }
1943
+ if (content.length === 0) throw new Error("dsh-claude: the newest direct human message has no supported content");
1944
+ return content;
1878
1945
  }
1879
1946
  function tokenUsage(usage) {
1880
1947
  const normalized = {
@@ -1897,11 +1964,13 @@ function resolveAgent(agents, options) {
1897
1964
  var ClaudeCodeAdapter = class extends LlmAdapter {
1898
1965
  #supervisor;
1899
1966
  #agents;
1967
+ #attachments;
1900
1968
  #presetIdFor;
1901
- constructor(supervisor, agents, presetIdFor) {
1969
+ constructor(supervisor, agents, attachments, presetIdFor) {
1902
1970
  super();
1903
1971
  this.#supervisor = supervisor;
1904
1972
  this.#agents = agents;
1973
+ this.#attachments = attachments;
1905
1974
  this.#presetIdFor = presetIdFor;
1906
1975
  }
1907
1976
  providerInfo(provider) {
@@ -1919,7 +1988,7 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
1919
1988
  id: model.id,
1920
1989
  name: model.name,
1921
1990
  description: model.description,
1922
- inputModalities: ["text"]
1991
+ inputModalities: ["text", "image"]
1923
1992
  }));
1924
1993
  }
1925
1994
  async resolveModel(provider, model) {
@@ -1931,7 +2000,7 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
1931
2000
  name: known?.name ?? `Claude Code ${model}`,
1932
2001
  ...known === void 0 ? {} : { description: known.description },
1933
2002
  ...contextWindow === void 0 ? {} : { context: { contextWindow } },
1934
- inputModalities: ["text"],
2003
+ inputModalities: ["text", "image"],
1935
2004
  reasoning: { efforts: THINKING_MODES.map((mode) => ({
1936
2005
  id: ReasoningEffortId(mode.id),
1937
2006
  name: mode.name,
@@ -1944,7 +2013,23 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
1944
2013
  const agent = resolveAgent(this.#agents, options);
1945
2014
  if (this.#presetIdFor(agent) !== "claude") throw new Error(`dsh-claude: provider ${CLAUDE_CODE_PROVIDER} is available only to the ${CLAUDE_CODE_PRESET_ID} preset`);
1946
2015
  const thinkingMode = thinkingModeFor(options.reasoningEffort);
1947
- const prompt = extractDirectUserText(options.messages);
2016
+ let prompt;
2017
+ try {
2018
+ prompt = await resolveDirectUserPrompt(options.messages, this.#attachments, options.signal);
2019
+ } catch (error) {
2020
+ if (error.name !== "AbortError") throw error;
2021
+ yield {
2022
+ type: "finish",
2023
+ reason: {
2024
+ kind: "aborted",
2025
+ failure: {
2026
+ code: "aborted",
2027
+ message: error instanceof Error ? error.message : "Claude Code input resolution aborted"
2028
+ }
2029
+ }
2030
+ };
2031
+ return;
2032
+ }
1948
2033
  const events = await this.#supervisor.runTurn({
1949
2034
  agent,
1950
2035
  prompt,
@@ -2029,8 +2114,8 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
2029
2114
  if (!completed) throw new Error("dsh-claude: Claude turn stream ended without a result");
2030
2115
  }
2031
2116
  };
2032
- function createClaudeCodeAdapter(supervisor, agents, presetIdFor) {
2033
- return new ClaudeCodeAdapter(supervisor, agents, presetIdFor);
2117
+ function createClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor) {
2118
+ return new ClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor);
2034
2119
  }
2035
2120
  //#endregion
2036
2121
  //#region src/http.ts
@@ -2373,6 +2458,11 @@ async function checkPluginUpdate(deps = {}) {
2373
2458
  };
2374
2459
  }
2375
2460
  }
2461
+ async function verifyInstalledVersion(installation, expectedVersion) {
2462
+ if (dependencySpec(await readManifest(join(installation.profileDir, "package.json"))) !== expectedVersion) throw new Error("DSH plugin update completed without updating the profile dependency");
2463
+ const installedManifest = await readManifest(join(installation.profileDir, "node_modules", ...PLUGIN_PACKAGE_NAME.split("/"), "package.json"));
2464
+ if (installedManifest.name !== "@norman-else/dsh-claude" || installedManifest.version !== expectedVersion) throw new Error("DSH plugin update completed without installing the requested version");
2465
+ }
2376
2466
  async function updatePlugin(deps = {}) {
2377
2467
  const { version, installation } = await packageContext(deps);
2378
2468
  if (installation === void 0 || installation.source !== "registry") throw new Error("Plugin update is unavailable for this installation");
@@ -2395,8 +2485,8 @@ async function updatePlugin(deps = {}) {
2395
2485
  "plugin",
2396
2486
  "--profile",
2397
2487
  installation.profile,
2398
- "update",
2399
- PLUGIN_PACKAGE_NAME
2488
+ "add",
2489
+ `${PLUGIN_PACKAGE_NAME}@${latest}`
2400
2490
  ],
2401
2491
  cwd: installation.profileDir,
2402
2492
  env: {},
@@ -2413,8 +2503,9 @@ async function updatePlugin(deps = {}) {
2413
2503
  const detail = handle.collected.stderr?.readFrom(0).text ?? "";
2414
2504
  throw new Error(`DSH plugin update failed (${outcome.exitCode ?? outcome.signal ?? "unknown exit"}): ${safeMessage(detail)}`);
2415
2505
  }
2506
+ await verifyInstalledVersion(installation, latest);
2416
2507
  return {
2417
- currentVersion: version,
2508
+ currentVersion: latest,
2418
2509
  latestVersion: latest,
2419
2510
  source: "registry",
2420
2511
  state: "current",
@@ -2452,6 +2543,194 @@ function registerClaudeUpdateRoutes(ctx, runtime, deps = {}) {
2452
2543
  }), `dsh-claude: ${route.method} ${route.path}`);
2453
2544
  }
2454
2545
  //#endregion
2546
+ //#region src/global-settings.ts
2547
+ const MAX_SETTINGS_BYTES = 262144;
2548
+ const MAX_REQUEST_BYTES = 8192;
2549
+ const MAX_STYLE_BYTES = 65536;
2550
+ const MAX_STYLE_FILES = 256;
2551
+ const BUILTIN_OUTPUT_STYLES = [
2552
+ "Default",
2553
+ "Proactive",
2554
+ "Concise",
2555
+ "Explanatory",
2556
+ "Learning"
2557
+ ];
2558
+ const STYLE_NAME = /^[\p{L}\p{N}][\p{L}\p{N} ._()\[\]-]{0,127}$/u;
2559
+ function pathsFor(deps) {
2560
+ const root = join(homedir(), ".claude");
2561
+ return {
2562
+ settingsFile: deps.paths?.settingsFile ?? join(root, "settings.json"),
2563
+ outputStylesDir: deps.paths?.outputStylesDir ?? join(root, "output-styles")
2564
+ };
2565
+ }
2566
+ function object(value) {
2567
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2568
+ }
2569
+ async function readDocument(path) {
2570
+ try {
2571
+ const text = await readFile(path, "utf8");
2572
+ if (Buffer.byteLength(text) > MAX_SETTINGS_BYTES) throw new Error("Claude Code settings file is too large");
2573
+ const parsed = object(JSON.parse(text));
2574
+ if (parsed === void 0) throw new Error("Claude Code settings file must contain a JSON object");
2575
+ return parsed;
2576
+ } catch (error) {
2577
+ if (error.code === "ENOENT") return {};
2578
+ throw error;
2579
+ }
2580
+ }
2581
+ function frontmatterName(text) {
2582
+ if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) return void 0;
2583
+ const normalized = text.replaceAll("\r\n", "\n");
2584
+ const end = normalized.indexOf("\n---\n", 4);
2585
+ if (end < 0) return void 0;
2586
+ for (const line of normalized.slice(4, end).split("\n")) {
2587
+ const match = /^name:\s*(.+?)\s*$/.exec(line);
2588
+ if (match?.[1] === void 0) continue;
2589
+ const raw = match[1];
2590
+ const value = raw.startsWith("\"") && raw.endsWith("\"") || raw.startsWith("'") && raw.endsWith("'") ? raw.slice(1, -1) : raw;
2591
+ return STYLE_NAME.test(value) ? value : void 0;
2592
+ }
2593
+ }
2594
+ async function userOutputStyleOptions(directory) {
2595
+ const options = [];
2596
+ let entries;
2597
+ try {
2598
+ entries = await opendir(directory);
2599
+ } catch (error) {
2600
+ if (error.code === "ENOENT") return options;
2601
+ throw error;
2602
+ }
2603
+ for await (const entry of entries) {
2604
+ if (options.length >= MAX_STYLE_FILES) break;
2605
+ if (!entry.isFile() || extname(entry.name).toLowerCase() !== ".md") continue;
2606
+ try {
2607
+ const text = await readFile(join(directory, entry.name), "utf8");
2608
+ if (Buffer.byteLength(text) > MAX_STYLE_BYTES) continue;
2609
+ const name = frontmatterName(text) ?? entry.name.slice(0, -3);
2610
+ if (STYLE_NAME.test(name)) options.push({
2611
+ value: name,
2612
+ label: name,
2613
+ source: "user"
2614
+ });
2615
+ } catch {}
2616
+ }
2617
+ return options;
2618
+ }
2619
+ const DESCRIPTORS = [{
2620
+ key: "outputStyle",
2621
+ kind: "select",
2622
+ effect: "new-session",
2623
+ async options(paths) {
2624
+ const builtIn = BUILTIN_OUTPUT_STYLES.map((value) => ({
2625
+ value,
2626
+ label: value,
2627
+ source: "built-in"
2628
+ }));
2629
+ const user = await userOutputStyleOptions(paths.outputStylesDir);
2630
+ const seen = new Set(builtIn.map((option) => option.value));
2631
+ return [...builtIn, ...user.filter((option) => !seen.has(option.value)).sort((a, b) => a.label.localeCompare(b.label))];
2632
+ },
2633
+ read(document, options) {
2634
+ const value = document.outputStyle;
2635
+ return typeof value === "string" && STYLE_NAME.test(value) ? value : "Default";
2636
+ },
2637
+ apply(document, value, options) {
2638
+ if (typeof value !== "string" || !options.some((option) => option.value === value)) throw new Error("Invalid value for global setting outputStyle");
2639
+ if (value === "Default") delete document.outputStyle;
2640
+ else document.outputStyle = value;
2641
+ }
2642
+ }];
2643
+ const DESCRIPTOR_BY_KEY = new Map(DESCRIPTORS.map((descriptor) => [descriptor.key, descriptor]));
2644
+ let pendingWrite = Promise.resolve();
2645
+ async function views(document, paths) {
2646
+ return { settings: await Promise.all(DESCRIPTORS.map(async (descriptor) => {
2647
+ const discovered = await descriptor.options(paths);
2648
+ const value = descriptor.read(document, discovered);
2649
+ const options = discovered.some((option) => option.value === value) ? discovered : [...discovered, {
2650
+ value,
2651
+ label: value,
2652
+ source: "configured"
2653
+ }];
2654
+ return {
2655
+ key: descriptor.key,
2656
+ kind: descriptor.kind,
2657
+ value,
2658
+ options,
2659
+ effect: descriptor.effect
2660
+ };
2661
+ })) };
2662
+ }
2663
+ async function readGlobalSettings(deps = {}) {
2664
+ const paths = pathsFor(deps);
2665
+ return views(await readDocument(paths.settingsFile), paths);
2666
+ }
2667
+ async function atomicWrite(path, document) {
2668
+ await mkdir(dirname(path), {
2669
+ recursive: true,
2670
+ mode: 448
2671
+ });
2672
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
2673
+ try {
2674
+ await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, {
2675
+ encoding: "utf8",
2676
+ mode: 384,
2677
+ flag: "wx"
2678
+ });
2679
+ await chmod(temporary, 384);
2680
+ await rename(temporary, path);
2681
+ await chmod(path, 384);
2682
+ } finally {
2683
+ await rm(temporary, { force: true }).catch(() => void 0);
2684
+ }
2685
+ }
2686
+ function updateGlobalSettings(changes, deps = {}) {
2687
+ const changeObject = object(changes);
2688
+ if (changeObject === void 0 || Object.keys(changeObject).length === 0) return Promise.reject(/* @__PURE__ */ new Error("Global settings changes must be a non-empty object"));
2689
+ for (const key of Object.keys(changeObject)) if (!DESCRIPTOR_BY_KEY.has(key)) return Promise.reject(/* @__PURE__ */ new Error(`Unsupported global setting: ${key}`));
2690
+ const paths = pathsFor(deps);
2691
+ const operation = pendingWrite.catch(() => void 0).then(async () => {
2692
+ const document = await readDocument(paths.settingsFile);
2693
+ for (const [key, value] of Object.entries(changeObject)) {
2694
+ const descriptor = DESCRIPTOR_BY_KEY.get(key);
2695
+ descriptor.apply(document, value, await descriptor.options(paths));
2696
+ }
2697
+ await atomicWrite(paths.settingsFile, document);
2698
+ return views(document, paths);
2699
+ });
2700
+ pendingWrite = operation;
2701
+ return operation;
2702
+ }
2703
+ async function requestJson(req) {
2704
+ const declared = Number(req.headers["content-length"] ?? 0);
2705
+ if (!Number.isFinite(declared) || declared < 0 || declared > MAX_REQUEST_BYTES) throw new Error("Request body is too large");
2706
+ const chunks = [];
2707
+ let bytes = 0;
2708
+ for await (const chunk of req) {
2709
+ const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2710
+ bytes += data.byteLength;
2711
+ if (bytes > MAX_REQUEST_BYTES) throw new Error("Request body is too large");
2712
+ chunks.push(data);
2713
+ }
2714
+ const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")));
2715
+ if (body === void 0 || Object.keys(body).some((key) => key !== "changes")) throw new Error("Invalid global settings request");
2716
+ return body.changes;
2717
+ }
2718
+ function registerClaudeGlobalSettingsRoute(ctx, deps = {}) {
2719
+ ctx.effect(() => ctx.webServer.register({
2720
+ kind: "exact",
2721
+ path: CLAUDE_GLOBAL_SETTINGS_PATH,
2722
+ handler: async (req, res) => {
2723
+ if (req.method !== "GET" && req.method !== "PATCH") return json(res, 405, { error: "method not allowed" });
2724
+ if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
2725
+ try {
2726
+ json(res, 200, req.method === "GET" ? await readGlobalSettings(deps) : await updateGlobalSettings(await requestJson(req), deps));
2727
+ } catch (error) {
2728
+ json(res, 400, { error: error instanceof Error ? error.message : "Invalid global settings request" });
2729
+ }
2730
+ }
2731
+ }), "dsh-claude: global settings");
2732
+ }
2733
+ //#endregion
2455
2734
  //#region src/index.ts
2456
2735
  const name = "llm-claude";
2457
2736
  const inject = [
@@ -2461,7 +2740,8 @@ const inject = [
2461
2740
  "commands",
2462
2741
  "subprocess",
2463
2742
  "approval",
2464
- "userQuestions"
2743
+ "userQuestions",
2744
+ "attachments"
2465
2745
  ];
2466
2746
  const Config = z.object({
2467
2747
  executablePath: z.string().default(""),
@@ -2601,7 +2881,7 @@ async function apply(ctx, config) {
2601
2881
  let resolutionError;
2602
2882
  try {
2603
2883
  supervisorConfig.executablePath = (await resolveClaudeExecutable(ctx.subprocess, config.executablePath === void 0 || config.executablePath.length === 0 ? void 0 : config.executablePath)).path;
2604
- ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, (agent) => ctx.agentPresets.composedPreset(agent.ctx)));
2884
+ ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, ctx.attachments, (agent) => ctx.agentPresets.composedPreset(agent.ctx)));
2605
2885
  ctx.effect(() => {
2606
2886
  const mounted = /* @__PURE__ */ new Map();
2607
2887
  const pending = /* @__PURE__ */ new Set();
@@ -2664,6 +2944,7 @@ async function apply(ctx, config) {
2664
2944
  ctx.inject(["webServer"], (webCtx) => {
2665
2945
  registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
2666
2946
  registerClaudeUpdateRoutes(webCtx, webCtx.subprocess);
2947
+ registerClaudeGlobalSettingsRoute(webCtx);
2667
2948
  registerClaudeProjectionRoute(webCtx, sidecar, (sessionId) => {
2668
2949
  const agent = webCtx.agents.get(sessionId);
2669
2950
  return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";