@norman-else/dsh-claude 0.1.25 → 0.1.26
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 +82 -82
- 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 +30 -4
- package/lib/client.js +1667 -184
- package/lib/client.js.map +1 -1
- package/lib/{command-bridge-B48egrCr.mjs → command-bridge-vyZoEeEO.mjs} +2 -2
- package/lib/command-bridge-vyZoEeEO.mjs.map +1 -0
- package/lib/{events-BhU0iJUi.mjs → events-CY6V9oQ4.mjs} +5 -3
- package/lib/events-CY6V9oQ4.mjs.map +1 -0
- package/lib/index.mjs +486 -126
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-Bzw8tuGI.mjs → preset-installer-BMRaIHLu.mjs} +2 -2
- package/lib/preset-installer-BMRaIHLu.mjs.map +1 -0
- package/lib/preset-route.mjs +2 -2
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +181 -181
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
- package/lib/command-bridge-B48egrCr.mjs.map +0 -1
- package/lib/events-BhU0iJUi.mjs.map +0 -1
- package/lib/preset-installer-Bzw8tuGI.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
1
|
+
import { C as CLAUDE_REPOSITORY_SETUP_PATH, D as CLAUDE_UPDATE_PATH, E as CLAUDE_UPDATE_CHECK_PATH, S as CLAUDE_REPOSITORY_FILE_PATH, T as CLAUDE_REVIEW_COMMENT_PATH, _ as CLAUDE_GLOBAL_SETTINGS_PATH, a as latestClaudeTasks, b as CLAUDE_REPOSITORY_ACTION_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ASK_PATH, g as CLAUDE_DOCTOR_PATH, h as CLAUDE_CODE_PROVIDER_IDS, i as latestClaudeSessionBinding, k as TASK_TOOL_NAMES, l as redactText, m as CLAUDE_CODE_PROVIDER, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PRESET_ID, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_JIRA_PATH, w as CLAUDE_REPOSITORY_STATUS_PATH, x as CLAUDE_REPOSITORY_FEEDBACK_PATH, y as CLAUDE_PROJECTION_PATH } from "./events-CY6V9oQ4.mjs";
|
|
2
|
+
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-vyZoEeEO.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-BMRaIHLu.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { chmod, mkdir, opendir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
|
-
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
+
import { basename, dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
8
8
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -12,6 +12,7 @@ import { EventEmitter } from "node:events";
|
|
|
12
12
|
import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subprocess";
|
|
13
13
|
import { LlmAdapter, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { StringDecoder } from "node:string_decoder";
|
|
15
16
|
//#region src/sidecar.ts
|
|
16
17
|
const SIDECAR_SCHEMA_VERSION = 1;
|
|
17
18
|
const MAX_ACTIVITIES = 1e4;
|
|
@@ -25,7 +26,7 @@ function emptyProjection() {
|
|
|
25
26
|
activities: []
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
|
-
function record$
|
|
29
|
+
function record$10(value) {
|
|
29
30
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
30
31
|
}
|
|
31
32
|
function finiteInteger(value) {
|
|
@@ -35,7 +36,7 @@ function string$4(value, max) {
|
|
|
35
36
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
36
37
|
}
|
|
37
38
|
function binding(value) {
|
|
38
|
-
const input = record$
|
|
39
|
+
const input = record$10(value);
|
|
39
40
|
if (input === void 0 || !string$4(input.claudeSessionId, 512) || !string$4(input.sdkVersion, 128) || !string$4(input.cwd, 4096) || input.cliVersion !== void 0 && !string$4(input.cliVersion, 128)) return void 0;
|
|
40
41
|
return {
|
|
41
42
|
claudeSessionId: input.claudeSessionId,
|
|
@@ -65,22 +66,22 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
65
66
|
"failed"
|
|
66
67
|
]);
|
|
67
68
|
function activity(value) {
|
|
68
|
-
const input = record$
|
|
69
|
+
const input = record$10(value);
|
|
69
70
|
if (input === void 0 || !finiteInteger(input.turn) || !finiteInteger(input.step) || !finiteInteger(input.ordinal) || typeof input.kind !== "string" || !ACTIVITY_KINDS.has(input.kind) || input.phase !== void 0 && (typeof input.phase !== "string" || !ACTIVITY_PHASES.has(input.phase))) return void 0;
|
|
70
71
|
return normalizeActivity(input);
|
|
71
72
|
}
|
|
72
73
|
function contextUsage(value) {
|
|
73
|
-
const input = record$
|
|
74
|
+
const input = record$10(value);
|
|
74
75
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
75
76
|
return normalizeContextUsage(input);
|
|
76
77
|
}
|
|
77
78
|
function tasks(value) {
|
|
78
|
-
const input = record$
|
|
79
|
+
const input = record$10(value);
|
|
79
80
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
80
81
|
return normalizeTasksEvent(input.tasks);
|
|
81
82
|
}
|
|
82
83
|
function parseClaudeSidecar(value) {
|
|
83
|
-
const input = record$
|
|
84
|
+
const input = record$10(value);
|
|
84
85
|
if (input === void 0 || input.schemaVersion !== SIDECAR_SCHEMA_VERSION || !finiteInteger(input.revision) || !Array.isArray(input.activities) || input.activities.length > MAX_ACTIVITIES) throw new Error("dsh-claude: invalid sidecar document");
|
|
85
86
|
const activities = input.activities.map(activity);
|
|
86
87
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
@@ -111,7 +112,7 @@ function mergeActivities(existing, additions) {
|
|
|
111
112
|
function normalizeBinding(input) {
|
|
112
113
|
return {
|
|
113
114
|
claudeSessionId: redactText(input.claudeSessionId, 512),
|
|
114
|
-
sdkVersion: redactText(input.sdkVersion ?? "0.3.
|
|
115
|
+
sdkVersion: redactText(input.sdkVersion ?? "0.3.247", 128),
|
|
115
116
|
cwd: redactText(input.cwd, 4096),
|
|
116
117
|
...input.cliVersion === void 0 ? {} : { cliVersion: redactText(input.cliVersion, 128) }
|
|
117
118
|
};
|
|
@@ -653,7 +654,7 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
653
654
|
}
|
|
654
655
|
//#endregion
|
|
655
656
|
//#region src/sdk-messages.ts
|
|
656
|
-
function record$
|
|
657
|
+
function record$9(value) {
|
|
657
658
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
658
659
|
}
|
|
659
660
|
function string$3(value) {
|
|
@@ -668,7 +669,7 @@ function taskUsageOf(usage) {
|
|
|
668
669
|
return Object.keys(normalized).length === 0 ? void 0 : normalized;
|
|
669
670
|
}
|
|
670
671
|
function resultUsage(message) {
|
|
671
|
-
const usage = record$
|
|
672
|
+
const usage = record$9(message.usage);
|
|
672
673
|
const normalized = {};
|
|
673
674
|
if (usage !== void 0) {
|
|
674
675
|
if (typeof usage.input_tokens === "number") normalized.inputTokens = usage.input_tokens;
|
|
@@ -680,7 +681,7 @@ function resultUsage(message) {
|
|
|
680
681
|
return normalized;
|
|
681
682
|
}
|
|
682
683
|
function normalizeAssistant(message) {
|
|
683
|
-
const content = record$
|
|
684
|
+
const content = record$9(message.message)?.content;
|
|
684
685
|
if (!Array.isArray(content)) return [{
|
|
685
686
|
kind: "protocol-error",
|
|
686
687
|
title: "Malformed Claude assistant message",
|
|
@@ -689,7 +690,7 @@ function normalizeAssistant(message) {
|
|
|
689
690
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
690
691
|
const normalized = [];
|
|
691
692
|
for (const item of content) {
|
|
692
|
-
const block = record$
|
|
693
|
+
const block = record$9(item);
|
|
693
694
|
if (block === void 0) continue;
|
|
694
695
|
if (block.type === "text") {
|
|
695
696
|
const text = string$3(block.text);
|
|
@@ -722,7 +723,7 @@ function normalizeAssistant(message) {
|
|
|
722
723
|
}
|
|
723
724
|
function normalizeUser(message) {
|
|
724
725
|
if (message.isReplay === true) return [];
|
|
725
|
-
const content = record$
|
|
726
|
+
const content = record$9(message.message)?.content;
|
|
726
727
|
if (typeof content === "string") return [];
|
|
727
728
|
if (!Array.isArray(content)) return [{
|
|
728
729
|
kind: "protocol-error",
|
|
@@ -732,7 +733,7 @@ function normalizeUser(message) {
|
|
|
732
733
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
733
734
|
const normalized = [];
|
|
734
735
|
for (const item of content) {
|
|
735
|
-
const block = record$
|
|
736
|
+
const block = record$9(item);
|
|
736
737
|
if (block?.type !== "tool_result") continue;
|
|
737
738
|
const toolUseId = string$3(block.tool_use_id);
|
|
738
739
|
if (toolUseId === void 0) continue;
|
|
@@ -813,7 +814,7 @@ function normalizeSystem(message) {
|
|
|
813
814
|
const summary = string$3(message.summary);
|
|
814
815
|
const subagentType = string$3(message.subagent_type);
|
|
815
816
|
const lastToolName = string$3(message.last_tool_name);
|
|
816
|
-
const usage = taskUsageOf(record$
|
|
817
|
+
const usage = taskUsageOf(record$9(message.usage));
|
|
817
818
|
return [{
|
|
818
819
|
kind: "subagent",
|
|
819
820
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -829,7 +830,7 @@ function normalizeSystem(message) {
|
|
|
829
830
|
}];
|
|
830
831
|
}
|
|
831
832
|
if (subtype === "task_updated") {
|
|
832
|
-
const patch = record$
|
|
833
|
+
const patch = record$9(message.patch);
|
|
833
834
|
const status = string$3(patch?.status);
|
|
834
835
|
const taskId = string$3(message.task_id);
|
|
835
836
|
const description = string$3(patch?.description);
|
|
@@ -852,7 +853,7 @@ function normalizeSystem(message) {
|
|
|
852
853
|
const taskId = string$3(message.task_id);
|
|
853
854
|
const summary = string$3(message.summary);
|
|
854
855
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
855
|
-
const usage = taskUsageOf(record$
|
|
856
|
+
const usage = taskUsageOf(record$9(message.usage));
|
|
856
857
|
return [{
|
|
857
858
|
kind: "subagent",
|
|
858
859
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -867,7 +868,7 @@ function normalizeSystem(message) {
|
|
|
867
868
|
if (subtype === "background_tasks_changed") return [{
|
|
868
869
|
kind: "background-tasks",
|
|
869
870
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
870
|
-
const entry = record$
|
|
871
|
+
const entry = record$9(item);
|
|
871
872
|
const taskId = string$3(entry?.task_id);
|
|
872
873
|
const description = string$3(entry?.description);
|
|
873
874
|
const taskType = string$3(entry?.task_type);
|
|
@@ -910,10 +911,10 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
910
911
|
function normalizeSdkMessage(message) {
|
|
911
912
|
const value = message;
|
|
912
913
|
if (value.type === "stream_event") {
|
|
913
|
-
const event = record$
|
|
914
|
+
const event = record$9(value.event);
|
|
914
915
|
const parentToolUseId = string$3(value.parent_tool_use_id);
|
|
915
916
|
if (event?.type === "content_block_delta") {
|
|
916
|
-
const delta = record$
|
|
917
|
+
const delta = record$9(event.delta);
|
|
917
918
|
if (delta?.type === "text_delta") {
|
|
918
919
|
const text = string$3(delta.text);
|
|
919
920
|
return text === void 0 ? [] : [{
|
|
@@ -948,7 +949,7 @@ function normalizeSdkMessage(message) {
|
|
|
948
949
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
949
950
|
const terminalReason = string$3(value.terminal_reason);
|
|
950
951
|
const userMessageUuid = string$3(value.user_message_uuid);
|
|
951
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
952
|
+
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$9(item)).filter((item) => item !== void 0).map((item) => {
|
|
952
953
|
const toolName = string$3(item.tool_name);
|
|
953
954
|
const toolUseId = string$3(item.tool_use_id);
|
|
954
955
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
@@ -974,7 +975,7 @@ function normalizeSdkMessage(message) {
|
|
|
974
975
|
detail: value.error ?? value.output
|
|
975
976
|
}];
|
|
976
977
|
if (value.type === "rate_limit_event") {
|
|
977
|
-
const status = string$3(record$
|
|
978
|
+
const status = string$3(record$9(value.rate_limit_info)?.status);
|
|
978
979
|
return [{
|
|
979
980
|
kind: "status",
|
|
980
981
|
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
@@ -1141,11 +1142,6 @@ const BACKGROUND_TASK_REPORT_PROMPT = [
|
|
|
1141
1142
|
"Report their final completed or failed outcomes concisely to the user.",
|
|
1142
1143
|
"Do not start new tools or tasks, and do not repeat the earlier progress update."
|
|
1143
1144
|
].join(" ");
|
|
1144
|
-
/** CLI ≥ 2.1.235 emits system/init only after its first stdin input, while
|
|
1145
|
-
* the supervisor must see init before submitting any real turn. A local
|
|
1146
|
-
* slash command nudges startup without costing a model call; its lifecycle
|
|
1147
|
-
* messages are ignored because no DSH turn is active while they arrive. */
|
|
1148
|
-
const CLAUDE_HANDSHAKE_PROMPT = "/status";
|
|
1149
1145
|
function usageSummary(usage) {
|
|
1150
1146
|
return `${usage.inputTokens ?? 0} input / ${usage.outputTokens ?? 0} output tokens${usage.cumulativeCostUsd === void 0 ? "" : ` · $${usage.cumulativeCostUsd.toFixed(4)} cumulative`}`;
|
|
1151
1147
|
}
|
|
@@ -1231,10 +1227,10 @@ var ClaudeSupervisor = class {
|
|
|
1231
1227
|
await this.#makeRoom();
|
|
1232
1228
|
entry = await this.#createEntry(request.agent, request.model ?? this.#config.defaultModel, request.thinkingMode);
|
|
1233
1229
|
this.#entries.set(sessionId, entry);
|
|
1234
|
-
this.#armInitializationTimer(entry);
|
|
1235
1230
|
}
|
|
1236
1231
|
if (entry.ownerAgent !== request.agent) throw new Error(`dsh-claude: live agent identity changed for session ${sessionId}`);
|
|
1237
1232
|
if (entry.active !== void 0 || entry.state === "interrupting") throw new ClaudeTurnBusyError(sessionId);
|
|
1233
|
+
await entry.sdkInitialization;
|
|
1238
1234
|
if (entry.idleTimer !== void 0) {
|
|
1239
1235
|
clearTimeout(entry.idleTimer);
|
|
1240
1236
|
entry.idleTimer = void 0;
|
|
@@ -1245,7 +1241,7 @@ var ClaudeSupervisor = class {
|
|
|
1245
1241
|
await this.#disposeEntry(entry);
|
|
1246
1242
|
entry = await this.#createEntry(request.agent, model, request.thinkingMode);
|
|
1247
1243
|
this.#entries.set(sessionId, entry);
|
|
1248
|
-
|
|
1244
|
+
await entry.sdkInitialization;
|
|
1249
1245
|
} else {
|
|
1250
1246
|
await this.#syncPermissionMode(entry);
|
|
1251
1247
|
if (model !== entry.model) {
|
|
@@ -1318,7 +1314,7 @@ var ClaudeSupervisor = class {
|
|
|
1318
1314
|
if (this.#disposed) throw new Error("dsh-claude: supervisor is disposed");
|
|
1319
1315
|
const entry = await this.#metadataEntry(agent, model);
|
|
1320
1316
|
try {
|
|
1321
|
-
await
|
|
1317
|
+
await entry.sdkInitialization;
|
|
1322
1318
|
return await withTimeout(operation(entry.query, entry), CLAUDE_METADATA_TIMEOUT_MS, "Claude metadata request");
|
|
1323
1319
|
} finally {
|
|
1324
1320
|
entry.lastUsedAt = Date.now();
|
|
@@ -1328,13 +1324,6 @@ var ClaudeSupervisor = class {
|
|
|
1328
1324
|
this.#admissionGate = admitted.then(() => void 0, () => void 0);
|
|
1329
1325
|
return admitted;
|
|
1330
1326
|
}
|
|
1331
|
-
#whenInitialized(entry) {
|
|
1332
|
-
if (entry.initialized) return Promise.resolve();
|
|
1333
|
-
if (entry.state === "disposed" || entry.state === "disconnected" || entry.state === "outcome-unknown") return Promise.reject(/* @__PURE__ */ new Error(`dsh-claude: session ${entry.sessionId} is ${entry.state}`));
|
|
1334
|
-
return new Promise((resolve, reject) => {
|
|
1335
|
-
entry.initWaiters.push((error) => error === void 0 ? resolve() : reject(error instanceof Error ? error : new Error(String(error))));
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
1327
|
async #metadataEntry(agent, model) {
|
|
1339
1328
|
const sessionId = agent.id;
|
|
1340
1329
|
let entry = this.#entries.get(sessionId);
|
|
@@ -1347,7 +1336,6 @@ var ClaudeSupervisor = class {
|
|
|
1347
1336
|
await this.#makeRoom();
|
|
1348
1337
|
entry = await this.#createEntry(agent, model);
|
|
1349
1338
|
this.#entries.set(sessionId, entry);
|
|
1350
|
-
this.#armInitializationTimer(entry);
|
|
1351
1339
|
}
|
|
1352
1340
|
if (entry.ownerAgent !== agent) throw new Error(`dsh-claude: live agent identity changed for session ${sessionId}`);
|
|
1353
1341
|
if (entry.active !== void 0 || entry.state === "interrupting") throw new ClaudeTurnBusyError(sessionId);
|
|
@@ -1408,11 +1396,7 @@ var ClaudeSupervisor = class {
|
|
|
1408
1396
|
lifetime,
|
|
1409
1397
|
claudeSessionId: binding?.claudeSessionId,
|
|
1410
1398
|
expectedResume: binding?.claudeSessionId,
|
|
1411
|
-
handshakeUuid: randomUUID(),
|
|
1412
|
-
handshakePending: true,
|
|
1413
1399
|
initialized: false,
|
|
1414
|
-
initWaiters: [],
|
|
1415
|
-
initTimer: void 0,
|
|
1416
1400
|
idleTimer: void 0,
|
|
1417
1401
|
tasks: /* @__PURE__ */ new Map(),
|
|
1418
1402
|
taskSnapshotAt: 0,
|
|
@@ -1470,8 +1454,11 @@ var ClaudeSupervisor = class {
|
|
|
1470
1454
|
prompt: input,
|
|
1471
1455
|
options
|
|
1472
1456
|
});
|
|
1473
|
-
entry.input.push(sdkUserMessage(CLAUDE_HANDSHAKE_PROMPT, entry.handshakeUuid));
|
|
1474
1457
|
entry.pump = this.#runDetached(() => this.#pump(entry));
|
|
1458
|
+
entry.sdkInitialization = withTimeout(entry.query.initializationResult(), CLAUDE_INITIALIZATION_TIMEOUT_MS, "Claude SDK initialization").then(() => {
|
|
1459
|
+
if (entry.state === "starting") entry.state = "idle";
|
|
1460
|
+
});
|
|
1461
|
+
entry.sdkInitialization.catch((error) => this.#handleDisconnect(entry, error));
|
|
1475
1462
|
return entry;
|
|
1476
1463
|
}
|
|
1477
1464
|
async #pump(entry) {
|
|
@@ -1487,14 +1474,9 @@ var ClaudeSupervisor = class {
|
|
|
1487
1474
|
const firstInitialization = !entry.initialized;
|
|
1488
1475
|
if (entry.expectedResume !== void 0 && message.sessionId !== entry.expectedResume) throw new ClaudeProtocolError(`Claude Code resumed unexpected session ${message.sessionId}; expected ${entry.expectedResume}`);
|
|
1489
1476
|
if (message.cwd !== entry.cwd) throw new ClaudeProtocolError(`Claude Code initialized in unexpected cwd ${message.cwd}; expected ${entry.cwd}`);
|
|
1490
|
-
if (entry.initTimer !== void 0) {
|
|
1491
|
-
clearTimeout(entry.initTimer);
|
|
1492
|
-
entry.initTimer = void 0;
|
|
1493
|
-
}
|
|
1494
1477
|
entry.initialized = true;
|
|
1495
1478
|
entry.claudeSessionId = message.sessionId;
|
|
1496
1479
|
entry.state = entry.active === void 0 ? "idle" : "running";
|
|
1497
|
-
for (const waiter of entry.initWaiters.splice(0)) waiter(void 0);
|
|
1498
1480
|
if (firstInitialization && entry.tasks.size > 0) {
|
|
1499
1481
|
entry.tasks.clear();
|
|
1500
1482
|
await this.#flushTasksSnapshot(entry);
|
|
@@ -1509,15 +1491,9 @@ var ClaudeSupervisor = class {
|
|
|
1509
1491
|
const taskId = message.kind === "subagent" ? message.taskId : void 0;
|
|
1510
1492
|
if (message.kind === "subagent" && taskId !== void 0) await this.#trackTask(entry, message, taskId, entry.active?.cursor.turn);
|
|
1511
1493
|
else if (message.kind === "background-tasks") await this.#trackBackgroundLevel(entry, message.tasks, entry.active?.cursor.turn);
|
|
1512
|
-
if (entry.handshakePending) {
|
|
1513
|
-
if (message.kind === "protocol-error") throw new ClaudeProtocolError(`${message.title}: ${JSON.stringify(message.detail).slice(0, 1e3)}`);
|
|
1514
|
-
if (message.kind === "result") entry.handshakePending = false;
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
1494
|
const active = entry.active;
|
|
1518
1495
|
if (active === void 0) return;
|
|
1519
1496
|
if (message.kind === "result") {
|
|
1520
|
-
if (message.userMessageUuid !== void 0 && message.userMessageUuid === entry.handshakeUuid) return;
|
|
1521
1497
|
if (entry.claudeSessionId === void 0) throw new ClaudeProtocolError("Claude Code sent a result before initialization");
|
|
1522
1498
|
if (message.sessionId !== entry.claudeSessionId) throw new ClaudeProtocolError(`Claude Code result session ${message.sessionId} does not match ${entry.claudeSessionId}`);
|
|
1523
1499
|
if (active.phase === "waiting-tasks") {
|
|
@@ -1957,20 +1933,9 @@ var ClaudeSupervisor = class {
|
|
|
1957
1933
|
active.output.fail(failure);
|
|
1958
1934
|
entry.active = void 0;
|
|
1959
1935
|
} else entry.state = "disconnected";
|
|
1960
|
-
const waiters = entry.initWaiters.splice(0);
|
|
1961
|
-
const waiterError = error instanceof Error ? error : new Error(String(error));
|
|
1962
|
-
for (const waiter of waiters) waiter(waiterError);
|
|
1963
1936
|
this.#entries.delete(entry.sessionId);
|
|
1964
1937
|
await this.#disposeEntry(entry);
|
|
1965
1938
|
}
|
|
1966
|
-
#armInitializationTimer(entry) {
|
|
1967
|
-
const timer = setTimeout(() => {
|
|
1968
|
-
if (entry.state !== "starting" || entry.initialized) return;
|
|
1969
|
-
this.#handleDisconnect(entry, /* @__PURE__ */ new Error("Claude Code initialization timed out"));
|
|
1970
|
-
}, CLAUDE_INITIALIZATION_TIMEOUT_MS);
|
|
1971
|
-
timer.unref?.();
|
|
1972
|
-
entry.initTimer = timer;
|
|
1973
|
-
}
|
|
1974
1939
|
#armIdleTimer(entry) {
|
|
1975
1940
|
if (this.#config.idleTimeoutMs <= 0) return;
|
|
1976
1941
|
const timer = setTimeout(() => {
|
|
@@ -1984,7 +1949,6 @@ var ClaudeSupervisor = class {
|
|
|
1984
1949
|
async #disposeEntry(entry) {
|
|
1985
1950
|
if (entry.state === "disposed") return;
|
|
1986
1951
|
if (entry.idleTimer !== void 0) clearTimeout(entry.idleTimer);
|
|
1987
|
-
if (entry.initTimer !== void 0) clearTimeout(entry.initTimer);
|
|
1988
1952
|
entry.state = "disposed";
|
|
1989
1953
|
entry.input.discard(abortFailure());
|
|
1990
1954
|
entry.query.close();
|
|
@@ -2000,7 +1964,7 @@ var ClaudeSupervisor = class {
|
|
|
2000
1964
|
//#region src/review-comments.ts
|
|
2001
1965
|
const MAX_COMMENTS_PER_SESSION = 50;
|
|
2002
1966
|
const MAX_TEXT_CHARS$1 = 2e3;
|
|
2003
|
-
const MAX_PATH_CHARS$
|
|
1967
|
+
const MAX_PATH_CHARS$3 = 1024;
|
|
2004
1968
|
const MAX_LINE = 1e7;
|
|
2005
1969
|
var ReviewCommentError = class extends Error {
|
|
2006
1970
|
code;
|
|
@@ -2019,16 +1983,19 @@ var ReviewCommentStore = class {
|
|
|
2019
1983
|
add(sessionId, input) {
|
|
2020
1984
|
const path = typeof input.path === "string" ? input.path.trim() : "";
|
|
2021
1985
|
const text = typeof input.text === "string" ? input.text.trim() : "";
|
|
2022
|
-
if (path.length === 0 || path.length > MAX_PATH_CHARS$
|
|
1986
|
+
if (path.length === 0 || path.length > MAX_PATH_CHARS$3 || /[\0\r\n]/u.test(path)) throw new ReviewCommentError("invalid-request", "The comment path is invalid.");
|
|
2023
1987
|
if (text.length === 0 || text.length > MAX_TEXT_CHARS$1 || text.includes("\0")) throw new ReviewCommentError("invalid-request", "The comment text is invalid.");
|
|
2024
1988
|
if (typeof input.line !== "number" || !Number.isSafeInteger(input.line) || input.line < 1 || input.line > MAX_LINE) throw new ReviewCommentError("invalid-request", "The comment line is invalid.");
|
|
2025
1989
|
if (!validSide(input.side)) throw new ReviewCommentError("invalid-request", "The comment side is invalid.");
|
|
1990
|
+
const startLine = input.startLine === void 0 || input.startLine === null ? void 0 : input.startLine;
|
|
1991
|
+
if (startLine !== void 0 && (typeof startLine !== "number" || !Number.isSafeInteger(startLine) || startLine < 1 || startLine > input.line)) throw new ReviewCommentError("invalid-request", "The comment start line is invalid.");
|
|
2026
1992
|
const existing = this.#comments.get(sessionId) ?? [];
|
|
2027
1993
|
if (existing.length >= MAX_COMMENTS_PER_SESSION) throw new ReviewCommentError("too-many-comments", "Too many pending review comments. Remove one before adding another.");
|
|
2028
1994
|
const comment = {
|
|
2029
1995
|
id: randomUUID(),
|
|
2030
1996
|
path,
|
|
2031
1997
|
line: input.line,
|
|
1998
|
+
...startLine === void 0 || startLine === input.line ? {} : { startLine },
|
|
2032
1999
|
side: input.side,
|
|
2033
2000
|
text
|
|
2034
2001
|
};
|
|
@@ -2064,8 +2031,8 @@ var ReviewCommentStore = class {
|
|
|
2064
2031
|
function formatReviewComments(comments) {
|
|
2065
2032
|
return [
|
|
2066
2033
|
"<user-review-comments>",
|
|
2067
|
-
"The user attached these code review comments to this message. Each references a file and line from the current working tree diff:",
|
|
2068
|
-
...comments.map((comment, index) => `${index + 1}. ${comment.path}:${comment.line}${comment.side === "old" ? " (old side)" : ""} — ${comment.text}`),
|
|
2034
|
+
"The user attached these code review comments to this message. Each references a file and line (or line range) from the current working tree diff:",
|
|
2035
|
+
...comments.map((comment, index) => `${index + 1}. ${comment.path}:${comment.startLine === void 0 ? comment.line : `${comment.startLine}-${comment.line}`}${comment.side === "old" ? " (old side)" : ""} — ${comment.text}`),
|
|
2069
2036
|
"</user-review-comments>"
|
|
2070
2037
|
].join("\n");
|
|
2071
2038
|
}
|
|
@@ -2510,7 +2477,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2510
2477
|
}
|
|
2511
2478
|
//#endregion
|
|
2512
2479
|
//#region src/projection-routes.ts
|
|
2513
|
-
const MAX_SESSION_ID_CHARS$
|
|
2480
|
+
const MAX_SESSION_ID_CHARS$4 = 1024;
|
|
2514
2481
|
/** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
|
|
2515
2482
|
* the transcript hot path so git/gh latency never delays visible text. */
|
|
2516
2483
|
const META_REFRESH_MS = 5e3;
|
|
@@ -2524,7 +2491,7 @@ function targetFromUrl(rawUrl) {
|
|
|
2524
2491
|
if (stream) encoded = encoded.slice(0, -7);
|
|
2525
2492
|
if (encoded.length === 0 || encoded.includes("/")) return void 0;
|
|
2526
2493
|
const sessionId = decodeURIComponent(encoded);
|
|
2527
|
-
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$
|
|
2494
|
+
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$4) return void 0;
|
|
2528
2495
|
return {
|
|
2529
2496
|
sessionId,
|
|
2530
2497
|
stream
|
|
@@ -2686,6 +2653,7 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
2686
2653
|
//#region src/repository-status.ts
|
|
2687
2654
|
const MAX_OUTPUT_BYTES$3 = 65536;
|
|
2688
2655
|
const MAX_DIFF_BYTES = 262144;
|
|
2656
|
+
const MAX_FILE_BYTES = 8388608;
|
|
2689
2657
|
const MAX_UNTRACKED_DIFFS = 50;
|
|
2690
2658
|
const GIT_TIMEOUT_MS$3 = 5e3;
|
|
2691
2659
|
const GH_TIMEOUT_MS$1 = 8e3;
|
|
@@ -2781,14 +2749,14 @@ function parseGitHubRemote(value) {
|
|
|
2781
2749
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
2782
2750
|
return `${match[1]}/${match[2]}`;
|
|
2783
2751
|
}
|
|
2784
|
-
function record$
|
|
2752
|
+
function record$8(value) {
|
|
2785
2753
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2786
2754
|
}
|
|
2787
2755
|
function aggregateChecks(value) {
|
|
2788
2756
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
2789
2757
|
let pending = false;
|
|
2790
2758
|
for (const item of value) {
|
|
2791
|
-
const check = record$
|
|
2759
|
+
const check = record$8(item);
|
|
2792
2760
|
if (check === void 0) continue;
|
|
2793
2761
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
2794
2762
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -2812,7 +2780,7 @@ function reviewState(value) {
|
|
|
2812
2780
|
return "none";
|
|
2813
2781
|
}
|
|
2814
2782
|
function parsePullRequest(value) {
|
|
2815
|
-
const input = record$
|
|
2783
|
+
const input = record$8(value);
|
|
2816
2784
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
2817
2785
|
let url;
|
|
2818
2786
|
try {
|
|
@@ -2833,12 +2801,20 @@ function parsePullRequest(value) {
|
|
|
2833
2801
|
review: reviewState(input.reviewDecision),
|
|
2834
2802
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
2835
2803
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
2836
|
-
...typeof record$
|
|
2804
|
+
...typeof record$8(input.author)?.login === "string" ? { author: bounded(String(record$8(input.author)?.login)) } : {},
|
|
2837
2805
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
2838
2806
|
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
2839
2807
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
2840
2808
|
};
|
|
2841
2809
|
}
|
|
2810
|
+
var RepositoryFileError = class extends Error {
|
|
2811
|
+
code;
|
|
2812
|
+
constructor(code, message) {
|
|
2813
|
+
super(message);
|
|
2814
|
+
this.name = "RepositoryFileError";
|
|
2815
|
+
this.code = code;
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2842
2818
|
var RepositoryStatusService = class {
|
|
2843
2819
|
#runtime;
|
|
2844
2820
|
#cacheTtlMs;
|
|
@@ -2880,6 +2856,29 @@ var RepositoryStatusService = class {
|
|
|
2880
2856
|
this.#lastReady.set(cwd, stable);
|
|
2881
2857
|
return stable;
|
|
2882
2858
|
}
|
|
2859
|
+
/** Lines [from, to] of a working-tree file, used to expand unmodified context around diff hunks. */
|
|
2860
|
+
async fileLines(cwd, relativePath, from, to) {
|
|
2861
|
+
if (relativePath.length === 0 || isAbsolute(relativePath) || relativePath.includes("\0") || relativePath.split(/[\\/]/u).includes("..")) throw new RepositoryFileError("invalid-request", "The file path must be relative to the repository.");
|
|
2862
|
+
if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || from < 1 || to < from || to - from >= 500) throw new RepositoryFileError("invalid-request", "The requested line range is invalid.");
|
|
2863
|
+
const git = await this.#git();
|
|
2864
|
+
const top = await run(this.#runtime, git, [
|
|
2865
|
+
"rev-parse",
|
|
2866
|
+
"--path-format=absolute",
|
|
2867
|
+
"--show-toplevel"
|
|
2868
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2869
|
+
if (top.exitCode !== 0) throw new RepositoryFileError("not-repository", "The directory is not inside a git repository.");
|
|
2870
|
+
const root = resolve(top.stdout.trim());
|
|
2871
|
+
const target = resolve(root, relativePath);
|
|
2872
|
+
if (target !== root && !target.startsWith(root + sep)) throw new RepositoryFileError("invalid-request", "The file path escapes the repository.");
|
|
2873
|
+
const content = await readFile(target, "utf8");
|
|
2874
|
+
if (content.length > MAX_FILE_BYTES) throw new RepositoryFileError("too-large", "The file is too large to expand.");
|
|
2875
|
+
const all = content.split(/\r?\n/u);
|
|
2876
|
+
if (all.at(-1) === "") all.pop();
|
|
2877
|
+
return {
|
|
2878
|
+
lines: all.slice(from - 1, to),
|
|
2879
|
+
total: all.length
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2883
2882
|
async #git() {
|
|
2884
2883
|
this.#gitExecutable ??= this.#runtime.resolveExecutable("git");
|
|
2885
2884
|
return this.#gitExecutable;
|
|
@@ -3099,7 +3098,7 @@ var RepositoryStatusService = class {
|
|
|
3099
3098
|
const MAX_OUTPUT_BYTES$2 = 131072;
|
|
3100
3099
|
const GIT_TIMEOUT_MS$2 = 1e4;
|
|
3101
3100
|
const GIT_FETCH_TIMEOUT_MS = 6e4;
|
|
3102
|
-
const MAX_PATH_CHARS$
|
|
3101
|
+
const MAX_PATH_CHARS$2 = 4096;
|
|
3103
3102
|
const MAX_BRANCH_CHARS = 512;
|
|
3104
3103
|
const LEASE_SCHEMA_VERSION = 1;
|
|
3105
3104
|
const CLEANUP_GRACE_MS = 12e4;
|
|
@@ -3123,7 +3122,7 @@ async function collect$2(handle) {
|
|
|
3123
3122
|
};
|
|
3124
3123
|
}
|
|
3125
3124
|
function safePath(value) {
|
|
3126
|
-
if (value.length === 0 || value.length > MAX_PATH_CHARS$
|
|
3125
|
+
if (value.length === 0 || value.length > MAX_PATH_CHARS$2 || !isAbsolute(value) || value.includes("\0")) throw new RepositorySetupError("invalid-path", "The workspace path is invalid.");
|
|
3127
3126
|
return resolve(value);
|
|
3128
3127
|
}
|
|
3129
3128
|
function safeBranch(value) {
|
|
@@ -4000,20 +3999,20 @@ var RepositoryActionService = class {
|
|
|
4000
3999
|
};
|
|
4001
4000
|
//#endregion
|
|
4002
4001
|
//#region src/repository-setup-routes.ts
|
|
4003
|
-
const MAX_BODY_BYTES$
|
|
4004
|
-
function record$
|
|
4002
|
+
const MAX_BODY_BYTES$4 = 16384;
|
|
4003
|
+
function record$7(value) {
|
|
4005
4004
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4006
4005
|
}
|
|
4007
|
-
async function readJson$
|
|
4006
|
+
async function readJson$4(req) {
|
|
4008
4007
|
const chunks = [];
|
|
4009
4008
|
let size = 0;
|
|
4010
4009
|
for await (const chunk of req) {
|
|
4011
4010
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4012
4011
|
size += buffer.length;
|
|
4013
|
-
if (size > MAX_BODY_BYTES$
|
|
4012
|
+
if (size > MAX_BODY_BYTES$4) throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
4014
4013
|
chunks.push(buffer);
|
|
4015
4014
|
}
|
|
4016
|
-
const value = record$
|
|
4015
|
+
const value = record$7(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4017
4016
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
4018
4017
|
return value;
|
|
4019
4018
|
}
|
|
@@ -4036,17 +4035,17 @@ function ndjsonHeaders(res) {
|
|
|
4036
4035
|
});
|
|
4037
4036
|
res.flushHeaders?.();
|
|
4038
4037
|
}
|
|
4039
|
-
function ndjson(res, value) {
|
|
4038
|
+
function ndjson$1(res, value) {
|
|
4040
4039
|
res.write(`${JSON.stringify(value)}\n`);
|
|
4041
4040
|
}
|
|
4042
4041
|
async function streamSetup(res, service, input) {
|
|
4043
4042
|
ndjsonHeaders(res);
|
|
4044
4043
|
let ended = false;
|
|
4045
4044
|
try {
|
|
4046
|
-
ndjson(res, {
|
|
4045
|
+
ndjson$1(res, {
|
|
4047
4046
|
type: "complete",
|
|
4048
4047
|
result: await service.setup(string$2(input, "cwd"), string$2(input, "branch"), input.worktree, optionalString$1(input, "branchName"), (stage) => {
|
|
4049
|
-
if (!ended) ndjson(res, {
|
|
4048
|
+
if (!ended) ndjson$1(res, {
|
|
4050
4049
|
type: "progress",
|
|
4051
4050
|
stage
|
|
4052
4051
|
});
|
|
@@ -4054,7 +4053,7 @@ async function streamSetup(res, service, input) {
|
|
|
4054
4053
|
});
|
|
4055
4054
|
} catch (error) {
|
|
4056
4055
|
const setupError = error instanceof RepositorySetupError ? error : void 0;
|
|
4057
|
-
ndjson(res, {
|
|
4056
|
+
ndjson$1(res, {
|
|
4058
4057
|
type: "error",
|
|
4059
4058
|
code: setupError?.code ?? "repository-setup-unavailable",
|
|
4060
4059
|
message: setupError?.message ?? "Repository setup is unavailable."
|
|
@@ -4081,19 +4080,19 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4081
4080
|
}
|
|
4082
4081
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
4083
4082
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4084
|
-
const input = await readJson$
|
|
4083
|
+
const input = await readJson$4(req);
|
|
4085
4084
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
4086
4085
|
await streamSetup(res, service, input);
|
|
4087
4086
|
return;
|
|
4088
4087
|
}
|
|
4089
4088
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
4090
4089
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4091
|
-
const input = await readJson$
|
|
4090
|
+
const input = await readJson$4(req);
|
|
4092
4091
|
return json(res, 200, await service.cleanupMerged(string$2(input, "path"), string$2(input, "baseBranch")));
|
|
4093
4092
|
}
|
|
4094
4093
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
4095
4094
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4096
|
-
const input = await readJson$
|
|
4095
|
+
const input = await readJson$4(req);
|
|
4097
4096
|
await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
|
|
4098
4097
|
return json(res, 200, { ok: true });
|
|
4099
4098
|
}
|
|
@@ -4111,8 +4110,8 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
4111
4110
|
}
|
|
4112
4111
|
//#endregion
|
|
4113
4112
|
//#region src/repository-action-routes.ts
|
|
4114
|
-
const MAX_BODY_BYTES$
|
|
4115
|
-
const MAX_SESSION_ID_CHARS$
|
|
4113
|
+
const MAX_BODY_BYTES$3 = 16384;
|
|
4114
|
+
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
4116
4115
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
4117
4116
|
"commit",
|
|
4118
4117
|
"commit-push",
|
|
@@ -4121,25 +4120,25 @@ const ACTIONS = /* @__PURE__ */ new Set([
|
|
|
4121
4120
|
"merge-pr",
|
|
4122
4121
|
"update-branch"
|
|
4123
4122
|
]);
|
|
4124
|
-
function record$
|
|
4123
|
+
function record$6(value) {
|
|
4125
4124
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4126
4125
|
}
|
|
4127
|
-
async function readJson$
|
|
4126
|
+
async function readJson$3(req) {
|
|
4128
4127
|
const chunks = [];
|
|
4129
4128
|
let size = 0;
|
|
4130
4129
|
for await (const chunk of req) {
|
|
4131
4130
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4132
4131
|
size += buffer.length;
|
|
4133
|
-
if (size > MAX_BODY_BYTES$
|
|
4132
|
+
if (size > MAX_BODY_BYTES$3) throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
4134
4133
|
chunks.push(buffer);
|
|
4135
4134
|
}
|
|
4136
|
-
const value = record$
|
|
4135
|
+
const value = record$6(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4137
4136
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
4138
4137
|
return value;
|
|
4139
4138
|
}
|
|
4140
4139
|
function sessionId$1(url) {
|
|
4141
4140
|
const value = url.searchParams.get("sessionId");
|
|
4142
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
4141
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$3) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
4143
4142
|
return value;
|
|
4144
4143
|
}
|
|
4145
4144
|
function string$1(input, key) {
|
|
@@ -4188,12 +4187,12 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
4188
4187
|
}
|
|
4189
4188
|
if (url.pathname === `/plugins/dsh-claude/repository/action/message`) {
|
|
4190
4189
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4191
|
-
const input = await readJson$
|
|
4190
|
+
const input = await readJson$3(req);
|
|
4192
4191
|
return json(res, 200, { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) });
|
|
4193
4192
|
}
|
|
4194
4193
|
if (url.pathname === "/plugins/dsh-claude/repository/action") {
|
|
4195
4194
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4196
|
-
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$
|
|
4195
|
+
return json(res, 200, await service.execute(cwd, actionRequest(await readJson$3(req))));
|
|
4197
4196
|
}
|
|
4198
4197
|
return json(res, 404, { error: "not found" });
|
|
4199
4198
|
} catch (error) {
|
|
@@ -4237,14 +4236,14 @@ async function collect(handle) {
|
|
|
4237
4236
|
lossy: stdout?.lossy === true
|
|
4238
4237
|
};
|
|
4239
4238
|
}
|
|
4240
|
-
function record$
|
|
4239
|
+
function record$5(value) {
|
|
4241
4240
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4242
4241
|
}
|
|
4243
4242
|
function parseReviewComments(value) {
|
|
4244
4243
|
if (!Array.isArray(value)) return [];
|
|
4245
4244
|
const comments = [];
|
|
4246
4245
|
for (const item of value) {
|
|
4247
|
-
const input = record$
|
|
4246
|
+
const input = record$5(item);
|
|
4248
4247
|
if (input === void 0 || !Number.isSafeInteger(input.id)) continue;
|
|
4249
4248
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
4250
4249
|
const path = typeof input.path === "string" ? input.path : "";
|
|
@@ -4256,7 +4255,7 @@ function parseReviewComments(value) {
|
|
|
4256
4255
|
path,
|
|
4257
4256
|
...line === void 0 ? {} : { line },
|
|
4258
4257
|
side: input.side === "LEFT" ? "old" : "new",
|
|
4259
|
-
author: typeof record$
|
|
4258
|
+
author: typeof record$5(input.user)?.login === "string" ? String(record$5(input.user)?.login) : "unknown",
|
|
4260
4259
|
body: body.slice(0, MAX_COMMENT_CHARS),
|
|
4261
4260
|
url
|
|
4262
4261
|
});
|
|
@@ -4273,7 +4272,7 @@ function parseFailingChecks(value) {
|
|
|
4273
4272
|
if (!Array.isArray(value)) return [];
|
|
4274
4273
|
const failing = [];
|
|
4275
4274
|
for (const item of value) {
|
|
4276
|
-
const input = record$
|
|
4275
|
+
const input = record$5(item);
|
|
4277
4276
|
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
4278
4277
|
failing.push({
|
|
4279
4278
|
name: input.name,
|
|
@@ -4385,10 +4384,10 @@ var PullRequestFeedbackService = class {
|
|
|
4385
4384
|
};
|
|
4386
4385
|
//#endregion
|
|
4387
4386
|
//#region src/pr-feedback-routes.ts
|
|
4388
|
-
const MAX_SESSION_ID_CHARS$
|
|
4387
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
4389
4388
|
function sessionId(url) {
|
|
4390
4389
|
const value = url.searchParams.get("sessionId");
|
|
4391
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
4390
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$2) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
|
|
4392
4391
|
return value;
|
|
4393
4392
|
}
|
|
4394
4393
|
function pullNumber(url) {
|
|
@@ -4425,7 +4424,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
4425
4424
|
}
|
|
4426
4425
|
//#endregion
|
|
4427
4426
|
//#region src/repository-status-routes.ts
|
|
4428
|
-
const MAX_PATH_CHARS = 4096;
|
|
4427
|
+
const MAX_PATH_CHARS$1 = 4096;
|
|
4429
4428
|
/** Read-only repository status for an arbitrary directory (the overview panel
|
|
4430
4429
|
* aggregates every Claude session's checkout through this). */
|
|
4431
4430
|
function registerRepositoryStatusRoute(ctx, service) {
|
|
@@ -4436,7 +4435,7 @@ function registerRepositoryStatusRoute(ctx, service) {
|
|
|
4436
4435
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4437
4436
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
4438
4437
|
const cwd = new URL(req.url ?? "/", "http://localhost").searchParams.get("cwd");
|
|
4439
|
-
if (cwd === null || cwd.length === 0 || cwd.length > MAX_PATH_CHARS || !isAbsolute(cwd) || cwd.includes("\0")) return json(res, 400, {
|
|
4438
|
+
if (cwd === null || cwd.length === 0 || cwd.length > MAX_PATH_CHARS$1 || !isAbsolute(cwd) || cwd.includes("\0")) return json(res, 400, {
|
|
4440
4439
|
error: "invalid-request",
|
|
4441
4440
|
message: "The cwd query parameter is invalid."
|
|
4442
4441
|
});
|
|
@@ -4452,6 +4451,45 @@ function registerRepositoryStatusRoute(ctx, service) {
|
|
|
4452
4451
|
}), "dsh-claude: repository status route");
|
|
4453
4452
|
}
|
|
4454
4453
|
//#endregion
|
|
4454
|
+
//#region src/repository-file-routes.ts
|
|
4455
|
+
const MAX_PATH_CHARS = 4096;
|
|
4456
|
+
/** Read-only slice of a working-tree file so the diff panel can expand unmodified lines around hunks. */
|
|
4457
|
+
function registerRepositoryFileRoute(ctx, service) {
|
|
4458
|
+
ctx.effect(() => ctx.webServer.register({
|
|
4459
|
+
kind: "exact",
|
|
4460
|
+
path: CLAUDE_REPOSITORY_FILE_PATH,
|
|
4461
|
+
handler: async (req, res) => {
|
|
4462
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4463
|
+
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
4464
|
+
const params = new URL(req.url ?? "/", "http://localhost").searchParams;
|
|
4465
|
+
const cwd = params.get("cwd");
|
|
4466
|
+
const path = params.get("path");
|
|
4467
|
+
const from = Number(params.get("from"));
|
|
4468
|
+
const to = Number(params.get("to"));
|
|
4469
|
+
if (cwd === null || cwd.length === 0 || cwd.length > MAX_PATH_CHARS || !isAbsolute(cwd) || cwd.includes("\0")) return json(res, 400, {
|
|
4470
|
+
error: "invalid-request",
|
|
4471
|
+
message: "The cwd query parameter is invalid."
|
|
4472
|
+
});
|
|
4473
|
+
if (path === null || path.length === 0 || path.length > MAX_PATH_CHARS) return json(res, 400, {
|
|
4474
|
+
error: "invalid-request",
|
|
4475
|
+
message: "The path query parameter is invalid."
|
|
4476
|
+
});
|
|
4477
|
+
try {
|
|
4478
|
+
return json(res, 200, await service.fileLines(cwd, path, from, to));
|
|
4479
|
+
} catch (error) {
|
|
4480
|
+
if (error instanceof RepositoryFileError) return json(res, error.code === "invalid-request" ? 400 : 409, {
|
|
4481
|
+
error: error.code,
|
|
4482
|
+
message: error.message
|
|
4483
|
+
});
|
|
4484
|
+
return json(res, 500, {
|
|
4485
|
+
error: "repository-file-unavailable",
|
|
4486
|
+
message: "The file could not be read."
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
}), "dsh-claude: repository file route");
|
|
4491
|
+
}
|
|
4492
|
+
//#endregion
|
|
4455
4493
|
//#region src/jira.ts
|
|
4456
4494
|
const REQUEST_TIMEOUT_MS = 15e3;
|
|
4457
4495
|
const MAX_RESULTS = 20;
|
|
@@ -4465,7 +4503,7 @@ var JiraError = class extends Error {
|
|
|
4465
4503
|
this.code = code;
|
|
4466
4504
|
}
|
|
4467
4505
|
};
|
|
4468
|
-
function record$
|
|
4506
|
+
function record$4(value) {
|
|
4469
4507
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4470
4508
|
}
|
|
4471
4509
|
/** Jira Cloud sites are origins; Data Center may carry a context path. */
|
|
@@ -4491,15 +4529,15 @@ function buildJql(query) {
|
|
|
4491
4529
|
return `text ~ "${trimmed.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}*" ORDER BY updated DESC`;
|
|
4492
4530
|
}
|
|
4493
4531
|
function parseTickets(value, siteUrl) {
|
|
4494
|
-
const issues = record$
|
|
4532
|
+
const issues = record$4(value)?.issues;
|
|
4495
4533
|
if (!Array.isArray(issues)) return [];
|
|
4496
4534
|
const tickets = [];
|
|
4497
4535
|
for (const item of issues) {
|
|
4498
|
-
const issue = record$
|
|
4499
|
-
const fields = record$
|
|
4536
|
+
const issue = record$4(item);
|
|
4537
|
+
const fields = record$4(issue?.fields);
|
|
4500
4538
|
if (issue === void 0 || typeof issue.key !== "string" || fields === void 0) continue;
|
|
4501
|
-
const status = record$
|
|
4502
|
-
const type = record$
|
|
4539
|
+
const status = record$4(fields.status)?.name;
|
|
4540
|
+
const type = record$4(fields.issuetype)?.name;
|
|
4503
4541
|
tickets.push({
|
|
4504
4542
|
key: issue.key,
|
|
4505
4543
|
summary: typeof fields.summary === "string" ? fields.summary.slice(0, 256) : "",
|
|
@@ -4541,7 +4579,7 @@ var JiraService = class {
|
|
|
4541
4579
|
email,
|
|
4542
4580
|
apiToken
|
|
4543
4581
|
};
|
|
4544
|
-
const myself = record$
|
|
4582
|
+
const myself = record$4(await this.#json(connection, "/rest/api/3/myself"));
|
|
4545
4583
|
const displayName = typeof myself?.displayName === "string" ? myself.displayName : void 0;
|
|
4546
4584
|
const accountId = typeof myself?.accountId === "string" ? myself.accountId : void 0;
|
|
4547
4585
|
const store = {
|
|
@@ -4560,7 +4598,7 @@ var JiraService = class {
|
|
|
4560
4598
|
if (ticket === void 0) throw new JiraError("invalid-request", "The ticket key is invalid.");
|
|
4561
4599
|
let accountId = store.accountId;
|
|
4562
4600
|
if (accountId === void 0) {
|
|
4563
|
-
const myself = record$
|
|
4601
|
+
const myself = record$4(await this.#json(store, "/rest/api/3/myself"));
|
|
4564
4602
|
if (typeof myself?.accountId !== "string") throw new JiraError("jira-failed", "The Jira account id is unavailable.");
|
|
4565
4603
|
accountId = myself.accountId;
|
|
4566
4604
|
await this.#write({
|
|
@@ -4628,7 +4666,7 @@ var JiraService = class {
|
|
|
4628
4666
|
throw error;
|
|
4629
4667
|
}
|
|
4630
4668
|
if (Buffer.byteLength(text) > MAX_STORE_BYTES) return void 0;
|
|
4631
|
-
const input = record$
|
|
4669
|
+
const input = record$4(JSON.parse(text));
|
|
4632
4670
|
if (input === void 0 || typeof input.siteUrl !== "string" || typeof input.email !== "string" || typeof input.apiToken !== "string") return void 0;
|
|
4633
4671
|
return {
|
|
4634
4672
|
siteUrl: input.siteUrl,
|
|
@@ -4659,20 +4697,20 @@ var JiraService = class {
|
|
|
4659
4697
|
};
|
|
4660
4698
|
//#endregion
|
|
4661
4699
|
//#region src/jira-routes.ts
|
|
4662
|
-
const MAX_BODY_BYTES$
|
|
4663
|
-
function record$
|
|
4700
|
+
const MAX_BODY_BYTES$2 = 8192;
|
|
4701
|
+
function record$3(value) {
|
|
4664
4702
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4665
4703
|
}
|
|
4666
|
-
async function readJson$
|
|
4704
|
+
async function readJson$2(req) {
|
|
4667
4705
|
const chunks = [];
|
|
4668
4706
|
let size = 0;
|
|
4669
4707
|
for await (const chunk of req) {
|
|
4670
4708
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4671
4709
|
size += buffer.length;
|
|
4672
|
-
if (size > MAX_BODY_BYTES$
|
|
4710
|
+
if (size > MAX_BODY_BYTES$2) throw new JiraError("body-too-large", "The request body is too large.");
|
|
4673
4711
|
chunks.push(buffer);
|
|
4674
4712
|
}
|
|
4675
|
-
const value = record$
|
|
4713
|
+
const value = record$3(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
4676
4714
|
if (value === void 0) throw new JiraError("invalid-request", "The request body is invalid.");
|
|
4677
4715
|
return value;
|
|
4678
4716
|
}
|
|
@@ -4695,7 +4733,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
4695
4733
|
}
|
|
4696
4734
|
if (url.pathname === `/plugins/dsh-claude/jira/connect`) {
|
|
4697
4735
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4698
|
-
const input = await readJson$
|
|
4736
|
+
const input = await readJson$2(req);
|
|
4699
4737
|
return json(res, 200, await service.connect({
|
|
4700
4738
|
siteUrl: string(input, "siteUrl"),
|
|
4701
4739
|
email: string(input, "email"),
|
|
@@ -4709,7 +4747,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
4709
4747
|
}
|
|
4710
4748
|
if (url.pathname === `/plugins/dsh-claude/jira/assign`) {
|
|
4711
4749
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4712
|
-
const input = await readJson$
|
|
4750
|
+
const input = await readJson$2(req);
|
|
4713
4751
|
await service.assignToMe(string(input, "key"));
|
|
4714
4752
|
return json(res, 200, { assigned: true });
|
|
4715
4753
|
}
|
|
@@ -4733,6 +4771,319 @@ function registerJiraRoute(ctx, service) {
|
|
|
4733
4771
|
}), "dsh-claude: jira route");
|
|
4734
4772
|
}
|
|
4735
4773
|
//#endregion
|
|
4774
|
+
//#region src/ask.ts
|
|
4775
|
+
const MAX_SELECTION_CHARS = 8e3;
|
|
4776
|
+
const MAX_CONTEXT_CHARS = 16e3;
|
|
4777
|
+
const MAX_QUESTION_CHARS = 2e3;
|
|
4778
|
+
const ASK_TIMEOUT_MS = 18e4;
|
|
4779
|
+
const MAX_STDERR_BYTES = 65536;
|
|
4780
|
+
const MAX_TOOL_SUMMARY_CHARS = 160;
|
|
4781
|
+
const READ_ONLY_TOOLS = [
|
|
4782
|
+
"Read",
|
|
4783
|
+
"Grep",
|
|
4784
|
+
"Glob"
|
|
4785
|
+
];
|
|
4786
|
+
var AskError = class extends Error {
|
|
4787
|
+
code;
|
|
4788
|
+
constructor(code, message) {
|
|
4789
|
+
super(message);
|
|
4790
|
+
this.name = "AskError";
|
|
4791
|
+
this.code = code;
|
|
4792
|
+
}
|
|
4793
|
+
};
|
|
4794
|
+
function fence(value) {
|
|
4795
|
+
return `"""\n${value.replaceAll("\"\"\"", "\" \" \"")}\n"""`;
|
|
4796
|
+
}
|
|
4797
|
+
function askPrompt(request) {
|
|
4798
|
+
const selection = request.selection.trim().slice(0, MAX_SELECTION_CHARS);
|
|
4799
|
+
const context = (request.context ?? "").trim().slice(0, MAX_CONTEXT_CHARS);
|
|
4800
|
+
const question = request.question.trim().slice(0, MAX_QUESTION_CHARS);
|
|
4801
|
+
return [
|
|
4802
|
+
"You are answering a follow-up question about part of an earlier assistant reply in this project.",
|
|
4803
|
+
"",
|
|
4804
|
+
"Selected passage:",
|
|
4805
|
+
fence(selection),
|
|
4806
|
+
...context.length === 0 || context === selection ? [] : [
|
|
4807
|
+
"",
|
|
4808
|
+
"Surrounding reply, for context only:",
|
|
4809
|
+
fence(context)
|
|
4810
|
+
],
|
|
4811
|
+
"",
|
|
4812
|
+
`Question: ${question}`,
|
|
4813
|
+
"",
|
|
4814
|
+
"Answer the question directly and concisely, in the same language as the question. Use Markdown.",
|
|
4815
|
+
"Only the read-only tools Read, Grep, and Glob are available; use them when the answer depends on project code, otherwise answer from the passage."
|
|
4816
|
+
].join("\n");
|
|
4817
|
+
}
|
|
4818
|
+
/** CLI effort for a session thinking mode; `off` and unknown modes send nothing. */
|
|
4819
|
+
function effortFor(thinkingMode) {
|
|
4820
|
+
if (thinkingMode === void 0 || thinkingMode === "off") return void 0;
|
|
4821
|
+
if (thinkingMode === "ultracode") return "max";
|
|
4822
|
+
return [
|
|
4823
|
+
"low",
|
|
4824
|
+
"medium",
|
|
4825
|
+
"high",
|
|
4826
|
+
"max"
|
|
4827
|
+
].includes(thinkingMode) ? thinkingMode : void 0;
|
|
4828
|
+
}
|
|
4829
|
+
function askArguments(preferences) {
|
|
4830
|
+
const effort = effortFor(preferences.thinkingMode);
|
|
4831
|
+
return [
|
|
4832
|
+
"-p",
|
|
4833
|
+
"--output-format",
|
|
4834
|
+
"stream-json",
|
|
4835
|
+
"--verbose",
|
|
4836
|
+
"--include-partial-messages",
|
|
4837
|
+
"--strict-mcp-config",
|
|
4838
|
+
"--mcp-config",
|
|
4839
|
+
"{\"mcpServers\":{}}",
|
|
4840
|
+
...preferences.model === void 0 || preferences.model === "default" ? [] : ["--model", preferences.model],
|
|
4841
|
+
...effort === void 0 ? [] : ["--effort", effort],
|
|
4842
|
+
"--tools",
|
|
4843
|
+
...READ_ONLY_TOOLS,
|
|
4844
|
+
"--allowedTools",
|
|
4845
|
+
...READ_ONLY_TOOLS
|
|
4846
|
+
];
|
|
4847
|
+
}
|
|
4848
|
+
function record$2(value) {
|
|
4849
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4850
|
+
}
|
|
4851
|
+
/** One-line description of a tool call, mirroring the main window's step titles. */
|
|
4852
|
+
function toolSummary(input) {
|
|
4853
|
+
const fields = record$2(input);
|
|
4854
|
+
if (fields === void 0) return void 0;
|
|
4855
|
+
const candidate = [
|
|
4856
|
+
fields.command,
|
|
4857
|
+
fields.pattern,
|
|
4858
|
+
fields.file_path,
|
|
4859
|
+
fields.path,
|
|
4860
|
+
fields.query
|
|
4861
|
+
].find((value) => typeof value === "string" && value.length > 0);
|
|
4862
|
+
return (typeof candidate === "string" ? candidate : JSON.stringify(fields)).replaceAll(/\s+/gu, " ").trim().slice(0, MAX_TOOL_SUMMARY_CHARS);
|
|
4863
|
+
}
|
|
4864
|
+
function eventsOfStreamLine(line) {
|
|
4865
|
+
let parsed;
|
|
4866
|
+
try {
|
|
4867
|
+
parsed = record$2(JSON.parse(line));
|
|
4868
|
+
} catch {
|
|
4869
|
+
return [];
|
|
4870
|
+
}
|
|
4871
|
+
if (parsed === void 0) return [];
|
|
4872
|
+
if (parsed.type === "system" && parsed.subtype === "init") return [{
|
|
4873
|
+
type: "status",
|
|
4874
|
+
text: "ready"
|
|
4875
|
+
}];
|
|
4876
|
+
if (parsed.type === "stream_event") {
|
|
4877
|
+
const event = record$2(parsed.event);
|
|
4878
|
+
if (event?.type === "content_block_start") {
|
|
4879
|
+
const block = record$2(event.content_block);
|
|
4880
|
+
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") return [{
|
|
4881
|
+
type: "tool",
|
|
4882
|
+
id: block.id,
|
|
4883
|
+
phase: "start",
|
|
4884
|
+
name: block.name
|
|
4885
|
+
}];
|
|
4886
|
+
return [];
|
|
4887
|
+
}
|
|
4888
|
+
const delta = record$2(event?.delta);
|
|
4889
|
+
if (event?.type !== "content_block_delta" || delta === void 0) return [];
|
|
4890
|
+
if (delta.type === "text_delta" && typeof delta.text === "string") return [{
|
|
4891
|
+
type: "text",
|
|
4892
|
+
text: delta.text
|
|
4893
|
+
}];
|
|
4894
|
+
if (delta.type === "thinking_delta" && typeof delta.thinking === "string") return [{
|
|
4895
|
+
type: "thinking",
|
|
4896
|
+
text: delta.thinking
|
|
4897
|
+
}];
|
|
4898
|
+
return [];
|
|
4899
|
+
}
|
|
4900
|
+
if (parsed.type === "assistant" || parsed.type === "user") {
|
|
4901
|
+
const content = record$2(parsed.message)?.content;
|
|
4902
|
+
if (!Array.isArray(content)) return [];
|
|
4903
|
+
const events = [];
|
|
4904
|
+
for (const item of content) {
|
|
4905
|
+
const block = record$2(item);
|
|
4906
|
+
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
4907
|
+
const summary = toolSummary(block.input);
|
|
4908
|
+
events.push({
|
|
4909
|
+
type: "tool",
|
|
4910
|
+
id: block.id,
|
|
4911
|
+
phase: "input",
|
|
4912
|
+
name: block.name,
|
|
4913
|
+
...summary === void 0 ? {} : { summary }
|
|
4914
|
+
});
|
|
4915
|
+
} else if (block?.type === "tool_result" && typeof block.tool_use_id === "string") events.push({
|
|
4916
|
+
type: "tool",
|
|
4917
|
+
id: block.tool_use_id,
|
|
4918
|
+
phase: "done",
|
|
4919
|
+
...block.is_error === true ? { error: true } : {}
|
|
4920
|
+
});
|
|
4921
|
+
}
|
|
4922
|
+
return events;
|
|
4923
|
+
}
|
|
4924
|
+
if (parsed.type === "result" && typeof parsed.result === "string") return [{
|
|
4925
|
+
type: "result",
|
|
4926
|
+
text: parsed.result
|
|
4927
|
+
}];
|
|
4928
|
+
return [];
|
|
4929
|
+
}
|
|
4930
|
+
/** One-shot Claude Code query answering a question about selected reply text. */
|
|
4931
|
+
var AskService = class {
|
|
4932
|
+
#runtime;
|
|
4933
|
+
#executablePath;
|
|
4934
|
+
constructor(runtime, executablePath) {
|
|
4935
|
+
this.#runtime = runtime;
|
|
4936
|
+
this.#executablePath = executablePath;
|
|
4937
|
+
}
|
|
4938
|
+
async ask(cwd, request, preferences, onEvent, signal) {
|
|
4939
|
+
if (request.question.trim().length === 0 || request.selection.trim().length === 0) throw new AskError("invalid-request", "A selection and a question are required.");
|
|
4940
|
+
if (this.#executablePath.length === 0) throw new AskError("claude-unavailable", "Claude Code is unavailable.");
|
|
4941
|
+
const timeout = AbortSignal.timeout(ASK_TIMEOUT_MS);
|
|
4942
|
+
const handle = this.#runtime.spawn({
|
|
4943
|
+
argv: [this.#executablePath, ...askArguments(preferences)],
|
|
4944
|
+
cwd,
|
|
4945
|
+
stdio: {
|
|
4946
|
+
stdin: { data: askPrompt(request) },
|
|
4947
|
+
stdout: "pipe",
|
|
4948
|
+
stderr: { maxBytes: MAX_STDERR_BYTES }
|
|
4949
|
+
},
|
|
4950
|
+
graceMs: 1e3,
|
|
4951
|
+
signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout]),
|
|
4952
|
+
env: {}
|
|
4953
|
+
});
|
|
4954
|
+
const stdout = handle.stdout;
|
|
4955
|
+
if (stdout === void 0) throw new AskError("ask-failed", "Claude output is unavailable.");
|
|
4956
|
+
const decoder = new StringDecoder("utf8");
|
|
4957
|
+
let buffer = "";
|
|
4958
|
+
let emitted = false;
|
|
4959
|
+
let result;
|
|
4960
|
+
const consume = (line) => {
|
|
4961
|
+
for (const event of eventsOfStreamLine(line)) {
|
|
4962
|
+
if (event.type === "result") {
|
|
4963
|
+
result = event.text;
|
|
4964
|
+
continue;
|
|
4965
|
+
}
|
|
4966
|
+
if (event.type !== "tool" && event.text.length === 0) continue;
|
|
4967
|
+
if (event.type === "text") emitted = true;
|
|
4968
|
+
onEvent(event);
|
|
4969
|
+
}
|
|
4970
|
+
};
|
|
4971
|
+
for await (const chunk of stdout) {
|
|
4972
|
+
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
4973
|
+
const lines = buffer.split("\n");
|
|
4974
|
+
buffer = lines.pop() ?? "";
|
|
4975
|
+
for (const line of lines) if (line.trim().length > 0) consume(line);
|
|
4976
|
+
}
|
|
4977
|
+
buffer += decoder.end();
|
|
4978
|
+
if (buffer.trim().length > 0) consume(buffer);
|
|
4979
|
+
const outcome = await handle.done;
|
|
4980
|
+
if (!emitted && result !== void 0 && result.length > 0) {
|
|
4981
|
+
emitted = true;
|
|
4982
|
+
onEvent({
|
|
4983
|
+
type: "text",
|
|
4984
|
+
text: result
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
if (!emitted) {
|
|
4988
|
+
if (signal?.aborted === true) throw new AskError("aborted", "The question was cancelled.");
|
|
4989
|
+
if (timeout.aborted) throw new AskError("timeout", "Claude took too long to answer.");
|
|
4990
|
+
const stderr = handle.collected.stderr?.readFrom(0).text.trim().split(/\r?\n/u).filter((line) => line.length > 0).at(-1);
|
|
4991
|
+
throw new AskError("ask-failed", stderr !== void 0 && stderr.length > 0 ? stderr : `Claude exited with code ${String(outcome.exitCode)}.`);
|
|
4992
|
+
}
|
|
4993
|
+
}
|
|
4994
|
+
};
|
|
4995
|
+
//#endregion
|
|
4996
|
+
//#region src/ask-routes.ts
|
|
4997
|
+
const MAX_BODY_BYTES$1 = 131072;
|
|
4998
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
4999
|
+
function record$1(value) {
|
|
5000
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5001
|
+
}
|
|
5002
|
+
async function readJson$1(req) {
|
|
5003
|
+
const chunks = [];
|
|
5004
|
+
let size = 0;
|
|
5005
|
+
for await (const chunk of req) {
|
|
5006
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5007
|
+
size += buffer.length;
|
|
5008
|
+
if (size > MAX_BODY_BYTES$1) throw new AskError("body-too-large", "The request body is too large.");
|
|
5009
|
+
chunks.push(buffer);
|
|
5010
|
+
}
|
|
5011
|
+
const value = record$1(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
5012
|
+
if (value === void 0) throw new AskError("invalid-request", "The request body is invalid.");
|
|
5013
|
+
return value;
|
|
5014
|
+
}
|
|
5015
|
+
function askRequest(input) {
|
|
5016
|
+
if (typeof input.selection !== "string" || typeof input.question !== "string" || input.context !== void 0 && typeof input.context !== "string") throw new AskError("invalid-request", "The selection and question fields are required.");
|
|
5017
|
+
return {
|
|
5018
|
+
selection: input.selection,
|
|
5019
|
+
question: input.question,
|
|
5020
|
+
...typeof input.context === "string" ? { context: input.context } : {}
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
function ndjson(res, value) {
|
|
5024
|
+
res.write(`${JSON.stringify(value)}\n`);
|
|
5025
|
+
}
|
|
5026
|
+
function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
5027
|
+
ctx.effect(() => ctx.webServer.register({
|
|
5028
|
+
kind: "exact",
|
|
5029
|
+
path: CLAUDE_ASK_PATH,
|
|
5030
|
+
handler: async (req, res) => {
|
|
5031
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
5032
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5033
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
5034
|
+
let cwd;
|
|
5035
|
+
let request;
|
|
5036
|
+
let sessionId;
|
|
5037
|
+
try {
|
|
5038
|
+
const value = url.searchParams.get("sessionId");
|
|
5039
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$1) throw new AskError("invalid-session", "The session is invalid.");
|
|
5040
|
+
sessionId = value;
|
|
5041
|
+
const resolved = cwdForSession(sessionId);
|
|
5042
|
+
if (resolved === void 0) throw new AskError("session-unavailable", "The Claude session is unavailable.");
|
|
5043
|
+
cwd = resolved;
|
|
5044
|
+
request = askRequest(await readJson$1(req));
|
|
5045
|
+
} catch (error) {
|
|
5046
|
+
if (error instanceof AskError) return json(res, 409, {
|
|
5047
|
+
error: error.code,
|
|
5048
|
+
message: error.message
|
|
5049
|
+
});
|
|
5050
|
+
if (error instanceof SyntaxError) return json(res, 400, { error: "invalid-json" });
|
|
5051
|
+
return json(res, 500, {
|
|
5052
|
+
error: "ask-unavailable",
|
|
5053
|
+
message: "The question could not be sent."
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5056
|
+
res.writeHead(200, {
|
|
5057
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
5058
|
+
"cache-control": "no-store",
|
|
5059
|
+
"x-content-type-options": "nosniff"
|
|
5060
|
+
});
|
|
5061
|
+
res.flushHeaders?.();
|
|
5062
|
+
const controller = new AbortController();
|
|
5063
|
+
req.on("close", () => {
|
|
5064
|
+
controller.abort();
|
|
5065
|
+
});
|
|
5066
|
+
try {
|
|
5067
|
+
await service.ask(cwd, request, preferencesFor(sessionId) ?? {}, (event) => {
|
|
5068
|
+
ndjson(res, event.type === "text" ? {
|
|
5069
|
+
type: "delta",
|
|
5070
|
+
text: event.text
|
|
5071
|
+
} : event);
|
|
5072
|
+
}, controller.signal);
|
|
5073
|
+
ndjson(res, { type: "done" });
|
|
5074
|
+
} catch (error) {
|
|
5075
|
+
ndjson(res, {
|
|
5076
|
+
type: "error",
|
|
5077
|
+
code: error instanceof AskError ? error.code : "ask-unavailable",
|
|
5078
|
+
message: error instanceof AskError ? error.message : "The question could not be answered."
|
|
5079
|
+
});
|
|
5080
|
+
} finally {
|
|
5081
|
+
res.end();
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
}), "dsh-claude: ask route");
|
|
5085
|
+
}
|
|
5086
|
+
//#endregion
|
|
4736
5087
|
//#region src/review-comment-routes.ts
|
|
4737
5088
|
const MAX_BODY_BYTES = 16384;
|
|
4738
5089
|
const MAX_SESSION_ID_CHARS = 1024;
|
|
@@ -4772,6 +5123,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
4772
5123
|
return json(res, 200, { comment: store.add(sessionId, {
|
|
4773
5124
|
path: input.path,
|
|
4774
5125
|
line: input.line,
|
|
5126
|
+
startLine: input.startLine,
|
|
4775
5127
|
side: input.side,
|
|
4776
5128
|
text: input.text
|
|
4777
5129
|
}) });
|
|
@@ -5591,6 +5943,7 @@ async function apply(ctx, config) {
|
|
|
5591
5943
|
});
|
|
5592
5944
|
registerRepositorySetupRoute(webCtx, repositorySetup);
|
|
5593
5945
|
registerRepositoryStatusRoute(webCtx, repositoryStatus);
|
|
5946
|
+
registerRepositoryFileRoute(webCtx, repositoryStatus);
|
|
5594
5947
|
registerJiraRoute(webCtx, new JiraService());
|
|
5595
5948
|
const repositoryActions = new RepositoryActionService(webCtx.subprocess, supervisorConfig.executablePath, (cwd) => repositoryStatus.invalidate(cwd));
|
|
5596
5949
|
const cwdForClaudeSession = (sessionId) => {
|
|
@@ -5600,6 +5953,13 @@ async function apply(ctx, config) {
|
|
|
5600
5953
|
};
|
|
5601
5954
|
registerRepositoryActionRoute(webCtx, repositoryActions, cwdForClaudeSession);
|
|
5602
5955
|
registerPullRequestFeedbackRoute(webCtx, new PullRequestFeedbackService(webCtx.subprocess), cwdForClaudeSession);
|
|
5956
|
+
registerAskRoute(webCtx, new AskService(webCtx.subprocess, supervisorConfig.executablePath), cwdForClaudeSession, (sessionId) => {
|
|
5957
|
+
const snapshot = supervisor.snapshots().find((item) => item.sessionId === sessionId);
|
|
5958
|
+
return snapshot === void 0 ? void 0 : {
|
|
5959
|
+
model: snapshot.model,
|
|
5960
|
+
...snapshot.thinkingMode === void 0 ? {} : { thinkingMode: snapshot.thinkingMode }
|
|
5961
|
+
};
|
|
5962
|
+
});
|
|
5603
5963
|
const ownsClaudeSession = (sessionId) => {
|
|
5604
5964
|
const agent = webCtx.agents.get(sessionId);
|
|
5605
5965
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|