@norman-else/dsh-claude 0.1.22 → 0.1.24
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/README.md +1 -1
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +30 -1
- package/lib/client.js +1120 -51
- package/lib/client.js.map +1 -1
- package/lib/{command-bridge-C10-lz6A.mjs → command-bridge-Br25ODdm.mjs} +2 -2
- package/lib/{command-bridge-C10-lz6A.mjs.map → command-bridge-Br25ODdm.mjs.map} +1 -1
- package/lib/{events-BdDs9ebF.mjs → events-DhrAr5gh.mjs} +3 -2
- package/lib/events-DhrAr5gh.mjs.map +1 -0
- package/lib/index.mjs +483 -100
- package/lib/index.mjs.map +1 -1
- package/lib/{preset-installer-DMANIjwu.mjs → preset-installer-B_Cb0RG6.mjs} +2 -2
- package/lib/{preset-installer-DMANIjwu.mjs.map → preset-installer-B_Cb0RG6.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -2
- package/package.json +1 -1
- package/lib/events-BdDs9ebF.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
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_UPDATE_PATH, S as CLAUDE_UPDATE_CHECK_PATH, T as TASK_TOOL_NAMES, _ as CLAUDE_PROJECTION_PATH, a as latestClaudeTasks, b as CLAUDE_REPOSITORY_SETUP_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_CODE_PRESET_ID, g as CLAUDE_GLOBAL_SETTINGS_PATH, h as CLAUDE_DOCTOR_PATH, i as latestClaudeSessionBinding, l as redactText, m as CLAUDE_CODE_PROVIDER_IDS, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_CODE_PROVIDER, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_REPOSITORY_ACTION_PATH, x as CLAUDE_REVIEW_COMMENT_PATH, y as CLAUDE_REPOSITORY_FEEDBACK_PATH } from "./events-DhrAr5gh.mjs";
|
|
2
|
+
import { n as projectClaudeCommands, t as CLAUDE_COMMANDS_SERVICE } from "./command-bridge-Br25ODdm.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-B_Cb0RG6.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
-
import { chmod, mkdir, opendir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { chmod, mkdir, opendir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
8
8
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
9
9
|
import { homedir } from "node:os";
|
|
@@ -25,7 +25,7 @@ function emptyProjection() {
|
|
|
25
25
|
activities: []
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
function record$
|
|
28
|
+
function record$6(value) {
|
|
29
29
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
30
30
|
}
|
|
31
31
|
function finiteInteger(value) {
|
|
@@ -35,7 +35,7 @@ function string$3(value, max) {
|
|
|
35
35
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
36
36
|
}
|
|
37
37
|
function binding(value) {
|
|
38
|
-
const input = record$
|
|
38
|
+
const input = record$6(value);
|
|
39
39
|
if (input === void 0 || !string$3(input.claudeSessionId, 512) || !string$3(input.sdkVersion, 128) || !string$3(input.cwd, 4096) || input.cliVersion !== void 0 && !string$3(input.cliVersion, 128)) return void 0;
|
|
40
40
|
return {
|
|
41
41
|
claudeSessionId: input.claudeSessionId,
|
|
@@ -65,22 +65,22 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
65
65
|
"failed"
|
|
66
66
|
]);
|
|
67
67
|
function activity(value) {
|
|
68
|
-
const input = record$
|
|
68
|
+
const input = record$6(value);
|
|
69
69
|
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
70
|
return normalizeActivity(input);
|
|
71
71
|
}
|
|
72
72
|
function contextUsage(value) {
|
|
73
|
-
const input = record$
|
|
73
|
+
const input = record$6(value);
|
|
74
74
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
75
75
|
return normalizeContextUsage(input);
|
|
76
76
|
}
|
|
77
77
|
function tasks(value) {
|
|
78
|
-
const input = record$
|
|
78
|
+
const input = record$6(value);
|
|
79
79
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
80
80
|
return normalizeTasksEvent(input.tasks);
|
|
81
81
|
}
|
|
82
82
|
function parseClaudeSidecar(value) {
|
|
83
|
-
const input = record$
|
|
83
|
+
const input = record$6(value);
|
|
84
84
|
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
85
|
const activities = input.activities.map(activity);
|
|
86
86
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
@@ -653,7 +653,7 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
653
653
|
}
|
|
654
654
|
//#endregion
|
|
655
655
|
//#region src/sdk-messages.ts
|
|
656
|
-
function record$
|
|
656
|
+
function record$5(value) {
|
|
657
657
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
658
658
|
}
|
|
659
659
|
function string$2(value) {
|
|
@@ -668,7 +668,7 @@ function taskUsageOf(usage) {
|
|
|
668
668
|
return Object.keys(normalized).length === 0 ? void 0 : normalized;
|
|
669
669
|
}
|
|
670
670
|
function resultUsage(message) {
|
|
671
|
-
const usage = record$
|
|
671
|
+
const usage = record$5(message.usage);
|
|
672
672
|
const normalized = {};
|
|
673
673
|
if (usage !== void 0) {
|
|
674
674
|
if (typeof usage.input_tokens === "number") normalized.inputTokens = usage.input_tokens;
|
|
@@ -680,7 +680,7 @@ function resultUsage(message) {
|
|
|
680
680
|
return normalized;
|
|
681
681
|
}
|
|
682
682
|
function normalizeAssistant(message) {
|
|
683
|
-
const content = record$
|
|
683
|
+
const content = record$5(message.message)?.content;
|
|
684
684
|
if (!Array.isArray(content)) return [{
|
|
685
685
|
kind: "protocol-error",
|
|
686
686
|
title: "Malformed Claude assistant message",
|
|
@@ -689,7 +689,7 @@ function normalizeAssistant(message) {
|
|
|
689
689
|
const parentToolUseId = string$2(message.parent_tool_use_id);
|
|
690
690
|
const normalized = [];
|
|
691
691
|
for (const item of content) {
|
|
692
|
-
const block = record$
|
|
692
|
+
const block = record$5(item);
|
|
693
693
|
if (block === void 0) continue;
|
|
694
694
|
if (block.type === "text") {
|
|
695
695
|
const text = string$2(block.text);
|
|
@@ -722,7 +722,7 @@ function normalizeAssistant(message) {
|
|
|
722
722
|
}
|
|
723
723
|
function normalizeUser(message) {
|
|
724
724
|
if (message.isReplay === true) return [];
|
|
725
|
-
const content = record$
|
|
725
|
+
const content = record$5(message.message)?.content;
|
|
726
726
|
if (typeof content === "string") return [];
|
|
727
727
|
if (!Array.isArray(content)) return [{
|
|
728
728
|
kind: "protocol-error",
|
|
@@ -732,7 +732,7 @@ function normalizeUser(message) {
|
|
|
732
732
|
const parentToolUseId = string$2(message.parent_tool_use_id);
|
|
733
733
|
const normalized = [];
|
|
734
734
|
for (const item of content) {
|
|
735
|
-
const block = record$
|
|
735
|
+
const block = record$5(item);
|
|
736
736
|
if (block?.type !== "tool_result") continue;
|
|
737
737
|
const toolUseId = string$2(block.tool_use_id);
|
|
738
738
|
if (toolUseId === void 0) continue;
|
|
@@ -813,7 +813,7 @@ function normalizeSystem(message) {
|
|
|
813
813
|
const summary = string$2(message.summary);
|
|
814
814
|
const subagentType = string$2(message.subagent_type);
|
|
815
815
|
const lastToolName = string$2(message.last_tool_name);
|
|
816
|
-
const usage = taskUsageOf(record$
|
|
816
|
+
const usage = taskUsageOf(record$5(message.usage));
|
|
817
817
|
return [{
|
|
818
818
|
kind: "subagent",
|
|
819
819
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -829,7 +829,7 @@ function normalizeSystem(message) {
|
|
|
829
829
|
}];
|
|
830
830
|
}
|
|
831
831
|
if (subtype === "task_updated") {
|
|
832
|
-
const patch = record$
|
|
832
|
+
const patch = record$5(message.patch);
|
|
833
833
|
const status = string$2(patch?.status);
|
|
834
834
|
const taskId = string$2(message.task_id);
|
|
835
835
|
const description = string$2(patch?.description);
|
|
@@ -852,7 +852,7 @@ function normalizeSystem(message) {
|
|
|
852
852
|
const taskId = string$2(message.task_id);
|
|
853
853
|
const summary = string$2(message.summary);
|
|
854
854
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
855
|
-
const usage = taskUsageOf(record$
|
|
855
|
+
const usage = taskUsageOf(record$5(message.usage));
|
|
856
856
|
return [{
|
|
857
857
|
kind: "subagent",
|
|
858
858
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -867,7 +867,7 @@ function normalizeSystem(message) {
|
|
|
867
867
|
if (subtype === "background_tasks_changed") return [{
|
|
868
868
|
kind: "background-tasks",
|
|
869
869
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
870
|
-
const entry = record$
|
|
870
|
+
const entry = record$5(item);
|
|
871
871
|
const taskId = string$2(entry?.task_id);
|
|
872
872
|
const description = string$2(entry?.description);
|
|
873
873
|
const taskType = string$2(entry?.task_type);
|
|
@@ -910,10 +910,10 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
910
910
|
function normalizeSdkMessage(message) {
|
|
911
911
|
const value = message;
|
|
912
912
|
if (value.type === "stream_event") {
|
|
913
|
-
const event = record$
|
|
913
|
+
const event = record$5(value.event);
|
|
914
914
|
const parentToolUseId = string$2(value.parent_tool_use_id);
|
|
915
915
|
if (event?.type === "content_block_delta") {
|
|
916
|
-
const delta = record$
|
|
916
|
+
const delta = record$5(event.delta);
|
|
917
917
|
if (delta?.type === "text_delta") {
|
|
918
918
|
const text = string$2(delta.text);
|
|
919
919
|
return text === void 0 ? [] : [{
|
|
@@ -948,7 +948,7 @@ function normalizeSdkMessage(message) {
|
|
|
948
948
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
949
949
|
const terminalReason = string$2(value.terminal_reason);
|
|
950
950
|
const userMessageUuid = string$2(value.user_message_uuid);
|
|
951
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
951
|
+
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$5(item)).filter((item) => item !== void 0).map((item) => {
|
|
952
952
|
const toolName = string$2(item.tool_name);
|
|
953
953
|
const toolUseId = string$2(item.tool_use_id);
|
|
954
954
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
@@ -974,7 +974,7 @@ function normalizeSdkMessage(message) {
|
|
|
974
974
|
detail: value.error ?? value.output
|
|
975
975
|
}];
|
|
976
976
|
if (value.type === "rate_limit_event") {
|
|
977
|
-
const status = string$2(record$
|
|
977
|
+
const status = string$2(record$5(value.rate_limit_info)?.status);
|
|
978
978
|
return [{
|
|
979
979
|
kind: "status",
|
|
980
980
|
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
@@ -2510,7 +2510,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
2510
2510
|
}
|
|
2511
2511
|
//#endregion
|
|
2512
2512
|
//#region src/projection-routes.ts
|
|
2513
|
-
const MAX_SESSION_ID_CHARS$
|
|
2513
|
+
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
2514
2514
|
/** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
|
|
2515
2515
|
* the transcript hot path so git/gh latency never delays visible text. */
|
|
2516
2516
|
const META_REFRESH_MS = 5e3;
|
|
@@ -2524,7 +2524,7 @@ function targetFromUrl(rawUrl) {
|
|
|
2524
2524
|
if (stream) encoded = encoded.slice(0, -7);
|
|
2525
2525
|
if (encoded.length === 0 || encoded.includes("/")) return void 0;
|
|
2526
2526
|
const sessionId = decodeURIComponent(encoded);
|
|
2527
|
-
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$
|
|
2527
|
+
if (sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$3) return void 0;
|
|
2528
2528
|
return {
|
|
2529
2529
|
sessionId,
|
|
2530
2530
|
stream
|
|
@@ -2684,17 +2684,17 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
2684
2684
|
}
|
|
2685
2685
|
//#endregion
|
|
2686
2686
|
//#region src/repository-status.ts
|
|
2687
|
-
const MAX_OUTPUT_BYTES$
|
|
2687
|
+
const MAX_OUTPUT_BYTES$3 = 65536;
|
|
2688
2688
|
const MAX_DIFF_BYTES = 262144;
|
|
2689
2689
|
const MAX_UNTRACKED_DIFFS = 50;
|
|
2690
|
-
const GIT_TIMEOUT_MS$
|
|
2691
|
-
const GH_TIMEOUT_MS = 8e3;
|
|
2690
|
+
const GIT_TIMEOUT_MS$3 = 5e3;
|
|
2691
|
+
const GH_TIMEOUT_MS$1 = 8e3;
|
|
2692
2692
|
const CACHE_TTL_MS = 5e3;
|
|
2693
2693
|
const MAX_TEXT_CHARS = 1024;
|
|
2694
2694
|
function bounded(value) {
|
|
2695
2695
|
return value.trim().slice(0, MAX_TEXT_CHARS);
|
|
2696
2696
|
}
|
|
2697
|
-
async function collect$
|
|
2697
|
+
async function collect$3(handle) {
|
|
2698
2698
|
const outcome = await handle.done;
|
|
2699
2699
|
const stdout = handle.collected.stdout?.readFrom(0);
|
|
2700
2700
|
return {
|
|
@@ -2703,15 +2703,15 @@ async function collect$2(handle) {
|
|
|
2703
2703
|
lossy: stdout?.lossy === true
|
|
2704
2704
|
};
|
|
2705
2705
|
}
|
|
2706
|
-
async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$
|
|
2706
|
+
async function run(runtime, executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$3) {
|
|
2707
2707
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
2708
|
-
return collect$
|
|
2708
|
+
return collect$3(runtime.spawn({
|
|
2709
2709
|
argv: [executable, ...args],
|
|
2710
2710
|
cwd,
|
|
2711
2711
|
stdio: {
|
|
2712
2712
|
stdin: "ignore",
|
|
2713
2713
|
stdout: { maxBytes },
|
|
2714
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES$
|
|
2714
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$3 }
|
|
2715
2715
|
},
|
|
2716
2716
|
graceMs: 1e3,
|
|
2717
2717
|
signal,
|
|
@@ -2781,14 +2781,14 @@ function parseGitHubRemote(value) {
|
|
|
2781
2781
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
2782
2782
|
return `${match[1]}/${match[2]}`;
|
|
2783
2783
|
}
|
|
2784
|
-
function record$
|
|
2784
|
+
function record$4(value) {
|
|
2785
2785
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2786
2786
|
}
|
|
2787
2787
|
function aggregateChecks(value) {
|
|
2788
2788
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
2789
2789
|
let pending = false;
|
|
2790
2790
|
for (const item of value) {
|
|
2791
|
-
const check = record$
|
|
2791
|
+
const check = record$4(item);
|
|
2792
2792
|
if (check === void 0) continue;
|
|
2793
2793
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
2794
2794
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -2812,7 +2812,7 @@ function reviewState(value) {
|
|
|
2812
2812
|
return "none";
|
|
2813
2813
|
}
|
|
2814
2814
|
function parsePullRequest(value) {
|
|
2815
|
-
const input = record$
|
|
2815
|
+
const input = record$4(value);
|
|
2816
2816
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
2817
2817
|
let url;
|
|
2818
2818
|
try {
|
|
@@ -2833,7 +2833,7 @@ function parsePullRequest(value) {
|
|
|
2833
2833
|
review: reviewState(input.reviewDecision),
|
|
2834
2834
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
2835
2835
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
2836
|
-
...typeof record$
|
|
2836
|
+
...typeof record$4(input.author)?.login === "string" ? { author: bounded(String(record$4(input.author)?.login)) } : {},
|
|
2837
2837
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
2838
2838
|
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
2839
2839
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
@@ -2906,7 +2906,7 @@ var RepositoryStatusService = class {
|
|
|
2906
2906
|
"--show-toplevel",
|
|
2907
2907
|
"--absolute-git-dir",
|
|
2908
2908
|
"--git-common-dir"
|
|
2909
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2909
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2910
2910
|
if (paths.exitCode !== 0) return {
|
|
2911
2911
|
status: "not-repository",
|
|
2912
2912
|
cwd: safeCwd
|
|
@@ -2924,7 +2924,7 @@ var RepositoryStatusService = class {
|
|
|
2924
2924
|
"--porcelain=v2",
|
|
2925
2925
|
"--branch",
|
|
2926
2926
|
"--untracked-files=normal"
|
|
2927
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2927
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2928
2928
|
if (statusResult.exitCode !== 0) return {
|
|
2929
2929
|
status: "unavailable",
|
|
2930
2930
|
cwd: safeCwd
|
|
@@ -2934,7 +2934,7 @@ var RepositoryStatusService = class {
|
|
|
2934
2934
|
"remote",
|
|
2935
2935
|
"get-url",
|
|
2936
2936
|
"origin"
|
|
2937
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2937
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2938
2938
|
const remote = remoteResult.exitCode === 0 ? parseGitHubRemote(remoteResult.stdout) : void 0;
|
|
2939
2939
|
const pullRequest = status.branch === void 0 || remote === void 0 ? void 0 : await this.#pullRequest(cwd, remote, status.branch);
|
|
2940
2940
|
const diffBase = pullRequest?.baseBranch === void 0 ? "HEAD" : await this.#mergeBase(cwd, git, pullRequest.baseBranch) ?? "HEAD";
|
|
@@ -2944,6 +2944,7 @@ var RepositoryStatusService = class {
|
|
|
2944
2944
|
files: 0,
|
|
2945
2945
|
truncated: false
|
|
2946
2946
|
};
|
|
2947
|
+
const baseBehind = pullRequest?.state === "open" && pullRequest.baseBranch !== void 0 ? await this.#baseBehind(cwd, git, pullRequest.baseBranch) : void 0;
|
|
2947
2948
|
return {
|
|
2948
2949
|
status: "ready",
|
|
2949
2950
|
cwd: safeCwd,
|
|
@@ -2952,7 +2953,8 @@ var RepositoryStatusService = class {
|
|
|
2952
2953
|
worktree: normalizedPath(gitDir) !== normalizedPath(commonDir),
|
|
2953
2954
|
...remote === void 0 ? {} : { remote },
|
|
2954
2955
|
...pullRequest === void 0 ? {} : { pullRequest },
|
|
2955
|
-
...diff === void 0 ? {} : { diff }
|
|
2956
|
+
...diff === void 0 ? {} : { diff },
|
|
2957
|
+
...baseBehind === void 0 ? {} : { baseBehind }
|
|
2956
2958
|
};
|
|
2957
2959
|
} catch {
|
|
2958
2960
|
return {
|
|
@@ -2961,13 +2963,28 @@ var RepositoryStatusService = class {
|
|
|
2961
2963
|
};
|
|
2962
2964
|
}
|
|
2963
2965
|
}
|
|
2966
|
+
async #baseBehind(cwd, git, baseBranch) {
|
|
2967
|
+
try {
|
|
2968
|
+
const result = await run(this.#runtime, git, [
|
|
2969
|
+
"rev-list",
|
|
2970
|
+
"--count",
|
|
2971
|
+
`HEAD..refs/remotes/origin/${baseBranch}`,
|
|
2972
|
+
"--"
|
|
2973
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2974
|
+
if (result.exitCode !== 0 || result.lossy) return void 0;
|
|
2975
|
+
const count = Number(bounded(result.stdout));
|
|
2976
|
+
return Number.isSafeInteger(count) && count >= 0 ? count : void 0;
|
|
2977
|
+
} catch {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2964
2981
|
async #mergeBase(cwd, git, baseBranch) {
|
|
2965
2982
|
try {
|
|
2966
2983
|
const result = await run(this.#runtime, git, [
|
|
2967
2984
|
"merge-base",
|
|
2968
2985
|
"HEAD",
|
|
2969
2986
|
`refs/remotes/origin/${baseBranch}`
|
|
2970
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
2987
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2971
2988
|
if (result.exitCode !== 0 || result.lossy) return void 0;
|
|
2972
2989
|
const oid = bounded(result.stdout);
|
|
2973
2990
|
return /^[0-9a-f]{40}$/iu.test(oid) ? oid : void 0;
|
|
@@ -2983,7 +3000,7 @@ var RepositoryStatusService = class {
|
|
|
2983
3000
|
"--numstat",
|
|
2984
3001
|
base,
|
|
2985
3002
|
"--"
|
|
2986
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
3003
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
2987
3004
|
if (numstat.exitCode !== 0 || numstat.lossy) return void 0;
|
|
2988
3005
|
const summary = parseDiffNumstat(numstat.stdout);
|
|
2989
3006
|
const patch = await run(this.#runtime, git, [
|
|
@@ -2993,7 +3010,7 @@ var RepositoryStatusService = class {
|
|
|
2993
3010
|
"--unified=3",
|
|
2994
3011
|
base,
|
|
2995
3012
|
"--"
|
|
2996
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
3013
|
+
], cwd, GIT_TIMEOUT_MS$3, MAX_DIFF_BYTES);
|
|
2997
3014
|
if (patch.exitCode !== 0) return {
|
|
2998
3015
|
...summary,
|
|
2999
3016
|
truncated: true
|
|
@@ -3017,7 +3034,7 @@ var RepositoryStatusService = class {
|
|
|
3017
3034
|
"--others",
|
|
3018
3035
|
"--exclude-standard",
|
|
3019
3036
|
"-z"
|
|
3020
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
3037
|
+
], cwd, GIT_TIMEOUT_MS$3);
|
|
3021
3038
|
if (listed.exitCode !== 0 || listed.lossy) return {
|
|
3022
3039
|
additions: 0,
|
|
3023
3040
|
files: 0,
|
|
@@ -3040,7 +3057,7 @@ var RepositoryStatusService = class {
|
|
|
3040
3057
|
"--",
|
|
3041
3058
|
"/dev/null",
|
|
3042
3059
|
path
|
|
3043
|
-
], cwd, GIT_TIMEOUT_MS$
|
|
3060
|
+
], cwd, GIT_TIMEOUT_MS$3, MAX_DIFF_BYTES);
|
|
3044
3061
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
3045
3062
|
truncated = true;
|
|
3046
3063
|
continue;
|
|
@@ -3069,7 +3086,7 @@ var RepositoryStatusService = class {
|
|
|
3069
3086
|
repository,
|
|
3070
3087
|
"--json",
|
|
3071
3088
|
"number,title,url,state,isDraft,reviewDecision,mergeStateStatus,mergedAt,statusCheckRollup,author,createdAt,baseRefName"
|
|
3072
|
-
], cwd, GH_TIMEOUT_MS);
|
|
3089
|
+
], cwd, GH_TIMEOUT_MS$1);
|
|
3073
3090
|
if (result.exitCode !== 0) return void 0;
|
|
3074
3091
|
return parsePullRequest(JSON.parse(result.stdout));
|
|
3075
3092
|
} catch {
|
|
@@ -3079,12 +3096,13 @@ var RepositoryStatusService = class {
|
|
|
3079
3096
|
};
|
|
3080
3097
|
//#endregion
|
|
3081
3098
|
//#region src/repository-setup.ts
|
|
3082
|
-
const MAX_OUTPUT_BYTES$
|
|
3083
|
-
const GIT_TIMEOUT_MS$
|
|
3099
|
+
const MAX_OUTPUT_BYTES$2 = 131072;
|
|
3100
|
+
const GIT_TIMEOUT_MS$2 = 1e4;
|
|
3084
3101
|
const GIT_FETCH_TIMEOUT_MS = 6e4;
|
|
3085
3102
|
const MAX_PATH_CHARS = 4096;
|
|
3086
3103
|
const MAX_BRANCH_CHARS = 512;
|
|
3087
3104
|
const LEASE_SCHEMA_VERSION = 1;
|
|
3105
|
+
const CLEANUP_GRACE_MS = 12e4;
|
|
3088
3106
|
var RepositorySetupError = class extends Error {
|
|
3089
3107
|
code;
|
|
3090
3108
|
constructor(code, message) {
|
|
@@ -3093,7 +3111,7 @@ var RepositorySetupError = class extends Error {
|
|
|
3093
3111
|
this.code = code;
|
|
3094
3112
|
}
|
|
3095
3113
|
};
|
|
3096
|
-
async function collect$
|
|
3114
|
+
async function collect$2(handle) {
|
|
3097
3115
|
const outcome = await handle.done;
|
|
3098
3116
|
const stdout = handle.collected.stdout?.readFrom(0);
|
|
3099
3117
|
const stderr = handle.collected.stderr?.readFrom(0);
|
|
@@ -3127,6 +3145,14 @@ function parseWorktreeBranches(value) {
|
|
|
3127
3145
|
function slug(value, fallback) {
|
|
3128
3146
|
return value.toLocaleLowerCase("en-US").replace(/[^a-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 48) || fallback;
|
|
3129
3147
|
}
|
|
3148
|
+
/** Comparable form for path identity: resolved, forward slashes, case-folded
|
|
3149
|
+
* so Windows drive-letter or case spelling differences cannot hide a match. */
|
|
3150
|
+
function comparablePath(value) {
|
|
3151
|
+
return resolve(value).replaceAll("\\", "/").toLocaleLowerCase("en-US");
|
|
3152
|
+
}
|
|
3153
|
+
function pathExists(value) {
|
|
3154
|
+
return stat(value).then(() => true, () => false);
|
|
3155
|
+
}
|
|
3130
3156
|
function lease(value) {
|
|
3131
3157
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3132
3158
|
const input = value;
|
|
@@ -3146,6 +3172,7 @@ var RepositorySetupService = class {
|
|
|
3146
3172
|
#leasePath;
|
|
3147
3173
|
#worktreeRoot;
|
|
3148
3174
|
#branchPrefix;
|
|
3175
|
+
#cleanupGraceMs;
|
|
3149
3176
|
#gitPath;
|
|
3150
3177
|
#pending = Promise.resolve();
|
|
3151
3178
|
constructor(runtime, options = {}) {
|
|
@@ -3153,6 +3180,7 @@ var RepositorySetupService = class {
|
|
|
3153
3180
|
this.#leasePath = options.leasePath ?? dshHomePath("plugins", "dsh-claude", "worktrees.json");
|
|
3154
3181
|
this.#worktreeRoot = options.worktreeRoot ?? dshHomePath("plugins", "dsh-claude", "worktrees");
|
|
3155
3182
|
this.#branchPrefix = options.branchPrefix ?? (async () => "claude");
|
|
3183
|
+
this.#cleanupGraceMs = options.cleanupGraceMs ?? CLEANUP_GRACE_MS;
|
|
3156
3184
|
}
|
|
3157
3185
|
async listBranches(cwd) {
|
|
3158
3186
|
const git = await this.#git();
|
|
@@ -3215,14 +3243,22 @@ var RepositorySetupService = class {
|
|
|
3215
3243
|
await this.#writeLeases(leases);
|
|
3216
3244
|
});
|
|
3217
3245
|
}
|
|
3218
|
-
|
|
3246
|
+
/** Reconcile leases against the set of directories still referenced by a
|
|
3247
|
+
* workspace: an unreferenced lease's clean worktree is removed. Fresh
|
|
3248
|
+
* leases are retained for a grace period so a worktree created moments ago
|
|
3249
|
+
* cannot be swept before its workspace registration lands, and dirty
|
|
3250
|
+
* worktrees are always retained to avoid losing uncommitted work. */
|
|
3251
|
+
cleanupOrphans(activePaths) {
|
|
3252
|
+
const active = new Set(activePaths.map(comparablePath));
|
|
3219
3253
|
return this.#serialize(async () => {
|
|
3220
3254
|
const leases = await this.#readLeases();
|
|
3221
3255
|
const retained = [];
|
|
3222
3256
|
let changed = false;
|
|
3223
3257
|
const git = await this.#git();
|
|
3258
|
+
const now = Date.now();
|
|
3224
3259
|
for (const item of leases) {
|
|
3225
|
-
|
|
3260
|
+
const age = now - Date.parse(item.createdAt);
|
|
3261
|
+
if (active.has(comparablePath(item.path)) || !(age >= this.#cleanupGraceMs)) {
|
|
3226
3262
|
retained.push(item);
|
|
3227
3263
|
continue;
|
|
3228
3264
|
}
|
|
@@ -3232,7 +3268,15 @@ var RepositorySetupService = class {
|
|
|
3232
3268
|
"--porcelain=v1",
|
|
3233
3269
|
"--untracked-files=normal"
|
|
3234
3270
|
], item.path);
|
|
3235
|
-
if (status.exitCode !== 0 || status.lossy
|
|
3271
|
+
if (status.exitCode !== 0 || status.lossy) {
|
|
3272
|
+
if (await pathExists(item.path)) retained.push(item);
|
|
3273
|
+
else {
|
|
3274
|
+
await this.#run(git, ["worktree", "prune"], item.root).catch(() => void 0);
|
|
3275
|
+
changed = true;
|
|
3276
|
+
}
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
if (status.stdout.trim().length > 0) {
|
|
3236
3280
|
retained.push(item);
|
|
3237
3281
|
continue;
|
|
3238
3282
|
}
|
|
@@ -3253,7 +3297,8 @@ var RepositorySetupService = class {
|
|
|
3253
3297
|
], item.root).catch(() => void 0);
|
|
3254
3298
|
changed = true;
|
|
3255
3299
|
} catch {
|
|
3256
|
-
retained.push(item);
|
|
3300
|
+
if (await pathExists(item.root)) retained.push(item);
|
|
3301
|
+
else changed = true;
|
|
3257
3302
|
}
|
|
3258
3303
|
}
|
|
3259
3304
|
if (changed) await this.#writeLeases(retained);
|
|
@@ -3386,14 +3431,14 @@ var RepositorySetupService = class {
|
|
|
3386
3431
|
this.#gitPath ??= this.#runtime.resolveExecutable("git");
|
|
3387
3432
|
return this.#gitPath;
|
|
3388
3433
|
}
|
|
3389
|
-
async #run(executable, args, cwd, timeoutMs = GIT_TIMEOUT_MS$
|
|
3390
|
-
return collect$
|
|
3434
|
+
async #run(executable, args, cwd, timeoutMs = GIT_TIMEOUT_MS$2) {
|
|
3435
|
+
return collect$2(this.#runtime.spawn({
|
|
3391
3436
|
argv: [executable, ...args],
|
|
3392
3437
|
cwd,
|
|
3393
3438
|
stdio: {
|
|
3394
3439
|
stdin: "ignore",
|
|
3395
|
-
stdout: { maxBytes: MAX_OUTPUT_BYTES$
|
|
3396
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES$
|
|
3440
|
+
stdout: { maxBytes: MAX_OUTPUT_BYTES$2 },
|
|
3441
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$2 }
|
|
3397
3442
|
},
|
|
3398
3443
|
graceMs: 1e3,
|
|
3399
3444
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -3434,12 +3479,12 @@ var RepositorySetupService = class {
|
|
|
3434
3479
|
};
|
|
3435
3480
|
//#endregion
|
|
3436
3481
|
//#region src/repository-actions.ts
|
|
3437
|
-
const MAX_OUTPUT_BYTES = 262144;
|
|
3482
|
+
const MAX_OUTPUT_BYTES$1 = 262144;
|
|
3438
3483
|
const MAX_PATCH_CHARS = 65536;
|
|
3439
3484
|
const MAX_MESSAGE_CHARS = 512;
|
|
3440
3485
|
const MAX_PR_TEXT_CHARS = 8192;
|
|
3441
3486
|
const MAX_UNPUSHED_COMMITS = 20;
|
|
3442
|
-
const GIT_TIMEOUT_MS = 15e3;
|
|
3487
|
+
const GIT_TIMEOUT_MS$1 = 15e3;
|
|
3443
3488
|
const REMOTE_TIMEOUT_MS = 6e4;
|
|
3444
3489
|
const GENERATE_TIMEOUT_MS = 6e4;
|
|
3445
3490
|
var RepositoryActionError = class extends Error {
|
|
@@ -3452,7 +3497,7 @@ var RepositoryActionError = class extends Error {
|
|
|
3452
3497
|
if (commit !== void 0) this.commit = commit;
|
|
3453
3498
|
}
|
|
3454
3499
|
};
|
|
3455
|
-
async function collect(handle) {
|
|
3500
|
+
async function collect$1(handle) {
|
|
3456
3501
|
const outcome = await handle.done;
|
|
3457
3502
|
const stdout = handle.collected.stdout?.readFrom(0);
|
|
3458
3503
|
const stderr = handle.collected.stderr?.readFrom(0);
|
|
@@ -3603,6 +3648,53 @@ var RepositoryActionService = class {
|
|
|
3603
3648
|
pushed: true
|
|
3604
3649
|
};
|
|
3605
3650
|
}
|
|
3651
|
+
if (request.action === "update-branch") {
|
|
3652
|
+
const base = safeText(request.baseBranch ?? "", 512, "Base branch");
|
|
3653
|
+
if (before.files.length > 0) throw new RepositoryActionError("dirty-workspace", "Commit or stash workspace changes before updating the branch.");
|
|
3654
|
+
const git = await this.#git();
|
|
3655
|
+
await this.#mustRun(git, [
|
|
3656
|
+
"fetch",
|
|
3657
|
+
"origin",
|
|
3658
|
+
"--",
|
|
3659
|
+
base
|
|
3660
|
+
], before.root, REMOTE_TIMEOUT_MS, "fetch-failed", "Git could not fetch the base branch.");
|
|
3661
|
+
const merged = await this.#run(git, [
|
|
3662
|
+
"merge",
|
|
3663
|
+
"--no-edit",
|
|
3664
|
+
"--",
|
|
3665
|
+
`origin/${base}`
|
|
3666
|
+
], before.root, REMOTE_TIMEOUT_MS);
|
|
3667
|
+
if (merged.exitCode !== 0 || merged.lossy) {
|
|
3668
|
+
const conflicted = await this.#run(git, [
|
|
3669
|
+
"diff",
|
|
3670
|
+
"--name-only",
|
|
3671
|
+
"--diff-filter=U",
|
|
3672
|
+
"--"
|
|
3673
|
+
], before.root, GIT_TIMEOUT_MS$1);
|
|
3674
|
+
const conflicts = conflicted.exitCode === 0 && !conflicted.lossy ? conflicted.stdout.split(/\r?\n/u).filter((line) => line.length > 0).slice(0, 100) : [];
|
|
3675
|
+
if (conflicts.length === 0) {
|
|
3676
|
+
await this.#run(git, ["merge", "--abort"], before.root, GIT_TIMEOUT_MS$1).catch(() => void 0);
|
|
3677
|
+
throw new RepositoryActionError("merge-failed", "Git could not merge the base branch.");
|
|
3678
|
+
}
|
|
3679
|
+
this.#invalidate(before.root);
|
|
3680
|
+
return {
|
|
3681
|
+
commit: before.head,
|
|
3682
|
+
pushed: false,
|
|
3683
|
+
conflicts
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
const mergedHead = (await this.#mustRun(git, ["rev-parse", "HEAD"], before.root, GIT_TIMEOUT_MS$1, "merge-failed", "The merge commit could not be verified.")).stdout.trim();
|
|
3687
|
+
try {
|
|
3688
|
+
await this.#push(git, before.root, before.branch);
|
|
3689
|
+
} catch (error) {
|
|
3690
|
+
throw new RepositoryActionError("push-failed", error instanceof Error ? error.message : "Git push failed.", mergedHead);
|
|
3691
|
+
}
|
|
3692
|
+
this.#invalidate(before.root);
|
|
3693
|
+
return {
|
|
3694
|
+
commit: mergedHead,
|
|
3695
|
+
pushed: true
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3606
3698
|
const message = safeText(request.message, MAX_MESSAGE_CHARS, "Commit message");
|
|
3607
3699
|
if (before.files.length === 0 && request.action !== "create-pr") throw new RepositoryActionError("nothing-to-commit", "There are no changes to commit.");
|
|
3608
3700
|
const git = await this.#git();
|
|
@@ -3615,7 +3707,7 @@ var RepositoryActionService = class {
|
|
|
3615
3707
|
"add",
|
|
3616
3708
|
"--",
|
|
3617
3709
|
...paths
|
|
3618
|
-
], before.root, GIT_TIMEOUT_MS, "stage-failed", "Changes could not be staged.");
|
|
3710
|
+
], before.root, GIT_TIMEOUT_MS$1, "stage-failed", "Changes could not be staged.");
|
|
3619
3711
|
}
|
|
3620
3712
|
await this.#rejectStagedWarp(git, before.root);
|
|
3621
3713
|
const staged = await this.#run(git, [
|
|
@@ -3624,7 +3716,7 @@ var RepositoryActionService = class {
|
|
|
3624
3716
|
"--quiet",
|
|
3625
3717
|
"--exit-code",
|
|
3626
3718
|
"--"
|
|
3627
|
-
], before.root, GIT_TIMEOUT_MS);
|
|
3719
|
+
], before.root, GIT_TIMEOUT_MS$1);
|
|
3628
3720
|
if (staged.exitCode === 0) throw new RepositoryActionError("nothing-to-commit", "There are no staged changes to commit.");
|
|
3629
3721
|
if (staged.exitCode !== 1) throw new RepositoryActionError("repository-unavailable", "The staged changes could not be verified.");
|
|
3630
3722
|
await this.#mustRun(git, [
|
|
@@ -3632,8 +3724,8 @@ var RepositoryActionService = class {
|
|
|
3632
3724
|
"-m",
|
|
3633
3725
|
message,
|
|
3634
3726
|
"--"
|
|
3635
|
-
], before.root, GIT_TIMEOUT_MS, "commit-failed", "Git commit failed.");
|
|
3636
|
-
oid = (await this.#mustRun(git, ["rev-parse", "HEAD"], before.root, GIT_TIMEOUT_MS, "commit-failed", "The new commit could not be verified.")).stdout.trim();
|
|
3727
|
+
], before.root, GIT_TIMEOUT_MS$1, "commit-failed", "Git commit failed.");
|
|
3728
|
+
oid = (await this.#mustRun(git, ["rev-parse", "HEAD"], before.root, GIT_TIMEOUT_MS$1, "commit-failed", "The new commit could not be verified.")).stdout.trim();
|
|
3637
3729
|
this.#invalidate(before.root);
|
|
3638
3730
|
if (request.action === "commit") return {
|
|
3639
3731
|
commit: oid,
|
|
@@ -3684,21 +3776,21 @@ var RepositoryActionService = class {
|
|
|
3684
3776
|
"rev-parse",
|
|
3685
3777
|
"--path-format=absolute",
|
|
3686
3778
|
"--show-toplevel"
|
|
3687
|
-
], cwd, GIT_TIMEOUT_MS, "not-repository", "The session directory is not a Git repository.")).stdout.trim();
|
|
3779
|
+
], cwd, GIT_TIMEOUT_MS$1, "not-repository", "The session directory is not a Git repository.")).stdout.trim();
|
|
3688
3780
|
const [branchResult, headResult, statusResult, stagedPatch, unstagedPatch] = await Promise.all([
|
|
3689
3781
|
this.#run(git, [
|
|
3690
3782
|
"symbolic-ref",
|
|
3691
3783
|
"--quiet",
|
|
3692
3784
|
"--short",
|
|
3693
3785
|
"HEAD"
|
|
3694
|
-
], root, GIT_TIMEOUT_MS),
|
|
3695
|
-
this.#run(git, ["rev-parse", "HEAD"], root, GIT_TIMEOUT_MS),
|
|
3786
|
+
], root, GIT_TIMEOUT_MS$1),
|
|
3787
|
+
this.#run(git, ["rev-parse", "HEAD"], root, GIT_TIMEOUT_MS$1),
|
|
3696
3788
|
this.#run(git, [
|
|
3697
3789
|
"status",
|
|
3698
3790
|
"--porcelain=v1",
|
|
3699
3791
|
"-z",
|
|
3700
3792
|
"--untracked-files=all"
|
|
3701
|
-
], root, GIT_TIMEOUT_MS),
|
|
3793
|
+
], root, GIT_TIMEOUT_MS$1),
|
|
3702
3794
|
this.#run(git, [
|
|
3703
3795
|
"diff",
|
|
3704
3796
|
"--cached",
|
|
@@ -3708,7 +3800,7 @@ var RepositoryActionService = class {
|
|
|
3708
3800
|
"--",
|
|
3709
3801
|
":(exclude)WARP.md",
|
|
3710
3802
|
":(exclude)**/WARP.md"
|
|
3711
|
-
], root, GIT_TIMEOUT_MS, MAX_OUTPUT_BYTES),
|
|
3803
|
+
], root, GIT_TIMEOUT_MS$1, MAX_OUTPUT_BYTES$1),
|
|
3712
3804
|
this.#run(git, [
|
|
3713
3805
|
"diff",
|
|
3714
3806
|
"--no-ext-diff",
|
|
@@ -3717,7 +3809,7 @@ var RepositoryActionService = class {
|
|
|
3717
3809
|
"--",
|
|
3718
3810
|
":(exclude)WARP.md",
|
|
3719
3811
|
":(exclude)**/WARP.md"
|
|
3720
|
-
], root, GIT_TIMEOUT_MS, MAX_OUTPUT_BYTES)
|
|
3812
|
+
], root, GIT_TIMEOUT_MS$1, MAX_OUTPUT_BYTES$1)
|
|
3721
3813
|
]);
|
|
3722
3814
|
if (branchResult.exitCode !== 0) throw new RepositoryActionError("detached-head", "A detached HEAD cannot be committed from this panel.");
|
|
3723
3815
|
if (headResult.exitCode !== 0 || statusResult.exitCode !== 0 || statusResult.lossy) throw new RepositoryActionError("repository-unavailable", "Repository state is unavailable.");
|
|
@@ -3737,7 +3829,7 @@ var RepositoryActionService = class {
|
|
|
3737
3829
|
"--abbrev-ref",
|
|
3738
3830
|
"--symbolic-full-name",
|
|
3739
3831
|
"@{upstream}"
|
|
3740
|
-
], root, GIT_TIMEOUT_MS);
|
|
3832
|
+
], root, GIT_TIMEOUT_MS$1);
|
|
3741
3833
|
const upstream = upstreamResult.exitCode === 0 && !upstreamResult.lossy ? upstreamResult.stdout.trim() : void 0;
|
|
3742
3834
|
const logResult = await this.#run(git, [
|
|
3743
3835
|
"log",
|
|
@@ -3746,7 +3838,7 @@ var RepositoryActionService = class {
|
|
|
3746
3838
|
String(21),
|
|
3747
3839
|
upstream === void 0 ? "HEAD" : "@{upstream}..HEAD",
|
|
3748
3840
|
"--"
|
|
3749
|
-
], root, GIT_TIMEOUT_MS);
|
|
3841
|
+
], root, GIT_TIMEOUT_MS$1);
|
|
3750
3842
|
const commitLines = logResult.exitCode === 0 && !logResult.lossy ? logResult.stdout.split(/\r?\n/u).filter((line) => line.includes(" ")) : [];
|
|
3751
3843
|
const unpushedCommits = commitLines.slice(0, MAX_UNPUSHED_COMMITS).flatMap((line) => {
|
|
3752
3844
|
const tab = line.indexOf(" ");
|
|
@@ -3778,7 +3870,7 @@ var RepositoryActionService = class {
|
|
|
3778
3870
|
"--cached",
|
|
3779
3871
|
"--name-only",
|
|
3780
3872
|
"--"
|
|
3781
|
-
], cwd, GIT_TIMEOUT_MS);
|
|
3873
|
+
], cwd, GIT_TIMEOUT_MS$1);
|
|
3782
3874
|
if (staged.exitCode !== 0 || staged.lossy) throw new RepositoryActionError("repository-unavailable", "Staged files could not be verified.");
|
|
3783
3875
|
if (staged.stdout.split(/\r?\n/u).some((path) => path.length > 0 && isProtectedWarpPath(path))) throw new RepositoryActionError("protected-warp-file", "WARP.md files cannot be committed. Unstage them before continuing.");
|
|
3784
3876
|
}
|
|
@@ -3788,7 +3880,7 @@ var RepositoryActionService = class {
|
|
|
3788
3880
|
"--abbrev-ref",
|
|
3789
3881
|
"--symbolic-full-name",
|
|
3790
3882
|
"@{upstream}"
|
|
3791
|
-
], cwd, GIT_TIMEOUT_MS)).exitCode === 0 ? ["push"] : [
|
|
3883
|
+
], cwd, GIT_TIMEOUT_MS$1)).exitCode === 0 ? ["push"] : [
|
|
3792
3884
|
"push",
|
|
3793
3885
|
"--set-upstream",
|
|
3794
3886
|
"origin",
|
|
@@ -3811,14 +3903,14 @@ var RepositoryActionService = class {
|
|
|
3811
3903
|
if (result.exitCode !== 0 || result.lossy) throw new RepositoryActionError(code, message);
|
|
3812
3904
|
return result;
|
|
3813
3905
|
}
|
|
3814
|
-
#run(executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES) {
|
|
3815
|
-
return collect(this.#runtime.spawn({
|
|
3906
|
+
#run(executable, args, cwd, timeoutMs, maxBytes = MAX_OUTPUT_BYTES$1) {
|
|
3907
|
+
return collect$1(this.#runtime.spawn({
|
|
3816
3908
|
argv: [executable, ...args],
|
|
3817
3909
|
cwd,
|
|
3818
3910
|
stdio: {
|
|
3819
3911
|
stdin: "ignore",
|
|
3820
3912
|
stdout: { maxBytes },
|
|
3821
|
-
stderr: { maxBytes: MAX_OUTPUT_BYTES }
|
|
3913
|
+
stderr: { maxBytes: MAX_OUTPUT_BYTES$1 }
|
|
3822
3914
|
},
|
|
3823
3915
|
graceMs: 1e3,
|
|
3824
3916
|
signal: AbortSignal.timeout(timeoutMs),
|
|
@@ -3829,7 +3921,7 @@ var RepositoryActionService = class {
|
|
|
3829
3921
|
//#endregion
|
|
3830
3922
|
//#region src/repository-setup-routes.ts
|
|
3831
3923
|
const MAX_BODY_BYTES$2 = 16384;
|
|
3832
|
-
function record$
|
|
3924
|
+
function record$3(value) {
|
|
3833
3925
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3834
3926
|
}
|
|
3835
3927
|
async function readJson$2(req) {
|
|
@@ -3841,7 +3933,7 @@ async function readJson$2(req) {
|
|
|
3841
3933
|
if (size > MAX_BODY_BYTES$2) throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
3842
3934
|
chunks.push(buffer);
|
|
3843
3935
|
}
|
|
3844
|
-
const value = record$
|
|
3936
|
+
const value = record$3(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3845
3937
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
3846
3938
|
return value;
|
|
3847
3939
|
}
|
|
@@ -3935,15 +4027,16 @@ function registerRepositorySetupRoute(ctx, service) {
|
|
|
3935
4027
|
//#endregion
|
|
3936
4028
|
//#region src/repository-action-routes.ts
|
|
3937
4029
|
const MAX_BODY_BYTES$1 = 16384;
|
|
3938
|
-
const MAX_SESSION_ID_CHARS$
|
|
4030
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
3939
4031
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
3940
4032
|
"commit",
|
|
3941
4033
|
"commit-push",
|
|
3942
4034
|
"push",
|
|
3943
4035
|
"create-pr",
|
|
3944
|
-
"merge-pr"
|
|
4036
|
+
"merge-pr",
|
|
4037
|
+
"update-branch"
|
|
3945
4038
|
]);
|
|
3946
|
-
function record$
|
|
4039
|
+
function record$2(value) {
|
|
3947
4040
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
3948
4041
|
}
|
|
3949
4042
|
async function readJson$1(req) {
|
|
@@ -3955,13 +4048,13 @@ async function readJson$1(req) {
|
|
|
3955
4048
|
if (size > MAX_BODY_BYTES$1) throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
3956
4049
|
chunks.push(buffer);
|
|
3957
4050
|
}
|
|
3958
|
-
const value = record$
|
|
4051
|
+
const value = record$2(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
3959
4052
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
3960
4053
|
return value;
|
|
3961
4054
|
}
|
|
3962
|
-
function sessionId(url) {
|
|
4055
|
+
function sessionId$1(url) {
|
|
3963
4056
|
const value = url.searchParams.get("sessionId");
|
|
3964
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
4057
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$2) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
3965
4058
|
return value;
|
|
3966
4059
|
}
|
|
3967
4060
|
function string(input, key) {
|
|
@@ -3981,7 +4074,7 @@ function actionRequest(input) {
|
|
|
3981
4074
|
return {
|
|
3982
4075
|
action,
|
|
3983
4076
|
fingerprint: string(input, "fingerprint"),
|
|
3984
|
-
message: action === "push" || action === "merge-pr" ? optionalString(input, "message") ?? "" : string(input, "message"),
|
|
4077
|
+
message: action === "push" || action === "merge-pr" || action === "update-branch" ? optionalString(input, "message") ?? "" : string(input, "message"),
|
|
3985
4078
|
includeUnstaged: input.includeUnstaged,
|
|
3986
4079
|
...optionalString(input, "prTitle") === void 0 ? {} : { prTitle: optionalString(input, "prTitle") },
|
|
3987
4080
|
...optionalString(input, "prBody") === void 0 ? {} : { prBody: optionalString(input, "prBody") },
|
|
@@ -4002,7 +4095,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
4002
4095
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4003
4096
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
4004
4097
|
try {
|
|
4005
|
-
const cwd = cwdForSession(sessionId(url));
|
|
4098
|
+
const cwd = cwdForSession(sessionId$1(url));
|
|
4006
4099
|
if (cwd === void 0) throw new RepositoryActionError("session-unavailable", "The Claude session is unavailable.");
|
|
4007
4100
|
if (url.pathname === `/plugins/dsh-claude/repository/action/preview`) {
|
|
4008
4101
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
@@ -4034,6 +4127,218 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
4034
4127
|
}), "dsh-claude: repository action route");
|
|
4035
4128
|
}
|
|
4036
4129
|
//#endregion
|
|
4130
|
+
//#region src/pr-feedback.ts
|
|
4131
|
+
const MAX_OUTPUT_BYTES = 524288;
|
|
4132
|
+
const GIT_TIMEOUT_MS = 1e4;
|
|
4133
|
+
const GH_TIMEOUT_MS = 6e4;
|
|
4134
|
+
const MAX_COMMENTS = 100;
|
|
4135
|
+
const MAX_COMMENT_CHARS = 4096;
|
|
4136
|
+
const MAX_FAILING_CHECKS = 3;
|
|
4137
|
+
const MAX_LOG_CHARS = 8192;
|
|
4138
|
+
var PullRequestFeedbackError = class extends Error {
|
|
4139
|
+
code;
|
|
4140
|
+
constructor(code, message) {
|
|
4141
|
+
super(message);
|
|
4142
|
+
this.name = "PullRequestFeedbackError";
|
|
4143
|
+
this.code = code;
|
|
4144
|
+
}
|
|
4145
|
+
};
|
|
4146
|
+
async function collect(handle) {
|
|
4147
|
+
const outcome = await handle.done;
|
|
4148
|
+
const stdout = handle.collected.stdout?.readFrom(0);
|
|
4149
|
+
return {
|
|
4150
|
+
exitCode: outcome.exitCode,
|
|
4151
|
+
stdout: stdout?.text ?? "",
|
|
4152
|
+
lossy: stdout?.lossy === true
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
4155
|
+
function record$1(value) {
|
|
4156
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4157
|
+
}
|
|
4158
|
+
function parseReviewComments(value) {
|
|
4159
|
+
if (!Array.isArray(value)) return [];
|
|
4160
|
+
const comments = [];
|
|
4161
|
+
for (const item of value) {
|
|
4162
|
+
const input = record$1(item);
|
|
4163
|
+
if (input === void 0 || !Number.isSafeInteger(input.id)) continue;
|
|
4164
|
+
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
4165
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
4166
|
+
const url = typeof input.html_url === "string" ? input.html_url : "";
|
|
4167
|
+
if (body.length === 0 || path.length === 0) continue;
|
|
4168
|
+
const line = Number.isSafeInteger(input.line) ? Number(input.line) : Number.isSafeInteger(input.original_line) ? Number(input.original_line) : void 0;
|
|
4169
|
+
comments.push({
|
|
4170
|
+
id: Number(input.id),
|
|
4171
|
+
path,
|
|
4172
|
+
...line === void 0 ? {} : { line },
|
|
4173
|
+
side: input.side === "LEFT" ? "old" : "new",
|
|
4174
|
+
author: typeof record$1(input.user)?.login === "string" ? String(record$1(input.user)?.login) : "unknown",
|
|
4175
|
+
body: body.slice(0, MAX_COMMENT_CHARS),
|
|
4176
|
+
url
|
|
4177
|
+
});
|
|
4178
|
+
if (comments.length >= MAX_COMMENTS) break;
|
|
4179
|
+
}
|
|
4180
|
+
return comments;
|
|
4181
|
+
}
|
|
4182
|
+
/** Extract the Actions job id from a check run link, when it is one. */
|
|
4183
|
+
function actionsJobId(link) {
|
|
4184
|
+
if (link === void 0) return void 0;
|
|
4185
|
+
return /\/actions\/runs\/\d+\/jobs?\/(\d+)/u.exec(link)?.[1];
|
|
4186
|
+
}
|
|
4187
|
+
function parseFailingChecks(value) {
|
|
4188
|
+
if (!Array.isArray(value)) return [];
|
|
4189
|
+
const failing = [];
|
|
4190
|
+
for (const item of value) {
|
|
4191
|
+
const input = record$1(item);
|
|
4192
|
+
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
4193
|
+
failing.push({
|
|
4194
|
+
name: input.name,
|
|
4195
|
+
...typeof input.link === "string" && input.link.length > 0 ? { link: input.link } : {},
|
|
4196
|
+
...typeof input.description === "string" && input.description.length > 0 ? { description: input.description } : {}
|
|
4197
|
+
});
|
|
4198
|
+
}
|
|
4199
|
+
return failing;
|
|
4200
|
+
}
|
|
4201
|
+
/** Keep the informative end of a failed-job log within the size budget. */
|
|
4202
|
+
function boundedLogTail(value) {
|
|
4203
|
+
const text = value.replaceAll("\0", "").trimEnd();
|
|
4204
|
+
return text.length <= MAX_LOG_CHARS ? text : text.slice(text.length - MAX_LOG_CHARS);
|
|
4205
|
+
}
|
|
4206
|
+
var PullRequestFeedbackService = class {
|
|
4207
|
+
#runtime;
|
|
4208
|
+
#gitExecutable;
|
|
4209
|
+
#ghExecutable;
|
|
4210
|
+
constructor(runtime) {
|
|
4211
|
+
this.#runtime = runtime;
|
|
4212
|
+
}
|
|
4213
|
+
async comments(cwd, pullNumber) {
|
|
4214
|
+
const repository = await this.#repository(cwd);
|
|
4215
|
+
const gh = await this.#gh();
|
|
4216
|
+
const result = await this.#run(gh, [
|
|
4217
|
+
"api",
|
|
4218
|
+
`repos/${repository}/pulls/${pullNumber}/comments`,
|
|
4219
|
+
"--paginate"
|
|
4220
|
+
], cwd, GH_TIMEOUT_MS);
|
|
4221
|
+
if (result.exitCode !== 0) throw new PullRequestFeedbackError("comments-unavailable", "Pull request comments could not be loaded.");
|
|
4222
|
+
try {
|
|
4223
|
+
const pages = result.stdout.replaceAll("][", ",");
|
|
4224
|
+
return parseReviewComments(JSON.parse(pages));
|
|
4225
|
+
} catch {
|
|
4226
|
+
throw new PullRequestFeedbackError("comments-unavailable", "Pull request comments could not be parsed.");
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
async failingChecks(cwd, pullNumber) {
|
|
4230
|
+
const gh = await this.#gh();
|
|
4231
|
+
const result = await this.#run(gh, [
|
|
4232
|
+
"pr",
|
|
4233
|
+
"checks",
|
|
4234
|
+
String(pullNumber),
|
|
4235
|
+
"--json",
|
|
4236
|
+
"name,state,link,description,bucket"
|
|
4237
|
+
], cwd, GH_TIMEOUT_MS);
|
|
4238
|
+
let checks;
|
|
4239
|
+
try {
|
|
4240
|
+
checks = parseFailingChecks(JSON.parse(result.stdout));
|
|
4241
|
+
} catch {
|
|
4242
|
+
throw new PullRequestFeedbackError("checks-unavailable", "Pull request checks could not be loaded.");
|
|
4243
|
+
}
|
|
4244
|
+
const detailed = [];
|
|
4245
|
+
for (const check of checks) {
|
|
4246
|
+
const jobId = detailed.length < MAX_FAILING_CHECKS ? actionsJobId(check.link) : void 0;
|
|
4247
|
+
if (jobId === void 0) {
|
|
4248
|
+
detailed.push(check);
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
const log = await this.#run(gh, [
|
|
4252
|
+
"run",
|
|
4253
|
+
"view",
|
|
4254
|
+
"--job",
|
|
4255
|
+
jobId,
|
|
4256
|
+
"--log-failed"
|
|
4257
|
+
], cwd, GH_TIMEOUT_MS);
|
|
4258
|
+
detailed.push(log.exitCode === 0 && log.stdout.trim().length > 0 ? {
|
|
4259
|
+
...check,
|
|
4260
|
+
log: boundedLogTail(log.stdout)
|
|
4261
|
+
} : check);
|
|
4262
|
+
}
|
|
4263
|
+
return detailed;
|
|
4264
|
+
}
|
|
4265
|
+
async #repository(cwd) {
|
|
4266
|
+
const git = await this.#git();
|
|
4267
|
+
const remote = await this.#run(git, [
|
|
4268
|
+
"remote",
|
|
4269
|
+
"get-url",
|
|
4270
|
+
"origin"
|
|
4271
|
+
], cwd, GIT_TIMEOUT_MS);
|
|
4272
|
+
const repository = remote.exitCode === 0 ? parseGitHubRemote(remote.stdout) : void 0;
|
|
4273
|
+
if (repository === void 0) throw new PullRequestFeedbackError("no-github-remote", "The repository has no GitHub origin remote.");
|
|
4274
|
+
return repository;
|
|
4275
|
+
}
|
|
4276
|
+
#git() {
|
|
4277
|
+
this.#gitExecutable ??= this.#runtime.resolveExecutable("git");
|
|
4278
|
+
return this.#gitExecutable;
|
|
4279
|
+
}
|
|
4280
|
+
#gh() {
|
|
4281
|
+
this.#ghExecutable ??= this.#runtime.resolveExecutable("gh").catch(() => {
|
|
4282
|
+
throw new PullRequestFeedbackError("gh-unavailable", "GitHub CLI is unavailable.");
|
|
4283
|
+
});
|
|
4284
|
+
return this.#ghExecutable;
|
|
4285
|
+
}
|
|
4286
|
+
#run(executable, args, cwd, timeoutMs) {
|
|
4287
|
+
return collect(this.#runtime.spawn({
|
|
4288
|
+
argv: [executable, ...args],
|
|
4289
|
+
cwd,
|
|
4290
|
+
stdio: {
|
|
4291
|
+
stdin: "ignore",
|
|
4292
|
+
stdout: { maxBytes: MAX_OUTPUT_BYTES },
|
|
4293
|
+
stderr: { maxBytes: 65536 }
|
|
4294
|
+
},
|
|
4295
|
+
graceMs: 1e3,
|
|
4296
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
4297
|
+
env: {}
|
|
4298
|
+
}));
|
|
4299
|
+
}
|
|
4300
|
+
};
|
|
4301
|
+
//#endregion
|
|
4302
|
+
//#region src/pr-feedback-routes.ts
|
|
4303
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
4304
|
+
function sessionId(url) {
|
|
4305
|
+
const value = url.searchParams.get("sessionId");
|
|
4306
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$1) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
|
|
4307
|
+
return value;
|
|
4308
|
+
}
|
|
4309
|
+
function pullNumber(url) {
|
|
4310
|
+
const value = Number(url.searchParams.get("number"));
|
|
4311
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > 1e9) throw new PullRequestFeedbackError("invalid-request", "The pull request number is invalid.");
|
|
4312
|
+
return value;
|
|
4313
|
+
}
|
|
4314
|
+
function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
4315
|
+
ctx.effect(() => ctx.webServer.register({
|
|
4316
|
+
kind: "prefix",
|
|
4317
|
+
path: CLAUDE_REPOSITORY_FEEDBACK_PATH,
|
|
4318
|
+
handler: async (req, res) => {
|
|
4319
|
+
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4320
|
+
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
4321
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
4322
|
+
try {
|
|
4323
|
+
const cwd = cwdForSession(sessionId(url));
|
|
4324
|
+
if (cwd === void 0) throw new PullRequestFeedbackError("session-unavailable", "The Claude session is unavailable.");
|
|
4325
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/comments`) return json(res, 200, { comments: await service.comments(cwd, pullNumber(url)) });
|
|
4326
|
+
if (url.pathname === `/plugins/dsh-claude/repository/feedback/checks`) return json(res, 200, { checks: await service.failingChecks(cwd, pullNumber(url)) });
|
|
4327
|
+
return json(res, 404, { error: "not found" });
|
|
4328
|
+
} catch (error) {
|
|
4329
|
+
if (error instanceof PullRequestFeedbackError) return json(res, 409, {
|
|
4330
|
+
error: error.code,
|
|
4331
|
+
message: error.message
|
|
4332
|
+
});
|
|
4333
|
+
return json(res, 500, {
|
|
4334
|
+
error: "pr-feedback-unavailable",
|
|
4335
|
+
message: "Pull request feedback is unavailable."
|
|
4336
|
+
});
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
}), "dsh-claude: pull request feedback route");
|
|
4340
|
+
}
|
|
4341
|
+
//#endregion
|
|
4037
4342
|
//#region src/review-comment-routes.ts
|
|
4038
4343
|
const MAX_BODY_BYTES = 16384;
|
|
4039
4344
|
const MAX_SESSION_ID_CHARS = 1024;
|
|
@@ -4374,6 +4679,12 @@ const BUILTIN_OUTPUT_STYLES = [
|
|
|
4374
4679
|
const STYLE_NAME = /^[\p{L}\p{N}][\p{L}\p{N} ._()\[\]-]{0,127}$/u;
|
|
4375
4680
|
const DEFAULT_WORKTREE_BRANCH_PREFIX = "claude";
|
|
4376
4681
|
const MAX_BRANCH_PREFIX_CHARS = 128;
|
|
4682
|
+
const MAX_PROCESSES_LIMIT = 16;
|
|
4683
|
+
const MAX_IDLE_TIMEOUT_MINUTES = 1440;
|
|
4684
|
+
const DEFAULT_LIMITS = {
|
|
4685
|
+
maxProcesses: 4,
|
|
4686
|
+
idleTimeoutMs: 18e5
|
|
4687
|
+
};
|
|
4377
4688
|
function pathsFor(deps) {
|
|
4378
4689
|
const root = join(homedir(), ".claude");
|
|
4379
4690
|
return {
|
|
@@ -4479,19 +4790,45 @@ const WORKTREE_BRANCH_PREFIX = {
|
|
|
4479
4790
|
else document.worktreeBranchPrefix = value;
|
|
4480
4791
|
}
|
|
4481
4792
|
};
|
|
4482
|
-
|
|
4793
|
+
function isBoundedInteger(value, min, max) {
|
|
4794
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
4795
|
+
}
|
|
4796
|
+
function integerSetting(key, min, max, defaultFor) {
|
|
4797
|
+
return {
|
|
4798
|
+
key,
|
|
4799
|
+
kind: "text",
|
|
4800
|
+
document: "plugin",
|
|
4801
|
+
effect: "new-session",
|
|
4802
|
+
maxLength: String(max).length,
|
|
4803
|
+
read(document, defaults = DEFAULT_LIMITS) {
|
|
4804
|
+
const value = document[key];
|
|
4805
|
+
return String(isBoundedInteger(value, min, max) ? value : defaultFor(defaults));
|
|
4806
|
+
},
|
|
4807
|
+
apply(document, value) {
|
|
4808
|
+
const parsed = typeof value === "string" && /^\d{1,6}$/u.test(value.trim()) ? Number(value.trim()) : value;
|
|
4809
|
+
if (!isBoundedInteger(parsed, min, max)) throw new Error(`Invalid value for global setting ${key}`);
|
|
4810
|
+
document[key] = parsed;
|
|
4811
|
+
}
|
|
4812
|
+
};
|
|
4813
|
+
}
|
|
4814
|
+
const DESCRIPTORS = [
|
|
4815
|
+
OUTPUT_STYLE,
|
|
4816
|
+
WORKTREE_BRANCH_PREFIX,
|
|
4817
|
+
integerSetting("maxProcesses", 1, MAX_PROCESSES_LIMIT, (limits) => limits.maxProcesses),
|
|
4818
|
+
integerSetting("idleTimeoutMinutes", 1, MAX_IDLE_TIMEOUT_MINUTES, (limits) => Math.max(1, Math.round(limits.idleTimeoutMs / 6e4)))
|
|
4819
|
+
];
|
|
4483
4820
|
const DESCRIPTOR_BY_KEY = new Map(DESCRIPTORS.map((descriptor) => [descriptor.key, descriptor]));
|
|
4484
4821
|
let pendingWrite = Promise.resolve();
|
|
4485
4822
|
function documentFor(descriptor, documents) {
|
|
4486
4823
|
return documents[descriptor.document];
|
|
4487
4824
|
}
|
|
4488
|
-
async function views(documents, paths) {
|
|
4825
|
+
async function views(documents, paths, defaults) {
|
|
4489
4826
|
return { settings: await Promise.all(DESCRIPTORS.map(async (descriptor) => {
|
|
4490
4827
|
const document = documentFor(descriptor, documents);
|
|
4491
4828
|
if (descriptor.kind === "text") return {
|
|
4492
4829
|
key: descriptor.key,
|
|
4493
4830
|
kind: descriptor.kind,
|
|
4494
|
-
value: descriptor.read(document),
|
|
4831
|
+
value: descriptor.read(document, defaults),
|
|
4495
4832
|
maxLength: descriptor.maxLength,
|
|
4496
4833
|
effect: descriptor.effect
|
|
4497
4834
|
};
|
|
@@ -4520,7 +4857,22 @@ async function readDocuments(paths) {
|
|
|
4520
4857
|
}
|
|
4521
4858
|
async function readGlobalSettings(deps = {}) {
|
|
4522
4859
|
const paths = pathsFor(deps);
|
|
4523
|
-
return views(await readDocuments(paths), paths);
|
|
4860
|
+
return views(await readDocuments(paths), paths, deps.defaultLimits ?? DEFAULT_LIMITS);
|
|
4861
|
+
}
|
|
4862
|
+
/** Supervisor limits the user overrode in Settings; absent keys fall back to the plugin config. */
|
|
4863
|
+
async function readSupervisorLimitOverrides(deps = {}) {
|
|
4864
|
+
let document;
|
|
4865
|
+
try {
|
|
4866
|
+
document = await readDocument(pathsFor(deps).pluginSettingsFile);
|
|
4867
|
+
} catch {
|
|
4868
|
+
return {};
|
|
4869
|
+
}
|
|
4870
|
+
const maxProcesses = document.maxProcesses;
|
|
4871
|
+
const idleTimeoutMinutes = document.idleTimeoutMinutes;
|
|
4872
|
+
return {
|
|
4873
|
+
...isBoundedInteger(maxProcesses, 1, MAX_PROCESSES_LIMIT) ? { maxProcesses } : {},
|
|
4874
|
+
...isBoundedInteger(idleTimeoutMinutes, 1, MAX_IDLE_TIMEOUT_MINUTES) ? { idleTimeoutMs: idleTimeoutMinutes * 6e4 } : {}
|
|
4875
|
+
};
|
|
4524
4876
|
}
|
|
4525
4877
|
async function readWorktreeBranchPrefix(deps = {}) {
|
|
4526
4878
|
const paths = pathsFor(deps);
|
|
@@ -4562,7 +4914,7 @@ function updateGlobalSettings(changes, deps = {}) {
|
|
|
4562
4914
|
}
|
|
4563
4915
|
if (changedDocuments.has("claude")) await atomicWrite(paths.settingsFile, documents.claude);
|
|
4564
4916
|
if (changedDocuments.has("plugin")) await atomicWrite(paths.pluginSettingsFile, documents.plugin);
|
|
4565
|
-
return views(documents, paths);
|
|
4917
|
+
return views(documents, paths, deps.defaultLimits ?? DEFAULT_LIMITS);
|
|
4566
4918
|
});
|
|
4567
4919
|
pendingWrite = operation;
|
|
4568
4920
|
return operation;
|
|
@@ -4590,7 +4942,9 @@ function registerClaudeGlobalSettingsRoute(ctx, deps = {}) {
|
|
|
4590
4942
|
if (req.method !== "GET" && req.method !== "PATCH") return json(res, 405, { error: "method not allowed" });
|
|
4591
4943
|
if (!trustedRequest(req)) return json(res, 403, { error: "forbidden" });
|
|
4592
4944
|
try {
|
|
4593
|
-
|
|
4945
|
+
const result = req.method === "GET" ? await readGlobalSettings(deps) : await updateGlobalSettings(await requestJson(req), deps);
|
|
4946
|
+
if (req.method === "PATCH") await deps.onUpdated?.();
|
|
4947
|
+
json(res, 200, result);
|
|
4594
4948
|
} catch (error) {
|
|
4595
4949
|
json(res, 400, { error: error instanceof Error ? error.message : "Invalid global settings request" });
|
|
4596
4950
|
}
|
|
@@ -4720,12 +5074,21 @@ async function installManagedPresetCompatibility(logger, install = ensureManaged
|
|
|
4720
5074
|
}
|
|
4721
5075
|
async function apply(ctx, config) {
|
|
4722
5076
|
await installManagedPresetCompatibility(ctx.logger);
|
|
5077
|
+
const defaultLimits = {
|
|
5078
|
+
idleTimeoutMs: config.idleTimeoutMs ?? 18e5,
|
|
5079
|
+
maxProcesses: config.maxProcesses ?? 4
|
|
5080
|
+
};
|
|
4723
5081
|
const supervisorConfig = {
|
|
4724
5082
|
executablePath: "",
|
|
4725
5083
|
defaultModel: config.model ?? "default",
|
|
4726
|
-
|
|
4727
|
-
|
|
5084
|
+
...defaultLimits
|
|
5085
|
+
};
|
|
5086
|
+
const applyLimitOverrides = async () => {
|
|
5087
|
+
const overrides = await readSupervisorLimitOverrides();
|
|
5088
|
+
supervisorConfig.idleTimeoutMs = overrides.idleTimeoutMs ?? defaultLimits.idleTimeoutMs;
|
|
5089
|
+
supervisorConfig.maxProcesses = overrides.maxProcesses ?? defaultLimits.maxProcesses;
|
|
4728
5090
|
};
|
|
5091
|
+
await applyLimitOverrides();
|
|
4729
5092
|
const sidecar = new ClaudeSidecarRepository();
|
|
4730
5093
|
const repositoryStatus = new RepositoryStatusService(ctx.subprocess);
|
|
4731
5094
|
const repositorySetup = new RepositorySetupService(ctx.subprocess, { branchPrefix: () => readWorktreeBranchPrefix() });
|
|
@@ -4805,7 +5168,21 @@ async function apply(ctx, config) {
|
|
|
4805
5168
|
ctx.on("agent/disposed", async ({ agent }) => {
|
|
4806
5169
|
reviewComments.disposeSession(agent.id);
|
|
4807
5170
|
await supervisor.disposeSession(agent.id);
|
|
4808
|
-
|
|
5171
|
+
});
|
|
5172
|
+
const injectWorkspaceRegistry = ctx.inject;
|
|
5173
|
+
injectWorkspaceRegistry(["workspaceRegistry"], (sweepCtx) => {
|
|
5174
|
+
const sweep = () => {
|
|
5175
|
+
try {
|
|
5176
|
+
const paths = sweepCtx.workspaceRegistry.list().map((workspace) => workspace.path);
|
|
5177
|
+
repositorySetup.cleanupOrphans(paths).catch(() => void 0);
|
|
5178
|
+
} catch {}
|
|
5179
|
+
};
|
|
5180
|
+
sweepCtx.effect(() => {
|
|
5181
|
+
sweep();
|
|
5182
|
+
const timer = setInterval(sweep, 6e4);
|
|
5183
|
+
timer.unref?.();
|
|
5184
|
+
return () => clearInterval(timer);
|
|
5185
|
+
}, "dsh-claude: worktree reconciliation");
|
|
4809
5186
|
});
|
|
4810
5187
|
ctx.effect(() => () => reviewComments.dispose(), "dsh-claude: review comments store");
|
|
4811
5188
|
ctx.effect(() => () => supervisor.dispose(), "dsh-claude: process supervisor");
|
|
@@ -4814,13 +5191,19 @@ async function apply(ctx, config) {
|
|
|
4814
5191
|
registerClaudeDoctorRoutes(webCtx, webCtx.subprocess, supervisor, supervisorConfig, resolutionError);
|
|
4815
5192
|
const desktopActions = webCtx.get("desktopActions");
|
|
4816
5193
|
registerClaudeUpdateRoutes(webCtx, webCtx.subprocess, { ...typeof desktopActions?.requestRestart === "function" ? { requestRestart: desktopActions.requestRestart.bind(desktopActions) } : {} });
|
|
4817
|
-
registerClaudeGlobalSettingsRoute(webCtx
|
|
5194
|
+
registerClaudeGlobalSettingsRoute(webCtx, {
|
|
5195
|
+
defaultLimits,
|
|
5196
|
+
onUpdated: applyLimitOverrides
|
|
5197
|
+
});
|
|
4818
5198
|
registerRepositorySetupRoute(webCtx, repositorySetup);
|
|
4819
|
-
|
|
5199
|
+
const repositoryActions = new RepositoryActionService(webCtx.subprocess, supervisorConfig.executablePath, (cwd) => repositoryStatus.invalidate(cwd));
|
|
5200
|
+
const cwdForClaudeSession = (sessionId) => {
|
|
4820
5201
|
const agent = webCtx.agents.get(sessionId);
|
|
4821
5202
|
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
4822
5203
|
return agent.session.header.cwd;
|
|
4823
|
-
}
|
|
5204
|
+
};
|
|
5205
|
+
registerRepositoryActionRoute(webCtx, repositoryActions, cwdForClaudeSession);
|
|
5206
|
+
registerPullRequestFeedbackRoute(webCtx, new PullRequestFeedbackService(webCtx.subprocess), cwdForClaudeSession);
|
|
4824
5207
|
const ownsClaudeSession = (sessionId) => {
|
|
4825
5208
|
const agent = webCtx.agents.get(sessionId);
|
|
4826
5209
|
return agent !== void 0 && webCtx.agentPresets.composedPreset(agent.ctx) === "claude";
|