@norman-else/dsh-claude 0.1.52 → 0.1.54
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 -0
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +33 -0
- package/lib/client.js +806 -219
- package/lib/client.js.map +1 -1
- package/lib/{events-B498uofA.mjs → events-QGz0a_LH.mjs} +3 -2
- package/lib/events-QGz0a_LH.mjs.map +1 -0
- package/lib/index.d.mts +26 -0
- package/lib/index.mjs +537 -183
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-CRFcjLSC.mjs → presenters-BaZaq9rR.mjs} +2 -2
- package/lib/{presenters-CRFcjLSC.mjs.map → presenters-BaZaq9rR.mjs.map} +1 -1
- package/lib/{preset-installer-Cj637ucf.mjs → preset-installer-3CuT5Lrc.mjs} +2 -2
- package/lib/{preset-installer-Cj637ucf.mjs.map → preset-installer-3CuT5Lrc.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -2
- package/package.json +1 -1
- package/lib/events-B498uofA.mjs.map +0 -1
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-
|
|
3
|
-
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-
|
|
1
|
+
import { A as CLAUDE_REPOSITORY_FEEDBACK_PATH, B as DEFAULT_CLAUDE_RENDER_MODE, C as CLAUDE_PROJECTION_PATH, D as CLAUDE_PROSE_MODES, E as CLAUDE_PROMPT_REFINE_PATH, F as CLAUDE_REWIND_PATH, G as isClaudeRenderMode, H as TASK_TOOL_NAMES, I as CLAUDE_UPDATE_CHECK_PATH, L as CLAUDE_UPDATE_PATH, M as CLAUDE_REPOSITORY_SETUP_PATH, N as CLAUDE_REPOSITORY_STATUS_PATH, O as CLAUDE_RENDER_MODES, P as CLAUDE_REVIEW_COMMENT_PATH, R as CLAUDE_USAGE_PATH, S as CLAUDE_PLAN_FEEDBACK_PATH, T as CLAUDE_PROMPT_NAME_PATH, U as isClaudeAlertMode, W as isClaudeProseMode, _ as CLAUDE_CODE_PROVIDER_IDS, a as latestClaudeTasks, b as CLAUDE_JIRA_PATH, c as normalizeTasksEvent, d as CLAUDE_ACTIVITY_EVENT, f as CLAUDE_ALERT_MODES, g as CLAUDE_CODE_PROVIDER, h as CLAUDE_CODE_PRESET_ID, i as latestClaudeSessionBinding, j as CLAUDE_REPOSITORY_FILE_PATH, k as CLAUDE_REPOSITORY_ACTION_PATH, l as redactText, m as CLAUDE_CLIENT_DIAGNOSTICS_PATH, n as currentClaudeActivityCursor, o as normalizeActivity, p as CLAUDE_ASK_PATH, r as latestClaudeContextUsage, s as normalizeContextUsage, t as boundText, u as safeDetail, v as CLAUDE_DOCTOR_PATH, w as CLAUDE_PROMPTS_PATH, x as CLAUDE_PERMISSION_MODE_PATH, y as CLAUDE_GLOBAL_SETTINGS_PATH, z as DEFAULT_CLAUDE_PROSE_MODE } from "./events-QGz0a_LH.mjs";
|
|
2
|
+
import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-BaZaq9rR.mjs";
|
|
3
|
+
import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-3CuT5Lrc.mjs";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -14,6 +14,74 @@ import { DSH_ENV_PREFIX, SENSITIVE_ENV_PATTERN } from "@deepseek-ai/dsh-subproce
|
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { deadline } from "@deepseek-ai/dsh-timeout";
|
|
16
16
|
import { StringDecoder } from "node:string_decoder";
|
|
17
|
+
//#region src/permission-mode.ts
|
|
18
|
+
/** Every mode the selector offers, in the order it lists them: tightest
|
|
19
|
+
* first. `auto` hands each ask to Claude Code's own classifier and is a
|
|
20
|
+
* per-model capability, so the selector greys it out where the catalog says
|
|
21
|
+
* the session's model lacks it. */
|
|
22
|
+
const CLAUDE_PERMISSION_MODES = [
|
|
23
|
+
"plan",
|
|
24
|
+
"default",
|
|
25
|
+
"acceptEdits",
|
|
26
|
+
"dontAsk",
|
|
27
|
+
"auto",
|
|
28
|
+
"bypassPermissions"
|
|
29
|
+
];
|
|
30
|
+
function isClaudePermissionMode(value) {
|
|
31
|
+
return typeof value === "string" && CLAUDE_PERMISSION_MODES.includes(value);
|
|
32
|
+
}
|
|
33
|
+
/** The DSH sandbox mode a Claude mode is carried on. The Host's preset table
|
|
34
|
+
* names its presets after these modes, so the same string selects the preset. */
|
|
35
|
+
const SANDBOX_BY_CLAUDE_MODE = {
|
|
36
|
+
plan: "read-only",
|
|
37
|
+
default: "workspace-write",
|
|
38
|
+
acceptEdits: "workspace-write",
|
|
39
|
+
dontAsk: "workspace-write",
|
|
40
|
+
auto: "workspace-write",
|
|
41
|
+
bypassPermissions: "danger-full-access"
|
|
42
|
+
};
|
|
43
|
+
/** The closest Claude mode for a sandbox mode chosen without this plugin's
|
|
44
|
+
* selector: what the session runs under until the selector is used. */
|
|
45
|
+
const CLAUDE_MODE_BY_SANDBOX = {
|
|
46
|
+
"read-only": "plan",
|
|
47
|
+
"workspace-write": "acceptEdits",
|
|
48
|
+
"danger-full-access": "bypassPermissions"
|
|
49
|
+
};
|
|
50
|
+
function isSandboxMode(value) {
|
|
51
|
+
return value === "read-only" || value === "workspace-write" || value === "danger-full-access";
|
|
52
|
+
}
|
|
53
|
+
/** The session's effective sandbox mode: the newest `sandbox/mode` event,
|
|
54
|
+
* undefined when there is none, and `null` for a newest event this plugin
|
|
55
|
+
* cannot read (which fails safe rather than falling back to an older one). */
|
|
56
|
+
function sandboxModeOf(events) {
|
|
57
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
58
|
+
const event = events[index];
|
|
59
|
+
if (event?.type !== "sandbox/mode") continue;
|
|
60
|
+
const mode = event.data.mode;
|
|
61
|
+
return isSandboxMode(mode) ? mode : null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** The Claude mode a session runs its next turn under.
|
|
65
|
+
*
|
|
66
|
+
* `chosen` is what the plugin's selector recorded; it stands for as long as
|
|
67
|
+
* the session's sandbox mode is the one it needs. A session that never chose
|
|
68
|
+
* runs the sandbox's closest mode, and one with no sandbox at all, or an
|
|
69
|
+
* unreadable one, runs `plan`: the mode that can do the least. */
|
|
70
|
+
function claudePermissionMode(events, chosen) {
|
|
71
|
+
const sandbox = sandboxModeOf(events);
|
|
72
|
+
if (chosen !== void 0 && (sandbox === void 0 || sandbox === SANDBOX_BY_CLAUDE_MODE[chosen])) return chosen;
|
|
73
|
+
return sandbox === void 0 || sandbox === null ? "plan" : CLAUDE_MODE_BY_SANDBOX[sandbox];
|
|
74
|
+
}
|
|
75
|
+
/** Which access control a Claude session shows and obeys: the Host's own
|
|
76
|
+
* three-preset selector with the sandbox mapping alone (`native`), or this
|
|
77
|
+
* plugin's selector over Claude Code's modes with the default mode and the
|
|
78
|
+
* creation-time alignment (`plugin`). */
|
|
79
|
+
const CLAUDE_PERMISSION_SELECTORS = ["plugin", "native"];
|
|
80
|
+
const DEFAULT_CLAUDE_PERMISSION_SELECTOR = "plugin";
|
|
81
|
+
function isClaudePermissionSelector(value) {
|
|
82
|
+
return value === "plugin" || value === "native";
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
17
85
|
//#region src/rewind.ts
|
|
18
86
|
const EMPTY_REWIND_STATE = {
|
|
19
87
|
ranges: [],
|
|
@@ -107,7 +175,7 @@ function emptyProjection() {
|
|
|
107
175
|
activities: []
|
|
108
176
|
};
|
|
109
177
|
}
|
|
110
|
-
function record$
|
|
178
|
+
function record$15(value) {
|
|
111
179
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
112
180
|
}
|
|
113
181
|
function finiteInteger(value) {
|
|
@@ -117,7 +185,7 @@ function string$4(value, max) {
|
|
|
117
185
|
return typeof value === "string" && value.length > 0 && value.length <= max;
|
|
118
186
|
}
|
|
119
187
|
function binding(value) {
|
|
120
|
-
const input = record$
|
|
188
|
+
const input = record$15(value);
|
|
121
189
|
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;
|
|
122
190
|
return {
|
|
123
191
|
claudeSessionId: input.claudeSessionId,
|
|
@@ -148,21 +216,21 @@ const ACTIVITY_PHASES = /* @__PURE__ */ new Set([
|
|
|
148
216
|
"failed"
|
|
149
217
|
]);
|
|
150
218
|
function activity(value) {
|
|
151
|
-
const input = record$
|
|
219
|
+
const input = record$15(value);
|
|
152
220
|
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;
|
|
153
221
|
return normalizeActivity(input);
|
|
154
222
|
}
|
|
155
223
|
function contextUsage(value) {
|
|
156
|
-
const input = record$
|
|
224
|
+
const input = record$15(value);
|
|
157
225
|
if (input === void 0 || !Array.isArray(input.categories)) return void 0;
|
|
158
226
|
return normalizeContextUsage(input);
|
|
159
227
|
}
|
|
160
228
|
function rewind(value) {
|
|
161
|
-
const input = record$
|
|
229
|
+
const input = record$15(value);
|
|
162
230
|
if (input === void 0 || !Array.isArray(input.ranges) || input.ranges.length > 200 || !Array.isArray(input.anchors) || input.anchors.length > 2e3) return void 0;
|
|
163
231
|
const ranges = [];
|
|
164
232
|
for (const item of input.ranges) {
|
|
165
|
-
const range = record$
|
|
233
|
+
const range = record$15(item);
|
|
166
234
|
if (range === void 0 || !finiteInteger(range.start) || !finiteInteger(range.end) || range.end < range.start) return void 0;
|
|
167
235
|
ranges.push({
|
|
168
236
|
start: range.start,
|
|
@@ -171,7 +239,7 @@ function rewind(value) {
|
|
|
171
239
|
}
|
|
172
240
|
const anchors = [];
|
|
173
241
|
for (const item of input.anchors) {
|
|
174
|
-
const anchor = record$
|
|
242
|
+
const anchor = record$15(item);
|
|
175
243
|
if (anchor === void 0 || !finiteInteger(anchor.turn) || !string$4(anchor.uuid, 128)) return void 0;
|
|
176
244
|
anchors.push({
|
|
177
245
|
turn: anchor.turn,
|
|
@@ -182,7 +250,7 @@ function rewind(value) {
|
|
|
182
250
|
if (input.snapshots !== void 0) {
|
|
183
251
|
if (!Array.isArray(input.snapshots) || input.snapshots.length > 100) return void 0;
|
|
184
252
|
for (const item of input.snapshots) {
|
|
185
|
-
const snapshot = record$
|
|
253
|
+
const snapshot = record$15(item);
|
|
186
254
|
if (snapshot === void 0 || !finiteInteger(snapshot.turn) || !string$4(snapshot.tree, 64)) return void 0;
|
|
187
255
|
snapshots.push({
|
|
188
256
|
turn: snapshot.turn,
|
|
@@ -190,7 +258,7 @@ function rewind(value) {
|
|
|
190
258
|
});
|
|
191
259
|
}
|
|
192
260
|
}
|
|
193
|
-
const pending = record$
|
|
261
|
+
const pending = record$15(input.pending);
|
|
194
262
|
if (input.pending !== void 0 && pending === void 0) return void 0;
|
|
195
263
|
if (pending === void 0) return {
|
|
196
264
|
ranges,
|
|
@@ -212,12 +280,12 @@ function rewind(value) {
|
|
|
212
280
|
};
|
|
213
281
|
}
|
|
214
282
|
function tasks(value) {
|
|
215
|
-
const input = record$
|
|
283
|
+
const input = record$15(value);
|
|
216
284
|
if (input === void 0 || !Array.isArray(input.tasks)) return void 0;
|
|
217
285
|
return normalizeTasksEvent(input.tasks);
|
|
218
286
|
}
|
|
219
287
|
function parseClaudeSidecar(value) {
|
|
220
|
-
const input = record$
|
|
288
|
+
const input = record$15(value);
|
|
221
289
|
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");
|
|
222
290
|
const activities = input.activities.map(activity);
|
|
223
291
|
if (activities.some((item) => item === void 0)) throw new Error("dsh-claude: invalid sidecar activity");
|
|
@@ -225,6 +293,7 @@ function parseClaudeSidecar(value) {
|
|
|
225
293
|
const parsedUsage = input.contextUsage === void 0 ? void 0 : contextUsage(input.contextUsage);
|
|
226
294
|
const parsedTasks = input.tasks === void 0 ? void 0 : tasks(input.tasks);
|
|
227
295
|
const parsedRewind = input.rewind === void 0 ? void 0 : rewind(input.rewind);
|
|
296
|
+
if (input.permissionMode !== void 0 && !isClaudePermissionMode(input.permissionMode)) throw new Error("dsh-claude: invalid sidecar permission mode");
|
|
228
297
|
if (input.binding !== void 0 && parsedBinding === void 0 || input.contextUsage !== void 0 && parsedUsage === void 0 || input.tasks !== void 0 && parsedTasks === void 0 || input.rewind !== void 0 && parsedRewind === void 0) throw new Error("dsh-claude: invalid sidecar projection");
|
|
229
298
|
return {
|
|
230
299
|
schemaVersion: SIDECAR_SCHEMA_VERSION,
|
|
@@ -233,7 +302,8 @@ function parseClaudeSidecar(value) {
|
|
|
233
302
|
...parsedBinding === void 0 ? {} : { binding: parsedBinding },
|
|
234
303
|
...parsedUsage === void 0 ? {} : { contextUsage: parsedUsage },
|
|
235
304
|
...parsedTasks === void 0 ? {} : { tasks: parsedTasks },
|
|
236
|
-
...parsedRewind === void 0 ? {} : { rewind: parsedRewind }
|
|
305
|
+
...parsedRewind === void 0 ? {} : { rewind: parsedRewind },
|
|
306
|
+
...input.permissionMode === void 0 ? {} : { permissionMode: input.permissionMode }
|
|
237
307
|
};
|
|
238
308
|
}
|
|
239
309
|
function compareActivity(left, right) {
|
|
@@ -434,6 +504,14 @@ var ClaudeSidecarRepository = class {
|
|
|
434
504
|
value: normalized
|
|
435
505
|
});
|
|
436
506
|
}
|
|
507
|
+
/** Record the mode the selector picked. No delta: the projection carries
|
|
508
|
+
* it as metadata, which the carrier refreshes on its own cadence. */
|
|
509
|
+
writePermissionMode(sessionId, mode) {
|
|
510
|
+
return this.#update(sessionId, (current) => ({
|
|
511
|
+
...current,
|
|
512
|
+
permissionMode: mode
|
|
513
|
+
}), true);
|
|
514
|
+
}
|
|
437
515
|
writeTasks(sessionId, value) {
|
|
438
516
|
const normalized = normalizeTasksEvent(value);
|
|
439
517
|
return this.#update(sessionId, (current) => ({
|
|
@@ -796,7 +874,9 @@ const SILENT_POLICY = "never";
|
|
|
796
874
|
const ASKING_POLICY = "ask";
|
|
797
875
|
function permissionReason(toolName, input, options) {
|
|
798
876
|
if (planText(toolName, input) !== void 0) return PLAN_APPROVAL_PROMPT;
|
|
799
|
-
const
|
|
877
|
+
const title = options.title ?? options.description;
|
|
878
|
+
const reason = options.decisionReason?.trim();
|
|
879
|
+
const prompt = title === void 0 ? reason ?? `Claude Code wants to use ${toolName}.` : reason === void 0 || reason === title ? title : `${title}\n${reason}`;
|
|
800
880
|
const detail = safeDetail(input);
|
|
801
881
|
return boundText(detail === void 0 ? prompt : `${prompt}\nInput: ${detail}`, MAX_REASON_CHARS);
|
|
802
882
|
}
|
|
@@ -1041,7 +1121,7 @@ function createUserQuestionBridge(userQuestions, activeContext) {
|
|
|
1041
1121
|
}
|
|
1042
1122
|
//#endregion
|
|
1043
1123
|
//#region src/sdk-messages.ts
|
|
1044
|
-
function record$
|
|
1124
|
+
function record$14(value) {
|
|
1045
1125
|
return value !== null && typeof value === "object" ? value : void 0;
|
|
1046
1126
|
}
|
|
1047
1127
|
function string$3(value) {
|
|
@@ -1073,12 +1153,12 @@ function hasUsageCounts(usage) {
|
|
|
1073
1153
|
return usage.inputTokens !== void 0 || usage.outputTokens !== void 0 || usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0;
|
|
1074
1154
|
}
|
|
1075
1155
|
function resultUsage(message) {
|
|
1076
|
-
const normalized = usageOf(record$
|
|
1156
|
+
const normalized = usageOf(record$14(message.usage));
|
|
1077
1157
|
if (typeof message.total_cost_usd === "number") normalized.cumulativeCostUsd = message.total_cost_usd;
|
|
1078
1158
|
return normalized;
|
|
1079
1159
|
}
|
|
1080
1160
|
function normalizeAssistant(message) {
|
|
1081
|
-
const envelope = record$
|
|
1161
|
+
const envelope = record$14(message.message);
|
|
1082
1162
|
const content = envelope?.content;
|
|
1083
1163
|
if (!Array.isArray(content)) return [{
|
|
1084
1164
|
kind: "protocol-error",
|
|
@@ -1088,7 +1168,7 @@ function normalizeAssistant(message) {
|
|
|
1088
1168
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
1089
1169
|
const normalized = [];
|
|
1090
1170
|
for (const item of content) {
|
|
1091
|
-
const block = record$
|
|
1171
|
+
const block = record$14(item);
|
|
1092
1172
|
if (block === void 0) continue;
|
|
1093
1173
|
if (block.type === "text") {
|
|
1094
1174
|
const text = string$3(block.text);
|
|
@@ -1117,7 +1197,7 @@ function normalizeAssistant(message) {
|
|
|
1117
1197
|
});
|
|
1118
1198
|
}
|
|
1119
1199
|
}
|
|
1120
|
-
const usage = usageOf(record$
|
|
1200
|
+
const usage = usageOf(record$14(envelope?.usage));
|
|
1121
1201
|
if (hasUsageCounts(usage)) normalized.push({
|
|
1122
1202
|
kind: "request-usage",
|
|
1123
1203
|
usage,
|
|
@@ -1127,7 +1207,7 @@ function normalizeAssistant(message) {
|
|
|
1127
1207
|
}
|
|
1128
1208
|
function normalizeUser(message) {
|
|
1129
1209
|
if (message.isReplay === true) return [];
|
|
1130
|
-
const content = record$
|
|
1210
|
+
const content = record$14(message.message)?.content;
|
|
1131
1211
|
if (typeof content === "string") return [];
|
|
1132
1212
|
if (!Array.isArray(content)) return [{
|
|
1133
1213
|
kind: "protocol-error",
|
|
@@ -1137,7 +1217,7 @@ function normalizeUser(message) {
|
|
|
1137
1217
|
const parentToolUseId = string$3(message.parent_tool_use_id);
|
|
1138
1218
|
const normalized = [];
|
|
1139
1219
|
for (const item of content) {
|
|
1140
|
-
const block = record$
|
|
1220
|
+
const block = record$14(item);
|
|
1141
1221
|
if (block?.type !== "tool_result") continue;
|
|
1142
1222
|
const toolUseId = string$3(block.tool_use_id);
|
|
1143
1223
|
if (toolUseId === void 0) continue;
|
|
@@ -1218,7 +1298,7 @@ function normalizeSystem(message) {
|
|
|
1218
1298
|
const summary = string$3(message.summary);
|
|
1219
1299
|
const subagentType = string$3(message.subagent_type);
|
|
1220
1300
|
const lastToolName = string$3(message.last_tool_name);
|
|
1221
|
-
const usage = taskUsageOf(record$
|
|
1301
|
+
const usage = taskUsageOf(record$14(message.usage));
|
|
1222
1302
|
return [{
|
|
1223
1303
|
kind: "subagent",
|
|
1224
1304
|
title: summary ?? description ?? "Claude subagent update",
|
|
@@ -1234,7 +1314,7 @@ function normalizeSystem(message) {
|
|
|
1234
1314
|
}];
|
|
1235
1315
|
}
|
|
1236
1316
|
if (subtype === "task_updated") {
|
|
1237
|
-
const patch = record$
|
|
1317
|
+
const patch = record$14(message.patch);
|
|
1238
1318
|
const status = string$3(patch?.status);
|
|
1239
1319
|
const taskId = string$3(message.task_id);
|
|
1240
1320
|
const description = string$3(patch?.description);
|
|
@@ -1257,7 +1337,7 @@ function normalizeSystem(message) {
|
|
|
1257
1337
|
const taskId = string$3(message.task_id);
|
|
1258
1338
|
const summary = string$3(message.summary);
|
|
1259
1339
|
const taskStatus = failed ? "failed" : stopped ? "stopped" : "completed";
|
|
1260
|
-
const usage = taskUsageOf(record$
|
|
1340
|
+
const usage = taskUsageOf(record$14(message.usage));
|
|
1261
1341
|
return [{
|
|
1262
1342
|
kind: "subagent",
|
|
1263
1343
|
title: summary ?? taskId ?? "Claude subagent finished",
|
|
@@ -1272,7 +1352,7 @@ function normalizeSystem(message) {
|
|
|
1272
1352
|
if (subtype === "background_tasks_changed") return [{
|
|
1273
1353
|
kind: "background-tasks",
|
|
1274
1354
|
tasks: (Array.isArray(message.tasks) ? message.tasks : []).flatMap((item) => {
|
|
1275
|
-
const entry = record$
|
|
1355
|
+
const entry = record$14(item);
|
|
1276
1356
|
const taskId = string$3(entry?.task_id);
|
|
1277
1357
|
const description = string$3(entry?.description);
|
|
1278
1358
|
const taskType = string$3(entry?.task_type);
|
|
@@ -1290,7 +1370,7 @@ function normalizeSystem(message) {
|
|
|
1290
1370
|
detail: message
|
|
1291
1371
|
}];
|
|
1292
1372
|
if (subtype === "compact_boundary") {
|
|
1293
|
-
const metadata = record$
|
|
1373
|
+
const metadata = record$14(message.compact_metadata);
|
|
1294
1374
|
const trigger = metadata?.trigger === "auto" || metadata?.trigger === "manual" ? metadata.trigger : void 0;
|
|
1295
1375
|
const preTokens = finiteNumber(metadata?.pre_tokens);
|
|
1296
1376
|
const postTokens = finiteNumber(metadata?.post_tokens);
|
|
@@ -1329,10 +1409,10 @@ const RESULT_ERROR_SUBTYPES = /* @__PURE__ */ new Set([
|
|
|
1329
1409
|
function normalizeSdkMessage(message) {
|
|
1330
1410
|
const value = message;
|
|
1331
1411
|
if (value.type === "stream_event") {
|
|
1332
|
-
const event = record$
|
|
1412
|
+
const event = record$14(value.event);
|
|
1333
1413
|
const parentToolUseId = string$3(value.parent_tool_use_id);
|
|
1334
1414
|
if (event?.type === "content_block_delta") {
|
|
1335
|
-
const delta = record$
|
|
1415
|
+
const delta = record$14(event.delta);
|
|
1336
1416
|
if (delta?.type === "text_delta") {
|
|
1337
1417
|
const text = string$3(delta.text);
|
|
1338
1418
|
return text === void 0 ? [] : [{
|
|
@@ -1367,7 +1447,7 @@ function normalizeSdkMessage(message) {
|
|
|
1367
1447
|
const errors = Array.isArray(value.errors) ? value.errors.filter((item) => typeof item === "string") : void 0;
|
|
1368
1448
|
const terminalReason = string$3(value.terminal_reason);
|
|
1369
1449
|
const userMessageUuid = string$3(value.user_message_uuid);
|
|
1370
|
-
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$
|
|
1450
|
+
const permissionDenials = Array.isArray(value.permission_denials) ? value.permission_denials.map((item) => record$14(item)).filter((item) => item !== void 0).map((item) => {
|
|
1371
1451
|
const toolName = string$3(item.tool_name);
|
|
1372
1452
|
const toolUseId = string$3(item.tool_use_id);
|
|
1373
1453
|
return toolName === void 0 || toolUseId === void 0 ? void 0 : {
|
|
@@ -1393,7 +1473,7 @@ function normalizeSdkMessage(message) {
|
|
|
1393
1473
|
detail: value.error ?? value.output
|
|
1394
1474
|
}];
|
|
1395
1475
|
if (value.type === "rate_limit_event") {
|
|
1396
|
-
const status = string$3(record$
|
|
1476
|
+
const status = string$3(record$14(value.rate_limit_info)?.status);
|
|
1397
1477
|
return [{
|
|
1398
1478
|
kind: "status",
|
|
1399
1479
|
title: status !== void 0 && status !== "allowed" ? "Claude rate limit is blocking requests" : "Claude rate limit status changed",
|
|
@@ -1505,7 +1585,8 @@ function projectModel(row, id) {
|
|
|
1505
1585
|
value: row.value,
|
|
1506
1586
|
name: row.displayName,
|
|
1507
1587
|
description: row.description,
|
|
1508
|
-
...contextWindow === void 0 ? {} : { contextWindow }
|
|
1588
|
+
...contextWindow === void 0 ? {} : { contextWindow },
|
|
1589
|
+
...row.supportsAutoMode === void 0 ? {} : { supportsAutoMode: row.supportsAutoMode }
|
|
1509
1590
|
};
|
|
1510
1591
|
}
|
|
1511
1592
|
let latest$1;
|
|
@@ -1634,7 +1715,7 @@ const FIXED_WINDOWS = [
|
|
|
1634
1715
|
"seven_day_opus",
|
|
1635
1716
|
"seven_day_sonnet"
|
|
1636
1717
|
];
|
|
1637
|
-
function record$
|
|
1718
|
+
function record$13(value) {
|
|
1638
1719
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1639
1720
|
}
|
|
1640
1721
|
/** Utilization is documented as 0-100; clamp so a server glitch cannot render
|
|
@@ -1646,7 +1727,7 @@ function resetsAt(value) {
|
|
|
1646
1727
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1647
1728
|
}
|
|
1648
1729
|
function window(id, source, label) {
|
|
1649
|
-
const entry = record$
|
|
1730
|
+
const entry = record$13(source);
|
|
1650
1731
|
if (entry === void 0) return void 0;
|
|
1651
1732
|
const used = utilization(entry.utilization);
|
|
1652
1733
|
const reset = resetsAt(entry.resets_at);
|
|
@@ -1660,9 +1741,9 @@ function window(id, source, label) {
|
|
|
1660
1741
|
}
|
|
1661
1742
|
/** Project the SDK's `/usage` response onto the windows the settings card shows. */
|
|
1662
1743
|
function normalizePlanUsage(value, fetchedAt) {
|
|
1663
|
-
const response = record$
|
|
1744
|
+
const response = record$13(value);
|
|
1664
1745
|
const subscription = typeof response?.subscription_type === "string" ? response.subscription_type : void 0;
|
|
1665
|
-
const limits = record$
|
|
1746
|
+
const limits = record$13(response?.rate_limits);
|
|
1666
1747
|
if (response?.rate_limits_available !== true || limits === void 0) return {
|
|
1667
1748
|
available: false,
|
|
1668
1749
|
...subscription === void 0 ? {} : { subscription },
|
|
@@ -1670,7 +1751,7 @@ function normalizePlanUsage(value, fetchedAt) {
|
|
|
1670
1751
|
fetchedAt
|
|
1671
1752
|
};
|
|
1672
1753
|
const windows = [...FIXED_WINDOWS.map((id) => window(id, limits[id])), ...(Array.isArray(limits.model_scoped) ? limits.model_scoped : []).map((entry, index) => {
|
|
1673
|
-
const name = record$
|
|
1754
|
+
const name = record$13(entry)?.display_name;
|
|
1674
1755
|
return window(`model:${typeof name === "string" ? name : index}`, entry, typeof name === "string" ? name : void 0);
|
|
1675
1756
|
})].filter((entry) => entry !== void 0);
|
|
1676
1757
|
return {
|
|
@@ -1918,11 +1999,6 @@ async function restoreWorktreeTree(runtime, cwd, tree) {
|
|
|
1918
1999
|
const CLAUDE_INITIALIZATION_TIMEOUT_MS = 3e4;
|
|
1919
2000
|
/** Control requests must settle; a wedged one must not clog the metadata chain. */
|
|
1920
2001
|
const CLAUDE_METADATA_TIMEOUT_MS = 15e3;
|
|
1921
|
-
const CLAUDE_MODE_BY_SANDBOX = {
|
|
1922
|
-
"read-only": "plan",
|
|
1923
|
-
"workspace-write": "acceptEdits",
|
|
1924
|
-
"danger-full-access": "bypassPermissions"
|
|
1925
|
-
};
|
|
1926
2002
|
/** Appended to the Claude Code system prompt on every session.
|
|
1927
2003
|
*
|
|
1928
2004
|
* The plan panel opens on exactly one signal: an `ExitPlanMode` call, whose
|
|
@@ -1935,16 +2011,6 @@ const CLAUDE_MODE_BY_SANDBOX = {
|
|
|
1935
2011
|
* conflict in favour of the handoff, so the plan reaches the panel the moment
|
|
1936
2012
|
* it is done rather than one message later. */
|
|
1937
2013
|
const PLAN_MODE_HANDOFF_PROMPT = "When you are in plan mode and the plan is written, end the turn by calling ExitPlanMode with the plan. Do not end a plan-mode turn by asking the user for confirmation in prose: the user reads and approves the plan through ExitPlanMode, and a turn that stops short of that call shows them nothing.";
|
|
1938
|
-
/** Fold DSH's native access selector into Claude Code's closest permission mode. */
|
|
1939
|
-
function claudePermissionMode(events) {
|
|
1940
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1941
|
-
const event = events[index];
|
|
1942
|
-
if (event?.type !== "sandbox/mode") continue;
|
|
1943
|
-
const mode = event.data.mode;
|
|
1944
|
-
return typeof mode === "string" && mode in CLAUDE_MODE_BY_SANDBOX ? CLAUDE_MODE_BY_SANDBOX[mode] : "plan";
|
|
1945
|
-
}
|
|
1946
|
-
return "plan";
|
|
1947
|
-
}
|
|
1948
2014
|
var ClaudeTurnBusyError = class extends Error {
|
|
1949
2015
|
constructor(sessionId) {
|
|
1950
2016
|
super(`Claude Code session ${sessionId} already has an active or interrupting turn`);
|
|
@@ -2054,6 +2120,8 @@ var ClaudeSupervisor = class {
|
|
|
2054
2120
|
#queryFactory;
|
|
2055
2121
|
#runDetached;
|
|
2056
2122
|
#sidecar;
|
|
2123
|
+
#defaultPermissionMode;
|
|
2124
|
+
#permissionSelector;
|
|
2057
2125
|
#dynamicPresenterNames = /* @__PURE__ */ new WeakMap();
|
|
2058
2126
|
#contextWindows = /* @__PURE__ */ new Map();
|
|
2059
2127
|
#disposed = false;
|
|
@@ -2075,6 +2143,8 @@ var ClaudeSupervisor = class {
|
|
|
2075
2143
|
this.#queryFactory = dependencies.queryFactory ?? ((params) => query(params));
|
|
2076
2144
|
this.#runDetached = dependencies.runDetached ?? ((operation) => operation());
|
|
2077
2145
|
this.#sidecar = dependencies.sidecar ?? new ClaudeSidecarRepository();
|
|
2146
|
+
this.#defaultPermissionMode = dependencies.defaultPermissionMode ?? (async () => void 0);
|
|
2147
|
+
this.#permissionSelector = dependencies.permissionSelector ?? (async () => "plugin");
|
|
2078
2148
|
}
|
|
2079
2149
|
snapshots() {
|
|
2080
2150
|
return [...this.#entries.values()].map((entry) => ({
|
|
@@ -2454,8 +2524,16 @@ var ClaudeSupervisor = class {
|
|
|
2454
2524
|
throw error;
|
|
2455
2525
|
}
|
|
2456
2526
|
}
|
|
2527
|
+
/** The mode a turn runs under: the session's choice, else the configured
|
|
2528
|
+
* default, folded against the DSH sandbox; and `auto` only on a model the
|
|
2529
|
+
* catalog does not rule out, since the CLI refuses it elsewhere. */
|
|
2530
|
+
async #permissionModeFor(events, chosen, model) {
|
|
2531
|
+
const mode = claudePermissionMode(events, await this.#permissionSelector() === "native" ? void 0 : chosen ?? await this.#defaultPermissionMode());
|
|
2532
|
+
return mode === "auto" && claudeModelRow(model)?.supportsAutoMode === false ? "default" : mode;
|
|
2533
|
+
}
|
|
2457
2534
|
async #syncPermissionMode(entry) {
|
|
2458
|
-
const
|
|
2535
|
+
const projection = await this.#sidecar.read(entry.sessionId);
|
|
2536
|
+
const mode = await this.#permissionModeFor(entry.ownerAgent.session.snapshotEvents(), projection.permissionMode, entry.model);
|
|
2459
2537
|
if (mode === entry.permissionMode) return;
|
|
2460
2538
|
await this.#control(entry, entry.query.setPermissionMode(mode), "Claude Code permission mode switch");
|
|
2461
2539
|
entry.permissionMode = mode;
|
|
@@ -2555,7 +2633,7 @@ var ClaudeSupervisor = class {
|
|
|
2555
2633
|
const pendingRewind = projection.rewind?.pending;
|
|
2556
2634
|
const forkAt = pendingRewind !== void 0 && "resumeAt" in pendingRewind ? pendingRewind.resumeAt : void 0;
|
|
2557
2635
|
const startFresh = pendingRewind !== void 0 && "fresh" in pendingRewind;
|
|
2558
|
-
const permissionMode =
|
|
2636
|
+
const permissionMode = await this.#permissionModeFor(agent.session.snapshotEvents(), projection.permissionMode, model);
|
|
2559
2637
|
const entry = {
|
|
2560
2638
|
sessionId,
|
|
2561
2639
|
ownerAgent: agent,
|
|
@@ -2657,7 +2735,7 @@ var ClaudeSupervisor = class {
|
|
|
2657
2735
|
if (message.kind === "init") {
|
|
2658
2736
|
const firstInitialization = !entry.initialized;
|
|
2659
2737
|
if (entry.expectedResume !== void 0 && message.sessionId !== entry.expectedResume) throw new ClaudeProtocolError(`Claude Code resumed unexpected session ${message.sessionId}; expected ${entry.expectedResume}`);
|
|
2660
|
-
if (message.cwd !== entry.cwd) throw new ClaudeProtocolError(`Claude Code initialized in unexpected cwd ${message.cwd}; expected ${entry.cwd}`);
|
|
2738
|
+
if (entry.expectedResume === void 0 && message.cwd !== entry.cwd) throw new ClaudeProtocolError(`Claude Code initialized in unexpected cwd ${message.cwd}; expected ${entry.cwd}`);
|
|
2661
2739
|
entry.initialized = true;
|
|
2662
2740
|
entry.claudeSessionId = message.sessionId;
|
|
2663
2741
|
entry.state = entry.active === void 0 ? "idle" : "running";
|
|
@@ -4214,7 +4292,7 @@ function registerClaudeDoctorRoutes(ctx, runtime, supervisor, config, resolution
|
|
|
4214
4292
|
}
|
|
4215
4293
|
//#endregion
|
|
4216
4294
|
//#region src/projection-routes.ts
|
|
4217
|
-
const MAX_SESSION_ID_CHARS$
|
|
4295
|
+
const MAX_SESSION_ID_CHARS$7 = 1024;
|
|
4218
4296
|
/** Slow-moving metadata refresh and stream heartbeat cadence; deliberately off
|
|
4219
4297
|
* the transcript hot path so git/gh latency never delays visible text. */
|
|
4220
4298
|
const META_REFRESH_MS = 5e3;
|
|
@@ -4222,7 +4300,7 @@ const META_REFRESH_MS = 5e3;
|
|
|
4222
4300
|
* so this cannot collide with one. */
|
|
4223
4301
|
const MULTI_SEGMENT = "multi";
|
|
4224
4302
|
function validSessionId(value) {
|
|
4225
|
-
return value.length > 0 && value.length <= MAX_SESSION_ID_CHARS$
|
|
4303
|
+
return value.length > 0 && value.length <= MAX_SESSION_ID_CHARS$7;
|
|
4226
4304
|
}
|
|
4227
4305
|
function targetFromUrl(url) {
|
|
4228
4306
|
const prefix = `${CLAUDE_PROJECTION_PATH}/`;
|
|
@@ -4262,6 +4340,8 @@ function envelope(projection, meta) {
|
|
|
4262
4340
|
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
4263
4341
|
...meta.repositories === void 0 ? {} : { repositories: meta.repositories },
|
|
4264
4342
|
reviewComments: meta.reviewComments,
|
|
4343
|
+
...meta.permissionMode === void 0 ? {} : { permissionMode: meta.permissionMode },
|
|
4344
|
+
...meta.permissionSelector === void 0 ? {} : { permissionSelector: meta.permissionSelector },
|
|
4265
4345
|
...projection.rewind === void 0 ? {} : { rewind: { ranges: projection.rewind.ranges } }
|
|
4266
4346
|
};
|
|
4267
4347
|
}
|
|
@@ -4276,7 +4356,7 @@ function envelope(projection, meta) {
|
|
|
4276
4356
|
* session spent it in proportion to how many Claude sessions existed — which
|
|
4277
4357
|
* is what left the settings panel unable to get a connection at all. One
|
|
4278
4358
|
* carrier is one connection, whatever the session count. */
|
|
4279
|
-
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => [], extraRepositoriesForSession = async () => []) {
|
|
4359
|
+
function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSession = () => [], repositoryForSession = async () => void 0, reviewCommentsForSession = () => [], extraRepositoriesForSession = async () => [], permissionModeForSession = async () => void 0, permissionSelector = () => "plugin") {
|
|
4280
4360
|
const info = (message) => {
|
|
4281
4361
|
ctx.logger?.info?.(message);
|
|
4282
4362
|
};
|
|
@@ -4286,17 +4366,23 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
4286
4366
|
return {
|
|
4287
4367
|
owned,
|
|
4288
4368
|
commands: commandsForSession(sessionId),
|
|
4289
|
-
reviewComments: owned ? reviewCommentsForSession(sessionId) : []
|
|
4369
|
+
reviewComments: owned ? reviewCommentsForSession(sessionId) : [],
|
|
4370
|
+
...owned ? { permissionSelector: permissionSelector() } : {}
|
|
4290
4371
|
};
|
|
4291
4372
|
};
|
|
4292
4373
|
const assembleMeta = async (sessionId, activities) => {
|
|
4293
4374
|
const meta = localMeta(sessionId);
|
|
4294
4375
|
if (!meta.owned) return meta;
|
|
4295
|
-
const [repository, repositories] = await Promise.all([
|
|
4376
|
+
const [repository, repositories, permissionMode] = await Promise.all([
|
|
4377
|
+
repositoryForSession(sessionId),
|
|
4378
|
+
extraRepositoriesForSession(sessionId, activities ?? (await sidecar.read(sessionId)).activities),
|
|
4379
|
+
permissionModeForSession(sessionId)
|
|
4380
|
+
]);
|
|
4296
4381
|
return {
|
|
4297
4382
|
...meta,
|
|
4298
4383
|
...repository === void 0 ? {} : { repository },
|
|
4299
|
-
...repositories.length === 0 ? {} : { repositories }
|
|
4384
|
+
...repositories.length === 0 ? {} : { repositories },
|
|
4385
|
+
...permissionMode === void 0 ? {} : { permissionMode }
|
|
4300
4386
|
};
|
|
4301
4387
|
};
|
|
4302
4388
|
const streamMulti = async (res, io, sessionIds) => {
|
|
@@ -4328,6 +4414,8 @@ function registerClaudeProjectionRoute(ctx, sidecar, ownsSession, commandsForSes
|
|
|
4328
4414
|
owned: meta.owned,
|
|
4329
4415
|
commands: meta.commands,
|
|
4330
4416
|
...meta.repository === void 0 ? {} : { repository: meta.repository },
|
|
4417
|
+
...meta.permissionMode === void 0 ? {} : { permissionMode: meta.permissionMode },
|
|
4418
|
+
...meta.permissionSelector === void 0 ? {} : { permissionSelector: meta.permissionSelector },
|
|
4331
4419
|
...meta.repositories === void 0 ? {} : { repositories: meta.repositories },
|
|
4332
4420
|
reviewComments: meta.reviewComments
|
|
4333
4421
|
});
|
|
@@ -4715,14 +4803,14 @@ function parseGitHubRemote(value) {
|
|
|
4715
4803
|
if (match?.[1] === void 0 || match[2] === void 0) return void 0;
|
|
4716
4804
|
return `${match[1]}/${match[2]}`;
|
|
4717
4805
|
}
|
|
4718
|
-
function record$
|
|
4806
|
+
function record$12(value) {
|
|
4719
4807
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
4720
4808
|
}
|
|
4721
4809
|
function aggregateChecks(value) {
|
|
4722
4810
|
if (!Array.isArray(value) || value.length === 0) return "none";
|
|
4723
4811
|
let pending = false;
|
|
4724
4812
|
for (const item of value) {
|
|
4725
|
-
const check = record$
|
|
4813
|
+
const check = record$12(item);
|
|
4726
4814
|
if (check === void 0) continue;
|
|
4727
4815
|
const conclusion = typeof check.conclusion === "string" ? check.conclusion.toUpperCase() : void 0;
|
|
4728
4816
|
const status = typeof check.status === "string" ? check.status.toUpperCase() : void 0;
|
|
@@ -4746,7 +4834,7 @@ function reviewState(value) {
|
|
|
4746
4834
|
return "none";
|
|
4747
4835
|
}
|
|
4748
4836
|
function parsePullRequest(value) {
|
|
4749
|
-
const input = record$
|
|
4837
|
+
const input = record$12(value);
|
|
4750
4838
|
if (input === void 0 || !Number.isSafeInteger(input.number) || Number(input.number) <= 0 || typeof input.title !== "string" || typeof input.url !== "string") return void 0;
|
|
4751
4839
|
let url;
|
|
4752
4840
|
try {
|
|
@@ -4768,7 +4856,7 @@ function parsePullRequest(value) {
|
|
|
4768
4856
|
checks: aggregateChecks(input.statusCheckRollup),
|
|
4769
4857
|
...typeof input.mergeStateStatus === "string" ? { mergeState: bounded(input.mergeStateStatus) } : {},
|
|
4770
4858
|
...typeof input.headRefName === "string" && input.headRefName.length > 0 ? { headBranch: bounded(input.headRefName) } : {},
|
|
4771
|
-
...typeof record$
|
|
4859
|
+
...typeof record$12(input.author)?.login === "string" ? { author: bounded(String(record$12(input.author)?.login)) } : {},
|
|
4772
4860
|
...typeof input.createdAt === "string" && Number.isFinite(Date.parse(input.createdAt)) ? { createdAt: new Date(input.createdAt).toISOString() } : {},
|
|
4773
4861
|
...typeof input.mergedAt === "string" && Number.isFinite(Date.parse(input.mergedAt)) ? { mergedAt: new Date(input.mergedAt).toISOString() } : {},
|
|
4774
4862
|
...typeof input.baseRefName === "string" && bounded(input.baseRefName).length > 0 ? { baseBranch: bounded(input.baseRefName) } : {}
|
|
@@ -4838,7 +4926,7 @@ var RepositoryStatusService = class {
|
|
|
4838
4926
|
status: "unavailable",
|
|
4839
4927
|
cwd
|
|
4840
4928
|
};
|
|
4841
|
-
const counts = record$
|
|
4929
|
+
const counts = record$12(raw);
|
|
4842
4930
|
const count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
4843
4931
|
const patch = await run(this.#runtime, gh, [
|
|
4844
4932
|
"pr",
|
|
@@ -5414,9 +5502,10 @@ var RepositorySetupService = class {
|
|
|
5414
5502
|
progress("switching-branch");
|
|
5415
5503
|
return !local && remote ? this.#checkoutRemote(info, branch) : this.#checkout(info, branch);
|
|
5416
5504
|
}
|
|
5417
|
-
/** Tear down a merged branch: remove a
|
|
5418
|
-
*
|
|
5419
|
-
* the
|
|
5505
|
+
/** Tear down a merged branch: remove a worktree (a plugin one with its
|
|
5506
|
+
* lease, or one another tool added) and its branch, or switch a plain
|
|
5507
|
+
* checkout back to the base branch and delete the merged branch. Refuses
|
|
5508
|
+
* dirty trees. */
|
|
5420
5509
|
/** `branch` names the merged branch when the checkout is no longer on it:
|
|
5421
5510
|
* a session that opened a pull request in another clone switched that
|
|
5422
5511
|
* clone back to base itself, and only the local branch is left to delete.
|
|
@@ -5460,6 +5549,42 @@ var RepositorySetupService = class {
|
|
|
5460
5549
|
};
|
|
5461
5550
|
});
|
|
5462
5551
|
}
|
|
5552
|
+
const dirs = await this.#run(git, [
|
|
5553
|
+
"rev-parse",
|
|
5554
|
+
"--path-format=absolute",
|
|
5555
|
+
"--git-dir",
|
|
5556
|
+
"--git-common-dir"
|
|
5557
|
+
], path);
|
|
5558
|
+
const [gitDir = "", commonDir = ""] = dirs.exitCode === 0 ? dirs.stdout.trim().split(/\r?\n/u) : [];
|
|
5559
|
+
if (gitDir.length > 0 && comparablePath(gitDir) !== comparablePath(commonDir)) {
|
|
5560
|
+
const mainRoot = resolve(commonDir, "..");
|
|
5561
|
+
const head = named === void 0 ? await this.#run(git, [
|
|
5562
|
+
"symbolic-ref",
|
|
5563
|
+
"--quiet",
|
|
5564
|
+
"--short",
|
|
5565
|
+
"HEAD"
|
|
5566
|
+
], path) : void 0;
|
|
5567
|
+
const branch = named ?? (head?.exitCode === 0 ? head.stdout.trim() : "");
|
|
5568
|
+
if (branch.length === 0) throw new RepositorySetupError("nothing-to-clean", "The worktree is not on a branch.");
|
|
5569
|
+
if (requirePushed) await this.#requirePushed(git, mainRoot, branch);
|
|
5570
|
+
if ((await this.#run(git, [
|
|
5571
|
+
"worktree",
|
|
5572
|
+
"remove",
|
|
5573
|
+
"--",
|
|
5574
|
+
path
|
|
5575
|
+
], mainRoot)).exitCode !== 0) throw new RepositorySetupError("worktree-remove-failed", "Git could not remove the worktree.");
|
|
5576
|
+
await this.#run(git, [
|
|
5577
|
+
"branch",
|
|
5578
|
+
"-D",
|
|
5579
|
+
"--",
|
|
5580
|
+
branch
|
|
5581
|
+
], mainRoot).catch(() => void 0);
|
|
5582
|
+
return {
|
|
5583
|
+
mode: "worktree",
|
|
5584
|
+
root: mainRoot,
|
|
5585
|
+
branch
|
|
5586
|
+
};
|
|
5587
|
+
}
|
|
5463
5588
|
const root = await this.#repositoryRoot(git, path);
|
|
5464
5589
|
if (base === void 0) throw new RepositorySetupError("nothing-to-clean", "A plain checkout needs the base branch to return to.");
|
|
5465
5590
|
const head = await this.#run(git, [
|
|
@@ -5863,6 +5988,9 @@ const MAX_UNPUSHED_COMMITS = 20;
|
|
|
5863
5988
|
* head of it otherwise, and the prompt says which. */
|
|
5864
5989
|
const MAX_GENERATE_PATCH_CHARS = 49152;
|
|
5865
5990
|
const MAX_SUBJECT_CHARS = 72;
|
|
5991
|
+
/** A body is for the reader skimming `git log`, not a change list: past this
|
|
5992
|
+
* many bullets it stops being read, so the rest are dropped. */
|
|
5993
|
+
const MAX_BODY_BULLETS = 4;
|
|
5866
5994
|
/** Past this the first line is not a subject at all; under it, a subject a
|
|
5867
5995
|
* few characters over what the prompt asked for is the user's to trim. */
|
|
5868
5996
|
const MAX_SUBJECT_KEPT_CHARS = 120;
|
|
@@ -5984,10 +6112,12 @@ function normalizeCommitMessage(value, fallback) {
|
|
|
5984
6112
|
const kept = [subject];
|
|
5985
6113
|
if (body.length > 0) kept.push("");
|
|
5986
6114
|
let previousBlank = false;
|
|
6115
|
+
let bullets = 0;
|
|
5987
6116
|
for (const line of body) {
|
|
5988
6117
|
const blank = line.trim() === "";
|
|
5989
6118
|
if (blank && previousBlank) continue;
|
|
5990
6119
|
previousBlank = blank;
|
|
6120
|
+
if (/^\s*[-*]\s/u.test(line) && ++bullets > MAX_BODY_BULLETS) continue;
|
|
5991
6121
|
if ([...kept, line].join("\n").length > MAX_MESSAGE_CHARS) break;
|
|
5992
6122
|
kept.push(line);
|
|
5993
6123
|
}
|
|
@@ -6085,10 +6215,10 @@ var RepositoryActionService = class {
|
|
|
6085
6215
|
const subjects = recent.exitCode === 0 && !recent.lossy ? recent.stdout.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0) : [];
|
|
6086
6216
|
const patch = boundedPatch(preview.patch);
|
|
6087
6217
|
const prompt = [
|
|
6088
|
-
"Write a git commit message for the changes below, in English.",
|
|
6089
|
-
`Line 1 is the subject: imperative mood, at most ${MAX_SUBJECT_CHARS} characters, saying what the change does
|
|
6090
|
-
"
|
|
6091
|
-
|
|
6218
|
+
"Write a git commit message for the changes below, in English, for someone skimming git log.",
|
|
6219
|
+
`Line 1 is the subject: imperative mood, at most ${MAX_SUBJECT_CHARS} characters, saying what the change does for the user or the code's behaviour, not which files it touches.`,
|
|
6220
|
+
"Most changes get the subject line only. Add a body only when the diff carries more than one change a reader would want to know about separately: leave line 2 blank, then one line per such change starting with \"- \", a short sentence about what now behaves differently.",
|
|
6221
|
+
`Never more than ${MAX_BODY_BULLETS} bullets. Do not list tests, documentation, README, translations, type or wiring plumbing, or the propagation of one change through several layers: those are part of the change they serve, not changes of their own. Do not name files or identifiers unless nothing else identifies the change.`,
|
|
6092
6222
|
"Describe only what the diff shows. Do not invent motivation, do not summarise the file list, and do not mention that the diff is truncated.",
|
|
6093
6223
|
"Return only the message: no quotes, no markdown fences, no explanation before or after it.",
|
|
6094
6224
|
...subjects.length > 0 ? [`Recent commit subjects of this repository, as a style reference only:\n${subjects.map((subject) => `- ${subject}`).join("\n")}`] : [],
|
|
@@ -6154,7 +6284,7 @@ var RepositoryActionService = class {
|
|
|
6154
6284
|
"Changes:",
|
|
6155
6285
|
"- <one line per independent change, describing what changed in the code>",
|
|
6156
6286
|
"",
|
|
6157
|
-
|
|
6287
|
+
`One bullet per change a reviewer would want to know about separately, at most 6; a change with a single purpose gets one bullet. Tests, documentation, translations, and the plumbing that carries a change through several layers belong to the change they serve, not to bullets of their own. Do not describe anything the diff does not show.`,
|
|
6158
6288
|
"No markdown headings, no quotes, no fences, no text before \"Title:\" or after the last bullet.",
|
|
6159
6289
|
...commits.length > 0 ? [`Commits on this branch, newest first:\n${commits.map((commit) => commit.body.length > 0 ? `- ${commit.subject}\n${commit.body}` : `- ${commit.subject}`).join("\n")}`] : [],
|
|
6160
6290
|
...preview.files.length > 0 ? [`Uncommitted files that will go into the same pull request: ${preview.files.map((file) => file.path).join(", ")}`] : [],
|
|
@@ -6601,19 +6731,19 @@ var RepositoryActionService = class {
|
|
|
6601
6731
|
};
|
|
6602
6732
|
//#endregion
|
|
6603
6733
|
//#region src/repository-setup-routes.ts
|
|
6604
|
-
const MAX_BODY_BYTES$
|
|
6605
|
-
function record$
|
|
6734
|
+
const MAX_BODY_BYTES$8 = 16384;
|
|
6735
|
+
function record$11(value) {
|
|
6606
6736
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6607
6737
|
}
|
|
6608
|
-
async function readJson$
|
|
6738
|
+
async function readJson$7(io) {
|
|
6609
6739
|
let parsed;
|
|
6610
6740
|
try {
|
|
6611
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
6741
|
+
parsed = await io.body(MAX_BODY_BYTES$8);
|
|
6612
6742
|
} catch (error) {
|
|
6613
6743
|
if (error instanceof SyntaxError) throw error;
|
|
6614
6744
|
throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
6615
6745
|
}
|
|
6616
|
-
const value = record$
|
|
6746
|
+
const value = record$11(parsed);
|
|
6617
6747
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
6618
6748
|
return value;
|
|
6619
6749
|
}
|
|
@@ -6682,7 +6812,7 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6682
6812
|
try {
|
|
6683
6813
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches/refresh`) {
|
|
6684
6814
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6685
|
-
const input = await readJson$
|
|
6815
|
+
const input = await readJson$7(io);
|
|
6686
6816
|
return json(res, 200, await service.refreshBranches(string$2(input, "cwd")));
|
|
6687
6817
|
}
|
|
6688
6818
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches`) {
|
|
@@ -6693,14 +6823,14 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6693
6823
|
}
|
|
6694
6824
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
6695
6825
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6696
|
-
const input = await readJson$
|
|
6826
|
+
const input = await readJson$7(io);
|
|
6697
6827
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
6698
6828
|
await streamSetup(res, service, input);
|
|
6699
6829
|
return;
|
|
6700
6830
|
}
|
|
6701
6831
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
6702
6832
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6703
|
-
const input = await readJson$
|
|
6833
|
+
const input = await readJson$7(io);
|
|
6704
6834
|
const path = string$2(input, "path");
|
|
6705
6835
|
const branch = typeof input.branch === "string" && input.branch.length > 0 ? input.branch : void 0;
|
|
6706
6836
|
const baseBranch = typeof input.baseBranch === "string" && input.baseBranch.length > 0 ? input.baseBranch : void 0;
|
|
@@ -6715,7 +6845,7 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6715
6845
|
}
|
|
6716
6846
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
6717
6847
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6718
|
-
const input = await readJson$
|
|
6848
|
+
const input = await readJson$7(io);
|
|
6719
6849
|
await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
|
|
6720
6850
|
return json(res, 200, { ok: true });
|
|
6721
6851
|
}
|
|
@@ -6733,8 +6863,8 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6733
6863
|
}
|
|
6734
6864
|
//#endregion
|
|
6735
6865
|
//#region src/repository-action-routes.ts
|
|
6736
|
-
const MAX_BODY_BYTES$
|
|
6737
|
-
const MAX_SESSION_ID_CHARS$
|
|
6866
|
+
const MAX_BODY_BYTES$7 = 16384;
|
|
6867
|
+
const MAX_SESSION_ID_CHARS$6 = 1024;
|
|
6738
6868
|
const MAX_ROOT_CHARS = 4096;
|
|
6739
6869
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
6740
6870
|
"commit",
|
|
@@ -6754,24 +6884,24 @@ const MESSAGELESS = /* @__PURE__ */ new Set([
|
|
|
6754
6884
|
"resolve-continue",
|
|
6755
6885
|
"resolve-abort"
|
|
6756
6886
|
]);
|
|
6757
|
-
function record$
|
|
6887
|
+
function record$10(value) {
|
|
6758
6888
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6759
6889
|
}
|
|
6760
|
-
async function readJson$
|
|
6890
|
+
async function readJson$6(io) {
|
|
6761
6891
|
let parsed;
|
|
6762
6892
|
try {
|
|
6763
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
6893
|
+
parsed = await io.body(MAX_BODY_BYTES$7);
|
|
6764
6894
|
} catch (error) {
|
|
6765
6895
|
if (error instanceof SyntaxError) throw error;
|
|
6766
6896
|
throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
6767
6897
|
}
|
|
6768
|
-
const value = record$
|
|
6898
|
+
const value = record$10(parsed);
|
|
6769
6899
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
6770
6900
|
return value;
|
|
6771
6901
|
}
|
|
6772
6902
|
function sessionId$1(url) {
|
|
6773
6903
|
const value = url.searchParams.get("sessionId");
|
|
6774
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
6904
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$6) throw new RepositoryActionError("invalid-session", "The session is invalid.");
|
|
6775
6905
|
return value;
|
|
6776
6906
|
}
|
|
6777
6907
|
function requestedRoot(url) {
|
|
@@ -6848,7 +6978,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6848
6978
|
status: 405,
|
|
6849
6979
|
value: { error: "method not allowed" }
|
|
6850
6980
|
};
|
|
6851
|
-
const input = await readJson$
|
|
6981
|
+
const input = await readJson$6(io);
|
|
6852
6982
|
return {
|
|
6853
6983
|
status: 200,
|
|
6854
6984
|
value: { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) }
|
|
@@ -6859,7 +6989,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6859
6989
|
status: 405,
|
|
6860
6990
|
value: { error: "method not allowed" }
|
|
6861
6991
|
};
|
|
6862
|
-
const input = await readJson$
|
|
6992
|
+
const input = await readJson$6(io);
|
|
6863
6993
|
return {
|
|
6864
6994
|
status: 200,
|
|
6865
6995
|
value: await service.generatePullRequest(cwd, string$1(input, "fingerprint"), optionalString(input, "baseBranch"))
|
|
@@ -6872,7 +7002,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6872
7002
|
};
|
|
6873
7003
|
return {
|
|
6874
7004
|
status: 200,
|
|
6875
|
-
value: await service.execute(cwd, actionRequest(await readJson$
|
|
7005
|
+
value: await service.execute(cwd, actionRequest(await readJson$6(io)))
|
|
6876
7006
|
};
|
|
6877
7007
|
}
|
|
6878
7008
|
return {
|
|
@@ -7326,7 +7456,7 @@ async function collect(handle) {
|
|
|
7326
7456
|
lossy: stdout?.lossy === true
|
|
7327
7457
|
};
|
|
7328
7458
|
}
|
|
7329
|
-
function record$
|
|
7459
|
+
function record$9(value) {
|
|
7330
7460
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7331
7461
|
}
|
|
7332
7462
|
/** Read `repository.pullRequest.reviewThreads.nodes` out of a GraphQL response.
|
|
@@ -7334,12 +7464,12 @@ function record$8(value) {
|
|
|
7334
7464
|
* throwing: the caller distinguishes "no threads" from "call failed" by the
|
|
7335
7465
|
* process exit code. */
|
|
7336
7466
|
function parseReviewThreads(value) {
|
|
7337
|
-
const nodes = record$
|
|
7467
|
+
const nodes = record$9(record$9(record$9(record$9(record$9(value)?.data)?.repository)?.pullRequest)?.reviewThreads)?.nodes;
|
|
7338
7468
|
if (!Array.isArray(nodes)) return [];
|
|
7339
7469
|
const threads = [];
|
|
7340
7470
|
let total = 0;
|
|
7341
7471
|
for (const item of nodes) {
|
|
7342
|
-
const input = record$
|
|
7472
|
+
const input = record$9(item);
|
|
7343
7473
|
if (input === void 0 || typeof input.id !== "string" || input.id.length === 0) continue;
|
|
7344
7474
|
const path = typeof input.path === "string" ? input.path : "";
|
|
7345
7475
|
if (path.length === 0) continue;
|
|
@@ -7351,14 +7481,14 @@ function parseReviewThreads(value) {
|
|
|
7351
7481
|
side
|
|
7352
7482
|
};
|
|
7353
7483
|
const comments = [];
|
|
7354
|
-
const commentNodes = record$
|
|
7484
|
+
const commentNodes = record$9(input.comments)?.nodes;
|
|
7355
7485
|
for (const node of Array.isArray(commentNodes) ? commentNodes : []) {
|
|
7356
7486
|
if (total >= MAX_COMMENTS) break;
|
|
7357
|
-
const comment = record$
|
|
7487
|
+
const comment = record$9(node);
|
|
7358
7488
|
if (comment === void 0 || !Number.isSafeInteger(comment.databaseId)) continue;
|
|
7359
7489
|
const body = typeof comment.body === "string" ? comment.body.trim() : "";
|
|
7360
7490
|
if (body.length === 0) continue;
|
|
7361
|
-
const author = record$
|
|
7491
|
+
const author = record$9(comment.author);
|
|
7362
7492
|
const avatarUrl = githubAvatarUrl(author?.avatarUrl);
|
|
7363
7493
|
const login = typeof author?.login === "string" ? author.login : "unknown";
|
|
7364
7494
|
comments.push({
|
|
@@ -7387,11 +7517,11 @@ function parseReviewThreads(value) {
|
|
|
7387
7517
|
}
|
|
7388
7518
|
/** One posted reply, shaped like the thread comments it joins. */
|
|
7389
7519
|
function parseReplyComment(value, anchor) {
|
|
7390
|
-
const input = record$
|
|
7520
|
+
const input = record$9(value);
|
|
7391
7521
|
if (input === void 0 || !Number.isSafeInteger(input.id)) return void 0;
|
|
7392
7522
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
7393
7523
|
if (body.length === 0) return void 0;
|
|
7394
|
-
const user = record$
|
|
7524
|
+
const user = record$9(input.user);
|
|
7395
7525
|
const avatarUrl = githubAvatarUrl(user?.avatar_url);
|
|
7396
7526
|
const login = typeof user?.login === "string" ? user.login : "unknown";
|
|
7397
7527
|
return {
|
|
@@ -7406,11 +7536,11 @@ function parseReplyComment(value, anchor) {
|
|
|
7406
7536
|
};
|
|
7407
7537
|
}
|
|
7408
7538
|
function parseMentionableUsers(value) {
|
|
7409
|
-
const nodes = record$
|
|
7539
|
+
const nodes = record$9(record$9(record$9(record$9(value)?.data)?.repository)?.mentionableUsers)?.nodes;
|
|
7410
7540
|
if (!Array.isArray(nodes)) return [];
|
|
7411
7541
|
const users = [];
|
|
7412
7542
|
for (const item of nodes) {
|
|
7413
|
-
const input = record$
|
|
7543
|
+
const input = record$9(item);
|
|
7414
7544
|
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
7415
7545
|
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
7416
7546
|
users.push({
|
|
@@ -7430,7 +7560,7 @@ function parseFailingChecks(value) {
|
|
|
7430
7560
|
if (!Array.isArray(value)) return [];
|
|
7431
7561
|
const failing = [];
|
|
7432
7562
|
for (const item of value) {
|
|
7433
|
-
const input = record$
|
|
7563
|
+
const input = record$9(item);
|
|
7434
7564
|
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
7435
7565
|
failing.push({
|
|
7436
7566
|
name: input.name,
|
|
@@ -7493,12 +7623,12 @@ var PullRequestFeedbackService = class {
|
|
|
7493
7623
|
let posted;
|
|
7494
7624
|
try {
|
|
7495
7625
|
const parsed = JSON.parse(result.stdout);
|
|
7496
|
-
const path = typeof record$
|
|
7497
|
-
const line = Number.isSafeInteger(record$
|
|
7626
|
+
const path = typeof record$9(parsed)?.path === "string" ? String(record$9(parsed)?.path) : "";
|
|
7627
|
+
const line = Number.isSafeInteger(record$9(parsed)?.line) ? Number(record$9(parsed)?.line) : void 0;
|
|
7498
7628
|
posted = parseReplyComment(parsed, {
|
|
7499
7629
|
path,
|
|
7500
7630
|
...line === void 0 ? {} : { line },
|
|
7501
|
-
side: record$
|
|
7631
|
+
side: record$9(parsed)?.side === "LEFT" ? "old" : "new"
|
|
7502
7632
|
});
|
|
7503
7633
|
} catch {
|
|
7504
7634
|
posted = void 0;
|
|
@@ -7519,7 +7649,7 @@ var PullRequestFeedbackService = class {
|
|
|
7519
7649
|
], cwd, GH_TIMEOUT_MS);
|
|
7520
7650
|
if (result.exitCode !== 0) throw new PullRequestFeedbackError("resolve-failed", "The thread could not be updated.");
|
|
7521
7651
|
try {
|
|
7522
|
-
const thread = record$
|
|
7652
|
+
const thread = record$9(record$9(record$9(record$9(JSON.parse(result.stdout))?.data)?.[resolved ? "resolveReviewThread" : "unresolveReviewThread"])?.thread);
|
|
7523
7653
|
if (typeof thread?.isResolved !== "boolean") throw new Error("missing state");
|
|
7524
7654
|
return thread.isResolved;
|
|
7525
7655
|
} catch {
|
|
@@ -7642,23 +7772,23 @@ var PullRequestFeedbackService = class {
|
|
|
7642
7772
|
};
|
|
7643
7773
|
//#endregion
|
|
7644
7774
|
//#region src/pr-feedback-routes.ts
|
|
7645
|
-
const MAX_SESSION_ID_CHARS$
|
|
7646
|
-
const MAX_BODY_BYTES$
|
|
7775
|
+
const MAX_SESSION_ID_CHARS$5 = 1024;
|
|
7776
|
+
const MAX_BODY_BYTES$6 = 16384;
|
|
7647
7777
|
const MAX_REPLY_CHARS = 2e3;
|
|
7648
7778
|
const MAX_THREAD_ID_CHARS = 512;
|
|
7649
7779
|
const MAX_MENTION_QUERY_CHARS = 64;
|
|
7650
|
-
function record$
|
|
7780
|
+
function record$8(value) {
|
|
7651
7781
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7652
7782
|
}
|
|
7653
|
-
async function readJson$
|
|
7783
|
+
async function readJson$5(io) {
|
|
7654
7784
|
let parsed;
|
|
7655
7785
|
try {
|
|
7656
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
7786
|
+
parsed = await io.body(MAX_BODY_BYTES$6);
|
|
7657
7787
|
} catch (error) {
|
|
7658
7788
|
if (error instanceof SyntaxError) throw error;
|
|
7659
7789
|
throw new PullRequestFeedbackError("body-too-large", "The request body is too large.");
|
|
7660
7790
|
}
|
|
7661
|
-
const value = record$
|
|
7791
|
+
const value = record$8(parsed);
|
|
7662
7792
|
if (value === void 0) throw new PullRequestFeedbackError("invalid-request", "The request body is invalid.");
|
|
7663
7793
|
return value;
|
|
7664
7794
|
}
|
|
@@ -7679,7 +7809,7 @@ function threadId(input) {
|
|
|
7679
7809
|
}
|
|
7680
7810
|
function sessionId(url) {
|
|
7681
7811
|
const value = url.searchParams.get("sessionId");
|
|
7682
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
7812
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$5) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
|
|
7683
7813
|
return value;
|
|
7684
7814
|
}
|
|
7685
7815
|
function pullNumber(url) {
|
|
@@ -7738,7 +7868,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
7738
7868
|
status: 405,
|
|
7739
7869
|
value: { error: "method not allowed" }
|
|
7740
7870
|
};
|
|
7741
|
-
const input = await readJson$
|
|
7871
|
+
const input = await readJson$5(io);
|
|
7742
7872
|
return {
|
|
7743
7873
|
status: 200,
|
|
7744
7874
|
value: { comment: await service.reply(cwd, pullNumber(url), commentId(input), replyBody(input)) }
|
|
@@ -7749,7 +7879,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
7749
7879
|
status: 405,
|
|
7750
7880
|
value: { error: "method not allowed" }
|
|
7751
7881
|
};
|
|
7752
|
-
const input = await readJson$
|
|
7882
|
+
const input = await readJson$5(io);
|
|
7753
7883
|
if (typeof input.resolved !== "boolean") throw new PullRequestFeedbackError("invalid-request", "The resolved field is required.");
|
|
7754
7884
|
return {
|
|
7755
7885
|
status: 200,
|
|
@@ -7895,7 +8025,7 @@ var JiraError = class extends Error {
|
|
|
7895
8025
|
this.code = code;
|
|
7896
8026
|
}
|
|
7897
8027
|
};
|
|
7898
|
-
function record$
|
|
8028
|
+
function record$7(value) {
|
|
7899
8029
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7900
8030
|
}
|
|
7901
8031
|
/** Jira Cloud sites are origins; Data Center may carry a context path. */
|
|
@@ -7919,14 +8049,14 @@ function ticketKeyOf(query) {
|
|
|
7919
8049
|
* search and does match keys; its hits become the key filter the normal
|
|
7920
8050
|
* search then reads the display fields from. */
|
|
7921
8051
|
function pickerKeys(value, number) {
|
|
7922
|
-
const sections = record$
|
|
8052
|
+
const sections = record$7(value)?.sections;
|
|
7923
8053
|
if (!Array.isArray(sections)) return [];
|
|
7924
8054
|
const keys = [];
|
|
7925
8055
|
for (const section of sections) {
|
|
7926
|
-
const issues = record$
|
|
8056
|
+
const issues = record$7(section)?.issues;
|
|
7927
8057
|
if (!Array.isArray(issues)) continue;
|
|
7928
8058
|
for (const issue of issues) {
|
|
7929
|
-
const raw = record$
|
|
8059
|
+
const raw = record$7(issue)?.key;
|
|
7930
8060
|
const key = ticketKeyOf(typeof raw === "string" ? raw : "");
|
|
7931
8061
|
if (key !== void 0 && key.endsWith(`-${number}`) && !keys.includes(key)) keys.push(key);
|
|
7932
8062
|
}
|
|
@@ -7945,15 +8075,15 @@ function buildJql(query) {
|
|
|
7945
8075
|
return `text ~ "${trimmed.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}*" ORDER BY updated DESC`;
|
|
7946
8076
|
}
|
|
7947
8077
|
function parseTickets(value, siteUrl) {
|
|
7948
|
-
const issues = record$
|
|
8078
|
+
const issues = record$7(value)?.issues;
|
|
7949
8079
|
if (!Array.isArray(issues)) return [];
|
|
7950
8080
|
const tickets = [];
|
|
7951
8081
|
for (const item of issues) {
|
|
7952
|
-
const issue = record$
|
|
7953
|
-
const fields = record$
|
|
8082
|
+
const issue = record$7(item);
|
|
8083
|
+
const fields = record$7(issue?.fields);
|
|
7954
8084
|
if (issue === void 0 || typeof issue.key !== "string" || fields === void 0) continue;
|
|
7955
|
-
const status = record$
|
|
7956
|
-
const type = record$
|
|
8085
|
+
const status = record$7(fields.status)?.name;
|
|
8086
|
+
const type = record$7(fields.issuetype)?.name;
|
|
7957
8087
|
tickets.push({
|
|
7958
8088
|
key: issue.key,
|
|
7959
8089
|
summary: typeof fields.summary === "string" ? fields.summary.slice(0, 256) : "",
|
|
@@ -7995,7 +8125,7 @@ var JiraService = class {
|
|
|
7995
8125
|
email,
|
|
7996
8126
|
apiToken
|
|
7997
8127
|
};
|
|
7998
|
-
const myself = record$
|
|
8128
|
+
const myself = record$7(await this.#json(connection, "/rest/api/3/myself"));
|
|
7999
8129
|
const displayName = typeof myself?.displayName === "string" ? myself.displayName : void 0;
|
|
8000
8130
|
const accountId = typeof myself?.accountId === "string" ? myself.accountId : void 0;
|
|
8001
8131
|
const store = {
|
|
@@ -8014,7 +8144,7 @@ var JiraService = class {
|
|
|
8014
8144
|
if (ticket === void 0) throw new JiraError("invalid-request", "The ticket key is invalid.");
|
|
8015
8145
|
let accountId = store.accountId;
|
|
8016
8146
|
if (accountId === void 0) {
|
|
8017
|
-
const myself = record$
|
|
8147
|
+
const myself = record$7(await this.#json(store, "/rest/api/3/myself"));
|
|
8018
8148
|
if (typeof myself?.accountId !== "string") throw new JiraError("jira-failed", "The Jira account id is unavailable.");
|
|
8019
8149
|
accountId = myself.accountId;
|
|
8020
8150
|
await this.#write({
|
|
@@ -8090,7 +8220,7 @@ var JiraService = class {
|
|
|
8090
8220
|
throw error;
|
|
8091
8221
|
}
|
|
8092
8222
|
if (Buffer.byteLength(text) > MAX_STORE_BYTES) return void 0;
|
|
8093
|
-
const input = record$
|
|
8223
|
+
const input = record$7(JSON.parse(text));
|
|
8094
8224
|
if (input === void 0 || typeof input.siteUrl !== "string" || typeof input.email !== "string" || typeof input.apiToken !== "string") return void 0;
|
|
8095
8225
|
return {
|
|
8096
8226
|
siteUrl: input.siteUrl,
|
|
@@ -8121,21 +8251,21 @@ var JiraService = class {
|
|
|
8121
8251
|
};
|
|
8122
8252
|
//#endregion
|
|
8123
8253
|
//#region src/jira-routes.ts
|
|
8124
|
-
const MAX_BODY_BYTES$
|
|
8125
|
-
function record$
|
|
8254
|
+
const MAX_BODY_BYTES$5 = 8192;
|
|
8255
|
+
function record$6(value) {
|
|
8126
8256
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8127
8257
|
}
|
|
8128
8258
|
/** The wrapper enforces the byte cap; its plain rejection is translated back
|
|
8129
8259
|
* into the JiraError shape the panel already knows how to render. */
|
|
8130
|
-
async function readJson$
|
|
8260
|
+
async function readJson$4(io) {
|
|
8131
8261
|
let body;
|
|
8132
8262
|
try {
|
|
8133
|
-
body = await io.body(MAX_BODY_BYTES$
|
|
8263
|
+
body = await io.body(MAX_BODY_BYTES$5);
|
|
8134
8264
|
} catch (error) {
|
|
8135
8265
|
if (error instanceof SyntaxError) throw error;
|
|
8136
8266
|
throw new JiraError("body-too-large", "The request body is too large.");
|
|
8137
8267
|
}
|
|
8138
|
-
const value = record$
|
|
8268
|
+
const value = record$6(body);
|
|
8139
8269
|
if (value === void 0) throw new JiraError("invalid-request", "The request body is invalid.");
|
|
8140
8270
|
return value;
|
|
8141
8271
|
}
|
|
@@ -8169,7 +8299,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
8169
8299
|
status: 405,
|
|
8170
8300
|
value: { error: "method not allowed" }
|
|
8171
8301
|
};
|
|
8172
|
-
const input = await readJson$
|
|
8302
|
+
const input = await readJson$4(io);
|
|
8173
8303
|
return {
|
|
8174
8304
|
status: 200,
|
|
8175
8305
|
value: await service.connect({
|
|
@@ -8195,7 +8325,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
8195
8325
|
status: 405,
|
|
8196
8326
|
value: { error: "method not allowed" }
|
|
8197
8327
|
};
|
|
8198
|
-
const input = await readJson$
|
|
8328
|
+
const input = await readJson$4(io);
|
|
8199
8329
|
await service.assignToMe(string(input, "key"));
|
|
8200
8330
|
return {
|
|
8201
8331
|
status: 200,
|
|
@@ -8314,12 +8444,12 @@ function askArguments(preferences) {
|
|
|
8314
8444
|
...READ_ONLY_TOOLS
|
|
8315
8445
|
];
|
|
8316
8446
|
}
|
|
8317
|
-
function record$
|
|
8447
|
+
function record$5(value) {
|
|
8318
8448
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8319
8449
|
}
|
|
8320
8450
|
/** One-line description of a tool call, mirroring the main window's step titles. */
|
|
8321
8451
|
function toolSummary(input) {
|
|
8322
|
-
const fields = record$
|
|
8452
|
+
const fields = record$5(input);
|
|
8323
8453
|
if (fields === void 0) return void 0;
|
|
8324
8454
|
const candidate = [
|
|
8325
8455
|
fields.command,
|
|
@@ -8333,7 +8463,7 @@ function toolSummary(input) {
|
|
|
8333
8463
|
function eventsOfStreamLine(line) {
|
|
8334
8464
|
let parsed;
|
|
8335
8465
|
try {
|
|
8336
|
-
parsed = record$
|
|
8466
|
+
parsed = record$5(JSON.parse(line));
|
|
8337
8467
|
} catch {
|
|
8338
8468
|
return [];
|
|
8339
8469
|
}
|
|
@@ -8343,9 +8473,9 @@ function eventsOfStreamLine(line) {
|
|
|
8343
8473
|
text: "ready"
|
|
8344
8474
|
}];
|
|
8345
8475
|
if (parsed.type === "stream_event") {
|
|
8346
|
-
const event = record$
|
|
8476
|
+
const event = record$5(parsed.event);
|
|
8347
8477
|
if (event?.type === "content_block_start") {
|
|
8348
|
-
const block = record$
|
|
8478
|
+
const block = record$5(event.content_block);
|
|
8349
8479
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") return [{
|
|
8350
8480
|
type: "tool",
|
|
8351
8481
|
id: block.id,
|
|
@@ -8354,7 +8484,7 @@ function eventsOfStreamLine(line) {
|
|
|
8354
8484
|
}];
|
|
8355
8485
|
return [];
|
|
8356
8486
|
}
|
|
8357
|
-
const delta = record$
|
|
8487
|
+
const delta = record$5(event?.delta);
|
|
8358
8488
|
if (event?.type !== "content_block_delta" || delta === void 0) return [];
|
|
8359
8489
|
if (delta.type === "text_delta" && typeof delta.text === "string") return [{
|
|
8360
8490
|
type: "text",
|
|
@@ -8367,11 +8497,11 @@ function eventsOfStreamLine(line) {
|
|
|
8367
8497
|
return [];
|
|
8368
8498
|
}
|
|
8369
8499
|
if (parsed.type === "assistant" || parsed.type === "user") {
|
|
8370
|
-
const content = record$
|
|
8500
|
+
const content = record$5(parsed.message)?.content;
|
|
8371
8501
|
if (!Array.isArray(content)) return [];
|
|
8372
8502
|
const events = [];
|
|
8373
8503
|
for (const item of content) {
|
|
8374
|
-
const block = record$
|
|
8504
|
+
const block = record$5(item);
|
|
8375
8505
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
8376
8506
|
const summary = toolSummary(block.input);
|
|
8377
8507
|
events.push({
|
|
@@ -8463,11 +8593,11 @@ var AskService = class {
|
|
|
8463
8593
|
};
|
|
8464
8594
|
//#endregion
|
|
8465
8595
|
//#region src/ask-routes.ts
|
|
8466
|
-
const MAX_BODY_BYTES$
|
|
8467
|
-
const MAX_SESSION_ID_CHARS$
|
|
8596
|
+
const MAX_BODY_BYTES$4 = 131072;
|
|
8597
|
+
const MAX_SESSION_ID_CHARS$4 = 1024;
|
|
8468
8598
|
/** Two sessions may await an answer at once; a third evicts the oldest. */
|
|
8469
8599
|
const MAX_CONCURRENT_ASKS = 2;
|
|
8470
|
-
function record$
|
|
8600
|
+
function record$4(value) {
|
|
8471
8601
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8472
8602
|
}
|
|
8473
8603
|
function askRequest(input) {
|
|
@@ -8499,12 +8629,12 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
8499
8629
|
let sessionId;
|
|
8500
8630
|
try {
|
|
8501
8631
|
const value = io.url.searchParams.get("sessionId");
|
|
8502
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
8632
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$4) throw new AskError("invalid-session", "The session is invalid.");
|
|
8503
8633
|
sessionId = value;
|
|
8504
8634
|
const resolved = cwdForSession(sessionId);
|
|
8505
8635
|
if (resolved === void 0) throw new AskError("session-unavailable", "The Claude session is unavailable.");
|
|
8506
8636
|
cwd = resolved;
|
|
8507
|
-
const body = record$
|
|
8637
|
+
const body = record$4(await io.body(MAX_BODY_BYTES$4));
|
|
8508
8638
|
if (body === void 0) throw new AskError("invalid-request", "The request body is invalid.");
|
|
8509
8639
|
request = askRequest(body);
|
|
8510
8640
|
} catch (error) {
|
|
@@ -8544,26 +8674,26 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
8544
8674
|
}
|
|
8545
8675
|
//#endregion
|
|
8546
8676
|
//#region src/review-comment-routes.ts
|
|
8547
|
-
const MAX_BODY_BYTES$
|
|
8548
|
-
const MAX_SESSION_ID_CHARS$
|
|
8549
|
-
function record$
|
|
8677
|
+
const MAX_BODY_BYTES$3 = 16384;
|
|
8678
|
+
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
8679
|
+
function record$3(value) {
|
|
8550
8680
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8551
8681
|
}
|
|
8552
|
-
async function readJson$
|
|
8682
|
+
async function readJson$3(io) {
|
|
8553
8683
|
let parsed;
|
|
8554
8684
|
try {
|
|
8555
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
8685
|
+
parsed = await io.body(MAX_BODY_BYTES$3);
|
|
8556
8686
|
} catch (error) {
|
|
8557
8687
|
if (error instanceof SyntaxError) throw error;
|
|
8558
8688
|
throw new ReviewCommentError("body-too-large", "The request body is too large.");
|
|
8559
8689
|
}
|
|
8560
|
-
const value = record$
|
|
8690
|
+
const value = record$3(parsed);
|
|
8561
8691
|
if (value === void 0) throw new ReviewCommentError("invalid-request", "The request body is invalid.");
|
|
8562
8692
|
return value;
|
|
8563
8693
|
}
|
|
8564
8694
|
function sessionIdFromUrl(url) {
|
|
8565
8695
|
const value = url.searchParams.get("sessionId");
|
|
8566
|
-
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$
|
|
8696
|
+
if (value === null || value.length === 0 || value.length > MAX_SESSION_ID_CHARS$3) throw new ReviewCommentError("invalid-session", "The session is invalid.");
|
|
8567
8697
|
return value;
|
|
8568
8698
|
}
|
|
8569
8699
|
function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
@@ -8579,7 +8709,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
8579
8709
|
if (!ownsSession(sessionId)) throw new ReviewCommentError("session-unavailable", "The Claude session is unavailable.");
|
|
8580
8710
|
const pathname = io.url.pathname;
|
|
8581
8711
|
if (pathname === "/plugins/dsh-claude/review-comments") {
|
|
8582
|
-
const input = await readJson$
|
|
8712
|
+
const input = await readJson$3(io);
|
|
8583
8713
|
return {
|
|
8584
8714
|
status: 200,
|
|
8585
8715
|
value: { comment: store.add(sessionId, {
|
|
@@ -8596,7 +8726,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
8596
8726
|
value: { removed: store.drain(sessionId).length }
|
|
8597
8727
|
};
|
|
8598
8728
|
if (pathname === `/plugins/dsh-claude/review-comments/remove`) {
|
|
8599
|
-
const input = await readJson$
|
|
8729
|
+
const input = await readJson$3(io);
|
|
8600
8730
|
if (typeof input.id !== "string" || input.id.length === 0 || input.id.length > 128) throw new ReviewCommentError("invalid-request", "The comment id is invalid.");
|
|
8601
8731
|
return {
|
|
8602
8732
|
status: 200,
|
|
@@ -8632,21 +8762,21 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
8632
8762
|
}
|
|
8633
8763
|
//#endregion
|
|
8634
8764
|
//#region src/plan-feedback-routes.ts
|
|
8635
|
-
const MAX_BODY_BYTES$
|
|
8636
|
-
const MAX_SESSION_ID_CHARS$
|
|
8765
|
+
const MAX_BODY_BYTES$2 = 65536;
|
|
8766
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
8637
8767
|
const MAX_TOOL_USE_ID_CHARS = 256;
|
|
8638
|
-
function record$
|
|
8768
|
+
function record$2(value) {
|
|
8639
8769
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8640
8770
|
}
|
|
8641
|
-
async function readJson$
|
|
8771
|
+
async function readJson$2(io) {
|
|
8642
8772
|
let parsed;
|
|
8643
8773
|
try {
|
|
8644
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
8774
|
+
parsed = await io.body(MAX_BODY_BYTES$2);
|
|
8645
8775
|
} catch (error) {
|
|
8646
8776
|
if (error instanceof SyntaxError) throw error;
|
|
8647
8777
|
throw new PlanFeedbackError("body-too-large", "The request body is too large.");
|
|
8648
8778
|
}
|
|
8649
|
-
const value = record$
|
|
8779
|
+
const value = record$2(parsed);
|
|
8650
8780
|
if (value === void 0) throw new PlanFeedbackError("invalid-request", "The request body is invalid.");
|
|
8651
8781
|
return value;
|
|
8652
8782
|
}
|
|
@@ -8666,7 +8796,7 @@ function registerPlanFeedbackRoute(ctx, gate, ownsSession) {
|
|
|
8666
8796
|
handler: async (io) => {
|
|
8667
8797
|
try {
|
|
8668
8798
|
const sessionId = io.url.searchParams.get("sessionId");
|
|
8669
|
-
if (sessionId === null || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$
|
|
8799
|
+
if (sessionId === null || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$2) return {
|
|
8670
8800
|
status: 400,
|
|
8671
8801
|
value: { error: "invalid-session" }
|
|
8672
8802
|
};
|
|
@@ -8674,7 +8804,7 @@ function registerPlanFeedbackRoute(ctx, gate, ownsSession) {
|
|
|
8674
8804
|
status: 409,
|
|
8675
8805
|
value: { error: "session-unavailable" }
|
|
8676
8806
|
};
|
|
8677
|
-
const body = await readJson$
|
|
8807
|
+
const body = await readJson$2(io);
|
|
8678
8808
|
const toolUseId = body.toolUseId;
|
|
8679
8809
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || toolUseId.length > MAX_TOOL_USE_ID_CHARS) return {
|
|
8680
8810
|
status: 400,
|
|
@@ -8759,16 +8889,16 @@ function registerClaudeClientDiagnosticsRoute(ctx) {
|
|
|
8759
8889
|
}
|
|
8760
8890
|
//#endregion
|
|
8761
8891
|
//#region src/rewind-routes.ts
|
|
8762
|
-
const MAX_BODY_BYTES = 4096;
|
|
8763
|
-
const MAX_SESSION_ID_CHARS = 1024;
|
|
8764
|
-
function record(value) {
|
|
8892
|
+
const MAX_BODY_BYTES$1 = 4096;
|
|
8893
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
8894
|
+
function record$1(value) {
|
|
8765
8895
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8766
8896
|
}
|
|
8767
8897
|
/** An oversized body fails the same field checks as a missing one; only
|
|
8768
8898
|
* malformed JSON is worth reporting separately. */
|
|
8769
|
-
async function readJson(io) {
|
|
8899
|
+
async function readJson$1(io) {
|
|
8770
8900
|
try {
|
|
8771
|
-
return record(await io.body(MAX_BODY_BYTES));
|
|
8901
|
+
return record$1(await io.body(MAX_BODY_BYTES$1));
|
|
8772
8902
|
} catch (error) {
|
|
8773
8903
|
if (error instanceof SyntaxError) throw error;
|
|
8774
8904
|
return;
|
|
@@ -8792,10 +8922,10 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
|
8792
8922
|
budget: "git",
|
|
8793
8923
|
handler: async (io) => {
|
|
8794
8924
|
try {
|
|
8795
|
-
const input = await readJson(io);
|
|
8925
|
+
const input = await readJson$1(io);
|
|
8796
8926
|
const sessionId = input?.sessionId;
|
|
8797
8927
|
const seq = input?.seq;
|
|
8798
|
-
if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS || typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0) return {
|
|
8928
|
+
if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS$1 || typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0) return {
|
|
8799
8929
|
status: 400,
|
|
8800
8930
|
value: { error: "invalid-request" }
|
|
8801
8931
|
};
|
|
@@ -8839,6 +8969,100 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
|
8839
8969
|
});
|
|
8840
8970
|
}
|
|
8841
8971
|
//#endregion
|
|
8972
|
+
//#region src/permission-mode-routes.ts
|
|
8973
|
+
const MAX_BODY_BYTES = 4096;
|
|
8974
|
+
const MAX_SESSION_ID_CHARS = 1024;
|
|
8975
|
+
function record(value) {
|
|
8976
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8977
|
+
}
|
|
8978
|
+
async function readJson(io) {
|
|
8979
|
+
try {
|
|
8980
|
+
return record(await io.body(MAX_BODY_BYTES));
|
|
8981
|
+
} catch (error) {
|
|
8982
|
+
if (error instanceof SyntaxError) throw error;
|
|
8983
|
+
return;
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
/** `POST <path>` with `{ sessionId, mode }`: make `mode` the session's Claude
|
|
8987
|
+
* permission mode from its next turn, and move the Host's access preset
|
|
8988
|
+
* with it. The plugin's record lands first: it is what the next turn reads,
|
|
8989
|
+
* and a Host that declines the preset changes nothing about that. */
|
|
8990
|
+
function registerClaudePermissionModeRoute(ctx, sidecar, access) {
|
|
8991
|
+
registerPluginRoute(ctx, {
|
|
8992
|
+
mode: "unary",
|
|
8993
|
+
kind: "exact",
|
|
8994
|
+
path: CLAUDE_PERMISSION_MODE_PATH,
|
|
8995
|
+
methods: ["POST"],
|
|
8996
|
+
budget: "fast",
|
|
8997
|
+
handler: async (io) => {
|
|
8998
|
+
try {
|
|
8999
|
+
const input = await readJson(io);
|
|
9000
|
+
const sessionId = input?.sessionId;
|
|
9001
|
+
const mode = input?.mode;
|
|
9002
|
+
if (typeof sessionId !== "string" || sessionId.length === 0 || sessionId.length > MAX_SESSION_ID_CHARS || !isClaudePermissionMode(mode)) return {
|
|
9003
|
+
status: 400,
|
|
9004
|
+
value: { error: "invalid-request" }
|
|
9005
|
+
};
|
|
9006
|
+
if (!access.ownsSession(sessionId)) return {
|
|
9007
|
+
status: 409,
|
|
9008
|
+
value: { error: "session-unavailable" }
|
|
9009
|
+
};
|
|
9010
|
+
if (access.busy(sessionId)) return {
|
|
9011
|
+
status: 409,
|
|
9012
|
+
value: { error: "session-busy" }
|
|
9013
|
+
};
|
|
9014
|
+
await sidecar.writePermissionMode(sessionId, mode);
|
|
9015
|
+
const sandbox = SANDBOX_BY_CLAUDE_MODE[mode];
|
|
9016
|
+
return {
|
|
9017
|
+
status: 200,
|
|
9018
|
+
value: {
|
|
9019
|
+
mode,
|
|
9020
|
+
sandbox,
|
|
9021
|
+
hostSynced: await access.applyHostPreset(sessionId, sandbox).catch((error) => {
|
|
9022
|
+
ctx.logger?.warn?.(`dsh-claude: Host preset ${sandbox} not applied for ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
9023
|
+
return false;
|
|
9024
|
+
})
|
|
9025
|
+
}
|
|
9026
|
+
};
|
|
9027
|
+
} catch (error) {
|
|
9028
|
+
if (error instanceof SyntaxError) return {
|
|
9029
|
+
status: 400,
|
|
9030
|
+
value: { error: "invalid-json" }
|
|
9031
|
+
};
|
|
9032
|
+
throw error;
|
|
9033
|
+
}
|
|
9034
|
+
}
|
|
9035
|
+
});
|
|
9036
|
+
}
|
|
9037
|
+
//#endregion
|
|
9038
|
+
//#region src/permission-mode-host.ts
|
|
9039
|
+
/** Put the Host on the preset named after `sandbox`. Answers whether it took:
|
|
9040
|
+
* a Host with no preset service, or whose table lacks that entry, leaves
|
|
9041
|
+
* Claude to enforce the mode on its own. */
|
|
9042
|
+
function applyHostPreset(access, agent, sandbox) {
|
|
9043
|
+
const presets = access.presets();
|
|
9044
|
+
if (presets === void 0 || !presets.names.includes(sandbox)) return false;
|
|
9045
|
+
presets.apply(agent.session, sandbox, (policy) => {
|
|
9046
|
+
access.setPolicy(agent, policy);
|
|
9047
|
+
});
|
|
9048
|
+
return true;
|
|
9049
|
+
}
|
|
9050
|
+
/** Put a session that has not chosen a mode on the sandbox its default needs.
|
|
9051
|
+
*
|
|
9052
|
+
* The fold in permission-mode.ts lets the sandbox win over a mode it cannot
|
|
9053
|
+
* carry, which is right for a sandbox the user switched deliberately -- and
|
|
9054
|
+
* wrong for the one a fresh session merely inherited from the Host's default
|
|
9055
|
+
* preset, where it would quietly turn a configured `auto` into whatever that
|
|
9056
|
+
* preset means. So a new Claude session is moved onto its default's sandbox
|
|
9057
|
+
* once, at creation; a session that already chose its own mode is left alone.
|
|
9058
|
+
* @returns the sandbox the Host was put on, or undefined when nothing moved. */
|
|
9059
|
+
function alignSessionWithDefault(access, agent, chosen, defaultMode) {
|
|
9060
|
+
if (chosen !== void 0) return void 0;
|
|
9061
|
+
const wanted = SANDBOX_BY_CLAUDE_MODE[defaultMode];
|
|
9062
|
+
if (sandboxModeOf(agent.session.snapshotEvents()) === wanted) return void 0;
|
|
9063
|
+
return applyHostPreset(access, agent, wanted) ? wanted : void 0;
|
|
9064
|
+
}
|
|
9065
|
+
//#endregion
|
|
8842
9066
|
//#region src/touched-repositories.ts
|
|
8843
9067
|
/** Which other repositories a session has written into.
|
|
8844
9068
|
*
|
|
@@ -9532,6 +9756,65 @@ const WORKTREE_BRANCH_PREFIX = {
|
|
|
9532
9756
|
else document.worktreeBranchPrefix = value;
|
|
9533
9757
|
}
|
|
9534
9758
|
};
|
|
9759
|
+
/** Which access control Claude sessions get. `native` is the Host's own
|
|
9760
|
+
* selector and nothing else: the sandbox alone decides the mode, as it did
|
|
9761
|
+
* before this plugin grew a selector; the default-mode setting and the
|
|
9762
|
+
* creation-time alignment stand down. The Client reads this to decide which
|
|
9763
|
+
* selector to draw, so the switch shows at once; a turn already running
|
|
9764
|
+
* keeps the mode it started under. */
|
|
9765
|
+
const PERMISSION_SELECTOR = {
|
|
9766
|
+
key: "permissionSelector",
|
|
9767
|
+
kind: "select",
|
|
9768
|
+
document: "plugin",
|
|
9769
|
+
effect: "immediate",
|
|
9770
|
+
async options() {
|
|
9771
|
+
return CLAUDE_PERMISSION_SELECTORS.map((value) => ({
|
|
9772
|
+
value,
|
|
9773
|
+
label: value,
|
|
9774
|
+
source: "built-in"
|
|
9775
|
+
}));
|
|
9776
|
+
},
|
|
9777
|
+
read(document) {
|
|
9778
|
+
const value = document.permissionSelector;
|
|
9779
|
+
return isClaudePermissionSelector(value) ? value : DEFAULT_CLAUDE_PERMISSION_SELECTOR;
|
|
9780
|
+
},
|
|
9781
|
+
apply(document, value) {
|
|
9782
|
+
if (!isClaudePermissionSelector(value)) throw new Error("Invalid value for global setting permissionSelector");
|
|
9783
|
+
if (value === "plugin") delete document.permissionSelector;
|
|
9784
|
+
else document.permissionSelector = value;
|
|
9785
|
+
}
|
|
9786
|
+
};
|
|
9787
|
+
/** `auto` unless the user says otherwise: Claude Code's classifier approves the
|
|
9788
|
+
* routine and asks about the rest, which is the mode a fresh session should
|
|
9789
|
+
* start in when nobody has thought about it yet. */
|
|
9790
|
+
const DEFAULT_CLAUDE_PERMISSION_MODE = "auto";
|
|
9791
|
+
/** The Claude permission mode a session runs under until its own selector
|
|
9792
|
+
* is used. Plugin settings: a new Claude session is moved onto the DSH
|
|
9793
|
+
* sandbox this mode needs as it is created (permission-mode-host.ts); a
|
|
9794
|
+
* session whose sandbox was switched since yields to that sandbox's own
|
|
9795
|
+
* mode (permission-mode.ts). */
|
|
9796
|
+
const PERMISSION_MODE = {
|
|
9797
|
+
key: "permissionMode",
|
|
9798
|
+
kind: "select",
|
|
9799
|
+
document: "plugin",
|
|
9800
|
+
effect: "new-session",
|
|
9801
|
+
async options() {
|
|
9802
|
+
return CLAUDE_PERMISSION_MODES.map((value) => ({
|
|
9803
|
+
value,
|
|
9804
|
+
label: value,
|
|
9805
|
+
source: "built-in"
|
|
9806
|
+
}));
|
|
9807
|
+
},
|
|
9808
|
+
read(document) {
|
|
9809
|
+
const value = document.permissionMode;
|
|
9810
|
+
return isClaudePermissionMode(value) ? value : DEFAULT_CLAUDE_PERMISSION_MODE;
|
|
9811
|
+
},
|
|
9812
|
+
apply(document, value) {
|
|
9813
|
+
if (!isClaudePermissionMode(value)) throw new Error("Invalid value for global setting permissionMode");
|
|
9814
|
+
if (value === "auto") delete document.permissionMode;
|
|
9815
|
+
else document.permissionMode = value;
|
|
9816
|
+
}
|
|
9817
|
+
};
|
|
9535
9818
|
/** Which renderer draws Claude's visible output. Plugin settings, not Claude's:
|
|
9536
9819
|
* the CLI has no opinion about how DSH paints a turn. The option labels stay
|
|
9537
9820
|
* machine-readable ids; the Client translates the two known values. */
|
|
@@ -9631,6 +9914,8 @@ function integerSetting(key, min, max, defaultFor, effect = "new-session") {
|
|
|
9631
9914
|
}
|
|
9632
9915
|
const DESCRIPTORS = [
|
|
9633
9916
|
OUTPUT_STYLE,
|
|
9917
|
+
PERMISSION_SELECTOR,
|
|
9918
|
+
PERMISSION_MODE,
|
|
9634
9919
|
RENDERER,
|
|
9635
9920
|
PROSE,
|
|
9636
9921
|
ALERTS,
|
|
@@ -9706,6 +9991,27 @@ async function readRenderMode(deps = {}) {
|
|
|
9706
9991
|
}
|
|
9707
9992
|
return isClaudeRenderMode(document.renderer) ? document.renderer : DEFAULT_CLAUDE_RENDER_MODE;
|
|
9708
9993
|
}
|
|
9994
|
+
/** Which selector Claude sessions use; unreadable settings mean this plugin's. */
|
|
9995
|
+
async function readPermissionSelector(deps = {}) {
|
|
9996
|
+
let document;
|
|
9997
|
+
try {
|
|
9998
|
+
document = await readDocument(pathsFor(deps).pluginSettingsFile);
|
|
9999
|
+
} catch {
|
|
10000
|
+
return DEFAULT_CLAUDE_PERMISSION_SELECTOR;
|
|
10001
|
+
}
|
|
10002
|
+
return isClaudePermissionSelector(document.permissionSelector) ? document.permissionSelector : DEFAULT_CLAUDE_PERMISSION_SELECTOR;
|
|
10003
|
+
}
|
|
10004
|
+
/** The mode a session runs under until it chooses one. A missing, unreadable,
|
|
10005
|
+
* or malformed plugin settings file means Claude Code's classifier decides. */
|
|
10006
|
+
async function readDefaultPermissionMode(deps = {}) {
|
|
10007
|
+
let document;
|
|
10008
|
+
try {
|
|
10009
|
+
document = await readDocument(pathsFor(deps).pluginSettingsFile);
|
|
10010
|
+
} catch {
|
|
10011
|
+
return DEFAULT_CLAUDE_PERMISSION_MODE;
|
|
10012
|
+
}
|
|
10013
|
+
return isClaudePermissionMode(document.permissionMode) ? document.permissionMode : DEFAULT_CLAUDE_PERMISSION_MODE;
|
|
10014
|
+
}
|
|
9709
10015
|
async function readWorktreeBranchPrefix(deps = {}) {
|
|
9710
10016
|
const paths = pathsFor(deps);
|
|
9711
10017
|
return WORKTREE_BRANCH_PREFIX.read(await readDocument(paths.pluginSettingsFile));
|
|
@@ -9929,6 +10235,7 @@ async function apply(ctx, config) {
|
|
|
9929
10235
|
supervisorConfig.maxProcesses = overrides.maxProcesses ?? defaultLimits.maxProcesses;
|
|
9930
10236
|
};
|
|
9931
10237
|
await applySettingsOverrides();
|
|
10238
|
+
let permissionSelector = await readPermissionSelector();
|
|
9932
10239
|
const sidecar = new ClaudeSidecarRepository();
|
|
9933
10240
|
const repositoryStatus = new RepositoryStatusService(subprocess);
|
|
9934
10241
|
const repositorySetup = new RepositorySetupService(subprocess, {
|
|
@@ -9937,13 +10244,22 @@ async function apply(ctx, config) {
|
|
|
9937
10244
|
});
|
|
9938
10245
|
const reviewComments = new ReviewCommentStore();
|
|
9939
10246
|
const commandCatalogs = /* @__PURE__ */ new Map();
|
|
10247
|
+
/** The Host's access knobs, driven from a Claude mode; see permission-mode-host.ts. */
|
|
10248
|
+
const hostPresets = {
|
|
10249
|
+
presets: () => ctx.get("permissionPresets"),
|
|
10250
|
+
setPolicy: (agent, policy) => {
|
|
10251
|
+
ctx.approval.setPolicy(agent, policy);
|
|
10252
|
+
}
|
|
10253
|
+
};
|
|
9940
10254
|
const supervisor = new ClaudeSupervisor({
|
|
9941
10255
|
runtime: subprocess,
|
|
9942
10256
|
approval: ctx.approval,
|
|
9943
10257
|
userQuestions: ctx.userQuestions,
|
|
9944
10258
|
config: supervisorConfig,
|
|
9945
10259
|
runDetached: (operation) => ctx.agents.withoutInitiator(operation),
|
|
9946
|
-
sidecar
|
|
10260
|
+
sidecar,
|
|
10261
|
+
defaultPermissionMode: () => readDefaultPermissionMode(),
|
|
10262
|
+
permissionSelector: async () => permissionSelector
|
|
9947
10263
|
});
|
|
9948
10264
|
let resolutionError;
|
|
9949
10265
|
try {
|
|
@@ -9954,9 +10270,24 @@ async function apply(ctx, config) {
|
|
|
9954
10270
|
const pending = /* @__PURE__ */ new Set();
|
|
9955
10271
|
const MOUNT_RETRY_MS = 200;
|
|
9956
10272
|
const MOUNT_RETRY_LIMIT = 50;
|
|
9957
|
-
|
|
10273
|
+
/** A session this plugin just met (created, or switched to the Claude
|
|
10274
|
+
* preset) is put on the sandbox its default mode needs, so a Host whose
|
|
10275
|
+
* default preset is full access does not turn a configured `auto` into
|
|
10276
|
+
* bypass. Sessions found on boot keep whatever they were on. */
|
|
10277
|
+
const align = async (agent) => {
|
|
10278
|
+
if (permissionSelector === "native") return;
|
|
10279
|
+
const sessionId = agent.id;
|
|
10280
|
+
try {
|
|
10281
|
+
const [projection, defaultMode] = await Promise.all([sidecar.read(sessionId), readDefaultPermissionMode()]);
|
|
10282
|
+
alignSessionWithDefault(hostPresets, agent, projection.permissionMode, defaultMode);
|
|
10283
|
+
} catch (error) {
|
|
10284
|
+
ctx.logger.warn(`dsh-claude: default permission mode not applied for ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10285
|
+
}
|
|
10286
|
+
};
|
|
10287
|
+
const mount = (agent, fresh) => {
|
|
9958
10288
|
if (mounted.has(agent)) return;
|
|
9959
10289
|
const sessionId = agent.id;
|
|
10290
|
+
if (fresh) align(agent);
|
|
9960
10291
|
const dispose = mountClaudeMetadata(ctx, supervisor, agent, supervisorConfig.defaultModel, sidecar, (commands) => {
|
|
9961
10292
|
if (commands.length === 0) commandCatalogs.delete(sessionId);
|
|
9962
10293
|
else commandCatalogs.set(sessionId, commands);
|
|
@@ -9964,10 +10295,10 @@ async function apply(ctx, config) {
|
|
|
9964
10295
|
if (dispose !== void 0) mounted.set(agent, dispose);
|
|
9965
10296
|
pending.delete(agent);
|
|
9966
10297
|
};
|
|
9967
|
-
const mountWhenPresetSettles = (agent) => {
|
|
10298
|
+
const mountWhenPresetSettles = (agent, fresh) => {
|
|
9968
10299
|
if (mounted.has(agent) || pending.has(agent)) return;
|
|
9969
10300
|
if (ctx.agentPresets.composedPreset(agent.ctx) !== void 0) {
|
|
9970
|
-
mount(agent);
|
|
10301
|
+
mount(agent, fresh);
|
|
9971
10302
|
return;
|
|
9972
10303
|
}
|
|
9973
10304
|
pending.add(agent);
|
|
@@ -9975,7 +10306,7 @@ async function apply(ctx, config) {
|
|
|
9975
10306
|
const retry = () => {
|
|
9976
10307
|
if (mounted.has(agent) || !pending.has(agent)) return;
|
|
9977
10308
|
if (ctx.agentPresets.composedPreset(agent.ctx) !== void 0) {
|
|
9978
|
-
mount(agent);
|
|
10309
|
+
mount(agent, fresh);
|
|
9979
10310
|
return;
|
|
9980
10311
|
}
|
|
9981
10312
|
attempts += 1;
|
|
@@ -9988,15 +10319,15 @@ async function apply(ctx, config) {
|
|
|
9988
10319
|
setTimeout(retry, MOUNT_RETRY_MS).unref?.();
|
|
9989
10320
|
};
|
|
9990
10321
|
const stopCreated = ctx.on("agent/created", ({ agent }) => {
|
|
9991
|
-
mountWhenPresetSettles(agent);
|
|
10322
|
+
mountWhenPresetSettles(agent, true);
|
|
9992
10323
|
});
|
|
9993
10324
|
const onPresetSelected = ctx.on;
|
|
9994
10325
|
const stopSelected = onPresetSelected("agent-preset/selected", (sessionId, preset) => {
|
|
9995
10326
|
if (preset !== "claude") return;
|
|
9996
10327
|
const agent = ctx.agents.get(sessionId);
|
|
9997
|
-
if (agent !== void 0) mountWhenPresetSettles(agent);
|
|
10328
|
+
if (agent !== void 0) mountWhenPresetSettles(agent, true);
|
|
9998
10329
|
});
|
|
9999
|
-
for (const agent of ctx.agents.list()) mountWhenPresetSettles(agent);
|
|
10330
|
+
for (const agent of ctx.agents.list()) mountWhenPresetSettles(agent, false);
|
|
10000
10331
|
return async () => {
|
|
10001
10332
|
stopCreated();
|
|
10002
10333
|
stopSelected();
|
|
@@ -10054,6 +10385,7 @@ async function apply(ctx, config) {
|
|
|
10054
10385
|
defaultLimits,
|
|
10055
10386
|
onUpdated: async () => {
|
|
10056
10387
|
await applySettingsOverrides();
|
|
10388
|
+
permissionSelector = await readPermissionSelector();
|
|
10057
10389
|
supervisor.limitsChanged();
|
|
10058
10390
|
}
|
|
10059
10391
|
});
|
|
@@ -10148,12 +10480,34 @@ async function apply(ctx, config) {
|
|
|
10148
10480
|
}
|
|
10149
10481
|
});
|
|
10150
10482
|
registerPlanUsageRoute(webCtx, (fetchedAt) => probePlanUsage(supervisorConfig.executablePath, fetchedAt));
|
|
10483
|
+
const claudeSessionBusy = (sessionId) => supervisor.snapshots().some((item) => item.sessionId === sessionId && (item.state === "running" || item.state === "interrupting"));
|
|
10484
|
+
registerClaudePermissionModeRoute(webCtx, sidecar, {
|
|
10485
|
+
ownsSession: ownsClaudeSession,
|
|
10486
|
+
busy: claudeSessionBusy,
|
|
10487
|
+
applyHostPreset: async (sessionId, sandbox) => {
|
|
10488
|
+
const agent = webCtx.agents.get(sessionId);
|
|
10489
|
+
return agent !== void 0 && applyHostPreset(hostPresets, agent, sandbox);
|
|
10490
|
+
}
|
|
10491
|
+
});
|
|
10492
|
+
const permissionModeForClaudeSession = async (sessionId) => {
|
|
10493
|
+
const agent = webCtx.agents.get(sessionId);
|
|
10494
|
+
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
10495
|
+
const projection = await sidecar.read(sessionId);
|
|
10496
|
+
const folded = claudePermissionMode(agent.session.snapshotEvents(), permissionSelector === "native" ? void 0 : projection.permissionMode ?? await readDefaultPermissionMode());
|
|
10497
|
+
const live = supervisor.snapshots().find((item) => item.sessionId === sessionId);
|
|
10498
|
+
const autoSupported = live === void 0 ? void 0 : claudeModelRow(live.model)?.supportsAutoMode;
|
|
10499
|
+
return {
|
|
10500
|
+
mode: folded === "auto" && autoSupported === false ? "default" : folded,
|
|
10501
|
+
locked: claudeSessionBusy(sessionId),
|
|
10502
|
+
...autoSupported === void 0 ? {} : { autoSupported }
|
|
10503
|
+
};
|
|
10504
|
+
};
|
|
10151
10505
|
registerClaudeProjectionRoute(webCtx, sidecar, ownsClaudeSession, (sessionId) => commandCatalogs.get(sessionId) ?? [], async (sessionId) => {
|
|
10152
10506
|
const agent = webCtx.agents.get(sessionId);
|
|
10153
10507
|
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
10154
10508
|
const cwd = agent.session.header.cwd;
|
|
10155
10509
|
return cwd === void 0 ? void 0 : repositoryStatus.inspect(cwd);
|
|
10156
|
-
}, (sessionId) => reviewComments.list(sessionId), extraRepositoriesForClaudeSession);
|
|
10510
|
+
}, (sessionId) => reviewComments.list(sessionId), extraRepositoriesForClaudeSession, permissionModeForClaudeSession, () => permissionSelector);
|
|
10157
10511
|
});
|
|
10158
10512
|
}
|
|
10159
10513
|
//#endregion
|