@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/lib/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { A as CLAUDE_REPOSITORY_FILE_PATH, C as CLAUDE_PROMPTS_PATH, D as CLAUDE_RENDER_MODES, E as CLAUDE_PROSE_MODES, F as CLAUDE_UPDATE_CHECK_PATH, H as isClaudeAlertMode, I as CLAUDE_UPDATE_PATH, L as CLAUDE_USAGE_PATH, M as CLAUDE_REPOSITORY_STATUS_PATH, N as CLAUDE_REVIEW_COMMENT_PATH, O as CLAUDE_REPOSITORY_ACTION_PATH, P as CLAUDE_REWIND_PATH, R as DEFAULT_CLAUDE_PROSE_MODE, S as CLAUDE_PROJECTION_PATH, T as CLAUDE_PROMPT_REFINE_PATH, U as isClaudeProseMode, V as TASK_TOOL_NAMES, W as isClaudeRenderMode, _ 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_SETUP_PATH, k as CLAUDE_REPOSITORY_FEEDBACK_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_PROMPT_NAME_PATH, x as CLAUDE_PLAN_FEEDBACK_PATH, y as CLAUDE_GLOBAL_SETTINGS_PATH, z as DEFAULT_CLAUDE_RENDER_MODE } from "./events-B498uofA.mjs";
2
- import { a as projectClaudeCommands, i as CLAUDE_COMMANDS_SERVICE, r as dynamicPresenterDefinition, t as CLAUDE_PRESENTER_NAMES } from "./presenters-CRFcjLSC.mjs";
3
- import { a as resolveClaudeExecutable, n as ensureManagedPreset, o as runClaudeDoctor, t as ManagedPresetConflictError } from "./preset-installer-Cj637ucf.mjs";
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$14(value) {
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$14(value);
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$14(value);
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$14(value);
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$14(value);
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$14(item);
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$14(item);
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$14(item);
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$14(input.pending);
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$14(value);
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$14(value);
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 prompt = options.title ?? options.description ?? options.decisionReason ?? `Claude Code wants to use ${toolName}.`;
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$13(value) {
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$13(message.usage));
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$13(message.message);
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$13(item);
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$13(envelope?.usage));
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$13(message.message)?.content;
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$13(item);
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$13(message.usage));
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$13(message.patch);
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$13(message.usage));
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$13(item);
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$13(message.compact_metadata);
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$13(value.event);
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$13(event.delta);
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$13(item)).filter((item) => item !== void 0).map((item) => {
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$13(value.rate_limit_info)?.status);
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$12(value) {
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$12(source);
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$12(value);
1744
+ const response = record$13(value);
1664
1745
  const subscription = typeof response?.subscription_type === "string" ? response.subscription_type : void 0;
1665
- const limits = record$12(response?.rate_limits);
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$12(entry)?.display_name;
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 mode = claudePermissionMode(entry.ownerAgent.session.snapshotEvents());
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 = claudePermissionMode(agent.session.snapshotEvents());
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$6 = 1024;
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$6;
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([repositoryForSession(sessionId), extraRepositoriesForSession(sessionId, activities ?? (await sidecar.read(sessionId)).activities)]);
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$11(value) {
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$11(item);
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$11(value);
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$11(input.author)?.login === "string" ? { author: bounded(String(record$11(input.author)?.login)) } : {},
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$11(raw);
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 rather than which files it touches.`,
6127
- "If the diff contains more than one independent change, leave line 2 blank and then list each change on its own line starting with \"- \", one sentence each, describing what changed as the diff shows it.",
6128
- "A change with a single purpose gets the subject line only.",
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
- "List every independent change the diff contains as its own bullet. Do not merge unrelated changes into one bullet, and do not describe anything the diff does not show.",
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$7 = 16384;
6642
- function record$10(value) {
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$6(io) {
6738
+ async function readJson$7(io) {
6646
6739
  let parsed;
6647
6740
  try {
6648
- parsed = await io.body(MAX_BODY_BYTES$7);
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$10(parsed);
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$6(io);
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$6(io);
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$6(io);
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$6(io);
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$6 = 16384;
6774
- const MAX_SESSION_ID_CHARS$5 = 1024;
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$9(value) {
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$5(io) {
6890
+ async function readJson$6(io) {
6798
6891
  let parsed;
6799
6892
  try {
6800
- parsed = await io.body(MAX_BODY_BYTES$6);
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$9(parsed);
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$5) throw new RepositoryActionError("invalid-session", "The session is invalid.");
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$5(io);
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$5(io);
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$5(io)))
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$8(value) {
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$8(record$8(record$8(record$8(record$8(value)?.data)?.repository)?.pullRequest)?.reviewThreads)?.nodes;
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$8(item);
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$8(input.comments)?.nodes;
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$8(node);
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$8(comment.author);
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$8(value);
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$8(input.user);
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$8(record$8(record$8(record$8(value)?.data)?.repository)?.mentionableUsers)?.nodes;
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$8(item);
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$8(item);
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$8(parsed)?.path === "string" ? String(record$8(parsed)?.path) : "";
7534
- const line = Number.isSafeInteger(record$8(parsed)?.line) ? Number(record$8(parsed)?.line) : void 0;
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$8(parsed)?.side === "LEFT" ? "old" : "new"
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$8(record$8(record$8(record$8(JSON.parse(result.stdout))?.data)?.[resolved ? "resolveReviewThread" : "unresolveReviewThread"])?.thread);
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$4 = 1024;
7683
- const MAX_BODY_BYTES$5 = 16384;
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$7(value) {
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$4(io) {
7783
+ async function readJson$5(io) {
7691
7784
  let parsed;
7692
7785
  try {
7693
- parsed = await io.body(MAX_BODY_BYTES$5);
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$7(parsed);
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$4) throw new PullRequestFeedbackError("invalid-session", "The session is invalid.");
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$4(io);
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$4(io);
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$6(value) {
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$6(value)?.sections;
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$6(section)?.issues;
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$6(issue)?.key;
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$6(value)?.issues;
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$6(item);
7990
- const fields = record$6(issue?.fields);
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$6(fields.status)?.name;
7993
- const type = record$6(fields.issuetype)?.name;
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$6(await this.#json(connection, "/rest/api/3/myself"));
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$6(await this.#json(store, "/rest/api/3/myself"));
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$6(JSON.parse(text));
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$4 = 8192;
8162
- function record$5(value) {
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$3(io) {
8260
+ async function readJson$4(io) {
8168
8261
  let body;
8169
8262
  try {
8170
- body = await io.body(MAX_BODY_BYTES$4);
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$5(body);
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$3(io);
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$3(io);
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$4(value) {
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$4(input);
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$4(JSON.parse(line));
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$4(parsed.event);
8476
+ const event = record$5(parsed.event);
8384
8477
  if (event?.type === "content_block_start") {
8385
- const block = record$4(event.content_block);
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$4(event?.delta);
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$4(parsed.message)?.content;
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$4(item);
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$3 = 131072;
8504
- const MAX_SESSION_ID_CHARS$3 = 1024;
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$3(value) {
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$3) throw new AskError("invalid-session", "The session is invalid.");
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$3(await io.body(MAX_BODY_BYTES$3));
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$2 = 16384;
8585
- const MAX_SESSION_ID_CHARS$2 = 1024;
8586
- function record$2(value) {
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$2(io) {
8682
+ async function readJson$3(io) {
8590
8683
  let parsed;
8591
8684
  try {
8592
- parsed = await io.body(MAX_BODY_BYTES$2);
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$2(parsed);
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$2) throw new ReviewCommentError("invalid-session", "The session is invalid.");
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$2(io);
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$2(io);
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$1 = 65536;
8673
- const MAX_SESSION_ID_CHARS$1 = 1024;
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$1(value) {
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$1(io) {
8771
+ async function readJson$2(io) {
8679
8772
  let parsed;
8680
8773
  try {
8681
- parsed = await io.body(MAX_BODY_BYTES$1);
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$1(parsed);
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$1) return {
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$1(io);
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
- const mount = (agent) => {
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