@norman-else/dsh-claude 0.1.53 → 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/INSTALL.md +120 -120
- package/LICENSE +21 -21
- package/README.md +1 -0
- package/cordis.patch.yml +12 -12
- package/legacy-preset/agent.cordis.yml +11 -11
- package/legacy-preset/preset.yml +4 -4
- package/lib/bin.mjs +1 -1
- package/lib/bin.mjs.map +1 -1
- package/lib/client.d.ts +30 -0
- package/lib/client.js +460 -46
- 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 +497 -180
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-CRFcjLSC.mjs → presenters-BaZaq9rR.mjs} +2 -2
- package/lib/presenters-BaZaq9rR.mjs.map +1 -0
- package/lib/{preset-installer-Cj637ucf.mjs → preset-installer-3CuT5Lrc.mjs} +2 -2
- package/lib/preset-installer-3CuT5Lrc.mjs.map +1 -0
- package/lib/preset-route.mjs +2 -2
- package/lib/preset-route.mjs.map +1 -1
- package/package.json +222 -222
- package/preset/claude/agent.cordis.yml +11 -11
- package/preset/claude/preset.yml +4 -4
- package/lib/events-B498uofA.mjs.map +0 -1
- package/lib/presenters-CRFcjLSC.mjs.map +0 -1
- package/lib/preset-installer-Cj637ucf.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",
|
|
@@ -5900,6 +5988,9 @@ const MAX_UNPUSHED_COMMITS = 20;
|
|
|
5900
5988
|
* head of it otherwise, and the prompt says which. */
|
|
5901
5989
|
const MAX_GENERATE_PATCH_CHARS = 49152;
|
|
5902
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;
|
|
5903
5994
|
/** Past this the first line is not a subject at all; under it, a subject a
|
|
5904
5995
|
* few characters over what the prompt asked for is the user's to trim. */
|
|
5905
5996
|
const MAX_SUBJECT_KEPT_CHARS = 120;
|
|
@@ -6021,10 +6112,12 @@ function normalizeCommitMessage(value, fallback) {
|
|
|
6021
6112
|
const kept = [subject];
|
|
6022
6113
|
if (body.length > 0) kept.push("");
|
|
6023
6114
|
let previousBlank = false;
|
|
6115
|
+
let bullets = 0;
|
|
6024
6116
|
for (const line of body) {
|
|
6025
6117
|
const blank = line.trim() === "";
|
|
6026
6118
|
if (blank && previousBlank) continue;
|
|
6027
6119
|
previousBlank = blank;
|
|
6120
|
+
if (/^\s*[-*]\s/u.test(line) && ++bullets > MAX_BODY_BULLETS) continue;
|
|
6028
6121
|
if ([...kept, line].join("\n").length > MAX_MESSAGE_CHARS) break;
|
|
6029
6122
|
kept.push(line);
|
|
6030
6123
|
}
|
|
@@ -6122,10 +6215,10 @@ var RepositoryActionService = class {
|
|
|
6122
6215
|
const subjects = recent.exitCode === 0 && !recent.lossy ? recent.stdout.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0) : [];
|
|
6123
6216
|
const patch = boundedPatch(preview.patch);
|
|
6124
6217
|
const prompt = [
|
|
6125
|
-
"Write a git commit message for the changes below, in English.",
|
|
6126
|
-
`Line 1 is the subject: imperative mood, at most ${MAX_SUBJECT_CHARS} characters, saying what the change does
|
|
6127
|
-
"
|
|
6128
|
-
|
|
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.`,
|
|
6129
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.",
|
|
6130
6223
|
"Return only the message: no quotes, no markdown fences, no explanation before or after it.",
|
|
6131
6224
|
...subjects.length > 0 ? [`Recent commit subjects of this repository, as a style reference only:\n${subjects.map((subject) => `- ${subject}`).join("\n")}`] : [],
|
|
@@ -6191,7 +6284,7 @@ var RepositoryActionService = class {
|
|
|
6191
6284
|
"Changes:",
|
|
6192
6285
|
"- <one line per independent change, describing what changed in the code>",
|
|
6193
6286
|
"",
|
|
6194
|
-
|
|
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.`,
|
|
6195
6288
|
"No markdown headings, no quotes, no fences, no text before \"Title:\" or after the last bullet.",
|
|
6196
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")}`] : [],
|
|
6197
6290
|
...preview.files.length > 0 ? [`Uncommitted files that will go into the same pull request: ${preview.files.map((file) => file.path).join(", ")}`] : [],
|
|
@@ -6638,19 +6731,19 @@ var RepositoryActionService = class {
|
|
|
6638
6731
|
};
|
|
6639
6732
|
//#endregion
|
|
6640
6733
|
//#region src/repository-setup-routes.ts
|
|
6641
|
-
const MAX_BODY_BYTES$
|
|
6642
|
-
function record$
|
|
6734
|
+
const MAX_BODY_BYTES$8 = 16384;
|
|
6735
|
+
function record$11(value) {
|
|
6643
6736
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6644
6737
|
}
|
|
6645
|
-
async function readJson$
|
|
6738
|
+
async function readJson$7(io) {
|
|
6646
6739
|
let parsed;
|
|
6647
6740
|
try {
|
|
6648
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
6741
|
+
parsed = await io.body(MAX_BODY_BYTES$8);
|
|
6649
6742
|
} catch (error) {
|
|
6650
6743
|
if (error instanceof SyntaxError) throw error;
|
|
6651
6744
|
throw new RepositorySetupError("body-too-large", "The request body is too large.");
|
|
6652
6745
|
}
|
|
6653
|
-
const value = record$
|
|
6746
|
+
const value = record$11(parsed);
|
|
6654
6747
|
if (value === void 0) throw new RepositorySetupError("invalid-request", "The request body is invalid.");
|
|
6655
6748
|
return value;
|
|
6656
6749
|
}
|
|
@@ -6719,7 +6812,7 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6719
6812
|
try {
|
|
6720
6813
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches/refresh`) {
|
|
6721
6814
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6722
|
-
const input = await readJson$
|
|
6815
|
+
const input = await readJson$7(io);
|
|
6723
6816
|
return json(res, 200, await service.refreshBranches(string$2(input, "cwd")));
|
|
6724
6817
|
}
|
|
6725
6818
|
if (pathname === `/plugins/dsh-claude/repository/setup/branches`) {
|
|
@@ -6730,14 +6823,14 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6730
6823
|
}
|
|
6731
6824
|
if (pathname === "/plugins/dsh-claude/repository/setup") {
|
|
6732
6825
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6733
|
-
const input = await readJson$
|
|
6826
|
+
const input = await readJson$7(io);
|
|
6734
6827
|
if (typeof input.worktree !== "boolean") throw new RepositorySetupError("invalid-request", "The worktree field is required.");
|
|
6735
6828
|
await streamSetup(res, service, input);
|
|
6736
6829
|
return;
|
|
6737
6830
|
}
|
|
6738
6831
|
if (pathname === `/plugins/dsh-claude/repository/setup/cleanup`) {
|
|
6739
6832
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6740
|
-
const input = await readJson$
|
|
6833
|
+
const input = await readJson$7(io);
|
|
6741
6834
|
const path = string$2(input, "path");
|
|
6742
6835
|
const branch = typeof input.branch === "string" && input.branch.length > 0 ? input.branch : void 0;
|
|
6743
6836
|
const baseBranch = typeof input.baseBranch === "string" && input.baseBranch.length > 0 ? input.baseBranch : void 0;
|
|
@@ -6752,7 +6845,7 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6752
6845
|
}
|
|
6753
6846
|
if (pathname === `/plugins/dsh-claude/repository/setup/bind`) {
|
|
6754
6847
|
if (io.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
6755
|
-
const input = await readJson$
|
|
6848
|
+
const input = await readJson$7(io);
|
|
6756
6849
|
await service.bindLease(string$2(input, "leaseId"), string$2(input, "sessionId"));
|
|
6757
6850
|
return json(res, 200, { ok: true });
|
|
6758
6851
|
}
|
|
@@ -6770,8 +6863,8 @@ function registerRepositorySetupRoute(ctx, service, sweep, cleaned) {
|
|
|
6770
6863
|
}
|
|
6771
6864
|
//#endregion
|
|
6772
6865
|
//#region src/repository-action-routes.ts
|
|
6773
|
-
const MAX_BODY_BYTES$
|
|
6774
|
-
const MAX_SESSION_ID_CHARS$
|
|
6866
|
+
const MAX_BODY_BYTES$7 = 16384;
|
|
6867
|
+
const MAX_SESSION_ID_CHARS$6 = 1024;
|
|
6775
6868
|
const MAX_ROOT_CHARS = 4096;
|
|
6776
6869
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
6777
6870
|
"commit",
|
|
@@ -6791,24 +6884,24 @@ const MESSAGELESS = /* @__PURE__ */ new Set([
|
|
|
6791
6884
|
"resolve-continue",
|
|
6792
6885
|
"resolve-abort"
|
|
6793
6886
|
]);
|
|
6794
|
-
function record$
|
|
6887
|
+
function record$10(value) {
|
|
6795
6888
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6796
6889
|
}
|
|
6797
|
-
async function readJson$
|
|
6890
|
+
async function readJson$6(io) {
|
|
6798
6891
|
let parsed;
|
|
6799
6892
|
try {
|
|
6800
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
6893
|
+
parsed = await io.body(MAX_BODY_BYTES$7);
|
|
6801
6894
|
} catch (error) {
|
|
6802
6895
|
if (error instanceof SyntaxError) throw error;
|
|
6803
6896
|
throw new RepositoryActionError("body-too-large", "The request body is too large.");
|
|
6804
6897
|
}
|
|
6805
|
-
const value = record$
|
|
6898
|
+
const value = record$10(parsed);
|
|
6806
6899
|
if (value === void 0) throw new RepositoryActionError("invalid-request", "The request body is invalid.");
|
|
6807
6900
|
return value;
|
|
6808
6901
|
}
|
|
6809
6902
|
function sessionId$1(url) {
|
|
6810
6903
|
const value = url.searchParams.get("sessionId");
|
|
6811
|
-
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.");
|
|
6812
6905
|
return value;
|
|
6813
6906
|
}
|
|
6814
6907
|
function requestedRoot(url) {
|
|
@@ -6885,7 +6978,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6885
6978
|
status: 405,
|
|
6886
6979
|
value: { error: "method not allowed" }
|
|
6887
6980
|
};
|
|
6888
|
-
const input = await readJson$
|
|
6981
|
+
const input = await readJson$6(io);
|
|
6889
6982
|
return {
|
|
6890
6983
|
status: 200,
|
|
6891
6984
|
value: { message: await service.generateMessage(cwd, string$1(input, "fingerprint")) }
|
|
@@ -6896,7 +6989,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6896
6989
|
status: 405,
|
|
6897
6990
|
value: { error: "method not allowed" }
|
|
6898
6991
|
};
|
|
6899
|
-
const input = await readJson$
|
|
6992
|
+
const input = await readJson$6(io);
|
|
6900
6993
|
return {
|
|
6901
6994
|
status: 200,
|
|
6902
6995
|
value: await service.generatePullRequest(cwd, string$1(input, "fingerprint"), optionalString(input, "baseBranch"))
|
|
@@ -6909,7 +7002,7 @@ function registerRepositoryActionRoute(ctx, service, cwdForSession) {
|
|
|
6909
7002
|
};
|
|
6910
7003
|
return {
|
|
6911
7004
|
status: 200,
|
|
6912
|
-
value: await service.execute(cwd, actionRequest(await readJson$
|
|
7005
|
+
value: await service.execute(cwd, actionRequest(await readJson$6(io)))
|
|
6913
7006
|
};
|
|
6914
7007
|
}
|
|
6915
7008
|
return {
|
|
@@ -7363,7 +7456,7 @@ async function collect(handle) {
|
|
|
7363
7456
|
lossy: stdout?.lossy === true
|
|
7364
7457
|
};
|
|
7365
7458
|
}
|
|
7366
|
-
function record$
|
|
7459
|
+
function record$9(value) {
|
|
7367
7460
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7368
7461
|
}
|
|
7369
7462
|
/** Read `repository.pullRequest.reviewThreads.nodes` out of a GraphQL response.
|
|
@@ -7371,12 +7464,12 @@ function record$8(value) {
|
|
|
7371
7464
|
* throwing: the caller distinguishes "no threads" from "call failed" by the
|
|
7372
7465
|
* process exit code. */
|
|
7373
7466
|
function parseReviewThreads(value) {
|
|
7374
|
-
const nodes = record$
|
|
7467
|
+
const nodes = record$9(record$9(record$9(record$9(record$9(value)?.data)?.repository)?.pullRequest)?.reviewThreads)?.nodes;
|
|
7375
7468
|
if (!Array.isArray(nodes)) return [];
|
|
7376
7469
|
const threads = [];
|
|
7377
7470
|
let total = 0;
|
|
7378
7471
|
for (const item of nodes) {
|
|
7379
|
-
const input = record$
|
|
7472
|
+
const input = record$9(item);
|
|
7380
7473
|
if (input === void 0 || typeof input.id !== "string" || input.id.length === 0) continue;
|
|
7381
7474
|
const path = typeof input.path === "string" ? input.path : "";
|
|
7382
7475
|
if (path.length === 0) continue;
|
|
@@ -7388,14 +7481,14 @@ function parseReviewThreads(value) {
|
|
|
7388
7481
|
side
|
|
7389
7482
|
};
|
|
7390
7483
|
const comments = [];
|
|
7391
|
-
const commentNodes = record$
|
|
7484
|
+
const commentNodes = record$9(input.comments)?.nodes;
|
|
7392
7485
|
for (const node of Array.isArray(commentNodes) ? commentNodes : []) {
|
|
7393
7486
|
if (total >= MAX_COMMENTS) break;
|
|
7394
|
-
const comment = record$
|
|
7487
|
+
const comment = record$9(node);
|
|
7395
7488
|
if (comment === void 0 || !Number.isSafeInteger(comment.databaseId)) continue;
|
|
7396
7489
|
const body = typeof comment.body === "string" ? comment.body.trim() : "";
|
|
7397
7490
|
if (body.length === 0) continue;
|
|
7398
|
-
const author = record$
|
|
7491
|
+
const author = record$9(comment.author);
|
|
7399
7492
|
const avatarUrl = githubAvatarUrl(author?.avatarUrl);
|
|
7400
7493
|
const login = typeof author?.login === "string" ? author.login : "unknown";
|
|
7401
7494
|
comments.push({
|
|
@@ -7424,11 +7517,11 @@ function parseReviewThreads(value) {
|
|
|
7424
7517
|
}
|
|
7425
7518
|
/** One posted reply, shaped like the thread comments it joins. */
|
|
7426
7519
|
function parseReplyComment(value, anchor) {
|
|
7427
|
-
const input = record$
|
|
7520
|
+
const input = record$9(value);
|
|
7428
7521
|
if (input === void 0 || !Number.isSafeInteger(input.id)) return void 0;
|
|
7429
7522
|
const body = typeof input.body === "string" ? input.body.trim() : "";
|
|
7430
7523
|
if (body.length === 0) return void 0;
|
|
7431
|
-
const user = record$
|
|
7524
|
+
const user = record$9(input.user);
|
|
7432
7525
|
const avatarUrl = githubAvatarUrl(user?.avatar_url);
|
|
7433
7526
|
const login = typeof user?.login === "string" ? user.login : "unknown";
|
|
7434
7527
|
return {
|
|
@@ -7443,11 +7536,11 @@ function parseReplyComment(value, anchor) {
|
|
|
7443
7536
|
};
|
|
7444
7537
|
}
|
|
7445
7538
|
function parseMentionableUsers(value) {
|
|
7446
|
-
const nodes = record$
|
|
7539
|
+
const nodes = record$9(record$9(record$9(record$9(value)?.data)?.repository)?.mentionableUsers)?.nodes;
|
|
7447
7540
|
if (!Array.isArray(nodes)) return [];
|
|
7448
7541
|
const users = [];
|
|
7449
7542
|
for (const item of nodes) {
|
|
7450
|
-
const input = record$
|
|
7543
|
+
const input = record$9(item);
|
|
7451
7544
|
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
7452
7545
|
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
7453
7546
|
users.push({
|
|
@@ -7467,7 +7560,7 @@ function parseFailingChecks(value) {
|
|
|
7467
7560
|
if (!Array.isArray(value)) return [];
|
|
7468
7561
|
const failing = [];
|
|
7469
7562
|
for (const item of value) {
|
|
7470
|
-
const input = record$
|
|
7563
|
+
const input = record$9(item);
|
|
7471
7564
|
if (input === void 0 || input.bucket !== "fail" || typeof input.name !== "string") continue;
|
|
7472
7565
|
failing.push({
|
|
7473
7566
|
name: input.name,
|
|
@@ -7530,12 +7623,12 @@ var PullRequestFeedbackService = class {
|
|
|
7530
7623
|
let posted;
|
|
7531
7624
|
try {
|
|
7532
7625
|
const parsed = JSON.parse(result.stdout);
|
|
7533
|
-
const path = typeof record$
|
|
7534
|
-
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;
|
|
7535
7628
|
posted = parseReplyComment(parsed, {
|
|
7536
7629
|
path,
|
|
7537
7630
|
...line === void 0 ? {} : { line },
|
|
7538
|
-
side: record$
|
|
7631
|
+
side: record$9(parsed)?.side === "LEFT" ? "old" : "new"
|
|
7539
7632
|
});
|
|
7540
7633
|
} catch {
|
|
7541
7634
|
posted = void 0;
|
|
@@ -7556,7 +7649,7 @@ var PullRequestFeedbackService = class {
|
|
|
7556
7649
|
], cwd, GH_TIMEOUT_MS);
|
|
7557
7650
|
if (result.exitCode !== 0) throw new PullRequestFeedbackError("resolve-failed", "The thread could not be updated.");
|
|
7558
7651
|
try {
|
|
7559
|
-
const thread = record$
|
|
7652
|
+
const thread = record$9(record$9(record$9(record$9(JSON.parse(result.stdout))?.data)?.[resolved ? "resolveReviewThread" : "unresolveReviewThread"])?.thread);
|
|
7560
7653
|
if (typeof thread?.isResolved !== "boolean") throw new Error("missing state");
|
|
7561
7654
|
return thread.isResolved;
|
|
7562
7655
|
} catch {
|
|
@@ -7679,23 +7772,23 @@ var PullRequestFeedbackService = class {
|
|
|
7679
7772
|
};
|
|
7680
7773
|
//#endregion
|
|
7681
7774
|
//#region src/pr-feedback-routes.ts
|
|
7682
|
-
const MAX_SESSION_ID_CHARS$
|
|
7683
|
-
const MAX_BODY_BYTES$
|
|
7775
|
+
const MAX_SESSION_ID_CHARS$5 = 1024;
|
|
7776
|
+
const MAX_BODY_BYTES$6 = 16384;
|
|
7684
7777
|
const MAX_REPLY_CHARS = 2e3;
|
|
7685
7778
|
const MAX_THREAD_ID_CHARS = 512;
|
|
7686
7779
|
const MAX_MENTION_QUERY_CHARS = 64;
|
|
7687
|
-
function record$
|
|
7780
|
+
function record$8(value) {
|
|
7688
7781
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7689
7782
|
}
|
|
7690
|
-
async function readJson$
|
|
7783
|
+
async function readJson$5(io) {
|
|
7691
7784
|
let parsed;
|
|
7692
7785
|
try {
|
|
7693
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
7786
|
+
parsed = await io.body(MAX_BODY_BYTES$6);
|
|
7694
7787
|
} catch (error) {
|
|
7695
7788
|
if (error instanceof SyntaxError) throw error;
|
|
7696
7789
|
throw new PullRequestFeedbackError("body-too-large", "The request body is too large.");
|
|
7697
7790
|
}
|
|
7698
|
-
const value = record$
|
|
7791
|
+
const value = record$8(parsed);
|
|
7699
7792
|
if (value === void 0) throw new PullRequestFeedbackError("invalid-request", "The request body is invalid.");
|
|
7700
7793
|
return value;
|
|
7701
7794
|
}
|
|
@@ -7716,7 +7809,7 @@ function threadId(input) {
|
|
|
7716
7809
|
}
|
|
7717
7810
|
function sessionId(url) {
|
|
7718
7811
|
const value = url.searchParams.get("sessionId");
|
|
7719
|
-
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.");
|
|
7720
7813
|
return value;
|
|
7721
7814
|
}
|
|
7722
7815
|
function pullNumber(url) {
|
|
@@ -7775,7 +7868,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
7775
7868
|
status: 405,
|
|
7776
7869
|
value: { error: "method not allowed" }
|
|
7777
7870
|
};
|
|
7778
|
-
const input = await readJson$
|
|
7871
|
+
const input = await readJson$5(io);
|
|
7779
7872
|
return {
|
|
7780
7873
|
status: 200,
|
|
7781
7874
|
value: { comment: await service.reply(cwd, pullNumber(url), commentId(input), replyBody(input)) }
|
|
@@ -7786,7 +7879,7 @@ function registerPullRequestFeedbackRoute(ctx, service, cwdForSession) {
|
|
|
7786
7879
|
status: 405,
|
|
7787
7880
|
value: { error: "method not allowed" }
|
|
7788
7881
|
};
|
|
7789
|
-
const input = await readJson$
|
|
7882
|
+
const input = await readJson$5(io);
|
|
7790
7883
|
if (typeof input.resolved !== "boolean") throw new PullRequestFeedbackError("invalid-request", "The resolved field is required.");
|
|
7791
7884
|
return {
|
|
7792
7885
|
status: 200,
|
|
@@ -7932,7 +8025,7 @@ var JiraError = class extends Error {
|
|
|
7932
8025
|
this.code = code;
|
|
7933
8026
|
}
|
|
7934
8027
|
};
|
|
7935
|
-
function record$
|
|
8028
|
+
function record$7(value) {
|
|
7936
8029
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7937
8030
|
}
|
|
7938
8031
|
/** Jira Cloud sites are origins; Data Center may carry a context path. */
|
|
@@ -7956,14 +8049,14 @@ function ticketKeyOf(query) {
|
|
|
7956
8049
|
* search and does match keys; its hits become the key filter the normal
|
|
7957
8050
|
* search then reads the display fields from. */
|
|
7958
8051
|
function pickerKeys(value, number) {
|
|
7959
|
-
const sections = record$
|
|
8052
|
+
const sections = record$7(value)?.sections;
|
|
7960
8053
|
if (!Array.isArray(sections)) return [];
|
|
7961
8054
|
const keys = [];
|
|
7962
8055
|
for (const section of sections) {
|
|
7963
|
-
const issues = record$
|
|
8056
|
+
const issues = record$7(section)?.issues;
|
|
7964
8057
|
if (!Array.isArray(issues)) continue;
|
|
7965
8058
|
for (const issue of issues) {
|
|
7966
|
-
const raw = record$
|
|
8059
|
+
const raw = record$7(issue)?.key;
|
|
7967
8060
|
const key = ticketKeyOf(typeof raw === "string" ? raw : "");
|
|
7968
8061
|
if (key !== void 0 && key.endsWith(`-${number}`) && !keys.includes(key)) keys.push(key);
|
|
7969
8062
|
}
|
|
@@ -7982,15 +8075,15 @@ function buildJql(query) {
|
|
|
7982
8075
|
return `text ~ "${trimmed.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}*" ORDER BY updated DESC`;
|
|
7983
8076
|
}
|
|
7984
8077
|
function parseTickets(value, siteUrl) {
|
|
7985
|
-
const issues = record$
|
|
8078
|
+
const issues = record$7(value)?.issues;
|
|
7986
8079
|
if (!Array.isArray(issues)) return [];
|
|
7987
8080
|
const tickets = [];
|
|
7988
8081
|
for (const item of issues) {
|
|
7989
|
-
const issue = record$
|
|
7990
|
-
const fields = record$
|
|
8082
|
+
const issue = record$7(item);
|
|
8083
|
+
const fields = record$7(issue?.fields);
|
|
7991
8084
|
if (issue === void 0 || typeof issue.key !== "string" || fields === void 0) continue;
|
|
7992
|
-
const status = record$
|
|
7993
|
-
const type = record$
|
|
8085
|
+
const status = record$7(fields.status)?.name;
|
|
8086
|
+
const type = record$7(fields.issuetype)?.name;
|
|
7994
8087
|
tickets.push({
|
|
7995
8088
|
key: issue.key,
|
|
7996
8089
|
summary: typeof fields.summary === "string" ? fields.summary.slice(0, 256) : "",
|
|
@@ -8032,7 +8125,7 @@ var JiraService = class {
|
|
|
8032
8125
|
email,
|
|
8033
8126
|
apiToken
|
|
8034
8127
|
};
|
|
8035
|
-
const myself = record$
|
|
8128
|
+
const myself = record$7(await this.#json(connection, "/rest/api/3/myself"));
|
|
8036
8129
|
const displayName = typeof myself?.displayName === "string" ? myself.displayName : void 0;
|
|
8037
8130
|
const accountId = typeof myself?.accountId === "string" ? myself.accountId : void 0;
|
|
8038
8131
|
const store = {
|
|
@@ -8051,7 +8144,7 @@ var JiraService = class {
|
|
|
8051
8144
|
if (ticket === void 0) throw new JiraError("invalid-request", "The ticket key is invalid.");
|
|
8052
8145
|
let accountId = store.accountId;
|
|
8053
8146
|
if (accountId === void 0) {
|
|
8054
|
-
const myself = record$
|
|
8147
|
+
const myself = record$7(await this.#json(store, "/rest/api/3/myself"));
|
|
8055
8148
|
if (typeof myself?.accountId !== "string") throw new JiraError("jira-failed", "The Jira account id is unavailable.");
|
|
8056
8149
|
accountId = myself.accountId;
|
|
8057
8150
|
await this.#write({
|
|
@@ -8127,7 +8220,7 @@ var JiraService = class {
|
|
|
8127
8220
|
throw error;
|
|
8128
8221
|
}
|
|
8129
8222
|
if (Buffer.byteLength(text) > MAX_STORE_BYTES) return void 0;
|
|
8130
|
-
const input = record$
|
|
8223
|
+
const input = record$7(JSON.parse(text));
|
|
8131
8224
|
if (input === void 0 || typeof input.siteUrl !== "string" || typeof input.email !== "string" || typeof input.apiToken !== "string") return void 0;
|
|
8132
8225
|
return {
|
|
8133
8226
|
siteUrl: input.siteUrl,
|
|
@@ -8158,21 +8251,21 @@ var JiraService = class {
|
|
|
8158
8251
|
};
|
|
8159
8252
|
//#endregion
|
|
8160
8253
|
//#region src/jira-routes.ts
|
|
8161
|
-
const MAX_BODY_BYTES$
|
|
8162
|
-
function record$
|
|
8254
|
+
const MAX_BODY_BYTES$5 = 8192;
|
|
8255
|
+
function record$6(value) {
|
|
8163
8256
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8164
8257
|
}
|
|
8165
8258
|
/** The wrapper enforces the byte cap; its plain rejection is translated back
|
|
8166
8259
|
* into the JiraError shape the panel already knows how to render. */
|
|
8167
|
-
async function readJson$
|
|
8260
|
+
async function readJson$4(io) {
|
|
8168
8261
|
let body;
|
|
8169
8262
|
try {
|
|
8170
|
-
body = await io.body(MAX_BODY_BYTES$
|
|
8263
|
+
body = await io.body(MAX_BODY_BYTES$5);
|
|
8171
8264
|
} catch (error) {
|
|
8172
8265
|
if (error instanceof SyntaxError) throw error;
|
|
8173
8266
|
throw new JiraError("body-too-large", "The request body is too large.");
|
|
8174
8267
|
}
|
|
8175
|
-
const value = record$
|
|
8268
|
+
const value = record$6(body);
|
|
8176
8269
|
if (value === void 0) throw new JiraError("invalid-request", "The request body is invalid.");
|
|
8177
8270
|
return value;
|
|
8178
8271
|
}
|
|
@@ -8206,7 +8299,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
8206
8299
|
status: 405,
|
|
8207
8300
|
value: { error: "method not allowed" }
|
|
8208
8301
|
};
|
|
8209
|
-
const input = await readJson$
|
|
8302
|
+
const input = await readJson$4(io);
|
|
8210
8303
|
return {
|
|
8211
8304
|
status: 200,
|
|
8212
8305
|
value: await service.connect({
|
|
@@ -8232,7 +8325,7 @@ function registerJiraRoute(ctx, service) {
|
|
|
8232
8325
|
status: 405,
|
|
8233
8326
|
value: { error: "method not allowed" }
|
|
8234
8327
|
};
|
|
8235
|
-
const input = await readJson$
|
|
8328
|
+
const input = await readJson$4(io);
|
|
8236
8329
|
await service.assignToMe(string(input, "key"));
|
|
8237
8330
|
return {
|
|
8238
8331
|
status: 200,
|
|
@@ -8351,12 +8444,12 @@ function askArguments(preferences) {
|
|
|
8351
8444
|
...READ_ONLY_TOOLS
|
|
8352
8445
|
];
|
|
8353
8446
|
}
|
|
8354
|
-
function record$
|
|
8447
|
+
function record$5(value) {
|
|
8355
8448
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8356
8449
|
}
|
|
8357
8450
|
/** One-line description of a tool call, mirroring the main window's step titles. */
|
|
8358
8451
|
function toolSummary(input) {
|
|
8359
|
-
const fields = record$
|
|
8452
|
+
const fields = record$5(input);
|
|
8360
8453
|
if (fields === void 0) return void 0;
|
|
8361
8454
|
const candidate = [
|
|
8362
8455
|
fields.command,
|
|
@@ -8370,7 +8463,7 @@ function toolSummary(input) {
|
|
|
8370
8463
|
function eventsOfStreamLine(line) {
|
|
8371
8464
|
let parsed;
|
|
8372
8465
|
try {
|
|
8373
|
-
parsed = record$
|
|
8466
|
+
parsed = record$5(JSON.parse(line));
|
|
8374
8467
|
} catch {
|
|
8375
8468
|
return [];
|
|
8376
8469
|
}
|
|
@@ -8380,9 +8473,9 @@ function eventsOfStreamLine(line) {
|
|
|
8380
8473
|
text: "ready"
|
|
8381
8474
|
}];
|
|
8382
8475
|
if (parsed.type === "stream_event") {
|
|
8383
|
-
const event = record$
|
|
8476
|
+
const event = record$5(parsed.event);
|
|
8384
8477
|
if (event?.type === "content_block_start") {
|
|
8385
|
-
const block = record$
|
|
8478
|
+
const block = record$5(event.content_block);
|
|
8386
8479
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") return [{
|
|
8387
8480
|
type: "tool",
|
|
8388
8481
|
id: block.id,
|
|
@@ -8391,7 +8484,7 @@ function eventsOfStreamLine(line) {
|
|
|
8391
8484
|
}];
|
|
8392
8485
|
return [];
|
|
8393
8486
|
}
|
|
8394
|
-
const delta = record$
|
|
8487
|
+
const delta = record$5(event?.delta);
|
|
8395
8488
|
if (event?.type !== "content_block_delta" || delta === void 0) return [];
|
|
8396
8489
|
if (delta.type === "text_delta" && typeof delta.text === "string") return [{
|
|
8397
8490
|
type: "text",
|
|
@@ -8404,11 +8497,11 @@ function eventsOfStreamLine(line) {
|
|
|
8404
8497
|
return [];
|
|
8405
8498
|
}
|
|
8406
8499
|
if (parsed.type === "assistant" || parsed.type === "user") {
|
|
8407
|
-
const content = record$
|
|
8500
|
+
const content = record$5(parsed.message)?.content;
|
|
8408
8501
|
if (!Array.isArray(content)) return [];
|
|
8409
8502
|
const events = [];
|
|
8410
8503
|
for (const item of content) {
|
|
8411
|
-
const block = record$
|
|
8504
|
+
const block = record$5(item);
|
|
8412
8505
|
if (block?.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
|
|
8413
8506
|
const summary = toolSummary(block.input);
|
|
8414
8507
|
events.push({
|
|
@@ -8500,11 +8593,11 @@ var AskService = class {
|
|
|
8500
8593
|
};
|
|
8501
8594
|
//#endregion
|
|
8502
8595
|
//#region src/ask-routes.ts
|
|
8503
|
-
const MAX_BODY_BYTES$
|
|
8504
|
-
const MAX_SESSION_ID_CHARS$
|
|
8596
|
+
const MAX_BODY_BYTES$4 = 131072;
|
|
8597
|
+
const MAX_SESSION_ID_CHARS$4 = 1024;
|
|
8505
8598
|
/** Two sessions may await an answer at once; a third evicts the oldest. */
|
|
8506
8599
|
const MAX_CONCURRENT_ASKS = 2;
|
|
8507
|
-
function record$
|
|
8600
|
+
function record$4(value) {
|
|
8508
8601
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8509
8602
|
}
|
|
8510
8603
|
function askRequest(input) {
|
|
@@ -8536,12 +8629,12 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
8536
8629
|
let sessionId;
|
|
8537
8630
|
try {
|
|
8538
8631
|
const value = io.url.searchParams.get("sessionId");
|
|
8539
|
-
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.");
|
|
8540
8633
|
sessionId = value;
|
|
8541
8634
|
const resolved = cwdForSession(sessionId);
|
|
8542
8635
|
if (resolved === void 0) throw new AskError("session-unavailable", "The Claude session is unavailable.");
|
|
8543
8636
|
cwd = resolved;
|
|
8544
|
-
const body = record$
|
|
8637
|
+
const body = record$4(await io.body(MAX_BODY_BYTES$4));
|
|
8545
8638
|
if (body === void 0) throw new AskError("invalid-request", "The request body is invalid.");
|
|
8546
8639
|
request = askRequest(body);
|
|
8547
8640
|
} catch (error) {
|
|
@@ -8581,26 +8674,26 @@ function registerAskRoute(ctx, service, cwdForSession, preferencesFor) {
|
|
|
8581
8674
|
}
|
|
8582
8675
|
//#endregion
|
|
8583
8676
|
//#region src/review-comment-routes.ts
|
|
8584
|
-
const MAX_BODY_BYTES$
|
|
8585
|
-
const MAX_SESSION_ID_CHARS$
|
|
8586
|
-
function record$
|
|
8677
|
+
const MAX_BODY_BYTES$3 = 16384;
|
|
8678
|
+
const MAX_SESSION_ID_CHARS$3 = 1024;
|
|
8679
|
+
function record$3(value) {
|
|
8587
8680
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8588
8681
|
}
|
|
8589
|
-
async function readJson$
|
|
8682
|
+
async function readJson$3(io) {
|
|
8590
8683
|
let parsed;
|
|
8591
8684
|
try {
|
|
8592
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
8685
|
+
parsed = await io.body(MAX_BODY_BYTES$3);
|
|
8593
8686
|
} catch (error) {
|
|
8594
8687
|
if (error instanceof SyntaxError) throw error;
|
|
8595
8688
|
throw new ReviewCommentError("body-too-large", "The request body is too large.");
|
|
8596
8689
|
}
|
|
8597
|
-
const value = record$
|
|
8690
|
+
const value = record$3(parsed);
|
|
8598
8691
|
if (value === void 0) throw new ReviewCommentError("invalid-request", "The request body is invalid.");
|
|
8599
8692
|
return value;
|
|
8600
8693
|
}
|
|
8601
8694
|
function sessionIdFromUrl(url) {
|
|
8602
8695
|
const value = url.searchParams.get("sessionId");
|
|
8603
|
-
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.");
|
|
8604
8697
|
return value;
|
|
8605
8698
|
}
|
|
8606
8699
|
function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
@@ -8616,7 +8709,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
8616
8709
|
if (!ownsSession(sessionId)) throw new ReviewCommentError("session-unavailable", "The Claude session is unavailable.");
|
|
8617
8710
|
const pathname = io.url.pathname;
|
|
8618
8711
|
if (pathname === "/plugins/dsh-claude/review-comments") {
|
|
8619
|
-
const input = await readJson$
|
|
8712
|
+
const input = await readJson$3(io);
|
|
8620
8713
|
return {
|
|
8621
8714
|
status: 200,
|
|
8622
8715
|
value: { comment: store.add(sessionId, {
|
|
@@ -8633,7 +8726,7 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
8633
8726
|
value: { removed: store.drain(sessionId).length }
|
|
8634
8727
|
};
|
|
8635
8728
|
if (pathname === `/plugins/dsh-claude/review-comments/remove`) {
|
|
8636
|
-
const input = await readJson$
|
|
8729
|
+
const input = await readJson$3(io);
|
|
8637
8730
|
if (typeof input.id !== "string" || input.id.length === 0 || input.id.length > 128) throw new ReviewCommentError("invalid-request", "The comment id is invalid.");
|
|
8638
8731
|
return {
|
|
8639
8732
|
status: 200,
|
|
@@ -8669,21 +8762,21 @@ function registerReviewCommentRoute(ctx, store, ownsSession) {
|
|
|
8669
8762
|
}
|
|
8670
8763
|
//#endregion
|
|
8671
8764
|
//#region src/plan-feedback-routes.ts
|
|
8672
|
-
const MAX_BODY_BYTES$
|
|
8673
|
-
const MAX_SESSION_ID_CHARS$
|
|
8765
|
+
const MAX_BODY_BYTES$2 = 65536;
|
|
8766
|
+
const MAX_SESSION_ID_CHARS$2 = 1024;
|
|
8674
8767
|
const MAX_TOOL_USE_ID_CHARS = 256;
|
|
8675
|
-
function record$
|
|
8768
|
+
function record$2(value) {
|
|
8676
8769
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8677
8770
|
}
|
|
8678
|
-
async function readJson$
|
|
8771
|
+
async function readJson$2(io) {
|
|
8679
8772
|
let parsed;
|
|
8680
8773
|
try {
|
|
8681
|
-
parsed = await io.body(MAX_BODY_BYTES$
|
|
8774
|
+
parsed = await io.body(MAX_BODY_BYTES$2);
|
|
8682
8775
|
} catch (error) {
|
|
8683
8776
|
if (error instanceof SyntaxError) throw error;
|
|
8684
8777
|
throw new PlanFeedbackError("body-too-large", "The request body is too large.");
|
|
8685
8778
|
}
|
|
8686
|
-
const value = record$
|
|
8779
|
+
const value = record$2(parsed);
|
|
8687
8780
|
if (value === void 0) throw new PlanFeedbackError("invalid-request", "The request body is invalid.");
|
|
8688
8781
|
return value;
|
|
8689
8782
|
}
|
|
@@ -8703,7 +8796,7 @@ function registerPlanFeedbackRoute(ctx, gate, ownsSession) {
|
|
|
8703
8796
|
handler: async (io) => {
|
|
8704
8797
|
try {
|
|
8705
8798
|
const sessionId = io.url.searchParams.get("sessionId");
|
|
8706
|
-
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 {
|
|
8707
8800
|
status: 400,
|
|
8708
8801
|
value: { error: "invalid-session" }
|
|
8709
8802
|
};
|
|
@@ -8711,7 +8804,7 @@ function registerPlanFeedbackRoute(ctx, gate, ownsSession) {
|
|
|
8711
8804
|
status: 409,
|
|
8712
8805
|
value: { error: "session-unavailable" }
|
|
8713
8806
|
};
|
|
8714
|
-
const body = await readJson$
|
|
8807
|
+
const body = await readJson$2(io);
|
|
8715
8808
|
const toolUseId = body.toolUseId;
|
|
8716
8809
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || toolUseId.length > MAX_TOOL_USE_ID_CHARS) return {
|
|
8717
8810
|
status: 400,
|
|
@@ -8796,16 +8889,16 @@ function registerClaudeClientDiagnosticsRoute(ctx) {
|
|
|
8796
8889
|
}
|
|
8797
8890
|
//#endregion
|
|
8798
8891
|
//#region src/rewind-routes.ts
|
|
8799
|
-
const MAX_BODY_BYTES = 4096;
|
|
8800
|
-
const MAX_SESSION_ID_CHARS = 1024;
|
|
8801
|
-
function record(value) {
|
|
8892
|
+
const MAX_BODY_BYTES$1 = 4096;
|
|
8893
|
+
const MAX_SESSION_ID_CHARS$1 = 1024;
|
|
8894
|
+
function record$1(value) {
|
|
8802
8895
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
8803
8896
|
}
|
|
8804
8897
|
/** An oversized body fails the same field checks as a missing one; only
|
|
8805
8898
|
* malformed JSON is worth reporting separately. */
|
|
8806
|
-
async function readJson(io) {
|
|
8899
|
+
async function readJson$1(io) {
|
|
8807
8900
|
try {
|
|
8808
|
-
return record(await io.body(MAX_BODY_BYTES));
|
|
8901
|
+
return record$1(await io.body(MAX_BODY_BYTES$1));
|
|
8809
8902
|
} catch (error) {
|
|
8810
8903
|
if (error instanceof SyntaxError) throw error;
|
|
8811
8904
|
return;
|
|
@@ -8829,10 +8922,10 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
|
8829
8922
|
budget: "git",
|
|
8830
8923
|
handler: async (io) => {
|
|
8831
8924
|
try {
|
|
8832
|
-
const input = await readJson(io);
|
|
8925
|
+
const input = await readJson$1(io);
|
|
8833
8926
|
const sessionId = input?.sessionId;
|
|
8834
8927
|
const seq = input?.seq;
|
|
8835
|
-
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 {
|
|
8836
8929
|
status: 400,
|
|
8837
8930
|
value: { error: "invalid-request" }
|
|
8838
8931
|
};
|
|
@@ -8876,6 +8969,100 @@ function registerClaudeRewindRoute(ctx, sidecar, access) {
|
|
|
8876
8969
|
});
|
|
8877
8970
|
}
|
|
8878
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
|
|
8879
9066
|
//#region src/touched-repositories.ts
|
|
8880
9067
|
/** Which other repositories a session has written into.
|
|
8881
9068
|
*
|
|
@@ -9569,6 +9756,65 @@ const WORKTREE_BRANCH_PREFIX = {
|
|
|
9569
9756
|
else document.worktreeBranchPrefix = value;
|
|
9570
9757
|
}
|
|
9571
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
|
+
};
|
|
9572
9818
|
/** Which renderer draws Claude's visible output. Plugin settings, not Claude's:
|
|
9573
9819
|
* the CLI has no opinion about how DSH paints a turn. The option labels stay
|
|
9574
9820
|
* machine-readable ids; the Client translates the two known values. */
|
|
@@ -9668,6 +9914,8 @@ function integerSetting(key, min, max, defaultFor, effect = "new-session") {
|
|
|
9668
9914
|
}
|
|
9669
9915
|
const DESCRIPTORS = [
|
|
9670
9916
|
OUTPUT_STYLE,
|
|
9917
|
+
PERMISSION_SELECTOR,
|
|
9918
|
+
PERMISSION_MODE,
|
|
9671
9919
|
RENDERER,
|
|
9672
9920
|
PROSE,
|
|
9673
9921
|
ALERTS,
|
|
@@ -9743,6 +9991,27 @@ async function readRenderMode(deps = {}) {
|
|
|
9743
9991
|
}
|
|
9744
9992
|
return isClaudeRenderMode(document.renderer) ? document.renderer : DEFAULT_CLAUDE_RENDER_MODE;
|
|
9745
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
|
+
}
|
|
9746
10015
|
async function readWorktreeBranchPrefix(deps = {}) {
|
|
9747
10016
|
const paths = pathsFor(deps);
|
|
9748
10017
|
return WORKTREE_BRANCH_PREFIX.read(await readDocument(paths.pluginSettingsFile));
|
|
@@ -9966,6 +10235,7 @@ async function apply(ctx, config) {
|
|
|
9966
10235
|
supervisorConfig.maxProcesses = overrides.maxProcesses ?? defaultLimits.maxProcesses;
|
|
9967
10236
|
};
|
|
9968
10237
|
await applySettingsOverrides();
|
|
10238
|
+
let permissionSelector = await readPermissionSelector();
|
|
9969
10239
|
const sidecar = new ClaudeSidecarRepository();
|
|
9970
10240
|
const repositoryStatus = new RepositoryStatusService(subprocess);
|
|
9971
10241
|
const repositorySetup = new RepositorySetupService(subprocess, {
|
|
@@ -9974,13 +10244,22 @@ async function apply(ctx, config) {
|
|
|
9974
10244
|
});
|
|
9975
10245
|
const reviewComments = new ReviewCommentStore();
|
|
9976
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
|
+
};
|
|
9977
10254
|
const supervisor = new ClaudeSupervisor({
|
|
9978
10255
|
runtime: subprocess,
|
|
9979
10256
|
approval: ctx.approval,
|
|
9980
10257
|
userQuestions: ctx.userQuestions,
|
|
9981
10258
|
config: supervisorConfig,
|
|
9982
10259
|
runDetached: (operation) => ctx.agents.withoutInitiator(operation),
|
|
9983
|
-
sidecar
|
|
10260
|
+
sidecar,
|
|
10261
|
+
defaultPermissionMode: () => readDefaultPermissionMode(),
|
|
10262
|
+
permissionSelector: async () => permissionSelector
|
|
9984
10263
|
});
|
|
9985
10264
|
let resolutionError;
|
|
9986
10265
|
try {
|
|
@@ -9991,9 +10270,24 @@ async function apply(ctx, config) {
|
|
|
9991
10270
|
const pending = /* @__PURE__ */ new Set();
|
|
9992
10271
|
const MOUNT_RETRY_MS = 200;
|
|
9993
10272
|
const MOUNT_RETRY_LIMIT = 50;
|
|
9994
|
-
|
|
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) => {
|
|
9995
10288
|
if (mounted.has(agent)) return;
|
|
9996
10289
|
const sessionId = agent.id;
|
|
10290
|
+
if (fresh) align(agent);
|
|
9997
10291
|
const dispose = mountClaudeMetadata(ctx, supervisor, agent, supervisorConfig.defaultModel, sidecar, (commands) => {
|
|
9998
10292
|
if (commands.length === 0) commandCatalogs.delete(sessionId);
|
|
9999
10293
|
else commandCatalogs.set(sessionId, commands);
|
|
@@ -10001,10 +10295,10 @@ async function apply(ctx, config) {
|
|
|
10001
10295
|
if (dispose !== void 0) mounted.set(agent, dispose);
|
|
10002
10296
|
pending.delete(agent);
|
|
10003
10297
|
};
|
|
10004
|
-
const mountWhenPresetSettles = (agent) => {
|
|
10298
|
+
const mountWhenPresetSettles = (agent, fresh) => {
|
|
10005
10299
|
if (mounted.has(agent) || pending.has(agent)) return;
|
|
10006
10300
|
if (ctx.agentPresets.composedPreset(agent.ctx) !== void 0) {
|
|
10007
|
-
mount(agent);
|
|
10301
|
+
mount(agent, fresh);
|
|
10008
10302
|
return;
|
|
10009
10303
|
}
|
|
10010
10304
|
pending.add(agent);
|
|
@@ -10012,7 +10306,7 @@ async function apply(ctx, config) {
|
|
|
10012
10306
|
const retry = () => {
|
|
10013
10307
|
if (mounted.has(agent) || !pending.has(agent)) return;
|
|
10014
10308
|
if (ctx.agentPresets.composedPreset(agent.ctx) !== void 0) {
|
|
10015
|
-
mount(agent);
|
|
10309
|
+
mount(agent, fresh);
|
|
10016
10310
|
return;
|
|
10017
10311
|
}
|
|
10018
10312
|
attempts += 1;
|
|
@@ -10025,15 +10319,15 @@ async function apply(ctx, config) {
|
|
|
10025
10319
|
setTimeout(retry, MOUNT_RETRY_MS).unref?.();
|
|
10026
10320
|
};
|
|
10027
10321
|
const stopCreated = ctx.on("agent/created", ({ agent }) => {
|
|
10028
|
-
mountWhenPresetSettles(agent);
|
|
10322
|
+
mountWhenPresetSettles(agent, true);
|
|
10029
10323
|
});
|
|
10030
10324
|
const onPresetSelected = ctx.on;
|
|
10031
10325
|
const stopSelected = onPresetSelected("agent-preset/selected", (sessionId, preset) => {
|
|
10032
10326
|
if (preset !== "claude") return;
|
|
10033
10327
|
const agent = ctx.agents.get(sessionId);
|
|
10034
|
-
if (agent !== void 0) mountWhenPresetSettles(agent);
|
|
10328
|
+
if (agent !== void 0) mountWhenPresetSettles(agent, true);
|
|
10035
10329
|
});
|
|
10036
|
-
for (const agent of ctx.agents.list()) mountWhenPresetSettles(agent);
|
|
10330
|
+
for (const agent of ctx.agents.list()) mountWhenPresetSettles(agent, false);
|
|
10037
10331
|
return async () => {
|
|
10038
10332
|
stopCreated();
|
|
10039
10333
|
stopSelected();
|
|
@@ -10091,6 +10385,7 @@ async function apply(ctx, config) {
|
|
|
10091
10385
|
defaultLimits,
|
|
10092
10386
|
onUpdated: async () => {
|
|
10093
10387
|
await applySettingsOverrides();
|
|
10388
|
+
permissionSelector = await readPermissionSelector();
|
|
10094
10389
|
supervisor.limitsChanged();
|
|
10095
10390
|
}
|
|
10096
10391
|
});
|
|
@@ -10185,12 +10480,34 @@ async function apply(ctx, config) {
|
|
|
10185
10480
|
}
|
|
10186
10481
|
});
|
|
10187
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
|
+
};
|
|
10188
10505
|
registerClaudeProjectionRoute(webCtx, sidecar, ownsClaudeSession, (sessionId) => commandCatalogs.get(sessionId) ?? [], async (sessionId) => {
|
|
10189
10506
|
const agent = webCtx.agents.get(sessionId);
|
|
10190
10507
|
if (agent === void 0 || webCtx.agentPresets.composedPreset(agent.ctx) !== "claude") return void 0;
|
|
10191
10508
|
const cwd = agent.session.header.cwd;
|
|
10192
10509
|
return cwd === void 0 ? void 0 : repositoryStatus.inspect(cwd);
|
|
10193
|
-
}, (sessionId) => reviewComments.list(sessionId), extraRepositoriesForClaudeSession);
|
|
10510
|
+
}, (sessionId) => reviewComments.list(sessionId), extraRepositoriesForClaudeSession, permissionModeForClaudeSession, () => permissionSelector);
|
|
10194
10511
|
});
|
|
10195
10512
|
}
|
|
10196
10513
|
//#endregion
|