@norman-else/dsh-claude 0.1.7 → 0.1.9
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/INSTALL.md +55 -55
- package/LICENSE +21 -21
- package/README.md +191 -191
- package/cordis.patch.yml +12 -12
- package/legacy-preset/agent.cordis.yml +11 -11
- package/legacy-preset/preset.yml +4 -4
- package/lib/bin.mjs +1 -1
- package/lib/bin.mjs.map +1 -1
- package/lib/client.d.ts +26 -0
- package/lib/client.js +329 -52
- package/lib/client.js.map +1 -1
- package/lib/{events-DP0Ov_Re.mjs → events-BVQkPmjg.mjs} +4 -2
- package/lib/events-BVQkPmjg.mjs.map +1 -0
- package/lib/index.d.mts +1 -1
- package/lib/index.mjs +366 -26
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-YMR2WMjn.mjs → presenters-CQ9dBVyY.mjs} +2 -2
- package/lib/presenters-CQ9dBVyY.mjs.map +1 -0
- package/lib/{preset-installer-BKw-SdOm.mjs → preset-installer-DUmmDvBm.mjs} +4 -4
- package/lib/preset-installer-DUmmDvBm.mjs.map +1 -0
- package/lib/preset-route.mjs +2 -2
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +3 -1
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
- package/lib/events-DP0Ov_Re.mjs.map +0 -1
- package/lib/presenters-YMR2WMjn.mjs.map +0 -1
- package/lib/preset-installer-BKw-SdOm.mjs.map +0 -1
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
|
|
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_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;
|
|
@@ -1865,15 +1866,81 @@ const NO_RETRY_POLICY = Object.freeze({
|
|
|
1865
1866
|
maxDelayMs: 1e4,
|
|
1866
1867
|
jitterRatio: .1
|
|
1867
1868
|
});
|
|
1868
|
-
function
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1869
|
+
function abortIfRequested(signal) {
|
|
1870
|
+
if (signal?.aborted !== true) return;
|
|
1871
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
1872
|
+
const error = /* @__PURE__ */ new Error("Claude Code input resolution aborted");
|
|
1873
|
+
error.name = "AbortError";
|
|
1874
|
+
throw error;
|
|
1875
|
+
}
|
|
1876
|
+
function finiteNonNegative(value) {
|
|
1877
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1878
|
+
}
|
|
1879
|
+
function validateImageRef(ref, attachments, imageIndex) {
|
|
1880
|
+
const limits = attachments.imageLimits;
|
|
1881
|
+
if (!limits.mediaTypes.includes(ref.mediaType)) throw new Error(`dsh-claude: image ${imageIndex} has an unsupported media type`);
|
|
1882
|
+
if (!finiteNonNegative(ref.bytes) || ref.bytes > limits.maxImageBytes) throw new Error(`dsh-claude: image ${imageIndex} exceeds the configured byte limit`);
|
|
1883
|
+
const maxDimension = "maxImageDimension" in limits && finiteNonNegative(limits.maxImageDimension) ? limits.maxImageDimension : void 0;
|
|
1884
|
+
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`);
|
|
1885
|
+
}
|
|
1886
|
+
function imageBlock(data, mediaType) {
|
|
1887
|
+
return {
|
|
1888
|
+
type: "image",
|
|
1889
|
+
source: {
|
|
1890
|
+
type: "base64",
|
|
1891
|
+
media_type: mediaType,
|
|
1892
|
+
data: Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64")
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
/** Resolve only the newest direct human message; Claude's session owns history. */
|
|
1897
|
+
async function resolveDirectUserPrompt(messages, attachments, signal) {
|
|
1898
|
+
const message = [...messages].reverse().find((candidate) => candidate.role === "user" && candidate.source.kind === "user");
|
|
1899
|
+
if (message === void 0) throw new Error("dsh-claude: no direct human input was present in this model step");
|
|
1900
|
+
const imageRefs = message.content.filter((block) => block.type === "image").map((block) => block.attachment);
|
|
1901
|
+
const limits = attachments.imageLimits;
|
|
1902
|
+
if (imageRefs.length > limits.maxImagesPerMessage) throw new Error("dsh-claude: prompt exceeds the configured image-count limit");
|
|
1903
|
+
let declaredBytes = 0;
|
|
1904
|
+
imageRefs.forEach((ref, index) => {
|
|
1905
|
+
validateImageRef(ref, attachments, index + 1);
|
|
1906
|
+
declaredBytes += ref.bytes;
|
|
1907
|
+
if (!Number.isSafeInteger(declaredBytes) || declaredBytes > limits.maxMessageImageBytes) throw new Error("dsh-claude: prompt exceeds the configured aggregate image-byte limit");
|
|
1908
|
+
});
|
|
1909
|
+
if (imageRefs.length === 0) {
|
|
1872
1910
|
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
|
|
1873
1911
|
if (text.length > 0) return text;
|
|
1874
|
-
|
|
1912
|
+
throw new Error("dsh-claude: the newest direct human message has no supported content");
|
|
1875
1913
|
}
|
|
1876
|
-
|
|
1914
|
+
const content = [];
|
|
1915
|
+
let imageIndex = 0;
|
|
1916
|
+
let verifiedBytes = 0;
|
|
1917
|
+
for (const block of message.content) {
|
|
1918
|
+
abortIfRequested(signal);
|
|
1919
|
+
if (block.type === "text") {
|
|
1920
|
+
content.push({
|
|
1921
|
+
type: "text",
|
|
1922
|
+
text: block.text
|
|
1923
|
+
});
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
if (block.type !== "image") continue;
|
|
1927
|
+
imageIndex += 1;
|
|
1928
|
+
let stored;
|
|
1929
|
+
try {
|
|
1930
|
+
stored = await attachments.readImage(block.attachment, signal);
|
|
1931
|
+
} catch {
|
|
1932
|
+
abortIfRequested(signal);
|
|
1933
|
+
throw new Error(`dsh-claude: image ${imageIndex} could not be read or verified`);
|
|
1934
|
+
}
|
|
1935
|
+
abortIfRequested(signal);
|
|
1936
|
+
validateImageRef(stored.ref, attachments, imageIndex);
|
|
1937
|
+
if (stored.data.byteLength !== stored.ref.bytes || stored.ref.mediaType !== block.attachment.mediaType) throw new Error(`dsh-claude: image ${imageIndex} failed attachment verification`);
|
|
1938
|
+
verifiedBytes += stored.data.byteLength;
|
|
1939
|
+
if (!Number.isSafeInteger(verifiedBytes) || verifiedBytes > limits.maxMessageImageBytes) throw new Error("dsh-claude: prompt exceeds the configured aggregate image-byte limit");
|
|
1940
|
+
content.push(imageBlock(stored.data, stored.ref.mediaType));
|
|
1941
|
+
}
|
|
1942
|
+
if (content.length === 0) throw new Error("dsh-claude: the newest direct human message has no supported content");
|
|
1943
|
+
return content;
|
|
1877
1944
|
}
|
|
1878
1945
|
function tokenUsage(usage) {
|
|
1879
1946
|
const normalized = {
|
|
@@ -1896,11 +1963,13 @@ function resolveAgent(agents, options) {
|
|
|
1896
1963
|
var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
1897
1964
|
#supervisor;
|
|
1898
1965
|
#agents;
|
|
1966
|
+
#attachments;
|
|
1899
1967
|
#presetIdFor;
|
|
1900
|
-
constructor(supervisor, agents, presetIdFor) {
|
|
1968
|
+
constructor(supervisor, agents, attachments, presetIdFor) {
|
|
1901
1969
|
super();
|
|
1902
1970
|
this.#supervisor = supervisor;
|
|
1903
1971
|
this.#agents = agents;
|
|
1972
|
+
this.#attachments = attachments;
|
|
1904
1973
|
this.#presetIdFor = presetIdFor;
|
|
1905
1974
|
}
|
|
1906
1975
|
providerInfo(provider) {
|
|
@@ -1918,7 +1987,7 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
1918
1987
|
id: model.id,
|
|
1919
1988
|
name: model.name,
|
|
1920
1989
|
description: model.description,
|
|
1921
|
-
inputModalities: ["text"]
|
|
1990
|
+
inputModalities: ["text", "image"]
|
|
1922
1991
|
}));
|
|
1923
1992
|
}
|
|
1924
1993
|
async resolveModel(provider, model) {
|
|
@@ -1930,7 +1999,7 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
1930
1999
|
name: known?.name ?? `Claude Code ${model}`,
|
|
1931
2000
|
...known === void 0 ? {} : { description: known.description },
|
|
1932
2001
|
...contextWindow === void 0 ? {} : { context: { contextWindow } },
|
|
1933
|
-
inputModalities: ["text"],
|
|
2002
|
+
inputModalities: ["text", "image"],
|
|
1934
2003
|
reasoning: { efforts: THINKING_MODES.map((mode) => ({
|
|
1935
2004
|
id: ReasoningEffortId(mode.id),
|
|
1936
2005
|
name: mode.name,
|
|
@@ -1943,7 +2012,23 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
1943
2012
|
const agent = resolveAgent(this.#agents, options);
|
|
1944
2013
|
if (this.#presetIdFor(agent) !== "claude") throw new Error(`dsh-claude: provider ${CLAUDE_CODE_PROVIDER} is available only to the ${CLAUDE_CODE_PRESET_ID} preset`);
|
|
1945
2014
|
const thinkingMode = thinkingModeFor(options.reasoningEffort);
|
|
1946
|
-
|
|
2015
|
+
let prompt;
|
|
2016
|
+
try {
|
|
2017
|
+
prompt = await resolveDirectUserPrompt(options.messages, this.#attachments, options.signal);
|
|
2018
|
+
} catch (error) {
|
|
2019
|
+
if (error.name !== "AbortError") throw error;
|
|
2020
|
+
yield {
|
|
2021
|
+
type: "finish",
|
|
2022
|
+
reason: {
|
|
2023
|
+
kind: "aborted",
|
|
2024
|
+
failure: {
|
|
2025
|
+
code: "aborted",
|
|
2026
|
+
message: error instanceof Error ? error.message : "Claude Code input resolution aborted"
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
1947
2032
|
const events = await this.#supervisor.runTurn({
|
|
1948
2033
|
agent,
|
|
1949
2034
|
prompt,
|
|
@@ -2028,8 +2113,8 @@ var ClaudeCodeAdapter = class extends LlmAdapter {
|
|
|
2028
2113
|
if (!completed) throw new Error("dsh-claude: Claude turn stream ended without a result");
|
|
2029
2114
|
}
|
|
2030
2115
|
};
|
|
2031
|
-
function createClaudeCodeAdapter(supervisor, agents, presetIdFor) {
|
|
2032
|
-
return new ClaudeCodeAdapter(supervisor, agents, presetIdFor);
|
|
2116
|
+
function createClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor) {
|
|
2117
|
+
return new ClaudeCodeAdapter(supervisor, agents, attachments, presetIdFor);
|
|
2033
2118
|
}
|
|
2034
2119
|
//#endregion
|
|
2035
2120
|
//#region src/http.ts
|
|
@@ -2064,7 +2149,7 @@ function json(res, status, value) {
|
|
|
2064
2149
|
//#region src/doctor-routes.ts
|
|
2065
2150
|
const CLAUDE_DOCTOR_PROBE_TIMEOUT_MS = 15e3;
|
|
2066
2151
|
const claudeBridgeDiagnostics = /* @__PURE__ */ new WeakMap();
|
|
2067
|
-
function safeMessage(error) {
|
|
2152
|
+
function safeMessage$1(error) {
|
|
2068
2153
|
return redactText(error instanceof Error ? error.message : String(error), 1e3);
|
|
2069
2154
|
}
|
|
2070
2155
|
/** Live command-bridge diagnostics: which agents exist, their presets, and how
|
|
@@ -2080,14 +2165,14 @@ function commandDiagnostics(ctx) {
|
|
|
2080
2165
|
const preset = ctx.agentPresets.composedPreset(agent.ctx);
|
|
2081
2166
|
if (preset !== void 0) info.preset = preset;
|
|
2082
2167
|
} catch (error) {
|
|
2083
|
-
info.error = safeMessage(error);
|
|
2168
|
+
info.error = safeMessage$1(error);
|
|
2084
2169
|
}
|
|
2085
2170
|
try {
|
|
2086
2171
|
const list = ctx.commands.list(agent);
|
|
2087
2172
|
info.commandCount = list.length;
|
|
2088
2173
|
info.sample = list.slice(0, 10).map((command) => command.name);
|
|
2089
2174
|
} catch (error) {
|
|
2090
|
-
info.error = info.error === void 0 ? safeMessage(error) : `${info.error}; ${safeMessage(error)}`;
|
|
2175
|
+
info.error = info.error === void 0 ? safeMessage$1(error) : `${info.error}; ${safeMessage$1(error)}`;
|
|
2091
2176
|
}
|
|
2092
2177
|
const bridge = claudeBridgeDiagnostics.get(agent);
|
|
2093
2178
|
if (bridge !== void 0) info.bridge = bridge;
|
|
@@ -2095,7 +2180,7 @@ function commandDiagnostics(ctx) {
|
|
|
2095
2180
|
})
|
|
2096
2181
|
};
|
|
2097
2182
|
} catch (error) {
|
|
2098
|
-
return { error: safeMessage(error) };
|
|
2183
|
+
return { error: safeMessage$1(error) };
|
|
2099
2184
|
}
|
|
2100
2185
|
}
|
|
2101
2186
|
function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolutionError) {
|
|
@@ -2119,7 +2204,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2119
2204
|
version: { status: "not-run" },
|
|
2120
2205
|
authentication: { status: "not-run" },
|
|
2121
2206
|
handshake: "not-run",
|
|
2122
|
-
message: safeMessage(resolutionError),
|
|
2207
|
+
message: safeMessage$1(resolutionError),
|
|
2123
2208
|
limits: {
|
|
2124
2209
|
idleTimeoutMs: config.idleTimeoutMs,
|
|
2125
2210
|
maxProcesses: config.maxProcesses
|
|
@@ -2149,7 +2234,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2149
2234
|
commandBridge: commandDiagnostics(ctx)
|
|
2150
2235
|
});
|
|
2151
2236
|
} catch (error) {
|
|
2152
|
-
json(res, 500, { error: safeMessage(error) });
|
|
2237
|
+
json(res, 500, { error: safeMessage$1(error) });
|
|
2153
2238
|
}
|
|
2154
2239
|
}
|
|
2155
2240
|
}), "dsh-claude: Doctor route");
|
|
@@ -2198,6 +2283,259 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession) {
|
|
|
2198
2283
|
}), "dsh-claude: sidecar projection route");
|
|
2199
2284
|
}
|
|
2200
2285
|
//#endregion
|
|
2286
|
+
//#region src/update-routes.ts
|
|
2287
|
+
const PLUGIN_PACKAGE_NAME = "@norman-else/dsh-claude";
|
|
2288
|
+
const UPDATE_TIMEOUT_MS = 3e4;
|
|
2289
|
+
const CHECK_TIMEOUT_MS = 1e4;
|
|
2290
|
+
const MAX_MANIFEST_BYTES = 262144;
|
|
2291
|
+
const MAX_UPDATE_OUTPUT_BYTES = 32768;
|
|
2292
|
+
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
2293
|
+
const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
2294
|
+
function safeMessage(error) {
|
|
2295
|
+
return redactText(error instanceof Error ? error.message : String(error), 500);
|
|
2296
|
+
}
|
|
2297
|
+
async function readManifest(path) {
|
|
2298
|
+
const text = await readFile(path, "utf8");
|
|
2299
|
+
if (Buffer.byteLength(text) > MAX_MANIFEST_BYTES) throw new Error("package manifest is too large");
|
|
2300
|
+
const value = JSON.parse(text);
|
|
2301
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("package manifest is invalid");
|
|
2302
|
+
return value;
|
|
2303
|
+
}
|
|
2304
|
+
function dependencySpec(manifest) {
|
|
2305
|
+
if (typeof manifest.dependencies !== "object" || manifest.dependencies === null || Array.isArray(manifest.dependencies)) return void 0;
|
|
2306
|
+
const value = manifest.dependencies[PLUGIN_PACKAGE_NAME];
|
|
2307
|
+
return typeof value === "string" && value.length <= 2e3 ? value : void 0;
|
|
2308
|
+
}
|
|
2309
|
+
function classifyInstallSpec(spec) {
|
|
2310
|
+
if (/^(?:link|file|workspace):/i.test(spec)) return "link";
|
|
2311
|
+
if (/^(?:git(?:\+[^:]+)?:|github:|https?:|npm:)/i.test(spec) || /\.git(?:#|$)/i.test(spec)) return "unsupported";
|
|
2312
|
+
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";
|
|
2313
|
+
}
|
|
2314
|
+
async function samePath(left, right) {
|
|
2315
|
+
try {
|
|
2316
|
+
const [a, b] = await Promise.all([realpath(left), realpath(right)]);
|
|
2317
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
2318
|
+
} catch {
|
|
2319
|
+
return false;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
function linkedTarget(profileDir, spec) {
|
|
2323
|
+
const match = /^(?:link|file):(.*)$/i.exec(spec);
|
|
2324
|
+
return match?.[1] === void 0 ? void 0 : resolve(profileDir, match[1]);
|
|
2325
|
+
}
|
|
2326
|
+
async function discoverInstallation(dshHome, packageDir) {
|
|
2327
|
+
const profilesDir = join(dshHome, "profiles");
|
|
2328
|
+
const matches = [];
|
|
2329
|
+
let profiles;
|
|
2330
|
+
try {
|
|
2331
|
+
profiles = await opendir(profilesDir);
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
if (error.code === "ENOENT") return void 0;
|
|
2334
|
+
throw error;
|
|
2335
|
+
}
|
|
2336
|
+
for await (const entry of profiles) {
|
|
2337
|
+
if (!entry.isDirectory() || !PROFILE_NAME.test(entry.name)) continue;
|
|
2338
|
+
const profileDir = join(profilesDir, entry.name);
|
|
2339
|
+
let manifest;
|
|
2340
|
+
try {
|
|
2341
|
+
manifest = await readManifest(join(profileDir, "package.json"));
|
|
2342
|
+
} catch {
|
|
2343
|
+
continue;
|
|
2344
|
+
}
|
|
2345
|
+
const spec = dependencySpec(manifest);
|
|
2346
|
+
if (spec === void 0) continue;
|
|
2347
|
+
const source = classifyInstallSpec(spec);
|
|
2348
|
+
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({
|
|
2349
|
+
profile: entry.name,
|
|
2350
|
+
profileDir,
|
|
2351
|
+
source,
|
|
2352
|
+
spec
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
2356
|
+
}
|
|
2357
|
+
function parseVersion(version) {
|
|
2358
|
+
const match = SEMVER.exec(version);
|
|
2359
|
+
if (match === null) throw new Error("package version is not valid semver");
|
|
2360
|
+
return match;
|
|
2361
|
+
}
|
|
2362
|
+
function compareVersions(left, right) {
|
|
2363
|
+
const a = parseVersion(left);
|
|
2364
|
+
const b = parseVersion(right);
|
|
2365
|
+
for (const index of [
|
|
2366
|
+
1,
|
|
2367
|
+
2,
|
|
2368
|
+
3
|
|
2369
|
+
]) {
|
|
2370
|
+
const difference = Number(a[index]) - Number(b[index]);
|
|
2371
|
+
if (difference !== 0) return Math.sign(difference);
|
|
2372
|
+
}
|
|
2373
|
+
const aPre = a[4];
|
|
2374
|
+
const bPre = b[4];
|
|
2375
|
+
if (aPre === void 0) return bPre === void 0 ? 0 : 1;
|
|
2376
|
+
if (bPre === void 0) return -1;
|
|
2377
|
+
return aPre.localeCompare(bPre, "en", { numeric: true });
|
|
2378
|
+
}
|
|
2379
|
+
async function registryLatest(signal) {
|
|
2380
|
+
const response = await fetch("https://registry.npmjs.org/%40norman-else%2Fdsh-claude", {
|
|
2381
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
2382
|
+
signal
|
|
2383
|
+
});
|
|
2384
|
+
if (!response.ok) throw new Error(`npm registry returned HTTP ${response.status}`);
|
|
2385
|
+
const latest = (await response.json())["dist-tags"]?.latest;
|
|
2386
|
+
if (typeof latest !== "string") throw new Error("npm registry response has no latest version");
|
|
2387
|
+
parseVersion(latest);
|
|
2388
|
+
return latest;
|
|
2389
|
+
}
|
|
2390
|
+
async function resolvePackageContextDir(configured) {
|
|
2391
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
2392
|
+
const candidates = configured === void 0 ? [moduleDir, dirname(moduleDir)] : [configured];
|
|
2393
|
+
for (const packageDir of candidates) try {
|
|
2394
|
+
const manifest = await readManifest(join(packageDir, "package.json"));
|
|
2395
|
+
if (manifest.name === "@norman-else/dsh-claude") return {
|
|
2396
|
+
packageDir,
|
|
2397
|
+
manifest
|
|
2398
|
+
};
|
|
2399
|
+
} catch {}
|
|
2400
|
+
throw new Error("plugin package manifest is invalid");
|
|
2401
|
+
}
|
|
2402
|
+
async function packageContext(deps) {
|
|
2403
|
+
const { packageDir, manifest } = await resolvePackageContextDir(deps.packageDir);
|
|
2404
|
+
if (typeof manifest.version !== "string") throw new Error("plugin package manifest is invalid");
|
|
2405
|
+
parseVersion(manifest.version);
|
|
2406
|
+
const installation = await discoverInstallation(deps.dshHome ?? resolveDshHome(), packageDir);
|
|
2407
|
+
return {
|
|
2408
|
+
version: manifest.version,
|
|
2409
|
+
...installation === void 0 ? {} : { installation }
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
async function checkPluginUpdate(deps = {}) {
|
|
2413
|
+
try {
|
|
2414
|
+
const { version, installation } = await packageContext(deps);
|
|
2415
|
+
if (installation === void 0) return {
|
|
2416
|
+
currentVersion: version,
|
|
2417
|
+
source: "unknown",
|
|
2418
|
+
state: "unavailable",
|
|
2419
|
+
canUpdate: false,
|
|
2420
|
+
restartRequired: false,
|
|
2421
|
+
message: "Active DSH profile could not be identified uniquely"
|
|
2422
|
+
};
|
|
2423
|
+
if (installation.source === "link") return {
|
|
2424
|
+
currentVersion: version,
|
|
2425
|
+
source: "link",
|
|
2426
|
+
state: "linked",
|
|
2427
|
+
canUpdate: false,
|
|
2428
|
+
restartRequired: false,
|
|
2429
|
+
message: "Local development link; updates come from the linked checkout"
|
|
2430
|
+
};
|
|
2431
|
+
if (installation.source !== "registry") return {
|
|
2432
|
+
currentVersion: version,
|
|
2433
|
+
source: installation.source,
|
|
2434
|
+
state: "unsupported",
|
|
2435
|
+
canUpdate: false,
|
|
2436
|
+
restartRequired: false,
|
|
2437
|
+
message: "This installation source cannot be updated from the npm registry"
|
|
2438
|
+
};
|
|
2439
|
+
const latest = await (deps.fetchLatest ?? registryLatest)(AbortSignal.timeout(CHECK_TIMEOUT_MS));
|
|
2440
|
+
const comparison = compareVersions(version, latest);
|
|
2441
|
+
return {
|
|
2442
|
+
currentVersion: version,
|
|
2443
|
+
latestVersion: latest,
|
|
2444
|
+
source: "registry",
|
|
2445
|
+
state: comparison < 0 ? "available" : "current",
|
|
2446
|
+
canUpdate: comparison < 0,
|
|
2447
|
+
restartRequired: comparison < 0
|
|
2448
|
+
};
|
|
2449
|
+
} catch (error) {
|
|
2450
|
+
return {
|
|
2451
|
+
currentVersion: "unknown",
|
|
2452
|
+
source: "unknown",
|
|
2453
|
+
state: "error",
|
|
2454
|
+
canUpdate: false,
|
|
2455
|
+
restartRequired: false,
|
|
2456
|
+
message: safeMessage(error)
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
async function updatePlugin(deps = {}) {
|
|
2461
|
+
const { version, installation } = await packageContext(deps);
|
|
2462
|
+
if (installation === void 0 || installation.source !== "registry") throw new Error("Plugin update is unavailable for this installation");
|
|
2463
|
+
const latest = await (deps.fetchLatest ?? registryLatest)(AbortSignal.timeout(CHECK_TIMEOUT_MS));
|
|
2464
|
+
if (compareVersions(version, latest) >= 0) return {
|
|
2465
|
+
currentVersion: version,
|
|
2466
|
+
latestVersion: latest,
|
|
2467
|
+
source: "registry",
|
|
2468
|
+
state: "current",
|
|
2469
|
+
canUpdate: false,
|
|
2470
|
+
restartRequired: false
|
|
2471
|
+
};
|
|
2472
|
+
const resolveExecutable = deps.resolveExecutable;
|
|
2473
|
+
const spawn = deps.spawn;
|
|
2474
|
+
if (resolveExecutable === void 0 || spawn === void 0) throw new Error("DSH update runtime is unavailable");
|
|
2475
|
+
const signal = AbortSignal.timeout(UPDATE_TIMEOUT_MS);
|
|
2476
|
+
const handle = spawn({
|
|
2477
|
+
argv: [
|
|
2478
|
+
await resolveExecutable("dsh", {}, signal),
|
|
2479
|
+
"plugin",
|
|
2480
|
+
"--profile",
|
|
2481
|
+
installation.profile,
|
|
2482
|
+
"update",
|
|
2483
|
+
PLUGIN_PACKAGE_NAME
|
|
2484
|
+
],
|
|
2485
|
+
cwd: installation.profileDir,
|
|
2486
|
+
env: {},
|
|
2487
|
+
stdio: {
|
|
2488
|
+
stdin: "ignore",
|
|
2489
|
+
stdout: { maxBytes: MAX_UPDATE_OUTPUT_BYTES },
|
|
2490
|
+
stderr: { maxBytes: MAX_UPDATE_OUTPUT_BYTES }
|
|
2491
|
+
},
|
|
2492
|
+
graceMs: 2e3,
|
|
2493
|
+
signal
|
|
2494
|
+
});
|
|
2495
|
+
const outcome = await handle.done;
|
|
2496
|
+
if (outcome.exitCode !== 0) {
|
|
2497
|
+
const detail = handle.collected.stderr?.readFrom(0).text ?? "";
|
|
2498
|
+
throw new Error(`DSH plugin update failed (${outcome.exitCode ?? outcome.signal ?? "unknown exit"}): ${safeMessage(detail)}`);
|
|
2499
|
+
}
|
|
2500
|
+
return {
|
|
2501
|
+
currentVersion: version,
|
|
2502
|
+
latestVersion: latest,
|
|
2503
|
+
source: "registry",
|
|
2504
|
+
state: "current",
|
|
2505
|
+
canUpdate: false,
|
|
2506
|
+
restartRequired: true,
|
|
2507
|
+
message: "Update installed; restart DSH Desktop to load it"
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
function registerClaudeUpdateRoutes(ctx, runtime, deps = {}) {
|
|
2511
|
+
const shared = {
|
|
2512
|
+
...deps,
|
|
2513
|
+
resolveExecutable: runtime.resolveExecutable.bind(runtime),
|
|
2514
|
+
spawn: runtime.spawn.bind(runtime)
|
|
2515
|
+
};
|
|
2516
|
+
for (const route of [{
|
|
2517
|
+
path: CLAUDE_UPDATE_CHECK_PATH,
|
|
2518
|
+
method: "GET",
|
|
2519
|
+
run: () => checkPluginUpdate(shared)
|
|
2520
|
+
}, {
|
|
2521
|
+
path: CLAUDE_UPDATE_PATH,
|
|
2522
|
+
method: "POST",
|
|
2523
|
+
run: () => updatePlugin(shared)
|
|
2524
|
+
}]) ctx.effect(() => ctx.webServer.register({
|
|
2525
|
+
kind: "exact",
|
|
2526
|
+
path: route.path,
|
|
2527
|
+
handler: async (req, res) => {
|
|
2528
|
+
if (req.method !== route.method) return json(res, 405, { error: "method not allowed" });
|
|
2529
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
2530
|
+
try {
|
|
2531
|
+
json(res, 200, await route.run());
|
|
2532
|
+
} catch (error) {
|
|
2533
|
+
json(res, 500, { error: safeMessage(error) });
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
}), `dsh-claude: ${route.method} ${route.path}`);
|
|
2537
|
+
}
|
|
2538
|
+
//#endregion
|
|
2201
2539
|
//#region src/index.ts
|
|
2202
2540
|
const name = "llm-claude";
|
|
2203
2541
|
const inject = [
|
|
@@ -2207,7 +2545,8 @@ const inject = [
|
|
|
2207
2545
|
"commands",
|
|
2208
2546
|
"subprocess",
|
|
2209
2547
|
"approval",
|
|
2210
|
-
"userQuestions"
|
|
2548
|
+
"userQuestions",
|
|
2549
|
+
"attachments"
|
|
2211
2550
|
];
|
|
2212
2551
|
const Config = z.object({
|
|
2213
2552
|
executablePath: z.string().default(""),
|
|
@@ -2347,7 +2686,7 @@ async function apply(ctx, config) {
|
|
|
2347
2686
|
let resolutionError;
|
|
2348
2687
|
try {
|
|
2349
2688
|
supervisorConfig.executablePath = (await resolveClaudeExecutable(ctx.subprocess, config.executablePath === void 0 || config.executablePath.length === 0 ? void 0 : config.executablePath)).path;
|
|
2350
|
-
ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, (agent) => ctx.agentPresets.composedPreset(agent.ctx)));
|
|
2689
|
+
ctx.llm.registerAdapter([...CLAUDE_CODE_PROVIDER_IDS], createClaudeCodeAdapter(supervisor, ctx.agents, ctx.attachments, (agent) => ctx.agentPresets.composedPreset(agent.ctx)));
|
|
2351
2690
|
ctx.effect(() => {
|
|
2352
2691
|
const mounted = /* @__PURE__ */ new Map();
|
|
2353
2692
|
const pending = /* @__PURE__ */ new Set();
|
|
@@ -2409,6 +2748,7 @@ async function apply(ctx, config) {
|
|
|
2409
2748
|
ctx.effect(() => () => supervisor.dispose(), "dsh-claude: process supervisor");
|
|
2410
2749
|
ctx.inject(["webServer"], (webCtx) => {
|
|
2411
2750
|
registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
|
|
2751
|
+
registerClaudeUpdateRoutes(webCtx, webCtx.subprocess);
|
|
2412
2752
|
registerClaudeProjectionRoute(webCtx, sidecar, (sessionId) => {
|
|
2413
2753
|
const agent = webCtx.agents.get(sessionId);
|
|
2414
2754
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|