@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/bin.mjs +1 -1
- package/lib/client.d.ts +6 -0
- package/lib/client.js +107 -0
- package/lib/client.js.map +1 -1
- package/lib/{events-BVQkPmjg.mjs → events-6xTApIQw.mjs} +3 -2
- package/lib/events-6xTApIQw.mjs.map +1 -0
- package/lib/index.d.mts +1 -1
- package/lib/index.mjs +302 -21
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-CQ9dBVyY.mjs → presenters-C8Yx_rNY.mjs} +2 -2
- package/lib/{presenters-CQ9dBVyY.mjs.map → presenters-C8Yx_rNY.mjs.map} +1 -1
- package/lib/{preset-installer-DUmmDvBm.mjs → preset-installer-B0NrA4Hi.mjs} +2 -2
- package/lib/{preset-installer-DUmmDvBm.mjs.map → preset-installer-B0NrA4Hi.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -2
- package/package.json +3 -1
- package/lib/events-BVQkPmjg.mjs.map +0 -1
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:
|
|
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
|
|
2
|
-
import { a as ClaudeCommandBridge, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
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
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
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
|
-
|
|
1913
|
+
throw new Error("dsh-claude: the newest direct human message has no supported content");
|
|
1876
1914
|
}
|
|
1877
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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:
|
|
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";
|